Commit Graph

2782 Commits

Author SHA1 Message Date
jgrusewski
991108471d perf(cuda): lower SM threshold to sm_70 + add warp-cooperative branching DQN forward
- Lower warp kernel SM threshold from sm_90 to sm_70 (covers all modern GPUs)
- Add q_forward_branching_warp_shmem: warp-cooperative branching DQN forward
  with 3 independent advantage heads + shared memory tiling
- Fix online/target dispatch to use warp branching variant when available

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 22:05:01 +01:00
jgrusewski
edd7ceb0f2 perf(cuda): H100 optimization Wave 0+1 — NVTX profiling, L2 cache pinning, dynamic shmem, async double buffer
Wave 0 (NVTX Instrumentation):
- Add ml-core::nvtx module with NvtxRange RAII guard (runtime dlopen, zero overhead when absent)
- Instrument 10 CUDA pipeline hot paths: experience collector, backtest evaluator,
  PPO collector, statistics, training guard, monitoring, replay buffer

Wave 1 (Low-Effort H100 Optimizations):
- L2 cache persistence: pin DQN weights (~23MB BF16) in H100's 50MB L2 via
  cudaCtxSetLimit(CU_LIMIT_PERSISTING_L2_CACHE_SIZE) — 3.5x effective bandwidth
- Dynamic shared memory: GPU-aware tile sizing (228KB H100, 164KB A100, 100KB RTX)
  via CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES opt-in
- Async double buffer: sync_staging() with CUDA stream synchronization before swap

Validation: 0 clippy errors, 1629 tests passed (308+410+911), 0 gpu-hotpath violations

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 22:05:01 +01:00
jgrusewski
4709ca8bc2 feat(dqn): enable Branching DQN with 45 factored actions (5×3×3)
Restore 45-action factored space via Branching DQN (Tavakoli 2018),
outputting 11 Q-values (5+3+3) instead of 45. This was reduced to 5
exposure-only actions during debugging and was never intended as permanent.

- Enable use_branching: true by default in DQNConfig and DQNHyperparameters
- Add branching paths to select_action_with_confidence and select_action_inference
- Update agent.rs select_action_factored for branching-aware selection
- Expand CountBonus to per-branch tracking with bonuses_branched()
- Add order_type + urgency distribution tracking in monitoring
- Add DQN_ORDER_ACTIONS=3, DQN_URGENCY_ACTIONS=3, DQN_TOTAL_ACTIONS=45 to CUDA header
- Fix 7 pre-existing clippy doc_markdown errors in regime_conditional.rs
- Fix pre-existing cognitive_complexity in replay_buffer_type.rs (extract helpers)
- Fix flaky GPU test OOM under parallel execution (CPU fallback + test VRAM safety)
- Delete unused flash_attention submodules (block_sparse, causal_masking, etc.)
- Add GPU hot-path guard scripts and ensemble/hyperopt adapter improvements

Tests: ml-dqn 416/0, ml 905/0, clippy 0 errors on both crates

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 22:00:13 +01:00
jgrusewski
accb9baef4 feat(cuda): GPU-native PPO + supervised model evaluation & hyperopt fitness
- Add signal_adapter module: PPO 45→5 action aggregation, supervised
  threshold-based signal→action conversion, TFT quantile extraction,
  trade-count-scaled Sharpe fitness function
- Wire GPU backtest fitness into PPO hyperopt adapter (replaces avg
  episode reward)
- Extend all 8 supervised hyperopt adapters with signal_high_bps /
  signal_low_bps tunable thresholds + backtest Sharpe fitness
- Add GPU-accelerated PPO and supervised eval paths in evaluate_baseline
- Update mamba2 argmin tests for expanded 14-param space

+1750/-91 lines across 13 files, 905 tests pass, 0 clippy warnings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 16:51:14 +01:00
jgrusewski
a4e9a80d6d fix(test): update mamba2 argmin tests for 14-param space (signal thresholds)
Continuous vectors and expected counts updated from 12 to 14 params
after adding signal_high_bps and signal_low_bps to Mamba2Params.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 16:12:14 +01:00
jgrusewski
43978e77a3 feat(eval): add GPU-accelerated supervised model evaluation to evaluate_baseline
- evaluate_supervised_fold_gpu(): loads any of 8 supervised models via
  UnifiedTrainable, runs GPU backtest with signal threshold mapping
