Files
foxhunt/docs/plans/2026-02-28-gpu-pipeline-design.md
jgrusewski 52630a77d3 perf: eliminate heap-alloc Decimal→float casts across 19 files (36 instances)
Replace all `.to_string().parse::<f32/f64>()` patterns with
`num_traits::ToPrimitive` methods (`.to_f32()`, `.to_f64()`).
Each string roundtrip heap-allocated per conversion — fatal in
DQN hot loop (300K+ bars × epochs). Decimal stays as canonical
financial type; conversions happen at GPU/float boundaries only.

Also fixes blocking_read() in async context (risk_integration.rs).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 02:29:04 +01:00

111 lines
4.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# GPU Pipeline Design: Custom CUDA Kernels for DQN/PPO Hyperopt
**Date**: 2026-02-28
**Status**: Approved (casting fixes implemented, CUDA kernels pending)
## Problem
Pipeline #208 confirmed GPU works but only hits 12% utilization on L4 (24GB).
Root cause: experience collection hot loop (trainer.rs:1317-1605) runs ~300K per-bar
iterations on CPU doing portfolio tracking, barrier checks, reward calculation, and
wasteful Decimal-to-float conversions via heap-allocated Strings.
## Phase 0: Casting Fixes (DONE)
Eliminated all `.to_string().parse::<f32/f64>()` patterns across 19 files (36 instances).
Replaced with `num_traits::ToPrimitive` methods (`.to_f32()`, `.to_f64()`).
Key changes:
- `feature_vector_to_state`: `price.to_string().parse::<f32>()``price.to_f32().unwrap_or(0.0)`
- Reward output: `reward_decimal.to_string().parse::<f32>()``reward_decimal.to_f32().unwrap_or(0.0)`
- Barrier scaling: `scaled.to_string().parse::<f32>()``scaled.to_f64().unwrap_or(0.0)`
- risk_integration.rs: `blocking_read()``.read().await` (no sync-in-async)
- 32 test files: same pattern fixes
Decimal stays as the canonical financial type. Conversions happen at GPU boundaries via ToPrimitive.
## Phase 1: Pre-upload Data to GPU (next)
Upload all training data as f32 GPU tensors at epoch start. Eliminates per-bar
`Vec<f32>` allocations and f64→f32 conversions.
```
Before: [CPU f64 Vec] → per-bar copy → Tensor → GPU
After: [GPU f32 tensor, pre-uploaded at epoch start]
```
## Phase 2: GPU Portfolio Simulator
New CUDA kernel `portfolio_sim_kernel` replaces `PortfolioTracker::execute_action()`.
Sequential per trial (inherent dependency), parallel across 5 hyperopt trials via
CUDA streams.
```cuda
__global__ void portfolio_sim_kernel(
const float* prices, // [num_bars, 4]
const int* actions, // [num_bars]
float* portfolio_state, // [5] per trial
float* portfolio_features, // [num_bars, 3] output
float initial_capital, float avg_spread, float cash_reserve_pct,
int num_bars
);
```
## Phase 3: Fused Experience Collection Kernel
Single CUDA kernel replaces entire inner loop body (state construction + portfolio sim
+ barrier tracking + reward calculation):
```cuda
__global__ void fused_experience_kernel(
const float* features, // [num_bars, 51]
const float* targets, // [num_bars, 4]
const int* actions, // [num_bars]
float* states_out, // [num_bars, 54]
float* next_states_out, // [num_bars, 54]
float* rewards_out, // [num_bars]
int8_t* barrier_labels_out, // [num_bars]
float* portfolio_state, // [num_trials, 5]
float* barrier_state, // [num_trials, 4]
float* reward_stats, // [num_trials, 3]
float initial_capital, float avg_spread,
int num_bars, int trial_id
);
```
## Phase 4: GPU Backtest
CUDA kernel for `EvaluationEngine::process_bar()`: position tracking in registers,
P&L per bar, aggregate metrics via atomic reduction.
## VRAM Budget (L4 24GB)
| Buffer | Size (5 trials × 300K bars) |
|--------|---------------------------|
| Features + Targets | 330 MB |
| States + Actions + Rewards | 556 MB |
| Replay buffer | 220 MB |
| DQN networks (5 × online+target) | ~50 MB |
| **Total** | **~1.2 GB** |
## Implementation
- New module: `crates/ml/src/cuda/` (parallel to `liquid/cuda/`)
- Kernel compilation: NVRTC at runtime (same as liquid/cuda/mod.rs)
- cudarc: already transitive dep via candle-core
- Feature gate: `cuda-pipeline`, CPU path always compiles
## Expected Performance
| Metric | Before (Phase 0) | After (Phase 3) |
|--------|-------------------|-----------------|
| GPU utilization | ~15-20% | 70-85% |
| Per-trial speed | ~40 min | ~8-12 min |
| 5-trial hyperopt | ~40 min | ~15-20 min |
## PPO Compatibility
PPO's hot loop is already f32-native (no Decimal). PPO benefits from Phase 1
(pre-upload) and Phase 3 (batched tensor construction replacing per-step
`Tensor::from_vec(state.clone(), ...)`).