# B4 — end-to-end proof that the 3-channel loop trains Two proofs that the Composer 3-channel loss (grpo + α·sdpo_kl + β·trace_replay_dpo) runs end-to-end, closing the gap left by `examples/composer_grpo_sdpo_smoke` (which proved *init* but never fired the SDPO channel — its toy rollouts carry no error sites). ## 1. CPU proof — SDPO channel FIRES nonzero through the real collator `run.py` — builds a REAL `ComposerDataCollator` batch from a trace with an error turn, so the shipped collator emits `ctx_teacher_input_ids` + `student/teacher_response_idx` (the ADR-011 alignment indices). Perturbs the student tokens at the aligned positions (mimicking the hint changing the recovery tokens) so the gathered student/teacher logits differ and the JSD is provably nonzero, then verifies a gradient flows. ``` $ python run.py proof path: TinyLM-stub-with-differing-tokens SDPO JSD (sdpo_kl): 0.056547 requires_grad: True grad norm into model: 0.001593 RESULT: PASS ✅ (SDPO channel FIRED nonzero via real collator indices) ``` Honest scope: the model is a deterministic CPU stub (no download); the *collator alignment path* is the real shipped code. Real-model path: `ALTERED_MINDS_REAL_MODEL=1`. ## 2. GPU proof — real Qwen2.5-0.5B trains, bf16, loss converges `modal_b4_gpu_smoke.py` — runs the real 3-channel composition on `Qwen/Qwen2.5-0.5B-Instruct` on a Modal A10G in bf16: GRPO-proxy LM loss + α·SDPO (hint-conditioned teacher = same model, no-grad) + β·replay-margin, 30 AdamW steps. ``` $ modal run modal_b4_gpu_smoke.py --n-steps 30 status : PASS dtype : torch.bfloat16 sdpo_fired_nonzero : True (max sdpo_kl 0.1855) loss_trend_down : True all_finite : True loss first → last : 4.7262 → 0.0050 (monotone decrease) ``` bf16 numerics finite throughout, SDPO channel nonzero, loss converges. Cost: ~$1-3 on A10G. Run date 2026-05-29; full curve in `gpu_smoke_result.json`. The proxies (GRPO→LM-loss, replay→margin) stand in for the full PG / DPO accounting so the smoke runs without a rollout buffer or teacher set; the SDPO channel is the *real* `generalized_jsd_loss` path. A full GRPO run with a real reward and rollouts is the LMA-budget-gated next step (ADR-013).