Skip to content

[Perf][GLM-5.3-Flash] Dense/masked-MHA sparse prefill for the NoPE (256, 0, 256) layout + skip the NoPE K concat - #55738

Merged
ZJY0516 merged 10 commits into
vllm-project:mainfrom
JaredforReal:perf/glm53-flash-masked-mha
Sep 14, 2026
Merged

ZJY0516 merged 10 commits into
vllm-project:mainfrom
JaredforReal:perf/glm53-flash-masked-mha

Conversation

@JaredforReal

@JaredforReal JaredforReal commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Purpose

One of three independent GLM-5.3-Flash perf PRs

On main, GLM-5.3-Flash's MLA layout (qk_nope 256, qk_rope 0, v 256) is unknown to the FlashAttention prefill backend and to the masked-MHA allow-list, so MLACommonImpl logs No MLA prefill backend supports this model and every prefill token goes through the per-token top-k MQA kernel. Profiling shows that kernel is KV-gather bound at the HBM roofline: 5.9 ms/layer per 16k-token chunk, and still 1.6 ms/layer for 8x2048 where dense MHA needs ~0.2 ms.

Three commits:

  1. Register the (256, 0, 256) dims with FlashAttnPrefillBackend (7 lines). Same lines as in the SM90 wiring PR [perf] wire FA and FlashMLA for sm90 GLM5Next NoPE SparseMLA #55385; kept separate so it can be dropped once that lands. This alone gives the dense-MHA prefill path for sequences ≤ index_topk (2048).
  2. Masked-MHA allow-list: _is_masked_mha_available only knew (128, 512, 128, 64, 128) and (64, 512, 192, 64, 256); GLM-5.3-Flash is (64, 512, 256, 0, 256). The kernels are identical for qk_head_dim 256 / v 256, so sequences between 2k and the existing FLASHINFER_MLA_SPARSE thresholds (36k at TP4, 64k at TP8) now use FA4 masked MHA instead of the per-token top-k MQA kernel.
  3. NoPE K concat: _concat_k_nope_k_pe returns k_nope directly when there is no RoPE part instead of allocating and copying a same-sized tensor (134 MB/layer per 16k chunk).

Test Plan

  • Unit / kernel tests listed under "Tests" below.
  • Serving benchmark, all builds back-to-back in one session on 4x GB300 (SM100), TP4, prefix caching disabled:
    vllm serve zai-org/GLM-5.3-Flash -tp 4 --attention-backend FLASHINFER_MLA_SPARSE \
      --max-model-len 69632 --max-num-seqs 256 --max-num-batched-tokens 16384 \
      --gpu-memory-utilization 0.9 --no-enable-prefix-caching
    vllm bench serve --backend vllm --model glm53-flash --dataset-name random --random-range-ratio 0 \
      --ignore-eos --seed 0 --random-input-len <in> --random-output-len <out> --max-concurrency <c> \
      --num-prompts <n> --num-warmups <c> --percentile-metrics ttft,tpot,itl,e2el

0 prefix-cache hit

  • Accuracy per build: lm_eval --model local-completions --tasks gsm8k --num_fewshot 5 --gen_kwargs temperature=0 (1319 questions)

Test Result

Performance of this PR alone

Measured on top of #55736 + #55737 (main + #55736 + #55737) vs the same + this PR, 4x GB300, TP4, --attention-backend FLASHINFER_MLA_SPARSE --max-model-len 69632 --max-num-seqs 256 --max-num-batched-tokens 16384, prefix caching disabled (hit rate 0.0% checked in the server log), vllm bench serve random dataset with warmups, back-to-back in the same session. Decode tok/s is the steady-state window value; TPOT is the per-request median.

point metric +#55736 +#55737 +#55736 +#55737 +#55738 delta
prefill 8x2048 mean TTFT ms 395 361 -8.5%
prefill 2x8192 mean TTFT ms 330 315 -4.6%
prefill 8x32768 mean TTFT ms 4693 4347 -7.4%
prefill 2x65536 mean TTFT ms 2450 2365 -3.5%
32k/256 c=16 decode tok/s 316 322 +1.9%
32k/256 c=16 mean TTFT ms 3642 3482 -4.4%
32k/256 c=16 median TPOT ms 37.50 36.97 -1.4%
1k/512 c=1 decode tok/s 147 147 -0.2%
1k/512 c=1 median TPOT ms 6.47 6.49 +0.3%
1k/512 c=64 decode tok/s 3132 3133 +0.0%
1k/512 c=64 median TPOT ms 19.24 19.09 -0.8%
1k/512 c=256 decode tok/s 7717 7755 +0.5%
1k/512 c=256 median TPOT ms 31.30 31.29 -0.0%