- TFT uses tft_quantile_to_signal() to extract median quantile before
  threshold comparison; scalar models use signal_to_action_scores() directly
- create_supervised_model() factory + find_supervised_checkpoint() helper
- Main loop dispatches GPU-first for all supervised models when --gpu-eval
- RefCell wrapper bridges &mut self forward() into Fn(&Tensor) closure

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 16:01:26 +01:00
jgrusewski
e690b1c60e feat(hyperopt): add signal threshold params + backtest fitness to all 8 supervised adapters
All supervised hyperopt adapters (TFT, Mamba2, Liquid, KAN, xLSTM, TGGN,
TLOB, Diffusion) now include:
- signal_high_bps and signal_low_bps in ParameterSpace (tunable thresholds)
- backtest_sharpe and backtest_trades fields in Metrics structs
- extract_objective prefers GPU backtest fitness over val_loss when available
- All tests updated (101 passing)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 15:59:38 +01:00
jgrusewski
7750c558c5 feat(cuda): add PPO GPU eval path in evaluate_baseline + hyperopt backtest fitness
- evaluate_ppo_fold_gpu(): loads PPO checkpoint, runs GPU backtest with
  ppo_to_exposure_scores (45→5 collapse), uses GpuBacktestEvaluator
- PPO main loop: tries GPU path first when --gpu-eval (default), falls
  back to CPU if CUDA unavailable or error
- PPOMetrics: backtest_sharpe/backtest_trades fields for GPU walk-forward
- PPOTrainer::run_gpu_backtest(): runs backtest on validation 20% split
- extract_objective(): prefers backtest_fitness when GPU results available,
  falls back to -avg_episode_reward on non-CUDA builds
- 2 new tests: backtest fitness priority + few-trades penalty

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 15:46:09 +01:00
jgrusewski
448111ca30 feat(hyperopt): add GPU backtest fitness to PPO adapter
Wire GpuBacktestEvaluator into PPO hyperopt trials so the optimizer
ranks candidates by walk-forward Sharpe ratio instead of raw episode
reward.  The PPO actor's 45-action softmax probabilities are collapsed
to 5 exposure scores via ppo_to_exposure_scores before the backtest
loop.  When CUDA is unavailable or the backtest fails, the adapter
falls back to the original -avg_episode_reward objective.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 15:46:01 +01:00
jgrusewski
c0dd5ef99c feat(cuda): add shared supervised GPU backtest helper in signal_adapter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 15:41:00 +01:00
jgrusewski
60281dca25 feat(cuda): add signal_adapter module — PPO 45→5, supervised thresholds, TFT quantile, fitness scoring
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 15:37:44 +01:00
jgrusewski
e4870ea5e9 perf(cuda): make GPU eval default, eliminate all mid-loop GPU→CPU transfers
- Flip --gpu-eval default to true (--no-gpu-eval to opt out)
- Move actions_history scatter-write into env kernel (zero CPU accumulation)
- Remove done-flag periodic download (env kernel handles per-thread)
- Only GPU→CPU transfer is final metrics readback (n_windows × 40 bytes)
- Add spread cost discrepancy warning when GPU path is active

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 15:03:03 +01:00
jgrusewski
0c85dcf56a Merge branch 'feature/cuda-backtest-plan' 2026-03-11 13:24:04 +01:00
jgrusewski
8c7e907fcf perf(cuda): eliminate GPU→CPU roundtrips from backtest evaluate loop
- gather_states: replace memcpy_dtoh + Tensor::from_vec with DtoD copy
  (cuMemcpyDtoDAsync) — state tensor stays on device, zero CPU touch
- actions: replace to_vec1 + memcpy_htod with DtoD copy from argmax
  tensor directly into actions_buf — eliminates per-step PCIe upload
