# BF16 Native Completion — Zero Casts, Zero nvrtc, Zero GpuTensor > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Complete the full BF16 conversion: native BF16 arithmetic in ALL CUDA kernels (no __bfloat162float casts), delete GpuTensor + nvrtc dependency, fix all Rust compilation errors, wire cublasGemmEx. **Architecture:** Each CUDA kernel is converted individually by a dedicated agent that reads the full file and rewrites float locals/shared memory to `__nv_bfloat16` using bf16_* wrappers from `common_device_functions.cuh`. GpuTensor is deleted and replaced with raw `CudaSlice` operations. The nvrtc cudarc feature is removed entirely — all kernels precompiled as cubins. **Tech Stack:** Rust, CUDA (`__nv_bfloat16`, `cuda_bf16.h`), cudarc 0.19.3 (`CudaSlice`, `f16` feature), cuBLAS (`cublasGemmEx`, `CUDA_R_16BF`) --- ## ⛔ LESSONS LEARNED — DO NOT REPEAT | What failed | Why | Correct approach | |---|---|---| | Bulk `sed` on CUDA kernel internals | Mangles parentheses, creates `float * __nv_bfloat16` ambiguity | Agent reads FULL file, converts MANUALLY | | Changing types without fixing arithmetic | `__nv_bfloat16` has no implicit conversion to/from `float` | Change BOTH pointer types AND local variable types | | Leaving `float` shared memory with BF16 buffers | `__shfl_xor_sync` and arithmetic mix BF16 and float | Shared memory → `__nv_bfloat16`, use `bf16_warp_sum()` | | Agents creating conversion layers | `f32_to_bf16_kernel` per-step = conversion layer | Data born as BF16 from source, NO per-step conversion | | Incremental kernel conversion | Forward produces BF16, loss expects F32 → mismatch | ALL kernels converted atomically | ## BF16 Conversion Rules (for EVERY agent) ``` POINTERS: float* → __nv_bfloat16* (already done at parameter level) LOCALS: float val → __nv_bfloat16 val (when loading from BF16 buffers) SHARED MEM: __shared__ float → __shared__ __nv_bfloat16 ARITHMETIC: +, -, *, /, >, < → native (SM80+) MATH: sqrtf() → bf16_sqrt() (from common header) expf() → bf16_exp() logf() → bf16_log() powf() → bf16_pow() fabsf() → bf16_fabs() fmaxf() → bf16_fmax() fminf() → bf16_fmin() cosf() → bf16_cos() rsqrtf()→ bf16(rsqrtf(__bfloat162float(x))) (no bf16 rsqrt HW) tanhf() → bf16(tanhf(__bfloat162float(x))) SHUFFLE: __shfl_xor_sync() → bf16_shfl_xor() warp sum → bf16_warp_sum() warp max → bf16_warp_max() ATOMIC: atomicAdd() → atomicAddBF16() (from common header) LITERALS: 0.0f → bf16_zero() 1.0f → bf16_one() other → bf16(literal) SCALARS: float lr, float gamma → KEEP as float, convert at use: bf16(lr) INTEGERS: int → KEEP as int EXCEPTIONS: total_loss → float* (single scalar, atomicAdd monitoring) grad_norm_buf → float* (single scalar) ``` --- ## Current State (commit f2304d68) | Component | Status | |---|---| | Rust types (`CudaSlice` → `CudaSlice`) | ✅ Done across ml + ml-core | | CUDA kernel parameter types (`float*` → `__nv_bfloat16*`) | ✅ Done, 36/37 compile | | CUDA kernel INTERNAL code (float locals → __nv_bfloat16) | ❌ 35 files have casts | | dt_kernels.cu compilation | ❌ Fails (last kernel) | | Common header BF16 wrappers | ✅ Complete (bf16_sqrt, bf16_warp_sum, etc.) | | ml-core Rust errors | ❌ 45 errors (22 nvrtc + 23 BF16 boundary) | | ml crate Rust errors | ❌ 23 errors | | cublasGemmEx wired | ❌ forward_online_bf16 exists but not wired | | GpuTensor | ❌ Still exists, wraps CudaSlice, uses nvrtc | --- ## Phase 1: CUDA Kernels — Native BF16 Internals ### Task 1: Fix dt_kernels.cu (the one failing kernel) **Complexity:** HIGH (24 casts, 14 kernels, 723 lines, shared memory, warp shuffles, layernorm, softmax, GELU) **Files:** - Modify: `crates/ml/src/cuda_pipeline/dt_kernels.cu` - [ ] **Step 1: Read the ENTIRE file (723 lines)** - [ ] **Step 2: Convert ALL float output parameters to __nv_bfloat16*** The following output parameters are still `float*` and must change: - `dt_embed_kernel`: `output` → `__nv_bfloat16*` - `dt_qkv_projection_kernel`: `Q_out`, `K_out`, `V_out` → `__nv_bfloat16*` - `dt_causal_attention_kernel`: `output` → `__nv_bfloat16*` - `dt_layernorm_kernel`: `output` → `__nv_bfloat16*` - `dt_ffn_kernel`: `output` → `__nv_bfloat16*` - `dt_action_head_kernel`: `logits` → `__nv_bfloat16*` - `dt_return_to_go_kernel`: `returns_to_go` → `__nv_bfloat16*` - `dt_build_trajectories_kernel`: `trajectories` → `__nv_bfloat16*` - `dt_compute_rewards_actions_kernel`: `rewards` → `__nv_bfloat16*` - Keep `total_loss` and `per_sample_loss` as `float*` (loss monitoring scalars) - Keep `d_logits`, `dW`, `db`, `d_input` as `__nv_bfloat16*` (already converted) - [ ] **Step 3: Convert ALL float locals to __nv_bfloat16** - [ ] **Step 4: Convert shared memory to __nv_bfloat16** `extern __shared__ float shmem[]` → `extern __shared__ __nv_bfloat16 shmem[]` All `float*` pointers into shmem → `__nv_bfloat16*` Warp reduction: use `bf16_warp_sum()` / `bf16_warp_max()` - [ ] **Step 5: Replace math functions with bf16_* wrappers** `expf()` → `bf16_exp()`, `logf()` → `bf16_log()`, `rsqrtf((float)N)` → `bf16(rsqrtf((float)N))`, `tanhf()` → `bf16(tanhf(__bfloat162float(x)))` (no bf16 tanh wrapper — add if needed), `fmaxf/fminf` → `bf16_fmax/bf16_fmin` - [ ] **Step 6: Fix float literals** `0.0f` → `bf16_zero()`, `1.0f` → `bf16_one()`, `-1e30f` → `bf16(-1e30f)`, `0.7978845608f` → `bf16(0.7978845608f)`, `0.044715f` → `bf16(0.044715f)`, `1e-8f` → `bf16(1e-8f)` - [ ] **Step 7: Verify compilation** Run: `SQLX_OFFLINE=true cargo check -p ml 2>&1 | grep "nvcc failed" | wc -l` Expected: 0 (all 37 kernels compile) - [ ] **Step 8: Commit** ```bash git commit -m "feat(bf16): dt_kernels.cu native BF16 — all 37 CUDA kernels compile" ``` --- ### Task 2: Convert simple kernels to native BF16 (batch of 12) **Complexity:** LOW (1-5 casts each, small kernels) **Files (12 kernels, one agent):** - `ema_kernel.cu` (3 casts) - `relu_mask_kernel.cu` (1 cast) - `per_update_kernel.cu` (2 casts) - `iqn_cvar_kernel.cu` (2 casts) - `trade_stats_kernel.cu` (1 cast) - `monitoring_kernel.cu` (1 cast) - `backward_kernels.cu` (3 casts) - `backtest_forward_supervised_kernel.cu` (1 cast) - `backtest_forward_ppo_kernel.cu` (1 cast) - `statistics_kernel.cu` (3 casts) - `curiosity_training_kernel.cu` (2 casts) - `her_relabel_kernel.cu` (3 casts) For each file: - [ ] Read file, find all `__bfloat162float()` / `__float2bfloat16()` casts - [ ] Change the local variable from `float` to `__nv_bfloat16` - [ ] Remove the cast wrapper - [ ] Replace math functions with bf16_* wrappers where needed - [ ] Fix float literals → bf16() - [ ] **Verify:** `SQLX_OFFLINE=true cargo check -p ml 2>&1 | grep "nvcc failed" | wc -l` = 0 - [ ] **Commit** --- ### Task 3: Convert medium kernels (batch of 8) **Complexity:** MEDIUM (5-17 casts each) **Files (8 kernels, one agent):** - `bias_kernels.cu` (7 casts) - `c51_grad_kernel.cu` (5 casts) - `mse_grad_kernel.cu` (6 casts) - `expected_q_kernel.cu` (5 casts) - `nstep_kernel.cu` (5 casts) - `signal_adapter_kernel.cu` (5 casts) - `cql_grad_kernel.cu` (7 casts) - `q_stats_kernel.cu` (7 casts) Same conversion rules as Task 2. - [ ] **Verify + Commit** --- ### Task 4: Convert training_guard_kernel.cu + epsilon_greedy_kernel.cu **Complexity:** MEDIUM (8 + 11 casts, warp shuffles in training_guard) - [ ] training_guard: warp reductions → `bf16_warp_sum()` - [ ] epsilon_greedy: branching action selection with bonus arrays - [ ] **Verify + Commit** --- ### Task 5: Convert c51_loss_kernel.cu + mse_loss_kernel.cu **Complexity:** HIGH (14 + 16 casts, shared memory softmax, block reductions) These are the core loss kernels. Shared memory stores softmax intermediates. - [ ] Change `extern __shared__ float shmem[]` → `extern __shared__ __nv_bfloat16 shmem[]` - [ ] Block reduction helpers (block_reduce_sum, block_reduce_max) → use `bf16_warp_sum`/`bf16_warp_max` - [ ] log_softmax: `bf16_log()`, `bf16_exp()` for transcendentals - [ ] Keep `total_loss` as `float*` (atomicAdd monitoring scalar) - [ ] **Verify + Commit** --- ### Task 6: Convert attention_kernel.cu + attention_backward_kernel.cu + ensemble_kernels.cu **Complexity:** HIGH (12 + 38 + 19 casts) attention_backward has the most casts outside the complex kernels. These have: - Multi-head attention QKV projections - Softmax with temperature scaling - Adam-style weight updates in attention_backward - KL divergence in ensemble - [ ] **Verify + Commit** --- ### Task 7: Convert backtest_gather_kernel.cu + backtest_env_kernel.cu + backtest_metrics_kernel.cu **Complexity:** MEDIUM-HIGH (17 + 4 + 3 casts) backtest_gather is the largest — loads states from BF16 market data. - [ ] **Verify + Commit** --- ### Task 8: Convert experience_kernels.cu (dedicated agent) **Complexity:** VERY HIGH (49 casts, 30 kernel parameters, portfolio simulation, trade physics) The most complex kernel. Uses trade_physics.cuh functions. Portfolio state read/write. Reward computation. Episode reset. - [ ] trade_physics.cuh functions: verify ALL converted (parameters + locals) - [ ] env_step kernel: portfolio state → BF16 reads/writes - [ ] experience gathering: state/reward output → BF16 - [ ] **Verify + Commit** --- ### Task 9: Convert dqn_utility_kernels.cu (dedicated agent) **Complexity:** HIGH (31 casts, Adam optimizer, spectral norm, gradient operations) - [ ] Adam kernel: params/grads/m/v → `__nv_bfloat16`, arithmetic → native - [ ] Gradient norm: BF16 input, warp reduction → `bf16_warp_sum()` - [ ] SAXPY/clip kernels: BF16 operands - [ ] Spectral norm: BF16 matvec with shared memory - [ ] `powf(beta1, t)` → `bf16_pow(bf16(beta1), bf16((float)t))` - [ ] **Verify + Commit** --- ### Task 10: Convert iql_value_kernel.cu + iqn_dual_head_kernel.cu (dedicated agent) **Complexity:** VERY HIGH (43 + 29 casts) IQL has SiLU activation, IQN has cosine embedding and quantile Huber loss. - [ ] SiLU: `x * sigmoid(x)` → `x * bf16_one() / (bf16_one() + bf16_exp(-x))` - [ ] Cosine embedding: `bf16_cos(bf16(pi) * bf16((float)(d+1)) * tau)` - [ ] Quantile Huber: piecewise with `bf16_fabs()` - [ ] **Verify + Commit** --- ## Phase 2: Delete GpuTensor + nvrtc ### Task 11: Replace GpuTensor usages with raw CudaSlice GpuTensor methods used (from analysis): - `from_host(&[f32], shape, stream)` → `stream.clone_htod(&bf16_data)?` - `from_vec(vec, shape, stream)` → same - `zeros(shape, stream)` → `stream.alloc_zeros::(n)?` - `cat(&[tensors], dim, stream)` → sequential `cuMemcpyDtoDAsync` - `full(shape, val, stream)` → custom fill kernel or `memset` for zero - `scalar(val, stream)` → `stream.clone_htod(&[half::bf16::from_f32(val)])?` - `randn(shape, stream)` → GPU RNG kernel (already exists in dqn_utility_kernels) - `.data()` → direct CudaSlice reference (already a thin wrapper) - `.shape()` → tracked separately as `(usize, usize)` or config-derived - `.numel()` → `slice.len()` - `.to_host(stream)` → `stream.memcpy_dtoh(&slice, &mut host)?` **Files to modify (~20 files):** - `crates/ml/src/trainers/dqn/fused_training.rs` — HER batch operations - `crates/ml/src/trainers/dqn/trainer/train_step.rs` — batch upload - `crates/ml/src/trainers/dqn/config.rs` — type references - `crates/ml/src/inference.rs` — inference path - All other files with GpuTensor references - [ ] **Step 1:** Create `bf16_cat` and `bf16_fill` helper functions in gpu_weights.rs or a new bf16_ops.rs - [ ] **Step 2:** Replace ALL `GpuTensor::xxx` calls with raw CudaSlice equivalents - [ ] **Step 3:** Remove `GpuTensor` import from all files - [ ] **Step 4:** Verify compilation - [ ] **Commit** --- ### Task 12: Delete ml-core cuda_autograd + nvrtc - [ ] **Step 1:** Remove `cudarc` nvrtc feature from ml-core/Cargo.toml - [ ] **Step 2:** Delete `crates/ml-core/src/cuda_compile.rs` (runtime kernel compilation) - [ ] **Step 3:** Delete or gut `crates/ml-core/src/cuda_autograd/` modules that depend on nvrtc: - `elementwise.rs`, `reductions.rs`, `loss.rs`, `linear.rs`, `activations.rs`, `optimizer.rs` - Keep `gpu_tensor.rs` temporarily if other crates reference GpuTensor type - [ ] **Step 4:** Fix all compilation errors - [ ] **Step 5:** If GpuTensor type is still referenced, create a thin `GpuTensor = CudaSlice` type alias - [ ] **Commit** --- ## Phase 3: Rust Compilation Fixes ### Task 13: Fix ml-core Rust errors After removing nvrtc (22 errors gone), fix remaining 23 BF16 boundary errors: - `Vec` → `Vec` at host upload boundaries - `[f32; N]` → `[half::bf16; N]` for fixed-size host arrays - `clone_htod(&[f32])` → `clone_htod(&[half::bf16])` - `memcpy_dtoh` → download to `Vec`, convert to f32 for display - [ ] **Verify:** `SQLX_OFFLINE=true cargo check -p ml-core` = 0 errors - [ ] **Commit** --- ### Task 14: Fix ml crate Rust errors ~23 remaining errors in trainers, config, tests: - Host data upload paths (feature vectors, rewards from replay buffer) - Scalar readback paths (loss, grad_norm for monitoring) - Test helpers (alloc_dueling, alloc_branching) - [ ] **Verify:** `SQLX_OFFLINE=true cargo check -p ml` = 0 errors - [ ] **Commit** --- ## Phase 4: Wire cuBLAS + Test ### Task 15: Wire cublasGemmEx BF16 forward path `forward_online_bf16()` already exists in batched_forward.rs. Wire it into `launch_cublas_forward()` in gpu_dqn_trainer.rs. - [ ] Change `launch_cublas_forward` to call `forward_online_bf16` with BF16 weight pointers - [ ] Change `forward_target` and `forward_online_next` similarly - [ ] **Verify + Commit** --- ### Task 16: Wire cublasGemmEx BF16 backward path Add `gemmex_bf16` to batched_backward.rs (same pattern as forward). - [ ] dW computation: BF16 × BF16 → BF16 - [ ] dX computation: BF16 × BF16 → BF16 - [ ] **Verify + Commit** --- ### Task 17: Full test suite validation - [ ] `SQLX_OFFLINE=true cargo test -p ml --lib -- dqn` - [ ] `SQLX_OFFLINE=true cargo test -p ml-dqn --lib` - [ ] `SQLX_OFFLINE=true cargo test -p ml --lib -- gradient_budget` - [ ] Update test helpers to construct `half::bf16` data natively (no Vec) - [ ] **Final commit + push** --- ## Execution Dependencies ``` Phase 1 (CUDA kernels — can parallelize): Task 1 (dt_kernels) ─┐ Task 2 (12 simple) ─┤ Task 3 (8 medium) ─┤→ ALL kernels compile natively Task 4 (guard+epsilon) ─┤ Task 5 (c51+mse loss) ─┤ Task 6 (attn+ensemble) ─┤ Task 7 (backtest) ─┤ Task 8 (experience) ─┤ Task 9 (dqn_utility) ─┤ Task 10 (iql+iqn) ─┘ Phase 2 (GpuTensor removal): Task 11 (replace usages) → Task 12 (delete infrastructure) Phase 3 (Rust fixes): Task 13 (ml-core) → Task 14 (ml) Phase 4 (wire + test): Task 15 (forward) → Task 16 (backward) → Task 17 (tests) ``` Phase 1 tasks are ALL independent — can run as parallel agents. Phase 2 depends on Phase 1 (kernels must compile). Phase 3 depends on Phase 2 (nvrtc removal changes error count). Phase 4 depends on Phase 3 (Rust must compile).