fix: checkpoints ranked by val_Sharpe not improvement_rate — rewind to best state

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) <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-04-15 19:14:40 +02:00
parent a57039ebfd
commit 38e65dace1
2 changed files with 7 additions and 2 deletions

View File

@@ -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<f32>, // DtoD copy of params_buf — GPU resident
pub target_params_gpu: CudaSlice<f32>, // DtoD copy of target_params_buf — GPU resident

View File

@@ -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();