Skip to main content

supervising-training-runs

Use when launching a multi-hour neural-network training, fine-tune, or other long GPU job autonomously from Claude Code and you need to catch failures (NaN, stuck-at-chance, dead process, throughput collapse, OOM) early instead of waking up to a wasted GPU window.

Zur Installation springen

Quellinformationen

Repository
AMindToThink/claude-code-settings
Letzte Quellaktivität
23. Juni 2026 um 00:45
Erkannte Sprache von SKILL.md
Englisch
Sterne
4
Forks
0

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
supervising-training-runs
description
Use when launching a multi-hour neural-network training, fine-tune, or other long GPU job autonomously from Claude Code and you need to catch failures (NaN, stuck-at-chance, dead process, throughput collapse, OOM) early instead of waking up to a wasted GPU window.
# Supervising training runs ## Overview **Core idea: don't tail logs, parse structured telemetry; don't poll, get notified.** A long training run is supervised well when (a) the run itself emits a single JSONL file with one fact per line, and (b) a separate watchdog process exits-with-anomaly the *moment* something looks wrong, re-invoking you. You check in via cheap `tail` reads, not by dumping full logs into context. This is different from how a human watches W&B trend curves — your strengths are parsing structured data and reacting on notification, not staring at plots. ## When to use - About to launch a multi-hour training / fine-tune / long GPU job that must run autonomously. - The user is away or sleeping and the GPU window is fixed. - Asked to "babysit" / "monitor" / "manage" a training run. Do not use for runs < ~15 min — the preflight + watchdog ceremony isn't worth it. ## The pattern (in order — skip none) 1. **Bench the GPU.** Run ~15 train steps of the *real* architecture at the *real* seq-len/precision/batch on the target GPU to measure samples/sec. Compute `time = N_samples × N_epochs / sps`. Budget separately for preprocessing and eval. If projection blows your window, subsample BEFORE writing more code — not after. 2. **Audit the data before training, not after.** Scraped code corpora routinely contain ~5–10% rows that aren't valid in the claimed language (broken syntax, wrong language, truncated). Training on them is noise — the model learns surface signatures of broken syntax. Run a parallel `ast.parse` (or equivalent) check on every row and **drop or flag the unparseable rows in BOTH the raw and any normalized variant** so the kept set stays aligned. If a normalizer (`black`, AST round-trip, formatter) is part of the experiment, filter to rows where **both** the validity check **and** the normalizer succeed — otherwise the "normalized" variant secretly contains verbatim-fallback rows that aren't normalized at all, and the treatment is impure. Cost: ~1 min on 14 cores for ~150k rows. 3. **Smoke preflight (mandatory).** ~2k samples, 1 epoch, eval every ~20 steps. Confirm: loss falls below chance, val AUROC moves *up* from ~0.5 (label polarity correct, not inverted), `metrics.jsonl` written, checkpoint saves+reloads. Most "wasted 4 hours" come from skipping this. 4. **Launch as `run_in_background=true` Bash, on a single physical line.** Use `python -u`, set `PYTHONUNBUFFERED=1 HF_HUB_DISABLE_PROGRESS_BARS=1 TRANSFORMERS_VERBOSITY=error TOKENIZERS_PARALLELISM=false`. The background completion notification is your "it finished" signal. 5. **Telemetry contract.** The training script appends to `metrics.jsonl` with `flush()`: - per-step (every ~10): `{"step","t","loss","lr","grad_norm","sps"}` - per-eval (every ~250): `{"step","eval_auroc","eval_acc"}` - Built-in fail-loud: assert label polarity at startup; **raise on non-finite loss** (don't write a NaN and continue). - Also enable `report_to=["tensorboard"]` for the *human's* dashboard (zero auth; SSH-tunnel `tensorboard --logdir <out> --port 6006`). W&B if the user has an account/key. A small `matplotlib` PNG render of `metrics.jsonl` makes a phone-friendly snapshot you can `SendUserFile`. 6. **Launch a watchdog** as a separate background job. Polls metrics file every ~30s. Exits **7 (anomaly)** on any of: NaN/inf loss, metrics file mtime stale > MAX_STALL (~360s = dead/hung), val AUROC < 0.70 after the warmup-ish step threshold (stuck at chance on an easy task), hard timeout. Exits **0** when the completion sentinel (e.g. `final_val_metrics.json`) appears. Either exit re-invokes you — early on failure, late on success. 6. **Don't poll-block.** Background-job completion + watchdog cover both outcomes. Each check-in: `tail -k metrics.jsonl` + one `nvidia-smi` + one `pgrep`. Cheap context. ## Healthy trajectory (alarm thresholds) For a typical fine-tune binary classifier: | Signal | Healthy | Alarm | |---|---|---| | Loss (binary CE) | < 0.5 after ~200 steps (chance ≈ 0.69) | flat at ~0.69 after ~400 steps | | Val AUROC | > 0.85 by end of epoch 1, > 0.9 within first few hundred steps on an "easy" task | pinned ~0.5 → label/data bug; trending toward 0 → inverted labels (just flip) | | Pre-clip grad-norm | 0.1 – 10 | sustained > 50 (exploding) or < 1e-3 (collapse) | | Throughput | stable | sustained > 25% drop vs first-200-step median | | NaN / inf loss | never | abort on the FIRST step, don't wait | ## Operational gotchas (caught the hard way) | Symptom | Cause | Fix | |---|---|---| | Background bash runs, GPU stays at 0% | newlines in a multi-line `run_in_background` command got collapsed into spaces, so `cd … mkdir … python` ran as one broken command | one physical line, `&&` between steps | | Balanced subset silently drops the `label` column | pandas 2.x `groupby().apply()` drops the grouping column | sample per group explicitly: `pd.concat([g.sample(per, random_state=SEED) for _, g in df.groupby("label")])` | | `TypeError: ... overwrite_output_dir` | removed in transformers v5 | drop it (the dir is fresh anyway); also `evaluation_strategy` → `eval_strategy` | | 18k-line log dominated by progress bars, real error buried | HF default progress bars | env: `HF_HUB_DISABLE_PROGRESS_BARS=1 TRANSFORMERS_VERBOSITY=error`; on read, `grep -avE "it/s\|Materializing\|Loading weights"` | | In-process `black` on 100k+ files times out | serial single-process | `multiprocessing.Pool(N)` with a **module-level** worker (must be picklable); 14 procs cuts ~15 min to ~1 min | | GPU shows 0% util right after launch | still in `.map()` tokenization or `from_pretrained` weight load | wait 1–2 min before alarming; the watchdog's MAX_STALL handles real stalls | | Credentials in `~/.git-credentials` look fine but `git push` 401s | stored token has been revoked/expired | tell the user; don't try other credential paths — the harness will block credential scanning, correctly | ## Common mistakes - **Skipping the smoke run** because "it's a standard recipe." It's the cheapest insurance you'll buy: ~3 min vs ~3 hours wasted. - **Hand-tailing the full log** on each check-in. Wastes context. Tail the *JSONL*, last few lines only. - **Polling for completion.** Background jobs *notify*. Use the notification; reserve `ScheduleWakeup` for genuinely external state (external API, queue) the harness can't track. - **Treating "no telemetry yet" as a stall** in the first 2 minutes. `.map()` on large datasets takes time before GPU activity. - **Writing a NaN, continuing, hoping it recovers.** Raise. Fail loud. - **Hardcoding the recipe's published `N_samples` × `epochs`** without checking it fits the GPU window. Bench first, subsample if needed, then write code. ## Checkpointing `load_best_model_at_end=True`, `metric_for_best_model="auroc"`, `save_total_limit=2`. A late crash costs at most one eval interval. ## Reference — a minimal Trainer callback that implements this ```python import json, time, numpy as np from pathlib import Path from transformers import TrainerCallback class JsonlMonitor(TrainerCallback): """One JSONL line per train log + per eval. Aborts loudly on non-finite loss.""" def __init__(self, path: Path): self.path = path self.path.parent.mkdir(parents=True, exist_ok=True) self.path.write_text("") self._t = self._lt = time.time(); self._last_step = 0 def _w(self, d): with self.path.open("a") as f: f.write(json.dumps(d) + "\n"); f.flush() def on_log(self, args, state, control, logs=None, **kw): logs = logs or {} rec = {"step": state.global_step, "t": round(time.time() - self._t, 1)} if "loss" in logs: if not np.isfinite(logs["loss"]): self._w({**rec, "FATAL": "non-finite loss"}) raise RuntimeError(f"non-finite loss at step {state.global_step}") now = time.time() sps = ((state.global_step - self._last_step) * args.per_device_train_batch_size / max(now - self._lt, 1e-6)) self._lt = now; self._last_step = state.global_step rec.update(loss=round(logs["loss"], 4), lr=logs.get("learning_rate"), grad_norm=round(logs.get("grad_norm", float("nan")), 3), sps=round(sps, 1)) if "eval_auroc" in logs: rec.update(eval_auroc=round(logs["eval_auroc"], 4), eval_acc=round(logs.get("eval_accuracy", float("nan")), 4)) self._w(rec) ``` ## Reference — a minimal watchdog ```python # Exit 7 (ANOMALY) on stall/NaN/stuck; exit 0 on completion sentinel. import json, time, sys from pathlib import Path DIR = Path(sys.argv[1]) metrics, done = DIR/"metrics.jsonl", DIR/"final_val_metrics.json" POLL, MAX_STALL, MIN_AUROC, MIN_AUROC_STEP, HARD = 30, 360, 0.70, 500, 3*3600 t0 = time.time() while True: time.sleep(POLL) if done.exists(): print("OK done"); sys.exit(0) if time.time() - t0 > HARD: print("ANOMALY hard timeout"); sys.exit(7) if not metrics.exists(): continue if time.time() - metrics.stat().st_mtime > MAX_STALL: print("ANOMALY stall"); sys.exit(7) rows = [json.loads(l) for l in metrics.read_text().splitlines()[-50:] if l.strip()] if rows and "loss" in rows[-1]: l = rows[-1]["loss"] if not isinstance(l, (int, float)) or l != l: print("ANOMALY NaN"); sys.exit(7) for e in [r for r in rows if "eval_auroc" in r]: if e.get("step", 0) >= MIN_AUROC_STEP and e["eval_auroc"] < MIN_AUROC: print(f"ANOMALY stuck val_auroc={e['eval_auroc']}"); sys.exit(7) ``` ## Red flags — STOP and reassess - About to launch a multi-hour run without having benched samples/sec on the target GPU. - About to launch without a smoke run. - Smoke val AUROC didn't move above ~0.5 → don't launch; you'd be burning hours on a broken pipeline. - About to use `evaluation_strategy=` or `overwrite_output_dir=` on transformers v5 — they'll error out. - About to tail a multi-MB stdout log to "check progress." Tail `metrics.jsonl` instead. - About to read credentials the user hasn't pointed you to — the harness will (correctly) block this.
Auf GitHub ansehen