feat(bf16): PPO crate fully native BF16 — zero f32 on GPU
Convert entire ml-ppo crate from CudaSlice<f32> to CudaSlice<half::bf16>:
- CudaLinear: weights/bias/grads all bf16, sgemm→cublasGemmEx BF16
- CudaLSTM: 8 weight/grad buffers bf16, sgemm→GemmEx
- CudaVec: wraps CudaSlice<half::bf16>
- AdamW optimizer: all states bf16
- All 5 CUDA kernels (activation, linear, softmax, lstm, adam) native __nv_bfloat16
- PPO compute_losses: ENABLED (was #[cfg(any())] stub)
- PPO update_gpu: ENABLED (was bf16 migration pending stub)
- d2d_subrange_bf16 replaces d2d_subrange_f32
- Host boundary: f32↔bf16 conversion only at upload/download
Test results:
ml-core: 300/300 (100%)
ml-dqn: 359/359 (100%)
ml-ppo: 167/168 (1 pre-existing action_masking logic bug)
ml: 894/895 (1 pre-existing hyperopt bounds index)
Total: 1720/1722 passing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>