Skip to content

[perf] wire FA and FlashMLA for sm90 GLM5Next NoPE SparseMLA - #55385

Open
JaredforReal wants to merge 1 commit into
vllm-project:mainfrom
JaredforReal:wire_sm90_kernels
Open

JaredforReal wants to merge 1 commit into
vllm-project:mainfrom
JaredforReal:wire_sm90_kernels

Conversation

@JaredforReal

@JaredforReal JaredforReal commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Wire up two additional sparse-MLA attention backends for GLM5Next NoPE (head_size=512, e.g. GLM-5.3-Flash) on SM90: FLASHMLA_SPARSE and FLASH_ATTN_MLA_SPARSE. FlashInfer SM90 remains the default; the new backends are opt-in via --attention-backend.

Changes

  • flashmla_sparse.py: admit head_size 512 (bf16 KV only; quantized DS-MLA cache formats stay 576-only) and skip the concat_mla_q call when the rope part is empty.
  • prefill/flash_attn.py: whitelist the GLM5Next NoPE dims (qk_nope=256, rope=0, v=256) for the dense-MHA prefill path (same kernel dims as DeepSeek's (192, 64, 256)).
  • cmake/external_projects/vllm_flash_attn.cmake: pin vllm-flash-attn to 9cd61de (Rope dim flash-attention#172, merged: FA3 OnlyQv forward for head_size==0). The FlashMLA pin is unchanged: the existing pin 6bc4941 already supports d_qk=512 sparse prefill/decode, and [Feature] add support NoPE head dim 512 for GLM5Next model on SM90 FlashMLA#17 (dense 512 decode) lands independently.

No duplication of existing PRs: the backend wiring is new; the FA3 kernel support landed in vllm-project/flash-attention#172 and FlashMLA dense-512 in vllm-project/FlashMLA#17.

Benchmarks

Setup: zai-org/GLM-5.3-Flash (bf16, ~306 GB), 4× H200 (SM90), TP=4, FlashInfer 0.6.18. vllm bench serve, random dataset, --ignore-eos, --max-model-len 36000, warmup with a different seed, two fresh-server runs per backend (cross-rep variance ±0.1%). Both workloads exceed index_topk (2048), so prefill and decode both exercise each backend's sparse-MQA kernel.

W1: 32k in / 512 out, concurrency 32 (96 requests)

Backend out tok/s (run1/run2) Δ vs FI TTFT mean (ms) TPOT mean / p99 (ms)
FlashInfer SM90 (default) 338.6 / 338.8 9342 75.9 / 90.5
FlashMLA 340.5 / 340.5 +0.5% 9310 75.4 / 89.9
FA3 346.2 / 346.4 +2.2% 9173 74.2 / 88.4

W2: 4k in / 1k out, concurrency 128 (384 requests)

Backend out tok/s (run1/run2) Δ vs FI TTFT mean (ms) TPOT mean / p99 (ms)
FlashInfer SM90 (default) 2619.8 / 2618.9 4304 44.4 / 47.8
FlashMLA 2676.7 / 2668.4 +2.0% 4294 43.5 / 46.8
FA3 2687.5 / 2689.5 +2.6% 4243 43.3 / 46.6

Independently reproduced by @simon-veitner-redhat with matching magnitudes. An earlier version of this table (showing +16~32%) was measured with the default 1M --max-model-len, which inflates FlashInfer's host-side plan time — corrected above.

Caveat: with --no-enable-prefix-caching + reduced --max-model-len, this hybrid model deterministically hits an IMA in the generic _compute_slot_mappings_kernel (vllm/v1/worker/gpu/block_table.py) under the FA backend only. Not related to the attention kernels; tracked as a separate core-infra issue.

Tests

  • Impl-level cross-backend correctness vs SDPA reference (NoPE geometry, block_size 64/128): all three backends pass.
  • FA3 kernel tests: hopper/test_only_qv.py in vllm-project/flash-attention (8/8).
  • Existing DeepSeek (576) sparse-MLA regression: tests/v1/attention/test_sparse_mla_backends.py passes.

AI assistance was used for the implementation and benchmarking.

@coderabbitai

coderabbitai Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

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.

@mergify mergify Bot added the glm label Sep 4, 2026
JaredforReal added a commit to JaredforReal/vllm that referenced this pull request Sep 7, 2026
…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>
@mergify mergify Bot added the ci/build label Sep 8, 2026
@JaredforReal
JaredforReal marked this pull request as ready for review September 8, 2026 13:46
Copilot AI lite review requested due to automatic review settings September 8, 2026 13:46

@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.

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

There is a correctness risk in the FlashMLA sparse tuple-input path (actual_num_heads handling) and the newly introduced head_size=512 NoPE behavior needs targeted test coverage to prevent regressions.

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

Pull request overview

This PR extends vLLM’s sparse-MLA attention backend support for SM90 to cover GLM5Next “NoPE” geometry, and updates pinned external dependencies to pick up newer FlashAttention/FlashMLA implementations.

Changes:

  • Add GLM5Next NoPE MLA dimension support to the FlashAttention prefill backend selector.
  • Extend FlashMLA sparse backend to advertise head_size=512, reject quantized KV-cache formats for that geometry, and handle rope_dim==0 (NoPE) query concatenation.
  • Bump pinned commits for vllm-project/flash-attention and vllm-project/FlashMLA.
File summaries
File Description
vllm/v1/attention/backends/mla/prefill/flash_attn.py Adds GLM5Next NoPE MLA dimensions to FlashAttn prefill backend support list.
vllm/v1/attention/backends/mla/flashmla_sparse.py Adds head_size=512 support and NoPE handling in FlashMLA sparse forward path plus a guard for quantized KV-cache formats.
cmake/external_projects/vllm_flash_attn.cmake Updates the pinned FlashAttention commit used by the build.
cmake/external_projects/flashmla.cmake Updates the pinned FlashMLA commit used by the build.
Review details
  • Files reviewed: 4/4 changed files
  • Comments generated: 2
  • 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 1001 to 1012
actual_num_heads = self.num_heads
if isinstance(q, tuple):
ql_nope, q_pe = q
q = self.q_concat_buffer[: ql_nope.shape[0]]
ops.concat_mla_q(ql_nope, q_pe, q)
if q_pe.size(-1) == 0:
# NoPE (GLM5Next): concat_mla_q requires rope_dim == 64,
# copy directly into the head-padded buffer instead.
q[:, : ql_nope.shape[1]].copy_(ql_nope)
else:
ops.concat_mla_q(ql_nope, q_pe, q)
else:
actual_num_heads = q.shape[1]
Comment on lines 137 to +139
def get_supported_head_sizes(cls) -> list[int]:
# DeepSeek V3.2 layout: 512 NoPE + 64 RoPE = 576.
return [576]
# DeepSeek V3.2: 512 NoPE + 64 RoPE = 576; GLM5Next NoPE: 512.
return [576, 512]
@mergify

mergify Bot commented Sep 8, 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

@mergify mergify Bot added the needs-rebase label Sep 8, 2026
# DeepSeek V3.2 layout: 512 NoPE + 64 RoPE = 576.
return [576]
# DeepSeek V3.2: 512 NoPE + 64 RoPE = 576; GLM5Next NoPE: 512.
return [576, 512]

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.

this is unconditionally done and doesn't guard on compute capability.
i think it may reorder priority for sm_100 backend as follows:

capability heads/rank before PR after PR
sm100 16 (TP=4) FLASHINFER_MLA_SPARSE FLASHINFER_MLA_SPARSE
sm100 32 (TP=2) FLASHINFER_MLA_SPARSE FLASHMLA_SPARSE
sm100 64 (TP=1) FLASHINFER_MLA_SPARSE FLASHMLA_SPARSE
sm90 any FLASHINFER_MLA_SPARSE_SM90 FLASHINFER_MLA_SPARSE_SM90

is this intended?

@simon-veitner-redhat

Copy link
Copy Markdown
Contributor

i get different numbers than you for the backends. could you please recheck?


Setup: zai-org/GLM-5.3-Flash on 4x H200 (sm90), TP=4 (16 heads per rank), vLLM at
PR head db053be, FlashInfer 0.6.18, CUDA graphs on, --max-model-len 36000.
MLA prefill backend is FLASH_ATTN in all runs (the default). Two runs per backend,
serialized on the same four GPUs. Workloads and flags are the ones from the PR
description.

W1: 32k in / 512 out, conc 32, 96 req

backend run out tok/s total tok/s TTFT mean ms TTFT p99 ms TPOT mean ms TPOT p99 ms ITL p99 ms
FLASH_ATTN_MLA_SPARSE 1 341.2 22179 9470 40248 74.9 89.3 317.4
FLASH_ATTN_MLA_SPARSE 2 340.9 22157 9472 40252 75.0 89.4 318.0
FLASHMLA_SPARSE 1 336.5 21871 9575 40680 76.0 90.7 323.3
FLASHMLA_SPARSE 2 336.1 21844 9580 40685 76.1 90.9 322.9
FLASHINFER_MLA_SPARSE_SM90 1 333.6 21686 9575 40670 76.8 91.4 321.6
FLASHINFER_MLA_SPARSE_SM90 2 334.3 21730 9666 40665 76.4 91.1 320.9

W2: 4k in / 1k out, conc 128, 384 req

backend run out tok/s total tok/s TTFT mean ms TTFT p99 ms TPOT mean ms TPOT p99 ms ITL p99 ms
FLASH_ATTN_MLA_SPARSE 1 2339.3 11697 7581 29454 47.1 56.2 288.3
FLASH_ATTN_MLA_SPARSE 2 2669.8 13349 4325 19822 43.5 46.8 287.8
FLASHMLA_SPARSE 1 2651.1 13256 4371 20045 43.8 47.3 291.2
FLASHMLA_SPARSE 2 2645.6 13228 4394 20134 43.9 47.3 291.4
FLASHINFER_MLA_SPARSE_SM90 1 2592.7 12963 4379 20079 44.9 48.2 292.1
FLASHINFER_MLA_SPARSE_SM90 2 2595.0 12975 4379 20074 44.8 48.1 291.5
Bench script
#!/usr/bin/env bash
# Benchmark one sparse MLA attention backend on GLM-5.3-Flash (TP=4) with the two
# workloads from the PR #55385 description. Run under the GPU reservation tool:
#   chg run --gpu-ids 0,1,2,3 --timeout 40m -- scripts/bench_sparse_backend.sh <BACKEND> <PORT> <RESULT_DIR>
# e.g. scripts/bench_sparse_backend.sh FLASHMLA_SPARSE 8802 logs/bench/FLASHMLA_SPARSE
set -x
BACKEND=${1:?backend}
PORT=${2:?port}
DIR=${3:?result dir}
ROOT=/home/simon-veitner-redhat/dev/glm5-sparse-mla-sm90
mkdir -p "$DIR"; DIR=$(realpath "$DIR"); cd "$DIR"
source $ROOT/.venv/bin/activate
export TMPDIR=/home/simon-veitner-redhat/tmp; mkdir -p $TMPDIR
echo "CUDA_VISIBLE_DEVICES=$CUDA_VISIBLE_DEVICES"

vllm serve zai-org/GLM-5.3-Flash -tp 4 --max-model-len 36000 --attention-backend $BACKEND --port $PORT --no-enable-log-requests > server.log 2>&1 &
SPID=$!

READY=0
for i in $(seq 1 90); do
  if curl -s localhost:$PORT/v1/models > /dev/null 2>&1; then READY=1; break; fi
  if ! kill -0 $SPID 2>/dev/null; then echo "SERVER DIED"; break; fi
  sleep 10
done
echo "READY=$READY after $((i*10))s"
if [ "$READY" != "1" ]; then tail -40 server.log; kill $SPID 2>/dev/null; sleep 20; kill -9 $SPID 2>/dev/null; exit 1; fi
grep -n "Using AttentionBackendEnum\|MLA prefill backend" server.log | head -3

# W1: long context, 32k in / 512 out, concurrency 32, 96 requests
vllm bench serve --model zai-org/GLM-5.3-Flash --port $PORT --dataset-name random \
  --random-input-len 32768 --random-output-len 512 --num-prompts 96 --max-concurrency 32 \
  --ignore-eos --save-result --result-dir "$DIR" --result-filename w1.json > w1.log 2>&1
echo "W1 exit $?"; tail -35 w1.log

# W2: mid context, 4k in / 1k out, concurrency 128, 384 requests
vllm bench serve --model zai-org/GLM-5.3-Flash --port $PORT --dataset-name random \
  --random-input-len 4096 --random-output-len 1024 --num-prompts 384 --max-concurrency 128 \
  --ignore-eos --save-result --result-dir "$DIR" --result-filename w2.json > w2.log 2>&1
echo "W2 exit $?"; tail -35 w2.log

kill $SPID 2>/dev/null; sleep 25
pgrep -u $(id -u) -f "port $PORT" && { pkill -9 -u $(id -u) -f "port $PORT"; sleep 10; }
pgrep -u $(id -u) -f "port $PORT" && echo "LEFTOVER" || echo "CLEAN"

@simon-veitner-redhat

Copy link
Copy Markdown
Contributor

do we want to change priority in this PR? if so, we should adjust it.

- FlashMLA sparse: admit head_size 512 (bf16 only) and skip the
  concat_mla_q call when the rope part is empty
- MLA prefill: whitelist the GLM5Next NoPE dims (256, 0, 256)
- cmake: pin vllm-flash-attn and FlashMLA to the NoPE support commits

Signed-off-by: JaredforReal <w13431838023@gmail.com>
Co-authored-by: Kimi Code <noreply@moonshot.cn>
@Leoyzen

Leoyzen commented Sep 14, 2026

Copy link
Copy Markdown

@JaredforReal there's a follow-up on your fork branch: JaredforReal#22

Two things:

  • Guards the head_size==512 case. As written, adding 512 to
    get_supported_head_sizes() reorders the sm100 list for >16 heads/rank
    (16 -> FLASHINFER, 32/64 -> FLASHMLA), and the FlashMLA sm100 path has no
    NoPE-512 entry. The bf16 arm is now sm90-only, so sm100 keeps
    FLASHINFER_MLA_SPARSE first. A test pins the order.
  • Ports the zero-rope fix from [Bugfix][GLM5.3-Flash] Handle zero RoPE head dimension in FlashAttention sparse MLA #56665 into flashattn_mla_sparse.py. That file
    isn't touched by this PR, so forward_mqa still hands FA3 an empty head-dim
    tensor when qk_rope_head_dim==0.

Once it's merged into wire_sm90_kernels this PR picks it up automatically and
the merge conflict should go away.

pytest tests/v1/attention/test_flashmla_nope_sm90_backend_selection.py
-> 28 passed, 3 skipped (GPU-only)

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants