Commit Graph

1977 Commits

Author SHA1 Message Date
jgrusewski
8c0bed4d2d fix(infra): update training job template to use ci-training-h100 pool
The nodeSelector pointed to non-existent 'ci-training' pool. The actual
Scaleway pool is 'ci-training-h100', matching the CI pipeline template.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 17:20:15 +01:00
jgrusewski
e89fce5eb2 fix(cuda): per-thread EMA reward normalization in GPU experience kernel
GPU kernel produced raw percentage PnL rewards (~3.5e-4) which were
~10,000x smaller than Q-values (~7.0), making rewards invisible in the
Bellman backup. Q-values froze at initialization.

Port the CPU RewardNormalizer algorithm (EMA mean/variance, z-score
normalization, [-3,3] clamp) directly into the CUDA kernel hot path
as per-thread register state. Each thread maintains its own running
mean/variance with configurable decay rate (reward_norm_alpha, default
0.01 = ~100-step window). EMA resets on episode boundaries.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 16:49:11 +01:00
jgrusewski
121962b7c5 Merge branch 'feature/ml-crate-split' — split ml monolith into 21 sub-crates
21 sub-crates extracted from the 260K-line ml monolith, reducing it to ~90K.
Deletes ~7.5K lines of dead code. Zero compilation errors, zero warnings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:23:14 +01:00
jgrusewski
1f1ba40eaa fix: resolve rebase artifacts — import paths and MLError variant
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:21:46 +01:00
jgrusewski
b95ee9d336 refactor(ml): remove dead code across 8 sub-crates — delete 742 lines
Remove unused struct fields, dead methods, unreachable code across
ml-dqn, ml-supervised, ml-features, ml-ppo, ml-checkpoint, ml-labeling,
ml-observability, and ml-universe. Gate test-only infra behind #[cfg(test)].

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:17:22 +01:00
jgrusewski
58f4f26113 refactor(ml): extract regime-detection, explainability, paper-trading
- ml-regime-detection (1.2K lines): feature_classifier, hmm modules.
  Depends on ml-core + ml-dqn (RegimeType). 23 tests passing.

- ml-explainability (329 lines): integrated_gradients module.
  Depends on ml-core + candle-core. 4 tests passing.

- ml-paper-trading (389 lines): broker, pnl_tracker modules.
  Depends on ml-ensemble (TradeAction, TradeSignal). 8 tests passing.

Total: 21 sub-crates extracted from ml monolith.
ml reduced from ~260K to ~90K lines (65% extracted).
All tests: 841 ml + 35 in new sub-crates = 876 passing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:17:22 +01:00
jgrusewski
4676fe79e2 refactor(ml): extract observability, stress-testing, security into sub-crates
- ml-observability (1.2K lines): alerts, dashboards, metrics modules.
  Depends on ml-core + common (ModelType). 4 tests passing.

- ml-stress-testing (1.3K lines): load_generator, market_simulator,
  performance_analyzer modules. Depends on ml-core + common + config.
  5 tests passing.

- ml-security (1.4K lines): anomaly_detector, prediction_validator
  modules. Depends on ml-core + ml-ensemble (EnsembleDecision,
  ModelVote, TradingAction). 17 tests passing.

Total: 18 sub-crates extracted from ml monolith.
Workspace: 0 errors, ml tests 876 + 26 in new sub-crates.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:17:22 +01:00
jgrusewski
b7597a7543 refactor(ml): extract universe, backtesting, asset-selection into sub-crates
- ml-universe (1.7K lines): correlation, liquidity, momentum, volatility
  modules. Only depends on ml-core (MLError, PRECISION_FACTOR).
  6 tests passing.

- ml-backtesting (1.1K lines): action_loader, barrier_backtest, report
  modules. Only depends on ml-core (MLError). Discovered and included
  previously undeclared report.rs module. 10 tests passing.

- ml-asset-selection (1.2K lines): scorer, selector modules with
  AssetClass, AssetUniverse, PredictabilityScorer, ActiveSetSelector.
  Only depends on ml-core (MLError). 33 tests passing.

