sm12x: hoist the E8M0 block-scale upcast out of the FP8 GEMM hot path - #38
Conversation
w8a8_triton_block_scaled_mm() upcasts both scale tensors from
float8_e8m0fnu to fp32 on every call. `As` is per-token and genuinely
dynamic, but `Bs` is the weight scale: static after loading, re-derived
on every forward.
DeepSeek-V4-Flash stores every dense FP8 weight scale as F8_E8M0
(wq_a, wkv, wq_b, wo_a, wo_b, shared experts), and on SM12x these
linears take the Triton fallback rather than DeepGEMM, so the upcast
runs ~256 times per decode step. Each one is two kernels (a uint8->int32
copy and a `<< 23` shift) plus an allocation.
Cache the fp32 copy once in process_weights_after_loading and hand it to
the kernel from apply_weights. E8M0 is exponent-only, so the upcast is a
pure bit shift and the cached tensor is bit-identical to what the kernel
computed per call.
The original E8M0 parameter is deliberately left in place: DeepSeek-V4's
fused o_proj and DSpark kernels read layer.weight_scale{,_inv} directly
(models/deepseek_v4/nvidia/ops/o_proj.py, nvidia/dspark.py), so replacing
it outright would break them. Opt-in via prefers_fp32_block_scale, set
only on TritonFp8BlockScaledMMKernel — DeepGEMM consumes ue8m0 natively
and must keep the original.
Measured on 2x RTX PRO 6000 Blackwell (SM120), DeepSeek-V4-Flash-0731,
TP=2 + EP, DSpark k=5.
Kernel counts, per 25 decode steps (torch profiler, rank0):
unrolled_elementwise<direct_copy_kernel_cuda> 8850 -> 2839 calls
23.68 -> 6.71 ms
vectorized_elementwise<BUnaryFunctor<int,...>> 7550 -> 0 calls
13.94 -> 0 ms
13,561 launches and 30.91 ms of GPU time removed per 25 steps
(1.24 ms/step).
End-to-end decode, fixed work (96 requests x 600 tokens, concurrency 12),
6 samples per arm across 2 reload cycles:
off: max 2305.3 median 2264.8 tok/s
on: max 2327.5 median 2293.2 tok/s
+1.0% (max) +1.3% (median)
Prefill (24 requests of ~8K tokens, concurrency 6): +0.5%, as expected —
the upcast is negligible beside ~539 us GEMMs.
|
Merged as 0286141 — thank you. Verified against the tree before merging rather than from the description:
That last point is the one I want to call out. Leaving the original E8M0 parameter in On the numbers: the one carrying the merge is the launch count, not the throughput. Same note as on #37: our CI hardware is 2× GB10 (SM121), so we cannot reproduce your Also picked up your DSv4 illegal-memory-access report on vllm-project#41834 — |
w8a8_triton_block_scaled_mm()upcasts both scale tensors fromfloat8_e8m0fnuto fp32 on every call.
Asis per-token and genuinely dynamic, butBsis theweight scale — static after loading, re-derived on every forward.
DeepSeek-V4-Flash stores every dense FP8 weight scale as
F8_E8M0(wq_a,wkv,wq_b,wo_a,wo_b, shared experts), and on SM12x these linears takethe Triton fallback rather than DeepGEMM, so the upcast runs ~256x per decode
step. Each one is two kernels (a
uint8->int32copy and a<< 23shift) plusan allocation.
Fix
Cache the fp32 copy once in
process_weights_after_loadingand hand it to thekernel from
apply_weights. E8M0 is exponent-only, so the upcast is a pure bitshift and the cached tensor is bit-identical to what the kernel computed per
call.
Two deliberate scoping decisions:
o_projand DSpark kernels read
layer.weight_scale{,_inv}directly(
models/deepseek_v4/nvidia/ops/o_proj.py:71,nvidia/dspark.py:268-282), soreplacing it outright would break them. Cost is ~1.3 MB of extra weight memory.
prefers_fp32_block_scale, set only onTritonFp8BlockScaledMMKernel. DeepGEMM consumes ue8m0 natively and must keepthe original tensor.
Measured
2x RTX PRO 6000 Blackwell (SM120), DeepSeek-V4-Flash-0731, TP=2 + EP, DSpark k=5.
Kernel counts per 25 decode steps (torch profiler, rank0):
unrolled_elementwise<direct_copy_kernel_cuda>vectorized_elementwise<BUnaryFunctor<int,...>>(the<< 23)13,561 launches and 30.91 ms of GPU time removed per 25 steps (1.24 ms/step).
End-to-end decode, fixed work (96 requests x 600 tokens, concurrency 12), 6
samples per arm across 2 reload cycles:
Prefill (24 requests of ~8K tokens, concurrency 6): +0.5%, as expected — the
upcast is negligible beside ~539 us GEMMs.