cleanup: remove ofi_enabled/ofi_pre conditionals — OFI always on

OFI (20 microstructure features) is unconditionally enabled.
mbp10_data_dir always set. state_dim unconditionally 96.
Removed dead else branches (ofi_dim=0, state_dim=72).
Simplifies 7 files across the workspace.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-04-17 09:28:49 +02:00
parent de13c165da
commit 614d4dbf84
7 changed files with 49 additions and 67 deletions

View File

@@ -755,8 +755,8 @@ impl GpuExperienceCollector {
let alloc_timesteps = timesteps_per_episode.min(MAX_TIMESTEPS_LIMIT);
let (shared_h1, shared_h2, value_h, adv_h) = network_dims;
let (state_dim, market_dim, num_atoms_max) = kernel_dims;
let portfolio_dim: usize = 12; // 8 base + 4 plan progress features
let ofi_dim: usize = if state_dim >= market_dim + portfolio_dim + 20 { 20 } else { 0 };
let _portfolio_dim: usize = 12; // 8 base + 4 plan progress features
let ofi_dim: usize = 20;
// Branch sizes for 4-branch hierarchical DQN (always enabled).
// [direction(3), magnitude(3), order(3), urgency(3)]

View File

@@ -399,7 +399,7 @@ impl DqnGpuData {
/// Estimated VRAM usage in bytes.
pub fn vram_bytes(&self) -> usize {
let ofi_dim = if self.ofi_features.is_some() { 20 } else { 0 };
let ofi_dim: usize = 20;
estimate_vram_bytes(self.num_bars * (self.feature_dim + 4 + ofi_dim))
}
@@ -474,7 +474,7 @@ impl DqnGpuData {
portfolio_features: &[f32; 3],
stream: &Arc<CudaStream>,
) -> Result<CudaSlice<f32>, MLError> {
let ofi_dim = if self.ofi_features.is_some() { 20 } else { 0 };
let ofi_dim: usize = 20;
let raw_dim = self.feature_dim + 3 + ofi_dim;
let final_dim = self.aligned_state_dim.unwrap_or(raw_dim);
@@ -513,7 +513,7 @@ impl DqnGpuData {
if count == 0 {
return Err(MLError::ModelError("Empty batch for state construction".to_owned()));
}
let ofi_dim = if self.ofi_features.is_some() { 20 } else { 0 };
let ofi_dim: usize = 20;
let raw_dim = self.feature_dim + 3 + ofi_dim;
let final_dim = self.aligned_state_dim.unwrap_or(raw_dim);

View File

@@ -1346,31 +1346,26 @@ impl DQNTrainer {
// The gather kernel places live portfolio at [feat_dim..feat_dim+8] and
// multi-timeframe features at [feat_dim+8..feat_dim+24].
// GpuBacktestEvaluator adds PORTFOLIO_AND_MTF_DIM=24 on top of feature_dim
// to reach the full state_dim (72 without OFI, 88 with OFI, both 8-aligned).
//
// For OFI-enabled models, feature_dim = 62 (42 market + 20 OFI)
// so state_dim = (62+24+7)&!7 = 88. Without OFI: (42+24+7)&!7 = 72.
let ofi_enabled = self.mbp10_data_dir.is_some();
// to reach the full state_dim: (62+24+7)&!7 = 88.
// feature_dim = 62 (42 market + 20 OFI). OFI always enabled.
let market_dim: usize = 42;
let feature_dim: usize = if ofi_enabled { 62 } else { market_dim };
let feature_dim: usize = 62; // 42 market + 20 OFI
// OFI feature overlay: preloaded OFI covers ALL bars (train+val), left-aligned.
// Validation bars start at index `train_end` in the global OFI array.
let ofi_offset = self.preloaded_train_end;
let ofi_ref = self.preloaded_ofi_features.as_deref();
if ofi_enabled {
let ofi_total = ofi_ref.map_or(0, |o| o.len());
let ofi_avail = ofi_total.saturating_sub(ofi_offset);
debug!(
ofi_offset,
ofi_total,
ofi_avail,
total_bars,
"walk-forward OFI overlay: {} of {} val bars have real OFI",
ofi_avail.min(total_bars),
total_bars,
);
}
let ofi_total = ofi_ref.map_or(0, |o| o.len());
let ofi_avail = ofi_total.saturating_sub(ofi_offset);
debug!(
ofi_offset,
ofi_total,
ofi_avail,
total_bars,
"walk-forward OFI overlay: {} of {} val bars have real OFI",
ofi_avail.min(total_bars),
total_bars,
);
let mut window_prices = Vec::with_capacity(window_count);
let mut window_features = Vec::with_capacity(window_count);
@@ -1405,15 +1400,13 @@ impl DQNTrainer {
let mut fv_f32: Vec<f32> = fv_slice.iter().map(|&v| v as f32).collect();
// Overlay real OFI features from preloaded MBP-10 data.
// Falls back to zero-padding only when OFI data is unavailable.
if ofi_enabled {
let ofi_idx = ofi_offset + i;
if let Some(ofi) = ofi_ref.filter(|o| ofi_idx < o.len()) {
for &v in ofi.get(ofi_idx).iter().flat_map(|f| f.iter()) {
fv_f32.push(v as f32);
}
} else {
fv_f32.resize(feature_dim, 0.0);
let ofi_idx = ofi_offset + i;
if let Some(ofi) = ofi_ref.filter(|o| ofi_idx < o.len()) {
for &v in ofi.get(ofi_idx).iter().flat_map(|f| f.iter()) {
fv_f32.push(v as f32);
}
} else {
fv_f32.resize(feature_dim, 0.0);
}
features.push(fv_f32);
}
@@ -1441,7 +1434,7 @@ impl DQNTrainer {
max_leverage: internal_trainer.hyperparams().max_leverage as f32,
// OFI reorder in gather kernel: produces [market, portfolio, OFI, pad]
// directly, eliminating the Candle narrow+cat closure.
ofi_dim: if ofi_enabled { 20 } else { 0 },
ofi_dim: 20,
min_hold_bars: internal_trainer.hyperparams().min_hold_bars as i32,
bars_per_day: internal_trainer.hyperparams().bars_per_day as f32,
trading_days_per_year: internal_trainer.hyperparams().trading_days_per_year as f32,
@@ -2028,7 +2021,7 @@ impl HyperparameterOptimizable for DQNTrainer {
let scaled_atoms = ((base_hp.num_atoms as f64 * params.architecture_intensity) as usize).max(11);
let scaled_buffer = (base_hp.buffer_size as f64 * params.replay_intensity).max(1000.0) as usize;
let vram_buffer = if budget.gpu_memory_mb <= 8192 { 0 } else { scaled_buffer.min(self.buffer_size_max) };
let aligned_state_dim: usize = if self.mbp10_data_dir.is_some() { 80 } else { 72 };
let aligned_state_dim: usize = 96; // (92+7)&!7 = 96, OFI always on
let vram_check = budget.trial_fits_vram(
scaled_hidden,
base_hp.batch_size,

View File

@@ -48,9 +48,8 @@ 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)
let ofi_pre = !hyperparams.mbp10_data_dir.is_empty();
// 42 market + 14 portfolio (8 base + 6 plan) + 16 multi-timeframe = 72 base, +20 OFI = 92
let input_dim: usize = if ofi_pre { 96 } else { 72 }; // (92+7)&!7=96, (72+7)&!7=72
// 42 market + 14 portfolio (8 base + 6 plan) + 16 multi-timeframe + 20 OFI = 92, aligned (92+7)&!7=96
let input_dim: usize = 96;
let output_dim: usize = 5;
let hidden_dims: Vec<usize> = match hyperparams.hidden_dim_base {
Some(base) => {
@@ -242,18 +241,17 @@ impl DQNTrainer {
// [0..42) 42 market features (OHLCV, technical, patterns, volume, time, statistical)
// [42..50) 8 portfolio features (position, P&L, drawdown, etc.)
// [50..66) 16 multi-timeframe features (4 windows x 4 features)
// [66..86) 20 OFI features (from MBP-10 order book data, when mbp10_data_dir set)
// Raw state_dim: 66 without OFI, 86 with OFI.
// [66..86) 20 OFI features (from MBP-10 order book data, always loaded)
// Raw state_dim: 92 (42+14+16+20). OFI always enabled.
// market_dim: always 42 (bottleneck compresses only base market features).
// OFI features bypass bottleneck via portfolio_dim (fed directly to shared trunk).
//
// GpuTensor core alignment: state_dim is rounded up to the next multiple of 8
// (66->72, 86->88) so that cuBLAS dispatches BF16 HMMA instructions instead
// (92->96) so that cuBLAS dispatches TF32 HMMA instructions instead
// of falling back to scalar FMA. The extra columns are zero-padded at the
// data pipeline boundaries (GpuPreloadedData and train_batch CPU path).
let ofi_enabled = !hyperparams.mbp10_data_dir.is_empty();
let raw_state_dim: usize = if ofi_enabled { 92 } else { 72 }; // 42 market + 14 portfolio + 16 MTF + (20 OFI)
let full_state_dim = (raw_state_dim + 7) & !7; // aligned: 88 with OFI, 72 without
let raw_state_dim: usize = 92; // 42 market + 14 portfolio + 16 MTF + 20 OFI
let full_state_dim = (raw_state_dim + 7) & !7; // aligned: 96
// state_dim is always the FULL padded dimension. The GpuDqnTrainer
// handles bottleneck reduction internally via compute_param_sizes().

View File

@@ -442,9 +442,8 @@ impl DQNTrainer {
// ── Lazy-init the GPU evaluator (once per fold, reused across epochs) ──
if self.gpu_evaluator.is_none() {
let ofi_enabled = self.ofi_features.is_some();
let market_dim: usize = 42;
let feature_dim: usize = if ofi_enabled { 62 } else { market_dim }; // 42 market + 20 OFI = 62 (portfolio+MTF in state_dim, not feature_dim)
let feature_dim: usize = 62; // 42 market + 20 OFI (portfolio+MTF in state_dim, not feature_dim)
// Build a single window from all val_data
let mut prices: Vec<[f32; 4]> = Vec::with_capacity(self.val_data.len());
@@ -458,18 +457,16 @@ impl DQNTrainer {
let close = if target.len() >= 2 { target[0] as f32 } else { fv[3] as f32 };
prices.push([close, close, close, close]);
// Features: 42 market features, plus 20 OFI if enabled
// Features: 42 market features + 20 OFI
let fv_slice = &fv[..market_dim.min(fv.len())];
let mut fv_f32: Vec<f32> = fv_slice.iter().map(|&v| v as f32).collect();
if ofi_enabled {
let ofi_idx = self.ofi_val_offset + i;
if let Some(ofi_row) = self.ofi_features.as_ref().and_then(|o| o.get(ofi_idx)) {
for &v in ofi_row.iter() {
fv_f32.push(v as f32);
}
} else {
fv_f32.resize(feature_dim, 0.0);
let ofi_idx = self.ofi_val_offset + i;
if let Some(ofi_row) = self.ofi_features.as_ref().and_then(|o| o.get(ofi_idx)) {
for &v in ofi_row.iter() {
fv_f32.push(v as f32);
}
} else {
fv_f32.resize(feature_dim, 0.0);
}
features.push(fv_f32);
}
@@ -487,7 +484,7 @@ impl DQNTrainer {
contract_multiplier: hp.contract_multiplier as f32,
margin_pct: hp.margin_pct as f32,
max_leverage: hp.max_leverage as f32,
ofi_dim: if ofi_enabled { 20 } else { 0 },
ofi_dim: 20,
min_hold_bars: hp.min_hold_bars as i32,
bars_per_day: hp.bars_per_day as f32,
trading_days_per_year: hp.trading_days_per_year as f32,

View File

@@ -1222,16 +1222,14 @@ impl DQNTrainer {
features, targets, ofi, stream,
).map_err(|e| anyhow::anyhow!("DqnGpuData::upload_slices failed: {e}"))?;
let ofi_enabled = gpu_data.ofi_features.is_some();
let raw_dim = if ofi_enabled { 65 } else { 45 };
let raw_dim: usize = 65; // 42 market + 3 portfolio + 20 OFI
let aligned_dim = (raw_dim + 7) & !7;
gpu_data.set_aligned_state_dim(aligned_dim);
tracing::info!(
"init_from_fxcache: {} bars uploaded to GPU ({:.1} MB, OFI={})",
"init_from_fxcache: {} bars uploaded to GPU ({:.1} MB, OFI=true)",
features.len(),
(features.len() * (42 + 4 + 20) * 4) as f64 / 1_048_576.0,
ofi_enabled,
);
self.gpu_data = Some(gpu_data);

View File

@@ -78,18 +78,14 @@ impl DQNTrainer {
vec![0.0, 0.0, 0.0] // Fallback if no price provided
};
// OFI regime features: 8 features from MBP-10 order book data.
// When OFI is enabled (mbp10_data_dir set), always return 8 features
// (zeros if data didn't load). GPU training uses state_dim=80 (74 raw, 8-aligned).
let ofi_enabled = !self.hyperparams.mbp10_data_dir.is_empty();
// OFI regime features: 20 features from MBP-10 order book data (always enabled).
// Returns real OFI when available, zeros otherwise. state_dim=96.
let regime_features: Vec<f32> = if let (Some(ofi), Some(idx)) = (&self.ofi_features, ofi_index) {
ofi.get(idx)
.map(|f| f.iter().map(|&v| v as f32).collect())
.unwrap_or_else(|| vec![0.0; 8])
} else if ofi_enabled {
vec![0.0; 8]
.unwrap_or_else(|| vec![0.0; 20])
} else {
vec![]
vec![0.0; 20]
};
// Use from_normalized() to preserve sign information