Skip to content

[ROCm][MiniMax-M3] MXFP4 K/V and index cache (mxfp4) on gfx950 - #64198

Draft
ganyi1996ppo wants to merge 6 commits into
vllm-project:mainfrom
ganyi1996ppo:ganyi/m31-rocm-mxfp4-kv-index
Draft

ganyi1996ppo wants to merge 6 commits into
vllm-project:mainfrom
ganyi1996ppo:ganyi/m31-rocm-mxfp4-kv-index

Conversation

@ganyi1996ppo

@ganyi1996ppo ganyi1996ppo commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Overview

Adds --kv-cache-dtype mxfp4 with --attention-config '{"indexer_kv_dtype": "mxfp4"}' for MiniMax-M3.1 on ROCm gfx950 (MI355X, TP4): K, V and the indexer's key cache are stored as MXFP4 (e2m1 values, one e8m0 scale per 32), 68 B per token and head. Reference implementation: ROCm/ATOM#2770.

Draft; not runnable yet. #64111 has merged, so this is now rebased onto main (16d667348d) as 5 commits (4 feature commits plus a test-only type fix). It still waits on three open AITER PRs (#7056, #7060, #7061), merged #7057, and an AITER pin bump (see Details). Placeholder to claim the work and get early design feedback.

AI-assisted (Claude Code). No human has reviewed the diff line by line yet; this stays a draft until that is done.

Claims

  • New cache dtype mxfp4 (uint8 storage, KVQuantMode.MXFP4) and a gfx950 attend for MiniMax-M3 sparse attention. Per head a 128-token page is [128 x 64 B e2m1][128 x 4 B e8m0] = 8704 B; the main K/V spec is num_head_slots=2*Hkv, state_content_bytes=68, the index spec head_size=68.
  • The fused QK-norm / RoPE kernel (AITER fused_qknorm_idxrqknorm) writes the MXFP4 K, V and index-K pages and writes the main q as e4m3 into q_out.
  • Index scores come from AITER's FlyDSL MXFP4 scorers (decode and prefill); the top-k is the one the NVFP4 indexer uses; the attend runs AITER's pa_decode_mxfp4 on a one-block-per-page table, for decode and prefill.
  • The MXFP4 decode scorer's work map is one persistent buffer built for the model-length block bound, so FULL decode CUDA graphs replay a valid map.
  • Not covered: Mono decode (stays off), indexer CP, TP other than 4, the MTP/DSpark draft layers (the existing check raises).

Validation

Rebased onto main 16d667348d (live main tip when this was pushed); git range-diff against the previous head d24ae8f7f2 shows the 4 feature commits patch-identical; the 5th commit (d6dbccaac4) only touches tests/models/minimax_m3/test_mxfp4_kv_cache.py. Checked on a CPU-only host, no GPU used:

Check Result
pytest tests/models/minimax_m3/test_mxfp4_kv_cache.py tests/kernels/attention/test_minimax_m3_mxfp4.py 9 passed, 1 skipped (the kernel test file skips: needs gfx950 FlyDSL kernels)
pre-commit run (all hooks, including mypy) on the 14 changed files, at d6dbccaac4 pass. The earlier head ffd9f4a06d had 3 mypy errors in tests/models/minimax_m3/test_mxfp4_kv_cache.py (dynamic attributes on the fake aiter module, an Optional decode.max_seq_len); fixed in the 5th commit by typing the fake module as Any and asserting decode is not None, assertions unchanged.

CI at d6dbccaac4: pre-commit and pre-run-check pass; docs build was still pending when this was written.

Not validated after the rebase: any GPU run. The kernel tests, end-to-end serving, CUDA-graph capture, accuracy and speed were not re-run on this head. Results quoted in earlier versions of this description were measured on the pre-rebase head d24ae8f7f2 on an ROCm image and are not repeated as evidence for this head. CI has no gfx950 job that can run these ops: the kernel tests skip until the AITER pin carries them.

