Skip to content

[Perf][Qwen4Exp] Add SM121 TP=1 skinny-GEMM plans - #59753

Merged
benchislett merged 4 commits into
vllm-project:mainfrom
stecasta:feat/qwen4exp-sm121-lmhead-plans
Oct 2, 2026
Merged

benchislett merged 4 commits into
vllm-project:mainfrom
stecasta:feat/qwen4exp-sm121-lmhead-plans

Conversation

@stecasta

@stecasta stecasta commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Overview

Qwen3.8-Flash-Next decode ITL -9.5% at concurrency 1 on a single DGX Spark (SM121, TP=1) by adding SM121 skinny-GEMM plans for the TP=1 BF16 shapes, which today fall back to cuBLAS. The LM head (248320, 2560) gives most of it (-8.3% alone); the remaining rows add -1.3% on local-inference-lab/Qwen3.8-Flash-Next-NVFP4, where the router, HC projections and shared-expert gate are BF16 and the other dense projections are MXFP8. On nvidia/Qwen3.8-Flash-Next-NVFP4 attention, GDN and shared experts are BF16 as well and use the same rows; that checkpoint does not fit a single Spark, so it is not measured end to end.

Complements #59632 (SM121 plans for TP=2 shapes, from #59605): this PR only adds TP=1 keys to the same mechanism. Happy to fold this into #59632 or rebase on it, whichever lands first.

Claims

  • In a c1 decode step (MTP k=3) the LM head runs 4 times: 3 draft steps at M=1 (cuBLAS gemv, 6.8 ms each) and the target verify at M=4 (5.3 ms), 25.7 ms or about 33% of the 77.6 ms step (torch profiler, local-inference-lab/Qwen3.8-Flash-Next-NVFP4). Almost all of the end-to-end gain comes from the three M=1 calls (about 2.2 ms saved each).
  • cuBLAS reaches about 175 GB/s at M=1 on this shape; the skinny GEMM reaches 253 GB/s: 1.45x at M=1, 1.04x to 1.08x at M=2 to 16.
  • End to end: ITL 25.30 to 22.91 ms (-9.5%), output tokens/s +9.9% at concurrency 1 (mean of two interleaved runs per arm; LM-head rows alone: 23.20 ms, -8.3%); parity at concurrency 8 (its M=32 verify step is outside the skinny GEMM range and stays on cuBLAS).
  • GSM8K within run-to-run noise: base 249 / 247, LM-head rows only 245 / 247, full table 246 / 246 of 256 (two runs each; two identical base runs differ by 2).

Validation

Kernel, GB10 (p4242-0063, about 2.5 GHz), FlashInfer CUPTI, CUDA graph, cold L2, median of 30; cosine 1.000000 vs F.linear for every config:

M cuBLAS (us) skinny (us) speedup
1 7262 5016 1.45x
2 5217 5038 1.04x
4 5257 4917 1.07x
8 5291 4890 1.08x
16 5336 4933 1.08x
32 5608 n/a (M <= 16)

Other TP=1 shapes, same sweep (selected config vs cuBLAS; cells under 1.03x stay on cuBLAS):

Shape (N, K) Layer M=1 M=4 M=8 M=16
(1, 2560) shared-expert gate 5.58x 1.91x 1.47x 1.07x
(96, 2560) GDN B/A 1.82x 5.53x 4.07x 1.14x
(336, 10240) HC down/injection 1.41x 1.36x 1.31x 1.13x
(512, 2560) router 1.71x 2.09x 1.88x 1.38x
(640, 2560) QSA indexer Q/K 1.73x 1.95x 1.92x 1.48x
(1280, 2560) shared-expert gate/up 1.86x 1.52x 1.55x 1.46x
(2560, 640) shared-expert down 2.01x 1.74x 1.72x 1.34x
(2560, 6144) GDN/QSA output 1.12x 1.17x 1.18x 1.13x
(10240, 320) HC up 1.23x 1.22x 1.20x cuBLAS
(13312, 2560) QSA QKV/gate 1.48x 1.12x 1.16x 1.13x
(16384, 2560) GDN QKVZ 1.47x 1.11x 1.12x 1.11x

These are cold-L2 numbers. Weights of 7 MB or less stay in L2 in the server, where an in-server torch profile showed little gain for the HC, router and shared-gate rows; the end-to-end numbers below are the claim.

A bake-off on the LM-head shape (FlashInfer mm_bf16 cudnn / cublaslt / tinygemm, b12x bf16_vocab_projection default config) found nothing faster than the skinny GEMM at M=2 to 16; at M=1 b12x was 4% faster than the skinny config but would need a new dependency.

End to end, same node (p4242-0074), local-inference-lab/Qwen3.8-Flash-Next-NVFP4, TP=1, MTP k=3 with forced acceptance length 3, FP8 KV, --max-num-batched-tokens 4096, SPEED-Bench 2K in / 1K out, two interleaved runs per arm, mean:

Concurrency Requests ITL p50 (ms) base / LM head / full Output tok/s base / LM head / full TTFT p50 (ms) base / LM head / full
1 8 25.30 / 23.20 / 22.91 (-9.5%) 38.3 / 41.55 / 42.1 (+9.9%) 891 / 878 / 881
8 16 57.92 / 58.05 / 58.17 127.7 / 128.0 / 128.3 4429 / 4416 / 4430

Accuracy: GSM8K, first 256 test questions, greedy, thinking off, speculative decoding off, 8 concurrent requests, two runs each: base 249 and 247, LM-head rows only 245 and 247, full table 246 and 246 (of 256). Batch composition varies between runs, so identical configs differ by a few questions.

Disclaimers:

  • End to end ran on vLLM af7f9488c (the image used for Flash-Next serving) with this PR's table overlaid on low_latency_gemm.py.
  • Small request counts per cell (8 and 16); baseline repeats on one node agreed within 1.5%.
  • (13312, 2560) is the unfused QSA QKV/gate shape; after [Perf][Qwen4Exp] Merge QSA QKVG and indexer QK projections #59533 lands, the fused (13952, 2560) key needs a row (sweep data available).
  • SM120 (RTX PRO 6000) is not covered: on the 600 W Server Edition the LM head gains only 1.07x to 1.09x, since cuBLAS is already close to that card's bandwidth.
Tests
pytest tests/kernels/test_bf16_skinny_gemm.py -k "qwen4_exp" -v

On GB10 (p4242-0070): the Qwen4Exp capability-routing test and the SM121 selected-shape test (cosine > 0.999 vs F.linear for every shape and M in the table), run against this branch's low_latency_gemm.py: 64 passed. The rest of the file needs GLM-5.2 modules that are not in the Spark serving image.

Details

AI assistance (Claude) was used for the sweep scripts, measurements and this description; the change and the numbers were reviewed by the submitter. Duplicate check: #59632 is the only open SM121 plan PR for Qwen4Exp and covers TP=2 shapes only.

On DGX Spark (SM121) at TP=1 the Qwen4Exp LM head (248320 x 2560, BF16)
has no low-latency plan and falls back to cuBLAS, which streams the
weight at about 175 GB/s for a single row. With MTP it runs once per
verify step and once per draft step, so it is a large share of decode.

Add an SM121 plan table for the TP=1 LM head at M=1, 2, 4, 8 and 16,
selected by a FlashInfer CUPTI sweep on GB10, and route capability
(12, 1) to it. M > 16 keeps cuBLAS (outside the skinny GEMM range).

Signed-off-by: Stefano Castagnetta <scastagnetta@nvidia.com>

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

@mergify mergify Bot added the qwen Related to Qwen models label Oct 2, 2026
Add GB10 skinny-GEMM plans for the TP=1 router, shared-expert gate and
projections, HC projections, GDN projections, QSA projections and the
GDN/QSA output projection. Configs are the fastest valid ones from a
CUDA-graph sweep against cuBLAS; cells under 1.03x are left on cuBLAS.