All three replaced with thin facade re-exports in ml — existing
`use ml::universe::*` / `use ml::backtesting::*` /
`use ml::asset_selection::*` paths continue to work.

Total: 15 sub-crates extracted from ml monolith.
Workspace: 0 errors, ml tests 902 passed + 49 in sub-crates.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:17:22 +01:00
jgrusewski
aa19b42255 refactor(ml): move UnifiedTrainable to ml-core + delete 6K dead deployment code
- Move UnifiedTrainable trait, TrainingMetrics, CheckpointMetadata, and
  checkpoint helpers from ml to ml-core (zero new dependencies — ml-core
  already had candle-core + serde_json)
- Wrap Mamba2SSM in Mamba2TrainableAdapter to satisfy orphan rule (trait
  in ml-core, type in ml-supervised — all other 9 models already used
  wrapper pattern)
- Make Mamba2SSM::validate() pub for cross-crate adapter access
- Delete 5 permanently disabled deployment modules (cfg(any()) — never
  compiled): registry, hot_swap, validation, monitoring, endpoints
  (-5,842 lines)
- Delete 2 undeclared dead files in training/: dqn_trainer.rs,
  transformer_trainer.rs (-138 lines)
- Fix pre-existing compute_loss test shape mismatch in mamba adapter

UnifiedTrainable in ml-core unblocks future trainers/ extraction (17.8K
lines) since model-specific trainers can now depend on ml-core for the
trait without pulling in the full ml monolith.

14 files changed, +128 -6,497 (net -6,369 lines)
Tests: 274 ml-core + 948 ml = 1,222 passed, 0 failed

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:17:22 +01:00
jgrusewski
d313486dc2 refactor(ml): split monolith into 9 sub-crates + delete dead code
Extract 9 new sub-crates from the ml monolith to enable parallel
compilation across the workspace:

New crates (this commit):
- ml-features (282 tests): feature engineering, 21 modules
- ml-labeling (45 tests): triple barrier, meta-labeling, fractional diff
- ml-ensemble (116 tests): ensemble coordination, voting, confidence
- ml-hyperopt (47 tests): core PSO/TPE optimizer, parameter space
- ml-checkpoint (41 tests): checkpoint persistence, compression, signing
- ml-regime (68 tests): CUSUM, Bayesian changepoint, regime classification
- ml-data-validation (67 tests): FDR correction, CPCV, data quality
- ml-risk (33 tests): neural VaR, Kelly criterion, circuit breakers
- ml-validation (43 tests): statistical validation, walk-forward, DSR

Extended existing crates:
- ml-dqn: added evaluation/ (backtesting engine, metrics, reports)
  and checkpoint implementation
- ml-supervised: added checkpoint implementations
- ml-core: added shared types needed by new sub-crates

Pattern: each module in ml/ becomes a thin facade (pub use subcrate::*)
with bridge modules staying in ml for cross-model adapter code.

Dead code deleted (~7K lines):
- 13 undeclared files in microstructure/ (never compiled)
- 7 undeclared files + tests/ in risk/ (never compiled)
- parquet_io, cache_service, cache_storage, minio_integration (unused)
- extraction_wave_d_impl.rs (bare fn outside impl block)

All 2,746 sub-crate tests + 951 ml tests pass.
Full workspace builds clean.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:17:22 +01:00
jgrusewski
3db7f4828b refactor(ml): extract 8 supervised models into ml-supervised crate (task 8)
Move TFT, Mamba-2, Liquid, TGGN, TLOB, KAN, xLSTM, and Diffusion model
implementations to ml-supervised. Bridge files (UnifiedTrainable adapters,
Checkpointable impls) stay in ml. Delete AsyncDataLoader (replaced by
StreamingDbnLoader + simple .chunks() batching). Remove empty ml-infra
scaffold — the remaining ml modules are too tightly coupled for clean
extraction, so ml stays as the orchestration facade.

