Loop Hardening (v0.70.0 → live in v0.71.11)
Six surfaces that protect the training loop from the failure modes that cost a real GPU-hour. Shipped schema-first in v0.70.0; as of v0.71.11 every callback is live — the detectors install real GRPO/PPO TrainerCallbacks, the distillation losses compute inside the distill trainer, the RL checkpoints write real state, and soup iterative-dpo runs the full loop. Validated end-to-end on SmolLM2-135M.
--reward-hack-detector — InfoRM + RM-ensemble divergence
soup train --task grpo --base-model meta-llama/Llama-3.1-8B \
--reward-model registry://rm-v1 \
--reward-hack-detector info_rm \
--reward-hack-haltTwo detectors:
info_rm— InfoRM Cluster-Separation Index (Wang et al. 2024, arXiv 2402.09345). Drops when the policy collapses onto a degenerate reward-maximising subspace.rm_ensemble— mean pairwise variance across an RM ensemble (cap 32). When ensemble members disagree, the policy is exploiting one of them.
Math kernels compute_cluster_separation, compute_rm_ensemble_divergence, classify_hack_signal are LIVE with OK / WARN / HACK bands at 0.10 / 0.30 relative drop. --reward-hack-halt auto-stops on HACK (exit 2). Cross-validator: task in {grpo, ppo} only, halt=True requires detector, rejects mlx; rm_ensemble requires ≥2 reward functions. Composes with v0.34 soup why for anomaly explanation. Live as of v0.71.11 — the GRPO callback reads per-step rewards via a shared thread-safe capture buffer, classifies the verdict, logs it to state.log_history, and halts on HACK.
v0.71.26 closes the loop. These detectors no longer only *halt*:
--reward-hack-mitigation kl_control|pid_lagrangianmakes the trainer self-correct mid-run — raise the KL penalty, shape the reward, roll back to the last-good checkpoint, then early-stop only as a last resort. See Closed-loop reward-hacking auto-mitigation.
--uld-strategy — cross-tokenizer Universal Logit Distillation
# soup.yaml
task: distill
training:
uld_strategy: wasserstein # or: topk_align
uld_top_k: 32 # required for topk_alignBoizard et al. 2024 (arXiv 2402.12030). Llama → Mistral, Llama → Qwen — no shared vocabulary required.
wasserstein— 1-D Wasserstein distance over sorted teacher / student logits, no alignment (cheap, robust default)topk_align— top-K teacher logits matched via BPE-overlap heuristic alignment (use when you have a good vocab-overlap heuristic and want sharper signal)_MAX_VOCAB_SIZE=262144covers multilingual SentencePiece + GPT-OSS 200K vocabularies- Gated to
task='distill'and rejects mlx backend - Live as of v0.71.11 — the distill trainer computes the real Wasserstein-1 (sorted-CDF) or top-k-aligned loss, clamping teacher ids to the teacher vocab on a size mismatch. (v0.71.12 also adds
distill_mode: sequencefor hard-label sequence-level KD, mutually exclusive with the ULD logit path.)
--minillm-enabled — reverse-KL with 3 stability tricks bundled
task: distill
training:
minillm_enabled: true
minillm_teacher_mix_ratio: 0.3
minillm_length_normalize: true
minillm_pretrain_anchor_weight: 0.1
minillm_pretrain_anchor_path: ./pretrain.jsonlGu et al. 2024 (arXiv 2306.08543). All three §3 stability tricks bundled: teacher-mixed sampling (mix teacher samples into the on-policy rollout), length normalisation (per-token KL averaged), pretrain-loss anchor (regularise toward an anchor distribution at weight α).
Cross-validators reject silent no-ops:
anchor_weight=0withanchor_pathset → erroranchor_weight > 0withpath = None→ error
Gated to task='distill'. Live as of v0.71.11 — the teacher-mixed, length-normalised reverse-KL term plus the optional pretrain-anchor SFT term train end-to-end; the anchor reader is cwd-contained + symlink-rejecting with a per-line byte cap.
--rl-checkpoint-save-every-steps — mid-epoch PPO/GRPO ckpt
soup train --task ppo --base-model ... \
--rl-checkpoint-save-every-steps 200 \
--rl-checkpoint-keep-last 4 \
--rl-checkpoint-include-optimizer \
--rl-checkpoint-include-ref-model \
--rl-checkpoint-include-rollout-bufferTorchTune explicitly punts mid-epoch checkpointing. Soup ships the schema today with bounds save_every_steps ∈ [1, 10M], keep_last ∈ [1, 100] (oldest pruned).
Composes with v0.32 spike recovery + v0.40 reference-model regen — recovery now hops to the most recent mid-epoch ckpt instead of restarting the epoch on a PPO crash. Live as of v0.71.11 — it writes a real adapter + optimizer state + JSON manifest every N steps and prunes to --rl-checkpoint-keep-last. (v0.71.11 also makes the GRPO reference-model EMA update in place, eliminating the three model-sized allocations per step.)
soup iterative-dpo — sample → score → re-pair → retrain driver
soup iterative-dpo --base-model registry://policy-v3 \
--reward-model registry://rm-v1 \
--prompts ./prompts.jsonl \
--output-dir ./iter-dpo \
--rounds 4 --pairs-per-round 4000Frozen IterativeDPOPlan with a consecutive-`round_index` invariant and canonical per-round artifacts:
./iter-dpo/round-01/pairs.jsonl
./iter-dpo/round-01/adapter/
./iter-dpo/round-02/pairs.jsonl
./iter-dpo/round-02/adapter/
...So a crashed run resumes cleanly. --plan-only renders the validated plan and exits 0. Live as of v0.71.11 — each round samples completions from the previous round's adapter, then trains a fresh LoRA from the base on that round's harvested pairs.
--echo-trap-enabled — RAGEN multi-turn n-gram repetition detector
soup train --task grpo ... \
--echo-trap-enabled \
--echo-trap-threshold 0.6 \
--echo-trap-haltZhu et al. 2025 (arXiv 2504.14437). Pure-Python n-gram repetition rate per trajectory + a batch mean — when an agent's rollout collapses into "echoing itself" (the same n-gram pattern appearing repeatedly within and across turns), this catches it before the reward model rewards the degenerate policy.
OK / WARN / TRAP bands at 0.30 / 0.60. DoS caps _MAX_NGRAM_N=32, _MAX_TRAJECTORY_TOKENS=1M, _MAX_BATCH_TRAJECTORIES=100k. Gated to task in {grpo, ppo} non-mlx. Composes with v0.53.11 GRPOStabilityCallback. Live as of v0.71.11 — the GRPO callback scores per-trajectory n-gram repetition, logs the verdict, and halts on TRAP when --echo-trap-halt is set.
Numbers
+337 tests in v0.70.0 (11,487 → 11,824); the live wiring in v0.71.11 is part of the broader v0.71 sweep that has taken the suite to 16,001 tests across 313 files.
See also
- Closed-loop reward-hacking auto-mitigation (v0.71.26) — the release that turns these detectors from "halt" into "self-correct".
- Lean install + live wiring (v0.71) — the release that made all six of these surfaces real.
- Adapter lifecycle (v0.67) —
soup adapters bisectfinds which mid-epoch ckpt regressed. - Anti-trend insurance (v0.68) —
soup distill-prompt+ ULD pair up to bridge tokeniser gaps. - Soup Loop (v0.58) — iterative-DPO runs inside a
soup loopiteration.