Skip to content

[Kernel][DSV4.1] Fuse MXFP8 wo_b GEMM with sequence-parallel reduce-scatter - #57428

Merged
ywang96 merged 15 commits into
vllm-project:mainfrom
gcanlin:perf/dsv41-gemm-rs-mxfp8-k3
Sep 22, 2026
Merged

ywang96 merged 15 commits into
vllm-project:mainfrom
gcanlin:perf/dsv41-gemm-rs-mxfp8-k3

Conversation

@gcanlin

@gcanlin gcanlin commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

Purpose

Under sequence parallel, DeepSeek-V4.1's wo_b runs as an MXFP8 row-parallel GEMM followed by a TP reduce-scatter: two kernels and one HBM round trip per layer. This PR fuses them with the SM100 GEMM-RS kernel. It builds on @gau-nernst's MXFP8 support for the Kimi-K3 GEMM-RS/AR kernel (the first five commits), moves that kernel to vllm/model_executor/kernels/linear/cute_dsl/gemm_rs_ar.py so both models share one workspace, init gate, warmup, test and benchmark, and binds V4.1's wo_b to it behind VLLM_ENABLE_GEMM_RS=1 (default off; this single flag replaces VLLM_KIMI_K3_GEMM_RS and gates the Kimi-K3 RS path too — VLLM_KIMI_K3_GEMM_AR is unchanged). The decoder layer skips its own sp_reduce_scatter when bound; RS zero-fills output rows beyond M for callers that pass an unpadded token count. Requires SM100, BF16 model dtype, sequence parallel, TP in 2..16, and FlashInfer CUTLASS or CuTe-DSL MXFP8 weights (what linear_backend=auto selects); anything else falls back with a warning.

No open PR fuses the V4.1 wo_b reduce-scatter: #57257 changes the SP all-gather side and #54151 is Kimi-K3 AG-GEMM.

Test Plan

pytest -v -s tests/kernels/test_gemm_rs_ar.py  # 2 GPUs: bf16, flashinfer_cutlass, flashinfer_cutedsl
torchrun --nproc-per-node=4 -- benchmarks/kernels/benchmark_gemm_rs_ar.py \
    --model deepseek_v41 --backend flashinfer_cutedsl --m 128 192 256 384 512 1024 2048 4096 8192 16384

Deploy

VLLM_ENABLE_GEMM_RS=1 vllm serve deepseek-ai/DeepSeek-V4.1-Flash --tensor-parallel-size 4 --enable-expert-parallel \
    --kernel-config '{"moe_backend":"deep_gemm_mega_moe"}' --attention-backend FLASHINFER_MLA_SPARSE_DSV41 \
    --kv-cache-dtype fp8 --tokenizer-mode deepseek_v41 --max-model-len 32768 --max-num-batched-tokens 16384 \
    --max-num-seqs 32 --no-enable-prefix-caching --seed 2026

