stage2 logits block load - #4555
Merged
Merged
Conversation
Contributor
🏷️ CI GuideRuns automatically on every PR:
Extended tests (opt-in via labels):
|
yanxuer-999
marked this pull request as draft
August 4, 2026 11:35
This was referenced Aug 5, 2026
yanxuer-999
force-pushed
the
stage2-logits-block-load
branch
from
August 10, 2026 02:56
be789ab to
77c3d7e
Compare
yanxuer-999
marked this pull request as ready for review
August 10, 2026 02:57
yanxuer-999
force-pushed
the
stage2-logits-block-load
branch
from
August 10, 2026 07:19
3887a9e to
53ea546
Compare
Contributor
Kimi-K3 TPOT comparisonSpeedups are relative to the Fixed-1 baseline. The last column compares AITER PR #4555 directly against vLLM PR #51119 Dynamic.
|
Co-authored-by: LiuYinfeng01 <199041580+LiuYinfeng01@users.noreply.github.com>
The bh16 split budget is now computed inside _bh16_num_kv_splits, leaving the host-side NUM_M_BLOCKS assignment unused (ruff F841).
NUM_KV_SPLITS stays a fixed launch budget. Both stages now derive the partition from the runtime per-batch KV length: kv_len_per_split = max(BLOCK_N, seq // NUM_KV_SPLITS) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Dewei-Wang-sh
force-pushed
the
stage2-logits-block-load
branch
from
August 11, 2026 08:13
53ea546 to
b6105a8
Compare
Closed
1 task
1 task
Merged
5 tasks
1 task
yudigege86
pushed a commit
to yudigege86/vllm
that referenced
this pull request
Aug 24, 2026
The comment said Gluon turns min_kv_seq_len into its split count, so the value had to be the shortest row actually submitted. That was true of aiter up to v0.1.19, where both bh16 regimes capped NUM_KV_SPLITS with it. ROCm/aiter#4555, merged and released in v0.1.20, drops it from that math entirely: bh16 now takes NUM_KV_SPLITS from the launch budget alone and derives the real partition inside the kernel from the runtime KV length. bh64 still reads min_kv_seq_len, but only to assert a lower bound on it, and the small-head verify path never routes to bh64. So the justification no longer holds on current aiter, and the comment asserted it unconditionally. Say what the value actually does on each side of that boundary instead. The computation stays: the shortest submitted row is the only value that is correct against both, and it is free, since the device->host read it shares with total_entries has to happen anyway. Signed-off-by: yudigege86 <naqin@amd.com>
yudigege86
pushed a commit
to yudigege86/vllm
that referenced
this pull request
Aug 24, 2026
The comment said Gluon turns min_kv_seq_len into its split count, so the value had to be the shortest row actually submitted. That was true of aiter up to v0.1.19, where both bh16 regimes capped NUM_KV_SPLITS with it. ROCm/aiter#4555, merged and released in v0.1.20, drops it from that math entirely: bh16 now takes NUM_KV_SPLITS from the launch budget alone and derives the real partition inside the kernel from the runtime KV length. bh64 still reads min_kv_seq_len, but only to assert a lower bound on it, and the small-head verify path never routes to bh64. So the justification no longer holds on current aiter, and the comment asserted it unconditionally. Say what the value actually does on each side of that boundary instead. The computation stays: the shortest submitted row is the only value that is correct against both, and it is free, since the device->host read it shares with total_entries has to happen anyway. Signed-off-by: yudigege86 <naqin@amd.com>
yudigege86
pushed a commit
to yudigege86/vllm
that referenced
this pull request
Aug 27, 2026
The comment said Gluon turns min_kv_seq_len into its split count, so the value had to be the shortest row actually submitted. That was true of aiter up to v0.1.19, where both bh16 regimes capped NUM_KV_SPLITS with it. ROCm/aiter#4555, merged and released in v0.1.20, drops it from that math entirely: bh16 now takes NUM_KV_SPLITS from the launch budget alone and derives the real partition inside the kernel from the runtime KV length. bh64 still reads min_kv_seq_len, but only to assert a lower bound on it, and the small-head verify path never routes to bh64. So the justification no longer holds on current aiter, and the comment asserted it unconditionally. Say what the value actually does on each side of that boundary instead. The computation stays: the shortest submitted row is the only value that is correct against both, and it is free, since the device->host read it shares with total_entries has to happen anyway. Signed-off-by: yudigege86 <naqin@amd.com>
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.
Motivation
Under vLLM CUDA Graph capture, the gluon MLA decode kernel's KV-split count
gets frozen to 1. The host derives
NUM_KV_SPLITSfrommin_kv_seq_len, whichvLLM hard-codes to
1during capture, so every long-context decode replays on asingle workgroup — collapsing throughput (e.g. 100k-token decode runs in
thousands of µs instead of tens). This affects the bh16 decode regimes
(
bh16bn64for nhead ≤ 96 andbh16bn128fp8) used by DeepSeek/Kimi-style MLA.This PR makes the split count immune to graph capture and, along the way,
removes a serial dependency chain in the stage-2 reduction.
Technical Details
vectorized tree reduction (load
BLOCK_Ssplits as a tile and reduce),with
BLOCK_Smade adaptive (min(64, next_pow2(NUM_KV_SPLITS))) to avoidVGPR over-allocation for small split counts.
Test Plan
op_tests/test_mla.py,checkAllcloseatol/rtol=0.01 vs torchgolden) across regimes and sequence lengths (32 / 64 / 128 / 256 / 16k / 100k):
bh64,bh16bn64(nhead ≤ 16 and 17–96),bh16bn128(fp8), plus MTP.min_kv_seq_len=1(the vLLM capture case)and compared against the device-side policy.
(
test_mla_gluon_dynamic_splits.py) verifying the budget matches the ~256-WGformula and is structurally independent of sequence length; it lives under
op_tests/triton_tests/attention/so the Triton CI selector runs it whenevermla_gluon.pychanges.Test Result
min_kv_seq_len=1(100k tokens, batch=1):short seq (64) ≈ 11 µs, bh64 @16k/b64 ≈ 521 µs.
Submission Checklist