Prefill up to the masked-MHA threshold (≤ 36k at TP4): −8.5% TTFT for 8x2048 (dense MHA), −7.4% for 8x32k (masked MHA), −4.4% TTFT in the 32k-context serving point. 2x8192 shows −4.6%, but that 16-request point has a ~4% run-to-run noise floor (last column of the ablation below), so treat it as indicative only. 64k prompts are above the threshold and stay on the sparse kernel; the −3.5% there (0.0% noise floor) is the K-concat removal alone. Decode is unchanged (±0.8%).

Accuracy (per build, same session)

Each rung of the ablation was also evaluated on its own: gsm8k (1319 questions, 5-shot, greedy, lm_eval local-completions), prompt-logprob agreement on real 4k/12k/30k/60k prompts (mean |Δ logprob| per token vs the main build; the "main again" row is the run-to-run noise floor of this FP8 model), and a needle-in-a-haystack retrieval set (12 codes per length at 6k/16k/30k tokens, greedy, reasoning_effort=low), which exercises the 2k-36k prefill range where #55738 switches kernels.

build gsm8k flexible-extract % gsm8k strict-match %
main 93.33 ± 0.69 93.10 ± 0.70
+#55736 +#55737 +#55738 93.10 ± 0.70 93.10 ± 0.70

Caveat: runtime JIT

The first request in each new sequence-length bucket JIT-compiles the FA4 CuTeDSL mask_mod variant (BlackwellFusedMultiHeadAttentionForward) plus the Triton _scatter_topk_single_req_kernel (~7 s each; jit_monitor warns). FA4MLAPrefillKernel.get_warmup_keys does not cover mask_mod, so this is a pre-existing gap for every masked-MHA model; a warmup for the masked variants should be a follow-up before relying on this in production.

Duplicate-work check

gh pr list --repo vllm-project/vllm --state open --search "NoPE masked MHA 256" / "GLM-5.3-Flash prefill": #55385 covers the FA dims (commit 1 here is the same 7 lines); #54951 / #55222 / #55543 touch the indexer prefill sharding, the indexer workspace and SM90 fp8 KV. None adds the masked-MHA layout or the K-concat skip.

Tests

  • New unit tests (all pass on GB300, 14 cases):
    • tests/v1/attention/test_sparse_mla_backends.py::test_sparse_backend_prefill_correctness[glm53_flash_nope_hd256_v256-*]: dense MHA, masked MHA and masked MHA with chunked context for the NoPE (64, 256, 0, 256) layout against the per-token SDPA reference.
    • tests/v1/attention/test_sparse_mla_backends.py::test_is_masked_mha_available_model_dims: allow-list accepts the DeepSeek-V3.2, GLM-5 and NoPE GLM-5.3-Flash geometries and rejects quantized KV, FA3, (256, 64, 256) and a different head count.
    • tests/v1/attention/test_mla_prefill_selector.py::TestBackendValidation::test_flash_attn_accepts_glm53_flash_nope_dimensions: FlashAttnPrefillBackend.validate_configuration accepts (256, 0, 256) and still rejects (256, 64, 256).
    • tests/v1/attention/test_mla_backends.py::test_concat_k_nope_k_pe_matches_torch_cat[rope|nope]: _concat_k_nope_k_pe equals torch.cat of k_nope and the broadcast k_pe; with no RoPE part it returns k_nope itself (same data_ptr).
  • tests/kernels/test_mhc_kernels.py, tests/kernels/test_glm5next_kda_recurrent_strided.py unaffected; prompt-logprob A/B on real 4k/12k/30k/60k prompts vs main: mean|Δ| 0.06-0.12 vs a main-vs-main noise floor of 0.04-0.11 (≈2.5x at 4k where dense/masked MHA replaces the sparse kernel), identical next tokens; gsm8k 1319 questions (5-shot, greedy): main 93.03% ± 0.70, full series 92.65% ± 0.72 (measured on the three PRs together).
  • pre-commit run ruff-check / ruff-format: passed.
  • AI assistance: developed with Claude Fable 5.1 and reviewed by the submitter; profiling notes and raw benchmark logs available on request.

Notes for review

AI assistance: developed with Claude and reviewed by the submitter.


Essential Elements of an Effective PR Description Checklist
  • The purpose of the PR, such as "Fix some issue (link existing issues this PR will resolve)".
  • The test plan, such as providing test command.
  • The test results, such as pasting the results comparison before and after, or e2e results
  • (Optional) The necessary documentation update, such as updating supported_models.md and examples for a new model.

JaredforReal and others added 3 commits September 7, 2026 08:59
…tention prefill backend

GLM-5.3-Flash (qk_nope 256, qk_rope 0, v 256) was not in
FlashAttnPrefillBackend.supports_mla_dimensions, so sparse MLA fell back to
"No MLA prefill backend supports this model" and every prefill token went
through the per-token top-k MQA kernel. The kernels for qk_head_dim 256 /
v_head_dim 256 are the ones already used for the (192, 64, 256) layout.

Same 7 lines as in the SM90 wiring PR (vllm-project#55385); kept as a
separate commit so it can be dropped once that lands.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Jared Wen <jaredwen@inferact.ai>
…4, 512, 256, 0, 256) layout

wire_sm90_kernels registers the (256, 0, 256) MLA dimensions with the
FlashAttention prefill backend, which gives GLM-5.3-Flash the dense-MHA
prefill path for sequences up to index_topk (2048). Sequences above that
still went through the per-token top-k MQA kernel on SM100, which is
KV-gather bound (5.9 ms/layer per 16k-token chunk, ~4.8 ms of it pure
HBM traffic), because the masked-MHA allow-list only knew the
(128, 512, 128, 64, 128) and (64, 512, 192, 64, 256) layouts. The masked
kernel path is identical for qk_head_dim 256 / v 256, so add the GLM
layout; the existing FLASHINFER_MLA_SPARSE TP4/TP8 thresholds (masked MHA
up to 36k / 64k tokens) apply.

Prefill TTFT on 4x GB300 (TP4, together with the FlashKDA and decode
cleanup PRs): 8x2048 462 -> 363 ms, 2x8192 382 -> 317 ms, 32k/256 c16
TTFT 3876 -> 3425 ms. First use of a new sequence-length bucket JIT
compiles the FA4 mask_mod variant (~7 s); the FA4 MLA prefill warmup does
not cover mask_mod yet.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Jared Wen <jaredwen@inferact.ai>
With qk_rope_head_dim == 0 the concatenated K is just k_nope; return it
instead of allocating and copying a same-sized tensor (134 MB per layer
for a 16k-token chunk on GLM-5.3-Flash).

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Jared Wen <jaredwen@inferact.ai>
@coderabbitai

coderabbitai Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Team

Run ID: 8d6c32b3-c5b7-4218-882f-a69394df12d6

📥 Commits

Reviewing files that changed from the base of the PR and between db202bb and 7c708a8.

📒 Files selected for processing (1)
  • vllm/v1/attention/backends/mla/prefill/flash_attn.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • vllm/v1/attention/backends/mla/prefill/flash_attn.py

Included review availability: Your plan provides up to 10 included reviews per hour; 6 remain after this review.


Walkthrough

MLA execution now handles zero-width RoPE tensors without concatenation. Masked MHA and FlashAttention prefill support the GLM-5.x dimensions (256, 0, 256).

Changes

GLM-5.x NoPE MLA support

Layer / File(s) Summary
NoPE MLA execution and backend support
vllm/model_executor/layers/attention/mla_attention.py, vllm/model_executor/layers/attention/sparse_mla_attention.py, vllm/v1/attention/backends/mla/prefill/flash_attn.py
NoPE MLA returns k_nope without concatenation. Masked MHA and FlashAttention prefill recognize GLM-5.x dimensions with 256-dimensional Q/K and V heads and no RoPE dimension.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to 7c708

This change enables optimized FlashAttention prefill support for GLM-5.x NoPE MLA dimensions. No concrete merge-blocking risk remains in the available evidence.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 4 functions across 3 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main changes: GLM-5.3-Flash NoPE prefill optimization, masked-MHA support, and skipping NoPE K concatenation.
Description check ✅ Passed The description directly explains the purpose, implementation changes, performance results, accuracy checks, tests, and runtime JIT caveat for the pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

Comment thread vllm/v1/attention/backends/mla/prefill/flash_attn.py Outdated
@ZJY0516 ZJY0516 added the ready ONLY add when PR is ready to merge/full CI is needed label Sep 7, 2026
@ZJY0516

ZJY0516 commented Sep 7, 2026

Copy link
Copy Markdown
Member

/ci run

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown

@JaredforReal, CI is now available for this PR.

  • /ci run starts upstream CI; /amd-ci run starts AMD CI only.
  • /ci retry retries failed jobs in the CI build for the current PR head. If the current head has no CI build, it starts a new CI build for the current head containing only jobs that failed in the latest earlier CI build for this PR.
  • /amd-ci retry retries failed jobs in AMD CI for the current PR head. Use /amd-ci run when the current head has no AMD CI build.
  • /ci cancel cancels scheduled or running CI builds for this PR branch; /amd-ci cancel does the same for AMD CI only.

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown

✅ Triggered Buildkite CI #87552 for commit db202bbe3550.

@coderabbitai

coderabbitai Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new hard-coded support/allow-list entries and NoPE concat fast-path should be accompanied by focused regression tests to prevent future backend-selection and correctness regressions.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR improves GLM-5.3-Flash prefill performance by routing the NoPE MLA layout (qk_nope=256, qk_rope=0, v=256) onto faster dense/masked-MHA paths when applicable, and by avoiding an unnecessary key concatenation copy for NoPE models.

Changes:

  • Register the (256, 0, 256) MLA dimensions as supported by the FlashAttention prefill backend.
  • Extend the masked-MHA allow-list to recognize GLM-5.3-Flash’s NoPE layout on SM100-family GPUs.
  • Skip allocating/copying concatenated K when the RoPE component is empty (pe_dim == 0), returning k_nope directly.
File summaries
File Description
vllm/v1/attention/backends/mla/prefill/flash_attn.py Adds (256, 0, 256) to FlashAttention MLA dimension support so NoPE layouts can use the dense prefill path.
vllm/model_executor/layers/attention/sparse_mla_attention.py Adds GLM-5.3-Flash NoPE dims to the masked-MHA allow-list so mid/long prefills can use FA4 masked MHA instead of per-token sparse MQA.
vllm/model_executor/layers/attention/mla_attention.py Avoids K concat allocation/copy for NoPE by returning k_nope when k_pe is empty.
Review details
  • Files reviewed: 3/3 changed files
  • Comments generated: 3
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +2792 to +2795
if k_pe.shape[-1] == 0:
# NoPE MLA: nothing to append, so no copy either.
return k_nope

Comment on lines 98 to 103
if model_dims not in (
(128, 512, 128, 64, 128),
(64, 512, 192, 64, 256),
# GLM-5.3-Flash: NoPE, qk_head_dim 256 == the (192, 64, 256) kernel.
(64, 512, 256, 0, 256),
):
Comment on lines +330 to +336
# GLM-5.x NoPE layout: qk_head_dim 256 + 0 and v_head_dim 256 run
# the same kernels as the (192, 64, 256) DeepSeek-V3.2 layout.
MLADimensions(
qk_nope_head_dim=256,
qk_rope_head_dim=0,
v_head_dim=256,
),
Signed-off-by: Jared Wen <w13431838023@gmail.com>
@ZJY0516

ZJY0516 commented Sep 7, 2026

Copy link
Copy Markdown
Member

@JaredforReal please add some unit tests, the coplilot review is right

mergify Bot and others added 2 commits September 8, 2026 14:47
…ll paths

- test_sparse_backend_prefill_correctness gains the GLM-5.3-Flash NoPE layout,
  so dense MHA, masked MHA and masked MHA with chunked context are checked
  against the SDPA reference with qk_rope_head_dim == 0.
- _is_masked_mha_available: allow-list test over the three supported
  geometries plus quantized-KV, FA3 and mismatched-dimension negatives.
- FlashAttnPrefillBackend.validate_configuration accepts (256, 0, 256) and
  still rejects (256, 64, 256).
- _concat_k_nope_k_pe equals torch.cat of k_nope and the broadcast k_pe, and
  returns k_nope itself (no allocation) when there is no RoPE part.

Signed-off-by: Jared Wen <jaredwen@inferact.ai>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
@ZJY0516

ZJY0516 commented Sep 9, 2026

Copy link
Copy Markdown
Member

/ci run

@ZJY0516
ZJY0516 enabled auto-merge (squash) September 9, 2026 07:10
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown

✅ Triggered Buildkite CI #87857 for commit 679cd7222e70.

@mergify

mergify Bot commented Sep 11, 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, @JaredforReal.

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

…sked-mha

# Conflicts:
#	tests/v1/attention/test_sparse_mla_backends.py
auto-merge was automatically disabled September 14, 2026 08:17

Head branch was pushed to by a user without write access

@JaredforReal

Copy link
Copy Markdown
Contributor Author

/ci run

@github-actions

Copy link
Copy Markdown

✅ Triggered Buildkite CI #88766 for commit 5e77badb544c.

@ZJY0516
ZJY0516 enabled auto-merge (squash) September 14, 2026 08:47
@ZJY0516
ZJY0516 merged commit 238cb2b into vllm-project:main Sep 14, 2026
162 checks passed
Shreya-gaur pushed a commit to Shreya-gaur/vllm_private that referenced this pull request Sep 14, 2026
…56, 0, 256) layout + skip the NoPE K concat (vllm-project#55738)

Signed-off-by: Jared Wen <jaredwen@inferact.ai>
Signed-off-by: Jared Wen <w13431838023@gmail.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

glm needs-rebase ready ONLY add when PR is ready to merge/full CI is needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants