sora 370953729b Fix perf stats (wrong import path), per-repeat checkpoints, README
- perf_stats aggregator lives in eval/, not model/: the import failed
  silently and EVERY perf column was empty (not just ttft). Now warns
  on stderr instead of swallowing.
- repeats > 1 get their own checkpoint key (:rep2, :rep3, ...): repeat 2
  previously restored repeat 1's predictions and finished instantly with
  identical scores. rep1 keeps the legacy key (existing checkpoints still
  resume).
- repeats summary: report the MEAN score and aggregate time/tokens over
  ALL runs (was: last run only).
- README: six-benchmark command as the primary example.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-09-11 13:38:04 +00:00

169 lines
6.1 KiB
Python

"""Docker sandbox: one implementation serving both consumers.
exec(): untrusted model-generated code — hard isolation
(--network none, cpu/mem/pids caps, read-only rootfs, tmpfs /tmp;
writes escape only through explicit bind mounts)
serve(): trusted engine containers (vllm/sglang) — network ON (weights pull),
GPU passthrough; consumed by the model Deployer via acquire()
"""
import os
import shlex
import subprocess
import tempfile
import time
from pathlib import Path
from typing import Any, Dict, List, Optional
from .base import EnvHandle, ExecResult, Sandbox, acquire, register_sandbox
def _run(cmd: List[str], **kw) -> subprocess.CompletedProcess:
return subprocess.run(cmd, capture_output=True, text=True, **kw)
def docker_available() -> bool:
return _run(['docker', 'info']).returncode == 0
@register_sandbox('docker')
class DockerSandbox(Sandbox):
name = 'docker'
DEFAULT_EXEC_IMAGE = 'python:3.11-slim'
def exec(
self,
files: Dict[str, str],
entry: str = 'main.py',
mounts: Optional[Dict[str, str]] = None,
timeout_s: int = 60,
image: str = '',
) -> ExecResult:
img = image or self.DEFAULT_EXEC_IMAGE
with tempfile.TemporaryDirectory(prefix='eh-sbx-') as host_dir:
workdir = Path(host_dir) / 'work'
workdir.mkdir()
for fname, content in (files or {}).items():
dest = workdir / fname
dest.parent.mkdir(parents=True, exist_ok=True)
dest.write_text(content, encoding='utf-8')
cmd = [
'docker', 'run', '--rm',
'--network', 'none', # untrusted code: no egress
'--cpus', '2', '--memory', '2g', '--pids-limit', '256',
'--read-only', '--tmpfs', '/tmp:rw,size=64m',
# /work must be writable: BigCodeBench tasks write output
# files (task_func_data/, matplotlib caches, etc.) to cwd;
# the official Evaluate.Dockerfile runs with a writable fs
'-v', f'{workdir}:/work:rw',
]
out_host = None
if mounts:
for cpath, hpath in mounts.items():
out_host = Path(hpath).expanduser()
out_host.mkdir(parents=True, exist_ok=True)
cmd += ['-v', f'{out_host}:{cpath}:rw']
runner = ['python', f'/work/{entry}'] if entry.endswith('.py') \
else ['sh', f'/work/{entry}']
cmd += [img, *runner]
t0 = time.time()
try:
proc = _run(cmd, timeout=timeout_s + 30)
except subprocess.TimeoutExpired:
return ExecResult(exit_code=-1, timed_out=True, duration_s=timeout_s,
error=f'sandbox timeout after {timeout_s}s')
return ExecResult(
exit_code=proc.returncode,
stdout=proc.stdout,
stderr=proc.stderr,
duration_s=round(time.time() - t0, 2),
)
# ---------------- serve-side (model deployment environments) ----------------
def docker_serve(
engine: str,
model: str,
cfg: Dict[str, Any],
default_image: str,
engine_args: List[str],
) -> Dict[str, str]:
"""Start (or reuse) an OpenAI-protocol serving container.
Used through sandbox.acquire() by the model Deployer — the deployment
container is just another environment this layer provides. Bind-mounts
the HF cache (weights stay on the host; containers come and go).
"""
import socket
image = cfg.get('image', default_image)
port = int(cfg.get('port', 0))
if not port:
with socket.socket() as s:
s.bind(('', 0))
port = s.getsockname()[1]
hf = cfg.get('hf_home', os.environ.get('HF_HOME', '~/.cache/huggingface'))
gpus = str(cfg.get('gpus', 'all'))
container = f'evalharness-{engine}-{model}'.replace('/', '-')[:120]
_run(['docker', 'rm', '-f', container]) # stale instance from a crashed run
cmd = [
'docker', 'run', '-d',
'--name', container,
'--gpus', f'device={gpus}' if gpus.isdigit() else gpus,
'--network', 'host',
'-v', f'{Path(hf).expanduser()}:/root/.cache/huggingface',
'-e', f'HF_ENDPOINT={os.environ.get("HF_ENDPOINT", "https://hf-mirror.com")}',
image,
'--model', cfg.get('model_id', cfg.get('model', model)),
'--served-model-name', model,
'--port', str(port),
*engine_args,
*shlex.split(cfg.get('extra_args', '')),
]
for key, flag in (('gpu_mem_util', '--gpu-memory-utilization'),
('max_model_len', '--max-model-len'),
('tp_size', '--tensor-parallel-size'),
('dtype', '--dtype')):
if cfg.get(key):
cmd += [flag, str(cfg[key])]
r = _run(cmd)
if r.returncode != 0:
raise RuntimeError(f'docker run failed: {r.stderr[:500]}')
api_base = f'http://localhost:{port}/v1'
_wait_healthy(api_base, int(cfg.get('timeout_s', 1800)))
return {'api_base': api_base, 'model': model, 'container': container, 'port': str(port)}
def _wait_healthy(api_base: str, timeout_s: int) -> None:
import urllib.request
deadline = time.time() + timeout_s
while time.time() < deadline:
try:
with urllib.request.urlopen(f'{api_base}/models', timeout=5) as resp:
if resp.status == 200:
return
except Exception:
time.sleep(5)
raise TimeoutError(f'serving engine not healthy after {timeout_s}s at {api_base}')
def docker_stop(handle: EnvHandle) -> None:
if handle.container:
_run(['docker', 'rm', '-f', handle.container])
def serve_env(engine: str, model: str, cfg: Dict[str, Any],
default_image: str, engine_args: Optional[List[str]] = None) -> EnvHandle:
"""acquire() wrapper: shared, refcounted serve environment."""
return acquire(
kind=engine,
name=model,
start_fn=lambda _m: docker_serve(engine, _m, cfg, default_image, engine_args or []),
stop_fn=docker_stop,
)