forked from vllm-project/vllm
-
Notifications
You must be signed in to change notification settings - Fork 28
perf(kquant): plan W4A16 trellis prefill on wide route blocks #563
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
myshytf
wants to merge
63
commits into
local-inference-lab:dev/infernal-invocation
Choose a base branch
from
myshytf:agent/kimi-k3-w4a16-prefill-route-block-20260902-pr
base: dev/infernal-invocation
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
63 commits
Select commit
Hold shift + click to select a range
826bb40
fix(structured-output): preserve grammar bitmask source widths
voipmonitor b9eea38
[Bugfix] Stream Kimi K3 tool-call arguments incrementally
guptaishaan efbaa95
tools: preserve partial Kimi XTML close markers
voipmonitor 9972d3c
fix(cache): preserve Mamba CoW after external hits
myshytf 990a63d
test(cache): assert external-hit continuation precondition
myshytf 3ebc896
Ignore Kimi tool calls after tools section close
voipmonitor c805ebd
fix(dspark): preserve draft graph capture contract
voipmonitor 3938209
spec_decode: preserve DFlash-family checkpoint semantics
voipmonitor 8f7f6c6
attention: execute replicated KV groups without DCP partitioning
voipmonitor f85d3d8
models: document DFlash rotary layout detection
voipmonitor 04a6acf
[II] Keep Kimi K3 protocol markers out of streamed content
voipmonitor c09bed0
Fix invalid block handling for hybrid KV cache groups
jungjiyu 6bafa63
test(scheduler): cover hybrid KV fail policy across 17 groups
voipmonitor 3c296be
test(scheduler): preserve hybrid KV recovery edge cases
voipmonitor a18a86e
fix(structured-output): map compacted DSpark grammar rows
voipmonitor e7ffc38
test(dspark): define graph-tail mode in capture fixture
voipmonitor b115455
fix(structured-output): stop XGrammar batches at termination
voipmonitor 5fe7989
fix(structured-output): validate speculative blocks before commit
voipmonitor fd0237e
test(structured-output): use prompt-aware reasoner contract
voipmonitor 2e8535c
fix(dspark): declare compact-RoPE ownership context
voipmonitor d931e0d
fix(warmup): narrow enabled DSpark debug events
voipmonitor c27d117
[Model][Spec Decode] Tap the pre-norm AttnRes mixture as the Kimi K3 …
rchalamala 0a00dac
test(kimi-k3): strengthen DFlash capture contracts
voipmonitor 71964b3
Bound Kimi vision RoPE allocation to input grids
voipmonitor 18d9e27
Bound Kimi projector transients per image
voipmonitor 4f34748
[II] Let cache specs own DCP block-table geometry
voipmonitor edb1042
fix(mamba): resume prefix hits on checkpoint grid
voipmonitor 832b1c9
fix(multimodal): gather uneven vision shards without TP padding
voipmonitor d19ef45
fix(kimi-k3): preserve final AttnRes block
voipmonitor 7aa4d61
fix(attention): preserve aliased LSE during state merge
voipmonitor 7671c66
fix(attention): align merge blocks to head groups
voipmonitor 62bead5
fix(kimi-k3): bound chunked MLA output lifetime
voipmonitor f7180bc
[Bugfix][Mamba] Fix overlapping state copy race (#50729)
AndreasKaratzas 4cfeb8a
Merge vLLM PR #414
voipmonitor bf5a9b2
Merge vLLM PR #295
voipmonitor e62cedb
Merge vLLM PR #294
voipmonitor 6f78b5c
Merge vLLM PR #320
voipmonitor 538ed2e
Merge vLLM PR #413
voipmonitor 8b6e1de
Merge vLLM PR #422
voipmonitor 799c2e7
Merge vLLM PR #310
voipmonitor bc3dd5a
Merge vLLM PR #415
voipmonitor afcbf4f
Merge vLLM PR #418
voipmonitor 23e7093
Merge vLLM PR #419
voipmonitor f52fadc
Merge vLLM PR #459
voipmonitor 94d3833
Merge vLLM PR #460
voipmonitor dc8f46d
Merge vLLM PR #463
voipmonitor 3c9031e
Merge vLLM PR #464
voipmonitor 7da8bd3
Merge vLLM PR #467
voipmonitor 6ba940c
Merge vLLM PR #468
voipmonitor 89b9db6
Merge vLLM PR #469
voipmonitor 1c93ca0
Merge upstream vLLM PR #50729 backport
voipmonitor a653e74
feat(spec_decode): run the Kimi-K3 DSpark draft on a dedicated remote…
myshytf 23f9f27
fix(spec_decode): allow scheduler-selected zero draft depth
myshytf f49f6c3
fix(cache): avoid target EAGLE drop for disaggregated DSpark
myshytf dbf86de
test(cache): cover DFlash target-cache policy
myshytf 1bed2da
scheduler: skip speculative decoding when all scheduled requests need…
malaiwah 27e71ca
fix: address CodeRabbit review — document lookahead reservation is ha…
malaiwah 74ec7af
fix(comm): prewarm FlashInfer PCIe graph shapes
myshytf 93917b3
feat(kquant): opt into W4A8 for coupled QSRT prefill
myshytf ffadb8a
perf(kquant): plan W4A16 trellis prefill on wide route blocks
myshytf 118a852
fix(kquant): return owned storage from the W4A8 prefill launch
myshytf ae4a1e9
fix(kquant): keep decode-band W4A16 launches on the 8-row plan
myshytf a5d2f8b
Merge branch 'feat/kimi-k3-w4a8-prefill-20260830' into agent/kimi-k3-…
myshytf File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,203 @@ | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| # SPDX-FileCopyrightText: Copyright contributors to the vLLM project | ||
| """Which value the DFlash drafter is fed under AttnRes. | ||
|
|
||
| `_capture_aux_hidden_stream` picks the weights it mixes against from one of | ||
| three places depending on where the tapped layer sits, and returns the plain | ||
| running prefix when the feature is off. The mixture itself is the kernel's | ||
| job and is covered by ``test_attn_res.py``; what is asserted here is the | ||
| selection, which is the part that can silently feed the drafter the wrong | ||
| tensor. | ||
| """ | ||
|
|
||
| from types import SimpleNamespace | ||
|
|
||
| import pytest | ||
| import torch | ||
|
|
||
| from vllm.models.kimi_k3.nvidia import model as k3_model | ||
|
|
||
| END_LAYER = 4 | ||
|
|
||
|
|
||
| def _weights(tag: float) -> SimpleNamespace: | ||
| """A norm/projection pair that is identifiable by value.""" | ||
| return SimpleNamespace( | ||
| weight=torch.full((2,), tag), | ||
| variance_epsilon=tag, | ||
| ) | ||
|
|
||
|
|
||
| def _stub_model(*, enabled: bool, use_attn_res: bool = True) -> SimpleNamespace: | ||
| """A stand-in carrying only what the tap reads. | ||
|
|
||
| Constructing the real model needs a distributed init and weights, and none | ||
| of it participates in the selection under test. | ||
| """ | ||
| model = SimpleNamespace( | ||
| _aux_attn_res_stream=enabled, | ||
| use_attn_res=use_attn_res, | ||
| end_layer=END_LAYER, | ||
| ) | ||
| if not use_attn_res: | ||
| return model | ||
|
|
||
| consumers = [] | ||
| for i in range(END_LAYER): | ||
| consumers.append( | ||
| SimpleNamespace( | ||
| self_attention_res_norm=_weights(float(i)), | ||
| self_attention_res_proj=SimpleNamespace( | ||
| weight=torch.full((1, 2), float(i)) | ||
| ), | ||
| prev_valid_blocks=i, | ||
| ) | ||
| ) | ||
| model.layers = consumers | ||
| model.output_attn_res_norm = _weights(99.0) | ||
| model.output_attn_res_proj = SimpleNamespace(weight=torch.full((1, 2), 99.0)) | ||
| model.num_attn_res_blocks = 99 | ||
| return model | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def recorder(monkeypatch): | ||
| """Replace the kernel so the call it would have made is inspectable.""" | ||
| calls = [] | ||
|
|
||
| def _fake_attn_res( | ||
| prefix, | ||
| delta, | ||
| block_residual, | ||
| norm_weight, | ||
| proj_weight, | ||
| output_norm_weight, | ||
| **kwargs, | ||
| ): | ||
| calls.append( | ||
| SimpleNamespace( | ||
| prefix=prefix, | ||
| delta=delta, | ||
| block_residual=block_residual, | ||
| norm_weight=norm_weight, | ||
| proj_weight=proj_weight, | ||
| kwargs=kwargs, | ||
| ) | ||
| ) | ||
| return torch.full_like(prefix, -1.0) | ||
|
|
||
| monkeypatch.setattr(k3_model, "attn_res", _fake_attn_res) | ||
| return calls | ||
|
|
||
|
|
||
| def _set_last_rank(monkeypatch, is_last: bool): | ||
| monkeypatch.setattr( | ||
| k3_model, | ||
| "get_pp_group", | ||
| lambda: SimpleNamespace(is_last_rank=is_last), | ||
| ) | ||
|
|
||
|
|
||
| def _call(stub, layer_idx, prefix_sum, pending_mlp_out, block_residual): | ||
| return k3_model.KimiLinearModel._capture_aux_hidden_stream( | ||
| stub, layer_idx, prefix_sum, pending_mlp_out, block_residual | ||
| ) | ||
|
|
||
|
|
||
| @pytest.mark.parametrize( | ||
| "enabled,use_attn_res", [(False, True), (True, False), (False, False)] | ||
| ) | ||
| def test_disabled_reproduces_the_plain_residual_sum( | ||
| recorder, monkeypatch, enabled, use_attn_res | ||
| ): | ||
| """Off, the tap must be exactly the sum it replaced. | ||
|
|
||
| Both conditions matter. `use_attn_res` is what constructs the norm and | ||
| projection weights, so without it the lookups below would raise rather | ||
| than fall back. | ||
| """ | ||
| _set_last_rank(monkeypatch, True) | ||
| prefix_sum = torch.tensor([1.0, 2.0]) | ||
| pending = torch.tensor([0.5, 0.25]) | ||
|
|
||
| got = _call( | ||
| _stub_model(enabled=enabled, use_attn_res=use_attn_res), | ||
| 0, | ||
| prefix_sum, | ||
| pending, | ||
| torch.zeros(2), | ||
| ) | ||
|
|
||
| torch.testing.assert_close(got, prefix_sum + pending) | ||
| assert not recorder, "the kernel must not run when the tap is off" | ||
|
|
||
|
|
||
| def test_taps_the_consumer_layer_when_one_follows(recorder, monkeypatch): | ||
| """The value the next layer reads is the mixture against *its* weights, | ||
| so the tap has to reach forward rather than use the current layer's.""" | ||
| _set_last_rank(monkeypatch, True) | ||
|
|
||
| _call(_stub_model(enabled=True), 1, torch.zeros(2), None, torch.zeros(2)) | ||
|
|
||
| assert len(recorder) == 1 | ||
| call = recorder[0] | ||
| # Layer 2's weights, not layer 1's. | ||
| torch.testing.assert_close(call.norm_weight, torch.full((2,), 2.0)) | ||
| torch.testing.assert_close(call.proj_weight, torch.full((2,), 2.0)) | ||
| assert call.kwargs["num_blocks"] == 2 | ||
|
|
||
|
|
||
| def test_last_layer_on_the_final_rank_uses_the_output_aggregation( | ||
| recorder, monkeypatch | ||
| ): | ||
| """Nothing downstream but the model's own output-side mixture.""" | ||
| _set_last_rank(monkeypatch, True) | ||
|
|
||
| _call( | ||
| _stub_model(enabled=True), END_LAYER - 1, torch.zeros(2), None, torch.zeros(2) | ||
| ) | ||
|
|
||
| assert len(recorder) == 1 | ||
| torch.testing.assert_close(recorder[0].norm_weight, torch.full((2,), 99.0)) | ||
| torch.testing.assert_close(recorder[0].proj_weight, torch.full((2,), 99.0)) | ||
| assert recorder[0].kwargs["num_blocks"] == 99 | ||
|
|
||
|
|
||
| def test_last_layer_of_a_non_final_stage_falls_back(recorder, monkeypatch): | ||
| """The consumer lives on the next rank and the output aggregation only | ||
| exists on the last one, so there is nothing here to mix against. | ||
|
|
||
| This is the case that would otherwise reach for weights this rank never | ||
| constructs. The forward guard is `layer_idx + 1 < end_layer`, where | ||
| `end_layer` is the rank's own exclusive bound from `get_pp_indices`, so a | ||
| `PPMissingLayer` is unreachable by construction -- the fallback below is | ||
| what makes that true rather than merely likely. | ||
| """ | ||
| _set_last_rank(monkeypatch, False) | ||
| prefix_sum = torch.tensor([3.0, 4.0]) | ||
|
|
||
| got = _call( | ||
| _stub_model(enabled=True), END_LAYER - 1, prefix_sum, None, torch.zeros(2) | ||
| ) | ||
|
|
||
| torch.testing.assert_close(got, prefix_sum) | ||
| assert not recorder, "no weights exist on this rank to mix against" | ||
|
|
||
|
|
||
| def test_pending_mlp_output_is_folded_in_rather_than_passed_as_delta( | ||
| recorder, monkeypatch | ||
| ): | ||
| """The kernel writes an applied delta back into the prefix in place, which | ||
| would double-add it into the live residual stream, so the pending output | ||
| has to arrive already summed into the prefix with `delta` left None.""" | ||
| _set_last_rank(monkeypatch, True) | ||
| prefix_sum = torch.tensor([1.0, 2.0]) | ||
| pending = torch.tensor([0.5, 0.25]) | ||
|
|
||
| _call(_stub_model(enabled=True), 0, prefix_sum, pending, torch.zeros(2)) | ||
|
|
||
| assert len(recorder) == 1 | ||
| assert recorder[0].delta is None | ||
| torch.testing.assert_close(recorder[0].prefix, prefix_sum + pending) | ||
| # And the caller's tensor is not mutated on the way. | ||
| torch.testing.assert_close(prefix_sum, torch.tensor([1.0, 2.0])) |
Oops, something went wrong.
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: local-inference-lab/vllm
Length of output: 1274
🏁 Script executed:
# Check if current_platform is imported in the test file head -30 tests/kernels/attention/test_merge_attn_states.pyRepository: local-inference-lab/vllm
Length of output: 1301
🏁 Script executed:
Repository: local-inference-lab/vllm
Length of output: 696
🏁 Script executed:
Repository: local-inference-lab/vllm
Length of output: 472
🏁 Script executed:
Repository: local-inference-lab/vllm
Length of output: 162
🏁 Script executed:
Repository: local-inference-lab/vllm
Length of output: 785
🏁 Script executed:
Repository: local-inference-lab/vllm
Length of output: 1349
🏁 Script executed:
Repository: local-inference-lab/vllm
Length of output: 184
🏁 Script executed:
# Look at all platform implementations ls -la vllm/platforms/Repository: local-inference-lab/vllm
Length of output: 720
🏁 Script executed:
Repository: local-inference-lab/vllm
Length of output: 2354
🏁 Script executed:
# Inspect the TPU platform implementation cat vllm/platforms/tpu.pyRepository: local-inference-lab/vllm
Length of output: 659
🏁 Script executed:
Repository: local-inference-lab/vllm
Length of output: 240
🏁 Script executed:
Repository: local-inference-lab/vllm
Length of output: 902
🏁 Script executed:
Repository: local-inference-lab/vllm
Length of output: 4812
🏁 Script executed:
Repository: local-inference-lab/vllm
Length of output: 3098
Use the platform wrapper for inference mode.
The test uses
@torch.inference_mode()directly, which bypasses the platform abstraction. The platform interface (vllm/platforms/interface.py:524-531) documents thatcurrent_platform.inference_mode()is the recommended wrapper because some platforms such as TPU do not supporttorch.inference_mode. The CPU and XPU platforms override this method to usetorch.no_grad()instead. Since the test already imports and usescurrent_platformfor the CUDA check at line 117, use@current_platform.inference_mode()for consistency and to enable platform-specific fallback behavior.Proposed fix
📝 Committable suggestion
🤖 Prompt for AI Agents