- batch_q_values (RegimeConditionalDQN): replace CPU-side regime
  classification (to_vec2 + serial loop + sub-batch re-upload) with
  on-device classify_regime_masks_gpu + all-heads forward + masked blend

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 13:05:02 +01:00
jgrusewski
057fefe846 docs(cuda): add review-suggested documentation to epoch state handling
Document known benign race on reset_flags (cross-block __threadfence
not being a grid-wide barrier) and writeback picking an arbitrary
episode's DSR/EMA as the representative seed. Remove dead reads for
epoch_port_value/pos/cash in both standard and warp-cooperative kernel
variants.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 11:50:28 +01:00
jgrusewski
49e214fb8f feat(eval): wire GPU backtester into evaluate_baseline binary
Add --gpu-eval flag that routes DQN fold evaluation through
GpuBacktestEvaluator instead of the per-bar CPU path. The GPU path
uploads the full test fold as a single window, runs the env kernel
loop on-device, and downloads only the final WindowMetrics (10 floats).

Key design choices:
- GPU path uses greedy argmax (not hierarchical softmax); results differ
  slightly from CPU path — this is documented and intentional
- Falls back to CPU path on any GPU error (no silent failures)
- Also adds --initial-capital flag needed by GpuBacktestConfig
- Fix pre-existing clippy::wildcard_enum_match_arm in
  GpuBacktestEvaluator::new (Device::_ → explicit variants)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 11:41:21 +01:00
jgrusewski
f19cd4b26a test(cuda): add GPU backtest validation tests with synthetic data
Adds crates/ml/tests/gpu_backtest_validation.rs with 6 deterministic
tests that validate GpuBacktestEvaluator produces reasonable metrics:
always-long on uptrend (positive PnL), always-long on downtrend
(negative PnL), always-flat (~zero PnL), multi-window ordering,
extended metrics finiteness/self-consistency, and trade count.

All tests are #[ignore] gated and skip gracefully on CPU-only machines
(CI passes with 6 ignored; intended for GPU development machines).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 11:38:45 +01:00
jgrusewski
0ad5dc8b42 feat(cuda): add GPU gather kernel for backtest state construction
Implements backtest_gather_kernel.cu (gather_states) which reads directly
from the pre-uploaded features buffer and live portfolio state on GPU,
eliminating the large GPU→CPU download of the full features buffer that
the old gather_states() path performed each step (n_windows×max_len×feat_dim
floats, e.g. 134 MB for 8 windows × 100k steps × 42 features).

The new path: kernel writes [n_windows, state_dim] into states_buf, then
only that tiny buffer (~1.5 KB for 8×48) is downloaded to create the
Candle tensor — a ~100,000x reduction in per-step data transfer.

Wiring changes in GpuBacktestEvaluator:
- Added GATHER_PTX OnceLock + compile_gather_ptx()
- Added gather_kernel (CudaFunction) and states_buf (CudaSlice<f32>) fields
- Added portfolio_dim field (always 3, validated in gather_states())
- Allocates states_buf = n_windows * (feature_dim + 3) in new()
- gather_states() now launches kernel then downloads small output buffer
- metrics download updated to 10 floats/window (Task 13 extended metrics)
- Added 3 new tests: gather PTX compilation, portfolio_dim validation,
  state_dim calculation; all 10 gpu_backtest tests pass

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 11:33:12 +01:00
jgrusewski
36a9b782ee feat(hyperopt): wire GpuBacktestEvaluator into DQN hyperopt adapter
Add GPU-accelerated backtest path that runs walk-forward evaluation
entirely on GPU (env step kernel + metrics reduction), falling back
to the existing CPU path if CUDA is unavailable or the GPU path fails.

