Commit Graph

3046 Commits

Author SHA1 Message Date
jgrusewski
2d426b5a4f perf(ppo): eliminate GPU roundtrip in metrics + fix MlDevice type
- Replaced compute_metrics_tensors (CPU→GPU→CPU roundtrip: 3 HtoD +
  8 kernels + 4 DtoH) with compute_metrics_cpu (pure f64 two-pass,
  data already on host). Zero GPU transfers for metrics.
- Fixed Device→MlDevice type, cuda_stream() extraction pattern
- Removed dead sample_action method (referenced non-existent Tensor type)
- Fixed GpuVarStore::new() to pass stream arg

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 08:31:06 +01:00
jgrusewski
d572dc466a perf(cuda): batch DQN metrics readbacks — 15 DtoH transfers → 4
metrics.rs: 4 fixes batching individual to_scalar() GPU syncs:
- Q-value stats: 4 transfers → 1 (cat [min,max,mean,var])
- Q diagnostics: 8 transfers → 1 (cat [gaps + per-action avgs])
- get_q_values: N transfers → 1 (bulk to_vec1)
- Validation Sharpe: 2 transfers → 1 (cat [mean,var])

Zero per-step DtoH leaks in training_loop.rs or metrics.rs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 08:29:26 +01:00
jgrusewski
b53df16b5f fix(ml-dqn): resolve final 24 compile errors — ml-dqn builds clean
- entropy_regularization: rand::rng()→thread_rng() (rand 0.8)
- agent: f32/f64 type casts for learning rate
- branching: cudarc 0.19 memcpy_htod 2-arg form, memcpy_dtoh deref
- distributional_dueling: RMSNorm::new_gpu→new_default
- noisy_layers: GpuTensor.data→into_parts()
- replay_buffer_type: GpuBatchSlices→GpuBatch conversion
- gpu_replay_buffer: bf16/u32 slice→GpuTensor helpers

ml-dqn: 0 errors, 44 warnings. Full candle elimination complete.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 08:23:30 +01:00
jgrusewski
3ed3594193 fix(ml-dqn): migrate dqn.rs — 187 compile errors resolved
Massive rewrite of DQNAgent core (3400+ lines):
- 30+ GPU helper functions added (gpu_clamp, gpu_abs, gpu_gather_3d, etc.)
- Forward pass: Candle ops→GpuTensor + host-side helpers
- Loss computation: simplified to host-side Bellman TD + Huber
  (fused CUDA trainer handles GPU-native path)
- Optimizer: ParamsAdam→AdamWConfig, Adam→GpuAdamW
- Target updates: polyak via copy_weights_from fallback
- Action selection: argmax returns Vec<u32>, Gumbel-max host-side
- Checkpoint: safetensors direct deserialization

24 errors remain across 12 other files (small counts, cascading type changes).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 08:12:00 +01:00
jgrusewski
2ffaf42197 fix(ml-dqn): migrate branching + regime_conditional — zero candle errors
branching.rs: MlDevice→Arc<CudaStream>, Candle gather/unsqueeze→host-side,
MaybeNoisyLinear::noisy_sigma_slices(), copy_weights via memcpy pattern.

regime_conditional.rs: device→stream field, all GpuTensor ops get &stream,
batch_softmax_actions via host-side Gumbel-max, gradient merge via BTreeMap.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 08:06:08 +01:00
jgrusewski
d6fa11ec6c fix: cudarc 0.19 API fixes in tests — CudaDevice→CudaContext, no Arc double-wrap
Test code used CudaDevice (cudarc 0.17) instead of CudaContext (0.19),
and wrapped Arc::new(CudaContext::new().new_stream()) which double-wraps
since new_stream() already returns Arc<CudaStream>.

740 tests pass across ml-core, ml-ppo, ml-supervised, ml-ensemble.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 08:05:32 +01:00
jgrusewski
f607335820 perf(cuda): eliminate NVRTC runtime compilation — all kernels now cubin
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>
2026-03-18 07:55:11 +01:00
jgrusewski
94b968d800 perf(cuda): pinned DtoH memory + NVRTC/device-attr caching
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>
2026-03-18 07:52:44 +01:00
jgrusewski
53382ad692 perf(cuda): unify dual-stream to single stream + event-based sync_all
gpu_backtest_evaluator: removed env_stream, env_event, main_event.
Sequential single-stream loop eliminates 4 event sync calls per step.
H100 forward pass occupies 100+ SMs — env_step overlap was not profitable.

cuda_streams: sync_all() reduced from N blocking cuStreamSynchronize
calls to 1 via cuEventRecord fan-in on stream[0]. 118 tests pass.

