- ml-dqn/dqn.rs: `apply_accumulated_gradients` is a scaffolding method
whose real optimizer step lives in the fused CUDA trainer. The
`grads` map was already being dropped silently; reword the comment
to describe that split explicitly (incidental: see trainer path for
the live gradient application).
- ml-features/mbp10_loader.rs: strip the "TODO optimize with binary
search" parenthetical from the docstring. Linear search over the
sorted snapshot slice is the intended behaviour for current call
sites.
- ml-hyperopt/optimizer.rs: `optimize_two_phase` short-circuits after
Phase A because `DQNTrainer` is not `Clone`. Describe that limit
and point callers at `optimize_parallel` (which requires `M: Clone`)
rather than a hypothetical Phase B.
- ml-checkpoint/signer.rs: `fetch_key_from_vault` is currently an
env-var resolver. Reword to say so plainly — no Vault client is
wired into this crate, production uses K8s secrets injected as env.
- backtesting/dbn_replay.rs: `DbnReplayEngine::from_bytes` remains an
Err stub because `DbnParser` is gated behind the `databento`
feature which this crate does not enable. Replace the pseudocode
block with a declarative comment.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
With 10 trials and n_initial=5, PSO got 5 remaining evals for a
20-particle swarm. Only 5 of 20 particles were evaluated before the
budget observer killed the run — 75% of the swarm had no cost value.
PSO can't compute a proper gbest from partial data.
Fix: when remaining_trials < n_particles × 2, skip PSO entirely and
use additional LHS samples instead. LHS gives better space coverage
than an incomplete swarm iteration.
For 10 trials: 5 initial LHS + 5 additional LHS = 10 independent
space-filling samples. Much better than 5 LHS + 5 broken PSO particles.
PSO still runs when budget is sufficient (≥40 remaining for 20 particles).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Argmin defaults: inertia=0.72, cognitive=1.19, social=1.19
Problem: social >> inertia causes particles to collapse toward the
global best immediately. With only 1 LHS trial as the initial best,
the entire swarm clusters around that point and can't explore.
Every PSO trial was worse than the random LHS trial.
Fix: inertia=0.9 (high momentum, maintains exploration),
cognitive=1.5 (strong personal best memory),
social=0.8 (weak global pull, prevents premature convergence).
Applied to both sequential and parallel optimizer paths.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Major changes:
- Eliminate ALL runtime NVRTC: c51_loss + mse_loss kernels converted to
precompiled cubins with runtime num_atoms/v_min/v_max/branch params
- Fix evaluator SIGSEGV: rng_states/q_gaps sized for chunked batch (cn)
not n_windows; NULL pointer guard in action_select kernel
- Add trade_physics.cuh shared header for train/eval consistency
- Add equity circuit breaker (25% DD from peak) + margin-aware position cap
- Consolidate 46D→22D hyperopt search space, enable ensemble by default
- Fix trade counting: use exposure index not factored action
- Fix Calmar overflow: clamp to ±100 in kernel
- Softer CVaR penalty (cap 3.0 not 10.0) for undertrained models
- Fix win_rate display (ratio→percentage)
- Remove dead code (normalize_reward, calculate_completion_penalty)
- Add tracing subscriber to hyperopt test for visible metrics
- Per-chunk sync in evaluator for reliable error reporting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Build-time #define was 256 but runtime config can be 128 (hidden_dim_base).
Caused CUDA_ERROR_ILLEGAL_ADDRESS in IQN trunk gradient. Now passed as
kernel params matching the STATE_DIM parameterization pattern.
Also: warn→error for training failures in optimizer, eprintln for debugging.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
increment_trial() was never called — budget enforcement was broken because
TrialBudgetObserver had its own internal Arc<AtomicUsize> disconnected from
the trial_counter used by evaluate_point() and cost(). With num_trials=2,
PSO ran 20 particle evaluations instead of stopping at 2.
Now TrialBudgetObserver::new() takes the caller-owned Arc<AtomicUsize> so
all evaluation paths (LHS via evaluate_point + PSO via cost()) share one
counter. ObjectiveFunction::cost() and ParallelObjectiveFunction::cost()
increment trial_counter directly (fetch_add SeqCst) and check budget inline;
observe_iter() reads the same counter to terminate PSO between iterations.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
NaN/divergence during training is a valid PSO/TPE outcome — it means
the sampled hyperparameters are unstable. Score failed trials with 1e6
penalty so the optimizer learns to avoid that region, instead of
aborting all remaining trials.
The PSO/argmin paths already handled this (lines 1118, 1261). Only
the shared evaluate_point (TPE path) propagated errors as fatal.
Tested: 6 trials × 20 epochs on RTX 3050 — 2 NaN trials scored as
penalty, optimizer completed all 6 trials in 597s.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 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>
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>
Three root causes fixed:
1. GPU PER + experience collector (cudarc) created dual CUDA allocator
fragmentation on GPUs ≤8 GB, making training impossible even at
batch_size=1. Added `use_gpu_replay_buffer` config flag; both GPU PER
and experience collector now disabled when VRAM ≤8192 MB.
2. Search space bounds were WIDENED instead of capped — max_batch_size
returning 4096 replaced the original 512 upper bound, and
max_hidden_dim_base_full returning 3072 replaced the original 1024.
Fixed with min() to only narrow, never widen.
3. VRAM estimator assumed GPU features always active, overcharging when
they're disabled on small GPUs. Now conditional: when
replay_buffer_capacity=0 (proxy for GPU PER disabled), collector/cudarc
costs are zero and fragmentation multiplier drops from 3× to 1.5×.
Additional small-GPU guard: GPUs ≤8 GB get clamped search space
(batch≤128, hidden≤512, atoms≤51, buffer≤50K) to fit 3 regime heads
+ C51 + noisy nets + dueling in limited VRAM.
Validated: 7/7 trials complete on RTX 3050 Ti 4 GB, zero OOM, best trial
Sharpe 9.8 with 50.6% win rate. Previous runs had 100% OOM failure rate.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
training_dtype() now returns BF16 on all CUDA devices, enabling full
tensor-core utilization. F32 is used only at boundaries (scalar
extraction, loss computation, softmax). VRAM estimator updated to
account for BF16 byte sizes, C51/QR atoms, dueling streams, NoisyNet
param doubling, and GPU PER buffer pre-allocation.
Changes:
- mixed_precision.rs: training_dtype() returns BF16 on CUDA
- curiosity.rs: F32 cast before scalar extraction
- network.rs: F32 output at NetworkLayers forward boundary
- traits.rs: estimate_trial_vram_mb_full() with BF16-aware sizing
- dqn.rs adapter: uses full estimator with worst-case architecture
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>