Changes:
- Make DQN::q_values_for_batch() public for external Q-value access
- Add RegimeConditionalDQN::batch_q_values() for regime-routed Q-values
- Add DQNAgentType::batch_q_values() dispatch method
- Add DQNTrainer::evaluate_gpu() method (#[cfg(feature = "cuda")])
- Wire GPU-first backtest at the decision point with CPU fallback

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 11:23:42 +01:00
jgrusewski
911347a1b6 feat(cuda): add GpuBacktestEvaluator orchestrator
Implements the GPU backtest evaluator (Task 9): uploads walk-forward
window data once, runs the step loop with Candle forward pass + env
kernel, then a single metrics reduction kernel with one final download.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 11:09:22 +01:00
jgrusewski
b04c095b0d perf(dqn): move epoch Q-value diagnostics to GPU reduction
compute_epoch_q_diagnostics now uses compute_q_diagnostics_gpu() free
function with Candle tensor ops. Batches gap stats + per-action means
into single 8-float readback instead of full N×5 to_vec2 download.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 11:00:52 +01:00
jgrusewski
de1dbba910 feat(cuda): add per-window backtest metrics reduction kernel
One block per window. Parallel reduction for Sharpe, Sortino,
total PnL, max drawdown, win rate, trade count. Single kernel
launch reduces all windows simultaneously.
2026-03-11 10:57:52 +01:00
jgrusewski
f501cc50cf feat(cuda): add vectorized backtest environment step kernel
One thread per walk-forward window, parallel across all windows.
Handles: action→exposure mapping, trade execution with tx costs,
mark-to-market, step return calculation, drawdown tracking.
Portfolio state persists across steps in GPU global memory.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 10:57:46 +01:00
jgrusewski
b122c2d0e7 perf(dqn): wire epoch-boundary state resets to GPU experience collector
DSR portfolio reset and normalizer reset now happen via kernel flags
instead of CPU state mutation. Eliminates cudaStreamSynchronize at
epoch boundaries.

Also fix brace mismatch in gpu_training_guard.rs that left the
accumulate_q_value/read_q_accumulator/reset_q_accumulator methods
outside the impl block (introduced by Task 4 in-progress work).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 10:53:13 +01:00
jgrusewski
4a90330a55 perf(dqn): replace per-launch monitoring download with epoch-end GPU reduction
Eliminates N*8 bytes of memcpy_dtoh per experience kernel launch.
MonitoringReducer accumulates stats on GPU, single 48-byte download
at epoch boundary. Zero cudaStreamSynchronize during experience collection.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 10:44:20 +01:00
jgrusewski
57fbf4d993 fix(gpu-monitoring): code quality fixes from Task 2 review
- Add n=0 early-return guard to reduce() to prevent CUDA divide-by-zero
- Add i32::MAX bounds check before casting n to prevent overflow
- Cache PTX compilation with OnceLock<Result<Ptx, String>> (compile once per process)
- Add Safety comment to unsafe block documenting slice/buffer invariants
- Fix doc comment: clarify 48-byte is GPU transfer size (12 × f32)
- Add shmem comment explaining kernel uses static __shared__ only

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 10:29:25 +01:00
jgrusewski
4439fba5c4 feat(cuda): add monitoring reduction kernel
Replaces per-launch rewards/actions download with single epoch-end
reduction. monitoring_reduce kernel computes mean, std, min, max,
Sharpe estimate, and per-action counts via parallel reduction.
Single 48-byte download instead of N*8 bytes per kernel launch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 10:22:57 +01:00
jgrusewski
fcad9584a9 fix(cuda): warm-start episode-boundary DSR/EMA resets from epoch state
Episode-boundary resets previously used cold hardcoded constants (0.0f,
1e-8f) for DSR accumulators and EMA normalizer. Now warm-starts from
the persistent epoch state so all episodes within an epoch benefit from
accumulated statistics, not just the first.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 10:19:25 +01:00
jgrusewski
83398cd301 fix(cuda): wire epoch state into EMA/DSR accumulators and vol EMA in experience kernel
The epoch_vol_ema, epoch_dsr_mean, and epoch_dsr_var variables were loaded
from global memory at kernel start but never wired into the actual computation.
The EMA normalizer (ema_mean/ema_var) and DSR accumulators (dsr_A/dsr_B) were
initialized with hardcoded constants, so epoch state round-tripped unchanged.

Changes:
- Seed ema_mean/ema_var from epoch_dsr_mean/epoch_dsr_var at kernel start
- Seed dsr_A/dsr_B from epoch_dsr_mean/epoch_dsr_var at kernel start
- Seed ema_init/dsr_initialized from epoch_step_count > 0 (skip cold start
  on subsequent epochs)
- Add local_vol_ema/local_median_vol seeded from epoch_vol_ema/epoch_median_vol
- Update vol EMA each timestep from market feature index 3 (log close return),
  mirroring CPU DQNTrainer::vol_ema / median_vol logic
- At writeback, write actual computed dsr_A/dsr_B or ema_mean/ema_var (conditioned
  on use_dsr), and computed local_vol_ema/local_median_vol, instead of unmodified
  loaded epoch values
- Applied identically to both dqn_full_experience_kernel (standard) and
  dqn_full_experience_kernel_warp (warp-cooperative) variants

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 10:14:53 +01:00
jgrusewski
cec9607244 Merge branch 'worktree-branching-dqn-plan' 2026-03-11 10:06:01 +01:00
jgrusewski
b25ce1dcbf fix(dqn): replace hardcoded PER memory cap with dynamic VRAM-proportional budget
The GPU PER replay buffer had a hardcoded 4 GB MAX_BYTES limit that
rejected the auto-sizer's 10M-entry proposal on H100 (80 GB VRAM).
Now per_max_buffer_bytes() computes 20% of total VRAM (min 1 GB) and
flows through OptimalReplayConfig → DQNConfig → GpuReplayBufferConfig
so both subsystems agree on the budget.

Also fixes misleading regime detection log (indices 211/219 → 40/41)
and renames dqn_config_2025 → dqn_default_config.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 10:03:21 +01:00
jgrusewski
7e098777be feat(cuda): add GPU-persistent epoch state to GpuExperienceCollector
Eliminates cudaStreamSynchronize between epochs by keeping vol EMA,
portfolio state, and DSR normalizer in persistent CudaSlice buffers.
Kernel reads initial state at launch, writes final state at exit.

- Add epoch_state CudaSlice<f32>[8] field and reset_flags u32 bitfield
  to GpuExperienceCollector struct
- Allocate epoch_state in new() with sensible defaults (vol_ema=0.01,
  initial_capital for portfolio, dsr_var=1.0 to avoid div-by-zero)
- Pass epoch_state and reset_flags as final args to both kernel variants
  (standard per-thread and warp-cooperative)
- Kernel: thread/lane 0 of block 0 applies reset flags atomically with
  __threadfence, all threads read 8-float epoch state from L1-cached
  global memory, last block writes back updated values at exit
- Auto-clear reset_flags after each launch (one-shot semantics)
- Add set_reset_flags(), clear_reset_flags(), epoch_state_gpu(),
  rewards_gpu(), actions_gpu() public methods

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 09:57:55 +01:00
jgrusewski
208cee4cf5 docs: fix 5 chunk 2 re-review issues in CUDA backtest plan
- Task 9: memcpy_stod_inplace → memcpy_htod (cudarc 0.17 API)
- Task 9: Fix transposed actions_history layout — accumulate CPU-side
  in window-major [window][step] layout, upload once before metrics kernel
- Task 9: Replace non-existent Tensor::from_raw_buffer with download-
  and-reupload pattern (temporary, replaced by Task 11 GPU gather kernel)
- Task 10: DqnOptimizer → DQNTrainer (hyperopt adapter) (actual struct name)
- Task 10: internal_trainer type → &InternalDQNTrainer (avoids name conflict
  with adapter's own DQNTrainer alias)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 09:40:51 +01:00
jgrusewski
33bcc44bd4 docs: fix Task 6 min/max API — candle min(D)/max(D) returns Tensor not tuple
In candle-core (git 671de1d), min(D) and max(D) return Result<Tensor>,
not Result<(Tensor, Tensor)>. Use flatten_all() then min(0)/max(0) for
scalar reduction instead of the tuple destructuring pattern.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 09:38:12 +01:00
jgrusewski
b704af9112 docs: fix Task 14 path and Task 15 EvaluationEngine API in plan
- Task 14: evaluate_baseline is at crates/ml/examples/evaluate_baseline.rs
  (not bin/fxt/src/commands/), fix cargo check command and git add path
- Task 15: process_bar_factored takes (usize, &OHLCVBarF32, &FactoredAction)
  not (f64, usize, f64, f64), fix test to use correct types

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 09:37:33 +01:00
jgrusewski
dce318b47f docs: fix 22 review issues in CUDA backtest implementation plan
Chunk 1 fixes:
- Task 1: Replace shared-memory epoch state with global memory + __threadfence()
  (shared memory is per-block, multi-block launch would read uninitialized shmem)
- Task 2: Move atomicMin_float/atomicMax_float before kernel definition
- Task 3: Add pub getters for private GPU buffers, fix broken pnl_history logic
  (pushing mean_reward N times gives zero std → NaN Sharpe)
- Task 4: Add missing accumulate_q_value/read_q_accumulator methods to GpuTrainingGuard
- Task 6: Fix sort_last_dim tuple destructuring, batch 3 to_scalar into single
  8-float readback, move function off GpuTrainingGuard to free function

Chunk 2 fixes:
- Task 8: Add shared-memory parallel reduction for drawdown, win_rate, trade_count
  (previously only reduced on thread 0's 1/256th data subset)
- Task 9: Remove dead code, fix task cross-references (12→11), implement
  actions_history DtoD copy (was TODO → trade count always 0)
- Task 10: Flesh out BacktestMetrics mapping (6 GPU fields → 16 struct fields),
  fix cfg compilation with nested block pattern

Chunk 3 fixes:
- Task 12: Replace "follow same pattern" with actual PPO/supervised code,
  enumerate all 8 supervised adapter files, add regression→action mapping
- Task 13: Add complete bitonic sort kernel code for VaR/CVaR extraction,
  document intentional spec deviation and deferred Phase 3 capabilities
- Task 14: Specify exact binary path (bin/fxt/src/commands/evaluate_baseline.rs)
- Task 15: Replace placeholder test comments with full synthetic-data validation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 09:32:58 +01:00
jgrusewski
9c6dc78238 Merge branch 'worktree-branching-dqn-plan' 2026-03-11 09:26:12 +01:00
jgrusewski
77029165c5 fix(k8s): remove data sync initContainer + fix --output flag
Training data is already on the PVC — the rclone sync step was
wasting 2-3 minutes per job. Also fixes --output-dir → --output
to match the actual CLI flag.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 09:25:54 +01:00
jgrusewski
6436c15d6d docs: CUDA backtest & GPU-residency implementation plan
15-task plan covering Phase 1 (seal GPU→CPU roundtrips in training loop),
Phase 2 (vectorized GPU backtest environment for hyperopt), and Phase 3
(general GPU backtester integration).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 09:22:17 +01:00
jgrusewski
d6f0538ac0 docs: CUDA backtest & GPU-resident training design spec
Three-phase plan to eliminate all GPU→CPU roundtrips from training:
- Phase 1: Seal training loop (persistent GPU epoch state, async monitoring)
- Phase 2: Vectorized CUDA backtest kernel for hyperopt evaluation
- Phase 3: General-purpose GPU backtester replacing CPU SIMD path

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 09:11:27 +01:00
jgrusewski
ca0a6b9c60 Merge branch 'worktree-branching-dqn-plan' 2026-03-11 08:46:58 +01:00
jgrusewski
1447760be3 fix(ci): replace bash substring syntax with POSIX-compatible cut
${var:0:8} is a bashism that fails on /bin/sh (dash). Use cut instead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 02:26:32 +01:00
jgrusewski
22f122d2d1 test: trivial api comment change to test incremental compile speed
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 02:23:14 +01:00
jgrusewski
6ddcb52825 perf(ci): fix full workspace rebuild — move version from compile-time to runtime
Root cause: `option_env!("FOXHUNT_BUILD_VERSION")` in common/build_info.rs
was a compile-time macro tracked by cargo fingerprints (Rust 1.80+). Every
pipeline run with a new tag invalidated `common` → cascading rebuild of all
38 dependent workspace crates, even when zero source files changed.

Fix: replace `option_env!()` with `std::env::var()` (runtime LazyLock). Cargo
no longer tracks the version env var, so `common` only recompiles when its
source actually changes.

Also: skip git checkout when HEAD already matches target SHA (zero mtime
changes), and drop `-x` from git clean to preserve gitignored files.

Expected: ~3.7min → <1min for unchanged-crate rebuilds on warm PVC.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 02:10:20 +01:00
jgrusewski
05eb574d0c fix(dqn): register NoisyLinear mu vars in VarMap + multi-thread runtime
Three fixes for GPU-accelerated Branching DQN training:

1. **GPU experience collector**: NoisyLinear creates standalone Vars via
   Var::from_tensor(), bypassing VarMap registration. The GPU collector
   looks up weights by name ("value_fc.weight") from VarMap and falls
   back to CPU (~5x slower) when missing. Fix: register mu vars in
   VarMap at construction, keep sigma vars standalone.

2. **Optimizer device mismatch**: Using only vars().all_vars() left
   NoisyLinear head params frozen. backward() produces gradients the
   optimizer doesn't know about → device mismatch in clip_grad_norm.
   Fix: all_trainable_vars() = VarMap (shared+mu) + sigma.

3. **Single-threaded CPU bottleneck**: Runtime::new() creates a
   current-thread scheduler → 1 OS thread → all async work serialized.
   Fix: multi-thread runtime (4 workers) created once in DQNTrainer::new(),
   shared across preload/training/backtest phases. Eliminates 3 fallback
   Runtime::new() callsites.

Also: polyak_update_var_pairs with debug_assert_eq, two-phase target
network sync (VarMap Polyak + sigma var_pairs Polyak), copy_weights_from
handles NoisyLinear heads.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 02:05:54 +01:00
jgrusewski
9ed204be95 chore(ci): disable gpu-warmup in ci-pipeline and training-workflow
Keep gpu-warmup only in compile-and-train-template where it runs
parallel with the CPU compile step. The other templates don't need
it — CI pipeline doesn't always train, and training-workflow already
has the GPU node available from the triggering event.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 01:59:26 +01:00
jgrusewski
97b3b9f30a perf(ci): persistent checkout on PVC to preserve file mtimes
git clone gives all files the current timestamp, causing cargo to
recompile every workspace crate even when source is unchanged. Switch
to persistent checkout on the PVC: git fetch + git checkout --force
only updates files that actually changed between commits, preserving
mtimes on unchanged files so cargo correctly skips them.

- compile-services: /cargo-target/src persistent checkout
- compile-training: /cargo-target/src persistent checkout
- compile-and-train: /cargo-target/src persistent checkout + CARGO_HOME
- First run: full clone (fallback), subsequent: fetch + checkout

Expected: ~40 crate recompile → only changed crates (~1-2 min)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 01:23:51 +01:00
jgrusewski
a5ea8713b6 feat(dqn): Branching DQN + KernelWeightPack + metrics propagation
Branching DQN (Tavakoli et al., 2018):
- 3 independent advantage heads (exposure=5, order=3, urgency=3) in CUDA kernel
- `use_branching` as hyperopt-tunable parameter (index 11 in 29D space)
- Branch weight pointers packed into KernelWeightPack struct

KernelWeightPack refactor:
- 87→38 CUDA kernel params by packing 48 weight pointers into 384-byte #[repr(C)] struct
- UNPACK_WEIGHT_PTRS macro in CUDA header for clean kernel-side access
- Single .arg(&weight_pack) replaces 48 individual .arg() calls

Hyperopt metrics in JSON output:
- Added `metrics: Option<serde_json::Value>` to TrialResult<P>
- `extract_metrics()` trait method with default None (DQN overrides)
- JSON output now includes per-trial backtest metrics (Sharpe, Sortino,
  Calmar, Omega, drawdown, win_rate, trades) + top-level best_metrics

Prometheus backtest gauges (Rust-side, no CUDA):
- 8 new gauges: foxhunt_hyperopt_best_{sharpe,sortino,calmar,omega,
  max_drawdown_pct,win_rate,total_trades,total_return_pct}

Dynamic episode scaling:
- Removed hardcoded 256 episode cap on small GPUs
- VRAM budget calculation in optimal_n_episodes() handles scaling naturally
- Removed stale .min(256) in PPO trainer

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 00:46:53 +01:00
jgrusewski
9d97f957bc perf(ci): revert sccache, keep persistent CARGO_HOME (incremental wins)
With 37+ workspace crates, cargo's incremental fingerprinting already
skips unchanged crates — sccache adds per-rustc wrapping overhead for
no benefit. The real speedup is CARGO_HOME on PVC (eliminates crate
re-downloads every build).

Kept: CARGO_HOME=/cargo-target/cargo-home on PVC
Removed: RUSTC_WRAPPER=sccache, SCCACHE_DIR, CARGO_INCREMENTAL=0

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 00:43:29 +01:00
jgrusewski
6d07a2b5f1 perf(ci): enable sccache + persistent CARGO_HOME for compile jobs
Both compile-services and compile-training were doing full rebuilds
every run because:
1. No RUSTC_WRAPPER=sccache — sccache was installed but never used
2. No persistent CARGO_HOME — crates.io registry re-downloaded each time
3. Incremental compilation doesn't work across git clones (mtime-based)

Fix: enable sccache (content-hash based, clone-agnostic), persist
CARGO_HOME on the PVC, disable incremental (conflicts with sccache).

Expected: first run populates cache, subsequent runs ~2-3min vs ~8min.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 00:23:24 +01:00