| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| """UV-script wrapper that runs MCPArena GRPO training on an HF Jobs T4. |
| |
| Clones the latest env from the HF Space, installs it in editable mode, then |
| invokes training/train_grpo.py. Pass phase + episodes via env vars: |
| PHASE=phase_1 EPISODES=200 |
| |
| Usage from local machine: |
| hf jobs uv run --flavor t4-medium -s HF_TOKEN \ |
| --env PHASE=phase_1 --env EPISODES=200 \ |
| scripts/launch_train_t4.py |
| """ |
| import os |
| import subprocess |
| import sys |
|
|
|
|
| def run(cmd: list[str], **kw) -> None: |
| print(f"[launch] $ {' '.join(cmd)}", flush=True) |
| subprocess.run(cmd, check=True, **kw) |
|
|
|
|
| def main() -> None: |
| phase = os.environ.get("PHASE", "phase_1") |
| episodes = int(os.environ.get("EPISODES", "200")) |
| repo_url = os.environ.get("REPO_URL", "https://huggingface.co/spaces/vex-0/mcparena") |
| runs_dataset = os.environ.get("RUNS_DATASET", "vex-0/mcparena-runs") |
| |
| |
| resume_from_dataset_path = os.environ.get("RESUME_FROM_DATASET_PATH", "") |
| workdir = "/tmp/mcparena" |
|
|
| |
| run(["git", "clone", repo_url, workdir]) |
| os.chdir(workdir) |
|
|
| |
| |
| |
| env = dict(os.environ, PYTHONPATH=workdir, PYTHONUNBUFFERED="1") |
|
|
| extra_args: list[str] = [] |
| |
| |
| reward_baseline = os.environ.get("REWARD_BASELINE", "") |
| if reward_baseline: |
| extra_args.extend(["--reward_baseline", reward_baseline]) |
| base_model = os.environ.get("BASE_MODEL", "") |
| if base_model: |
| extra_args.extend(["--base_model", base_model]) |
| if resume_from_dataset_path: |
| from huggingface_hub import snapshot_download |
| local_resume = "/tmp/resume_adapter" |
| print(f"[launch] downloading resume adapter from {runs_dataset}/{resume_from_dataset_path}", flush=True) |
| snapshot_download( |
| repo_id=runs_dataset, |
| repo_type="dataset", |
| allow_patterns=[f"{resume_from_dataset_path}/*"], |
| local_dir=local_resume, |
| ) |
| resume_dir = os.path.join(local_resume, resume_from_dataset_path) |
| if not os.path.isdir(resume_dir): |
| raise SystemExit(f"[launch] expected resume dir {resume_dir} not found") |
| print(f"[launch] resume adapter at {resume_dir}", flush=True) |
| extra_args.extend(["--resume_from", resume_dir]) |
|
|
| train_failed = False |
| try: |
| run( |
| [sys.executable, "training/train_grpo.py", "--phase", phase, "--episodes", str(episodes), *extra_args], |
| env=env, |
| ) |
| except subprocess.CalledProcessError as e: |
| print(f"[launch] training failed with exit {e.returncode}; uploading partial runs/ anyway", flush=True) |
| train_failed = True |
|
|
| |
| runs_dir = os.path.join(workdir, "runs") |
| if os.path.isdir(runs_dir): |
| from huggingface_hub import HfApi, create_repo |
| api = HfApi() |
| try: |
| create_repo(runs_dataset, repo_type="dataset", exist_ok=True, private=True) |
| except Exception as e: |
| print(f"[launch] create_repo warning (likely already exists): {e}", flush=True) |
| |
| from datetime import datetime |
| ts = datetime.utcnow().strftime("%Y%m%d-%H%M%S") |
| path_in_repo = f"{phase}/{ts}" |
| api.upload_folder( |
| folder_path=runs_dir, |
| repo_id=runs_dataset, |
| repo_type="dataset", |
| path_in_repo=path_in_repo, |
| ) |
| print(f"[launch] uploaded {runs_dir} → dataset {runs_dataset}/{path_in_repo}", flush=True) |
| else: |
| print(f"[launch] no runs/ directory to upload", flush=True) |
|
|
| if train_failed: |
| sys.exit(1) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|