Skip to content

[Perf][GLM] Fuse the kpool tail slot mapping into one Triton kernel - #57534

Merged
ZJY0516 merged 3 commits into
vllm-project:mainfrom
JaredforReal:perf/sparse-indexer-decode-metadata
Sep 20, 2026
Merged

ZJY0516 merged 3 commits into
vllm-project:mainfrom
JaredforReal:perf/sparse-indexer-decode-metadata

Conversation

@JaredforReal

@JaredforReal JaredforReal commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Purpose

GLM-5.3-Flash sparse-MLA models keep a one-block-per-request circular tail cache (kpool). Every decode step — twice per step with MTP, once for the target model and once for the drafter — KpoolTailMetadataBuilder.build() maps each token to its request's tail ring with a chain of small torch ops (arange + searchsorted + clamp + index_select + remainder + copy_): 12 kernel launches and ~250 us of CPU enqueue time per build.

This PR replaces the chain with a single Triton kernel driven directly by query_start_loc, including the CUDA-graph padding rows. The CPU-tensor path keeps the torch implementation, and the emitted values are bit-identical to it.

With #57317 the generic _compute_slot_mappings_kernel no longer runs for KpoolTailSpec, so compute_kpool_tail_slot_mapping is now the only slot-mapping path for the tail group — this fusion covers all of it. It composes with #57477 (the seed-kernel stride fix): this PR only produces the logical slot ids (block * kpool + pos % kpool) that consumers decompose with // kpool / % kpool; physical addressing of the padded tail view is untouched, and the kernel reads the block table through its real stride(0).

Not a duplicate: #56562 covers the sparse-indexer decode expansion and the token→request mapping (an earlier revision of this PR had similar pieces and was rescoped); #57161 reworks the kpool compress kernel and #57458 the NoPE query packing — neither touches the tail slot mapping.

Test Plan

.venv/bin/python -m pytest tests/v1/attention/test_kpool_tail_slot_mapping.py
.venv/bin/python -m pytest tests/v1/attention/test_sparse_indexer_decode_seq_lens.py tests/v1/attention/test_indexer_native_next_n.py

E2E setup: 4x GB300 (SM103), TP4, zai-org/GLM-5.3-Flash (FP8, flashinfer_trtllm MoE), --attention-backend FLASHINFER_MLA_SPARSE --max-model-len 69632 --max-num-seqs 256 --max-num-batched-tokens 16384 --no-enable-prefix-caching --speculative-config '{"method":"mtp","num_speculative_tokens":1}', V2 model runner; vllm bench serve --dataset-name random --random-input-len 8192 --random-output-len 1024 --random-range-ratio 0 --ignore-eos, two runs per build, base = 42a33039eb.

Test Result

  • test_kpool_tail_slot_mapping.py: 17 passed — 4 new CUDA cases check the Triton path against the torch reference, including tokens past the last request boundary, CUDA-graph padding rows, and a non-contiguous (strided-view) block table.
  • test_sparse_indexer_decode_seq_lens.py + test_indexer_native_next_n.py: 16 passed.
  • Bit-exact vs the torch formulation → no output-affecting change, so no separate model eval was run.

Micro (compute_kpool_tail_slot_mapping, kpool=4, GB300; CPU = enqueue time without sync, GPU = profiler device time):

shape torch CPU this PR CPU torch GPU this PR GPU launches
decode bs=1 (2 tok, MTP k=1) 157 us 38 us 18.2 us 1.5 us 12 -> 1
decode bs=16 (32 tok) 266 us 35 us 12 -> 1
decode bs=64 (128 tok) 259 us 34 us 12 -> 1
decode bs=256 (512 tok) 254 us 33 us 22.1 us 2.0 us 12 -> 1
prefill 1x8192 251 us 33 us 22.9 us 12.2 us 12 -> 1
prefill 2x8192 243 us 32 us 12 -> 1

E2E (8k in / 1k out, output tok/s, two runs per build):

point main +this PR Δ (avg) main run-to-run noise
c=1 220.1 / 215.3 229.0 / 228.7 +5.1% ±2.2%
c=16 1359.8 / 1433.0 1461.9 / 1397.8 +2.4% ±5.4%
c=64 2229.8 / 2266.4 2148.2 / 2285.4 -1.4% ±1.6%
c=256 4176.0 / 4158.9 4183.4 / 4188.9 +0.4% ±0.4%

Median TPOT deltas are within noise at every point (-0.6% to +2.7%). The c=1 throughput ordering was consistent across both pairs of runs (and c=1 mean TPOT was 4.19 ms on both PR runs vs 4.37/4.47 ms on main), but with n=2 per build this sits at the edge of the noise floor. The durable claim is the removed per-step CPU work (~0.44 ms/step with MTP: two builds x ~220 us) and 11 fewer launches per build, which async scheduling largely overlaps on this hardware.

AI assistance (Claude) was used for the profiling, the implementation and the benchmark harness; every changed line was reviewed and the tests/benchmarks above were run by the submitter.

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

@mergify mergify Bot added the glm label Sep 18, 2026
tl.store(out_ptr + offs, tl.load(slot_mapping_ptr + offs, mask=mask), mask=mask)


def compute_kpool_tail_slot_mapping(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why we don't fuse the entire operation?

Semantic rebase of PR #11 onto current upstream main: the sparse-indexer
decode expansion and token_to_req_indices parts have already landed upstream
(13e221f, _indexer_decode_metadata_kernel/_token_request_mapping_kernel).
The remaining delta is the kpool tail slot mapping, which still ran an
arange/searchsorted/index_select/remainder chain per step.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Jared Wen <jaredwen@inferact.ai>
@JaredforReal
JaredforReal force-pushed the perf/sparse-indexer-decode-metadata branch from c610955 to 731c67f Compare September 18, 2026 10:15
@ZJY0516 ZJY0516 added the ready ONLY add when PR is ready to merge/full CI is needed label Sep 19, 2026
@github-actions

Copy link
Copy Markdown

@JaredforReal, CI is now available for this PR.

  • /ci run starts upstream CI; /amd-ci run starts AMD CI only.
  • Your branch must contain every commit currently on its upstream target branch. Merge or rebase onto the latest target branch, then rerun the command. Append --allow-stale to a run command to test an outdated branch at your own risk.
  • /ci retry retries failed jobs in the CI build for the current PR head. If the current head has no CI build, it starts a new CI build for the current head containing only jobs that failed in the latest earlier CI build for this PR.
  • /amd-ci retry retries failed jobs in AMD CI for the current PR head. Use /amd-ci run when the current head has no AMD CI build.
  • /ci cancel cancels scheduled or running CI builds for this PR branch; /amd-ci cancel does the same for AMD CI only.

@JaredforReal

Copy link
Copy Markdown
Contributor Author

/ci run

@github-actions

Copy link
Copy Markdown

❌ This PR is 18 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.

@JaredforReal

Copy link
Copy Markdown
Contributor Author

/ci run

@github-actions

Copy link
Copy Markdown

✅ Triggered Buildkite CI #90030 for commit fed2f6878784.

@ZJY0516
ZJY0516 merged commit 1b9fa3e into vllm-project:main Sep 20, 2026
163 checks passed
Morrowmake pushed a commit to Morrowmake/vllm-cmp170hx that referenced this pull request Sep 22, 2026
Upstream vllm-project#57534 (1b9fa3e) fused the kpool tail slot mapping into a single
Triton kernel inside compute_kpool_tail_slot_mapping(), with a stronger
contract than ours: it takes request query-start/end boundaries (extending
the last interval to the actual token count), shares the launch with the
optional tail slot-mapping copy, and bounds work by the actual token count.

Our VLLM_GLM5_PROLOGUE_FUSE_KPOOL path predates that and is now strictly
weaker, so drop it: the early-return hook in mla/indexer.py, the kernel,
kpool_tail_slot_mapping_ref/kpool_tail_slot_mapping, the `kpool` settings
field and its _KPOOL parser entry, and the tests of the removed private
implementation. Upstream's kpool tests are untouched.

The Mamba block-table and GDN speculative-metadata families keep working
exactly as before behind VLLM_GLM5_PROLOGUE_FUSE{,_MAMBA_BT,_GDN,_DEBUG}.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

glm ready ONLY add when PR is ready to merge/full CI is needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants