Skip to content

[Perf][GLM5] Shard long-context indexer prefill rows across TP - #16

Closed
zigzagcai wants to merge 3 commits into
ZJY0516:glm-releasefrom
zigzagcai:newly-optimize-GLM-5.3-Flash-long-context
Closed

zigzagcai wants to merge 3 commits into
ZJY0516:glm-releasefrom
zigzagcai:newly-optimize-GLM-5.3-Flash-long-context

Conversation

@zigzagcai

@zigzagcai zigzagcai commented Sep 3, 2026

Copy link
Copy Markdown

Summary

  • shard replicated GLM-5.3-Flash sparse indexer prefill rows across TP ranks with a cost-balanced contiguous row planner
  • compute MQA logits/top-k locally and exchange only final int32 token indices with one all_gatherv
  • preserve GLM index_kpool=4 pool expansion, tail-token handling, and mixed decode/prefill semantics
  • gate the optimization for long MQA prefills and exclude DCP/PCP, disabled PynCCL/symm-mem, batch-invariant, ROCm, and FULL CUDA graph modes

Motivation

The indexer projections and K cache are replicated across TP ranks, so long prefill currently repeats the same MQA/top-k work on every rank. At 128K and 1M contexts the planner partitions rows by causal compressed-key cost; the final communication is only prefill_rows * index_topk * sizeof(int32) per indexer layer (1 MiB and 8 MiB respectively for index_topk=2048).

Matched optimized A/B

Both arms used the same 4x NVIDIA H200 TP4 process, native FP8 weights, BF16 KV, chunked prefill with max_num_batched_tokens=8192, no prefix cache, enforce_eager, MTP=0, temperature 0, ignore_eos, and one warmup request. The control is the unmodified vendor vLLM package; the treatment overlays only this PR's Python changes. Values are from vllm bench serve percentile output; every request succeeded.

context / concurrency arm completed p50 TTFT p50 TPOT output tok/s total tok/s
128K / C1 (2 requests) baseline 2/2 5297.9 ms 95.17 ms 5.667 11339
128K / C1 (2 requests) optimized 2/2 5146.9 ms 95.15 ms 5.744 11494
128K / C4 (4 requests) baseline 4/4 13321.9 ms 205.68 ms 8.949 17907
128K / C4 (4 requests) optimized 4/4 12816.8 ms 200.68 ms 9.221 18451
1M / C1 (1 request) baseline 1/1 63898.9 ms 102.70 ms 0.910 14212
1M / C1 (1 request) optimized 1/1 49482.6 ms 92.75 ms 1.157 18076

Relative to baseline, optimized changes are:

  • 128K/C1: TTFT -2.85%, TPOT -0.02%, throughput +1.36%
  • 128K/C4: TTFT -3.79%, TPOT -2.43%, throughput +3.03%
  • 1M/C1: TTFT -22.56%, TPOT -9.69%, throughput +27.19%

The 1M test is intentionally C1 to isolate the long-prefill critical path; C4 at 128K covers queueing/throughput behavior. Raw JSON results and the full reproduction note are in the serving workspace at results/long_context_row_sharding.md.

Scope and non-regression

The functional change is limited to four files: the generic sparse indexer, the GLM kpool indexer, the MLA metadata/planner, and a focused TP row-sharding test. No scheduler, model weights, KV-cache layout, decode kernel, or public API is changed. For short prefills the planner returns None below 1024 rows per TP rank, so the existing replicated indexer path is used byte-for-byte. DCP/PCP, symm-mem/PynCCL-disabled, batch-invariant, ROCm, and FULL CUDA-graph configurations are explicitly gated off and retain their prior path.

  • pytest -q tests/v1/attention/test_indexer_tp_row_shard.py: 50 passed
  • Existing related indexer/MLA regression tests: 61 passed
  • python -m compileall over changed modules: passed
  • git diff --check: passed
  • 7/7 optimized long-context requests completed; no CUDA illegal-memory-access or engine errors during inference

Planner evidence

  • 128K: [65536, 27146, 20829, 17561] rows on TP0..TP3
  • 1M: [524288, 217167, 166638, 140483] rows on TP0..TP3

The source editable checkout cannot currently be used for an apples-to-apples runtime because its DeepGEMM/FlashAttention binaries are ABI-incompatible with the model image and fail in the pre-existing fp8_fp4_mqa_logits path even with VLLM_DISABLE_PYNCCL=1. Therefore the A/B above deliberately uses the matching vendor runtime for both arms; this isolates the PR change instead of attributing an unrelated extension failure to the optimization.

Test environment

4x NVIDIA H200, TP4, native FP8 weights, BF16 KV, chunked prefill, model snapshot from the GLM-5.3-Flash release.

RichApple123 and others added 2 commits September 2, 2026 18:52
The CUDA DSA prefill indexer recomputes identical logits and Top-K on
every tensor-parallel rank. Assign each prefill query row to exactly one
rank and publish the finished rows with one all_gatherv per indexer
layer. Rows are independent, so this is a layout-preserving
concatenation rather than a Top-K merge.

The partition is contiguous and balances the exact number of scored
keys, derived from CPU scheduler metadata with no device sync.

Assisted-by: OpenAI Codex
Assisted-by: Claude Code (Opus)
Signed-off-by: lanqinghuan <qinghuan_lan@163.com>
@zigzagcai

Copy link
Copy Markdown
Author

Updated the PR body with matched vendor-runtime A/B data and explicit scope/non-regression evidence.

Headline result: at 1M context / C1, p50 TTFT improved from 63,898.9 ms to 49,482.6 ms (-22.56%), p50 TPOT from 102.70 ms to 92.75 ms (-9.69%), and total token throughput from 14,212 to 18,076 tok/s (+27.19%). At 128K / C4, the corresponding changes are TTFT -3.79%, TPOT -2.43%, and throughput +3.03%.

Control is the unmodified vendor vLLM package; treatment overlays only this PR's three implementation files. Both arms use TP4 on H200, BF16 KV, FP8 weights, chunked prefill, MTP=0, no prefix cache, enforce-eager, temperature 0, ignore-eos, and one warmup. All 7 optimized requests and all control requests completed successfully. Focused row-sharding tests pass (50/50), and related existing indexer/MLA regression tests pass (61/61).

@zigzagcai

Copy link
Copy Markdown
Author

Performance improvement rationale and results

The main bottleneck addressed by this PR is the replicated sparse indexer prefill computation across tensor-parallel ranks.

For GLM-5.3-Flash, the indexer projections and compressed K cache are replicated on every TP rank. During a long prefill, every rank therefore performs the same MQA-logits and top-k computation for all query rows, even though the final top-k indices are the only result needed by the subsequent attention computation. This redundant work becomes increasingly expensive as the context length grows because each query row scans a longer causal compressed-K range.

This PR introduces cost-balanced contiguous row sharding across TP ranks:

  1. The CPU-side planner partitions prefill rows according to their estimated compressed-MQA key range, rather than using an even row split.
  2. Each TP rank computes MQA logits and top-k only for its local rows.
  3. The final int32 top-k token indices are exchanged once using all_gatherv.
  4. The GLM index_kpool=4 pool expansion and tail-token semantics are preserved.

The communication overhead is small compared with the eliminated replicated computation. For index_topk=2048, the exchanged payload is approximately 1 MiB for a 128K prefill and 8 MiB for a 1M prefill per indexer layer, while the full logits matrix is never exchanged.

Matched A/B benchmark

Both arms used the same configuration:

  • 4x NVIDIA H200
  • TP4
  • Native FP8 weights
  • BF16 KV cache
  • Chunked prefill, max_num_batched_tokens=8192
  • MTP disabled
  • enforce-eager
  • No prefix caching
  • Temperature 0 and ignore_eos
  • One warmup request
  • Baseline: unmodified vendor vLLM package
  • Optimized: only the implementation files from this PR overlaid

All requests completed successfully.

Workload p50 TTFT p50 TPOT Total token throughput
128K / C1 baseline 5297.9 ms 95.17 ms 11339 tok/s
128K / C1 optimized 5146.9 ms (-2.85%) 95.15 ms (-0.02%) 11494 tok/s (+1.36%)
128K / C4 baseline 13321.9 ms 205.68 ms 17907 tok/s
128K / C4 optimized 12816.8 ms (-3.79%) 200.68 ms (-2.43%) 18451 tok/s (+3.03%)
1M / C1 baseline 63898.9 ms 102.70 ms 14212 tok/s
1M / C1 optimized 49482.6 ms (-22.56%) 92.75 ms (-9.69%) 18076 tok/s (+27.19%)

The improvement becomes substantially larger at 1M context because the eliminated replicated MQA/top-k work scales with the causal context length, while the additional communication remains limited to the final top-k indices.

Scope and non-regression

The functional change is limited to:

  • the generic sparse indexer prefill path;
  • the GLM kpool indexer prefill path;
  • MLA indexer metadata/planner construction;
  • focused TP row-sharding tests.

No scheduler, model weights, KV-cache layout, decode kernel, or public API is modified.

The optimization is explicitly disabled for short prefills and unsupported execution modes, including DCP/PCP, ROCm, batch-invariant execution, symm-mem/PynCCL-disabled configurations, and FULL CUDA graph execution. When the row threshold is not met, the existing replicated indexer path is retained.

Validation completed:

  • Focused row-sharding tests: 50/50 passed
  • Related existing indexer/MLA regression tests: 61/61 passed
  • Total related tests: 111 passed
  • compileall: passed
  • git diff --check: passed
  • All optimized long-context requests completed without CUDA or engine errors

The A/B test intentionally uses the matching vendor runtime for both arms because the editable checkout currently has an unrelated DeepGEMM/FlashAttention ABI mismatch. This keeps the comparison focused on the PR's Python implementation changes rather than mixing in a binary compatibility issue.

We would appreciate a review of the implementation and benchmark methodology. Given the clear long-context gains, especially the 1M-context result, we hope this optimization can be considered for merging.

Signed-off-by: Zheng Cai <8370601+zigzagcai@users.noreply.github.com>
@zigzagcai

Copy link
Copy Markdown
Author

Inference-accuracy A/B (updated)

I also validated inference correctness because this optimization changes the prefill execution/communication path.

The first version exposed an important GLM-5.3-specific detail: index_kpool=4 appends three incomplete-pool tail indices after the logical index_topk history. The TP exchange must include those tail slots as well. Commit ea87e70880 fixes the exchange width and is the commit used for the accuracy qualification below.

On 4x NVIDIA H200 (TP=4), with native FP8 weights, BF16 KV cache, chunked prefill (max_num_batched_tokens=8192), eager mode, no prefix cache, and MTP disabled:

  • Baseline: row sharding disabled in the same GLM runtime.
  • Optimized: row sharding enabled, commit ea87e70880.
  • Workload: the same deterministic 128K-token prompt, greedy decoding (temperature=0, top_k=1, top_p=1, fixed seed), 32 generated tokens.
  • Result: 4/4 repeated baseline requests and 4/4 repeated optimized requests produced identical token-id sequences. The optimized sequence is exactly equal to the baseline sequence, including finish reason and output length.
  • No CUDA/engine errors were observed in the corrected run.

The change is limited to long CUDA prefill MQA/indexer row partitioning and one TP all-gather of the complete token-index rows. Decode, DCP/PCP, full CUDA-graph, short-prefill, and unsupported communication configurations remain on the original path. The focused row-sharding test passes (50 passed), and the modified modules compile cleanly.

The 1M-token case is not included in this accuracy run because this checkpoint/runtime is capped at max_model_len=131072; the 128K case exercises the supported long-context boundary where the optimization is enabled.

@zigzagcai

zigzagcai commented Sep 3, 2026

Copy link
Copy Markdown
Author

Close this PR and re-open a PR targeted for vLLM upstream, since maintainer suggest a separate PR would be better

@zigzagcai zigzagcai closed this Sep 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants