From 60d0b62b8d7051699123c943b33b47caf38a8c36 Mon Sep 17 00:00:00 2001 From: jgrusewski Date: Wed, 20 May 2026 23:51:20 +0200 Subject: [PATCH] feat(crt-train): add --smoothness-base-lambda CLI flag + telemetry --- crates/ml-alpha/examples/alpha_train.rs | 16 +++++++++++++++- crates/ml-alpha/src/trainer/perception.rs | 13 +++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/crates/ml-alpha/examples/alpha_train.rs b/crates/ml-alpha/examples/alpha_train.rs index 71e8e6c78..2afd8ba84 100644 --- a/crates/ml-alpha/examples/alpha_train.rs +++ b/crates/ml-alpha/examples/alpha_train.rs @@ -153,6 +153,14 @@ struct Cli { #[arg(long, default_value_t = 0)] cv_train_window: usize, + /// CRT.train intervention A — output-smoothness regularizer. + /// + /// λ[h] = base × (HORIZONS[h] / HORIZONS[0]). Default 0.0 disables + /// the regularizer (kernel still launches, produces zero gradient). + /// Recommended sweep: 0.001 / 0.01 / 0.1. See + /// `docs/superpowers/specs/2026-05-20-crt-train-output-smoothness-design.md`. + #[arg(long, default_value_t = 0.0)] + smoothness_base_lambda: f32, } #[derive(Serialize, serde::Deserialize, Default)] @@ -196,6 +204,11 @@ struct AlphaTrainSummary { /// Written by the X14 wiring inside the `auc_h6000_improved` block. #[serde(default, skip_serializing_if = "Option::is_none")] best_h6000_ckpt_path: Option, + /// Per-horizon raw mean-squared adjacent-prob diff at the final epoch + /// (CRT.train intervention A telemetry). All zeros when + /// `--smoothness-base-lambda` is unset (default). + #[serde(default)] + final_smooth_loss_per_horizon: [f32; N_HORIZONS], } /// Linear warmup then cosine decay to `lr_min`. `step_idx` is the @@ -261,7 +274,7 @@ fn main() -> Result<()> { seed: cli.seed, horizon_weights, n_batch: cli.batch_size, - smoothness_base_lambda: 0.0, + smoothness_base_lambda: cli.smoothness_base_lambda, }; let mut trainer = PerceptionTrainer::new(&dev, &trainer_cfg).context("trainer init")?; @@ -655,6 +668,7 @@ fn main() -> Result<()> { best_auc_h6000_per_horizon, early_stopped, best_h6000_ckpt_path, + final_smooth_loss_per_horizon: trainer.last_smoothness_loss_per_horizon(), }; let summary_path = cli.out.join("alpha_train_summary.json"); std::fs::write(&summary_path, serde_json::to_vec_pretty(&summary).context("serialize summary")?) diff --git a/crates/ml-alpha/src/trainer/perception.rs b/crates/ml-alpha/src/trainer/perception.rs index db791ca75..1652b4061 100644 --- a/crates/ml-alpha/src/trainer/perception.rs +++ b/crates/ml-alpha/src/trainer/perception.rs @@ -3928,6 +3928,19 @@ impl PerceptionTrainer { download(&self.stream, &self.probs_per_k_d) } + /// Last-step per-horizon raw mean-squared-diff (pre-λ). Reads from + /// the mapped-pinned host shadow populated by `step_batched`. Returns + /// all zeros when the smoothness regularizer is disabled + /// (`cfg.smoothness_base_lambda == 0.0`). + pub fn last_smoothness_loss_per_horizon(&self) -> [f32; N_HORIZONS] { + let raw = self.smoothness_loss_per_horizon_host_d.read_all(); + let mut out = [0.0f32; N_HORIZONS]; + for (i, v) in raw.into_iter().take(N_HORIZONS).enumerate() { + out[i] = v; + } + out + } + /// Convenience for callers that only need the LAST position's probs /// (legacy API — final-position AUC scoring). pub fn last_probs(&self) -> Result<[f32; N_HORIZONS]> {