qwenjev / scripts /v4_recover_bootstrap.py
tchbcb's picture
v0.4.10: heads-WIP upload hole (FUKJA61G lesson) + recover fixes (archive root, shutil.move nest, stamps prep/data only, max_steps 110)
a04922f verified
Raw History Blame Contribute Delete
6.23 kB
#!/usr/bin/env python3
"""One-command v4 recovery bootstrap for a FRESH Colab T4.
Context (2026-09-23): the FUKJA61G T4 died mid-real_c (uptime ~5.8h).
Everything that matters is on HF:
* code v0.4.9 (vision-aware pooling fix) -> repo root
* v4 Stage B weights (loss 0.25-0.55 @ s160-164) -> t4_assets_v4/checkpoints/hf_lora/adapter_model.pt
* the merged 167,635-sample dataset -> t4_assets_v3/data/dataset_final.tgz
* HANDOVER with full RCA -> HANDOVER.md
This script (run INSIDE the fresh T4, after the standard code+deps bootstrap):
1. extracts dataset_final.tgz -> data/dataset_final (data stage equivalent)
2. pulls v4 hf_lora .pt -> checkpoints/qwenjev-s1-hf-v4/stage_a_lora + hf_lora
3. stamps prep/data as done -> orchestrator re-runs real_b (heads regen, s110)
(v0.4.10: real_b is NOT skipped any more — the v4 heads file was never
uploaded to HF and died with the FUKJA61G VM; calibrate_rlcd requires it)
4. launches the orchestrator (GPU) -> real_b(110) -> real_c -> ALL_DONE -> upload
5. launches the eps0.45 collector (CPU) -> data/traces_real3 (in parallel)
Usage (on the T4, inside /content/qwenjev after deps install):
QWENJEV_OUT=qwenjev-s1-hf-v4 QWENJEV_ASSETS=t4_assets_v4 \
nohup python3 -u -B scripts/v4_recover_bootstrap.py > /content/v4_recover.log 2>&1 &
"""
import json
import os
import shutil
import subprocess
import sys
import tarfile
import time
from pathlib import Path
REPO = Path("/content/qwenjev")
WORK = Path(os.environ.get("QWENJEV_WORK", "/content/qwenjev_work_v3"))
OUT = REPO / "checkpoints" / os.environ.get("QWENJEV_OUT", "qwenjev-s1-hf-v4")
ASSETS = os.environ.get("QWENJEV_ASSETS", "t4_assets_v4")
DONE = WORK / "done_v3"
HF_REPO = "tchbcb/qwenjev"
os.chdir(REPO)
token = None
for cand in (os.environ.get("HF_TOKEN"), WORK / ".hf_token"):
if cand and str(cand).strip() and os.path.exists(str(cand)):
token = open(str(cand)).read().strip()
break
if not token:
raise SystemExit("no HF_TOKEN / .hf_token found")
os.environ["HF_TOKEN"] = token
from huggingface_hub import hf_hub_download # noqa: E402
print("[recover] 1/4 dataset_final", flush=True)
ds = REPO / "data" / "dataset_final"
if not (ds / "train" / "samples.jsonl").exists():
tgz = hf_hub_download(HF_REPO, f"{ASSETS}/data/dataset_final.tgz"
if False else "t4_assets_v3/data/dataset_final.tgz")
ds.mkdir(parents=True, exist_ok=True)
with tarfile.open(tgz) as t:
t.extractall(REPO) # v0.4.10: verified archive root is
# 'dataset_final' (NOT data/dataset_final)
if not (ds / "train" / "samples.jsonl").exists():
alt = REPO / "dataset_final"
if (alt / "train" / "samples.jsonl").exists():
if ds.exists():
shutil.rmtree(ds) # empty and/or botched nested copy —
# shutil.move(dst-exists) nests src inside
shutil.move(str(alt), str(ds))
print(" moved dataset_final -> data/", flush=True)
print(" train rows:", sum(1 for _ in open(ds / "train" / "samples.jsonl")), flush=True)
print("[recover] 2/4 v4 stage_b weights -> stage_a_lora + hf_lora", flush=True)
pt = hf_hub_download(HF_REPO, f"{ASSETS}/checkpoints/hf_lora/adapter_model.pt")
for sub in ("stage_a_lora", "hf_lora"):
d = OUT / sub
d.mkdir(parents=True, exist_ok=True)
shutil.copyfile(pt, d / "adapter_model.safetensors")
for f in ("adapter_config.json", "README.md"):
try:
src = hf_hub_download(HF_REPO, f"{ASSETS}/checkpoints/hf_lora/{f}")
shutil.copyfile(src, d / f)
except Exception:
pass
meta = None
for meta_path in (f"{ASSETS}/checkpoints/hf_lora_wip/stage_b_meta.json",
f"{ASSETS}/checkpoints/stage_b_meta.json"):
try:
meta = hf_hub_download(HF_REPO, meta_path)
shutil.copyfile(meta, OUT / "stage_b_meta.json")
print(" stage_b_meta <-", meta_path, flush=True)
break
except Exception as exc:
print(" no meta at", meta_path, ":", str(exc)[:80], flush=True)
if meta is None:
(OUT / "stage_b_meta.json").write_text(json.dumps(
{"wip": True,
"note": "v4 recovery: heads missing from HF, real_b re-run "
"regenerates them (v0.4.10)"}, indent=2))
print(" restored ->", OUT, flush=True)
print("[recover] 3/4 stamps", flush=True)
DONE.mkdir(parents=True, exist_ok=True)
now = time.strftime("%F %T")
# v0.4.10: stamp prep+data ONLY. real_b MUST re-run: the v4 decision heads
# (OUT/qwenjev_s1.pt) were never uploaded and died with the old VM —
# calibrate_rlcd hard-fails without them ("stage B checkpoint missing").
# The s160 LoRA IS on HF, so real_b resumes from it and only the heads
# re-converge (max_steps: 110 keeps it clear of the s165 OOM wall).
for name in ("prep", "data"):
(DONE / f"{name}.stamp").write_text(
f"{now} (v4 recovery: prep=v2 lora re-seed skipped, "
"data=dataset_final from HF)")
print(" stamps:", sorted(p.name for p in DONE.glob("*.stamp")), flush=True)
print(" NOTE: real_b will re-run (heads regen from s160 LoRA)", flush=True)
print("[recover] 4/4 launch orchestrator (GPU) + collector (CPU)", flush=True)
env = {**os.environ, "QWENJEV_OUT": os.environ.get("QWENJEV_OUT", "qwenjev-s1-hf-v4"),
"QWENJEV_ASSETS": ASSETS, "PYTHONUNBUFFERED": "1"}
subprocess.Popen(["python3", "-u", "-B", "ops/t4_real25.py"],
cwd=str(REPO), stdout=open("/content/t4_v4.log", "w"),
stderr=subprocess.STDOUT, env=env,
start_new_session=True)
subprocess.Popen(["python3", "-u", "-B", "-m", "data.collect_selfplay",
"--config", "configs/collect_real_v4.yaml"],
cwd=str(REPO), stdout=open("/content/collect_v4.log", "w"),
stderr=subprocess.STDOUT, env=env,
start_new_session=True)
print("[recover] launched: orchestrator -> real_c; collector -> traces_real3", flush=True)
print("[recover] monitor: tail /content/t4_v4.log /content/collect_v4.log", flush=True)