Repository navigation
[Perf][SM70] One grouped GEMM for DeepSeek-V4 wo_a; opt-in TurboMind tuning freeze after warmup - #1273
Open
Peuqui wants to merge 3 commits into
Open
[Perf][SM70] One grouped GEMM for DeepSeek-V4 wo_a; opt-in TurboMind tuning freeze after warmup#1273Peuqui wants to merge 3 commits into
Peuqui wants to merge 3 commits into
Conversation
… group On the V100 the DeepSeek-V4 wo_a projection (8 groups of 1024 x 4096 block FP8 per pipeline stage) ran one TurboMind GEMM per group, each with a tile sized for large M: 195 µs per layer at the six tokens a DSpark verification step sends. The one-launch grouped path (fp8_moe_gemm_sm70_per_expert_dispatch_out) existed only for two groups of that shape and one token. The grouped path now covers every grouped BMM with the native ops, up to SM70_FP8_GROUPED_DECODE_MAX_TOKENS (16) tokens per group; the layer keeps one row of group offsets per token count, and the warmup launches each decode size it serves. V100, layer 12 weights, CUDA graph, µs per layer (eight GEMMs / one grouped call): 1 token 204.3 / 56.0, 6 tokens 195.4 / 125.4; with kernel_config sm70_fp8.native.fp8_tune_small_shapes (TurboMind measures its tile per shape at warmup) 6 tokens 161.3 / 50.5, 8 tokens 156.5 / 54.7, 16 tokens 184.4 / 56.9. Error against FP32 on the dequantized weights: RMS 2.1e-4 in every case. An 8-layer cut of DeepSeek-V4-Flash (PP2 V100 + RTX, decode without speculation), ms per token: before 9.40 / 9.46, this commit 9.13, with the small- shape tuning 8.78 / 8.83 / 8.90; prefill unchanged. Short greedy text identical in all six boots, without tuning its logprobs bit-identical to the previous kernel loop. Tests: tests/quantization/test_sm70_warmup.py: the grouped decode tests follow the new contract (decode and six-token verification take one grouped call, more than 16 tokens per group the per-group loop, warmup per decode size); the 13 remaining failures fail the same without this change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0141pGKkAa2PjDMhrUwfX3ox Signed-off-by: Peuqui <43776522+Peuqui@users.noreply.github.com>
…reeze the plan after it With kernel_config sm70_fp8.native.fp8_tune_small_shapes (on by default), TurboMind measures its tile per FP8 shape: dense shapes up to M 64 and grouped (MoE dispatch) shapes up to 128 tokens. The V100 warmup did not measure anything that serving reused: sm70_awq_warmup captured its native policy from the current vLLM config, which is not set when the worker runs compile_or_warm_up_model, so the warmup GEMMs dispatched in the default policy partition (TM_GEMM_TRACE: policy=default, measured=0), one the layers never dispatch in. It now runs inside set_current_vllm_config(worker.vllm_config): 14 shapes measured per DeepSeek-V4 V100 stage instead of 0. Every shape the warmup does not cover is measured on the first request that brings it. On DeepSeek-V4 the last prefill chunk has n mod 1020 tokens (1020-token chunks), so about one request in 16 met a new size: +1.4 .. 3.7 s once per size and boot (6 of 24 prompt lengths around 35.7k tokens slow), and the kernel choice depended on the request order. kernel_config sm70_runtime.freeze_tuning_after_warmup (default off), read from the worker's config, marks the device final after the warmup through the new native op sm70_gemm_freeze_tuning: dense and MoE dispatch then return kReuse within the tuning limits (the next larger measured plan, else the heuristic) and never kMeasure. With it, all 24 prompt lengths took 7.0-7.1 s. Tests (with the test update of the next commit, on a Tesla V100): tests/quantization/test_sm70_warmup.py 27 passed, test_sm70_native_cache.py 3, test_sm70_fp8_prefill_exact_dense.py 24, tests/config/ test_runtime_default_ownership.py 31, test_unused_kernel_policy_hash.py 6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0141pGKkAa2PjDMhrUwfX3ox Signed-off-by: Peuqui <43776522+Peuqui@users.noreply.github.com>
…the missing operator The native bindings pass the engine policy to every operator as the keyword native_policy, and the FP8 tuning switches come from the captured native and runtime config, not from environment variables. 14 tests in test_sm70_warmup.py still assumed the old convention (fakes without the keyword, exact keyword comparison, warmup.envs which no longer exists) and failed before this work. They now accept the keyword (one of them asserts that it is passed) and set the tuning through the captured config (_configure_fp8_tuning). gguf_grouped_prefill_gemm_sm70_out is registered with a native_policy argument but was missing from CONFIGURED_OPERATORS, which test_packaged_native_policy_schemas_parse_before_build checks. It is added; it is not called through a NativeBindings instance today, so nothing changes at run time. Tests: tests/quantization/test_sm70_warmup.py 27 passed (14 of 25 failed before), test_sm70_native_policy.py 91 passed (1 failed before), test_sm70_native_cache.py 3, tests/config/test_runtime_default_ownership.py 31, test_unused_kernel_policy_hash.py 6. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011k7FsBAcZ8Cw91wisiZxMf Signed-off-by: Peuqui <43776522+Peuqui@users.noreply.github.com>
Peuqui
force-pushed
the
pr-dsv4-woa-freeze
branch
from
October 28, 2026 06:06
ad925b1 to
9a71c5c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Update 2026-10-28: rebased onto main c59b3c4. The three commits are unchanged apart from the regenerated docs/design/architecture/runtime_reference.md (git range-diff). Built in place on this head; the six Test Plan files pass on a Tesla V100: test_sm70_warmup.py 29, test_sm70_native_policy.py 91, test_sm70_native_cache.py 3, test_sm70_fp8_prefill_exact_dense.py 24, test_runtime_default_ownership.py 31 and test_unused_kernel_policy_hash.py 6 passed. On main c59b3c4 test_sm70_warmup.py still fails 14 of 25 (Tesla V100) and test_sm70_native_policy.py fails test_packaged_native_policy_schemas_parse_before_build (Quadro RTX 8000). pre-commit run --from-ref origin/main --to-ref HEAD on this head: every hook passes except check-layering, which now reports vllm/config/speculative.py (model coupling 142 -> 144) next to vllm/model_executor/layers/fused_moe/runner/moe_runner.py (platform coupling 37 -> 39); main c59b3c4 reports the same two counts, so this PR adds none. pre-commit run mypy-3.10 --hook-stage manual: passed.
Purpose
Two changes for the V100 stages of DeepSeek-V4, in production in our fork since 2026-10-24:
Not a duplicate: I searched the open PRs for wo_a, "grouped GEMM", freeze_tuning, fp8_tune_small_shapes and sm70_awq_warmup, and compared the changed files with the complete file lists of all open PRs.
AI assistance was used for this change. I reviewed every line and ran the tests and measurements below.
Test Plan
Native build of this branch (in-place), then:
python -m pytest tests/quantization/test_sm70_warmup.py tests/quantization/test_sm70_native_policy.py tests/quantization/test_sm70_native_cache.py tests/quantization/test_sm70_fp8_prefill_exact_dense.py tests/config/test_runtime_default_ownership.py tests/config/test_unused_kernel_policy_hash.py, one process per file, on a Tesla V100.
pre-commit run --from-ref origin/main --to-ref HEAD; pre-commit run mypy-3.10 --hook-stage manual --from-ref origin/main --to-ref HEAD
Path coverage
Acceleration and benchmark contract (required for performance changes)
Test Result
Kernel (V100, DeepSeek-V4 layer 12 weights, CUDA graph, us per layer, eight GEMMs / one grouped call, lower is better): 1 token 204.3 / 56.0, 6 tokens 195.4 / 125.4; with fp8_tune_small_shapes 6 tokens 161.3 / 50.5, 8 tokens 156.5 / 54.7, 16 tokens 184.4 / 56.9. Error against FP32 on the dequantized weights: RMS 2.1e-4 in every case.
DeepSeek-V4-Flash, A/B/A/B through the server entry (reference a/b -> changed a/b, one boot each after an uncounted cold boot; ms and s lower is better, tok/s higher is better):
Split of the decode gain (single boots, decode step in ms, prose / code / edit / 178k / 287k): reference 59 / 62 / 67 / 82.2 / 88.4; grouped path alone with the tuning off 57 / 60 / 65 / 79.8 / 85.9 (greedy text, token count and acceptance identical to the reference in two boots); with the tuning on and the freeze 55 / 58.5 / 63 / 78.0 / 84.4.
Draft acceptance (one boot per arm, sampling temperature 1.0, top_k 40, top_p 1.0, min_p 0.1; rounds 6-17 of each request, paired per request): prose 2.77 -> 2.78, english 2.90 -> 2.87, code 4.59 -> 4.60, edit 5.67 -> 5.68 tokens per round (all |t| <= 1.1); tok/s +4.0 .. +6.2 %.
Tests on a Tesla V100, native in-place build of this branch (ad925b1), one process per file:
The same card on main 6a14319 (in-place build; the tree also carried the Python-only [Bugfix][KV] MLA specs carry the codec of their cache dtype #1272, which touches neither file): test_sm70_warmup.py 14 failed, 11 passed; test_sm70_native_policy.py 1 failed (test_packaged_native_policy_schemas_parse_before_build), 90 passed.
pre-commit run --from-ref origin/main --to-ref HEAD: every hook passes except check-layering, which reports vllm/model_executor/layers/fused_moe/runner/moe_runner.py (platform coupling 37 -> 39); this PR does not touch that file and main 6a14319 reports the same. pre-commit run mypy-3.10 --hook-stage manual: passed. The pre-commit CI job of this PR fails exactly like main's own run at 6a14319 (run 40009610580): mypy in vllm/model_executor/layers/quantization/gguf_turbomind_moe.py:609 and vllm/v1/kv_cache_interface.py:156-158, and the check-layering count above; none of these files is touched here.
🤖 Generated with Claude Code (https://claude.com/claude-code)
https://claude.ai/code/session_0172gtUiymppDzziDqKfy78Q