For orientation only, ATOM's own measurements of the same kernels (ATOM#2770, MiniMax-M3.1 TP4, MXFP4 KV and index against the NVFP4 path): c32 +7.4% throughput and +26.2% P90 interactivity, c48 +8.1% and +32.3%; KV capacity 13.4M against 7.3M tokens for fp8. These are ATOM results, not vLLM results.

Details

Prerequisites. ROCm/aiter#7057 (fused write kernel: MXFP4 K/V and index-K, e4m3 q_out; this PR follows its final API, in which q_fp8_out and q_scale are gone) has merged; the remaining ROCm/aiter#7056 (FlyDSL pa_decode_mxfp4), #7060 (index decode scorer) and #7061 (index prefill scorers) need to merge, then an AITER pin bump in docker/Dockerfile.rocm_base, then this PR. The pin is not bumped here. AITER is imported inside functions behind VLLM_ROCM_USE_AITER=1; without the ops the layer raises a message naming those PRs.

Commits (on 16d667348d):

  1. 99556ddca5 Add the mxfp4 KV cache dtype and its page geometry (no behavior change on its own)
  2. 507ac36aef MXFP4 K/V and index cache on gfx950 (indexer, attend, write, tests, docs)
  3. 06fdab1145 Pass the e4m3 main q as q_out to the aiter MXFP4 write
  4. ffd9f4a06d Keep the MXFP4 decode work map stable for CUDA-graph replay
  5. d6dbccaac4 Fix mypy errors in test_mxfp4_kv_cache.py (test-only)

Why not a duplicate. No open or merged PR stores MiniMax-M3 K/V or index keys as MXFP4. #63547 is fp8 KV, #45838 is MXFP4 experts, #59739 (mxfp4_mla) and #64120 (mxfp4_ds_mla) are MLA/DSv4 caches. The dtype string mxfp4 is a distinct exact-match name from mxfp4_mla and mxfp4_ds_mla; KVQuantMode.MXFP4 = 16 avoids the 15 #64120 takes.

Design.

  • It mirrors the NVFP4 hooks (is_mxfp4 dispatch in amd/model.py, a sibling indexer and sparse backend), and reuses its metadata builders, FlyDSL top-k and the existing sparse_pa block-table kernels, which gain a pages_per_block argument (1 for a page-128 cache).
  • AITER's pa_sparse_block_topk is not used: its build only accepts pages_per_block == 8, and it expects scores already pinned by the scorer, which the MXFP4 scorers do not do. A small Triton kernel writes AITER's init/local sentinels (1e30 / 1e29) on each row's causal blocks, and the existing top-k reads only a row's causal blocks, so the scorer's unwritten entries are never seen.
  • The index query is bf16 and the main query e4m3 (the configuration ATOM measured).
  • It needs one KV head per rank (TP4 on the 4-KV-head checkpoint); the checks raise a message with the reason otherwise.

Size. About 850 lines of non-test code, most of it the two new files (indexer_mxfp4.py, sparse_attention_mxfp4.py) that parallel the NVFP4 ones, plus about 700 lines of tests. Docs: one line in docs/features/quantization/quantized_kvcache.md.


Pull Request Checklist
  • I used vLLM's /pr-checklist skill. (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.

🤖 Generated with Claude Code

@mergify

mergify Bot commented Oct 28, 2026

Copy link
Copy Markdown
Contributor

Documentation preview: https://vllm--64198.org.readthedocs.build/en/64198/

@mergify mergify Bot added documentation Improvements or additions to documentation ci/build deepseek Related to DeepSeek models multi-modality Related to multi-modality (#4194) performance Performance-related issues quantization qwen Related to Qwen models kimi k3 minimax DSv4.1 Related to DeepSeek-V4.1 models nvidia rocm Related to AMD ROCm labels Oct 28, 2026
@github-project-automation github-project-automation Bot moved this to Todo in AMD Oct 28, 2026
@mergify mergify Bot added the mrv2 Model Runner V2 specific label Oct 28, 2026
@mergify mergify Bot added the tool-calling label Oct 28, 2026
@github-project-automation github-project-automation Bot moved this to Unsorted queue in Tool Calling (arpera) Oct 28, 2026
@github-project-automation github-project-automation Bot moved this to Unsorted queue in Tool Calling Oct 28, 2026
@mergify

mergify Bot commented Oct 28, 2026

Copy link
Copy Markdown
Contributor

This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @ganyi1996ppo.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

@mergify mergify Bot added the needs-rebase label Oct 28, 2026
amd-dlimpus added a commit to amd-dlimpus/vllm that referenced this pull request Oct 28, 2026
…de.MXFP4 = 16)

Move KVQuantMode.MXFP4 from 15 to 16, since 15 is MXFP4_DS_MLA in vllm-project#64120,
and make the cache.py, torch_utils.py and kv_cache_interface.py hunks
identical to c7f5944 in vllm-project#64198 so whichever PR lands second drops a
clean duplicate.

Signed-off-by: Limpus, David <dlimpus@amd.com>
amd-dlimpus added a commit to amd-dlimpus/vllm that referenced this pull request Oct 28, 2026
…VQuantMode.MXFP4 = 16)"

This reverts commit 762bcf8.

Signed-off-by: Limpus, David <dlimpus@amd.com>
amd-dlimpus added a commit to amd-dlimpus/vllm that referenced this pull request Oct 28, 2026
15 is MXFP4_DS_MLA in vllm-project#64120 and 16 is MXFP4 in vllm-project#64198.

Signed-off-by: Limpus, David <dlimpus@amd.com>
amd-dlimpus added a commit to amd-dlimpus/vllm that referenced this pull request Oct 28, 2026
15 is MXFP4_DS_MLA in vllm-project#64120 and 16 is MXFP4 in vllm-project#64198.

Signed-off-by: Limpus, David <dlimpus@amd.com>
(cherry picked from commit b3a9d3e)
ganyi1996ppo and others added 4 commits November 1, 2026 19:48
Register `--kv-cache-dtype mxfp4` (uint8 storage, `KVQuantMode.MXFP4`) and the
MXFP4 page layout the MiniMax-M3 K/V and index caches use on gfx950: per head,
128 tokens x 64 B of e2m1 data followed by 128 tokens x 4 B of e8m0 scales,
8704 B in all. The dtype is not accepted by any attention path yet.

Signed-off-by: ganyi <ygan@amd.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Serve MiniMax-M3.1 with `--kv-cache-dtype mxfp4` and
`indexer_kv_dtype="mxfp4"` on gfx950, TP4 (one KV head per rank):

- the fused QK-norm / RoPE kernel writes MXFP4 K, V and index-K pages and
  hands back the main q as e4m3 (new `fused_qknorm_idxrqknorm_mxfp4` wrapper);
- the index scores come from AITER's FlyDSL MXFP4 scorers, the forced
  init/local blocks are pinned with AITER's sentinels, and the top-k is the
  one the NVFP4 indexer uses;
- the attend turns each query token's top-k into a one-block-per-page table
  and runs AITER's `pa_decode_mxfp4` on it, for decode and prefill.