- ml-supervised: 234 tests, 0 failures
- ml: 1687 tests, 0 failures
- Workspace: 0 compilation errors

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:16:08 +01:00
jgrusewski
58d0f535df refactor(ml): extract PPO module into ml-ppo crate (task 7)
Move 24 PPO source files + flow_policy/ from ml into standalone
ml-ppo crate. The ml crate's ppo module is now a thin re-export layer
(`pub use ml_ppo::*`) plus two bridge files (trainable_adapter.rs,
stress_testing.rs) that depend on ml-internal types.

Key changes:
- ml-ppo: 25 modules (incl flow_policy subdir), 198 tests, standalone
- ml: depends on ml-ppo, re-exports via ppo/mod.rs
- Import rewrites: crate::common::action → ml_core::action_space,
  crate::dqn::{mixed_precision,xavier_init} → ml_core::*,
  crate::gradient_accumulation → ml_core::gradient_accumulation
- ml tests: 1929 pass (down from 2127 — 198 moved to ml-ppo)
- Workspace: 0 errors

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:16:08 +01:00
jgrusewski
88f0b3ec23 refactor(ml): extract DQN module into ml-dqn crate (task 6)
Move 53 DQN source files + gpu_replay_buffer from ml into standalone
ml-dqn crate. The ml crate's dqn module is now a thin re-export layer
(`pub use ml_dqn::*`) plus two bridge files (trainable_adapter.rs,
stress_testing.rs) that depend on ml-internal types.

Key changes:
- ml-dqn: 45 modules, 334 tests, compiles standalone
- ml: depends on ml-dqn, re-exports via dqn/mod.rs
- DQN.config: pub(crate) → pub for cross-crate access
- DQNAgent checkpoint methods moved into ml-dqn
- gpu_replay_buffer moved from cuda_pipeline/ to ml-dqn
- 8 dead-code files removed (never declared in mod.rs)

Net: -30,648 lines from ml crate. Workspace: 0 errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:16:08 +01:00
jgrusewski
d2f3b6c799 refactor(ml): move metrics/ + performance.rs to ml-core (5g)
Move Sharpe ratio metrics and SIMD performance module to ml-core.
These only depend on MLError which is already in ml-core.

Add approx = "0.5" to ml-core dev-dependencies for Sharpe tests.
Skip observability/ (needs prometheus dep — stays in ml).

Test counts: 271 ml-core + 2487 ml = 2758 total, 0 failures

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:13:41 +01:00
jgrusewski
4843b4a2a6 refactor(ml): move shared infrastructure to ml-core + eliminate FactoredActionExt (5e)
Move 6 shared modules (~2.3K lines) from ml to ml-core:
- trading_action.rs (TradingAction enum)
- action_space.rs (action masking, re-exports)
- xavier_init.rs (Xavier/Glorot weight initialization)
- mixed_precision.rs (AMP utilities, FP16/BF16)
- order_router.rs (deterministic order routing)
- portfolio_tracker.rs (portfolio state tracking)

With TradingAction now in ml-core alongside FactoredAction, the
FactoredActionExt extension trait is eliminated entirely —
from_trading_action()/to_trading_action() become inherent methods
on FactoredAction. This removes the need for `use FactoredActionExt`
imports in reward.rs, gae.rs, and trajectories.rs.

Test counts: 250 ml-core + 2508 ml = 2758 total, 0 failures

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:13:41 +01:00
jgrusewski
f951fddc86 refactor(ml): move memory_optimization/ + batch_size_resolver to ml-core (5d)
- Move memory_optimization/ (4.6K lines, 7 files) from ml to ml-core
- Move batch_size_resolver.rs to ml-core (now both deps in same crate)
- Add tempfile to ml-core dev-dependencies (qat tests)
- Re-export both modules from ml facade
- Keep checkpoint/ in ml (model_implementations.rs has model-specific deps)

