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.

설치로 이동

소스 정보

저장소
AMindToThink/claude-code-settings
최근 소스 활동
2026년 6월 23일 00:45
감지된 SKILL.md 언어
영어
스타
4
포크
0

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
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.
GitHub에서 보기