Repository navigation
perf(moe): prefill nvfp4 W4A4 moe kernel on sm_120 - #736
Conversation
…p4 activations on sm_120 (with TP sharding support)
…optimizations and benchmark results
Known issues, not addressed in this PRExperts of several formats fail to load on sm_120On sm_120 with Gluon and the automatic kernel choice, the NVFP4 layers of a checkpoint whose experts declare
So such a checkpoint stops at startup, where main binds Reproduced on CPU with the bf16 + NVFP4 model of Workaround: FTW checkpoints keep the W4A16 prefill
|
2d67c66 to
6ad8991
Compare
- hybrid fetch split: upstream per-layer-kind bench rows (FlashML-org#734) first, our startup split now measured per layer kind without a row (before the CPU executor), then cap 1; dropped the profile-driven auto->hybrid pick and its cache-share guard with upstream - qwen3_5_moe: upstream fp32 runtime norm +1 (FlashML-org#755) under our DFlash hidden-layer return and LinearOProj - qwen4_exp: upstream quant-config reader and MXFP8 scale aliases (FlashML-org#759) replace our .weight_scale rename; our validating scan_ple_table and NVFP4 PLE tables gain the bf16 table support - nvfp4_banks: upstream W4A4 input scales (FlashML-org#736) combined with our TP sharding and disk-tier skip - ftw: fp16 nvfp4 global refusal (FlashML-org#739) on top of our eager index validation - kernel/nvfp4_moe: drop the e2m1 LUT argument our bit-placement decode removed - benchbw: upstream layer-kind rows plus our THP bench banks, decode-shaped gather and custom profile hint - tests: disk-tier spec test skips the quant-config-driven qwen4_exp hook Assisted-by: Claude Opus 5.5
Prefill path of the NVFP4 MoE on sm_120 for checkpoints whose experts carry static activation scales (ModelOpt
NVFP4, compressed-tensors NVFP4 withdynamic: localinputs): both GEMMs run on NVFP4 activations (W4A4). Same banks as the Triton kernels, no repack; decode, small prefills and W4A16 checkpoints are unchanged.Changes
w4a4(layers/quantization/moe/nvfp4.py), first in the table on sm_120 with Gluon, for checkpoints whose experts carryinput_scale. It keeps the Triton kernel's banks, decode and CPU format; prefill runs W4A4 from 4 tokens per expert and main's W4A16 below.--quant-backend moe.nvfp4=tritonkeeps W4A16.kernel/gluon/nvfp4/moe/sm120_w4a4.py: two warp-specialized Gluon GEMMs onmma.sync kind::mxf4nvf4. gate_up applies the gated activation and quantizes its output to NVFP4 in the epilogue; down applies the routing weight and sums the top-k routes with bf16 atomics (add order varies run to run). No bf16 intermediates, no act / moe_sum kernels. Below I = 1024 the down GEMM runs persistently with separate epilogue warps.input_scale(compressed-tensors:input_global_scale), kept as GPU-resident alphas; each GEMM input uses the max over the layer's experts, as vLLM does.kernel/nvfp4_moe.py; a test of thew4a4kernel against a torch emulation of the NVFP4 activation rounding.Results
Measured against main at ac6a15d. torch 2.14.1+cu130, Triton 3.8.0.
Kernel: the whole MoE layer, main
fused_experts_nvfp4vs PRfused_experts_nvfp4_w4a4, random banks in 9 checkpoint shapes (Qwen3.6-35B-A3B, Qwen3.5-122B-A10B, Qwen3.8-Flash, Gemma-4-26B-A4B, MiniMax-M2.5, GLM-4.7, GLM-5.2, GLM-5.3-Flash, MiniMax-M3) x T in {16, 32, 64, 128, 256, 512, 768, 1024, 1536, 2048, 3072, 4096}, static input scales from a calibration batch, each model's activation;triton.testing.do_benchmedian. Speedup over main, geomean (min - max), by tokens per expert:Below 4 tokens per expert the PR runs main's path. The RTX 5090 minimum is a host-bound point of a small layer (Qwen3.6-35B-A3B / Gemma-4-26B-A4B shapes, T <= 256; the busy host adds ~130 us now and then); the other shapes there are 1.00 - 1.04x.
W4A4 at T = 8192 (
main / PRis the speedup):RTX PRO 6000 (the kernels of this PR before the port into the tree; they match the PR output to 1e-3):
RTX 5090:
At T = 32768 the geomean is 3.06x on the RTX PRO 6000 (MFU 38 - 57%) and 4.63x on the RTX 5090 (42 - 57%).
PR MFU: useful GEMM FLOPs (2 x T x top_k x 3 x hidden x moe_intermediate) over the whole layer time, against the dense FP4 mma.sync peak at the rated boost clock: RTX PRO 6000 188 SMs x 2.43 GHz x 4096 FLOP/clk = 1871 TFLOP/s, RTX 5090 170 SMs x 2.41 GHz x 4096 FLOP/clk = 1676 TFLOP/s. An MMA-only loop reaches 1781 / 2007 TFLOP/s: under this load the RTX PRO 6000 runs ~2.31 GHz and the RTX 5090 ~2.88 GHz, above its rated boost. FP4 runs at full rate with fp32 accumulate on GeForce too, while bf16 does not, so main's W4A16 GEMM loses more on the RTX 5090.
vs FlashInfer b12x W4A4 (flashinfer 0.6.18
b12x_fused_moe(quant_mode="nvfp4"), weights and swizzled scales prepared once, the same static input scales and activations, whole layer): b12x time / ours, the kernels of this PR before the port (they match the PR output to 1e-3):b12x's gated kernel (I <= 512) keeps Qwen3.6-35B-A3B level; on the other shapes our GEMMs run at 1.5 - 1.9x its rate.
Decode (
fused_experts_decode_nvfp4_marlin) and W4A16 prefill are untouched.Accuracy: W4A4 rounds the activations to 4 bits, the precision the checkpoint was calibrated for, so outputs differ from W4A16 by more than rounding noise. RadixArk
Qwen3.8-Flash-Next-NVFP4(offload, RTX PRO 6000), AIME25 pass@3 on the four problems whose prompts reach 4 tokens per expert (10, 11, 20, 21): 3 / 4 pass; problem 20 ran all three samples into the 16384-token limit without an answer.Tests
pytest tests/moe/test_nvfp4_backends.pyon the RTX PRO 6000: 15 passed, 4 skipped (Marlin / vLLM not installed).