Commit Graph

1059 Commits

Author SHA1 Message Date
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
ca0a6b9c60 Merge branch 'worktree-branching-dqn-plan' 2026-03-11 08:46:58 +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
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
3137064df2 fix(ci): gate remaining cuda-only variables to eliminate all non-cuda warnings
- Gate IndexOp import (only used by GPU batch Q-value indexing)
- Gate grad_norm_tensor binding (only consumed by GPU accumulation path)
- Gate grad_norm_scalar_tensor squeeze (only stored in cuda GradientResult)

Services build with 0 errors, 0 warnings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 20:57:59 +01:00
jgrusewski
edee8c74fa fix(ci): gate cuda-only fields and methods behind #[cfg(feature = "cuda")]
compile-services failed (exit 101) because three cuda-only fields
(features_raw_cuda, targets_raw_cuda) and select_actions_batch_gpu()
were referenced outside #[cfg(feature = "cuda")] blocks. Services
build without the cuda feature.

- Gate VRAM release block (features_raw_cuda/targets_raw_cuda cleanup)
- Gate select_actions_batch_gpu() method definition
- Gate GPU tensor batch construction + action selection dispatch
- Remove dead non-cuda gpu_sim_results stub

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 20:55:18 +01:00
jgrusewski
d4624d3534 perf(dqn): eliminate GPU→CPU roundtrips from training hot path
Three optimizations targeting GPU allocation churn and lock contention:

1. In-place polyak update: Var::set() reuses existing GPU buffer instead
   of Var::from_tensor() which allocates a new one per call. Eliminates
   ~10,000 cudaMalloc/cudaFree per epoch (20 params × 500 steps).

2. Fused affine ops: Replace 13 Tensor::full()/Tensor::ones() constant
   tensor allocations per step with tensor.affine(mul, add) — a single
   fused kernel. Patterns: 1-x → x.affine(-1,1), γ*x → x.affine(γ,0),
   0.5*x² → (x*x).affine(0.5,0). Applied across all 4 loss paths
   (branching Bellman/Huber, standard Bellman/Huber, IQN). Eliminates
   ~6,500 GPU allocs/epoch.

3. Batch pre-sampling (K=8): Sample 8 batches under one READ lock, train
   all 8 under one WRITE lock. Reduces async lock acquisitions from
   2×N to 2×ceil(N/8). Priority staleness across 8 steps is negligible.

Combined estimated impact: 20-35% H100 throughput improvement.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 19:58:43 +01:00
jgrusewski
1d93b6e44d perf(dqn): replace O(n²) cumsum with custom CUDA prefix-sum kernel
Candle's Tensor::cumsum(0) internally allocates an [n,n] upper-triangular
matrix (9.3 GB for n=50K) causing OOM on GPUs ≤48 GB. Replace with a
block-parallel Hillis-Steele scan kernel that is O(n) in time and memory.

Additional fixes in this commit:
- Break autograd chain leak in loss/grad accumulation via .detach()
  (was leaking ~32 MB/step across entire training run)
- Release features_raw_cuda/targets_raw_cuda after GPU experience
  collection (~164 MB VRAM reclaimed)
- Use softmax eval (temp=0.3) in walk-forward backtest to prevent
  action collapse causing trades=0 on early-stage models

Validated: 1286 tests pass (408 ml-dqn + 878 ml), 0 failures.
VRAM stable at 754 MB across 45K+ training steps on RTX 3050 Ti.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 19:09:51 +01:00
jgrusewski
3a4b9a095b fix(gpu): hard-cap episodes to 256 on small GPUs (<=8 GB)
Prevents experience collector output tensors from starving backward
graph and GPU PER on VRAM-constrained GPUs like RTX 3050 Ti 4GB.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 17:28:01 +01:00
jgrusewski
b4602aa499 fix(gpu): refine VRAM headroom for runtime-measured free memory
free_memory_mb is sampled after model/optimizer load, so subtract only
backward-graph + fragmentation + data upload headroom (400 MB), not the
full model/PER/data stack. Increase collector share to 50%.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 17:27:25 +01:00
jgrusewski
d3ced2da44 fix(gpu): prevent OOM from overflow in replay buffer, dynamic shmem tiles
- Staging/GPU replay buffer: truncate batch when it exceeds ring buffer
  capacity (CPU fallback can flush 100K+ experiences at once)
- GpuExperienceCollector: compute shmem tile rows dynamically to stay
  under 48 KB default limit instead of hardcoded 64-row constant
- Auto batch size: subtract concurrent VRAM consumers (~530 MB model +
  optimizer + PER + data) before budgeting experience collector at 40%
