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:
jgrusewski
2026-04-02 14:50:39 +02:00
parent 9aad6ff60e
commit 99392ffcb4

View File

@@ -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 (f64f32)
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 (f64f32)
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 (ADX0.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={})",