| """Train a Go1 velocity policy on one terrain curriculum. |
| |
| python train.py hard --iters 10000 |
| |
| Checkpoints and the per-iteration curve stream to HuggingFace as training runs; |
| Kaggle kills sessions at 12 h and truncates logs, so nothing is written only at |
| the end. |
| """ |
|
|
| import argparse, csv, glob, os, re, shutil, subprocess, threading, time |
|
|
| os.environ.setdefault("MUJOCO_GL", "egl") |
| os.environ["WANDB_MODE"] = "disabled" |
|
|
| REPO = "mitanshugoel/go1-terrain" |
| RUNDIR, OUT = "/tmp/run", "/kaggle/working/out" |
|
|
| p = argparse.ArgumentParser() |
| p.add_argument("terrain", choices=["flat", "rough", "hard"]) |
| |
| |
| |
| |
| |
| |
| p.add_argument("--iters", type=int, default=4000) |
| p.add_argument("--envs", type=int, default=4096) |
| p.add_argument("--sync-every", type=int, default=600) |
| a = p.parse_args() |
|
|
| os.makedirs(RUNDIR, exist_ok=True) |
| os.makedirs(OUT, exist_ok=True) |
|
|
| from huggingface_hub import HfApi |
|
|
| api = HfApi(token=os.environ["HF_TOKEN"]) |
| api.create_repo(REPO, repo_type="model", private=False, exist_ok=True) |
|
|
| CSV = f"{OUT}/curve.csv" |
| COLS = ["iteration", "reward", "ep_len", "steps_per_s", "fell_over", |
| "value_loss", "action_std", "track_lin_vel", "err_vel_xy"] |
| PATS = {c: p for c, p in zip(COLS[1:], [ |
| r"Mean reward:\s*(-?[\d.]+)", r"Mean episode length:\s*([\d.]+)", |
| r"Steps per second:\s*(\d+)", r"Episode_Termination/fell_over:\s*([\d.]+)", |
| r"Mean value loss:\s*(-?[\d.]+)", r"Mean action std:\s*([\d.]+)", |
| r"Episode_Reward/track_linear_velocity:\s*(-?[\d.]+)", |
| r"Metrics/twist/error_vel_xy:\s*([\d.]+)"])} |
|
|
|
|
| def sync(): |
| """Mirror the whole run dir; upload_folder dedups so repeats are cheap.""" |
| try: |
| api.upload_folder(folder_path=RUNDIR, path_in_repo=f"{a.terrain}/run", |
| repo_id=REPO, repo_type="model") |
| api.upload_file(path_or_fileobj=CSV, path_in_repo=f"{a.terrain}/curve.csv", |
| repo_id=REPO, repo_type="model") |
| except Exception as e: |
| print("[hf]", e, flush=True) |
|
|
|
|
| def syncer(): |
| while True: |
| time.sleep(a.sync_every) |
| sync() |
|
|
|
|
| threading.Thread(target=syncer, daemon=True).start() |
|
|
| |
| shutil.copy(os.path.join(os.path.dirname(__file__), "terrain.py"), "/tmp/terrain.py") |
| |
| |
| |
| |
| |
| patch = f""" |
| import sys; sys.path.insert(0, "/tmp") |
| from terrain import TERRAINS |
| from mjlab.tasks.registry import load_env_cfg as _orig |
| def load_env_cfg(task_id, **kw): |
| cfg = _orig(task_id, **kw) |
| gen = TERRAINS["{a.terrain}"] |
| gen.curriculum = True # parity with mjlab's rough default |
| cfg.scene.terrain.terrain_generator = gen |
| return cfg |
| import mjlab.tasks.registry as R; R.load_env_cfg = load_env_cfg |
| import mjlab.scripts.train as T; T.load_env_cfg = load_env_cfg |
| T.main() |
| """ |
| open("/tmp/entry.py", "w").write(patch) |
|
|
| cf = open(CSV, "w", newline="") |
| w = csv.writer(cf) |
| w.writerow(COLS) |
| TASK = "Mjlab-Velocity-Rough-Unitree-Go1" |
| cmd = (f"cd {RUNDIR} && python /tmp/entry.py {TASK} " |
| f"--env.scene.num-envs {a.envs} --agent.max-iterations {a.iters}") |
| print(">>>", cmd, flush=True) |
|
|
| proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, |
| stderr=subprocess.STDOUT, text=True, bufsize=1) |
| cur = {} |
| for line in proc.stdout: |
| m = re.search(r"Learning iteration (\d+)/", line) |
| if m: |
| if cur: |
| w.writerow([cur.get(c) for c in COLS]) |
| cf.flush() |
| cur = {"iteration": int(m.group(1))} |
| for k, pat in PATS.items(): |
| g = re.search(pat, line) |
| if g: |
| cur[k] = float(g.group(1)) |
| if re.search(r"Learning iteration|Mean reward|Error|Traceback", line): |
| print(line.rstrip(), flush=True) |
|
|
| proc.wait() |
| if cur: |
| w.writerow([cur.get(c) for c in COLS]) |
| cf.close() |
| sync() |
| print(f"done rc={proc.returncode} https://huggingface.co/{REPO}/tree/main/{a.terrain}", |
| flush=True) |
|
|