From 2e06297671f912db173866774ad947b3c0f41a47 Mon Sep 17 00:00:00 2001 From: jgrusewski Date: Fri, 22 May 2026 10:12:55 +0200 Subject: [PATCH] feat(aux-supervision): per-epoch aux observability in alpha_train (B6.5) Smoke 1 at HEAD 21e7dfd63 showed BCE bit-identical to baseline (asymmetric stop-grad isolation working) but zero aux signal in logs. Adds: - Per-epoch tracing 'aux snapshot' line with aux_huber_h{10,100,1000}, aux_dir_acc_h{10,100,1000}, stop_grad_aux_to_encoder - AlphaTrainSummary JSON fields: final_aux_huber_ema_per_h, final_aux_dir_acc_ema_per_h, final_stop_grad_aux_to_encoder Reads PerceptionTrainer's pub fields directly (no accessor noise). After re-running Smoke 1 we'll have empirical evidence whether aux is learning on REAL ES MBP-10 data (vs the synthetic constant-direction test where dir_acc trivially hits 1.0). This informs B7's gate threshold design. Co-Authored-By: Claude Opus 4.7 (1M context) --- crates/ml-alpha/examples/alpha_train.rs | 35 +++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/crates/ml-alpha/examples/alpha_train.rs b/crates/ml-alpha/examples/alpha_train.rs index 9ca8278d4..c053fa881 100644 --- a/crates/ml-alpha/examples/alpha_train.rs +++ b/crates/ml-alpha/examples/alpha_train.rs @@ -239,6 +239,21 @@ struct AlphaTrainSummary { /// `--smoothness-base-lambda` is unset (default). #[serde(default)] final_smooth_loss_per_horizon: [f32; N_HORIZONS], + /// Per-horizon Huber-loss EMA on the D-style aux supervision targets + /// (Layer B). Stays at 0.0 if aux supervision is disabled. Tracks + /// `PerceptionTrainer.aux_huber_ema_per_h` at the final epoch. + #[serde(default)] + final_aux_huber_ema_per_h: [f32; N_AUX_HORIZONS], + /// Per-horizon directional accuracy EMA on the D-label sign (Layer B). + /// > 0.85 with `aux_huber_ema < threshold` triggers the asymmetric + /// stop-grad lift (`stop_grad_aux_to_encoder = false`). + #[serde(default)] + final_aux_dir_acc_ema_per_h: [f32; N_AUX_HORIZONS], + /// Whether the asymmetric stop-grad was still active at the final epoch. + /// `true` means aux trunk gradient never flowed back into the encoder + /// (still "passive auditor" mode); `false` means the lift fired. + #[serde(default)] + final_stop_grad_aux_to_encoder: bool, } /// Linear warmup then cosine decay to `lr_min`. `step_idx` is the @@ -636,6 +651,23 @@ fn main() -> Result<()> { tracing::warn!(epoch, error = %e, "isv snapshot failed (continuing)"); } } + + // SDD-3 Layer B6.5: aux supervision metrics per epoch (Layer B). + // aux_huber_ema_per_h close to 0 = aux head learning; baseline + // (predicting per-horizon mean of D-labels) is ~0.5-1.0. dir_acc + // > 0.5 means aux predicts direction better than chance. Both + // > thresholds trigger the asymmetric stop-grad lift (E3 design). + tracing::info!( + epoch, + aux_huber_h10 = trainer.aux_huber_ema_per_h[0], + aux_huber_h100 = trainer.aux_huber_ema_per_h[1], + aux_huber_h1000 = trainer.aux_huber_ema_per_h[2], + aux_dir_acc_h10 = trainer.aux_dir_acc_ema_per_h[0], + aux_dir_acc_h100 = trainer.aux_dir_acc_ema_per_h[1], + aux_dir_acc_h1000 = trainer.aux_dir_acc_ema_per_h[2], + stop_grad_aux_to_encoder = trainer.stop_grad_aux_to_encoder, + "aux snapshot" + ); final_val_loss = val_avg; final_val_auc = per_horizon_auc; n_val_seqs_consumed = val_loader.yielded(); @@ -728,6 +760,9 @@ fn main() -> Result<()> { early_stopped, best_h1000_ckpt_path, final_smooth_loss_per_horizon: trainer.last_smoothness_loss_per_horizon(), + final_aux_huber_ema_per_h: trainer.aux_huber_ema_per_h, + final_aux_dir_acc_ema_per_h: trainer.aux_dir_acc_ema_per_h, + final_stop_grad_aux_to_encoder: trainer.stop_grad_aux_to_encoder, }; let summary_path = cli.out.join("alpha_train_summary.json"); std::fs::write(&summary_path, serde_json::to_vec_pretty(&summary).context("serialize summary")?)