Skip to content

[Kernel][Build] Bump DeepGEMM to the 26/09/30 release and adapt the sparse indexer - #61149

Merged
vllm-bot merged 2 commits into
vllm-project:mainfrom
zyongye:deepgemm-bump-0930
Oct 13, 2026
Merged

vllm-bot merged 2 commits into
vllm-project:mainfrom
zyongye:deepgemm-bump-0930

Conversation

@zyongye

@zyongye zyongye commented Oct 12, 2026 •

Copy link
Copy Markdown
Member

Overview

Bump the vendored DeepGEMM pin to the tip of vllm-project/DeepGEMM dev (6ce1c5e): upstream's 26/09/30 public release (vllm-project/DeepGEMM#30, deepseek-ai/DeepGEMM#462) plus vendoring import fixes (vllm-project/DeepGEMM#31, vllm-project/DeepGEMM#32) and a relaxed paged sparse MQA page-stride guard (vllm-project/DeepGEMM#33). Adapt vLLM to the breaking MQA-logits API.

Claims

  • vLLM builds, vendors and imports DeepGEMM 2.8.1.
  • The DSA sparse indexer (DeepSeek-V3.2, GLM-5.x, GLM-5.3, DeepSeek-V4/V4.1, HY-V4) keeps working on SM90/SM100/SM120 with the new MQA-logits API.
  • On SM100 the FP8 indexer cache stays numerically identical: same FP8 values and the same per-token power-of-two scale, now stored as four packed UE8M0 bytes (a valid MXFP8 encoding) because SM100 MQA kernels only take MX scales.

Breaking changes in DeepGEMM 1e1842a → 6ce1c5e

