mcparena / scripts /launch_train_t4.py
vex-0's picture
support BASE_MODEL env var for 7B / larger model training
dae32fe
Raw
History Blame Contribute Delete
4.64 kB
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "torch>=2.4",
# "transformers>=4.45",
# "trl>=0.11",
# "unsloth",
# "peft>=0.13",
# "bitsandbytes>=0.43",
# "accelerate>=0.34",
# "fastapi>=0.111",
# "pydantic>=2.7",
# "numpy>=1.26",
# "huggingface-hub>=0.24",
# ]
# ///
"""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")
# Optional path inside the runs dataset to a prior LoRA adapter
# (e.g. "phase_1/20260425-191159/grpo_phase_1/final" for Phase 2 warm start).
resume_from_dataset_path = os.environ.get("RESUME_FROM_DATASET_PATH", "")
workdir = "/tmp/mcparena"
# Clone env code from HF Space (master branch is named 'main' on Spaces)
run(["git", "clone", repo_url, workdir])
os.chdir(workdir)
# 'server' / 'client' / 'training' are importable from CWD; UV-managed venvs
# don't ship pip, so we skip `pip install -e .` and rely on PYTHONPATH=cwd.
# PYTHONUNBUFFERED ensures print() output streams immediately in HF Job logs.
env = dict(os.environ, PYTHONPATH=workdir, PYTHONUNBUFFERED="1")
extra_args: list[str] = []
# REINFORCE baseline tuning — bump above the no-commit reward floor (~0.50) so
# don't-commit episodes get negative gradient and the policy is pushed off the trap.
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
# Upload runs/ to the HF dataset regardless of training success
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)
# Upload with phase-prefixed path so multi-phase runs don't collide
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()