Repository navigation
[ROCm][Perf] Qwen3.8-2.4T specialized decode on MI355X (--monokernel) - #60833
mjkvaak-amd wants to merge 1 commit into
Conversation
|
This pull request has merge conflicts that must be resolved before it can be |
Copy gdn.py, layer.py and layout.py from vllm-project/vllm#60833 at 6a1c13b2fc. Keep the bf16 SSM state in K1. The glue passes the per-width speed flags (tuned) to both kernels and sizes the scratch with them. The weights stay as SGLang loads them: bf16 dense layers, an unfused shared expert, and the output gate in the attention layer. So fse, oq, qq and og stay off. The K1 pad-row test now runs each case with and without the speed flags and checks that the outputs are bitwise equal. Co-authored-by: Cursor <cursoragent@cursor.com>
|
@mjkvaak-amd, I think that the GDN projections in this PR should be MXFP6 instead of MXFP4. With the online MXFP4 targets for Accuracy. lm_eval gsm8k, 5-shot completions,
The MXFP6 row also has the attention How to make the MXFP6 weights.
The rounding of step 2: def round_e2m3(x: torch.Tensor) -> torch.Tensor:
v = x.float().reshape(-1, x.shape[-1] // 32, 32)
amax = v.abs().amax(dim=-1, keepdim=True)
# Adding 2**19 to the fp32 bits of the block maximum rounds its mantissa
# at the third bit, and a carry moves into the exponent. The mask keeps
# only the exponent field.
exp = (((amax.view(torch.int32) + 0x80000) & 0x7F800000) >> 23) - 127 - 2
# An all-zero block keeps a normal scale, because a scale of zero would
# turn 0 / 0 into NaN.
scale = torch.exp2(exp.clamp(-126, 127).float())
a = v.abs() / scale
q = torch.where(
a < 2.0,
torch.round(a * 8.0) / 8.0,
torch.where(a < 4.0, torch.round(a * 4.0) / 4.0, torch.round(a * 2.0) / 2.0),
)
return (torch.sign(v) * q.clamp(max=7.5) * scale).reshape(x.shape).to(x.dtype)Cost. In our build on top of |
3346929 to
1301a7c
Compare
… MTP verify steps Flash-Next's kernels and host code move onto the Qwen mono base in vllm-project#60833: the fp4 GEMV, x quantization, o_proj, route and expert stages from qwen3_5/amd/mono/moe.py (fp4.py is gone), the GDN tasks and the in_proj_ba stage from gdn.py, the HC down GEMV's split-K reduction from moe.ksplit_rows, and the refusals, step gate, buffers and weight checks from mono_base.QwenMonoDecode. The layout and host tests are merged into tests/models/qwen3_5 and run for both models; the Qwen3.8 mono CI step runs Flash-Next's kernel test too (40 minutes). The kernels are unchanged: the ISA of every build variant is byte-identical to before, except K2 at 4-8 rows, where the shared expert's prefetch address order reschedules instructions (same instruction count, VGPRs and no spills). MTP verify steps: K1's recurrence keeps no per-draft-token state to roll back rejected drafts, so on a verify step every layer runs K1's HC front alone, then vLLM's GDN core on the spec-decode metadata (the base's path) or the QSA core, then K2. The multi-stream state before the final mixer goes to the drafter's hidden-state buffer, as the stock forward leaves it. Signed-off-by: Mikko Tukiainen <Mikko.Tukiainen@amd.com> Co-authored-by: Cursor <cursoragent@cursor.com>
A GDN layer (K1, K2 with the MXFP4 out_proj) and a QSA layer (the HC front, K2 with the gated o_proj) at six consecutive decode steps of widths 1-8, captured in one HIP graph and replayed 300 times at TP2: bit-identical to the eager run every time, on both ranks. vllm-project#60833's race stress for these kernels. Signed-off-by: Mikko Tukiainen <Mikko.Tukiainen@amd.com> Co-authored-by: Cursor <cursoragent@cursor.com>
At 2 and 3 rows, K2 gives every expert pick its own slot (`up`) instead of building the union of the picked experts. An expert that two tokens pick gets two slots, and each token's contributions are summed in top-k order, so the outputs are those of the union in expert order. At 2 rows, the fused shared expert's ug also starts before the route lands (`ps`). K2 also issues some loads in a better order. The comment next to each change explains why. - A router CTA issues its router weights after run_oproj. Before, they delayed its o_proj, which every norm task waits for. - A CTA with at most two ug tasks issues both tasks' loads before the first task's MFMAs, with no loop that carries the tiles. - When one batch covers the union, the down rounds run without a prefetch, and the second row group's tiles go out behind the INTER polls. A prefetch past the last batch reads no memory. This is the first commit of vllm-project#61271 on the fixed schedule of vllm-project#60833. The W4A16 option for MXFP4 projections (`wa`) is left out, because the MXFP6 projections of the next commit replace it and W4A16 does not match the stock decode. Signed-off-by: Markus Hartikainen <markus.hartikainen@amd.com> Co-authored-by: Cursor <cursoragent@cursor.com>
…layer on MI355X Port the vllm-project/vllm#60833 kernels. A decode step of at most 8 rows runs each layer as K1 (GDN front) and K2 (o_proj/out_proj, in-kernel TP all-reduces, norm, router, MXFP4 experts, shared expert). The full-attention layers keep SGLang's fused HIP front through a new attention_core(). Builds on the Kimi-K3 mono framework (k3_mono_flydsl common/ and stages/). Off by default: SGLANG_QWEN3_5_MONO_DECODE=1.
…uccessor Step 1 took any PR a title or comment called this one's "replacement, follow-up or reland" as a successor, and the superseded row fired for any unmerged PR with one, with no Step 9 block behind it. A follow-up builds on a PR rather than ending it: on vllm-project/vllm#60833, open, xrefs.tsv lists #61271 "Mono decode follow-up", a draft stacked on #60833's commits, and the skill read literally answered "NOT-APPLICABLE -- superseded by #61271" for a live PR. A successor is now a PR that either PR's title, body or comments call this one's replacement or reland, the test check-prior-art's SUPERSEDED class uses; a stacked or follow-up PR is not one. Each successor gets a successor:<ref> block in Step 9, attacked in both PRs' bodies, and the superseded row, like the revert row, needs one that survived. The file stays at its 280-line budget. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
This pull request has merge conflicts that must be resolved before it can be |
…uccessor Step 1 took any PR a title or comment called this one's "replacement, follow-up or reland" as a successor, and the superseded row fired for any unmerged PR with one, with no Step 9 block behind it. A follow-up builds on a PR rather than ending it: on vllm-project/vllm#60833, open, xrefs.tsv lists #61271 "Mono decode follow-up", a draft stacked on #60833's commits, and the skill read literally answered "NOT-APPLICABLE -- superseded by #61271" for a live PR. A successor is now a PR that either PR's title, body or comments call this one's replacement or reland, the test check-prior-art's SUPERSEDED class uses; a stacked or follow-up PR is not one. Each successor gets a successor:<ref> block in Step 9, attacked in both PRs' bodies, and the superseded row, like the revert row, needs one that survived. The file stays at its 280-line budget. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Thanks for pointing these up! 💯 The issues with the long MXFP4 thinking chains have been fixed in #62750. Alone the Hadamard rotations from #62750 hurt the throughput, but there's another stacked PR #63272 that returns the performance by fusing the rotations with preceding quant kernels of the MXFP4 GEMM. Please have a look and let me know what you think. |
1301a7c to
075a85f
Compare
Co-authored-by: Cursor <cursoragent@cursor.com> Signed-off-by: Mikko Tukiainen <Mikko.Tukiainen@amd.com> Co-authored-by: Cursor <cursoragent@cursor.com>
075a85f to
63036c1
Compare
|
This pull request has merge conflicts that must be resolved before it can be |
Overview
Opt-in
--monokerneldecode for Qwen3.8-2.4T (amd/Qwen3.8-2.4T-A95B-Quark-MXFP4,Qwen3_5MoeForCausalLM) at TP8 on MI355X. Laid out per the specialized-decode RFC #60904 undervllm/models/qwen3_5/amd/specialized_decode/, as Qwen3.8-Flash-Next's (#63032). A decode step of 1–16 rows runs two persistent FlyDSL launches a layer; everything else runs vLLM's forward. No core changes beyond the--monokerneldocstring.Claims
VLLM_GPU_SYNC_CHECK=error(Triton GDN prefill, since AITER's FlyDSL GDN prefill metadata syncs the host on prefills) completes with 0.968: no host syncs in the decode path. RULER NIAH at 64k / 128k: being measured.--monokernelswitch, default-deny refusals in three stages (each listing every reason, TP ranks agreeing), oneplan()per step, MRV2 only, model-owned code, weights and caches read in place.Validation
Serving (MI355X TP8,
vllm/vllm-openai-rocm:nightly-5d35ce3934a7804b298559d2d99eec33792e1eb9with this branch'svllm/models/qwen3_5/amd/and model hook overlaid; both armsVLLM_ROCM_USE_AITER=1, AITER fused shared experts, FULL CUDA graphs, FP8 KV cache, prefix caching off,--max-num-seqs 256; stock adds nothing, the candidate adds--monokernel;vllm bench serverandom dataset, about 4× concurrency prompts (at least 8), as many warmups as concurrency; mean TPOT, ms):--monokernel--monokernelPer launch (µs, TP8 on MI355X, each launch in a CUDA graph of 46, weights rotated over enough copies that the 256 MB MALL does not hold a layer's weights across launches, random routing):
From 8 rows up the layer block's routed experts stream about 6 TB/s of MXFP4 weights; the rest is the all-reduce, norm and route hand-offs (about 3 µs each between CTAs).
Tests (MI355X,
vllm/vllm-openai-rocm:nightly-5d35ce3934a7804b298559d2d99eec33792e1eb9with this branch'svllm/models/qwen3_5/amd/overlaid,VLLM_ROCM_USE_AITER=1 pytest -v -s tests/models/qwen3_5/amd_specialized_decode): all 41 tests pass.test_kernels.py: the GDN block against the Gemma residual add + norm, in_proj, AITER's conv update and fused gated delta rule andRMSNormGatedat widths 1, 5, 8, 9, 16 (pad rows included, first layer included), on either MXFP4 GEMM layout; the layer block against o_proj / out_proj, the all-reduce,fused_add_rms_norm, the router's softmax top-10, the MXFP4 experts with the fused shared expert and the MoE all-reduce at widths 1, 3, 8, 9, 15, 16 (each twice: both epoch parities), on all 8 ranks with rank agreement and the health words left clear. A stalled-rank test launches the layer block on rank 0 only: its waits give up after ~10 s, the failed word and rank 0's flag are set, the given-up waits name only mailbox regions of this kernel, and the next launch returns at once.test_runtime.py(host side, no kernels launched; skipped off ROCm liketest_kernels.py): off without the flag, every-reason refusal messages, parallel/feature refusals, the PIECEWISE warning, andplan()routing.--monokernelcompiles 64 builds (92 layers, widths 1–16) at startup, captures FULL graphs, and takes specialized steps.Details
Launches. Per layer: (1) on the GDN layers, the GDN block: residual add + input_layernorm, in_proj_qkvz (MXFP4) and in_proj_ba, the conv update, the gated delta rule on the cached states, RMSNormGated; on the full attention layers vLLM's input norm and attention run instead, up to o_proj's input. (2) The layer block: out_proj (MXFP4) / o_proj (BF16, with the output gate), the TP all-reduce, the residual add + post_attention_layernorm, the router's softmax top-10 and the shared-expert gate, the MXFP4 experts with the fused shared expert (expert 512), the TP all-reduce. Embeddings, final norm, head and sampling stay vLLM's.
x without an LDS tile. At H = 8192 a 16-row bf16 x is 262 KB, more than a CU's LDS, so neither launch holds it. The norm CTAs put their columns' sums of squares and every consumer builds what it reads of x from global memory (in the GDN block at up to 3 rows every CTA computes the rows' rstd itself, cheaper than the hand-off); from 4 (GDN) / 5 (layer) rows, the norm CTAs instead quantize their columns once into a shared MXFP4 image that in_proj_qkvz and the experts copy, as every CTA rebuilding all of x would move 256 × 2 [S, H] bf16 through the L2s each launch.
MoE. At decode widths stock runs AITER's
AITER_MXFP4_MXFP4path with x and silu(g)·u through dynamic MXFP4 (1×32 blocks, RCEIL scales); the layer block does the same, the intermediate quantized where it is produced, and feeds the codes to the scaled fp4 MFMA. From 4 rows the fused shared expert's gate/up runs before the route is known; from 8 rows down starts on the union slots whose gate/up finished in the full rounds, under the last, partial round.MXFP4 layouts. The GDN projections are read in the layout of whichever AITER FP4 GEMM vLLM picked for the shape (
runtime/guards.py,mxfp4_shuffled), quantizing their inputs with the matching rounding (Triton's vs ASM's RCEIL). Both layouts are covered bytest_kernels.py. No weights are duplicated.Refusals. Config (model
__init__): an architecture other thanQwen3_5MoeForCausalLM, TP≠8, PP, DP, EP, EPLB, DCP, PCP, DBO, sequence-parallel MoE, LoRA, KV connectors, routed-expert capture, speculative decoding, sleep mode, CPU weight offload, weight updates, GDN prefix caching, other GDN cache dtypes, an unfused shared expert, MRV1,STOCK_TORCH_COMPILE, other shapes, unnormalized top-k weights, layer scale, non-gfx950 or partitioned GPUs. Model (after load): every layer's modules, quant methods, weight layouts and shapes. Caches (every bind): GDN state dtypes, shapes and slot layout. Without--monokernelnone of this runs.Steps.
plan()returns aStepfor a pure decode of 1–16 rows with int32 GDN state indices, else None (stock forward). Every width is compiled when the executor is built (FlyDSLCOMPILE_ONLY), so no JIT on the first step of a width. PIECEWISE steps run stock.Shared helpers. The launch ABI and health words, the tagged mailbox with its bounded waits, the build keys, the GEMV stage, peer memory and
FailStopare adapted from the Kimi-K3 decode (#60683, by @hyukjlee, after ROCm/ATOM), as in #63032, and trimmed to what these kernels use. The package imports nothing from other models; the kernel cache key covers every source under itsmono/, and the Buildkite step triggers only on this model's directories.Limitations / follow-ups.
FailStopreads that word after each step without blocking and the TP ranks agree every 16 steps, then raise together. There is no fallback to the stock forward after a failure.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.