Change vLLM impact Handling
deep_gemm/__init__.py imports a new deep_gemm/locality_domain.py not vendored → import vllm.third_party.deep_gemm fails install it in deepgemm.cmake
_C.py registered the locality-domain shim as deep_gemm._C.locality_domain relative import fails when vendored as vllm.third_party.deep_gemm, and vLLM then silently treats DeepGEMM as unavailable fixed in vllm-project/DeepGEMM#31 (in the pin); tools/check_wheel_deepgemm.py now imports the package so CI catches this
The C++ MLOPart bridge imported deep_gemm.utils.mlopart and the epilogue module was named deep_gemm.epilogue localize() (not called by vLLM) would fail when vendored fixed in vllm-project/DeepGEMM#32 (in the pin): _C.py passes its __package__ to the new init(library_root_path, python_package_name)
fp8_fp4_paged_sparse_mqa_logits page-stride guard lowered from 512 to 256 B (vllm-project/DeepGEMM#33) DeepSeek-V4.1's sparse-logits block stride stays 512-aligned, which still satisfies it none
fp8_mqa_logits, fp8_paged_mqa_logits, fp8_gemm_nt_skip_head_mid, get_bf16_mega_gate_config removed unused by vLLM none
clean_logits / logits_dtype removed from fp8_fp4_(paged_)mqa_logits; max_seqlen_k now required (> 0) wrapper signatures vllm/utils/deep_gemm.py; prefill chunk metadata carries a host max_seqlen_k bound (no device sync)
fp8_fp4_mqa_logits returns compressed logits [M, max_seqlen_k] (row i starts at K position ks[i]) on every arch prefill top-k / candidate-block / DCP consumers indexed absolute columns consumers run on row starts 0, ends ke - ks; output indices are unchanged (already relative to ks)
SM100 MQA kernels take only MXFP8/MXFP4 with packed UE8M0 scales, BF16 weights, return BF16 logits; paged is varlen-only FP8 indexer (fp32 per-token scale, scale folded into fp32 weights) rejected see below
bf16_mega_gate default scoring_func → 'sqrtsoftplus' vLLM always passes it none
FP8×FP4 / BF16 Mega MoE assert DeepGEMM's SM count equals the device's only DBO + DeepEP-HT lowers it (ubatch_utils.py), not combined with Mega MoE today none (noted)
Epilogue classes (epilogue= kwarg), locality domains, shutdown additive none

SM100 FP8 indexer

Every CUDA FP8 indexer writer already stores an exact power-of-two scale 2^ceil(log2(amax/448)) per 128-wide token. On SM100 (mqa_logits_use_mx_scales()) the writers store that scale's exponent as four equal UE8M0 bytes instead of the fp32, which is an exact MXFP8 encoding of the same values (same cache size, same FP8 bytes):

Writer Models
C++ indexer_k_quant_and_cache (scale_fmt="mxfp8") DeepSeek-V2-class Indexer (V3.2 via that class, DeepSeek MTP), HY-V4, DeepSeek-V3.2 under PCP
DeepSeek-V3.2 fused norm/RoPE (_fused_norm_rope_kernel) DeepSeek-V3.2, GLM-5/5.2 (GlmMoeDsa), Dots3Note, V3.2 MTP
DeepSeek-V4 compressor (Triton; the CuTeDSL compressor is head-512 MLA only) DeepSeek-V4
DeepSeek-V4.1 indexer_k_norm_rope_store DeepSeek-V4.1
GLM-5.3 kpool prefill compress + decode update GLM-5.3

Readers need no change: cp_gather_indexer_k_quant_cache (prefill, all of the above) and the DCP/PCP gathers copy the 4 scale bytes verbatim, and DeepGEMM reads the paged cache directly. The 512-wide MLA caches, MXFP4 caches, and the MiniMax-M3 / Qwen4 indexers are untouched.

FP8 Q keeps folding its per-(token, head) power-of-two scale into weights; mqa_logits_mx_inputs passes unit MX Q scales (exact, since relu(s·x) = s·relu(x) for s > 0) and casts weights to BF16. The BF16 logits are upcast to FP32 for the existing FP32-only top-k kernels (as_fp32_logits); routing BF16 logits straight to a BF16-capable top-k (DeepSelect / LiteTopK, #59921/#59922) is a follow-up.

SM90 and SM120 keep the FP8 format, FP32 weights and FP32 logits. Consequently the FP8 indexer cache's scale bytes now differ between SM100 and other architectures: moving that KV cache between SM100 and non-SM100 nodes (cross-generation PD disaggregation, shared KV offload) is not supported for these models. Same-architecture setups are unaffected.

Validation

All on 4x GB200 (SM100), CUDA 13, torch 2.13; baseline = main with the old pin 1e1842a, same build otherwise.

GLM-5.3-Flash (FP8, TP4 + EP, MTP=3, --max-model-len 131072 --max-num-batched-tokens 131072 --kv-cache-dtype fp8_e4m3 --reasoning-parser glm47, reasoning_effort=low), on the rebased branch vs main bfa7e5b. MRCR prompts are far longer than the indexer's coverage (2048 pools × 4 = 8K tokens), so they exercise top-k selection; long prefills also engage the TP prefill row sharding from #54951 (confirmed with a temporary log, e.g. 129K-row steps split [47458, 18791, 42577, 19957] across ranks):

main + 1e1842a this PR
MRCR, 60 samples of 16K–32K tokens (tests/evals/mrcr), match ratio (2 / 4 / 8 needles) 0.794 (0.823 / 0.798 / 0.760) 0.810 (0.904 / 0.791 / 0.735)
MRCR, 24 samples of 57K–66K tokens, match ratio 0.578 (4 runs hit max_tokens) 0.665
GSM8K (1319, 5-shot) 90.8% 91.6%
Serving, 4 runs of 128 × (4096 in / 512 out) at concurrency 32, output tok/s 1492 / 1544 / 1491 / 1471 1484 / 1476 / 1337 / 1501
Median ITL 19.85 ms 19.9 ms

GLM-5.3 serving throughput tracks the MTP acceptance length, which varies per run (the 1337 tok/s run had 1.31 vs ~1.7–1.8 elsewhere); runs with matching acceptance agree (1476 @ 1.70 vs 1471 @ 1.71).

DeepSeek-V4-Flash (before the rebase onto current main; baseline 26a488a), TP2 + EP, --kv-cache-dtype fp8 --attention_config.indexer_kv_dtype=fp8, --max-model-len 8192; GSM8K via tests/evals/gsm8k/gsm8k_eval.py (1319 questions, 5-shot, 2 rounds). GSM8K prompts fit inside the indexer's coverage (512 × 4 = 2K tokens), so this checks the SM100 path end to end rather than top-k selection:

indexer cache main + 1e1842a this PR
fp8 95.5% / 94.6% 94.8% / 94.6%
mxfp4 94.9% 94.7%

Serving — same server, vllm bench serve --dataset-name random --random-input-len 4096 --random-output-len 512 --num-prompts 256 --max-concurrency 64 --ignore-eos, one warm-up + 3 runs each, sequential on the same GPUs:

output tok/s mean TPOT median ITL
main + 1e1842a 2968 (2962–2974) 18.73 ms 12.80 ms
this PR 2946 (2928–2958) 18.89 ms 12.94 ms

The ~0.8% gap matches the two extra small kernels per indexer layer on SM100 (weights → BF16, row-aware logits → FP32, ~3 µs together). Feeding BF16 straight from the Q producers and into a BF16 top-k removes both.

Unit tests (GB200):

pytest tests/kernels/attention/test_deepgemm_attention.py                      # 7 passed (SM100 MX + varlen paths)
pytest tests/model_executor/layers/test_mla_short_prefill_indexer.py \
  tests/v1/attention/test_indexer_*.py tests/v1/attention/test_sparse_indexer_decode_seq_lens.py \
  tests/v1/attention/test_glm5next_indexer_block_sizes.py tests/v1/attention/test_kpool_*.py \
  tests/models/glm5next/test_sparse_indexer_topk_dispatch.py tests/v1/cudagraph/test_breakable_cudagraph.py \
  tests/kernels/test_compressor_kv_cache.py tests/kernels/test_fused_indexer_q_rope_quant.py \
  tests/kernels/test_kpool_decode_update_batched.py tests/v1/attention/test_rocm_paged_mxfp4_indexer_plan.py
                                                                               # 558 passed, 35 skipped
pytest tests/kernels/test_fused_deepseek_v32_norm_rope.py                      # 222 passed
pytest tests/kernels/moe/test_deepgemm.py tests/kernels/moe/test_batched_deepgemm.py \
  tests/models/test_deepseek_v4_mega_moe.py tests/kernels/mhc/test_mhc_kernels.py \
  tests/model_executor/test_deep_gemm_warmup.py                                 # all passed
pytest tests/kernels/quantization/test_block_fp8.py tests/kernels/moe/test_block_fp8.py -k deep_gemm
                                                                               # 100 passed, 169 skipped
python tools/check_wheel_deepgemm.py                                           # now also imports the package

tests/kernels/moe/test_silu_mul_fp8_quant_deep_gemm.py fails on this GB200 with unspecified launch failure identically on main + 1e1842a (pre-existing, unrelated). Not run (no hardware): SM90 and SM120; CI's H100 DeepGEMM job covers the SM90 attention tests. CI has no SM100 DeepGEMM job, so the MX path is validated only by the runs above.

Pin delta 1448881 → 6ce1c5e: the end-to-end runs above used 1448881. The two later commits change no kernel math (vllm-project/DeepGEMM#33 only lowers the host and device stride asserts, vllm-project/DeepGEMM#32 only module naming). Re-checked on 6ce1c5e on GB200 (the GPUs were shared with another job, so no end-to-end rerun):

python tools/check_wheel_deepgemm.py                                           # imports; init takes the package name
pytest tests/kernels/attention/test_deepgemm_attention.py \
  tests/model_executor/layers/test_mla_short_prefill_indexer.py tests/model_executor/test_deep_gemm_warmup.py
                                                                               # 42 passed, 7 skipped (need pip deep_gemm)
pytest tests/kernels/moe/test_deepgemm.py tests/kernels/quantization/test_block_fp8.py -k "deep_gemm or deepgemm"
                                                                               # 125 passed, 28 skipped
# DeepGEMM's tests/test_attention.py::test_sparse_mqa_logits, paged cases only, on the vendored build:
# 60/60 passed with 64-token pages of 8448 B (MXFP8) / 4352 B (MXFP4), 256- but not 512-aligned

Details


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

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

@cleonard530

cleonard530 commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Hi @zyongye, I was looking at this as well for my own PR, (#59482), and came to a lot of the same conclusions. One thing that I also found as an issue in DeepGEMM was in csrc/runtime/mlopart.hpp, where it assumed DeepGEMM was pulled in as deep_gemm.utils.mlopart. As you've noticed, that's not always true (e.g. here is can be vllm.third_party.deep_gemm), but your updates haven't addressed this yet. Was this not an issue for you at runtime? If it is, we may need to update DeepGEMM one more time before this can be merged. I have a fix in this PR draft, vllm-project/DeepGEMM#32. I will open this once I test it a little more thoroughly. This is open now.

cc @Harry-Chen

@mergify

mergify Bot commented Oct 12, 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, @zyongye.

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

@zyongye

zyongye commented Oct 12, 2026

Copy link
Copy Markdown
Member Author

Hi @zyongye, I was looking at this as well for my own PR, (#59482), and came to a lot of the same conclusions. One thing that I also found as an issue in DeepGEMM was in csrc/runtime/mlopart.hpp, where it assumed DeepGEMM was pulled in as deep_gemm.utils.mlopart. As you've noticed, that's not always true (e.g. here is can be vllm.third_party.deep_gemm), but your updates haven't addressed this yet. Was this not an issue for you at runtime? If it is, we may need to update DeepGEMM one more time before this can be merged. I have a fix in this PR draft, vllm-project/DeepGEMM#32. I will open this once I test it a little more thoroughly. This is open now.

cc @Harry-Chen

mlopart is never used in tree. But thank you for the catch. We should fix it. Thanks

@zyongye
zyongye force-pushed the deepgemm-bump-0930 branch from fdbba7a to 8988e5e Compare October 12, 2026 21:43
@mergify mergify Bot removed the needs-rebase label Oct 12, 2026
…parse indexer

Pin vllm-project/DeepGEMM dev 6ce1c5e: the upstream 26/09/30 release
(vllm-project/DeepGEMM#30), the vendoring import fixes
vllm-project/DeepGEMM#31 and vllm-project/DeepGEMM#32, and the paged
sparse MQA page-stride relaxation vllm-project/DeepGEMM#33. Vendor the
new deep_gemm/locality_domain.py. DeepGEMM#32 makes the MLOPart bridge
and the epilogue module use the package's own name, so they also work
when vendored as vllm.third_party.deep_gemm. DeepGEMM#33 lowers the
page-stride guard from 512 to 256 bytes; the DeepSeek-V4.1
sparse-logits block stride stays 512-aligned, which still satisfies it.

The MQA logits API changed: clean_logits/logits_dtype are gone,
max_seqlen_k is required, prefill logits are compressed to each row's
K span, and SM100 takes only MXFP8/MXFP4 with BF16 weights and returns
BF16 logits. On SM100 the FP8 indexer K writers store their existing
power-of-two scale as four packed UE8M0 bytes (a lossless MXFP8
encoding), FP8 Q passes unit MX scales with its scale still folded into
the weights, and BF16 logits are upcast row-aware for the FP32 top-k
kernels. check_wheel_deepgemm.py now imports the vendored package.

build_deepgemm_C.py passes --force to setup.py build_ext: DeepGEMM's
setup.py only tracks python_api.cpp, so an incremental build after a
header-only pin change kept a stale extension. CMake already decides
when this step reruns.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Yongye Zhu <zyy1102000@gmail.com>
@zyongye
zyongye force-pushed the deepgemm-bump-0930 branch from 8988e5e to 6f63ba7 Compare October 13, 2026 02:54
@zyongye

zyongye commented Oct 13, 2026

Copy link
Copy Markdown
Member Author

/ci run

@github-actions

Copy link
Copy Markdown

✅ Triggered Buildkite CI #94360 for commit 16470a135ecd.

@vllm-agent

Copy link
Copy Markdown
Collaborator

CI selector (shadow): no narrower answer, every step runs: 195 test steps (271 jobs) instead of 195 (271 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 195 (271) 195 (271) 0 (0) 0 (0)
AMD mirrors 163 (227) 163 (227) 0 (0) 0 (0)

Why: fail-open: tools/build_deepgemm_C.py is a docker-image build input (docker/Dockerfile COPY); the CI image is rebuilt from it; running everything

Today's rules run everything for this diff.

Selector would run (195)
  • amd-fp8-moe-kernels-mi355
  • amd-kernels-mi355
  • amd-lm-eval-small-models-harness
  • amd-native-quantization-kernels-mi355
  • amd-qwen3-next-mtp-async-eplb-accuracy
  • arm-cpu-test ×3
  • ascend-npu-test
  • async-engine-inputs-utils-worker
  • 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-extra-initialization ×14
  • basic-models-tests-initialization
  • basic-models-tests-other
  • batch-invariance-b200
  • batch-invariance-h100
  • benchmarks-cli-test
  • cpu-distributed-tests-dp-tp
  • cpu-distributed-tests-pp-tp
  • cpu-kernel-tests ×2
  • cpu-language-generation-and-pooling-model-tests ×3
  • cpu-multi-modal-model-tests-n ×4
  • cpu-multimodal-config
  • cpu-params-env-tokenizers-parser
  • cpu-quantization-model-tests
  • cpu-qwen2-5-vl-multimodal-tests
  • cpu-reasoning-renderers
  • cpu-spec-decode-tests
  • cpu-tool-parsers
  • crcr-report
  • crosslayer-kv-layout-distributed-nixlconnector-pd-accuracy-tests-4-gpus
  • cudagraph
  • deepseek-v4-kernel-test-b200
  • deepseek-v4-kernel-test-h100
  • distributed-comm-ops
  • distributed-compile-comm-4-gpus
  • distributed-compile-rpc-tests-2-gpus
  • distributed-compile-unit-tests-2xh100
  • distributed-dp-tests-2-gpus
  • distributed-dp-tests-4-gpus
  • distributed-flashinfer-nixlconnector-pd-accuracy-4-gpus
  • distributed-model-tests-2-gpus ×3
  • distributed-mooncakeconnector-pd-accuracy-4-gpus
  • distributed-nixlconnector-pd-accuracy-4-gpus
  • distributed-torchrun-examples-4-gpus
  • distributed-torchrun-shutdown-tests-2-gpus
  • docker-build-metadata
  • dp-ep-distributed-nixlconnector-pd-accuracy-tests-4-gpus
  • dsv41-mono-decode-numerics-amd
  • e2e-core-1-gpu
  • e2e-core-large-memory
  • e2e-scheduling-1-gpu
  • e2e-scheduling-accuracy-1-gpu
  • elastic-ep-scaling-test
  • engine
  • engine-1-gpu
  • 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
  • entrypoints-unit-tests
  • eplb-algorithm
  • eplb-execution
  • examples
  • extract-hidden-states-integration
  • extract-hidden-states-integration-2-gpus
  • fault-tolerance-e2e-2xh100
  • fusion-and-compile-unit-tests-2xb200
  • fusion-e2e-config-sweep-h100
  • fusion-e2e-quick-h100
  • fusion-e2e-tp2-ar-rms-config-sweep-h100
  • fusion-e2e-tp2-ar-rms-dynamo-partition-amd
  • fusion-e2e-tp2-ar-rms-inductor-partition-amd
  • fusion-e2e-tp2-b200
  • fusion-e2e-tp2-quick-h100
  • gemm-rs-ar-2xb200
  • glm5next-unit-tests
  • hybrid-ssm-nixlconnector-pd-accuracy-tests-4-gpus
  • hybrid-ssm-nixlconnector-pd-prefix-cache-2-gpus
  • inkling-unit-tests-b200
  • intel-hpu-test
  • jit-monitor-no-runtime-jit
  • kernels-attention-diffkv-test-h100
  • kernels-attention-test ×7
  • kernels-b200 ×3
  • kernels-core-operation-test ×3
  • kernels-deepgemm-test-h100
  • kernels-fla-ops-test-b200
  • kernels-flashmla-test-h100
  • kernels-fusedmoe-layer-test-2-b200s
  • kernels-fusedmoe-layer-test-2-h100s
  • kernels-helion-test ×5
  • kernels-mamba-test
  • kernels-mhc-test-b200
  • kernels-minimax-m3-attention-b200
  • kernels-minimax-reduce-rms-test-2-gpus
  • kernels-moe-test ×5
  • kernels-quantization-test ×6
  • kernels-root-misc-test-b200
  • kimi-k3-prefix-cache-4xb200 ×2
  • kimi-k3-unit-tests-b200
  • kv-offload-large
  • kv-offload-medium
  • kv-offload-small
  • language-models-tests-extra-standard ×2
  • language-models-tests-granite-l4-compatibility
  • language-models-tests-hybrid ×2
  • language-models-tests-standard
  • lm-eval-dspark-watermark-2xh100
  • lm-eval-small-models
  • lm-eval-turboquant-k3v4nc
  • lm-eval-turboquant-k8v4
  • lm-eval-turboquant-t3nc
  • lm-eval-turboquant-t4nc
  • lm-eval-watermarking
  • lora ×4
  • lora-tp-distributed ×4
  • metrics-tracing-2-gpus
  • model-executor
  • mooncake-ec-tcp-e2e-2-gpus
  • mrcr-eval-small-models
  • multi-modal-accuracy-eval-small-models
  • 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
  • multiconnector-nixl-offloading-pd-accuracy-2-gpus
  • multiconnector-nixl-offloading-pd-edge-cases-2-gpus
  • nixlconnector-pd-edge-cases-2-gpus
  • openai-api-correctness
  • pipeline-context-parallelism-4-gpus
  • platform-tests
  • plugin-tests-2-gpus
  • push-nixlconnector-pp-prefill-pd-accuracy-4-gpus
  • pytorch-compilation-dynamic-shapes
  • pytorch-compilation-passes-unit-tests
  • pytorch-compilation-unit-tests
  • pytorch-compilation-unit-tests-h100
  • pytorch-fullgraph-cudagraph-l4-compatibility
  • pytorch-fullgraph-test
  • pytorch-nightly-dependency-override-check
  • quantization ×4
  • quantized-fusions
  • quantized-models-test
  • quantized-moe-test-b200
  • qwen4-exp-sp-tests-4-gpus
  • qwen4-exp-unit-tests
  • qwen4-exp-unit-tests-cpu
  • ray-dependency-compatibility-check
  • rayexecutorv2-4-gpus
  • regression
  • replayssm-e2e
  • rl-entrypoints-tests
  • rust-frontend-cargo-style-clippy
  • rust-frontend-cargo-tests
  • rust-frontend-core-correctness
  • rust-frontend-distributed
  • rust-frontend-openai-coverage
  • rust-frontend-serve-admin-coverage
  • rust-frontend-tool-use
  • samplers-multimodal-beam-search
  • samplers-test
  • scale-out-ec-e2e-2-gpus
  • sharded-rdt-weight-transfer
  • spec-decode-draft-model ×4
  • spec-decode-eagle-1-deepseek-qwen
  • spec-decode-eagle-2-llama3-qwen-vl-other
  • spec-decode-mtp-deepseek-mimo
  • spec-decode-mtp-gemma4
  • spec-decode-mtp-qwen3-5
  • spec-decode-ngram-suffix
  • spec-decode-speculators
  • torch-stable-abi-audit
  • v1-attention-b200 ×2
  • v1-attention-h100-mi300 ×2
  • v1-core
  • v1-executor-worker
  • v1-kv-connectors ×4
  • v1-kv-offload
  • v1-logits-oracle
  • v1-metrics-lmeval
  • v1-others-cpu
  • v1-sample
  • v1-spec-decode
  • vllm-ir-tests
Would skip (today's rules run them) (0)

none

Would add (today's rules do not run them) (0)

none

AMD mirrors: would skip (0)

none

AMD mirrors: would add (0)

none

23 changed files · base c077dc663d · head 16470a135e · Python record: build 93039 at 1e5d0ea888 · kernel record: table fe341a5 (build 94315), map fe341a5 · not counted: 11 build steps, 5 A100 steps the generator no longer emits, 139 optional steps the selector would also run

@zyongye

zyongye commented Oct 13, 2026

Copy link
Copy Markdown
Member Author

/ci retry

@github-actions

Copy link
Copy Markdown

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

@vllm-bot
vllm-bot merged commit a6009ea into vllm-project:main Oct 13, 2026
474 of 477 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in AMD Oct 13, 2026
AndreasKaratzas pushed a commit to AndreasKaratzas/vllm that referenced this pull request Oct 16, 2026
…parse indexer (vllm-project#61149)

Signed-off-by: Yongye Zhu <zyy1102000@gmail.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Signed-off-by: Andreas Karatzas <AndreasKaratzas@users.noreply.github.com>
michael-gpu pushed a commit to practical-inference/vllm-ampere-extended that referenced this pull request Oct 20, 2026
…parse indexer (vllm-project#61149)

Signed-off-by: Yongye Zhu <zyy1102000@gmail.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
JaredforReal added a commit to JaredforReal/vllm that referenced this pull request Oct 20, 2026
Since vllm-project#61149 the CUDA prefill MQA logits are row-relative (row_starts is
None), so gvr_2's read-from-column-0 contract holds for every chunk, not
only single-request ones. resolve_prefill_backend now gates on
row_starts is None plus GVR2_PREFILL_MIN_ROWS instead of num_reqs; XPU,
which still passes per-row starts, stays on top_k_per_row_prefill.

FlashInfer 0.7.2 (now pinned) compiles hint-free gvr_2 engines, so pass
pre_idx=None with top_k instead of a cached arange(k) hint buffer, and
feature-detect that signature.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Jared Wen <w13431838023@gmail.com>
@LopezCastroRoberto

LopezCastroRoberto commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Hey @zyongye @JaredforReal, I've been looking at the indexer numerics after this bump and I'm a bit worried about non-DSv4 models.

With the old pin, the SM100 kernel summed over heads in FP32 and gave us FP32 logits. The 26/09/30 release dropped logits_dtype and now does the whole head reduction in BF16 (__hfma2, here). So the index scores themselves are now BF16, and as_fp32_logits only upcasts them after scoring. The precision is already gone at that point.

That's fine for DSv4, since they trained for it:

We further quantize the index scores 𝐼:,: from FP32 to BF16 during this QAT process. This optimization achieves a 2× speedup for the top-k selector, while preserving a 99.7% recall rate of KV entries.

However, this might not be the case for other existing models, or new upcoming ones. For instance, DSv3.2 and GLM-5 both use FP32 index scores in their official code: DeepSeek's V3.2 release inference code, and the slime indexer (written by Zilin Zhu @ Z.ai) for GLM-5 through 5.3 and GLM-5.3-Flash (that's their RL stack, but it's what the policy was trained against). So we're now running them with different numerics than their own code uses.

This is also already shaping follow-up work in vLLM. #62240 (DeepSelect on BF16 prefill logits), #62172 (cooperative_topk reading BF16 logits), #62170 (BF16 indexer weights), #63200 (drop the FP32 upcast) all make sense for DSv4, and each one is correctly argued as "no precision change, since main already rounds to BF16". But together they lock in pure BF16 scoring + top-k for every DSA model on SM100, not just DSv4.

I don't think the evals in the description would catch this either. GSM8K prompts are shorter than index_topk, so the indexer just picks every token, and the MRCR runs are pretty small to tell: 60 prompts at 16K–32K and 24 at 57K–66K (vs 1M supported). That’s also where the effect should be smallest, since longer contexts put more tokens near the top-k cutoff, where BF16 rounding and ties can flip the selection.

Is there an experiment that stressed this that I'm missing? If it's not basically identical, I'd recommend to keep BF16 only for DSv4 and bring back an FP32 path for the rest. FWIW, SGLang already does this: BF16 index logits/weights only for the DSv4.1 index layers on SM100, FP32 for everything else (index_logits.py). Their pinned DeepGEMM still takes logits_dtype, so the FP32 path is still available there.

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 rocm Related to AMD ROCm

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

6 participants