Per pearl_build_rs_rerun_if_env_changed, every std::env::var() must be
paired with cargo:rerun-if-env-changed. Task #321 fixed crates/ml/build.rs
(commit e3d082968) but missed 6 sibling build.rs files. Each reads
CUDA_COMPUTE_CAP without registering the rerun directive, so cargo
sees no env-change between e.g. SM 89 (L40S) → SM 90 (H100) workflow
re-submissions and cache-hits the wrong-arch cubin from the previous
run. At runtime, the H100 fails to load the SM 89 cubin with
CUDA_ERROR_NO_BINARY_FOR_GPU on rmsnorm — exactly what killed
train-multi-seed-vlv8c (commit 0371d6a76, post-Class-B chain).
Files (all add `println!("cargo:rerun-if-env-changed=CUDA_COMPUTE_CAP")`
just before the env::var() read):
- crates/ml-dqn/build.rs (rmsnorm — root cause of vlv8c failure)
- crates/ml-ensemble/build.rs
- crates/ml-explainability/build.rs
- crates/ml-ppo/build.rs
- crates/ml-supervised/build.rs
- crates/ml-core/build.rs
Atomic single commit per feedback_no_partial_refactor.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The 4-branch DQN (direction x magnitude) had 3 degenerate variants
(Short25, Flat, Long25) that all mapped to 0.0 target exposure when
direction=Flat, causing 82% Flat collapse. Collapse these into a
single Flat variant, giving 7 levels (ShortSmall/Half/Full, Flat,
LongSmall/Half/Full) and 63 total factored actions (7x3x3).
- ExposureLevel enum: 9 variants -> 7 (add direction/magnitude/from_dir_mag)
- FactoredAction: 81 -> 63 total actions, from_index/to_index updated
- DQN epsilon-greedy: use from_dir_mag() instead of dir*3+mag indexing
- DQN config: num_actions default 9 -> 7
- PPO action space: 45 -> 63 actions, action masking updated
- Signal adapter CUDA kernel: 5-bin -> 7-bin exposure aggregation
- All tests updated for new variant names and index ranges
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Complete bf16 elimination across all crates (ml, ml-core, ml-dqn, ml-ppo,
ml-supervised). Zero half::bf16, __nv_bfloat16, or CudaSlice<half::bf16>
references remain (verified by grep).
CUDA: All 60+ .cu kernels and .cuh headers converted to native float.
- Half-precision intrinsics (__hmul, __hadd, __hdiv) → float operators
- atomicAddBF16 → native atomicAdd(float)
- bf16 wrapper functions → f32 identity passthroughs
Rust: All CudaSlice<half::bf16> → CudaSlice<f32> across 90+ files.
- htod_f32_to_bf16/dtoh_bf16_to_f32 → htod_f32/dtoh_f32 (direct, no conversion)
- Deleted bf16 mirror infrastructure (DuelingWeightSetBf16, alloc_bf16_mirror, etc.)
- Renamed params_bf16→params_flat, d_value_logits_bf16→d_value_logits, etc.
- Fixed .to_f32() sed damage on Decimal::to_f32() and rng.f32()
FxCache: Single f32 disk format (was bf16/f64 dual-version).
- Deleted --bf16 CLI flag from precompute_features
- PVC cache files need regeneration via precompute_features
TF32 tensor cores activated via cublasLtMatmul CUBLAS_COMPUTE_32F — no
explicit TF32 types needed. Storage is pure f32 everywhere.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three root causes of sporadic NaN during training:
1. --use_fast_math (nvcc) breaks IEEE 754 NaN semantics: fmaxf(NaN,x)
returns NaN instead of x, isnan()/isinf() compile to false.
Replaced with --ftz=true --fmad=true --prec-div=true --prec-sqrt=true
across all 4 build.rs (ml, ml-dqn, ml-ppo, ml-core).
2. Cross-stream race: replay buffer wrote batch data on the device's
original stream while the trainer read it on a forked stream.
Fixed by passing the forked stream to the DQN agent via agent_device,
so all GPU components share a single CUDA stream (zero sync overhead).
3. Rewards/dones stored as bf16 in replay buffer caused done=0xFFFF NaN.
Converted entire rewards/dones pipeline to f32: experience collector,
replay buffer storage, nstep kernel, loss/grad kernels.
Also:
- Removed fast_isnan/fast_isinf/fast_isfinite wrappers — standard
isnan/isinf/isfinite work correctly without --use_fast_math
- Updated dqn-smoketest.toml: lr=1e-4, epsilon=1e-8 (f32 Adam values)
- Removed debug printfs from gather kernels
- Added curiosity_weight to training profile system
- Cleaned up smoke_params() inline overrides
11/11 smoke tests pass, 5/5 stress runs of 50-epoch test pass,
359/359 ml-dqn + 895/895 ml unit tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fix last 2 tests:
- action_masking: test used 81-action FactoredAction decoder for
45-action mask. Fixed to use mask's own 5×9 exposure encoding.
- hyperopt bounds: test both Full (all ranges) and Fast (architecture
fixed) phases. Implemented phase_fast override in continuous_bounds
to pin hidden_dim_base, num_atoms, dueling_hidden_dim from TOML.
Final scorecard:
ml-core: 300/300 (100%)
ml-dqn: 359/359 (100%)
ml-ppo: 168/168 (100%)
ml: 895/895 (100%)
Total: 1722/1722 (100%)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PpoExperienceBatch: all 6 fields Vec<T>→CudaSlice<T>. Data stays on GPU
from kernel collection through training. Zero DtoH for batch data.
- collect_experiences(): returns CudaSlice via D2D copy, no memcpy_dtoh
- gpu_batch_to_trajectory_batch(): DELETED (was CPU conversion)
- PPO::update_gpu(): reads states via D2D mini-batch, forward on GPU
- compute_metrics_from_gpu(): downloads only scalars (returns/advantages/
actions), NOT the 123 MB state tensor
- download_*() methods for debug/checkpoint only
States (123 MB/epoch) never leave GPU. Only 2 scalar losses come to CPU.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaced 4 cudarc::nvrtc::compile_ptx() calls in ml-ppo cuda_nn with
compile_ptx_for_device() — native cubin via nvcc -O3 with disk cache.
Before: virtual PTX → driver JIT (no -O3, no arch targeting, ~100ms first launch)
After: native SASS for exact sm_XX, cached to disk, <10ms load
Files: lstm.rs (2 kernels), linear.rs (1), softmax.rs (1)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Experience collector: PinnedHostBuf<T> via cuMemHostAlloc(PORTABLE) for
6 DtoH buffers — ~25-30% faster transfers vs pageable memory.
NVRTC caching: 4 OnceLock additions in ml-ppo cuda_nn:
- softmax.rs: was re-compiling NVRTC on EVERY forward pass batch (!)
- linear.rs, lstm.rs: cached for multi-layer construction
Device attribute: OnceLock<u32> for max_threads in gpu_replay_buffer
pfx_sum — eliminates ~5µs driver query per call.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Activations: ml-ppo/cuda_nn/activations.rs 208→60 lines (-71%).
Deleted 4 inline CUDA kernels, replaced with thin wrappers delegating
to ml-core::cuda_autograd::ActivationKernels via new _raw methods.
AdamW: Unified kernel source between ml-ppo and ml-core. Switched to
architecture-aware SASS compilation (compile_ptx_for_device). Fixed
weight decay from L2-regularization to true decoupled AdamW formula.
Added GpuTensor::into_parts() for zero-copy CudaSlice extraction.
Added ActivationKernels::relu_fwd_raw/bwd_raw/tanh_fwd_raw/sigmoid_fwd_raw
for direct CudaSlice operation without GpuTensor wrapping.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace PolicyNetwork and ValueNetwork with CudaPolicyNetwork/CudaValueNetwork
backed implementations. Each struct now stores a cuda_nn GPU-native network
(cuBLAS sgemm + CUDA kernels) alongside shadow Candle layers for autograd
training compatibility. Adds forward_cuda() inference paths that bypass Candle
entirely. Wire CudaTrajectoryTensors into TrajectoryBatch with to_cuda_tensors().
Re-export CudaLSTM from lstm_networks and all cuda_nn types from crate root.
Import GpuContext into continuous_policy, continuous_action_masking, flow_policy,
coupling_layer, and adaptive_entropy for future GPU migration. Add CudaVec::to_tensor()
bridge for cuda_nn→Candle interop. Fix upload_host_to_gpu→upload_to_gpu rename
in ml-core cuda_autograd init.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds CudaTrajectoryTensors — GPU-resident trajectory batch storage that
replaces the Candle-based TrajectoryTensors. All trajectory data (states,
actions, log_probs, advantages, returns) uploaded as CudaSlice<f32> with
zero Candle tensor overhead.
The cuda_nn module is now complete with all primitives needed to replace
Candle in the PPO training loop:
- CudaLinear (cuBLAS sgemm)
- CudaLSTM (fused gate kernel)
- CudaPolicyNetwork / CudaValueNetwork (MLP stacks)
- CudaAdam (GPU-resident optimizer)
- CudaTrajectoryTensors (GPU batch storage)
- Softmax / log-softmax / ReLU / tanh / sigmoid kernels
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
GPU-native MLP networks that use CudaLinear layers + cuBLAS sgemm
internally. These are drop-in replacements for the Candle-based
PolicyNetwork and ValueNetwork, providing the same forward/softmax/
log_softmax API surface but with zero Candle overhead.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Introduces the cuda_nn module as the foundation for eliminating Candle from
the ml-ppo crate. All primitives store weights as CudaSlice<f32> and use
cuBLAS sgemm for matrix multiplication, custom CUDA kernels for activations.
Components:
- GpuContext: shared cuBLAS handle + CUDA stream wrapper
- CudaLinear: fully connected layer via cuBLAS sgemm + bias-add kernel
- CudaLSTM: fused gate LSTM cell via cuBLAS + gate nonlinearity kernel
- CudaAdam: GPU-resident Adam optimizer (m/v state on GPU, single kernel per step)
- cuda_softmax/cuda_log_softmax: warp-shuffle numerically stable kernels
- cuda_relu/cuda_tanh/cuda_sigmoid: element-wise activation kernels
- CudaVec: GPU buffer wrapper with known length
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove ALL #[cfg(feature = "cuda")] guards (~400+ occurrences)
- Remove ALL #[cfg_attr(not(feature = "cuda"), ignore)] test annotations (~250)
- Make cuda default feature in 9 ML crates (ml, ml-core, ml-dqn, ml-ppo, etc.)
- Convert nvrtc JIT compilation to precompiled nvcc (searchsorted, prefix_sum)
- Move compile_ptx_for_device() to ml-core for shared access
- Delete dead CPU code: multi_step.rs, self_supervised_pretraining.rs,
training_guard_gpu_tests.rs, CPU PER buffer paths, CPU Q-diagnostics
- Replace unwrap_or(Device::Cpu) with hard errors everywhere
- Remove dead is_cuda() else branches in DQN/PPO/hyperopt trainers
- Change config defaults from "cpu" to "cuda" (rainbow, tlob, pipeline)
- Port IQL value network to GPU kernel (5 CUDA entry points)
- Port HER goal relabeling to GPU kernel (warp-per-sample)
- Wire DSR GPU-to-CPU sync in training loop
- cfg!(feature = "cuda") → true in inference_validator
Zero warnings, zero errors across entire workspace.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Cast all Tensor::full() / Tensor::from_vec() call sites to training_dtype
instead of defaulting to F32. Fixes dtype mismatch errors (BF16 vs F32)
in PPO training on CUDA:
- tensor_ops: scalar_mul, clamp, normalize match operand dtype
- trajectories: TrajectoryBatch/MiniBatch to_tensors cast to training dtype
- continuous_ppo: ContinuousTrajectoryBatch/MiniBatch to_tensors cast
- adaptive_entropy: cast entropy to F32 for alpha multiplication boundary
- continuous_policy: forward() input cast, Tensor::full scalars match dtype
- flow_policy: sample_base_noise cast to training dtype
- hidden_state_manager: reset tensors use training_dtype
- ensemble/ppo adapter: predict input cast to training dtype
- trainable_adapter: test uses training_dtype instead of hardcoded F32
Verified: 198/198 ml-ppo tests pass, 63/63 ml PPO tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Cast input to weight dtype in DQN residual, rmsnorm, noisy_layers
- Set use_gpu=true in QNetworkConfig defaults and all config sites
- Resolve BF16 boundary mismatches in attention, curiosity, branching,
distributional_dueling across ml-dqn
- GPU-resident regime ops with BF16 boundary casts, eliminate .expect() in CUDA paths
- Eliminate all Device::Cpu fallbacks — GPU-only across 10 ML crates
- PPO: cast logits to F32 before softmax, cast batch tensors to training dtype
- Gradient collapse detection for RegimeConditionalDQN
- Wire halt_grad_collapse from CUDA guard kernel to halt training
- Dead neuron detection uses active network VarMap + squeeze factored readback
- Increment gradient_logging_step in GPU PER path
- Gradient collapse warmup guards use original buffer_size
- Cap training steps per epoch + tracing migration
- Replace Tensor::all() with sum_all() for pinned Candle compatibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace closure-based evaluate() with evaluate_dqn_graphed() for non-OFI
walk-forward backtest path. Extracts DuelingWeightSet from VarMap (branching
or standard dueling) and runs hand-written warp-cooperative CUDA forward
kernel with CUDA Graph capture — zero Candle dispatch overhead per step.
Key changes:
- GpuBacktestEvaluator::stream() getter for weight extraction on eval stream
- DQNAgentType::is_using_branching() / network_dims() for CUDA kernel config
- Hyperopt evaluate_gpu() non-OFI path: extract_dueling_weights_branching()
→ evaluate_dqn_graphed() (CUDA Graph accelerated)
- OFI path: retains Candle closure for state permutation (gather kernel
layout mismatch — future CUDA permutation kernel)
- 66+ GPU hot-path violations hardened to hard errors across DQN/PPO/supervised
- Stripped all gpu-ok suppression comments
- Proper #[cfg(feature = "cuda")] gating for CUDA-only code paths
77 files, 0 errors, 0 warnings across workspace.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Eliminate all CPU fallback paths in the PPO trainer, PPO hyperopt
adapter, and DQN hyperopt adapter. On H100, every GPU operation must
succeed or abort — silent CPU fallback runs at 1/10th throughput and
produces stale/inconsistent results.
PPO trainer (5 sites):
- GPU unavailable → hard error (was warn + CPU fallback)
- Collector init, episode reset, collection, weight sync → hard errors
- Test updated: GPU batch limit test expects error without CUDA
PPO hyperopt adapter (8 sites):
- ensure_gpu_data() now returns Result<(), MLError>
- Features/targets upload, collector init, weight sync → hard errors
- gpu_collect_trajectories() now returns Result<TrajectoryBatch>
- Experience collection caller uses ? instead of Option fallback
- GPU backtest failure → hard error
DQN hyperopt adapter (3 sites):
- CUDA synchronize between trials → hard error
- GPU backtest None on CUDA → hard error (upstream of extract_objective)
- extract_objective: panic! on CUDA build if backtest_metrics is None
- CPU backtest path guarded by #[cfg(not(feature = "cuda"))]
continuous_ppo.rs (1 site):
- clip_grads passed &Device::Cpu instead of actor's actual device
- Forces CPU roundtrip for gradient norm computation on every mini-batch
- Fixed: passes `device` (from self.actor.device()) — stays GPU-resident
gpu_experience_collector.rs (1 site):
- cuCtxSetLimit(STACK_SIZE) failure: warn → hard error
- 16KB stack is required — kernel segfaults without it
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The ml crate fix alone wasn't enough — ml-core, ml-dqn, ml-ppo,
ml-supervised, ml-ensemble, ml-explainability, ml-hyperopt, and
ml-labeling all had `default = ["cuda"]`, each independently pulling
in cudarc via candle-core/cuda.
Now `default = []` on all sub-crates. CUDA activates only when the
compile-and-train template passes `--features ml/cuda`, which
propagates through ml's cuda feature gate to all sub-crates.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
- 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>
On H100 with BF16 mixed precision enabled, Candle's comparison ops (gt,
lt, le, ge) and arithmetic ops require matching dtypes. F32 threshold
constants compared against BF16 state tensors caused every training step
to fail with "dtype mismatch in cmp, lhs: BF16, rhs: F32".
Fixes:
- regime_conditional.rs: ADX/CUSUM thresholds cast to adx.dtype()
- quantile_regression.rs: Huber kappa, zero, and half tensors match
input dtype (both quantile_huber_loss variants)
- rainbow_network.rs: LeakyReLU slope and ELU alpha/one tensors cast
to x.dtype()
- continuous_ppo.rs: Huber delta/half tensors cast to abs_diff.dtype()
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>