- Hyperopt DQN adapter: GPU PER always on, include replay buffer in
  VRAM estimate for small GPUs (was excluded assuming CPU-only PER)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 17:26:43 +01:00
jgrusewski
e2fa781cb2 fix(ml-dqn): make is_gpu_prioritized() available without cuda feature
Move the cfg gate inside the method body instead of gating the entire
function. Without cuda, the GpuPrioritized variant doesn't exist so
matches! returns false — no need for separate cfg blocks at call sites.

Fixes compile-services CI failure (services build without --features cuda).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 17:11:22 +01:00
jgrusewski
759ac15383 fix(ml): remove dead parquet path, enable GPU features by default
- DQN hyperopt adapter: remove static VRAM gate for GPU experience
  collector and GPU PER — dynamic scaling handles constraints at
  runtime with graceful CPU fallback on init failure
- Remove is_parquet_file branching from hyperopt eval path (all data
  loads via DBN pipeline now)
- Update training example CLI args for consistency

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 16:54:45 +01:00
jgrusewski
403725e4ce perf(cuda): replace memcpy_dtoh with mapped pinned memory in training guard
Eliminate all per-step GPU→CPU synchronization barriers from the
training guard. Replace device+host buffer pairs and memcpy_dtoh
with cuMemHostAlloc(DEVICEMAP) mapped pinned memory.

Key changes:
- MappedBuffer struct: cuMemHostAlloc + cuMemHostGetDevicePointer_v2
  allocates memory visible to both CPU and GPU simultaneously
- Double-buffering: kernel writes to buffer[N%2], CPU reads buffer
  [(N-1)%2] — one-step delayed halt detection, zero sync
- __threadfence_system() in CUDA kernels ensures writes visible to
  CPU across PCIe without explicit memcpy
- read_volatile on host pointer prevents CPU-side caching

Eliminated:
- check_and_accumulate: 28-byte memcpy_dtoh (every training step)
- qvalue_stats: 16-byte memcpy_dtoh (every 50 steps)
- qvalue_divergence: 20-byte memcpy_dtoh (every 50 steps)

Kept: read_accumulators memcpy_dtoh (12 bytes, epoch boundary only —
accumulator buffer stays in device memory for kernel read-modify-write).

1286 tests pass (878 ml + 408 ml-dqn), 0 failures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 15:51:59 +01:00
jgrusewski
607541aaef perf(dqn): eliminate remaining GPU→CPU readbacks from CUDA guard hot path
Two remaining GPU→CPU synchronization barriers in the CUDA-active
training loop:

1. detect_dead_neurons() → to_vec0() called every training step from
   log_diagnostics() in the guard path. Added check_gradient_collapse()
   method that performs the same collapse detection logic but skips the
   expensive per-parameter weight scan. Dead neuron detection now only
   runs at epoch boundary via log_diagnostics().

2. forward() Q-value clipping monitoring → to_vec2() called every 1000
   steps during compute_loss_internal() and Q-value estimation forward
   passes. Added training_forward_active flag that gates the monitoring
   block; set to true during all training-path forward() calls
   (compute_loss_internal + Q-value estimation), false during
   inference/evaluation.

All 1577 tests pass (878 ml + 408 ml-dqn + 291 ml-core), 0 failures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 15:51:59 +01:00
jgrusewski
3dde6d285c feat(cuda): GPU-resident action routing in select_actions_batch_gpu
Wire route_exposure_to_factored() into both select_actions_batch_gpu
and select_actions_batch GPU paths, eliminating per-item CPU routing.
Previously, exposure indices (0-4) were downloaded from GPU and routed
to factored indices (0-44) one-by-one on CPU via route_action(). Now
the exposure→factored mapping runs entirely on GPU via the routing
kernel, with a single batch readback of the final factored indices.