The sparse block-table builders gain a `pages_per_block` argument (1 for a
page-128 cache). Needs an AITER with the MXFP4 FlyDSL ops (ROCm/aiter#7056,
vllm-project#7057, vllm-project#7060, vllm-project#7061). Mono, indexer CP, other TP sizes and the draft layers
are not covered.

Signed-off-by: ganyi <ygan@amd.com>
Co-Authored-By: Claude <noreply@anthropic.com>
…rite

Follow the final ROCm/aiter#7057 API: `fused_qknorm_idxrqknorm` no longer has
`q_fp8_out` / `q_scale`; an e4m3 `q_out` is written with a saturating cast at
scale 1.0 and no bf16 q row is written. The bytes are those `q_fp8_out` gave.

Signed-off-by: ganyi <ygan@amd.com>
Co-Authored-By: Claude <noreply@anthropic.com>
…ph replay

The MXFP4 index scorer's work map was allocated by every `build()` and sized
from the step's `max_seq_len`. A FULL decode graph bakes in the address of the
map it was captured with and the scorer's grid (the map's row count), so a
replay read a map that was no longer allocated, and one built for a different
block bound. On the first 32-request burst this faults with
`hipErrorIllegalAddress`.

Refill one persistent buffer (sized with AITER's `work_map_capacity`) every step
and always build the map, and score the decode rows, with the model-length
block bound the graph is captured with.

Signed-off-by: ganyi <ygan@amd.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: ganyi <ygan@amd.com>
@ganyi1996ppo
ganyi1996ppo force-pushed the ganyi/m31-rocm-mxfp4-kv-index branch from d24ae8f to ffd9f4a Compare November 2, 2026 02:05
@mergify mergify Bot removed the needs-rebase label Nov 2, 2026
Annotate the fake aiter module as Any (its attributes are set dynamically)
and narrow the Optional decode metadata before reading max_seq_len. The
assertions are unchanged.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Signed-off-by: ganyi <ygan@amd.com>
ROCm/aiter#7056 merges the standalone pa_decode_mxfp4 entry into pa_decode.
Call pa_decode(out, q, k_data, v_data, ctx, bt, scale, 1, key_scale=,
value_scale=, work_plan=None, max_context_length=) instead; max_partitions=1
is dropped because a plan-free call already runs one task per row. The
availability probe imports pa_decode.

Depends on ROCm/aiter#7056.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Signed-off-by: ganyi <ygan@amd.com>
@ganyi1996ppo

Copy link
Copy Markdown
Contributor Author

Pushed 91d7ee1 (one fast-forward commit on top of d6dbcca): the MXFP4 sparse attend now calls aiter's pa_decode(out, q, k_data, v_data, ctx, bt, scale, 1, key_scale=, value_scale=, work_plan=None, max_context_length=) instead of aiter.ops.flydsl.pa_decode_mxfp4; max_partitions=1 is dropped (a plan-free call already runs one task per row). rocm_mxfp4_aiter_unavailable_reason probes pa_decode instead of the removed module, and the mxfp4_pages docstrings are updated. Only vllm/models/minimax_m3/amd/{sparse_attention_mxfp4,indexer_mxfp4,ops/mxfp4_pages}.py changed.

Why: ROCm/aiter#7056 (head f017c23591) merges the standalone pa_decode_mxfp4 entry into pa_decode at the review's request; without this commit the availability probe reports the MXFP4 ops unavailable and the sparse path cannot run once #7056 lands. Depends on ROCm/aiter#7056.

Tested on gfx950 against aiter#7056 f017c23591 (same PA files), from an archive of this commit overlaid on the vllm nightly rocm100 image (0.32.0rc1, torch 2.13; python overlay, not a source build): tests/kernels/attention/test_minimax_m3_mxfp4.py -k sparse_attend, 6 passed; ruff and ruff format clean. Not run: the rest of that file (index/write/layer tests), model serving, CI.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/build deepseek Related to DeepSeek models dflash documentation Improvements or additions to documentation DSv4.1 Related to DeepSeek-V4.1 models k3 kimi minimax mrv2 Model Runner V2 specific multi-modality Related to multi-modality (#4194) nvidia performance Performance-related issues quantization qwen Related to Qwen models rocm Related to AMD ROCm speculative-decoding tool-calling

Projects

Status: Todo
Status: No status
Status: Backlog
Status: Unsorted queue

Development

Successfully merging this pull request may close these issues.

1 participant