Repository navigation
Conversation
Contributor
|
This pull request has merge conflicts that must be resolved before it can be |
fxmarty-amd
reviewed
Oct 12, 2026
Comment on lines
+294
to
+307
| # VLLM_QWEN38_FAKE_MXFP6_PROJ=1 rounds the bf16 projections to MXFP6 | ||
| # here, after loading and before the mono kernels or a CUDA graph | ||
| # read them. | ||
| from vllm.models.qwen3_5.amd import mxfp6 | ||
|
|
||
| if mxfp6.ROUND_PROJ: | ||
| mxfp6.round_projections(self) | ||
| # The MXFP6 copies for the mono kernels are packed from the weights | ||
| # after the rounding, so that they hold the same values. | ||
| from vllm.models.qwen3_5.amd import mono_decode | ||
|
|
||
| if mono_decode.FP6: | ||
| mono_decode.pack_fp6(self) | ||
| return loaded |
Contributor
There was a problem hiding this comment.
Please refer to https://docs.vllm.ai/en/stable/features/quantization/online/
VLLM_QWEN38_FAKE_MXFP6_PROJ and VLLM_QWEN38_MONO_FP6 env vars should be removed, please use standard --quantization-config API.
maeehart
force-pushed
the
qwen38-mono-decode-followup
branch
from
October 14, 2026 19:02
d22391e to
2cb5450
Compare
maeehart
added a commit
to maeehart/vllm
that referenced
this pull request
Oct 14, 2026
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>
Contributor
|
This pull request has merge conflicts that must be resolved before it can be |
Co-authored-by: Cursor <cursoragent@cursor.com> Signed-off-by: Mikko Tukiainen <Mikko.Tukiainen@amd.com> Co-authored-by: Cursor <cursoragent@cursor.com>
…r block load order At 2 and 3 rows, the layer block 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`). The layer block 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. - When one batch covers the union (up to 3 rows), 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. The earlier version of this commit also issued both ug tasks' loads of a CTA before the first task's MFMAs. On the ug tasks that run in two halves, that needs about 70 VGPRs more than the CTA has at 1 to 5 rows, so it is left out. Signed-off-by: Markus Hartikainen <markus.hartikainen@amd.com>
The online MXFP4 in_proj_qkvz, out_proj and o_proj keep the gsm8k score
within noise, but they make the answers longer. In gsm8k 5-shot with
max_gen_toks 2048, the mean answer is 564 characters with MXFP4
projections against 433 with bf16, and 27 answers run into the token
limit against 8. MXFP6 E2M3 brings that back to 452 characters and 9
answers, and the kernels take about the same time as with MXFP4.
--additional-config '{"monokernel_projections": "mxfp6"}' together with
--monokernel selects them. After the weights load, runtime/mxfp6.py rounds
the bf16 in_proj_qkvz, out_proj, qkv_proj and o_proj to MXFP6 in place, so
the prefill GEMMs multiply the same values that decode reads. It then packs
MXFP6 copies of in_proj_qkvz, out_proj and o_proj for the kernels. A copy
holds 32 codes in 24 bytes and one e8m0 scale per 32 weights along K, in
tiles of 16 rows and 128 columns, so that one v_cvt_scalef32_pk32_bf16_fp6
turns 32 codes into bf16 exactly. The GDN block multiplies in_proj_qkvz in
MXFP6 (`q6`) and the layer block out_proj or o_proj (`o6`), both with the
bf16 rows (W6A16). The bf16 x rows sit in LDS, so these builds serve up to
8 rows, and wider steps run vLLM's decode.
The setting needs the projections in bf16, for example with
`--quantization-config.targets '{"*shared_expert*": "mxfp4"}'`. The
loaded-model check refuses any other format of them, with the other
reasons. An unknown value of monokernel_projections is refused at startup.
The copies come on top of the bf16 weights, which prefill still reads.
The layer block's router tasks now read the norm tasks' bf16 AR sums and
sums of squares as tagged pairs (XAR and MSQ), so the norm tasks' drain of
their xbuf stores is not on the router's path. The other consumers read
xbuf after MSQ2, which a norm task puts after the drain. The norm task also
loads its residual before it polls the partials.
Signed-off-by: Markus Hartikainen <markus.hartikainen@amd.com>
…V cache write Between the layer block of one layer and the attention of a full-attention layer, the step ran the residual add with input_layernorm, qkv_proj, fused_qk_rmsnorm_rope_gate and reshape_and_cache_flash as separate launches. afront.py runs all of them in one launch for up to 8 rows when qkv_proj has an MXFP6 copy, and the MXFP6 projections now also pack qkv_proj when it has no bias. Every CTA builds the x rows itself, so no CTA waits for another before its qkv_proj tasks. The q and k tasks hand their rows through a mailbox to the CTAs that run the q and k norms and RoPE, and the CTAs with two tasks run gate rows, because nothing in the kernel waits for them. The mailbox is the bounded one of the other two blocks, so a wait that gives up records itself in the rank's health words. The kernel also writes k and v into the FP8 KV cache as reshape_and_cache_flash does. The attention then runs with q only (_attention_q_only), so vLLM does not write them again. _af_kv keeps vLLM's cache write for any other attention backend, KV cache dtype or layout, for a shared KV cache, a quantized query or a slot_mapping without the pad rows, and logs the reason once. Every row has its own position and KV cache slot, so the kernel would also serve the full-attention layers of MTP verify steps. Against the model's kernels, only the order of the fp32 sums differs, and whether the compiler fuses the RoPE products into FMAs. Signed-off-by: Markus Hartikainen <markus.hartikainen@amd.com>
…he layer block The layer block's router could only start after the all-reduce of o_proj and the post-attention norm had built the x rows. With `rm`, at up to 5 rows, the router logits come from the core rows instead, so every CTA routes as soon as its rstd is in. rm_pack folds the router rows, the norm's (1 + w) and this rank's o_proj rows into one bf16 weight a layer, and appends the scaled router rows for this rank's slice of the residual. Over the 8 ranks, that weight times [core rows | residual slice] sums to the router logits before the rstd, which the route applies when the norm sums are in. The MXFP6 projections make that weight for every layer from the MXFP6 o_proj values, which the layer block multiplies. An MXFP4 o_proj runs without rm. The folded weight is rounded to bf16, so the logits differ from the stock router's in rounding, and a near tie between two experts can go the other way. Signed-off-by: Markus Hartikainen <markus.hartikainen@amd.com>
…MXFP6 in_proj With an MXFP6 in_proj_qkvz (`q6`), the GDN block now places its in_proj tasks by what waits for them (`qp`). The q, k and v rows, which the conv of every GDN task polls, come from CTAs with one in_proj task and no front. The CTAs with two in_proj tasks run z rows, which only the gated norm at the end reads, and they issue their second task's weights after the x loads. When the GDN tasks fill one round, the owner tasks of the key heads move to CTAs 64 to 95, whose in_proj tasks are v rows, so the front CTAs and the CTAs with two tasks run no owner task. The outputs are those of the build without `qp`, bit for bit, which test_gdn_block_mxfp6_matches_vllm_ops checks. Signed-off-by: Markus Hartikainen <markus.hartikainen@amd.com>
…FP6 paths The README describes the MXFP6 projections and the build options that come with them (q6 with qp, o6, rm, the attention front kernel) and the slot per pick at 2 and 3 rows. test_builds checks on the CPU which builds the executor compiles: with the MXFP6 projections the q6 and qp GDN builds, the o6 layer builds with rm up to 5 rows, and both attention front builds of every width. Without them, the builds stay those of the stock formats. The kernel tests of the new options came with their commits: test_gdn_block_mxfp6_matches_vllm_ops (q6, and qp bit for bit against the build without it), test_layer_tp8_matches_vllm_ops (o6 with and without the output gate, rm with the route checked against rm's own logits, and the slot per pick at 3 rows), test_attention_front_matches_vllm_ops (with and without the KV cache write) and test_mxfp6.py (the rounding and the packed layout, on the CPU). Signed-off-by: Markus Hartikainen <markus.hartikainen@amd.com>
maeehart
force-pushed
the
qwen38-mono-decode-followup
branch
from
November 10, 2026 20:42
2cb5450 to
5c63c21
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This draft follows #60833. On
amd/Qwen3.8-2.4T-A95B-Quark-MXFP4with TP8 on MI355X, it moves the GDN and attention projections of the specialized decode from MXFP4 to MXFP6 E2M3, which keeps the gsm8k answers as short as with bf16 projections, and adds an attention front kernel, router logits througho_projand a better task placement. A first measurement on the older layout put the decode step 0.94 ms faster at 1M context (median ITL 9.39 ms to 8.45 ms, with the caveats under Validation).The branch is stacked on #60833 at
63036c1291, the--monokernelversion in the layout of RFC #60904, so the first commit is #60833 and the last 6 are new. #60833 rewrote the kernels in that version, so these commits were ported by hand, not rebased. The earlier version of this PR on1301a7cfcfhad 6 commits with the same intent.What this PR does
o_projtasks, and when one batch covers the union, the down rounds run without a prefetch.--additional-config '{"monokernel_projections": "mxfp6"}'with--monokernelrounds the bf16in_proj_qkvz,out_proj,qkv_projando_projto MXFP6 after loading, so prefill multiplies the same values that decode reads, and packs MXFP6 copies of them. The GDN and layer blocks multiply those copies with the bf16 rows (W6A16). These builds serve up to 8 rows, and wider steps run vLLM's decode. The layer block's router also reads the AR sums and sums of squares as tagged pairs, so the norm tasks' drain is not on its path.input_layernorm, the MXFP6qkv_proj, the q and k norms, RoPE and the FP8 KV cache write of a full-attention layer. The attention then runs with q only, so vLLM does not write the cache again. Any other backend, cache dtype or layout keeps vLLM's cache write, and the reason is logged once.o_projin the layer block. Up to 5 rows, a folded bf16 weight, the router rows times the norm's (1 + w) times this rank'so_projrows, gives the router logits straight from the core rows. So every CTA routes before the all-reduce and the norm have built the x rows.in_proj. The q, k and v rows, which the conv of every GDN task waits for, run on CTAs with one task, and the z rows on CTAs with two tasks. The outputs stay the same bit for bit.A configuration that does not fit is refused at startup with every reason, as in #60833. An unknown
monokernel_projectionsvalue is refused with the config reasons, and projections that are not bf16 with the loaded-model reasons. No environment variable is added.Numerics
Unlike #60833, two of these changes do not keep the stock decode numerics bit for bit. MXFP6 is a different weight format, and the folded router weight rounds the logits differently, so a near tie between two experts can go the other way. On the older layout, gsm8k and the needle prompts below stayed within noise.
Validation
The numbers below are from the earlier version of these commits on #60833 at
6a1c13b2fc, which had the same kernels in the older layout. They have not been measured again on this port yet.amd/Qwen3.8-2.4T-A95B-Quark-MXFP4, TP8 on MI355X,vllm/vllm-openai-rocm:nightly-8cbd5d03006c33185f402249ff2b448efd594986with the branch on top. The #60833 arm serves the projections in online MXFP4. This PR's arm serves them in bf16 with--quantization-config.targets '{"*shared_expert*": "mxfp4"}'and the MXFP6 projections.6a1c13b2fcmax_gen_toks2048Both arms also ran the FlyDSL paged-attention decode of ROCm/aiter#5949 in
ROCM_AITER_FA, so the difference between them is the decode kernels.On this port so far: every commit passes pre-commit (ruff and mypy) on its own, the CPU tests in
tests/models/qwen3_5/amd_specialized_decode/give 29 passed and 30 skipped (the kernel tests need gfx950), and every build that the executor compiles for the MXFP6 projections at 1 to 8 rows compiles for gfx950 without register spills or scratch.Commands
The #60833 arm drops
--additional-configand uses--quantization-config.targets '{"*shared_expert*": "mxfp4", "*linear_attn.in_proj_qkv": "mxfp4", "*linear_attn.in_proj_z": "mxfp4", "*linear_attn.out_proj": "mxfp4"}'.Notes
ugtasks' loads of a CTA before the first task's MFMAs. On [ROCm][Perf] Qwen3.8-2.4T specialized decode on MI355X (--monokernel) #60833'sugtasks that run in two halves, that spills about 70 VGPRs at 1 to 5 rows, so it is left out.63036c1291refuses speculative decoding, so the MTP verify steps that the attention front kernel could serve do not reach it here.Test plan
tests/models/qwen3_5/amd_specialized_decode/test_kernels.py(GDN block with q6 and qp, layer block with o6 and rm, attention front with and without the KV cache write)63036c1291