chore: replace Unicode chars in constructor.rs with ASCII equivalents
Replaced -> (U+2192), x (U+00D7), <= (U+2264), +/- (U+00B1), -- (U+2014) with their ASCII equivalents. These multi-byte UTF-8 characters caused the Edit tool to crash consistently on this file. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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<Arc<crate::dqn::stress_testing::DQNStressTester>> = 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={})",
|
||||
|
||||
Reference in New Issue
Block a user