refactor: remove gpu_n_episodes override — auto-scale from VRAM everywhere
gpu_n_episodes was manually overridden in GPU profiles, training configs, test files, and hyperopt — all set to 0 or small fixed values that bypassed the auto-scaling logic, causing a div-by-zero crash in train_baseline_rl. Now: single auto-scaling path via optimal_n_episodes() from VRAM/SM count. No manual override field. Cap at 16384 (consistent with AutoBatchSizer's 8192 cap pattern). Floor at 32 for small GPUs. Removed gpu_n_episodes from: - DQNHyperparameters, PpoHyperparameters structs - All 4 GPU profiles (rtx3050, h100, a100, default) - Training profiles (smoketest, localdev) - ExperienceProfile struct + serde - Hyperopt adapter - All test overrides Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,6 @@ hidden_dim_base = 256
|
||||
replay_buffer_vram_fraction = 0.70
|
||||
|
||||
[experience]
|
||||
gpu_n_episodes = 0 # 0 = auto from VRAM
|
||||
gpu_timesteps_per_episode = 500
|
||||
|
||||
[cuda]
|
||||
|
||||
@@ -7,7 +7,6 @@ hidden_dim_base = 256
|
||||
replay_buffer_vram_fraction = 0.50
|
||||
|
||||
[experience]
|
||||
gpu_n_episodes = 0 # 0 = auto from VRAM
|
||||
gpu_timesteps_per_episode = 200
|
||||
|
||||
[cuda]
|
||||
|
||||
@@ -7,10 +7,6 @@ hidden_dim_base = 256
|
||||
replay_buffer_vram_fraction = 0.70
|
||||
|
||||
[experience]
|
||||
# 2048 episodes: cuBLAS SGEMM batch=2048 saturates 132 SMs.
|
||||
# At 256 episodes, the tiny 256×48 GEMMs leave >90% of SMs idle.
|
||||
# 2048×100 = 204K experiences/epoch (vs 256×500 = 128K).
|
||||
gpu_n_episodes = 0 # 0 = auto from VRAM
|
||||
gpu_timesteps_per_episode = 100
|
||||
|
||||
[cuda]
|
||||
|
||||
@@ -7,7 +7,6 @@ hidden_dim_base = 256
|
||||
replay_buffer_vram_fraction = 0.40
|
||||
|
||||
[experience]
|
||||
gpu_n_episodes = 0 # 0 = auto from VRAM
|
||||
gpu_timesteps_per_episode = 100
|
||||
|
||||
[cuda]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# DQN Local Dev Profile — extended training on RTX 3050 (4GB VRAM)
|
||||
# Larger buffer + more episodes than smoketest for meaningful convergence.
|
||||
# gpu_n_episodes=64 × gpu_timesteps=200 = 12,800 experiences/epoch
|
||||
# gpu_n_episodes=auto × gpu_timesteps=200 (auto-scaled from VRAM)
|
||||
# buffer_size=50K → fills over ~4 epochs, reducing memorization
|
||||
|
||||
[training]
|
||||
@@ -45,7 +45,6 @@ patience = 20
|
||||
min_epochs_before_stopping = 80
|
||||
|
||||
[experience]
|
||||
gpu_n_episodes = 64
|
||||
gpu_timesteps_per_episode = 200
|
||||
min_hold_bars = 3
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# DQN Smoke Test Profile — f32 master weights + bf16 tensor core shadows
|
||||
# gpu_n_episodes=32 × gpu_timesteps=100 = 3200 experiences/epoch
|
||||
# gpu_n_episodes=auto × gpu_timesteps=100 (auto-scaled from VRAM)
|
||||
# batch_size=64 fits in 4GB RTX 3050
|
||||
#
|
||||
# Mixed-precision (NVIDIA AMP pattern):
|
||||
@@ -49,7 +49,6 @@ patience = 5
|
||||
min_epochs_before_stopping = 5
|
||||
|
||||
[experience]
|
||||
gpu_n_episodes = 32
|
||||
gpu_timesteps_per_episode = 100
|
||||
min_hold_bars = 3
|
||||
|
||||
|
||||
@@ -46,7 +46,6 @@ pub struct TrainingProfile {
|
||||
/// GPU experience collection parameters.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct ExperienceProfile {
|
||||
pub gpu_n_episodes: usize,
|
||||
pub gpu_timesteps_per_episode: usize,
|
||||
}
|
||||
|
||||
@@ -203,7 +202,6 @@ impl GpuProfile {
|
||||
replay_buffer_vram_fraction: 0.50,
|
||||
},
|
||||
experience: ExperienceProfile {
|
||||
gpu_n_episodes: 64,
|
||||
gpu_timesteps_per_episode: 200,
|
||||
},
|
||||
cuda: CudaProfile {
|
||||
@@ -276,7 +274,6 @@ mod tests {
|
||||
assert_eq!(profile.training.batch_size, 64);
|
||||
assert_eq!(profile.training.num_atoms, 11);
|
||||
assert_eq!(profile.training.buffer_size, 5_000);
|
||||
assert_eq!(profile.experience.gpu_n_episodes, 16);
|
||||
assert_eq!(profile.experience.gpu_timesteps_per_episode, 100);
|
||||
assert_eq!(profile.cuda.cuda_stack_bytes, 16384);
|
||||
}
|
||||
@@ -287,7 +284,6 @@ mod tests {
|
||||
assert_eq!(profile.training.batch_size, 1024);
|
||||
assert_eq!(profile.training.num_atoms, 51);
|
||||
assert_eq!(profile.training.buffer_size, 500_000);
|
||||
assert_eq!(profile.experience.gpu_n_episodes, 2048);
|
||||
assert_eq!(profile.experience.gpu_timesteps_per_episode, 100);
|
||||
assert_eq!(profile.cuda.cuda_stack_bytes, 65536);
|
||||
}
|
||||
@@ -307,7 +303,6 @@ mod tests {
|
||||
assert_eq!(profile.training.batch_size, 256);
|
||||
assert_eq!(profile.training.num_atoms, 21);
|
||||
assert_eq!(profile.training.buffer_size, 50_000);
|
||||
assert_eq!(profile.experience.gpu_n_episodes, 64);
|
||||
assert_eq!(profile.experience.gpu_timesteps_per_episode, 200);
|
||||
assert_eq!(profile.cuda.cuda_stack_bytes, 32768);
|
||||
}
|
||||
@@ -341,7 +336,6 @@ mod tests {
|
||||
assert_eq!(profile.training.batch_size, 256);
|
||||
assert_eq!(profile.training.num_atoms, 21);
|
||||
assert_eq!(profile.training.buffer_size, 50_000);
|
||||
assert_eq!(profile.experience.gpu_n_episodes, 64);
|
||||
assert_eq!(profile.experience.gpu_timesteps_per_episode, 200);
|
||||
assert_eq!(profile.cuda.cuda_stack_bytes, 32768);
|
||||
}
|
||||
|
||||
@@ -562,8 +562,7 @@ fn train_dqn_fold(
|
||||
// Small GPUs (RTX 3050 profile: buffer_size=5000 < 100K threshold) bypass
|
||||
// AutoReplaySizer entirely, so VRAM fraction is irrelevant for them.
|
||||
replay_buffer_vram_fraction: gpu_profile.training.replay_buffer_vram_fraction,
|
||||
// GPU experience collection: profile-driven defaults replace VRAM if/else chains
|
||||
gpu_n_episodes: gpu_profile.experience.gpu_n_episodes,
|
||||
// GPU experience collection: n_episodes auto-scales from VRAM
|
||||
gpu_timesteps_per_episode: gpu_profile.experience.gpu_timesteps_per_episode,
|
||||
max_training_steps_per_epoch: args.max_steps_per_epoch,
|
||||
..DQNHyperparameters::default()
|
||||
|
||||
@@ -2042,8 +2042,6 @@ impl HyperparameterOptimizable for DQNTrainer {
|
||||
hyperparams.mbp10_data_dir = self.mbp10_data_dir.clone().unwrap_or_default();
|
||||
hyperparams.trades_data_dir = self.trades_data_dir.clone().unwrap_or_default();
|
||||
|
||||
// GPU tier-dependent settings
|
||||
hyperparams.gpu_n_episodes = if budget.gpu_memory_mb >= 40960 { 512 } else { 128 };
|
||||
hyperparams.max_training_steps_per_epoch = if budget.gpu_memory_mb >= 40960 { 2000 } else { 200 };
|
||||
// AutoReplaySizer VRAM fraction
|
||||
hyperparams.replay_buffer_vram_fraction = {
|
||||
|
||||
@@ -1172,9 +1172,6 @@ pub struct DQNHyperparameters {
|
||||
/// Enable the NVRTC-compiled GPU experience collection kernel.
|
||||
/// When false, experience collection uses the batched CPU path while
|
||||
/// the training step (forward/backward/optimizer) still runs on CUDA.
|
||||
/// Number of parallel episodes per GPU kernel launch (default: 128, scaled dynamically)
|
||||
/// Higher values improve GPU utilization on larger GPUs (e.g. H100: 8192)
|
||||
pub gpu_n_episodes: usize,
|
||||
/// Timesteps per episode in GPU kernel (default: 500, max: 1000)
|
||||
/// Higher values collect more experience per launch at the cost of VRAM
|
||||
pub gpu_timesteps_per_episode: usize,
|
||||
@@ -1681,7 +1678,6 @@ impl DQNHyperparameters {
|
||||
spectral_decoupling_lambda: 0.01, // Default: mild logit magnitude penalty (Pezeshki 2021)
|
||||
|
||||
// Phase 3: GPU experience collection
|
||||
gpu_n_episodes: 256, // Floor: optimal_n_episodes() scales up dynamically (H100→8192)
|
||||
gpu_timesteps_per_episode: 500, // Default: 500 timesteps per episode
|
||||
avg_spread: 0.0001, // Default: 1bp (ES/NQ futures)
|
||||
max_training_steps_per_epoch: 0, // Default: unlimited (full dataset training)
|
||||
|
||||
@@ -894,20 +894,18 @@ impl DQNTrainer {
|
||||
}
|
||||
|
||||
/// Compute allocation episode count for GPU buffers.
|
||||
/// Auto-scales when configured >= 128 (production), respects smaller test overrides.
|
||||
/// Always auto-scales from VRAM — no manual override.
|
||||
fn compute_alloc_episodes(&self, state_dim: usize) -> usize {
|
||||
use ml_core::memory_optimization::detect_gpu_hardware;
|
||||
let configured = self.hyperparams.gpu_n_episodes;
|
||||
if configured >= 128 {
|
||||
match detect_gpu_hardware() {
|
||||
Ok(hw) => configured.max(hw.optimal_n_episodes(
|
||||
match detect_gpu_hardware() {
|
||||
Ok(hw) => {
|
||||
let optimal = hw.optimal_n_episodes(
|
||||
state_dim,
|
||||
self.hyperparams.gpu_timesteps_per_episode,
|
||||
)).min(0x8000),
|
||||
Err(_) => configured,
|
||||
);
|
||||
optimal.max(32).min(16384)
|
||||
}
|
||||
} else {
|
||||
configured
|
||||
Err(_) => 256, // safe fallback
|
||||
}
|
||||
}
|
||||
|
||||
@@ -962,32 +960,25 @@ impl DQNTrainer {
|
||||
let raw_sd = if !self.hyperparams.mbp10_data_dir.is_empty() { 53 } else { 45 };
|
||||
let aligned_sd = (raw_sd + 7) & !7;
|
||||
|
||||
// Cache n_episodes on first epoch
|
||||
// Cache n_episodes on first epoch — always auto-scaled from VRAM.
|
||||
let n_episodes = if let Some(cached) = self.cached_n_episodes {
|
||||
cached
|
||||
} else {
|
||||
use ml_core::memory_optimization::detect_gpu_hardware;
|
||||
let configured = self.hyperparams.gpu_n_episodes;
|
||||
let computed = if configured >= 128 {
|
||||
match detect_gpu_hardware() {
|
||||
Ok(hw) => {
|
||||
let optimal = hw.optimal_n_episodes(
|
||||
aligned_sd,
|
||||
self.hyperparams.gpu_timesteps_per_episode,
|
||||
);
|
||||
let chosen = configured.max(optimal).min(4096);
|
||||
if chosen != configured {
|
||||
info!(
|
||||
"GPU auto-scaled n_episodes: {} -> {} (SMs={}, VRAM={:.0}MB)",
|
||||
configured, chosen, hw.sm_count, hw.free_memory_mb
|
||||
);
|
||||
}
|
||||
chosen as i32
|
||||
}
|
||||
Err(_) => configured as i32,
|
||||
let computed = match detect_gpu_hardware() {
|
||||
Ok(hw) => {
|
||||
let optimal = hw.optimal_n_episodes(
|
||||
aligned_sd,
|
||||
self.hyperparams.gpu_timesteps_per_episode,
|
||||
);
|
||||
let chosen = optimal.max(32).min(16384) as i32;
|
||||
info!(
|
||||
"GPU auto-scaled n_episodes: {} (SMs={}, VRAM={:.0}MB)",
|
||||
chosen, hw.sm_count, hw.free_memory_mb
|
||||
);
|
||||
chosen
|
||||
}
|
||||
} else {
|
||||
configured as i32
|
||||
Err(_) => 256_i32, // safe fallback
|
||||
};
|
||||
self.cached_n_episodes = Some(computed);
|
||||
computed
|
||||
|
||||
@@ -65,8 +65,6 @@ pub struct PpoHyperparameters {
|
||||
pub max_grad_norm_lstm: f64,
|
||||
|
||||
// Phase 3: GPU experience collection kernel configuration
|
||||
/// Number of parallel episodes per GPU kernel launch (default: 128, scaled dynamically)
|
||||
pub gpu_n_episodes: usize,
|
||||
/// Timesteps per episode in GPU kernel (default: 500, max: 1000)
|
||||
pub gpu_timesteps_per_episode: usize,
|
||||
/// Initial capital for portfolio simulation (default: 1_000_000.0)
|
||||
@@ -125,7 +123,6 @@ impl PpoHyperparameters {
|
||||
max_grad_norm_lstm: 0.5, // Tighter clipping for LSTM vs MLP (10.0)
|
||||
|
||||
// Phase 3: GPU experience collection
|
||||
gpu_n_episodes: 128, // Default: 128 (good for 4-8GB VRAM GPUs)
|
||||
gpu_timesteps_per_episode: 500, // Default: 500 timesteps per episode
|
||||
initial_capital: 1_000_000.0, // Default: $1M
|
||||
avg_spread: 0.0001, // Default: 1bp (ES/NQ futures)
|
||||
@@ -532,8 +529,18 @@ impl PpoTrainer {
|
||||
) {
|
||||
use crate::cuda_pipeline::gpu_ppo_collector::PpoCollectorConfig;
|
||||
|
||||
let n_episodes = self.hyperparams.gpu_n_episodes as i32;
|
||||
let timesteps = self.hyperparams.gpu_timesteps_per_episode.min(1000) as i32;
|
||||
// Auto-scale n_episodes from VRAM
|
||||
let n_episodes = {
|
||||
use ml_core::memory_optimization::detect_gpu_hardware;
|
||||
match detect_gpu_hardware() {
|
||||
Ok(hw) => {
|
||||
let optimal = hw.optimal_n_episodes(80, self.hyperparams.gpu_timesteps_per_episode);
|
||||
optimal.max(32).min(16384) as i32
|
||||
}
|
||||
Err(_) => 128_i32,
|
||||
}
|
||||
};
|
||||
let total_bars = self.raw_data_num_bars as i32;
|
||||
let usable_bars = (total_bars - timesteps).max(1);
|
||||
let stride = (usable_bars / n_episodes).max(1);
|
||||
|
||||
@@ -258,7 +258,6 @@ pub struct RewardSection {
|
||||
/// GPU experience collection parameters.
|
||||
#[derive(Debug, Clone, Deserialize, Default)]
|
||||
pub struct ExperienceSection {
|
||||
pub gpu_n_episodes: Option<usize>,
|
||||
pub gpu_timesteps_per_episode: Option<usize>,
|
||||
pub initial_capital: Option<f64>,
|
||||
/// Maps to `DQNHyperparameters::transaction_cost_multiplier`.
|
||||
@@ -937,9 +936,6 @@ impl DqnTrainingProfile {
|
||||
// TOML: tx_cost_multiplier → hp: transaction_cost_multiplier
|
||||
// TOML: initial_capital → hp: initial_capital (f32 in hp, f64 in TOML)
|
||||
if let Some(ref ex) = self.experience {
|
||||
if let Some(v) = ex.gpu_n_episodes {
|
||||
hp.gpu_n_episodes = v;
|
||||
}
|
||||
if let Some(v) = ex.gpu_timesteps_per_episode {
|
||||
hp.gpu_timesteps_per_episode = v;
|
||||
}
|
||||
|
||||
@@ -155,7 +155,6 @@ async fn test_early_stopping_terminates_with_error() {
|
||||
hyperparams.max_training_steps_per_epoch = 300;
|
||||
hyperparams.replay_buffer_vram_fraction = 0.0;
|
||||
|
||||
hyperparams.gpu_n_episodes = 16;
|
||||
hyperparams.gpu_timesteps_per_episode = 50;
|
||||
let mut trainer = DQNTrainer::new(hyperparams.clone())
|
||||
.expect("Failed to create DQN trainer");
|
||||
@@ -228,7 +227,6 @@ async fn test_gradient_collapse_propagates_error() {
|
||||
hyperparams.max_training_steps_per_epoch = 300;
|
||||
hyperparams.replay_buffer_vram_fraction = 0.0;
|
||||
|
||||
hyperparams.gpu_n_episodes = 16;
|
||||
hyperparams.gpu_timesteps_per_episode = 50;
|
||||
let mut trainer = DQNTrainer::new(hyperparams.clone())
|
||||
.expect("Failed to create DQN trainer");
|
||||
@@ -289,7 +287,6 @@ async fn test_healthy_training_completes_successfully() {
|
||||
hyperparams.max_training_steps_per_epoch = 300; // Fast epochs: ~3s vs ~370s
|
||||
hyperparams.replay_buffer_vram_fraction = 0.0; // Disable AutoReplaySizer for test determinism
|
||||
|
||||
hyperparams.gpu_n_episodes = 16;
|
||||
hyperparams.gpu_timesteps_per_episode = 50;
|
||||
let mut trainer = DQNTrainer::new(hyperparams.clone())
|
||||
.expect("Failed to create DQN trainer");
|
||||
|
||||
@@ -107,13 +107,11 @@ fn scale_for_gpu(hp: &mut DQNHyperparameters) {
|
||||
let profile = GpuProfile::load();
|
||||
hp.buffer_size = hp.buffer_size.min(profile.training.buffer_size);
|
||||
hp.batch_size = hp.batch_size.min(profile.training.batch_size);
|
||||
hp.gpu_n_episodes = hp.gpu_n_episodes.min(profile.experience.gpu_n_episodes);
|
||||
hp.gpu_timesteps_per_episode = hp.gpu_timesteps_per_episode.min(profile.experience.gpu_timesteps_per_episode);
|
||||
hp.num_atoms = hp.num_atoms.min(profile.training.num_atoms);
|
||||
info!(
|
||||
"GPU profile: buffer={}, batch={}, episodes={}, timesteps={}, atoms={}",
|
||||
hp.buffer_size, hp.batch_size, hp.gpu_n_episodes,
|
||||
hp.gpu_timesteps_per_episode, hp.num_atoms,
|
||||
"GPU profile: buffer={}, batch={}, timesteps={}, atoms={}",
|
||||
hp.buffer_size, hp.batch_size, hp.gpu_timesteps_per_episode, hp.num_atoms,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -196,7 +194,6 @@ async fn test_dqn_trains_on_es_fut() -> Result<()> {
|
||||
// ========================================================================
|
||||
info!("ACT: Running DQN training...");
|
||||
|
||||
hyperparams.gpu_n_episodes = 16;
|
||||
hyperparams.gpu_timesteps_per_episode = 50;
|
||||
hyperparams.max_training_steps_per_epoch = 64;
|
||||
scale_for_gpu(&mut hyperparams);
|
||||
@@ -314,7 +311,6 @@ async fn test_dqn_loss_decreases() -> Result<()> {
|
||||
hyperparams.learning_rate = 0.001;
|
||||
hyperparams.early_stopping_enabled = false;
|
||||
|
||||
hyperparams.gpu_n_episodes = 16;
|
||||
hyperparams.gpu_timesteps_per_episode = 50;
|
||||
hyperparams.max_training_steps_per_epoch = 64;
|
||||
scale_for_gpu(&mut hyperparams);
|
||||
@@ -389,7 +385,6 @@ async fn test_dqn_checkpoint_save_load() -> Result<()> {
|
||||
hyperparams.batch_size = 64;
|
||||
hyperparams.checkpoint_frequency = 2;
|
||||
|
||||
hyperparams.gpu_n_episodes = 16;
|
||||
hyperparams.gpu_timesteps_per_episode = 50;
|
||||
hyperparams.max_training_steps_per_epoch = 64;
|
||||
scale_for_gpu(&mut hyperparams);
|
||||
@@ -459,7 +454,6 @@ async fn test_dqn_q_value_predictions() -> Result<()> {
|
||||
hyperparams.epochs = 2;
|
||||
hyperparams.batch_size = 32;
|
||||
|
||||
hyperparams.gpu_n_episodes = 16;
|
||||
hyperparams.gpu_timesteps_per_episode = 50;
|
||||
hyperparams.max_training_steps_per_epoch = 64;
|
||||
scale_for_gpu(&mut hyperparams);
|
||||
@@ -521,7 +515,6 @@ async fn test_dqn_epsilon_greedy() -> Result<()> {
|
||||
|
||||
hyperparams.batch_size = 64;
|
||||
hyperparams.max_training_steps_per_epoch = 64;
|
||||
hyperparams.gpu_n_episodes = 16;
|
||||
hyperparams.gpu_timesteps_per_episode = 50;
|
||||
hyperparams.min_replay_size = 50;
|
||||
hyperparams.warmup_steps = 0;
|
||||
@@ -603,7 +596,6 @@ async fn test_dqn_full_production_training() -> Result<()> {
|
||||
hyperparams.checkpoint_frequency = 10;
|
||||
hyperparams.early_stopping_enabled = true;
|
||||
|
||||
hyperparams.gpu_n_episodes = 16;
|
||||
hyperparams.gpu_timesteps_per_episode = 50;
|
||||
hyperparams.max_training_steps_per_epoch = 64;
|
||||
scale_for_gpu(&mut hyperparams);
|
||||
|
||||
@@ -171,7 +171,6 @@ fn create_test_hyperparams(_use_lstm: bool, sequence_length: usize) -> PpoHyperp
|
||||
circuit_breaker_threshold: 5,
|
||||
sequence_length,
|
||||
max_grad_norm_lstm: 0.5,
|
||||
gpu_n_episodes: 16,
|
||||
gpu_timesteps_per_episode: 50,
|
||||
initial_capital: 1_000_000.0,
|
||||
avg_spread: 0.0001,
|
||||
|
||||
@@ -801,7 +801,6 @@ async fn smoke_e2e_dqn_training_loop() {
|
||||
hyperparams.min_replay_size = 50;
|
||||
// CI smoke: 16 episodes x 50 timesteps = 800 experiences/epoch.
|
||||
// Exercises full fused CUDA kernel (branching+C51+NoisyNets+DSR).
|
||||
hyperparams.gpu_n_episodes = 2;
|
||||
hyperparams.gpu_timesteps_per_episode = 10;
|
||||
// CI: cap training steps to avoid full 204K-bar dataset sweep (6375->64 steps).
|
||||
// 64 steps x batch_size 32 = 2048 gradient updates — sufficient to validate
|
||||
|
||||
Reference in New Issue
Block a user