Files
foxhunt/docs/superpowers/specs/2026-04-19-kernel-optimization-design.md
jgrusewski d070dccf6e spec: kernel optimization — cuBLAS projections + batch-parallel scans
nsys data: custom kernels = 95% of GPU time. mamba2_scan_backward = 75%.
Root cause: one-thread-per-weight anti-pattern serializes B=8192 samples.
Fix: cuBLAS for projections, lightweight scans for sequential state.
Target: 1950ms/step → <50ms/step.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 12:10:49 +02:00

152 lines
6.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.
# Custom Kernel Optimization — cuBLAS Projections + Batch-Parallel Scans
## Problem
nsys profiling reveals custom CUDA kernels consume 95% of GPU time. cuBLAS GEMMs
take <1%. The root cause: all custom kernels use a "one thread per weight, loop
over batch" anti-pattern that serializes B=8192 samples into each thread.
## nsys Breakdown (batch=8192, H100 PCIe)
| Kernel | Time% | ms/call | Calls | Pattern |
|--------|-------|---------|-------|---------|
| mamba2_scan_backward | 75.0% | 1567 | 25 | 1 thread/weight, loop B×K×SH2 |
| q_denoise_backward | 7.3% | 152 | 25 | 1 thread/weight, loop B |
| iqn_bias_grad_reduce | 3.9% | 16 | 130 | 1 thread/neuron, loop B×Q |
| kan_grad_reduce | 3.2% | 8 | 208 | 1 thread/param, loop B |
| curiosity_fwd_bwd_per_block | 2.1% | 1085 | 1 | per-block reduction |
| mamba2_temporal_scan | 1.1% | 22 | 26 | 1 thread/sample (OK), inner matmul SH2 |
| risk_budget_forward | 1.0% | 20 | 26 | 1 thread/sample, inner matmul SH2 |
## Design
### 1. Mamba2 Scan — cuBLAS Projections + Lightweight Scan (75% → <0.5%)
**Current:** One thread per weight element (12,288 threads), each loops over all
8192 samples. Each iteration does inner products over SH2=256 dimensions. Total:
12,288 × 8192 × 8 × 256 = 206 billion operations on 12,288 threads.
**Reference:** Official Mamba CUDA kernel (state-spaces/mamba) uses
`grid=(batch, dim)` — one block per (sample, feature). Uses `cub::BlockScan` for
sequential dependency. Our kernel uses the exact opposite pattern.
Source: https://github.com/state-spaces/mamba/blob/main/csrc/selective_scan/selective_scan_bwd_kernel.cuh
**New (Approach C — cuBLAS projections + lightweight scan):**
#### Forward (`mamba2_temporal_scan`):
Step 1: Pre-project all timesteps with cuBLAS (3 GEMMs):
```
A_vals[B×K, STATE_D] = h_history[B×K, SH2] @ W_A[SH2, STATE_D] // m=16, n=B×K, k=256
B_vals[B×K, STATE_D] = h_history[B×K, SH2] @ W_B[SH2, STATE_D]
C_vals[B, STATE_D] = h_s2[B, SH2] @ W_C[SH2, STATE_D]
```
Step 2: Lightweight scan kernel — grid=(B, ceil(STATE_D/32)), block=(32):
```c
// Each thread handles one (sample, state_dim) pair
// K=8 sequential steps — trivial, no inner matmul
int b = blockIdx.x;
int s = blockIdx.y * blockDim.x + threadIdx.x;
float x = 0.0f;
for (int t = 0; t < K; t++) {
float gate = sigmoid(A_vals[b*K*D + t*D + s]);
x = gate * x + B_vals[b*K*D + t*D + s];
}
// temporal_context = C_vals[b,s] * x
float context_s = C_vals[b*D + s] * x;
// Accumulate into h_enriched via atomicAdd or shared memory reduce
```
Step 3: Apply temporal context to h_enriched (element-wise kernel or cuBLAS).
#### Backward (`mamba2_scan_backward`):
Step 1: Pre-project (same 3 cuBLAS GEMMs as forward — replay activations).
Step 2: Lightweight reverse scan kernel — grid=(B, STATE_D):
```c
// Forward replay + reverse scan for d_x
// Each thread: one (sample, state_dim) pair
// K=8 steps forward, K=8 steps reverse — 16 iterations, no inner matmul
```
Step 3: Weight gradient accumulation via cuBLAS:
```
d_W_A[SH2, STATE_D] = h_history^T[SH2, B×K] @ d_gate[B×K, STATE_D] // one GEMM
d_W_B[SH2, STATE_D] = h_history^T[SH2, B×K] @ d_x[B×K, STATE_D] // one GEMM
d_W_C[SH2, STATE_D] = h_s2^T[SH2, B] @ d_context[B, STATE_D] // one GEMM
```
Total: 6 cuBLAS GEMMs + 2 lightweight scan kernels.
Expected: 1567ms + 22ms → ~5ms total.
### 2. Q-Denoise Backward (7.3% → <0.1%)
**Current:** 2-layer MLP backward (12→24→12), one thread per weight (1800 params),
loops over B=8192.
**New:** Replace with 2× cuBLAS backward GEMMs + 2× bias_grad_reduce.
Same as the main DQN backward pass. Dimensions are tiny (24×24, 12×24) but
batch=8192 makes it a proper GEMM.
### 3. IQN Bias Grad Reduce (3.9% → <0.2%)
**Current:** 130 calls × 16ms. Each reduces [B×Q, out_dim] over batch. One thread
per output neuron, loops over B×Q=524,288. With out_dim=3, that's 3 threads doing
524K iterations each.
**New:** 2-phase warp reduction:
- Phase 1: grid=(ceil(B×Q/256), out_dim), block=256. Each block sums 256 elements.
- Phase 2: reduce block partials → final result.
Or use `cub::DeviceSegmentedReduce` for segmented reduction.
### 4. KAN Grad Reduce (3.2% → <0.1%)
Same pattern as IQN — one thread per param, loop over batch. Fix: batch-parallel
reduction with warp shuffle.
### 5. Curiosity Fwd+Bwd (2.1% → <0.1%)
**Current:** 1 instance × 1085ms. Check grid dimensions — likely too few blocks.
**New:** 2× cuBLAS GEMMs for the curiosity MLP (already cuBLAS for inference,
but training kernel is custom). Replace with cuBLAS forward + backward +
element-wise loss.
### 6. Risk Budget Forward (1.0% → <0.01%)
**Current:** One thread per sample, inner matmul loop over SH2=256.
**New:** 2× cuBLAS GEMMs. Concat [h_s2; ISV; pred_error] → GEMM1 → ReLU → GEMM2 → sigmoid.
Same pattern as the main DQN forward pass.
## Scratch Buffer Requirements
Mamba2 pre-projection needs:
- `A_vals[B×K, STATE_D]` = 8192 × 8 × 16 × 4 = 4MB
- `B_vals[B×K, STATE_D]` = 4MB
- `C_vals[B, STATE_D]` = 0.5MB
- `d_gate[B×K, STATE_D]` = 4MB (backward only)
- `d_x[B×K, STATE_D]` = 4MB (backward only)
Total: ~17MB additional VRAM. Negligible on H100.
## Success Criteria
- Step time: 1950ms → <50ms
- mamba2_scan_backward: 1567ms → <3ms
- All custom kernels use batch-parallel patterns (no per-weight batch loops)
- 899 unit tests + 20 smoke tests pass
- Training loss and grad_norm match within 5% of current values
## Files Modified
- `crates/ml/src/cuda_pipeline/experience_kernels.cu` — mamba2_scan_backward, q_denoise_backward, kan_grad_reduce, risk_budget_forward
- `crates/ml/src/cuda_pipeline/mamba2_temporal_kernel.cu` — mamba2_temporal_scan
- `crates/ml/src/cuda_pipeline/iqn_dual_head_kernel.cu` — iqn_bias_grad_reduce
- `crates/ml/src/cuda_pipeline/curiosity_training_kernel.cu` — curiosity_fwd_bwd_per_block
- `crates/ml/src/cuda_pipeline/gpu_dqn_trainer.rs` — new cuBLAS GEMM descriptors for Mamba2 projections, denoise, risk_budget
- `crates/ml/src/trainers/dqn/fused_training.rs` — updated capture for new kernel topology