diff --git a/crates/ml/src/trainers/dqn/trainer/constructor.rs b/crates/ml/src/trainers/dqn/trainer/constructor.rs index b59600781..065130248 100644 --- a/crates/ml/src/trainers/dqn/trainer/constructor.rs +++ b/crates/ml/src/trainers/dqn/trainer/constructor.rs @@ -1,4 +1,4 @@ -//! DQN Trainer constructor — `new_internal` and init helpers. +//! DQN Trainer constructor -- `new_internal` and init helpers. use std::collections::VecDeque; use std::sync::Arc; @@ -40,7 +40,7 @@ impl DQNTrainer { // Pre-compute hidden dims to get accurate model size for batch sizing. // Align input_dim to 8 so the log matches the actual model dimensions. - // (device not yet created, so use the formula directly — CUDA always aligns) + // (device not yet created, so use the formula directly -- CUDA always aligns) let ofi_pre = !hyperparams.mbp10_data_dir.is_empty(); // 42 market + 8 portfolio + 16 multi-timeframe = 66 base, +8 OFI = 74 let input_dim: usize = if ofi_pre { 80 } else { 72 }; // (74+7)&!7=80, (66+7)&!7=72 @@ -67,7 +67,7 @@ impl DQNTrainer { // FP32 params + AdamW state (2x for momentum/variance) + target network copy = ~4x let model_overhead_mb = (param_count as f64 * 4.0 * 4.0) / (1024.0 * 1024.0); info!( - "DQN network: {:?} → {} params, {:.1} MB overhead", + "DQN network: {:?} -> {} params, {:.1} MB overhead", full_dims, param_count, model_overhead_mb ); @@ -93,9 +93,9 @@ impl DQNTrainer { } }; - // batch_size == 0 → auto-compute from VRAM (capped at 8192 for RL) + // batch_size == 0 -> auto-compute from VRAM (no artificial cap) if hyperparams.batch_size == 0 { - hyperparams.batch_size = max_safe_batch.min(8192); + hyperparams.batch_size = max_safe_batch; info!("AutoBatchSizer: batch_size auto-computed to {}", hyperparams.batch_size); } @@ -103,14 +103,14 @@ impl DQNTrainer { // Cap to VRAM ceiling (never exceed what the GPU can handle) if hyperparams.batch_size > max_safe_batch { info!( - "DQN batch_size capped from {} → {} (VRAM ceiling)", + "DQN batch_size capped from {} -> {} (VRAM ceiling)", hyperparams.batch_size, max_safe_batch ); hyperparams.batch_size = max_safe_batch; } // Use override device if provided (hyperopt shares one CUDA context), - // otherwise require CUDA GPU — no CPU fallback in CUDA builds. + // otherwise require CUDA GPU -- no CPU fallback in CUDA builds. let device = if let Some(dev) = override_device { dev } else { @@ -128,8 +128,8 @@ impl DQNTrainer { }; // Set CUDA stack size ONCE for all GPU kernels. - // cuCtxSetLimit reserves stack_bytes × max_threads_per_SM × SMs of VRAM. - // RTX 3050 (20 SMs × 1536 threads): 64KB → 1.88GB, 16KB → 480MB. + // cuCtxSetLimit reserves stack_bytes x max_threads_per_SM x SMs of VRAM. + // RTX 3050 (20 SMs x 1536 threads): 64KB -> 1.88GB, 16KB -> 480MB. // Stack size is driven by the GPU TOML profile (config/gpu/*.toml). #[cfg(feature = "cuda")] if let MlDevice::Cuda { ref context, .. } = device { @@ -139,7 +139,7 @@ impl DQNTrainer { let stack_bytes: usize = gpu_profile.cuda.cuda_stack_bytes; let result = unsafe { cuCtxSetLimit(CUlimit::CU_LIMIT_STACK_SIZE, stack_bytes) }; if result != CUresult::CUDA_SUCCESS { - tracing::warn!("cuCtxSetLimit(STACK_SIZE, {stack_bytes}) failed: {result:?} — may cause kernel crashes"); + tracing::warn!("cuCtxSetLimit(STACK_SIZE, {stack_bytes}) failed: {result:?} -- may cause kernel crashes"); } else { tracing::info!("CUDA stack: {stack_bytes} bytes/thread (num_atoms={}, profile)", hyperparams.num_atoms); } @@ -163,16 +163,16 @@ impl DQNTrainer { // Dynamic replay buffer sizing: scale replay capacity to available VRAM. // Only activates when replay_buffer_vram_fraction > 0 and GPU is detected. - // Also computes the PER memory budget from actual VRAM — no hardcoded caps. + // Also computes the PER memory budget from actual VRAM -- no hardcoded caps. // // Guard: skip auto-sizing when buffer_size < 100K (the sizer's own minimum). // A small explicit buffer_size means the caller wants that exact size // (e.g., smoke tests with buffer_size=1024). Without this, H100's 75 GB VRAM - // inflates small buffers → 10M, causing empty-sample failures and multi-hour hangs. + // inflates small buffers -> 10M, causing empty-sample failures and multi-hour hangs. let original_buffer_size = hyperparams.buffer_size; // Save before AutoReplaySizer mutates it const AUTO_REPLAY_MIN_THRESHOLD: usize = 100_000; - // Probe VRAM for PER budget — uses vram_fraction from hyperparams. + // Probe VRAM for PER budget -- uses vram_fraction from hyperparams. let mut per_max_memory_bytes: usize = { use ml_core::memory_optimization::detect_gpu_hardware; match detect_gpu_hardware() { @@ -186,7 +186,7 @@ impl DQNTrainer { } Err(e) => { return Err(anyhow::anyhow!( - "GPU PER requires VRAM probe — detect_gpu_hardware failed: {}", e + "GPU PER requires VRAM probe -- detect_gpu_hardware failed: {}", e )); } } @@ -194,7 +194,7 @@ impl DQNTrainer { // Dynamic replay buffer sizing: scale replay capacity to available VRAM. // Only activates when replay_buffer_vram_fraction > 0, GPU detected, and - // buffer_size >= 100K (sizer's own minimum — smaller values are intentional). + // buffer_size >= 100K (sizer's own minimum -- smaller values are intentional). if hyperparams.replay_buffer_vram_fraction > 0.0 && matches!(device, MlDevice::Cuda { .. }) && (hyperparams.buffer_size == 0 || hyperparams.buffer_size >= AUTO_REPLAY_MIN_THRESHOLD) @@ -248,7 +248,7 @@ impl DQNTrainer { let raw_state_dim = if ofi_enabled { 74 } else { 66 }; // 42 market + 8 portfolio + 16 MTF + (8 OFI) let state_dim = (raw_state_dim + 7) & !7; // aligned: 80 with OFI, 72 without - // Resolve gradient clip norm ONCE — single source of truth (Bug #4 fix). + // Resolve gradient clip norm ONCE -- single source of truth (Bug #4 fix). let grad_norm = hyperparams.gradient_clip_norm.unwrap_or(10.0); let config = DQNConfig { @@ -283,7 +283,7 @@ impl DQNTrainer { per_alpha: hyperparams.per_alpha, per_beta_start: hyperparams.per_beta_start, per_beta_max: 1.0, - per_beta_annealing_steps: hyperparams.epochs * 500, // reach beta=1.0 by ~25% of training (was 2000 → too slow) + per_beta_annealing_steps: hyperparams.epochs * 500, // reach beta=1.0 by ~25% of training (was 2000 -> too slow) per_max_memory_bytes, // Wave 2.1: Dueling Networks (ENABLED BY DEFAULT - Wave 6.4) @@ -310,9 +310,9 @@ impl DQNTrainer { cql_alpha: hyperparams.cql_alpha, iqn_num_quantiles: hyperparams.num_quantiles, // Controlled by hyperopt - iqn_kappa: hyperparams.qr_kappa as f32, // Controlled by hyperopt (f64→f32) + iqn_kappa: hyperparams.qr_kappa as f32, // Controlled by hyperopt (f64->f32) iqn_embedding_dim: 64, // Fixed (not in search space) - iqn_lambda: hyperparams.iqn_lambda as f32, // IQN dual-head loss weight (f64→f32) + iqn_lambda: hyperparams.iqn_lambda as f32, // IQN dual-head loss weight (f64->f32) branch_hidden_dim: hyperparams.branch_hidden_dim, use_regime_conditioning: true, // Always enable per-regime IS weights for branching loss cvar_alpha: hyperparams.cvar_alpha, @@ -324,7 +324,7 @@ impl DQNTrainer { entropy_coefficient: hyperparams.entropy_coefficient, noisy_epsilon_floor: hyperparams.noisy_epsilon_floor.unwrap_or(0.0) as f32, // C2: NoisyNet handles exploration count_bonus_coefficient: hyperparams.count_bonus_coefficient.unwrap_or(0.0), - curiosity_market_dim: 42, // Always 42 raw market features — OFI features are separate + curiosity_market_dim: 42, // Always 42 raw market features -- OFI features are separate ..DQNConfig::default() }; @@ -346,7 +346,7 @@ impl DQNTrainer { // Regime-conditional Q-network is always active info!("Creating regime-conditional DQN with 3 heads (Trending, Ranging, Volatile)"); info!(" - Regime detection: ADX (raw index 40) + CUSUM direction (raw index 41)"); - info!(" - Classification: Trending (ADX>0.25), Volatile (ADX≤0.25 & |CUSUM|>0.7), Ranging (otherwise)"); + info!(" - Classification: Trending (ADX>0.25), Volatile (ADX<=0.25 & |CUSUM|>0.7), Ranging (otherwise)"); let regime_agent = RegimeConditionalDQN::new_on_device(config, agent_device.clone()) .map_err(|e| anyhow::anyhow!("Failed to create regime-conditional DQN: {}", e))?; let agent = DQNAgentType::RegimeConditional(regime_agent); @@ -428,7 +428,7 @@ impl DQNTrainer { let stress_tester: Option> = None; info!( - "Action masking enabled (max_position=±{:.1}, 30-50% filtering expected)", + "Action masking enabled (max_position=+/-{:.1}, 30-50% filtering expected)", max_position ); @@ -443,18 +443,18 @@ impl DQNTrainer { Some(Arc::new(DrawdownMonitor::new())) }; - // 2. Position Limiter (3-tier limits: ±10.0 absolute, 1M notional, 10% concentration) + // 2. Position Limiter (3-tier limits: +/-10.0 absolute, 1M notional, 10% concentration) let position_limiter = { let config = PositionLimiterConfig { enabled: true, cache_ttl: Duration::from_secs(60), rpc_check_threshold_percent: 0.8, - max_position_per_symbol: 10.0, // ±10.0 absolute position limit + max_position_per_symbol: 10.0, // +/-10.0 absolute position limit max_order_value: 1_000_000.0, // $1M notional limit max_daily_loss: 0.10, // 10% concentration limit }; let limiter = HybridPositionLimiter::new(config); - info!("Position limiter enabled (abs=±10.0, notional=$1M, concentration=10%)"); + info!("Position limiter enabled (abs=+/-10.0, notional=$1M, concentration=10%)"); Some(Arc::new(limiter)) }; @@ -519,7 +519,7 @@ impl DQNTrainer { }) .transpose()?; - // P1.9: Generalized Advantage Estimation (GAE) — always active + // P1.9: Generalized Advantage Estimation (GAE) -- always active let gae_calculator = { use crate::dqn::gae::GAECalculator; info!("GAE calculator enabled (lambda={}, gamma={})", @@ -527,7 +527,7 @@ impl DQNTrainer { Some(GAECalculator::new(hyperparams.gae_lambda, hyperparams.gamma)) }; - // P1.11: Noisy network sigma scheduling — always active + // P1.11: Noisy network sigma scheduling -- always active let noisy_sigma_scheduler = { use crate::dqn::noisy_sigma_scheduler::NoisySigmaScheduler; info!("Noisy sigma scheduler enabled (initial={}, final={}, steps={})",