185 ml-core tests + 2573 ml tests = 2758 total, 0 failures

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:13:41 +01:00
jgrusewski
70546ad1c4 refactor(ml): move safety/ to ml-core + rename Real-prefixed types (5c)
- Move safety/ module (6.5K lines) from ml to ml-core (self-contained)
- Keep security/ in ml (depends on ensemble::EnsembleDecision)
- Move From<ProductionTrainingError> for MLSafetyError to ml (cross-crate)
- Rename Real-prefixed inference types to proper names:
  RealInferenceError → InferenceError
  RealInferenceConfig → InferenceConfig
  RealPredictionResult → InferencePrediction
  RealNeuralNetwork → NeuralNetwork
  RealMLInferenceEngine → MLInferenceEngine
- Re-export safety from ml facade for backward compatibility

129 ml-core tests + 2629 ml tests = 2758 total, 0 failures

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:13:41 +01:00
jgrusewski
9fc9522821 refactor(ml): remove all legacy naming and deprecated wrappers
- Rename FactoredActionLegacy → FactoredActionExt trait
- Rename from_legacy() → from_trading_action(), to_legacy_action() → to_trading_action()
- Delete 5 deprecated free functions from ml-core (create_hft_*, create_ultra_low_latency_*)
- Update ml_tests.rs to call associated methods directly
- Remove #[allow(deprecated)] now unnecessary

8 files, -9 net lines, 2758 tests pass (81 ml-core + 2677 ml)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:13:41 +01:00
jgrusewski
33cc076eb3 refactor(ml): move compute primitives to ml-core (5b)
Move optimizers, gradient_accumulation, gradient_utils, cuda_compat,
tensor_ops, and gpu (device config, capabilities, memory profiling) to
ml-core. These are shared compute primitives used by all models.

Also commit module files for core types (common, config, error, model,
traits, types) that were moved from ml to ml-core in task 5a but left
staged without being committed.

Notable changes:
- resolve_batch_size() stays in ml (new batch_size_resolver module)
  because it depends on memory_optimization::auto_batch_size which
  has not yet moved to ml-core
- FactoredAction legacy bridge converted from inherent impl to
  extension trait (FactoredActionLegacy) since FactoredAction is now
  defined in ml-core, not ml
- candle-optimisers added to ml-core dependencies (needed by Adam)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:13:41 +01:00
jgrusewski
7d1b0f232f refactor(ml): move core types to ml-core + dedup MLError (5a)
Move all inline type definitions from ml/src/lib.rs to ml-core:
MLError, MLResult, Trade, MarketRegime, HealthStatus, Features,
MLModel trait, ModelRegistry, ParallelExecutor, LatencyOptimizer,
TrainingMetrics, ValidationMetrics, InferenceResult, ModelMetadata.

Dedup: consolidate ConfigError{reason}/ConfigurationError(msg) into
single ConfigError(String) tuple variant (was 2 variants, 174 refs).

Cleanup: convert create_hft_* free functions to associated methods
(HFTPerformanceProfile::ultra_low_latency(), ParallelExecutor::hft()).

ml facade re-exports via `pub use ml_core::*`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:13:41 +01:00
jgrusewski
5c18f17a01 refactor(ml): extract shared items from dqn/ to top-level modules
Move mixed_precision, xavier_init, portfolio_tracker, action_space,
order_router from dqn/ to ml/src/ root. Extract TradingAction enum
from dqn/agent.rs to standalone trading_action module. Re-export
from dqn/mod.rs for backward compatibility.

These modules are shared infrastructure used by PPO, trainers,
hyperopt, and ensemble — not DQN-specific.

Test results: 2757 passed, 0 failed, 25 ignored (unchanged).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:13:07 +01:00
jgrusewski
e440de4c6b feat(ml): scaffold sub-crate directory structure for ml split
Add 5 empty sub-crates (ml-core, ml-dqn, ml-ppo, ml-supervised, ml-infra)
to workspace. Modules will be moved from monolithic ml crate in subsequent
tasks.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:13:07 +01:00
jgrusewski
092b1d2a24 docs(ml): update plan for 6-crate split (DQN/PPO separate)
Split ml-rl into ml-dqn and ml-ppo for 3-way parallel compilation
and better sccache hit rate. Extract TradingAction to ml-core to
enable full DQN/PPO independence.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:13:07 +01:00
jgrusewski
985e5b3343 docs(ml): add design doc and implementation plan for crate split
Split the monolithic ml crate (260K lines, 55s compile) into 5 crates:
ml-core, ml-rl, ml-supervised, ml-infra, ml (facade).
15-task plan with full module inventory and import migration guide.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:13:07 +01:00
jgrusewski
809295dc5f fix(ml): correct GPU experience path — aligned state_dim, reward tracking, Prometheus label
Three bugs in the GPU experience collection hot path:

