pub fn cublas_fp8_proj_prequant(
gpu: &dyn GpuBackend,
scale_kmajor_k: KernelHandle,
act_fp8: DevicePtr,
act_scale: DevicePtr,
act_scale_kmajor: DevicePtr,
fp8w: &Fp8Weight,
out: DevicePtr,
m: u32,
n: u32,
k: u32,
stream: u64,
) -> Result<()>Expand description
cublas_fp8_proj for an activation that is ALREADY quantized — the
caller ran per_token_group_quant_fp8 itself.
WHY the split (#917/#928): the dense FFN’s gate and up projections consume
the SAME [M, K] input, so quantizing inside the GEMM helper would pay the
per-token quant twice per layer. The FFN quantizes once and calls this for
both, then quantizes the post-SiLU intermediate once for down.
⚠ SCALE LAYOUT. cuBLASLt reads the VEC128 B-scale tensor with the TOKEN
index contiguous ([K/128, ceil16(M)]), not the [M, K/128] the quantizer
writes — cuBLAS “Scaling factors layouts”, and the reason this helper needs
act_scale_kmajor at all. Handing the quantizer’s buffer over directly is
what the 2026-09-11 H100 run measured at rel_rms 7.7e-2 / ~33 000 BF16 ULP
against the in-tree kernel on identical FP8 bytes; ATLAS_CUBLAS_SCALE_LAYOUT =rowmajor reproduces that reading deliberately.
⚠ PADDED-M EXTENTS. cuBLASLt is handed ceil16(M), so:
outmust holdceil16(M) * NBF16 elements — the phantom rows are WRITTEN (well-defined: their activation scales are zeroed below).act_fp8must holdceil16(M) * Kbytes,act_scaleM * (K/128)f32 andact_scale_kmajorceil16(M) * (K/128)f32 — the phantom rows are READ.
The arena sizes that headroom in; see the sizing notes in
spark_runtime::buffers::sizes (fp8_act, ffn_act_a, ffn_act_scale,
ffn_act_scale_kmajor, expert_gate_out, moe_output).