Files
foxhunt/docs/superpowers/plans/2026-03-27-bf16-native-completion.md
jgrusewski 8267fa9bd7 docs: BF16 native completion plan — zero casts, zero nvrtc, zero GpuTensor
17 tasks across 4 phases:
- Phase 1: 10 tasks converting 35 CUDA kernel internals to native BF16
  (bf16_* wrappers, native arithmetic, no __bfloat162float casts)
- Phase 2: Delete GpuTensor + nvrtc dependency (replace with raw CudaSlice)
- Phase 3: Fix Rust compilation errors at host boundaries
- Phase 4: Wire cublasGemmEx + test

Includes lessons learned from failed bulk-sed approach and explicit
conversion rules for every agent.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 23:39:43 +01:00

15 KiB
Raw Blame History

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<half::bf16> 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<half::bf16>, 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<f32>CudaSlice<half::bf16>) 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/fminfbf16_fmax/bf16_fmin

  • Step 6: Fix float literals

0.0fbf16_zero(), 1.0fbf16_one(), -1e30fbf16(-1e30f), 0.7978845608fbf16(0.7978845608f), 0.044715fbf16(0.044715f), 1e-8fbf16(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
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::<half::bf16>(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<half::bf16> 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<f32>Vec<half::bf16> 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<half::bf16>, 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).