diff --git a/crates/ml-alpha/examples/alpha_train.rs b/crates/ml-alpha/examples/alpha_train.rs index ea594aa1e..060aead41 100644 --- a/crates/ml-alpha/examples/alpha_train.rs +++ b/crates/ml-alpha/examples/alpha_train.rs @@ -164,7 +164,7 @@ struct Cli { decision_stride: usize, } -#[derive(Serialize)] +#[derive(Serialize, serde::Deserialize, Default)] struct AlphaTrainSummary { epochs: usize, seq_len: usize, @@ -200,6 +200,11 @@ struct AlphaTrainSummary { best_auc_h6000_per_horizon: [f32; N_HORIZONS], /// True if training stopped early (patience exceeded). early_stopped: bool, + /// Relative path (within `out_dir`) to the best-h6000 trunk + /// checkpoint file, or None if no h6000 improvement was ever recorded. + /// Written by the X14 wiring inside the `auc_h6000_improved` block. + #[serde(default, skip_serializing_if = "Option::is_none")] + best_h6000_ckpt_path: Option, } /// Linear warmup then cosine decay to `lr_min`. `step_idx` is the @@ -310,6 +315,7 @@ fn main() -> Result<()> { let mut best_auc_h6000 = f32::NEG_INFINITY; let mut best_auc_h6000_epoch = 0usize; let mut best_auc_h6000_per_horizon = [0.5_f32; N_HORIZONS]; + let mut best_h6000_ckpt_path: Option = None; let mut auc_h6000_no_improvement = 0usize; let lr_min_cfc = cli.lr_cfc * cli.lr_min_factor; @@ -606,7 +612,16 @@ fn main() -> Result<()> { best_auc_h6000_epoch = epoch; best_auc_h6000_per_horizon = per_horizon_auc; auc_h6000_no_improvement = 0; - tracing::info!(epoch, auc_h6000, "new best auc_h6000"); + // X14: emit a CheckpointV2 file alongside summary.json so + // fxt-backtest --checkpoint can load the trained trunk. + let ckpt_path = cli.out.join("trunk_best_h6000.bin"); + trainer.save_checkpoint(&ckpt_path) + .context("save_checkpoint(trunk_best_h6000.bin)")?; + best_h6000_ckpt_path = Some("trunk_best_h6000.bin".to_string()); + tracing::info!( + epoch, auc_h6000, path = %ckpt_path.display(), + "new best auc_h6000 (saved checkpoint)" + ); } else { auc_h6000_no_improvement += 1; } @@ -650,6 +665,7 @@ fn main() -> Result<()> { best_auc_h6000, best_auc_h6000_per_horizon, early_stopped, + best_h6000_ckpt_path, }; 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 69c6ae2ac..5e338b612 100644 --- a/crates/ml-alpha/src/trainer/perception.rs +++ b/crates/ml-alpha/src/trainer/perception.rs @@ -2306,6 +2306,14 @@ impl PerceptionTrainer { /// Single-sequence forward-only eval — thin wrapper around /// [`evaluate_batched`] for cfg.n_batch == 1. + /// X13: Persist the trunk's v2 weights to a CheckpointV2 bincode file. + /// Delegates to `self.trunk.save_checkpoint` — gradient buffers and + /// AdamW state are NOT serialized (training-only, not needed for + /// inference). + pub fn save_checkpoint(&self, path: &std::path::Path) -> Result<()> { + self.trunk.save_checkpoint(path).context("trunk save_checkpoint") + } + pub fn evaluate( &mut self, snapshots: &[Mbp10RawInput],