Estimated 15-20% walltime reduction in backtest evaluation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 07:50:00 +01:00
jgrusewski
bc0f1f2bd8 perf(cuda): pre-allocate GPU buffers in PPO collector — eliminate per-epoch malloc
reset_episodes() was allocating 5 CPU Vecs per epoch for HtoD zero-fill.
Replaced with:
- memset_zeros() for barrier/diversity buffers (async GPU-side, no HtoD)
- Pre-allocated host staging vectors for portfolio_init and RNG seeds

gpu_curiosity_trainer already optimized (fused kernel zeros internally).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 07:46:38 +01:00
jgrusewski
08eb6d5cc2 refactor(dedup): consolidate activations + AdamW kernels — PPO→ml-core
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>
2026-03-18 07:41:22 +01:00
jgrusewski
e068224830 refactor(dedup): consolidate GpuTensor/GpuLinear — ml-supervised→ml-core
Moved ml-supervised's duplicate GpuTensor (stream-carrying) + GpuLinear +
50 free functions to ml-core/cuda_autograd/stream_ops.rs as StreamTensor
and StreamLinear. ml-supervised/gpu_tensor.rs → 53 lines of re-exports.

Two tensor flavors now canonical in ml-core:
- GpuTensor: takes &Arc<CudaStream> per-call (autograd integration)
- StreamTensor: carries Arc<CudaStream> internally (self-contained ops)

Zero consumer import changes. Both crates compile clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 07:37:49 +01:00
jgrusewski
6d8ba0708c fix(ml-supervised): resolve all 104 compile errors — clean build
- mamba/mod.rs: ~90 errors fixed — _candle suffixed functions replaced,
  operator overloads→free functions, autograd→pseudo-gradients,
  checkpoint→JSON serialization
- gpu_tensor.rs: added gpu_eye, gpu_cat_dim0, gpu_stack_tensors
- TFT/SSD: unused imports cleaned, type mismatches fixed
- ml-core: GpuTensor algebra methods (17 new), cuda_compat.rs deleted,
  GpuVarStore::vars/all_vars/linear_xavier added

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 07:26:56 +01:00
jgrusewski
dd62f3fcfd refactor: eliminate candle from entire workspace — tests, examples, Cargo.toml
Final cleanup:
- 61 test files + 5 example files: candle imports replaced
- 8 testing/integration files: migrated to cudarc/ml-core types
- 3 services/trading_service test files: migrated
- Root Cargo.toml: candle-core, candle-nn removed from [workspace.dependencies]
- crates/ml/Cargo.toml: candle-nn dependency removed
- testing/e2e/Cargo.toml: candle-core dependency removed

Zero active candle_core/candle_nn/candle_optimisers code references remain.
Zero candle dependency declarations in any Cargo.toml.
Remaining "candle" strings are exclusively in doc comments.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 00:53:47 +01:00
jgrusewski
0d62bdba2e refactor(ml): eliminate candle from all 104 src files
Zero candle_core/candle_nn imports in ml/src/. Three-agent parallel migration:

- cuda_pipeline/ (19 files): Tensor→CudaSlice, Device→Arc<CudaStream>,
  VarMap→GpuVarStore, cudarc import path fixed
- trainers/ + adapters (45 files): DQN/PPO/TFT trainers, 10 ensemble
  adapters, 11 hyperopt adapters — all migrated to MlDevice, GpuTensor,
  GpuVarStore, GpuAdamW
- model dirs + infra (40 files): 10 trainable adapters, preprocessing,
  inference, transformers, validation, benchmarks

61 test/example files still reference candle — next commit.
candle-nn still in Cargo.toml (needed by tests until migrated).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 00:24:35 +01:00
jgrusewski
20319618b6 refactor(ml-supervised): eliminate candle — GpuTensor/GpuLinear throughout
Zero candle_core/candle_nn imports remain. All 13 source files migrated:

- TFT: GatedResidualNetwork, LSTMEncoder, TemporalAttention → GpuLinear
- Mamba2: SSM layers → GpuLinear + local GpuTensor ops, grad norm host-side
- Liquid CfC: Training rewritten with perturbation-based gradient estimation
- SSD layer: Constructor takes Arc<CudaStream> not VarBuilder
- gpu_tensor.rs: from_candle/to_candle bridge methods deleted

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 23:32:39 +01:00
jgrusewski
bf838976eb refactor(ml-dqn): eliminate candle — pure cudarc + GpuTensor/GpuLinear
Zero candle_core/candle_nn/candle_optimisers imports remain. All 25 source
files migrated to ml-core cuda_autograd types:

