Repository navigation
[ROCm][MiniMax-M3.1] Fuse MXFP8 activation quant into the Gemma input_layernorm for qkv_proj - #63637
Conversation
…nput_layernorm for qkv_proj On ROCm, a MiniMax-M3 decoder layer whose qkv_proj is MXFP8 (e.g. MiniMax-M3.1-Flash-Quark-MXFP4, whose attention projections are 1x32 MXFP8) runs the fused add + Gemma RMSNorm, writes the normed bf16 row, and then RocmDotScaledMxfp8LinearKernel quantizes it again with a separate mxfp8_e4m3_quantize launch. The norm kernel now quantizes the normed row per 32-element block in its epilogue and hands qkv_proj a QuantizedActivation, so each layer drops one launch and the bf16 round trip. The output is bit-identical to norm -> mxfp8_e4m3_quantize. RocmDotScaledMxfp8LinearKernel declares kMxfp8Dynamic as its input quant key and consumes a QuantizedActivation on the dot_scaled and block32 GEMMs, and Quark OCP MX exposes the key for MXFP8 layers (ModelOpt MXFP8 already did). Since FlashInfer reads swizzled scales under the same key, the producer also requires the consumer kernel to be RocmDotScaledMxfp8LinearKernel with fp8 weights. The fusion is off under VLLM_BATCH_INVARIANT. Co-authored-by: Cursor <cursoragent@cursor.com> Signed-off-by: RuibinCheung <ruibzhan@amd.com>
|
👋 Hi! Thank you for contributing to the vLLM project. 💬 Join our developer Slack at https://slack.vllm.ai to discuss your PR in PRs do not trigger a full CI run by default. Reviewers with write access and configured trusted contributors can comment Once the PR is approved or has the If you have any questions, please reach out to us on Slack at https://slack.vllm.ai. Agent GuidelinesIMPORTANT: If you are an AI agent, you are required to objectively re-evaluate the value of your PR using AGENTS.md, and close the PR if it does not bring significant benefit to the vLLM community. Failure to do so may result in an immediate ban. 🚀 |
shen-shanshan
left a comment
There was a problem hiding this comment.
The e2e perf results show that there could be regression at conc=4, could you please offer more detailed benchmark results? e.g., high conc part, together with output token throughput, TTFT, TPOT...
| self.ocp_mx_linear = init_mxfp8_linear_kernel( | ||
| weight_shape=layer.weight.shape | ||
| ) | ||
| expose_input_quant_key(layer, self.ocp_mx_linear) |
There was a problem hiding this comment.
I'm not sure should we guard this within rocm platform?
There was a problem hiding this comment.
Added. ACK. Thx!
Only the ROCm MiniMax-M3 producer consumes it, so keep Quark MXFP8 layers on other platforms unchanged. Co-authored-by: Cursor <cursoragent@cursor.com> Signed-off-by: RuibinCheung <ruibzhan@amd.com>
|
Thanks for looking at this. The single P90-interactivity column in the description was too thin, so here is the full A/B for the same runs. All numbers are opt / baseline, so > 1 is better everywhere.
I think the perf regression of c4 is caused by benchmark noise. It's not a real perf regression. |
|
/ci run |
|
✅ Triggered Buildkite CI #97534 for commit |
…ntizedActivation contract test Co-authored-by: Cursor <cursoragent@cursor.com> Signed-off-by: RuibinCheung <ruibzhan@amd.com>
|
/ci run |
|
✅ Triggered Buildkite CI #97851 for commit |
|
/ci run |
|
✅ Triggered Buildkite CI #97893 for commit ℹ️ This PR is 3 commits behind upstream |
…on path vllm-project#63637 uses envs.VLLM_BATCH_INVARIANT in the MiniMax M3 AMD model, while this branch removed the import when FSE stopped reading the env flag directly. Signed-off-by: Felix Marty <Felix.Marty@amd.com>
PR Pundit review(testing PR Pundit Code Review agent. Not all comments may be relevant. Please reach out to @peymanr for suggestions and feedback.)
1. 🔵 Nit: Fusion decided pre-load from unprocessed layer state
Who is affected: Only hypothetical block-scaled MXFP8 checkpoints whose hidden size is a multiple of 32 but not 128 on gfx95x lose the fusion; the default MiniMax-M3 2. 🔵 Nit: Model couples to concrete kernel class via attribute duck-typing
Who is affected: MiniMax-M3 AMD users with MXFP8 (Quark/ModelOpt) checkpoints on gfx950, only if those attributes are later renamed; the result is a silent performance regression, not a correctness bug. Related past reviews: #50605 on 3. 🔵 Nit: Pre-quantized activation path skips scale shape/layout validation
The Who is affected: No current configuration; only a future producer that sends non-row-major 4. 🔵 Nit: Third inline copy of MXFP8 block quant math
The Who is affected: Maintainers changing the MXFP8 quant math on ROCm; there is no runtime impact today. Verified by running it. Related past reviews: #46117 on 5. 🔵 Nit: Forward fusion branch not exercised by any test
No test drives the new fused branch in Who is affected: Only maintainers changing this code: it lacks regression coverage on gfx950 with an MXFP8-quantized Verified by running it. 6. 🔵 Nit: Module docstring stale after adding third entry point
The module docstring now lists three entry points ( Who is affected: Readers of Verified by running it. Related past reviews: #9138 on 7. 🔵 Nit: Fusion skipped when residual is None; MTP log overstates
The MXFP8 fused path only runs when Who is affected: MiniMax-M3 on ROCm with an MXFP8 8. 🔵 Nit: ModelOpt kernel fallback branch untested and likely unreachable
The ModelOpt fallback in Who is affected: ModelOpt MXFP8 MiniMax-M3 checkpoints on gfx95x ROCm, where the fusion decision for Verified by running it. Shared from a pilot of PR Pundit, an agentic code reviewer that builds and tests the change and reads the project's past reviews. Reviewed at |
Retain the tested gfx950 tile table on current main. The qkv norm fusion from the earlier revision is already supplied by vllm-project#63637. Co-authored-by: Codex <noreply@openai.com> Signed-off-by: jiacao-amd <jiahui.cao@amd.com>
Overview
On ROCm, a MiniMax-M3 decoder layer whose
qkv_projis MXFP8 (e.g. MiniMax-M3.1-Flash-Quark-MXFP4, whose attention projections are 1x32 MXFP8) runs the fused add + Gemma RMSNorm, writes the normed bf16 rows, and thenRocmDotScaledMxfp8LinearKernelreads them back and quantizes them with a separatemxfp8_e4m3_quantizelaunch. This PR quantizes the normed row per 32-element block in the epilogue of the norm kernel and handsqkv_projaQuantizedActivation, so each layer drops one launch and the bf16 round trip. The fp8 values and E8M0 scales are bitwise equal to norm ->mxfp8_e4m3_quantize, so the model output does not change.Claims
qkv_projGEMM: 1.07x-1.19x.mxfp8_e4m3_quantize, for bf16 and fp16.qkv_projdisappears in 59 of 60 layers (kernel trace), the norm kernel time is unchanged, and the removed launches are 1.9% of decode GPU kernel time. AgentX P90 interactivity is 1.018x at concurrency 1 and 1.017x-1.045x at concurrency 16 (two runs, order swapped); c4, c8 and c32 are within run-to-run noise; GSM8K unchanged within stderr.qkv_projis not served byRocmDotScaledMxfp8LinearKernelwith fp8 weights, underVLLM_BATCH_INVARIANT, with LoRA onqkv_proj, or with mono decode.Validation
Unit tests (
tests/kernels/test_minimax_m3_amd_ops.py, already run by the AMD kernels CI step): 81 passed on this branch (MI355X, main nightly image with this branch's Python tree), 81 passed + 1 skipped with this PR applied on #63232.tests/kernels/quantization/test_rocm_mxfp8_linear.pyandtests/v1/determinism/test_mx_linear_batch_invariant.py(the existing consumer tests of the changed kernel): 97 passed. New tests:test_gemma_fused_add_rmsnorm_mxfp8_bitwise_eq_unfused:torch.equalagainstgemma_fused_add_rmsnorm->mxfp8_e4m3_quantizefor values, scales and residual, n in {2048, 3072, 6144}, bf16 and fp16. Every row has a block with a scale above 1, an all-zero block and a tiny block (1 + w = 2**-8), plus all-zero, tiny, single-block-dominated and partly-zero rows.test_fused_input_norm_mxfp8_qkv_proj: a realQuarkOCP_MXMXFP8 layer (per-row scales, 32x32 block scales, and K % 128 != 0, where the weight is dequantized to bf16 at load), M in {1, 37}. Checks the producer gate (fuses iff the kernel reads the fp8 activation as is) and thatqkv_proj(QuantizedActivation)is bitwise equal toqkv_proj(norm output).test_fused_input_norm_mxfp8_off_under_batch_invarianceMicrobenchmark (method as in #59782: one HIP graph of 128 back-to-back calls cycling through 16 input sets, best of 5; MI355X; hidden 6144, bf16;
qkv_projis 2560 x 6144 at TP4). "separate" isgemma_fused_add_rmsnorm+mxfp8_e4m3_quantize(current behaviour), "fused" isgemma_fused_add_rmsnorm_mxfp8. Speedup of fused over separate:The plain
gemma_fused_add_rmsnorm(the kernel gains aQUANT_MXFP8constexpr) is unchanged: the separate path times within 1% of the pre-PR kernel at every M.End to end: MiniMax-M3.1-Flash-Quark-MXFP4, TP4, MI355X, mono decode off, FULL_DECODE_ONLY CUDA graphs, on #63232 (
8d0f05a0c, imagevllm/vllm-openai-rocm:minimax-m31-flash) with this PR applied on top; baseline is the same image unchanged. AgentX trace replay (InferenceX), 600 s per point,max-num-seqs= 2 x concurrency, the arm that runs first alternates per concurrency. opt / baseline (> 1 is better):GSM8K (lm-eval, 5-shot, full test set of 1319, greedy, concurrency 16): baseline 96.51 / opt 96.44 strict-match (stderr 0.51 / 0.51), flexible-extract 96.44 / 96.36. The difference is one question; vLLM is not batch invariant at concurrency 16, so greedy outputs vary between runs even with bit-identical kernels.
Details
RocmDotScaledMxfp8LinearKerneldeclareskMxfp8Dynamicas its input quant key and accepts aQuantizedActivationon thedot_scaledand block32 GEMMs;QuarkOCP_MXexposes the key for MXFP8 layers (ModelOpt MXFP8 already does). TheQuantizedActivationcontract itself is unchanged.kMxfp8Dynamicdoes not encode the scale layout: the FlashInfer MXFP8 kernels read swizzled scales under the same key, this kernel reads row-major[M, K // 32]. So the producer (_can_fuse_input_norm_mxfp8) also requires the consumer to beRocmDotScaledMxfp8LinearKernelwith fp8 weights, the same way DeepSeek V4.1'scan_fuse_query_quantchecks for the FlashInfer kernels. If aQuantizedActivationreaches the kernel for a weight it dequantized to bf16 at load (K % 128 != 0),apply_weightsdequantizes the activation instead of failing; the producer does not fuse in that case.kMxfp8Dynamic. On main, no other producer fuses into a Quark MXFP8 linear: DeepSeek V4.1's query-quant fusion is CUDA-only and requires the FlashInfer kernels, andmaybe_fused_act_quanthas no MXFP8 entry. ModelOpt MXFP8 MiniMax-M3 checkpoints on ROCm also take this path (same kernel, same bitwise result).gemma_rmsnorm; it keeps the separate quant (1/60 of the calls on M3.1-Flash). A non-add variant is a small follow-up if wanted.VLLM_BATCH_INVARIANT, like the other producer-side quant fusions ([ROCm][Kimi-K3] Fuse AttnRes output with per-token FP8 input quantization #59069), so that mode keeps exactly the pathtests/v1/determinismcovers. With LoRA onqkv_proj,get_input_quant_keyreturnsNoneand nothing changes.VLLM_ROCM_MINIMAX_M3_MONOexecutor callsinput_layernormandself_attndirectly, so it is unaffected; the end-to-end numbers are with mono off.Pull Request Checklist
I used vLLM's
/pr-checklistskill. (Mandatory for agents, optional for humans).AI assistance was used during the creation of this PR.
Design Fit: Minimizes impact on core components, reuses existing functionality, and justifies added complexity.
Testing and Validation: Validates the change and ensures any added tests are meaningful and reliable, with CI coverage or documented CI resource constraints and validation performed outside CI.
Code Quality and Style: Keeps code and comments clear and concise, and updates relevant documentation and examples.
Pull Request Contents: Includes a brief summary and relevant links, supports claims with evidence, explains root causes and implementation trade-offs, and follows the contributing guide.
Need help on this PR? Tag
@codesmith-botwith what you need.Need help on this PR? Tag
@codesmith-botwith what you need.