1. gpu_batch_to_experiences() had hardcoded state_dim=43 but the CUDA kernel
   outputs states at the ALIGNED dimension (56 with OFI, 48 without). After
   sample 0, every replay buffer entry had corrupted state vectors — the
   network was learning from garbage data.

2. GPU path never called monitor.track_reward(), so mean_reward was always
   reported as 0.0 in epoch logs despite the agent generating real rewards.

3. Action tracking was double-counted (direct array write + track_action_by_exposure),
   inflating diversity metrics by 2x. Consolidated into single bounded call.

Also adds missing app.kubernetes.io/component label to job-template.yaml
so Prometheus training-pods scrape job discovers training pods.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:11:01 +01:00
jgrusewski
7295d2c79f feat(ml): add sharpe_weight to hyperopt search space (C4) — 27D → 28D
PSO can now tune sharpe_weight in [0.0, 0.5] instead of hardcoded 0.3.
This lets hyperopt discover the optimal Sharpe ratio blending weight
in the composite reward signal per-symbol.

Also updates docstring to reflect current C1-C4 search space state.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 14:29:09 +01:00
jgrusewski
48dfe12171 fix(ml): inject STATE_DIM/MARKET_DIM/NUM_ATOMS_MAX into CUDA kernel via NVRTC
The GPU experience kernel had hardcoded STATE_DIM=54, MARKET_DIM=51,
NUM_ATOMS_MAX=51 but the host-side feature buffer uploads 40 features
per bar and networks use state_dim=56 with up to 100 atoms. Past ~702K
bars the stride mismatch caused out-of-bounds GPU reads → ILLEGAL_ADDRESS.

All dimension constants now use #ifndef guards so the NVRTC JIT compiler
receives the actual values via #define injection — zero runtime overhead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 14:18:23 +01:00
jgrusewski
f921bbe5d0 fix(ml): inject dynamic network dims into CUDA kernel via NVRTC
Hyperopt varies hidden_dim_base (256–1024) but the CUDA experience
kernel hardcoded SHARED_H1=256, SHARED_H2=256. When hyperopt picked
larger dims, the kernel did matvec with wrong strides → illegal memory
access → training crash on first epoch.

Fix: wrap CUDA #defines in #ifndef guards and inject actual dims from
the dueling network config at NVRTC compile time. The kernel is now
specialized per-trial with the exact weight matrix dimensions — zero
runtime overhead, no dimension mismatch possible.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:33:59 +01:00
jgrusewski
f916b17e6b fix(ml): align dueling shared layers with CUDA kernel — eliminate CPU fallback
from_dqn_params() was stripping the last hidden_dim, creating only 1
shared layer (shared_0) when hidden_dims=[256,256]. The CUDA experience
kernel hardcodes 2 shared layers and gpu_weights.rs expects shared_1.*
weight keys. This caused "Missing weight: shared_1.weight" every epoch,
forcing CPU fallback for all experience collection on H100.

