From b136dfee7a52ffd26444bc07ddef073f3c3d642c Mon Sep 17 00:00:00 2001 From: jgrusewski Date: Sun, 22 Mar 2026 19:25:39 +0100 Subject: [PATCH] fix: continuous_bounds_for skips fixed Phase Fast bounds + PVC feature cache MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit continuous_bounds_for large GPU expansion used .max() which overwrote Phase Fast's single-point bounds (128,128) → (128,2048). Now skips expansion when lo==hi (bound is fixed by Phase Fast). Feature cache uses CARGO_TARGET_DIR/.foxhunt_feature_cache when set (PVC-persisted on CI), falls back to /tmp (ephemeral). Saves ~2 min of DBN parsing per hyperopt run on H100. Co-Authored-By: Claude Opus 4.6 (1M context) --- crates/ml/src/feature_cache.rs | 12 ++++++++--- crates/ml/src/hyperopt/adapters/dqn.rs | 29 ++++++++++---------------- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/crates/ml/src/feature_cache.rs b/crates/ml/src/feature_cache.rs index 0003a36fc..52f0d9e2c 100644 --- a/crates/ml/src/feature_cache.rs +++ b/crates/ml/src/feature_cache.rs @@ -388,8 +388,9 @@ fn collect_dbn_files_for_hash(dir: &Path) -> Vec { /// Return the cache directory, or `None` when caching is disabled. /// -/// Caching is disabled when `FOXHUNT_FEATURE_CACHE=0`. -/// Default cache root is `/tmp/.foxhunt_feature_cache`. +/// Resolution: `$CARGO_TARGET_DIR/.foxhunt_feature_cache` (PVC-persisted on CI), +/// then `/tmp/.foxhunt_feature_cache` (ephemeral fallback). +/// Disabled with `FOXHUNT_FEATURE_CACHE=0`. fn dbn_cache_dir() -> Option { if std::env::var("FOXHUNT_FEATURE_CACHE") .map(|v| v == "0") @@ -398,7 +399,12 @@ fn dbn_cache_dir() -> Option { debug!("FOXHUNT_FEATURE_CACHE=0 — DBN feature cache disabled"); return None; } - Some(PathBuf::from("/tmp/.foxhunt_feature_cache")) + let dir = if let Ok(target_dir) = std::env::var("CARGO_TARGET_DIR") { + PathBuf::from(target_dir).join(".foxhunt_feature_cache") + } else { + PathBuf::from("/tmp/.foxhunt_feature_cache") + }; + Some(dir) } /// Try to load a cached training dataset for `data_dir`. diff --git a/crates/ml/src/hyperopt/adapters/dqn.rs b/crates/ml/src/hyperopt/adapters/dqn.rs index 4a295d3d7..e55b12d38 100644 --- a/crates/ml/src/hyperopt/adapters/dqn.rs +++ b/crates/ml/src/hyperopt/adapters/dqn.rs @@ -868,25 +868,18 @@ impl ParameterSpace for DQNParams { // On large GPUs (≥40 GB: L40S 48GB, H100 80GB), EXPAND search space to // exploit available VRAM for larger models and better gradient statistics. - // The VRAM estimator below will cap these if they exceed actual capacity. + // IMPORTANT: skip expansion for fixed bounds (lo == hi) — Phase Fast fixes + // architecture dims to single points, and expansion would break them. if large_gpu { - // batch_size: [64, 1024] — larger batches give more stable gradients, - // reduce PER sampling variance, and amortize GPU kernel launch overhead. - if let Some(b) = bounds.get_mut(1) { b.1 = b.1.max(1024.0); } - // buffer_size: expand to ln(500_000) ≈ 13.12 — larger replay buffer - // reduces correlation between consecutive training batches, critical for - // PER convergence on diverse market regimes. - if let Some(b) = bounds.get_mut(3) { b.1 = b.1.max(500_000_f64.ln()); } - // hidden_dim_base: [256, 2048] — wider networks capture more complex - // feature interactions in 42D market + 8D OFI feature space. - if let Some(b) = bounds.get_mut(21) { b.1 = b.1.max(2048.0); } - // dueling_hidden_dim: [128, 768] — wider value/advantage streams - if let Some(b) = bounds.get_mut(13) { b.1 = b.1.max(768.0); } - // branch_hidden_dim: [64, 512] — wider per-branch heads (exposure, - // order_type, urgency each get more capacity) - if let Some(b) = bounds.get_mut(28) { b.1 = b.1.max(512.0); } - // num_atoms: [51, 301] — finer C51 distribution resolution - if let Some(b) = bounds.get_mut(15) { b.1 = b.1.max(301.0); } + let expand = |b: &mut (f64, f64), max_val: f64| { + if (b.0 - b.1).abs() > 1e-6 { b.1 = b.1.max(max_val); } + }; + if let Some(b) = bounds.get_mut(1) { expand(b, 1024.0); } // batch_size + if let Some(b) = bounds.get_mut(3) { expand(b, 500_000_f64.ln()); } // buffer_size + if let Some(b) = bounds.get_mut(21) { expand(b, 2048.0); } // hidden_dim_base + if let Some(b) = bounds.get_mut(13) { expand(b, 768.0); } // dueling_hidden_dim + if let Some(b) = bounds.get_mut(28) { expand(b, 512.0); } // branch_hidden_dim + if let Some(b) = bounds.get_mut(15) { expand(b, 301.0); } // num_atoms // learning_rate: expand upper to ln(1e-3) — linear scaling rule: // 2x batch → sqrt(2)x LR is safe. 1024 batch / 512 base = 2x → allow ~4e-4. // Allow up to 1e-3 for the optimizer to explore.