fix: eval v_range tracks Q-stats — was [-100,100] producing constant val_Sharpe
Root cause: eval compute_expected_q used [-100,100] atom range. With 51 atoms that's delta_z=4.0 — all Q-values collapsed to ~0. Model weights changed but eval actions didn't → identical val_Sharpe=-9.16 every epoch. Fix: eval_v_range_buf initialized from config v_min/v_max, then updated per-epoch from observed Q-stats (q_mean ± 3σ). Eval atoms now track the actual Q-value distribution. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1009,6 +1009,25 @@ impl GpuDqnTrainer {
|
||||
}
|
||||
|
||||
pub fn eval_v_range_ptr(&self) -> u64 { self.eval_v_range_ptr }
|
||||
|
||||
/// Update eval v_range from observed Q-value statistics.
|
||||
/// Called at epoch boundary after compute_q_stats.
|
||||
pub fn update_eval_v_range(&mut self, q_mean: f32, q_std: f32) {
|
||||
let half = (3.0 * q_std + 1.0).max(1.0); // 3σ + floor
|
||||
let v_min = q_mean - half;
|
||||
let v_max = q_mean + half;
|
||||
unsafe {
|
||||
let ptr = self.eval_v_range_ptr as *mut f32;
|
||||
// Pinned device-mapped: write directly from host
|
||||
// eval_v_range_buf is regular device mem — use async HtoD
|
||||
cudarc::driver::sys::cuMemcpyHtoDAsync_v2(
|
||||
self.eval_v_range_ptr,
|
||||
[v_min, v_max].as_ptr().cast(),
|
||||
2 * std::mem::size_of::<f32>(),
|
||||
self.stream.cu_stream(),
|
||||
);
|
||||
}
|
||||
}
|
||||
pub fn set_per_sample_support_ptr(&mut self, ptr: u64) {
|
||||
self.per_sample_support_ptr = ptr;
|
||||
}
|
||||
@@ -2426,7 +2445,8 @@ impl GpuDqnTrainer {
|
||||
// Fixed wide v_range for compute_expected_q (argmax, not C51 loss).
|
||||
let mut eval_v_range_buf = stream.alloc_zeros::<f32>(2)
|
||||
.map_err(|e| MLError::ModelError(format!("alloc eval_v_range: {e}")))?;
|
||||
let eval_range = [-100.0_f32, 100.0_f32];
|
||||
// Eval v_range: initial from config, updated per-epoch from Q-stats.
|
||||
let eval_range = [config.v_min, config.v_max];
|
||||
stream.memcpy_htod(&eval_range, &mut eval_v_range_buf)
|
||||
.map_err(|e| MLError::ModelError(format!("eval_v_range HtoD: {e}")))?;
|
||||
let eval_v_range_ptr = eval_v_range_buf.raw_ptr();
|
||||
|
||||
@@ -2083,6 +2083,10 @@ impl FusedTrainingCtx {
|
||||
pub(crate) fn per_sample_support_ptr(&self) -> u64 { self.gpu_iql.per_sample_support_ptr() }
|
||||
/// Fixed wide v_range pointer for eval compute_expected_q (not C51 loss).
|
||||
pub(crate) fn eval_v_range_ptr(&self) -> u64 { self.trainer.eval_v_range_ptr() }
|
||||
/// Update eval v_range from observed Q-value statistics.
|
||||
pub(crate) fn update_eval_v_range(&mut self, q_mean: f32, q_std: f32) {
|
||||
self.trainer.update_eval_v_range(q_mean, q_std);
|
||||
}
|
||||
/// Per-sample epsilon from IQL expectile gap.
|
||||
pub(crate) fn per_sample_epsilon_ptr(&self) -> u64 { self.gpu_iql.per_sample_epsilon_ptr() }
|
||||
pub(crate) fn num_atoms(&self) -> usize { self.trainer.config().num_atoms }
|
||||
|
||||
@@ -513,8 +513,8 @@ impl DQNTrainer {
|
||||
let f = &*fused_ptr;
|
||||
(f.online_dueling_ref() as *const _, f.online_branching_ref() as *const _)
|
||||
};
|
||||
// Wide fixed range for eval expected_q argmax — per-sample support only used in training loss.
|
||||
let vr = [-100.0_f32, 100.0_f32];
|
||||
// Eval v_range from hyperparams — must match the training config's v_min/v_max.
|
||||
let vr = [self.hyperparams.computed_v_min() as f32, self.hyperparams.computed_v_max() as f32];
|
||||
|
||||
let agent = self.agent.read().await;
|
||||
let network_dims = agent.network_dims();
|
||||
|
||||
@@ -1388,7 +1388,10 @@ impl DQNTrainer {
|
||||
self.epoch_q_gap = self.epoch_q_gap.max(stats.avg_max_q as f32 - stats.q_mean);
|
||||
self.epoch_atom_entropy = stats.atom_entropy;
|
||||
self.epoch_atom_utilization = stats.atom_utilization;
|
||||
let _q_gap = stats.avg_max_q as f32 - stats.q_mean;
|
||||
|
||||
// Update eval v_range from observed Q-stats
|
||||
let q_std = stats.q_variance.max(0.0).sqrt();
|
||||
fused.update_eval_v_range(stats.q_mean, q_std);
|
||||
}
|
||||
}
|
||||
train_step_count += 1;
|
||||
|
||||
Reference in New Issue
Block a user