From 38e65dace1da7bc837b853bf213c8b708b9b536c Mon Sep 17 00:00:00 2001 From: jgrusewski Date: Wed, 15 Apr 2026 19:14:40 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20checkpoints=20ranked=20by=20val=5FSharpe?= =?UTF-8?q?=20not=20improvement=5Frate=20=E2=80=94=20rewind=20to=20best=20?= =?UTF-8?q?state?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit train-w2z55 rewound to epoch 0 (improvement_rate=17.3, val_Sharpe=17.3) instead of epoch 20 (improvement_rate=0.4, val_Sharpe=45.4). The model lost ALL learned behavior and couldn't re-learn at low cosine LR. Fix: sort checkpoints by val_Sharpe (highest first). Rewind targets the BEST state (val_Sharpe=45), not the fastest-climbing moment (epoch 0). Added val_sharpe field to TrajectoryCheckpoint for proper ranking. Co-Authored-By: Claude Opus 4.6 (1M context) --- crates/ml/src/trainers/dqn/trainer/mod.rs | 1 + crates/ml/src/trainers/dqn/trainer/training_loop.rs | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/ml/src/trainers/dqn/trainer/mod.rs b/crates/ml/src/trainers/dqn/trainer/mod.rs index 5a9316599..1476e216d 100644 --- a/crates/ml/src/trainers/dqn/trainer/mod.rs +++ b/crates/ml/src/trainers/dqn/trainer/mod.rs @@ -46,6 +46,7 @@ mod tests; /// Saved training state for trajectory backtracking. pub(crate) struct TrajectoryCheckpoint { pub epoch: usize, + pub val_sharpe: f32, // val_Sharpe at save time — used for ranking pub improvement_rate: f32, // d(val_Sharpe)/d(epoch) at save time pub params_gpu: CudaSlice, // DtoD copy of params_buf — GPU resident pub target_params_gpu: CudaSlice, // DtoD copy of target_params_buf — GPU resident diff --git a/crates/ml/src/trainers/dqn/trainer/training_loop.rs b/crates/ml/src/trainers/dqn/trainer/training_loop.rs index 3b69e8db8..0de3ab865 100644 --- a/crates/ml/src/trainers/dqn/trainer/training_loop.rs +++ b/crates/ml/src/trainers/dqn/trainer/training_loop.rs @@ -2358,6 +2358,7 @@ impl DQNTrainer { let checkpoint = super::TrajectoryCheckpoint { epoch, + val_sharpe, improvement_rate, params_gpu, target_params_gpu, @@ -2367,10 +2368,13 @@ impl DQNTrainer { per_branch_q_gap_ema: fused.per_branch_q_gap_ema(), }; - // Insert sorted by improvement rate, keep top-3 + // Sort by val_Sharpe (highest first), keep top-3. + // Rewind should go to the BEST state, not the fastest-improving. + // Epoch 0 (improvement=17.3, val_Sharpe=17) is useless as a rewind target. + // Epoch 20 (improvement=0.4, val_Sharpe=45) is the BEST rewind target. self.backtracking.checkpoints.push(checkpoint); self.backtracking.checkpoints.sort_by(|a, b| - b.improvement_rate.partial_cmp(&a.improvement_rate).unwrap_or(std::cmp::Ordering::Equal) + b.val_sharpe.partial_cmp(&a.val_sharpe).unwrap_or(std::cmp::Ordering::Equal) ); if self.backtracking.checkpoints.len() > self.backtracking.max_checkpoints { self.backtracking.checkpoints.pop();