…lestone M4)
SPEC §1.3 R8 claims `sep-CMA-ES > SFT > RS > REINFORCE on all 4 tasks` (Table 4),
and §9 pins both recipes exactly. Two consumers of that ordering are already
merged — `analysis.convergence.R8_EXPECTED_ORDER` names "sft" and "reinforce",
and `configs/trinity.yaml` ships `imitation_sft` / `rl_reinforce` entries plus
`sft:` / `reinforce:` hyperparameter blocks — but nothing in `src/` could ever
*produce* either run. The verifiers existed with no producer, so R8 was
unverifiable at its source. These are the producers.
- `optim/sft.py` — frozen SLM, head-only multinomial logistic regression.
Teacher categorical built from the measured per-(query, model) solve rates in
the oracle matrices already on disk; softmax cross-entropy against it. SPEC §9
recipe verbatim: Adam, lr 1e-6, batch 64.
- `optim/reinforce.py` — one-step (contextual-bandit) policy gradient with an
EMA advantage baseline carried in from previous iterations, so the baseline is
action-independent and the gradient stays unbiased.
Both implement `BaseTrainer`, so their results are directly comparable with
sep-CMA-ES and Random Search, and both reuse `coordinator.warmstart.load_labels`
/ `pack_warmstart_theta` rather than reimplementing the on-disk matrix schema —
one code path, no drift. No new API calls: labels come from cached artifacts.
torch is imported lazily inside the fitting functions, so `import trinity.optim`
stays torch-free (`test_shaped_fitness.py::test_no_torch_imported`). Test files
are named `test_torch_*` so alphabetical collection keeps them after that guard.
`optim/baselines.py`'s docstring deferred these two on the grounds that they
"cannot be validated in a CPU-only environment"; that was too strong and is
corrected here. Both are head-only with small tensors and their update rules are
exercised on CPU. What genuinely still needs a GPU is producing the Table 4
*numbers* for R8, not the trainers themselves — the docstring now says so.
Additive and default-off: no existing caller changes behaviour.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The gap
SPEC §1.3 R8 claims
sep-CMA-ES > SFT > RS > REINFORCE on all 4 tasks(Table 4), and §9 pins both recipes exactly. Two consumers of that ordering are already merged:analysis/convergence.py→R8_EXPECTED_ORDERnames"sft"and"reinforce"configs/trinity.yamlshipsimitation_sft/rl_reinforceentries plussft:/reinforce:hyperparameter blocks…but nothing in
src/could ever produce either run. The verifiers exist with no producer, so R8 was unverifiable at its source, and those config knobs had zero readers. This PR adds the producers.optim/baselines.pysaid so itself — its docstring carried a written IOU deferring both "to a follow-up". This is that follow-up.What's here
optim/sft.py— the SLM is frozen, so the only trainable tensor is the head weightW ∈ R^{n_a × d_h}. With frozen features the routing head is a multinomial logistic regression over precomputed hidden states, and SFT is imitation learning: for each query, build a teacher categorical over pool models from the measured per-(query, model) solve rates, then minimize softmax cross-entropy against it. SPEC §9 recipe verbatim — Adam, lr 1e-6, batch 64, head-only.optim/reinforce.py— one-step (contextual-bandit) policy gradient,∇_W J = E[(r − b)·∇_W log π(a|h)], withban EMA advantage baseline carried in from previous iterations. Becausebis a function of past batches only it is independent of the action being scored, so it subtracts nothing in expectation — the gradient stays unbiased while losing most of its variance, which is what makes REINFORCE usable on a reward this noisy. (Using the post-update baseline would leak the batch's own sampled actions into its baseline; there's a regression test pinning the carried-in semantics.)Both implement
BaseTrainerand return the documented trainer summary, so their results are directly comparable with sep-CMA-ES and Random Search.Why it's low-risk
coordinator.warmstart.load_labels/pack_warmstart_thetaare reused rather than reimplemented, so these cannot drift from the on-disk matrix schema. A run produces a θ of exactlyspec.n_total, so R8 compares like with like.import trinity.optimstill imports no torch — the invarianttest_shaped_fitness.py::test_no_torch_importedenforces. Test files are namedtest_torch_*so alphabetical collection keeps them after that guard.Deliberately not folded into
warmstart.pywarmstart.fit_agent_headalso fits agent rows by cross-entropy, but it is a pure-numpy full-batch GD helper whose only job is to produce a CMA-ES initial meanx0(bespoke lr, L2, disagreement reweighting, no optimizer state, no trainer summary). This module is an R8 competitor and must reproduce the SPEC recipe verbatim. Sharing a loss family is expected — same statistical problem — but a warm-start and a baseline are different experiments, and collapsing them would make R8 compare CMA-ES against its own initializer.Scope, stated honestly
Only the head's agent rows are fit: the oracle matrices carry per-model correctness and nothing about roles, so there is no role supervision to learn from. Role rows stay 0 (uniform) and SVF scales stay 1.0 (identity), matching
pack_warmstart_theta.The old
baselines.pydocstring claimed these "cannot be validated in a CPU-only environment". That was too strong and is corrected in this PR: both are head-only with small tensors, and their update rules are exercised on CPU (convergence on toy bandit problems, gradient/unbiasedness properties, budget matching). What genuinely still needs a GPU is producing the Table 4 numbers for R8, not the trainers themselves. This PR does not claim to have reproduced Table 4.Verification
pytest tests/ -q→ 1830 passed, 0 failed (95 of them new, covering both trainers)ruff check src/ scripts/repo_governance/→ cleanimport trinity.optimconfirmed to leavetorchout ofsys.modules🤖 Generated with Claude Code