Branching DQN path unchanged (already produces factored indices 0-44).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 15:49:40 +01:00
jgrusewski
ce534c26a5 perf(dqn): wire GPU Q-value monitoring — eliminate to_scalar/to_vec2 readbacks
Replace CPU-bound Q-value monitoring with GPU-resident qvalue_stats and
qvalue_divergence kernels from GpuTrainingGuard. The two readback sites
(estimate_avg_q_value_with_early_stopping's mean_all().to_scalar() and
log_q_values' to_vec2()) are now bypassed when the GPU training guard is
active. CPU fallback path preserved for non-CUDA builds and guard-absent
scenarios.

Changes:
- Add DQN::log_q_values_from_stats() accepting pre-computed GPU stats
- Add DQNAgentType::log_q_values_from_stats() delegate
- Replace Q-estimation in train_step_single_batch with GPU kernel path
- Replace Q-estimation in train_step_with_accumulation with GPU kernel path
- Import IndexOp trait for Tensor::i() in trainer.rs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 15:49:40 +01:00
jgrusewski
ed91f12dd2 feat(cuda): add batch_route_exposure_to_factored kernel for GPU-resident action routing
Adds a standalone CUDA kernel that converts DQN exposure indices (0-4)
to factored action indices (0-44) entirely on GPU, reusing the existing
route_order() device function from common_device_functions.cuh. Wired
into GpuActionSelector as route_exposure_to_factored() method, following
the same DtoD copy pattern as the existing select_actions methods. This
eliminates a GPU->CPU->GPU roundtrip when post-hoc routing is needed
after epsilon_greedy_select.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 15:49:40 +01:00
jgrusewski
56a7a2406b feat(cuda): bypass check_gradients_finite when GPU training guard active
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 15:49:40 +01:00
jgrusewski
6bbd2eb5ce feat(cuda): wire GpuTrainingGuard into train_step_with_accumulation (zero-sync accumulators)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 15:49:40 +01:00
jgrusewski
38f0bdec00 perf(dqn): wire GpuTrainingGuard into train_step_single_batch (zero-sync loss/grad)
Replace the GPU->CPU sync barrier (to_vec1 readback) in the DQN training
hot path with the GpuTrainingGuard CUDA kernel that performs NaN detection,
loss clipping, and gradient collapse checks entirely on-device. The guard
is lazy-initialized on first training step and falls back to the original
CPU readback path if CUDA kernel compilation fails.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 15:49:40 +01:00
jgrusewski
0aaa53bd18 test(cuda): add training guard CPU parity tests
Seven pure-Rust reference tests covering all four training_guard_kernel.cu
kernels: PTX NVRTC smoke-test (skips on CPU-only), NaN/Inf detection,
loss clip, gradient collapse, accumulator averaging, GuardResult boolean
threshold construction, and qvalue_stats_reduce per-sample max-Q reduction.
Zero clippy warnings in the new file.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 15:49:40 +01:00
jgrusewski
966300aa49 feat(cuda): add GpuTrainingGuard wrapper with pinned memory + accumulators
Wraps 4 CUDA kernels (training_guard_check, training_guard_accumulate,
qvalue_stats_reduce, qvalue_divergence_check) with a Rust struct that
uses OnceLock PTX caching, pre-allocated device buffers, and host-side
Vec mirrors for zero-allocation readbacks per training step.
Accumulator (3-float acc_buf) stays on-device for epoch-boundary
averaging without CPU roundtrips.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 15:49:40 +01:00
jgrusewski
fb1fac3e93 feat(cuda): add training guard + Q-value monitor CUDA kernels
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 15:49:40 +01:00
jgrusewski
0c385c38e2 Merge branch 'worktree-gpu-hotpath-audit' into main
GPU hotpath optimization: zero GPU→CPU roundtrips during training.
Adds DSR/n-step config, branching DQN GPU action selection,
warp experience kernel, GPU statistics, and epsilon-greedy kernel.

Merge conflicts resolved in 3 files:
- dqn.rs: kept clippy fixes + branch structure
- gpu_experience_collector.rs: removed duplicate methods, kept hotpath version
- trainer.rs: fixed brace mismatch from conflict resolution

Applied clippy fixes to new hotpath files (wildcard_enum_match_arm,
missing_debug_implementations).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 14:00:45 +01:00
jgrusewski
d06c99b0e1 fix(clippy): achieve zero warnings across entire workspace
- Fix format_push_string: write!() instead of push_str(&format!()) (25 sites)
- Fix str_to_string: .to_owned() instead of .to_string() on &str (6 sites)
- Fix unseparated_literal_suffix: add _ separator (6 sites)
- Fix multiple_inherent_impl: merge split impl blocks in TGGN, TFT, OFI (3)
- Fix else_if_without_else: add exhaustive else clauses (3 sites)
- Fix if_then_some_else_none: use .then().transpose() (1 site)
- Fix unwrap_in_result: replace expect() with match + ? (2 sites)
- Fix wildcard_enum_match_arm: enumerate Storage variants explicitly (2)
- Fix decimal_literal_representation: use hex for power-of-2 constants (5)
- Fix rc_buffer: Arc<Vec<T>> → Arc<[T]> for OFI features
- Fix needless_range_loop: convert to iterator patterns (17 sites)
- Fix used_underscore_binding: remove prefix on used vars (6 sites)
- Fix doc list item indentation (7 sites)
- Allow too_many_arguments on ML training functions (4)
- Allow multiple_unsafe_ops_per_block on CUDA FFI functions (3)
- Allow upper_case_acronyms on SLSTM/MLSTM model names (2)
- Add ML-crate pedantic allows: shadow, similar_names, type_complexity,
  indexing_slicing, partial_pub_fields, non_ascii_literal, same_name_method
  (following existing ml-labeling/ml-universe pattern)

Result: cargo clippy --workspace -- -D warnings passes with zero warnings.
All 2758+ lib tests pass (2 pre-existing backtesting failures unchanged).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 13:18:57 +01:00
jgrusewski
d6a583e4b3 feat(cuda): wire DSR/n-step config from trainer through to GPU kernel launch
Add use_dsr, dsr_eta, and n_steps fields to ExperienceCollectorConfig
with safe defaults (DSR off, eta=0.01, n_steps=1). Pass them as kernel
args after fill_simulation_enabled, matching the parameter order added
in Tasks 4 and 5. Wire from DqnHyperparams in the trainer hot path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 13:11:27 +01:00
jgrusewski
2df6a17ee7 feat(cuda): wire branching DQN GPU action selection into trainer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 13:06:19 +01:00
jgrusewski
61477bc360 feat(cuda): add DSR + n-step to warp experience kernel
Mirror the DSR (Differential Sharpe Ratio) and n-step return
accumulation added by Task 4 to the scalar kernel into the warp
kernel (dqn_full_experience_kernel_warp). DSR/n-step state runs
on lane 0 only, matching the existing reward computation pattern.
All accumulators are reset on episode boundaries.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 13:05:54 +01:00
jgrusewski
aee3871788 feat(cuda): add DSR + n-step to scalar experience kernel
Adds Differential Sharpe Ratio reward shaping and n-step return
accumulation to dqn_full_experience_kernel (scalar path). DSR replaces
EMA normalization when use_dsr=1; n-step ring buffer accumulates
discounted returns for effective_n>1. Both are reset on episode
boundaries.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 13:03:01 +01:00
jgrusewski
923fab9919 test(cuda): add DSR + n-step CPU parity tests for GPU device functions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 12:56:03 +01:00
jgrusewski
b11669b17c feat(cuda): add GpuStatistics wrapper with host-side mean/variance computation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 12:54:25 +01:00
jgrusewski
626255d507 feat(cuda): add select_actions_branching() with allocate-then-DtoD pattern
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 12:53:47 +01:00
jgrusewski
a168fa86ad feat(cuda): add nstep_push_and_sum() device function for GPU n-step returns
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 12:52:37 +01:00
jgrusewski
4c9dc52fbf feat(cuda): add branching_action_select kernel + composition tests
Append third CUDA kernel to epsilon_greedy_kernel.cu for Branching DQN:
3 independent epsilon-greedy heads (exposure/5, order/3, urgency/3)
compose to factored action index 0-44 (exposure*9 + order*3 + urgency).

Add Rust composition tests verifying full 45-action coverage and
round-trip decomposition correctness.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 12:45:09 +01:00
jgrusewski
ef7b336818 feat(cuda): add batch_statistics single-block parallel reduction kernel
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 12:44:55 +01:00
jgrusewski
61a9f45af5 feat(cuda): add dsr_step() device function for GPU DSR reward shaping
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 12:44:01 +01:00
jgrusewski
ccf9e22a55 fix(clippy): resolve misc warnings across ML crates
- Implement Display trait instead of inherent to_string() (versioning.rs)
- Replace iter().nth() with .get(), .get(0) with .first()
- Collapse else-if chains, derive Default for enums
- Fix deref warnings, redundant let bindings, push_str('\n')
- Convert match-to-if-let, use saturating_sub, RangeInclusive::contains
- Replace vec![push;push] with vec![...] literal (feature_extraction.rs)
- Remove redundant redefinitions, unnecessary parentheses, casts
- Fix &Vec<_> to &[_] in function parameters

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 12:12:14 +01:00
jgrusewski
fca2495a73 fix(clippy): add doc backticks and const fn across ML crates
- Add backticks to type names in doc comments (doc_markdown)
- Mark eligible functions as const fn (missing_const_for_fn)
No behavior changes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 11:51:31 +01:00
jgrusewski
278fd3673f fix(clippy): allow module_name_repetitions and integer_division in ML crates
module_name_repetitions: PpoConfig in ppo module is conventional ML naming.
Renaming breaks every import across the workspace.

integer_division: Basis point calculations, batch size math, combinatorial
formulas — truncation is intentional. Float conversion would introduce bugs.

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