Signed-off-by: Stefano Castagnetta <scastagnetta@nvidia.com>
@stecasta stecasta changed the title [Perf][Qwen4Exp] Add SM121 TP=1 LM-head skinny-GEMM plans [Perf][Qwen4Exp] Add SM121 TP=1 skinny-GEMM plans Oct 2, 2026

@benchislett benchislett left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

@benchislett benchislett added the ready ONLY add when PR is ready to merge/full CI is needed label Oct 2, 2026
@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown

✅ @stecasta, CI is now available for this PR.

  • /ci run starts upstream CI; /amd-ci run starts AMD CI only.
  • Your branch must contain every commit currently on its upstream target branch. Merge or rebase onto the latest target branch, then rerun the command. Append --allow-stale to a run command to test an outdated branch at your own risk.
  • /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.

@benchislett
benchislett enabled auto-merge (squash) October 2, 2026 15:47
@mgoin

mgoin commented Oct 2, 2026

Copy link
Copy Markdown
Member

/ci run

@mgoin mgoin added performance Performance-related issues nvidia labels Oct 2, 2026
@github-project-automation github-project-automation Bot moved this to Ready in NVIDIA Oct 2, 2026
@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown

✅ Triggered Buildkite CI #92659 for commit e5fa725bbd56.

@vllm-agent

Copy link
Copy Markdown
Collaborator

CI selector (shadow): 2 test steps (2 jobs) instead of 36 (49 jobs)

Shadow mode: this changes nothing about what CI runs. It shows what the evidence-based selector would pick for this PR, next to today's rules. How it works.

Feedback welcome: reply here if it would skip a step this change needs, or runs something unrelated.

steps (jobs) Today's rules Selector Would skip Would add
NVIDIA, CPU and others 36 (49) 2 (2) 34 (47) 0 (0)
AMD mirrors 33 (41) 1 (1) 32 (40) 0 (0)
Selector would run (2)
  • kernels-root-misc-test-b200
  • kimi-k3-unit-tests-b200
