a789495a98
FreeBSD Smoke / FreeBSD Smoke (x86_64) (push) Has been cancelled
CI / Quality Guardrails (push) Has been cancelled
CI / Build & Test (macos-latest) (push) Has been cancelled
CI / Build & Test (ubuntu-latest) (push) Has been cancelled
CI / Build & Test (windows-latest) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / PowerShell Syntax (push) Has been cancelled
CI / Windows Cross-Target Check (Linux) (push) Has been cancelled
409 lines
13 KiB
Python
Executable File
409 lines
13 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Benchmark single agent vs swarm on the Anthropic Performance Take-Home.
|
|
|
|
Usage:
|
|
BENCHMARK_TIMEOUT=5 python scripts/benchmark_takehome.py single
|
|
BENCHMARK_TIMEOUT=10 python scripts/benchmark_takehome.py swarm
|
|
BENCHMARK_TIMEOUT=10 python scripts/benchmark_takehome.py both
|
|
"""
|
|
|
|
import socket
|
|
import json
|
|
import os
|
|
import sys
|
|
import time
|
|
import select
|
|
import shutil
|
|
import subprocess
|
|
import threading
|
|
from pathlib import Path
|
|
|
|
DEBUG_SOCKET = f"/run/user/{os.getuid()}/jcode-debug.sock"
|
|
TAKEHOME_SOURCE = os.environ.get(
|
|
"TAKEHOME_SOURCE", str(Path.home() / "original_performance_takehome")
|
|
)
|
|
BENCHMARK_DIR = "/tmp/takehome-benchmark"
|
|
TIMEOUT_MINUTES = int(os.environ.get('BENCHMARK_TIMEOUT', '10'))
|
|
BASELINE = 147734
|
|
|
|
|
|
def send_cmd(cmd: str, session_id: str = None, timeout: float = 300) -> tuple:
|
|
"""Send a debug command and get response."""
|
|
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
sock.connect(DEBUG_SOCKET)
|
|
sock.setblocking(False)
|
|
|
|
req = {"type": "debug_command", "id": 1, "command": cmd}
|
|
if session_id:
|
|
req["session_id"] = session_id
|
|
|
|
sock.send((json.dumps(req) + '\n').encode())
|
|
|
|
start = time.time()
|
|
data = b""
|
|
while time.time() - start < timeout:
|
|
ready, _, _ = select.select([sock], [], [], 1.0)
|
|
if ready:
|
|
try:
|
|
chunk = sock.recv(65536)
|
|
if not chunk:
|
|
break
|
|
data += chunk
|
|
if b'\n' in data:
|
|
break
|
|
except BlockingIOError:
|
|
continue
|
|
|
|
sock.close()
|
|
|
|
if not data:
|
|
return False, "", "Timeout"
|
|
|
|
try:
|
|
resp = json.loads(data.decode().strip())
|
|
return resp.get('ok', False), resp.get('output', ''), resp.get('error', '')
|
|
except json.JSONDecodeError as e:
|
|
return False, "", f"JSON error: {e}"
|
|
|
|
|
|
def create_session(working_dir: str) -> tuple:
|
|
"""Create a session and return (session_id, friendly_name)."""
|
|
ok, output, err = send_cmd(f"create_session:{working_dir}", timeout=120)
|
|
if not ok:
|
|
raise RuntimeError(f"Failed to create session: {err}")
|
|
data = json.loads(output)
|
|
return data['session_id'], data.get('friendly_name', data['session_id'][:12])
|
|
|
|
|
|
def destroy_session(session_id: str):
|
|
"""Destroy a session."""
|
|
send_cmd(f"destroy_session:{session_id}")
|
|
|
|
|
|
def setup_workspace(name: str) -> str:
|
|
"""Create a clean copy of the take-home."""
|
|
workspace = os.path.join(BENCHMARK_DIR, name)
|
|
if os.path.exists(workspace):
|
|
shutil.rmtree(workspace)
|
|
shutil.copytree(TAKEHOME_SOURCE, workspace)
|
|
return workspace
|
|
|
|
|
|
def get_cycles(workspace: str) -> int:
|
|
"""Run tests and return cycle count."""
|
|
try:
|
|
result = subprocess.run(
|
|
["python", "tests/submission_tests.py", "-v"],
|
|
cwd=workspace,
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=120
|
|
)
|
|
for line in (result.stdout + result.stderr).split('\n'):
|
|
if 'CYCLES:' in line:
|
|
return int(line.split('CYCLES:')[1].strip())
|
|
except Exception as e:
|
|
print(f"Error getting cycles: {e}")
|
|
return BASELINE
|
|
|
|
|
|
def make_single_prompt(workspace: str) -> str:
|
|
return f"""You are optimizing a VLIW SIMD kernel for Anthropic's performance take-home.
|
|
|
|
IMPORTANT: You MUST work in this directory: {workspace}
|
|
All file paths should be relative to or within this directory.
|
|
|
|
Goal: Reduce the cycle count from 147,734 to as low as possible.
|
|
|
|
Key files (in {workspace}):
|
|
- problem.py: Defines the Machine, instruction set (VLIW bundles, vector ops, VLEN=16)
|
|
- perf_takehome.py: Contains KernelBuilder.build_kernel() - this is what you optimize
|
|
- tests/submission_tests.py: Run to verify correctness and see cycle count
|
|
|
|
DO NOT modify tests/ folder.
|
|
|
|
Key optimizations to try:
|
|
1. VLIW parallelism - pack independent operations into single bundles
|
|
2. Vector operations - use VLEN=16 to process 16 elements at once
|
|
3. Reduce memory access latency - batch loads/stores
|
|
4. Optimize the hash function - it runs many times per element
|
|
|
|
Start by reading {workspace}/problem.py to understand the machine, then optimize build_kernel().
|
|
After each change, run `cd {workspace} && python tests/submission_tests.py` to check correctness and cycles.
|
|
|
|
Work efficiently - focus on the highest-impact optimizations first."""
|
|
|
|
|
|
def run_single_agent() -> dict:
|
|
"""Run a single agent benchmark using async messaging."""
|
|
print("\n" + "=" * 60)
|
|
print(f"SINGLE AGENT BENCHMARK (timeout: {TIMEOUT_MINUTES}m)")
|
|
print("=" * 60)
|
|
|
|
workspace = setup_workspace("single")
|
|
print(f"Workspace: {workspace}")
|
|
|
|
start_time = time.time()
|
|
session_id = None
|
|
best_cycles = BASELINE
|
|
|
|
try:
|
|
session_id, name = create_session(workspace)
|
|
print(f"Session: {name}")
|
|
|
|
# Initial cycles
|
|
cycles = get_cycles(workspace)
|
|
print(f"Baseline: {cycles} cycles")
|
|
|
|
# Send optimization task asynchronously
|
|
print("\nStarting optimization (async)...")
|
|
prompt = make_single_prompt(workspace)
|
|
|
|
# Use message_async to start the job
|
|
ok, output, err = send_cmd(f"message_async:{prompt}", session_id, timeout=30)
|
|
if not ok:
|
|
print(f"Failed to start async job: {err}")
|
|
return {"approach": "single", "cycles": BASELINE, "time_seconds": 0, "error": err}
|
|
|
|
job_data = json.loads(output)
|
|
job_id = job_data.get("job_id")
|
|
print(f"Job started: {job_id}")
|
|
|
|
timeout_seconds = TIMEOUT_MINUTES * 60
|
|
last_cycles = BASELINE
|
|
check_interval = 30 # Check every 30 seconds
|
|
|
|
while time.time() - start_time < timeout_seconds:
|
|
elapsed = time.time() - start_time
|
|
|
|
# Check job status
|
|
ok, status_output, _ = send_cmd(f"job_status:{job_id}", session_id, timeout=10)
|
|
if ok:
|
|
try:
|
|
status = json.loads(status_output)
|
|
job_status = status.get("status", "unknown")
|
|
if job_status in ["completed", "failed"]:
|
|
print(f"\n[{elapsed/60:.1f}m] Job {job_status}")
|
|
break
|
|
except:
|
|
pass
|
|
|
|
# Check current cycles in workspace
|
|
cycles = get_cycles(workspace)
|
|
if cycles < best_cycles:
|
|
best_cycles = cycles
|
|
print(f"[{elapsed/60:.1f}m] NEW BEST: {cycles} cycles ({BASELINE/cycles:.2f}x speedup)")
|
|
elif cycles != last_cycles:
|
|
print(f"[{elapsed/60:.1f}m] Cycles: {cycles}")
|
|
last_cycles = cycles
|
|
|
|
time.sleep(check_interval)
|
|
|
|
# Final check
|
|
cycles = get_cycles(workspace)
|
|
if cycles < best_cycles:
|
|
best_cycles = cycles
|
|
|
|
elapsed = time.time() - start_time
|
|
print(f"\nFinal: {best_cycles} cycles in {elapsed/60:.1f}m ({BASELINE/best_cycles:.2f}x)")
|
|
|
|
return {
|
|
"approach": "single",
|
|
"cycles": best_cycles,
|
|
"time_seconds": elapsed,
|
|
"workspace": workspace
|
|
}
|
|
|
|
except Exception as e:
|
|
print(f"Error: {e}")
|
|
return {
|
|
"approach": "single",
|
|
"cycles": BASELINE,
|
|
"time_seconds": time.time() - start_time,
|
|
"error": str(e)
|
|
}
|
|
finally:
|
|
if session_id:
|
|
destroy_session(session_id)
|
|
|
|
|
|
def run_swarm(n_agents: int = 2) -> dict:
|
|
"""Run autonomous swarm benchmark using swarm_message_async.
|
|
|
|
This uses the full swarm capability where ONE agent becomes coordinator,
|
|
creates a plan, and spawns subagents automatically.
|
|
"""
|
|
print("\n" + "=" * 60)
|
|
print(f"AUTONOMOUS SWARM BENCHMARK (timeout: {TIMEOUT_MINUTES}m)")
|
|
print("=" * 60)
|
|
|
|
workspace = setup_workspace("swarm")
|
|
print(f"Workspace: {workspace}")
|
|
|
|
start_time = time.time()
|
|
session_id = None
|
|
best_cycles = BASELINE
|
|
|
|
try:
|
|
# Create ONE session - it becomes coordinator and spawns agents
|
|
session_id, name = create_session(workspace)
|
|
print(f"Coordinator: {name}")
|
|
|
|
baseline = get_cycles(workspace)
|
|
print(f"Baseline: {baseline} cycles")
|
|
|
|
# Use swarm_message_async - this will:
|
|
# 1. Plan subtasks automatically
|
|
# 2. Spawn subagents to work in parallel
|
|
# 3. Integrate results
|
|
prompt = f"""Optimize the VLIW SIMD kernel in {workspace}/perf_takehome.py to minimize cycle count.
|
|
|
|
Current baseline: 147,734 cycles. Goal: as low as possible.
|
|
|
|
The problem:
|
|
- {workspace}/problem.py defines the machine (VLEN=16 vectors, VLIW bundles, slot limits)
|
|
- {workspace}/perf_takehome.py has build_kernel() which needs optimization
|
|
- Run `cd {workspace} && python tests/submission_tests.py` to verify correctness and check cycles
|
|
|
|
Key optimization strategies:
|
|
1. Vectorization - use VLEN=16 to process 16 elements at once
|
|
2. VLIW packing - bundle independent operations together
|
|
3. Reduce memory latency - batch loads/stores
|
|
4. Optimize hash function - it runs many times per element
|
|
|
|
Break this into parallel subtasks and spawn agents to work on different optimizations.
|
|
DO NOT modify tests/ folder."""
|
|
|
|
print("\nStarting autonomous swarm (swarm_message_async)...")
|
|
ok, output, err = send_cmd(f"swarm_message_async:{prompt}", session_id, timeout=30)
|
|
if not ok:
|
|
print(f"Failed to start swarm: {err}")
|
|
return {"approach": "swarm", "cycles": BASELINE, "time_seconds": 0, "error": err}
|
|
|
|
job_data = json.loads(output)
|
|
job_id = job_data.get("job_id")
|
|
print(f"Swarm job started: {job_id}")
|
|
|
|
timeout_seconds = TIMEOUT_MINUTES * 60
|
|
last_cycles = BASELINE
|
|
check_interval = 30
|
|
|
|
while time.time() - start_time < timeout_seconds:
|
|
elapsed = time.time() - start_time
|
|
|
|
# Check job status
|
|
ok, status_output, _ = send_cmd(f"job_status:{job_id}", session_id, timeout=10)
|
|
if ok:
|
|
try:
|
|
status = json.loads(status_output)
|
|
job_status = status.get("status", "unknown")
|
|
if job_status == "completed":
|
|
print(f"\n[{elapsed/60:.1f}m] Swarm completed!")
|
|
break
|
|
elif job_status == "failed":
|
|
print(f"\n[{elapsed/60:.1f}m] Swarm failed: {status.get('error', 'unknown')}")
|
|
break
|
|
except:
|
|
pass
|
|
|
|
# Check swarm members (to see how many agents were spawned)
|
|
ok, swarm_output, _ = send_cmd("swarm:members", session_id, timeout=10)
|
|
if ok and elapsed < 60: # Only print once early on
|
|
try:
|
|
print(f"[{elapsed/60:.1f}m] Swarm: {swarm_output[:100]}...")
|
|
except:
|
|
pass
|
|
|
|
# Check current cycles
|
|
cycles = get_cycles(workspace)
|
|
if cycles < best_cycles:
|
|
best_cycles = cycles
|
|
print(f"[{elapsed/60:.1f}m] NEW BEST: {cycles} cycles ({BASELINE/cycles:.2f}x)")
|
|
elif cycles != last_cycles:
|
|
print(f"[{elapsed/60:.1f}m] Cycles: {cycles}")
|
|
last_cycles = cycles
|
|
|
|
time.sleep(check_interval)
|
|
|
|
# Final check
|
|
cycles = get_cycles(workspace)
|
|
if cycles < best_cycles:
|
|
best_cycles = cycles
|
|
|
|
elapsed = time.time() - start_time
|
|
print(f"\nFinal: {best_cycles} cycles in {elapsed/60:.1f}m ({BASELINE/best_cycles:.2f}x)")
|
|
|
|
return {
|
|
"approach": "swarm",
|
|
"cycles": best_cycles,
|
|
"time_seconds": elapsed,
|
|
"workspace": workspace
|
|
}
|
|
|
|
except Exception as e:
|
|
print(f"Error: {e}")
|
|
return {
|
|
"approach": "swarm",
|
|
"cycles": BASELINE,
|
|
"time_seconds": time.time() - start_time,
|
|
"error": str(e)
|
|
}
|
|
finally:
|
|
if session_id:
|
|
destroy_session(session_id)
|
|
|
|
|
|
def print_results(results: dict):
|
|
"""Print comparison table."""
|
|
print("\n" + "=" * 60)
|
|
print("RESULTS")
|
|
print("=" * 60)
|
|
print(f"{'Approach':<15} {'Cycles':<12} {'Time':<10} {'Speedup':<10}")
|
|
print("-" * 60)
|
|
|
|
for name, data in results.items():
|
|
cycles = data['cycles']
|
|
time_m = data['time_seconds'] / 60
|
|
speedup = BASELINE / cycles
|
|
print(f"{name:<15} {cycles:<12} {time_m:<10.1f}m {speedup:<10.2f}x")
|
|
|
|
if len(results) > 1:
|
|
winner = min(results.items(), key=lambda x: x[1]['cycles'])
|
|
print(f"\nWinner: {winner[0]} ({winner[1]['cycles']} cycles)")
|
|
|
|
|
|
def main():
|
|
if len(sys.argv) < 2:
|
|
print(__doc__)
|
|
sys.exit(1)
|
|
|
|
mode = sys.argv[1].lower()
|
|
os.makedirs(BENCHMARK_DIR, exist_ok=True)
|
|
|
|
print(f"Benchmark timeout: {TIMEOUT_MINUTES} minutes per approach")
|
|
print(f"Set BENCHMARK_TIMEOUT env var to change (e.g., BENCHMARK_TIMEOUT=30)")
|
|
|
|
if mode == "single":
|
|
r = run_single_agent()
|
|
print_results({"single": r})
|
|
|
|
elif mode == "swarm":
|
|
r = run_swarm()
|
|
print_results({"swarm": r})
|
|
|
|
elif mode == "both":
|
|
results = {}
|
|
results['single'] = run_single_agent()
|
|
results['swarm'] = run_swarm()
|
|
print_results(results)
|
|
|
|
else:
|
|
print(f"Unknown mode: {mode}")
|
|
print(__doc__)
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|