- AdaptiveGate rewritten (Netflix Gradient2): window-vs-window per-stream speed gradient, count-driven windows with admission stamps, no thresholds or mode state machine; failures x0.7 + 30s drain pause - session-level admission for multi-turn agents (_SessionGate): in-progress sessions hold slots until done, newcomers queue at the door; capacity follows the model gate's discovered limit (CONCUR-style continuity) - image service: memory-first register (zero docker calls for known images), TTL-cached docker images listing, optimistic ready when the daemon is unreachable (docker save contention no longer kills runs); es tar loading removed in favor of ModelScope shipping (ms_images.py per-image tar upload/pull with round-trip verification) - runner: circuit breaker (12 consecutive failures abort the bench), first-failure error printed immediately - swe_agentic: image wait / docker run / rm off the event loop; exec timeout becomes an observation the agent can react to; container gets curlrc + git low-speed aborts (stalled github downloads fail fast) - eval run excludes its own endpoints from http_proxy (a sick personal proxy read as 'endpoint dead' and killed whole runs) - progress bar shows failed count; swe agentic exec_workers 2 -> 4 Co-Authored-By: Claude <noreply@anthropic.com>
215 lines
7.9 KiB
Python
215 lines
7.9 KiB
Python
"""ModelScope-hosted sandbox images: per-image tar upload + download.
|
|
|
|
registry.modelscope.cn does not accept personal docker pushes, so images
|
|
ship as ONE docker-save tar per image inside a ModelScope MODEL repo
|
|
(file hosting with a stable CN CDN -- measured ~12MB/s anonymous; the
|
|
per-task granularity means running 3 instances downloads exactly 3 tars).
|
|
This is the only working network source for the swebench/* namespace --
|
|
public CN mirrors (daocloud/1ms/...) 403 it.
|
|
|
|
NB: it must be a MODEL repo, not a dataset repo: upload_file defaults to
|
|
repo_type='model' and silently CREATES one when given a dataset repo_id
|
|
(the first upload landed in a phantom model repo while the dataset repo
|
|
stayed empty -- confusing 404s on pull).
|
|
|
|
Upload (resumable; skips tars already on the remote):
|
|
EVALHARNESS_MS_TOKEN=ms-... python -m evalharness.sandbox.ms_images \\
|
|
upload --repo SoraAmami/swebench-images [--limit N]
|
|
|
|
Download side is wired into ImageService._pull_one (first source, before
|
|
the docker-hub mirror chain) whenever EVALHARNESS_MS_IMAGE_REPO is set
|
|
(defaults on for this deployment; empty string disables).
|
|
"""
|
|
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import time
|
|
from typing import List, Optional, Set
|
|
|
|
# a private-repo token for downloads (public repos work without it)
|
|
_MS_TOKEN = os.environ.get('EVALHARNESS_MS_TOKEN', '').strip()
|
|
_MS_ENDPOINT = os.environ.get('EVALHARNESS_MS_ENDPOINT', 'https://modelscope.cn')
|
|
# big enough for a ~4GB uncompressed save; / has less headroom than /data1
|
|
_TMP_ROOT = os.environ.get('EVALHARNESS_MS_TMP', '/data1/sora/temp/ms_images')
|
|
_STATE = os.path.join(_TMP_ROOT, 'uploaded.txt') # resume manifest
|
|
|
|
# image prefixes this deployment ships (swe per-instance + code benches)
|
|
_DEFAULT_PREFIXES = ('swebench/',)
|
|
|
|
|
|
def _docker(args, timeout=1800):
|
|
return subprocess.run(['docker'] + args, capture_output=True, text=True,
|
|
timeout=timeout)
|
|
|
|
|
|
def file_for(image: str) -> str:
|
|
"""Image ref -> dataset filename: last path segment minus tag.
|
|
|
|
swebench/sweb.eval.x86_64.django_1776_django-13964:latest
|
|
-> sweb.eval.x86_64.django_1776_django-13964.tar.gz
|
|
"""
|
|
base = image.rsplit('/', 1)[-1]
|
|
base = base.split(':')[0]
|
|
return f'{base}.tar.gz'
|
|
|
|
|
|
def image_from_file(fname: str) -> Optional[str]:
|
|
"""Inverse of file_for for the swe namespace (namespace is not
|
|
recoverable in general; callers here only ship swebench/*)."""
|
|
if not fname.endswith('.tar.gz'):
|
|
return None
|
|
return f"swebench/{fname[:-len('.tar.gz')]}:latest"
|
|
|
|
|
|
def _local_images() -> Set[str]:
|
|
r = _docker(['images', '--format', '{{.Repository}}:{{.Tag}}'], timeout=60)
|
|
return set(r.stdout.split()) if r.returncode == 0 else set()
|
|
|
|
|
|
def _remote_files(repo: str) -> Set[str]:
|
|
"""Files currently in the MODEL repo (resume manifest source of truth)."""
|
|
from modelscope.hub.api import HubApi
|
|
|
|
api = HubApi()
|
|
if _MS_TOKEN:
|
|
api.login(_MS_TOKEN)
|
|
files: Set[str] = set()
|
|
for e in api.get_model_files(repo, recursive=True):
|
|
p = (e.get('Path') or e.get('Name') or '').lstrip('/')
|
|
if p.endswith('.tar.gz'):
|
|
files.add(p)
|
|
return files
|
|
|
|
|
|
def _upload_one(api, repo: str, image: str, tmp_dir: str) -> bool:
|
|
tar = os.path.join(tmp_dir, file_for(image))
|
|
os.makedirs(tmp_dir, exist_ok=True)
|
|
# gzip -1: ~2x faster than default for ~10% more bytes -- upload wall
|
|
# time is dominated by docker save IO, not the extra size
|
|
r = subprocess.run(f'docker save {image} | gzip -1 > {tar}',
|
|
shell=True, timeout=3600)
|
|
if r.returncode != 0 or not os.path.exists(tar):
|
|
print(f' save failed: {image}', flush=True)
|
|
return False
|
|
try:
|
|
api.upload_file(repo_id=repo, path_or_fileobj=tar,
|
|
path_in_repo=file_for(image),
|
|
repo_type='model', token=_MS_TOKEN or None)
|
|
with open(_STATE, 'a') as f:
|
|
f.write(file_for(image) + '\n')
|
|
gb = os.path.getsize(tar) / 1e9
|
|
print(f' uploaded {file_for(image)} ({gb:.2f} GB)', flush=True)
|
|
return True
|
|
except Exception as e:
|
|
print(f' upload failed {image}: {type(e).__name__}: {str(e)[:120]}',
|
|
flush=True)
|
|
return False
|
|
finally:
|
|
try:
|
|
os.unlink(tar)
|
|
except OSError:
|
|
pass
|
|
|
|
|
|
def upload(repo: str, limit: int = 0, prefixes=None) -> None:
|
|
"""Ship every local matching image to the MODEL repo. Resumable:
|
|
already-remote tars are skipped (state file + remote listing)."""
|
|
from modelscope.hub.api import HubApi
|
|
|
|
os.makedirs(_TMP_ROOT, exist_ok=True)
|
|
api = HubApi()
|
|
if _MS_TOKEN:
|
|
api.login(_MS_TOKEN)
|
|
try:
|
|
api.get_model(repo)
|
|
except Exception:
|
|
print(f'creating model repo {repo} ...', flush=True)
|
|
api.create_model(model_id=repo)
|
|
done = set()
|
|
try:
|
|
done = {l.strip() for l in open(_STATE) if l.strip()}
|
|
except OSError:
|
|
pass
|
|
print(f'remote listing {repo} ...', flush=True)
|
|
try:
|
|
done |= _remote_files(repo)
|
|
except Exception as e:
|
|
print(f' remote listing failed ({e}); relying on local state',
|
|
flush=True)
|
|
prefixes = prefixes or _DEFAULT_PREFIXES
|
|
imgs = sorted(i for i in _local_images() if i.startswith(prefixes))
|
|
if limit:
|
|
imgs = imgs[:limit]
|
|
todo = [i for i in imgs if file_for(i) not in done]
|
|
print(f'{len(imgs)} local images, {len(todo)} to upload '
|
|
f'({len(imgs) - len(todo)} already remote)', flush=True)
|
|
ok = 0
|
|
for n, img in enumerate(todo, 1):
|
|
print(f'[{n}/{len(todo)}] {img}', flush=True)
|
|
for attempt in range(3):
|
|
if _upload_one(api, repo, img, _TMP_ROOT):
|
|
ok += 1
|
|
break
|
|
time.sleep(10 * (attempt + 1)) # hub hiccups: backoff + retry
|
|
print(f'done: {ok}/{len(todo)} uploaded', flush=True)
|
|
|
|
|
|
def ms_pull(image: str, repo: str) -> bool:
|
|
"""Fetch one image tar from the dataset repo and docker-load it.
|
|
Returns True iff the image is local afterwards."""
|
|
import urllib.request
|
|
|
|
fname = file_for(image)
|
|
url = (f'{_MS_ENDPOINT}/api/v1/models/{repo}/repo'
|
|
f'?Revision=master&FilePath={fname}')
|
|
hdrs = {'User-Agent': 'evalharness'}
|
|
if _MS_TOKEN:
|
|
hdrs['Authorization'] = f'Bearer {_MS_TOKEN}'
|
|
os.makedirs(_TMP_ROOT, exist_ok=True)
|
|
fd, tar = tempfile.mkstemp(suffix='.tar.gz', dir=_TMP_ROOT)
|
|
os.close(fd)
|
|
try:
|
|
req = urllib.request.Request(url, headers=hdrs)
|
|
t0 = time.time()
|
|
with urllib.request.urlopen(req, timeout=600) as r, open(tar, 'wb') as f:
|
|
while True:
|
|
chunk = r.read(1 << 20)
|
|
if not chunk:
|
|
break
|
|
f.write(chunk)
|
|
r = _docker(['load', '-qi', tar], timeout=1800)
|
|
if r.returncode == 0 and image in _local_images():
|
|
gb = os.path.getsize(tar) / 1e9
|
|
print(f'· ms-images: loaded {image.split("/")[-1][:50]} '
|
|
f'({gb:.2f} GB in {time.time() - t0:.0f}s)', flush=True)
|
|
return True
|
|
print(f'· ms-images: load failed for {image}: '
|
|
f'{(r.stderr or "")[:120]}', flush=True)
|
|
return False
|
|
except Exception as e:
|
|
# 404 = this image was never uploaded; anything else = transient
|
|
code = getattr(e, 'code', None)
|
|
if code != 404:
|
|
print(f'· ms-images: fetch {fname}: {type(e).__name__}: '
|
|
f'{str(e)[:100]}', flush=True)
|
|
return False
|
|
finally:
|
|
try:
|
|
os.unlink(tar)
|
|
except OSError:
|
|
pass
|
|
|
|
|
|
if __name__ == '__main__':
|
|
import argparse
|
|
|
|
p = argparse.ArgumentParser()
|
|
p.add_argument('cmd', choices=['upload'])
|
|
p.add_argument('--repo', default='SoraAmami/swebench-images')
|
|
p.add_argument('--limit', type=int, default=0)
|
|
a = p.parse_args()
|
|
if a.cmd == 'upload':
|
|
upload(a.repo, limit=a.limit)
|