Baseline: the same command with VLLM_ENABLE_GEMM_RS=0 (main's wo_b path).

Benchmark

vllm bench serve --backend vllm --model dsv41 --dataset-name random --random-input-len 8192 --random-output-len 1024 \
    --random-range-ratio 0 --ignore-eos --temperature 0 --seed 2026 --max-concurrency 32 --num-prompts 96 --num-warmups 8

GSM8K: lm-eval 0.4.12, gsm8k 5-shot chat (--apply_chat_template --fewshot_as_multiturn, thinking off), greedy, 1024 max tokens, all 1319 questions.

Test Result

4xB200, TP4 + EP4 with MegaMoE (sequence parallel), FlashInfer sparse MLA, fp8 KV, no speculative decoding. Kernel test: 3 passed.

Operator level (benchmark_gemm_rs_ar.py), TP4, wo_b shape N=5120 with K=2048 per rank, MXFP8 CuTe-DSL layout, activation quantization included in both candidates, CUDA graphs, median over 40 samples of the slowest rank. The baseline is the model's unfused path with vLLM's own collective dispatch (custom Lamport reduce-scatter for small messages, NCCL above its size cap):

M vLLM linear + sp_reduce_scatter (us) GEMM-RS (us) latency
128 26.6 29.2 +9.7%
512 36.9 38.9 +5.6%
1024 48.0 47.1 -2.0%
2048 100.9 63.5 -37.1%
4096 141.8 96.3 -32.1%
8192 226.8 162.8 -28.2%
16384 428.5 289.2 -32.5%

The fused kernel wins from about 1024 tokens; below that vLLM's Lamport reduce-scatter is faster than the fused kernel's fixed flag/exit handshake. Without speculative decoding, decode batches (at most max_num_seqs tokens) never reach the fused path and prefill chunks are large, so serving only sees the winning region.

Torch profiler, one 8192-token prefill step on rank 0: the 40 NCCL reduce-scatter launches (9.1 ms) and 40 wo_b GEMMs (2.8 ms) are replaced by 40 fused launches (6.6 ms); GPU busy time per step drops from 127.1 ms to 118.1 ms (-7.1%).

Online serving, bench serve with fixed-length random requests, mean of two runs per arm (arms off, on, on, off with a fresh server each), zero preemptions:

workload output tok/s total tok/s mean TTFT (ms) mean TPOT (ms)
8192 in / 1024 out, c16 1697.8 -> 1708.0 (+0.6%) 15279.8 -> 15371.6 (+0.6%) 1050.5 -> 1038.5 (-1.1%) 8.40 -> 8.36 (-0.5%)
8192 in / 1024 out, c32 2592.0 -> 2668.2 (+2.9%) 23328.4 -> 24014.2 (+2.9%) 1565.3 -> 1512.5 (-3.4%) 10.82 -> 10.51 (-2.9%)
1024 in / 2048 out, c32 3178.2 -> 3324.3 (+4.6%) 4767.4 -> 4986.5 (+4.6%) 771.4 -> 767.5 (-0.5%) 9.75 -> 9.27 (-5.0%)

Run-to-run spread: baseline runs differ by 0.5% (c16), 5.4% (c32) and 16% (1k/2k); fused runs by 0.1%, 1.7% and 7%. The c16 change is within drift; c32 is at its edge with every percentile improving (p99 TTFT 4760 -> 3610 ms); the 1k/2k row is not resolved by two runs.

GSM8K, same server config:

metric off on paired changes
flexible-extract 95.98% (+-0.54) 96.29% (+-0.52) 10 became correct, 6 became wrong
strict-match 79.45% (+-1.11) 78.70% (+-1.13) 30 became correct, 40 became wrong

Both within one standard error; 417 of 1319 generations are byte-identical, the rest differ in BF16 partial rounding order between the fused FP32 multicast reduction and NCCL's ring.

Not measured: speculative decoding (decode batches near 192 tokens would also take the fused path) and TP8. The off arm runs this branch with the flag unset rather than a separate main checkout; the earlier commits only touch Kimi-K3 and cute_utils, so V4.1's wo_b path is main's.

AI assistance was used (Claude Code) for the V4.1 integration, the kernel relocation and driving the benchmarks; every changed line and result was reviewed by the author.

🤖 Generated with Claude Code

gau-nernst and others added 6 commits September 17, 2026 07:48
Signed-off-by: Thien Tran <gau.nernst@yahoo.com.sg>
Signed-off-by: Thien Tran <gau.nernst@yahoo.com.sg>
Use torch._scaled_mm for the MXFP8 reference and require contiguous weights in the kernel wrapper.

Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Thien Tran <gau.nernst@yahoo.com.sg>
Select compatible backends before weight loading, adapt loaded weights and scales at callsites, and warm up each selected dtype. Exercise BF16 and both FlashInfer MXFP8 layouts against torch._scaled_mm.

Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Thien Tran <gau.nernst@yahoo.com.sg>
Fold the tensor launch path into apply and migrate the tracked benchmark to the projection-based API.

Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Thien Tran <gau.nernst@yahoo.com.sg>
…tter

Bind the DeepSeek-V4.1 sequence-parallel wo_b projection to the SM100
BF16/MXFP8 GEMM-RS kernel (opt-in via VLLM_DSV41_GEMM_RS=1) and move that
kernel out of the Kimi-K3 tree into
vllm/model_executor/kernels/linear/cute_dsl/gemm_rs_ar.py so both models
share one workspace, init gate, warmup, test and benchmark.

- DeepseekV4Attention gains bind_gemm_rs()/_wo_b_proj(); every _o_proj
  routes wo_b through it and the decoder layer skips its own
  sp_reduce_scatter when the fused path is bound. DSpark draft layers
  bind as well.
- GemmRsAr.apply zeroes RS output rows beyond M so callers that pass an
  unpadded token count see the same padding as sp_reduce_scatter.
- Benchmark gains --model deepseek_v41 and --backend
  flashinfer_cutlass|flashinfer_cutedsl presets.

Builds on gau-nernst's MXFP8 support in the Kimi-K3 GEMM-RS/AR kernel.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Canlin <canlinguosdu@gmail.com>
@mergify mergify Bot added ci/build deepseek Related to DeepSeek models performance Performance-related issues kimi k3 DSv4 DSv4.1 Related to DeepSeek-V4.1 models nvidia labels Sep 17, 2026
@mergify

mergify Bot commented Sep 17, 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, @gcanlin.

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

…r path

The online-MXFP8 baseline projections all-reduced inside RowParallelLinear,
so the reference partial was already summed and every collective candidate
failed the correctness check by the TP factor. Build them with
reduce_results=False like sequence-parallel layers do, label assertion
failures with the candidate and M, and add a candidate that runs the
model's unfused path (projection kernel + sp_reduce_scatter) so the table
compares against what the fused kernel actually replaces.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Canlin <canlinguosdu@gmail.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.

Signed-off-by: Canlin <canlinguosdu@gmail.com>
@gcanlin
gcanlin force-pushed the perf/dsv41-gemm-rs-mxfp8-k3 branch from 940b37c to 8f272b2 Compare September 21, 2026 04:24
@gcanlin

gcanlin commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

For the benchmark script, can you keep the bare GEMM and collective (but use the one dispatched by vLLM)? This establishes the optimization floor.

I will trigger CI after you rebase.

Done. Thanks!

@gau-nernst

Copy link
Copy Markdown
Contributor

/ci run

@github-actions

Copy link
Copy Markdown

✅ Triggered Buildkite CI #90137 for commit 8f272b25bf20.

@mergify mergify Bot removed the needs-rebase label Sep 21, 2026
@gcanlin

gcanlin commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

/ci retry

@github-actions

Copy link
Copy Markdown

❌ @gcanlin, A reviewer with write access must run /ci run, approve the PR, or add the ready label first.

@gau-nernst

Copy link
Copy Markdown
Contributor

/ci retry

@github-actions

Copy link
Copy Markdown

✅ Queued 1 failed job(s) for retry in Buildkite CI #90137.

Comment thread vllm/envs.py Outdated
… flag

Both models bind the same SM100 GEMM-RS kernel, so replace the per-model
VLLM_KIMI_K3_GEMM_RS and VLLM_DSV41_GEMM_RS switches (both default off)
with a single VLLM_ENABLE_GEMM_RS. VLLM_KIMI_K3_GEMM_AR is unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Canlin <canlinguosdu@gmail.com>
@github-project-automation github-project-automation Bot moved this to Ready in NVIDIA Sep 22, 2026
@jeejeelee

Copy link
Copy Markdown
Member

/ci run

@jeejeelee
jeejeelee enabled auto-merge (squash) September 22, 2026 00:42
@github-actions github-actions Bot added the ready ONLY add when PR is ready to merge/full CI is needed label Sep 22, 2026
@gau-nernst

Copy link
Copy Markdown
Contributor

/ci run

@github-actions

Copy link
Copy Markdown

❌ This PR is 8 commits behind upstream main. Your branch must contain every commit currently on upstream main. No new CI build was started. Merge or rebase onto the latest main, then rerun /ci run. To test this branch at your own risk, use /ci run --allow-stale.

@gau-nernst

Copy link
Copy Markdown
Contributor

/ci run

@github-actions

Copy link
Copy Markdown

✅ Triggered Buildkite CI #90359 for commit b95aff62d624.

@gcanlin

gcanlin commented Sep 22, 2026

Copy link
Copy Markdown
Contributor Author

Test with AIPerf + Agentx datasets:

metric nightly + GEMM-RS (#57428) nightly 3df4ae1 ref 09-15 (PR56568) GEMM-RS vs nightly
output tok/s 2,551.2 2,494.9 2,376.2 +2.3 %
input tok/s 339,620 330,913 312,780 +2.6 %
total tok/s 342,171 333,408 315,157 +2.6 %
per-user decode avg / p50 (tok/s/user) 137.5 / 124.9 124.6 / 122.0 112.4 / 103.4 +10 % / +2.4 %
TTFT avg / p50 / p99 (ms) 360 / 188 / 4,008 535 / 219 / 5,140 634 / 252 / 8,342 -33 % / -14 % / -22 %
ITL avg / p50 / p99 (ms) 8.76 / 8.01 / 26.8 10.72 / 8.19 / 62.7 10.97 / 9.67 / 39.6 -18 % / -2 % / -57 %
request latency avg / p50 (ms) 8,752 / 3,542 9,622 / 4,052 10,961 / 4,431 -9 % / -13 %
requests completed 3,083 3,023 2,901 +2.0 %
effective concurrency avg 21.9 23.7 25.9
ISL / OSL avg (tokens) 135,496 / 1,018 134,642 / 1,015 132,616 / 1,007
theoretical prefix-cache hit 97.02 % 96.99 % 97.02 %

@gau-nernst

Copy link
Copy Markdown
Contributor

/ci retry

@github-actions

Copy link
Copy Markdown

✅ Queued 2 failed job(s) for retry in Buildkite CI #90359.

@ywang96
ywang96 disabled auto-merge September 22, 2026 08:07
@ywang96
ywang96 merged commit f92b78f into vllm-project:main Sep 22, 2026
179 of 182 checks passed
@github-project-automation github-project-automation Bot moved this from Ready to Done in NVIDIA Sep 22, 2026
vrdn-23 added a commit to vrdn-23/vllm that referenced this pull request Oct 8, 2026
…nflicts

Took the branch's vllm/envs.py wholesale: main's legacy TYPE_CHECKING block
and environment_variables dict are already superseded by the pydantic
*Settings models here. Then ported main's semantic delta from the 57
main-side commits that touched vllm/envs.py since 03a8d0b:

  vllm-project#52431 VLLM_USE_MODELSCOPE: description (bool already accepts "1")
  vllm-project#52216 prefix_cache_retention_interval promoted to an arg (see vllm-project#55353)
  vllm-project#48484 + VLLM_REPLICATE_EMBED
  vllm-project#52633 + VLLM_CPU_CI_ENV
  vllm-project#52182 - VLLM_TEST_FORCE_FP8_MARLIN
  vllm-project#52523 VLLM_API_KEY not a compile factor (branch already marked it)
  vllm-project#52998 VLLM_ALLREDUCE_USE_FLASHINFER default -> True
  vllm-project#53040 + VLLM_DISABLE_DSV4_MEGAMOE_SHARED_EXPERT_FUSION
  vllm-project#53053 + VLLM_KIMI_K3_GEMM_AR
  vllm-project#50501 + VLLM_XPU_INC_WNA16_BACKEND (case-sensitive Literal)
  vllm-project#52018 + VLLM_B12X_MOE_FP4_FORCE_A16
  vllm-project#43018 + VLLM_KV_OFFLOAD_MAX_BATCH_DESCRIPTORS
  vllm-project#51718 VLLM_KV_CACHE_LAYOUT Literal widened to 8 layouts + description
  vllm-project#51896 + VLLM_MAX_MEDIA_DOWNLOAD_SIZE_MB
  vllm-project#53219 VLLM_ENABLE_COHERE_API: description
  vllm-project#53559 - VLLM_TRITON_ATTN_USE_TD shim (already absent on the branch)
  vllm-project#53561 VLLM_MAX_AUDIO_CLIP_FILESIZE_MB: description
  vllm-project#53378 VLLM_ELASTIC_EP_SCALE_UP_LAUNCH not a compile factor
  vllm-project#53141 VLLM_ROCM_USE_AITER_FP4_ASM_GEMM removed; restored by vllm-project#57055
  vllm-project#54160 + VLLM_ENABLE_HPC_OPS
  vllm-project#53536 + VLLM_XPU_FORCE_N_CONTIG_WEIGHT
  vllm-project#53576 + VLLM_ALLREDUCE_USE_FLASHINFER_PCIE_IPC
  vllm-project#54632 + VLLM_MAX_EMBED_DECODE_BYTES (not a compile factor)
  vllm-project#54579 + VLLM_ENABLE_SCALE_OUT_ENDPOINTS; removed by vllm-project#55176 (net zero)
  vllm-project#53491 VLLM_GPU_SYNC_CHECK: description
  vllm-project#54371 + VLLM_PLE_CPU_OFFLOAD; reworked vllm-project#56512, dropped vllm-project#57937 (net zero)
  vllm-project#48247 VLLM_ROCM_USE_AITER_CUSTOM_AR: description
  vllm-project#55353 - VLLM_MM_HASHER_ALGORITHM (+ _lower_mm_hasher),
         - VLLM_PREFIX_CACHE_RETENTION_INTERVAL
  vllm-project#54985 - VLLM_NIXL_EP_MAX_NUM_RANKS
  vllm-project#56512 VLLM_PLE_CPU_OFFLOAD rework (net zero, see vllm-project#54371)
  vllm-project#55176 - VLLM_ENABLE_SCALE_OUT_ENDPOINTS (net zero, see vllm-project#54579)
  vllm-project#53940 + VLLM_ROCM_USE_AITER_MOE_SITUV2 (case-insensitive Literal)
  vllm-project#41074 + VLLM_SKIP_VERSION_SUFFIX (not a compile factor)
  vllm-project#48599 - envs.validate_environ (moved to Platform.validate_environ,
         which reads the branch's environment_variables shim)
  vllm-project#52136 pydocstyle docstring restyle of legacy helpers (no-op here)
  vllm-project#57212 docstring rejoin of legacy helpers (no-op here)
  vllm-project#56913 + FORCE_COLOR (alias, non-"0" is True, not a compile factor)
  vllm-project#57055 VLLM_ROCM_USE_AITER_FP4_ASM_GEMM restored (net zero, see vllm-project#53141)
  vllm-project#57142 shm buffer name autogen -> new _generate_shm_name() (<=30 chars)
  vllm-project#56685 VLLM_HUMMING_INPUT_QUANT_CONFIG: description
  vllm-project#57937 - VLLM_PLE_CPU_OFFLOAD (net zero, see vllm-project#54371)
  vllm-project#50814 + VLLM_MXFP4_EMULATION_DEQUANT_AT_LOAD
  vllm-project#57428 + VLLM_ENABLE_GEMM_RS, - VLLM_KIMI_K3_GEMM_RS
  vllm-project#51600 - VLLM_XPU_ENABLE_XPU_GRAPH
  vllm-project#58582 + VLLM_TRITON_JIT_WARMUP_NUM_THREADS (not a compile factor)
  vllm-project#55145 + VLLM_XPU_PP_MICROBATCH
  vllm-project#58201 VLLM_ROCM_USE_AITER_MOE_SITUV2 6-way choice,
         - VLLM_ROCM_USE_AITER_MOE_SITUV2_A8W4
  vllm-project#58919 + VLLM_MOONCAKE_CONNECTOR_TIMEOUT
  vllm-project#56861 + VLLM_ROCM_AITER_MLA_DCP_VERIFY (case-insensitive Literal)
  vllm-project#53492 + VLLM_ROCM_USE_AITER_TRITON_SPARSE_MLA
  vllm-project#57991 VLLM_DEEPEP_V2_ALLOW_HYBRID_MODE default True (branch already
         True) + description
  vllm-project#58819 + VLLM_ROCM_USE_AITER_MOE_A4W4_DSV4 (tri-state, bool(int(v)))
  vllm-project#50300 + VLLM_CHAT_TEMPLATE_RENDER_TIMEOUT
  vllm-project#54024 VLLM_CPU_INT4_W4A8: description
  vllm-project#59781 - VLLM_ENGINE_ITERATION_TIMEOUT_S
  vllm-project#57995 + VLLM_FLASHINFER_MOE_A2A_LOW_PRECISION_COMBINE
  vllm-project#59965 VLLM_ROCM_AITER_MLA_DCP_VERIFY default route (final "auto")

Also VLLM_SSM_CONV_STATE_LAYOUT's description follows vllm-project#51718's layout rename.

Parity: 311 branch fields vs 311 main runtime entries, no difference in
either direction. Every var's value matches main's lambdas in an empty
environment and under 15 override scenarios, except case normalization of
the two lowercased ROCm choices (both callers already call .lower()).

Intentionally not ported: main's new consumer tests that match
env_with_choices' error text (test_invalid_backend_value_raises,
test_dcp_verify_env_rejects_unknown_route, and the ROCm-gated
test_aiter_moe_situv2_rejects_unknown_activation) fail on this branch:
pydantic's ValidationError names the field, not the env var. Invalid values
are still rejected. Tracked as a branch-wide follow-up.

Other conflicts and merge fallout:
- requirements/docs.txt, requirements/test/{cpu,cuda,rocm,xpu}.txt: took
  main's lockfiles and regenerated them with the pip-compile pre-commit
  hooks; they differ from main only by the branch's pydantic-settings lines.
- tests/test_envs_pydantic.py: dropped the mm-hasher test, pointed the
  validate_environ tests at Platform.validate_environ, updated the shm-name
  prefix assertion.
- check-test-tethering (new on main): wired test_envs_pydantic.py into the
  CPU params/env job; allowlisted tests/docs/test_env_vars_gen.py, which
  cannot run from a CI job's tests/ cwd (tests/docs shadows docs/).

Tests: pytest tests/test_envs.py tests/test_envs_pydantic.py
tests/docs/test_env_vars_gen.py -> 55 passed; pre-commit run --files on all
changed files -> clean.

AI assistance (Claude) was used for this merge resolution.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Vinay Damodaran <vrdn@hey.com>
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 DSv4 DSv4.1 Related to DeepSeek-V4.1 models k3 kimi nvidia performance Performance-related issues 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