- Network structs: Vec<Linear> → Vec<GpuLinear>
- DQNAgent: VarMap→GpuVarStore, Adam→GpuAdamW, Device→MlDevice
- GpuReplayBuffer: Candle Tensor wrappers deleted, returns CudaSlice directly
- NoisyLinear: cudarc-native noise buffers
- All softmax/logit/entropy functions: &Tensor → &[f32]
- Module trait impls deleted, forward() is direct method

316 implementation-level errors remain (missing GpuTensor algebra methods:
argmax, gather, unsqueeze, etc.) — these are next-layer work, not candle deps.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 23:12:18 +01:00
jgrusewski
22004a7368 refactor(cuda): eliminate candle from ml-core, ml-ppo, and 4 thin crates
Hard refactor — no shims, no compat layers. Candle removed from Cargo.toml
and all source files in 6 crates:

- ml-core: MlDevice enum, checkpoint.rs (safetensors direct), cudarc imports
  fixed from candle re-export to direct, AdamWConfig lr_decay, cuda_compat
  gutted. Net -7,341 lines.
- ml-ppo: All 16 files rewritten. LSTM→CudaLSTM, VarMap→GpuVarStore,
  PPOAgent 2306→700 lines, checkpoint→binary format.
- ml-ensemble: GPU-resident sigmoid via custom CUDA kernel.
- ml-explainability: Integrated gradients via GPU finite-difference kernels.
- ml-labeling: Device→MlDevice.
- ml-hyperopt: Cargo.toml only.

Remaining: ml-dqn (24 files), ml-supervised (4 files), ml crate (104 files).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 22:27:56 +01:00
jgrusewski
622d7cd757 refactor(cuda): replace Candle internals with GpuLinear/GpuTensor in model adapters
Convert forward paths of TGGN, Liquid CfC, and multi-timeframe LSTM
encoder from Candle Linear/Module/ops to cuBLAS-backed GpuLinear and
gpu_* element-wise ops from ml-supervised. Candle Tensor remains at
the UnifiedTrainable trait boundary; VarMap/AdamW retained for autograd.

- TGGN: forward uses GpuLinear + gpu_relu instead of candle_nn::Linear
- Liquid CfC: RNN loop uses GpuLinear + gpu_sigmoid/gpu_tanh/gpu_mul
- MultiTimeframeEncoder: LSTM gates use gpu_matmul + gpu_sigmoid/gpu_tanh
- Checkpoints save GpuLinear weights as JSON instead of safetensors
- Remove candle_nn::Module, candle_nn::Linear from adapter imports

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 19:08:45 +01:00
jgrusewski
8a6bca399c refactor(cuda): replace Tensor→CudaSlice conversions with direct accessors in trainers/
Add CudaSlice<f32> accessor methods to GpuTrainResult and GradientResult
(loss_cuda_slice, grad_norm_cuda_slice) in ml-dqn, eliminating redundant
tensor_to_cuda_slice_f32 calls at every training guard check site.

- GpuTrainResult: add from_fused_scalars() constructor and CudaSlice extractors
- GradientResult: add CudaSlice extractors that delegate to GpuTrainResult
- fused_training.rs: use from_fused_scalars() instead of Tensor::new() wrapping
- train_step.rs: use result.loss_cuda_slice() instead of tensor_to_cuda_slice_f32
- training_loop.rs: same CudaSlice accessor pattern for both fused and accum paths

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 19:01:02 +01:00
jgrusewski
31a7c1b171 refactor(cuda): remove Candle intermediaries from cuda_pipeline weight extraction
- gpu_weights.rs: Remove slow-path Candle fallback (flatten/cast/contiguous)
  from extract_one() and sync_one(). F32-contiguous is now a hard requirement
  enforced by ensure_f32_contiguous() at network construction. Non-F32 or
  non-contiguous tensors produce a clear error instead of silently falling
  back to the Candle conversion pipeline.

- gpu_action_selector.rs: Remove unused ensure_contiguous_f32(). Keep
  stream_from_device/cuda_u32_to_tensor/cuda_f32_to_tensor as boundary
  converters (they have external callers).

- gpu_experience_collector.rs: Clean up doc comments referencing Candle
  where only cudarc CudaSlice is used.

- mod.rs: Update module docs to clarify Candle Tensor/Device are only
  at the upload boundary (DqnGpuData, PpoGpuData, tensor_to_cuda_slice).

- tlob/trainable_adapter.rs: Fix double candle_core:: path that broke build.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 18:57:53 +01:00
jgrusewski
2fb9220576 fix(dqn): F32 master weights across all DQN networks
All VarBuilder/NoisyLinear init changed from BF16 to F32.
The fused CUDA trainer's Adam kernel operates on F32 master weights
with separate BF16 mirrors for forward kernels. BF16 VarBuilder
caused dtype mismatches in Candle forward paths and broke ensure_f32.

381 ml-dqn tests pass, 0 errors, 0 warnings workspace-wide.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 18:32:25 +01:00
jgrusewski
c326b7f654 refactor(cuda): migrate Candle references to native cudarc in ml crate
Replace deprecated cudarc memcpy_stod with clone_htod across all GPU
upload paths (14 call sites in trainers, cuda_pipeline, hyperopt).
Replace deprecated memcpy_dtov with clone_dtoh in ml-supervised
gpu_tensor.rs.

Bridge GpuTensor-migrated submodules (xLSTM, Liquid CfC, TFT GRN)
with Candle Tensor callers via from_candle_tensor/to_candle_tensor
conversion utilities at API boundaries. Fix CudaDevice->CudaContext
in ml-dqn distributional_dueling.rs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 18:20:26 +01:00
jgrusewski
e459e108ba refactor(ml-dqn): migrate RMSNorm, LayerNorm, ResidualBlock from Candle to cuda_autograd
Replace VarMap/VarBuilder weight storage with GpuVarStore (native CUDA)
for RMSNorm, LayerNorm, and ResidualBlock. Linear layers in ResidualBlock
now use GpuLinear with cuBLAS sgemm. Cold-path forwards convert between
GpuTensor and Candle Tensor at the boundary since downstream ops (GELU,
LayerNorm, dropout, broadcast) still use Candle.

Changes:
- ml-core cuda_autograd: add GpuTensor::to_candle/from_candle interop,
  export GpuParam/AdamWConfig/ActivationKernels/LossKernels/LossResult,
  make init::upload_to_gpu public
- rmsnorm.rs: RMSNorm/LayerNorm now take (Arc<CudaStream>, Device, dim)
  instead of (VarBuilder, dim). Weights stored in GpuVarStore.
- residual.rs: ResidualBlock now takes (Arc<CudaStream>, Device, config, name).
  fc1/fc2 are GpuLinear with cuBLAS sgemm forward. LayerNorm params in GpuVarStore.
- distributional_dueling.rs: updated RMSNorm constructor calls to new API

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 18:14:46 +01:00
jgrusewski
8b11b7046f feat(ppo): wire cuda_nn into all PPO model code
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>
2026-03-17 17:57:37 +01:00
jgrusewski
1627ca57a1 fix(dqn): init weights as F32, not BF16 — fixes ensure_f32 Var::set crash
VarBuilder and NoisyLinear were creating BF16 weights, then ensure_f32
tried Var::set() which rejects dtype changes. Fix: create F32 from the
start. BF16 mirrors are managed separately by GpuDqnTrainer.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 15:34:19 +01:00
jgrusewski
0e2f82ab54 feat(cuda): complete Candle elimination + cudarc 0.19.3 upgrade
Integration of 7 hive agents:
- gpu_replay_buffer: 103 Candle refs → 0 (14 new CUDA kernels)
- gpu_action_selector: 27 refs → CudaSlice API
- signal_adapter: 26 refs → 3 new CUDA kernels
- gpu_experience_collector: 5 refs → CudaSlice output
- gpu_weights+iql+guard: 13 refs eliminated
- DQN forward: new forward_only_kernel for inference
- VarMap: F32 contiguous enforcement, fast-path extraction

New modules:
- ml-core/cuda_autograd: GpuTensor, GpuVarStore, GpuLinear, GpuAdamW
- ml-ppo/cuda_nn: CudaLinear, CudaLSTM, CudaAdam, networks
- ml-supervised/gpu_tensor: GpuTensor + cuBLAS for KAN, Diffusion

cudarc 0.17.3 → 0.19.3 (via candle 0.9.1 → 0.9.2)
safetensors 0.4 → 0.7

Zero errors, zero warnings workspace-wide.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 15:13:04 +01:00
jgrusewski
f92c9d5f79 feat(cuda): replace Candle with cudarc CudaSlice + cuBLAS in KAN and Diffusion models
Eliminate candle-core/candle-nn from the KAN and Diffusion model forward
paths in ml-supervised. All dense layers now use cuBLAS sgemm via the new
gpu_tensor module. Element-wise ops (SiLU, sigmoid, tanh, exp) use host
roundtrips for now; fused CUDA kernels are a follow-up.

Changes:
- Add gpu_tensor.rs: GpuTensor (CudaSlice<f32> + shape), GpuLinear
  (cuBLAS sgemm), and ~30 element-wise GPU ops
- Rewrite kan/{spline,layer,network}.rs to use GpuTensor instead of
  candle_core::Tensor and candle_nn::{VarBuilder,Linear}
- Rewrite diffusion/{denoiser,noise,sampler}.rs to use GpuTensor and
  GpuLinear instead of candle_nn::Linear
- Update ml crate trainable adapters (kan/trainable.rs,
  diffusion/trainable.rs) to bridge Candle<->GpuTensor at the
  UnifiedTrainable interface boundary
- Update ensemble inference adapters for both models
- Add cudarc 0.17 with cublas feature to ml-supervised Cargo.toml
- Candle deps retained in ml-supervised for unconverted models (TFT,
  Liquid, Mamba, xLSTM) -- will be removed once all 8 models are
  converted

Net: -424 lines, 509 -> ~453 Candle refs remaining (KAN: 0, Diffusion: 0)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 14:58:44 +01:00
jgrusewski
ad63c67499 feat(cuda): add cuda_autograd module — cudarc-native replacement for Candle autograd
Replace Candle's backward()/GradStore/VarMap/VarBuilder system with CUDA-native
primitives in ml-core::cuda_autograd. This eliminates the Candle dispatch overhead
(~2100 kernel launches per batch) for models that adopt the new API.

Components:
- GpuTensor: CudaSlice<f32> wrapper with shape metadata (replaces candle Tensor)
- GpuVarStore: named parameter store with flatten/unflatten (replaces VarMap/VarBuilder)
- GpuLinear: cuBLAS sgemm forward + manual backward (replaces candle_nn::Linear)
- GpuAdamW: per-parameter CUDA kernel optimizer (replaces candle_optimisers::Adam)
- ActivationKernels: ReLU/LeakyReLU/GELU/Sigmoid/Tanh forward+backward CUDA kernels
- LossKernels: MSE/Huber loss with fused gradient computation
- init: Xavier/Kaiming/near-zero initialization via CPU generate + GPU upload

Added cublas feature to ml-core's cudarc dependency for sgemm support.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 14:57:54 +01:00
jgrusewski
0ed2277319 feat(cuda): add CudaTrajectoryTensors and finalize cuda_nn module
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>
2026-03-17 14:37:44 +01:00
jgrusewski
43e10c8887 feat(cuda): add forward-only CUDA kernel for DQN inference, mark Candle forward paths as cold
Add dqn_forward_only_kernel to dqn_training_kernel.cu — runs a single
branching forward pass (shared layers -> value head -> 3 branch heads ->
C51 distributional -> expected Q) and writes per-branch Q-values to
q_out[B, 11]. No loss computation, no activation saves, no backward.
~3x faster than forward_loss for pure inference.

Wire the kernel into GpuDqnTrainer via forward_only_q() which takes
CudaSlice<f32> states directly and returns &CudaSlice<f32> Q-values,
replacing the Candle BranchingDuelingQNetwork::forward_branches() call
chain (~160 Candle kernel dispatches -> 1 fused CUDA launch).

Mark all 6 ml-dqn Candle forward methods as #[cold] with documentation
that hot-path compute is handled by fused CUDA kernels:
- noisy_layers.rs: NoisyLinear::forward() -> dqn_experience_kernel.cu
- quantile_regression.rs: QuantileNetwork::forward() -> iqn_dual_head_kernel.cu
- distributional.rs: CategoricalDistribution::to_scalar() -> dqn_forward_only_kernel
- curiosity.rs: ForwardDynamicsModel::predict() -> curiosity_training_kernel.cu
- residual.rs: ResidualBlock::forward() -> dqn_forward_only_kernel
- rmsnorm.rs: RMSNorm::forward() / LayerNorm::forward() -> dqn_experience_kernel.cu

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 14:36:40 +01:00
jgrusewski
da2aff98c6 feat(cuda): add CudaPolicyNetwork and CudaValueNetwork to ml-ppo cuda_nn
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>
2026-03-17 14:35:52 +01:00
jgrusewski
4ef9cdb169 refactor(cuda): replace Candle re-export paths with direct cudarc in ml-core
CUDA infrastructure in ml-core (cuda_compile, gpu/capabilities, gpu/l2_cache)
previously accessed cudarc types through candle_core::cuda_backend::cudarc --
a transitive re-export that coupled low-level CUDA driver calls to Candle.

Changes:
- Add cudarc 0.17 as direct optional dep (gated behind cuda feature)
- Replace all candle_core::cuda_backend::cudarc paths with direct cudarc::
- Generalize OOM detection to accept &dyn Debug (was &CandleError)
- Add generic computation_error_to_common_error() alongside legacy alias

Candle references: 163 -> 72 (remaining are autograd-dependent: Tensor,
Var, GradStore, VarBuilder -- cannot be replaced with cudarc raw ops)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 14:34:58 +01:00
jgrusewski
39feb10f13 feat(cuda): add cuda_nn module to ml-ppo — raw cudarc/cuBLAS neural network primitives
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>
2026-03-17 14:34:07 +01:00
jgrusewski
a29f109b67 fix(cuda): resolve 35 caller-boundary type mismatches after CudaSlice migration
Update all callers to match the new pure-cudarc APIs introduced by the
hive agent CudaSlice migration. Key changes:

- GpuTrainingGuard::new() now takes Arc<CudaStream>; callers use from_device()
- check_and_accumulate/qvalue_stats/qvalue_divergence take &CudaSlice<f32>
  instead of &Tensor; callers convert via tensor_to_cuda_slice_f32()
- accumulate_q_value takes f32 scalar, returns () (no Result)
- GpuReplayBuffer::insert_batch gains batch_size arg, takes CudaSlice params
- signal_adapter functions take &Arc<CudaStream> (cudarc 0.17 Arc requirement)
- Add tensor_to_cuda_slice_u32() and cuda_f32_to_tensor() utility functions
- Replace CudaView usage with owned CudaSlice via tensor_to_cuda_slice_f32()
- Fix CudaStorage.device field access (was method call in older API)
- Fix borrow-after-move in copy_actions_out via scoped DtoD copy

Zero errors, zero warnings across lib + tests + examples + full workspace.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 14:05:31 +01:00
jgrusewski
bc75cdd7c9 refactor(cuda): enforce F32 contiguous weights, eliminate Candle intermediaries from VarMap extraction
BranchingDuelingQNetwork now converts all weight tensors to F32 contiguous
at construction via ensure_f32_contiguous(). This guarantees the invariant
that extract_one/sync_one/reverse_sync_one in gpu_weights.rs can bypass
the flatten_all().to_dtype(F32).contiguous() Candle pipeline and do a
single direct DtoD memcpy from the Var's CUDA storage.

Key changes:
- branching.rs: add ensure_f32_contiguous() + ensure_f32() for NoisyLinear
  sigma/epsilon, forward_branches casts to F32 instead of BF16
- noisy_layers.rs: sample_noise/reset_noise/disable_noise use weight_mu
  dtype (F32 after ensure_f32) instead of hardcoded BF16, add ensure_f32()
  to convert sigma vars and epsilon buffers
- gpu_weights.rs: extract_one/sync_one fast path skips Candle ops when
  tensor is already F32 contiguous, reverse_sync_one fixed to access Var's
  own CUDA storage directly (was writing to a temporary F32 tensor before)
- fused_training.rs: updated doc comments for CudaSlice-as-source-of-truth

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 13:36:13 +01:00
jgrusewski
4047ee1961 refactor(cuda): replace Candle Tensor internals with cudarc CudaSlice in GpuReplayBuffer
Eliminate all ~103 Candle Tensor references from GpuReplayBuffer internals.
Ring buffer storage, insert, cumsum, searchsorted, gather, IS-weight
computation, and priority update now run as 16 custom CUDA kernels via
pure cudarc CudaSlice operations -- zero Candle dispatch overhead.

Key changes:
- GpuReplayBuffer stores CudaSlice<u16/u32/f32> + Arc<CudaStream>
- New replay_buffer_kernels.cu with 14 entry points (scatter insert,
  gather, pow_alpha, IS weights, priority update, reduce_max, etc.)
- Removed SearchSorted CustomOp2 and CumsumOp CustomOp1 wrappers
  (direct kernel launch via compiled CudaFunction handles)
- StagedGpuBuffer::flush uses CudaSlice HtoD + insert_batch
- Output GpuBatch preserves Candle Tensor fields for downstream
  neural network compatibility (DtoD wrap at output boundary)
- Added half crate dependency for BF16 CudaSlice<->Tensor wrapping

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 13:32:33 +01:00
jgrusewski
f68c324c30 refactor(cuda): replace Candle Tensor with cudarc CudaSlice in gpu_experience_collector
Eliminate ALL Candle Tensor usage from gpu_experience_collector.rs to fix
cross-stream deadlocks. The old code created Candle Tensors on the forked
CudaStream, which conflicted with Candle's default stream (stream 0) used
by GpuReplayBuffer::insert_batch.

Changes to gpu_experience_collector.rs:
- GpuExperienceBatch now holds CudaSlice<f32>/CudaSlice<i32> instead of Tensor
- Remove cuda_slice_to_tensor_f32() and cuda_slice_i32_to_tensor_u32() helpers
- Add dtod_clone_f32(), dtod_clone_i32() pure-cudarc DtoD copy helpers
- Add build_next_states_dtod() for episode-aware state shift via pointer math
- Remove device: &Device parameter from collect_experiences_gpu()
- Remove candle_core::{DType, Device, Tensor} import entirely

Changes to training_loop.rs:
- Move CudaSlice->Tensor conversion to training_loop boundary (post stream-sync)
- Add cuda_slice_to_tensor_f32() and cuda_slice_i32_to_tensor_u32() at boundary
- Conversions happen AFTER stream.synchronize(), on the synced stream -- no
  cross-stream hazard

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 13:27:03 +01:00
jgrusewski
9f0bf84443 refactor(cuda): update DQN action callers to use pure CudaSlice API
action.rs:
- Replace Tensor-based selector calls with CudaSlice extraction via
  extract_cuda_f32! macro + ensure_contiguous_f32() helper
- Constructor now passes Arc<CudaStream> via stream_from_device()
- Replace per-element narrow+squeeze+to_scalar readback with bulk
  readback_actions() DtoH memcpy (single transfer vs N scalar reads)
- select_actions_branching() now takes explicit batch_size parameter

metrics.rs:
- Same CudaSlice extraction pattern for validation action selection
- Wrap CudaSlice<u32> result back to Candle Tensor via cuda_u32_to_tensor()
  for downstream GPU PnL/Sharpe computation that needs Tensor ops

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 13:19:43 +01:00
jgrusewski
81513983d8 refactor(cuda): eliminate Candle Tensor from gpu_iql_trainer and gpu_training_guard
gpu_iql_trainer.rs:
- Remove Tensor-based train_value_step(), compute_advantages(),
  advantage_weights(), forward_only(), and tensor_to_cuda_slice_f32()
- Rename train_value_step_raw() to train_value_step() (now the only version)
- Remove candle_core::{DType, Tensor} import (only cudarc re-export remains)

gpu_training_guard.rs:
- Replace Device field with Arc<CudaStream> — no Candle Device stored
- Constructor takes Arc<CudaStream>; from_device() adapter for callers
- check_and_accumulate() takes &CudaSlice<f32> instead of &Tensor
- qvalue_stats() and qvalue_divergence() take &CudaSlice<f32>
- accumulate_q_value() takes f32 scalar (CPU-side Welford, no GPU tensor)
- read_q_accumulator() and reset_q_accumulator() simplified (no Tensor)

cuda_pipeline/mod.rs:
- Add tensor_to_cuda_slice_f32() shared utility for callers at the boundary

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 13:17:53 +01:00
jgrusewski
4127828d65 refactor(cuda): replace Candle Tensor with cudarc CudaSlice in signal_adapter
Eliminate all 26 Candle Tensor references from signal_adapter.rs.
Three functions (ppo_to_exposure_scores, signal_to_action_scores,
tft_quantile_to_signal) now take CudaSlice<f32> + CudaStream and
return CudaSlice<f32>, backed by three fused CUDA kernels in
signal_adapter_kernel.cu. Dead code evaluate_supervised_gpu_backtest
removed (zero callers). Added cuda_f32_to_tensor helper to
gpu_action_selector for DtoD copy back to Candle Tensor at API
boundaries (PPO hyperopt adapter, evaluate_baseline example).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 13:16:58 +01:00
jgrusewski
3db5e86db9 fix(cuda): synchronize forked stream before cross-stream replay buffer ops
GpuReplayBuffer uses Candle Tensor ops (slice_scatter, cumsum, narrow)
which execute on Candle's default stream (stream 0). The experience
collector outputs data on a forked CudaStream. Without an explicit
synchronization barrier, the default stream may read partially-written
data from the forked stream, causing cross-stream deadlocks or data
corruption.

Add stream.synchronize() in three places:
- training_loop.rs: after experience collection, before insert_batch_tensors
- gpu_experience_collector.rs: cuda_slice_to_tensor_f32 after DtoD copy
- gpu_experience_collector.rs: cuda_slice_i32_to_tensor_u32 after DtoD copy

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 12:54:25 +01:00
jgrusewski
f79739fa51 fix(cuda): remove disable_event_tracking entirely
With unified forked stream, cudarc event tracking is self-referencing
(no cross-stream conflicts). disable_event_tracking caused more
problems than it solved — stale events, Drop failures, hangs.
Let cudarc manage events naturally on the single stream.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 12:37:09 +01:00
jgrusewski
2196cdc60c fix(cuda): disable event tracking at stream fork, not at trainer init
Moving disable_event_tracking() from GpuDqnTrainer::new() to the
stream fork in constructor.rs ensures ALL CudaSlice allocations
(experience collector, replay buffer, curiosity trainer) are created
with tracking already disabled. Previous placement caused hangs because
earlier allocations had stale events.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 12:23:34 +01:00
jgrusewski
b39d10c968 fix(cuda): remove Candle from GPU training hot path
- upload_batch_gpu: BF16→F32 via CUDA kernel, no Candle to_dtype()
- IQL: reuses DQN trainer's F32 buffers via train_value_step_raw()
- Re-enable disable_event_tracking() — safe with unified stream
- Reduce smoke test epochs to 1 (debug mode is too slow for 3)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 12:07:59 +01:00
jgrusewski
3b4ba67fc5 perf(cuda): eliminate Candle VarMap/Tensor from DQN GPU training hot path
Replace all Candle tensor operations in the fused training loop with
pure cudarc CudaSlice operations to prevent Candle tensor Drops from
recording events on the default stream (which conflicts with our forked
training stream via cudarc event tracking).

Key changes:
- Re-enable disable_event_tracking() in GpuDqnTrainer::new() — safe now
  that no Candle tensors are created during training
- Rewrite upload_batch_gpu(): BF16 states/next_states use DtoD + bf16→f32
  kernel instead of Candle to_dtype(F32); F32 rewards/dones/weights use
  direct DtoD copy with layout offset handling
- Load bf16_to_f32_kernel from training module (was defined in CUH but
  not loaded)
- Add train_value_step_raw() to GpuIqlTrainer that takes CudaSlice<f32>
  directly, bypassing Candle tensor manipulation
- IQL in fused training now reuses DQN trainer's already-converted F32
  states_buf/rewards_buf instead of creating Candle temporaries
- Fix dtod_from_candle_f32/u32 to respect Candle layout start_offset

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 11:33:14 +01:00
jgrusewski
44ddd38112 fix(cuda): unify all GPU ops onto single forked CudaStream
Fork a dedicated CudaStream once in DQNTrainer constructor and share it
via Arc::clone with all GPU components (experience collector, fused
trainer, portfolio sim, monitoring). This eliminates:

1. Candle's default stream (stream 0) from the training hot path
2. Dual-stream cudarc event tracking conflicts during CUDA Graph capture
3. Implicit serialization points between default and forked streams

The fused training context no longer forks its own stream — it receives
the trainer's unified stream. With all GPU work on a single stream,
cudarc event tracking is safely disabled in GpuDqnTrainer::new(),
removing the cuStreamWaitEvent/cuEventRecord overhead that broke CUDA
Graph capture.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 10:42:56 +01:00
jgrusewski
fa739ba40c wip(cuda): identify dual-stream root cause, TODO for stream unification
The fused DQN trainer uses a forked stream (for CUDA Graph capture)
while the experience collector + curiosity trainer use Candle's
default stream (stream 0). cudarc's context-wide event tracking
creates conflicts between the two streams.

disable_event_tracking() is context-wide — it fixes the forked
stream but breaks the default stream (curiosity trainer hangs).

Proper fix: create ONE forked stream at DQNTrainer construction,
pass it to ALL GPU components (experience collector, curiosity
trainer, portfolio sim, fused trainer). No Candle default stream
in the hot path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 10:17:59 +01:00
jgrusewski
475a22b2d6 refactor(test): single production-config smoke test, all features always on
Replace 13 per-feature toggle tests with 1 production-config test.
There is no code path without curiosity, IQN, branching, CQL, Kelly,
action masking, circuit breaker — these are always on in production.

smoke_params() now enables everything: Rainbow DQN + branching + IQN +
curiosity + CQL + Kelly + action masking + circuit breaker.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 10:02:55 +01:00
jgrusewski
d792abdfbe fix(test): switch smoke_test_real_data to BranchingDuelingQNetwork
DuelingQNetwork uses advantage_fc.* VarMap keys, but
GpuExperienceCollector expects branch_0_fc.* (branching always on).
Also remove stale use_noisy_nets/use_distributional fields.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 09:41:49 +01:00