fix: continuous_bounds_for skips fixed Phase Fast bounds + PVC feature cache

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) <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-03-22 19:25:39 +01:00
parent d1a5a66f73
commit b136dfee7a
2 changed files with 20 additions and 21 deletions

View File

@@ -388,8 +388,9 @@ fn collect_dbn_files_for_hash(dir: &Path) -> Vec<PathBuf> {
/// 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<PathBuf> {
if std::env::var("FOXHUNT_FEATURE_CACHE")
.map(|v| v == "0")
@@ -398,7 +399,12 @@ fn dbn_cache_dir() -> Option<PathBuf> {
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`.

View File

@@ -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.