feat(ml-alpha): PerceptionTrainer.save_checkpoint + alpha_train wiring (X13+X14)
X13: Adds PerceptionTrainer::save_checkpoint as a thin delegate to self.trunk.save_checkpoint. Inference-only serialization — grads + AdamW state aren't included. X14: Inside the existing auc_h6000_improved block in alpha_train.rs, calls trainer.save_checkpoint(out_dir / 'trunk_best_h6000.bin') so the trained trunk lands alongside alpha_train_summary.json. Extends AlphaTrainSummary with best_h6000_ckpt_path (Option<String>) so downstream tooling (fxt-backtest --checkpoint) can locate the file without re-deriving the path. After this commit, every alpha-perception Argo workflow run produces a CheckpointV2 file at every new-best-h6000 epoch, ready for backtest consumption. Verification: - ml-alpha lib tests: 34 pass - alpha_train example builds clean (release) Per spec §1.1 (X13+X14).
This commit is contained in:
@@ -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<String>,
|
||||
}
|
||||
|
||||
/// 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<String> = 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")?)
|
||||
|
||||
@@ -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],
|
||||
|
||||
Reference in New Issue
Block a user