Fix: use all hidden_dims as shared layers. Default [256,256] now creates
shared_0 + shared_1, matching the CUDA kernel architecture exactly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 12:58:52 +01:00
jgrusewski
c2606790e2 fix(ml): use explicit trades_data_dir when set in DQN adapter
The DQN hyperopt adapter has an explicit trades_data_dir field from CLI
args, but load_ofi_features() was only using the derived sibling path.
Now uses the explicit field when available, falling back to derivation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 12:09:11 +01:00
jgrusewski
ddb4300c29 fix(ml): feed trade data into OFI pipeline — VPIN/Kyle's Lambda were always zero
The OFI calculator's compute_ofi_from_file() never called feed_trade(),
leaving 3/8 features (VPIN, Kyle's Lambda, trade_imbalance) at zero in
production. Added compute_ofi_with_trades() that interleaves trades by
timestamp into the MBP-10 streaming callback. Updated DQN and PPO
hyperopt adapters to derive trades_dir as sibling of dbn_data_dir.

Smoke test validates: without trades VPIN=0/5000, with trades VPIN=5000/5000.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 12:06:22 +01:00
jgrusewski
f2a028752c test(ml): add 8 GPU kernel parity tests — validate CUDA forward pass
Tests require a CUDA GPU and are #[ignore]d by default. Run with:
  cargo test -p ml --test gpu_kernel_parity_test -- --ignored

Covers:
- Standard dueling forward: finite Q-values, valid action range [0,4]
- C51 distributional forward: Q-values bounded by atom support [-25,25]
- Candle vs kernel Q-value parity: argmax action consistency
- NoisyNet exploration: noise injection produces action diversity
- Weight extraction roundtrip: VarMap → CudaSlice → sync
- Distributional weight shapes: value_out [51,128], advantage_out [255,128]
- RMSNorm gamma extraction: initialized to 1.0
- Repeated kernel launches: 5 consecutive runs all produce finite output

All 8 tests pass on RTX 3050 Ti (4.49s total).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 11:32:30 +01:00
jgrusewski
d37d5a57df feat(ml): port NoisyNets (D5) and C51 distributional (D6) to GPU kernel
Port factorized Gaussian noise exploration and 51-atom categorical value
distribution to the CUDA experience collection kernel, fixing a correctness
bug where GPU Q-values were wrong when use_distributional=true (production
default) due to misinterpreted distributional weight shapes.

- D5: NoisyNet factorized noise (Box-Muller + f(x)=sign(x)*sqrt(|x|)) on
  all 6 dueling layers, online network only — target stays deterministic
- D6: C51 distributional dueling forward with per-action atom softmax,
  RMSNorm after shared/value/advantage layers, correct [51,128]/[255,128]
  weight interpretation
- RmsNormWeightSet extraction and post-epoch sync (GPU-to-GPU)
- Fix get_effective_epsilon() to report actual 2% noisy floor instead of 0.0
- Proportional diversity entropy penalty (continuous gradient vs cliff)
- 2758 tests passing, 0 failures

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 11:09:26 +01:00
jgrusewski
8926c6e1cf perf(ml): eliminate CPU from DQN training hot path — GPU-resident ops only
GPU experience collector was falling back to CPU because it required a
curiosity VarMap, but curiosity_weight=0.0 means the module is never
created. Fix: make curiosity optional (CuriosityWeightSet::zeros() for
GPU buffers, curiosity_scale=0.0 in kernel config). Also require
explicit binary-tag SHA in Argo training workflow (no "latest" fallback).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 09:15:52 +01:00
jgrusewski
6ab9e184f5 fix(ml): align hyperopt backtest state_dim for tensor cores
The walk-forward backtest in the DQN hyperopt adapter constructed batch
tensors with raw state_dim (51/43) but the model expects aligned
state_dim (56/48). This caused shape mismatch errors during backtest
evaluation: matmul [1024, 51] vs [56, 1024].

Fix: use align_dim_for_tensor_cores() and zero-pad each state vector
before tensor construction, matching the same pattern used in
compute_loss_internal.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 04:00:56 +01:00
jgrusewski
c1287efbcf fix(ml): pad remaining state tensor paths for tensor core alignment
Additional padding in get_q_values and convert_to_state — these are
currently unused in the training pipeline but would crash if called.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 03:24:49 +01:00
jgrusewski
2a9e185db9 fix(ml): pad state tensors in ALL code paths for tensor core alignment
Validation, action selection, and Q-value monitoring paths were using
raw state dimensions (51) while the model expected aligned dims (56).
Training epoch 1 passed because the GPU pipeline pads correctly, but
validation crashed: shape mismatch [1000,51] vs [56,1024].

Fixed: validation batch, select_actions_batch CPU fallback,
estimate_avg_q_value, compute_q_gap — all now zero-pad to aligned dim.
Also fixed model size estimation log to show aligned dims.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 03:20:43 +01:00
jgrusewski
7382ffd1e2 feat(infra): QuestDB metrics sink + monitoring network policies
Add QuestDB ILP sink for training metrics, update Prometheus scrape
configs, and fix network policies for monitoring stack connectivity.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 02:42:53 +01:00
jgrusewski
03c2ad5920 perf(ml): proper tensor core alignment — pad at data pipeline, not forward pass
Move BF16 tensor core alignment from per-forward-pass allocation to
data pipeline boundaries. On H100, state_dim 43→48 and 51→56 (8-aligned)
so cuBLAS dispatches HMMA instructions instead of falling back to scalar FMA.

Architecture:
- Trainer computes aligned state_dim at source (align_dim_for_tensor_cores)
- GPU path: DqnGpuData.pad_state_tensor() pads once at upload boundary
- CPU path: train_batch() fold zero-pads Experience.state vectors
- Networks receive pre-aligned tensors — zero per-step overhead

All state_dim defaults updated to aligned values (43→48, 51→56).
Removed pad_to_aligned() from all network forward() methods.
2758 tests pass, 0 failures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 02:42:19 +01:00
jgrusewski
542be32bed perf(ml): pad DQN input dims for H100 tensor core alignment
cuBLAS requires M/N/K dimensions to be multiples of 8 for BF16 tensor
core dispatch. state_dim=51 (with OFI) caused silent fallback to scalar
FMA ops, leaving tensor cores at 0.1% utilization despite BF16 enabled.

Pad state_dim to next 8-multiple (51→56, 43→48) at network construction
and zero-pad input tensors in forward pass across all DQN network types:
Sequential, DuelingQNetwork, DistributionalDuelingQNetwork, NetworkLayers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 02:10:38 +01:00
jgrusewski
8f95908e16 perf(ml): eliminate CPU from DQN training hot path — GPU-resident ops only
- Fix NaN detection: replace non-existent isnan() with ne(&self) (NaN≠NaN)
- C51 gradient strip: copy().detach() replaces to_vec2→from_vec GPU→CPU→GPU roundtrip
- GPU batch fast path: skip CPU fold + 5× from_vec when GpuBatch available
- Action validation: GPU clamp() replaces CPU loop in GPU batch path
- PER TD errors: keep as GPU Tensor when GpuPrioritized replay active
- PER indices: use GpuBatch.indices directly instead of CPU Vec→Tensor
- IS weights: cache GPU tensor from GpuBatch, reuse in both loss paths
- Logging: Q-values 10→500 steps, diagnostics 100→1000 steps

2758 tests pass, 0 clippy warnings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 01:08:00 +01:00
jgrusewski
88f6d90576 refactor(ml): DQN/PPO adapters use shared load_ofi_features_parallel
Both adapters now call load_ofi_features_parallel from mbp10_loader
instead of duplicating the rayon + streaming logic inline.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 23:34:00 +01:00
jgrusewski
78db6f5389 fix(ml): streaming parallel OFI + fix hardcoded state_dim=54 in backtest
Three fixes:
- Streaming MBP-10 parser (parse_mbp10_streaming): computes OFI inline
  during decode — eliminates Vec<Mbp10Snapshot> allocation (41.9M clones)
- Parallel file processing: rayon par_iter across 9 MBP-10 files
  (778s sequential → ~90s expected on H100 24-core)
- Fix hardcoded state_dim=54 in walk-forward backtest tensor creation
  that caused panic "range end index 55296 out of range for slice of
  length 52224" — now uses dynamic state_dim (43 or 51 with OFI)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 23:28:18 +01:00
jgrusewski
3d43c80264 feat(infra): compile-and-train unified workflow + exclude trading-service from CI deploy
- New compile-and-train-template.yaml: compile + GPU warmup in parallel,
  then fetch-binary → hyperopt → train-best → evaluate → upload-results
- Compile step outputs SHA tag via Argo output parameter, fetch-binary
  uses it directly (no 'latest' package indirection)
- Remove trading-service from CI deploy step — must be explicitly enabled

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 23:13:43 +01:00
jgrusewski
4976e2e1d7 fix(ml): OFI preload uses adapter's load_ofi_features, not trainer's empty field
Root cause: preload_data() called loader.ofi_features.take() on the internal
DQN trainer, but load_training_data() only loads OHLCV bars — it never
populates ofi_features. The OFI loading is done by the hyperopt adapter's
own load_ofi_features() method.

Fixes:
- preload_data() now calls self.load_ofi_features() directly
- load_ofi_features() uses self.mbp10_data_dir when set (was hardcoded ../mbp10)
- input_dim pre-computation uses OFI-aware size (51 when enabled, 43 otherwise)
- CI 'latest' package: delete-then-upload to avoid GitLab duplicate file issue
- Warn when mbp10_data_dir is set but no OFI features loaded

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 22:29:22 +01:00
jgrusewski
0491387d27 fix(infra): node-bootstrap DaemonSet + GitLab package auth + latest tag
- Rename node-dns-fix → node-bootstrap, fix nvidia gate race condition
  (always create conf.d/ and write 50-registry.toml, don't gate on
  99-nvidia.toml which doesn't exist on fresh autoscaled GPU nodes)
- Update busybox 1.36 → 1.37
- Fix fetch-binary: use PRIVATE-TOKEN/gitlab-pat (not DEPLOY-TOKEN)
- Fix upload-results: use gitlab-pat (gitlab-ci-token didn't exist)
- Add 'latest' rolling package version in both compile-services and
  compile-training (re-upload after CalVer upload)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 21:53:31 +01:00
jgrusewski
ebe8b41055 fix(infra): add network policy for GitLab package registry access
Service pods' initContainers need egress to gitlab-webservice-default:8181
to fetch release binaries from the Generic Package Registry. Without this,
curl hangs for 2+ minutes then times out (default-deny-all blocks it).

Covers all app.kubernetes.io/part-of=foxhunt pods in one policy.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 21:15:19 +01:00
jgrusewski
72bc133dba fix(infra): use inputs.parameters for Argo template tag passing
Argo Workflows can't resolve {{tasks.X.outputs}} inside template
bodies — only in DAG task arguments. Pass tag via arguments →
inputs.parameters in compile-services, compile-training,
upload-release, and deploy-services templates.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 20:44:52 +01:00
jgrusewski
d7061ca2ac fix(ml): preload OFI features in hyperopt adapter
The hyperopt preload_data() created a loader with default hyperparams
(mbp10_data_dir=None), so MBP-10/trades data was never loaded. Each
trial then set mbp10_data_dir → state_dim=51, but the preloaded data
had no OFI features → shape mismatch [128,43] vs [51,1024].

- Pass mbp10_data_dir/trades_data_dir to preload hyperparams
- Extract ofi_features from loader after preload
- Store as preloaded_ofi_features: Option<Arc<Vec<[f64;8]>>>
- Inject into each trial's DQNTrainer before training

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 20:43:47 +01:00
jgrusewski
7a9683d5fb feat(infra): GitLab releases with CalVer auto-versioning
Replace MinIO binary distribution with GitLab Generic Package Registry.
Every code push to main auto-creates a CalVer tag (vYYYY.MM.N),
compiles, uploads binaries to GitLab packages, creates a Release
with auto-generated notes, and deploys via deployment patching.

- New CI templates: create-tag, upload-release
- Modified: compile-services/training upload to GitLab packages
- Modified: deploy-services patches FOXHUNT_RELEASE on deployments
- All 7 service initContainers fetch from GitLab (curl, deploy token)
- Training job-template binary fetch from GitLab (data stays MinIO)
- MinIO retains: sccache, training data, model checkpoints

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 20:38:41 +01:00