Would skip (today's rules run them) (34)
  • ascend-npu-test
  • basic-correctness ×2
  • basic-correctness-cpu-offload
  • basic-correctness-cumem
  • basic-correctness-prefetch-offload
  • basic-correctness-sleep-mode
  • basic-models-test-other-cpu
  • basic-models-tests-initialization
  • basic-models-tests-other
  • benchmarks-cli-test
  • cpu-language-generation-and-pooling-model-tests ×3
  • entrypoints-integration-api-server ×4
  • entrypoints-integration-api-server-generate
  • entrypoints-integration-api-server-openai-chat_completion
  • entrypoints-integration-api-server-openai-completion
  • entrypoints-integration-llm
  • entrypoints-integration-multimodal
  • entrypoints-integration-pooling
  • entrypoints-integration-responses-api
  • entrypoints-integration-speech_to_text
  • kernels-fla-ops-test-b200
  • kernels-mhc-test-b200
  • language-models-tests-granite-l4-compatibility
  • language-models-tests-hybrid ×2
  • language-models-tests-standard
  • multi-modal-models-standard-1-qwen2
  • multi-modal-models-standard-2-qwen3-gemma
  • multi-modal-models-standard-3-llava-qwen2-vl
  • multi-modal-models-standard-4-other-whisper
  • multi-modal-processor ×4
  • multi-modal-processor-cpu ×4
  • qwen4-exp-unit-tests
  • qwen4-exp-unit-tests-cpu
  • v1-spec-decode
Would add (today's rules do not run them) (0)

none

AMD mirrors: would skip (32)
  • basic-correctness ×2
  • basic-correctness-cpu-offload
  • basic-correctness-cumem
  • basic-correctness-prefetch-offload
  • basic-correctness-sleep-mode
  • basic-models-tests-initialization
  • basic-models-tests-other
  • benchmarks-cli-test
  • entrypoints-integration-api-server ×4
  • entrypoints-integration-api-server-generate
  • entrypoints-integration-api-server-openai-chat_completion
  • entrypoints-integration-api-server-openai-completion
  • entrypoints-integration-llm
  • entrypoints-integration-multimodal
  • entrypoints-integration-pooling
  • entrypoints-integration-responses-api
  • entrypoints-integration-speech_to_text
  • kernels-fla-ops-test-b200
  • kernels-mhc-test-b200
  • kimi-k3-unit-tests-b200
  • language-models-tests-granite-l4-compatibility
  • language-models-tests-hybrid ×2
  • language-models-tests-standard
  • multi-modal-models-standard-1-qwen2
  • multi-modal-models-standard-2-qwen3-gemma
  • multi-modal-models-standard-3-llava-qwen2-vl
  • multi-modal-models-standard-4-other-whisper
  • multi-modal-processor ×4
  • platform-tests
  • pytorch-compilation-passes-unit-tests
  • qwen4-exp-unit-tests
  • v1-spec-decode
AMD mirrors: would add (0)

none

2 changed files · base 4c4003bb5f · head e5fa725bbd · Python record: build 92561 at 4056c8ac1f · kernel record: table 4056c8a (build 92561), map 4056c8a · not counted: 10 build steps, 5 A100 steps the generator no longer emits, 2 optional steps the selector would also run

@benchislett
benchislett merged commit f590eb2 into vllm-project:main Oct 2, 2026
96 of 97 checks passed
@github-project-automation github-project-automation Bot moved this from Ready to Done in NVIDIA Oct 2, 2026
aijanai pushed a commit to aijanai/vllm that referenced this pull request Oct 5, 2026
_gemm_plans() returns {} on anything that is not sm_103, sm_100 or sm_90, so the
CuTe skinny GEMM never runs on sm_120 parts. The shipped plans are also keyed by
the TP-local (N, K) and were measured at TP=4, so even with the gate opened the
TP=1 and TP=2 shapes would miss the table.

Add benchmarks/kernels/qwen38fn_skinny_gemm/, a tuner adapted from
benchmark_k3_cutedsl_residual.py: parameterised over (N, K), no SM gate, driving
the installed shape_dynamic_skinny_gemm, scoring every candidate against
F.linear, and sharding the search across nodes with a merge step that selects the
global winners. Add the resulting sm_120 plan table and route _gemm_plans() to it.

Measured on an RTX 5090 over 31046 points: 58 shape/M points across TP=1 and
TP=2, every one with the weights rotated past the 96 MiB L2.

The gains sit where MTP puts the verification GEMM at batch size 1, since it runs
at M = (k + 1) * batch_size: M=2 at k=1 and M=4 at k=3. They come from the
baseline rather than from this kernel -- at M=1 cuBLAS dispatches a dedicated
GEMV, and from M=2 it pads a tiled GEMM whose minimum tile height the work never
fills. The shared-expert projection shows it plainly: F.linear jumps from 0.0068
to 0.0145 ms between M=1 and M=2 and stays flat to M=16, while the skinny kernel
climbs smoothly from 0.0042 to 0.0076.

The LM head is the exception at 1.06x, flat across M: it is bandwidth-bound, so
cuBLAS wastes nothing and there is nothing to win.

Complementary to vllm-project#59632, which adds plans for sm_121 only despite its SM12x
title, and to vllm-project#59753, which adds the sm_121 LM head.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Signed-off-by: Alberto Massidda <amassidda@nvidia.com>
michael-gpu pushed a commit to practical-inference/vllm-ampere-extended that referenced this pull request Oct 20, 2026
Signed-off-by: Stefano Castagnetta <scastagnetta@nvidia.com>
Co-authored-by: Benjamin Chislett <bchislett@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

nvidia performance Performance-related issues qwen Related to Qwen models ready ONLY add when PR is ready to merge/full CI is needed

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants