Skip to content

[ROCm][Perf] TP-shard Lightning indexer prefill - #46103

Open
Concurrensee wants to merge 7 commits into
vllm-project:mainfrom
Concurrensee:glm-indexer-tp-shard
Open

Concurrensee wants to merge 7 commits into
vllm-project:mainfrom
Concurrensee:glm-indexer-tp-shard

Conversation

@Concurrensee

@Concurrensee Concurrensee commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Purpose

On ROCm/gfx950 the DeepSeek-V3.2-style Lightning indexer prefill (the FP8 MQA logits + per-row top_k) is computed redundantly on every tensor-parallel rank: each rank runs the full indexer over all prefill query rows and produces the identical topk_indices_buffer. Unlike the main attention/MLP, this work is not split by TP, so it is replicated tp_size times. Its cost grows with the causal range each query attends to, so at long context the replicated indexer prefill becomes a dominant, TP-non-scaling chunk of TTFT.

This PR shards that prefill indexer work across the TP group on the query (M / token) dimension. Each rank computes rocm_fp8_mqa_logits + top_k_per_row_prefill for only its slice of prefill rows, then the ranks exchange their slices so every rank ends up with the full topk_indices_buffer that the subsequent sparse attention needs. The KV-cache quant/gather and the decode path are unchanged.

Two partition schemes are provided:

  • TP_SHARD (default ON): split the num_prefill_tokens rows into tp_size contiguous shards. The remainder rows are assigned to rank 0 (the cheapest, lowest-index shard). Reassembly is hybrid NCCL: a ring all_gather over the evenly-divisible block plus a single broadcast for the remainder rows.
  • TP_SHARD_FOLD (default OFF): a work-balanced fold for the case where a step spans a very large causal range. Because per-row indexer cost rises with token index (~ ke - ks), a plain contiguous split overloads the last rank. Fold cuts the rows into 2*tp groups and pairs a cheap low-index group with an expensive high-index one on each rank. The split is fixed symmetric (M ≈ P/2, rounded to a multiple of tp so the first half can ring all_gather), which already balances the ~linear per-row cost. Reassembly is the rank-ordered first half via ring all_gather and the reversed second half via per-group broadcasts (batched in a single group_start/group_end).

Sharding only kicks in on gfx950 with tp_size > 1 and num_prefill_tokens >= 512 * tp_size, so short prefills (where the indexer is negligible and the collective would just add latency) fall back to the original replicated path.

Minor cleanups in the same change:

  • Skip the redundant cp_gather_indexer_k_quant_cache when chunk.skip_kv_gather is set (avoids re-gathering K for chunks that don't need it).
  • Drop the unconditional topk_indices_buffer[...] = -1 memset on gfx950 (the kernel fully writes the buffer there); keep it as a safety net on other archs.

Test Plan

  • Correctness: GSM8K (lm_eval, 5-shot, full 1319-example set) on 8×MI355 (gfx950, TP=8) for both models and both shard paths.
  • Performance: vllm bench serve sweeps over ISL × concurrency for both models (baseline vs TP_SHARD vs TP_SHARD+FOLD), reported below.

Test Result

Accuracy — GSM8K, 5-shot, full set

Model Config exact_match (flexible) exact_match (strict)
GLM-5.1-FP8 TP_SHARD 0.9386 0.9454
GLM-5.1-FP8 TP_SHARD+FOLD 0.9386 0.9431
DeepSeek-V4-Pro TP_SHARD 0.9500 0.8825
DeepSeek-V4-Pro TP_SHARD+FOLD 0.9409 0.8719

Performance

Setup: 8×MI355 (gfx950), TP=8, rocm/vllm-dev:nightly (vllm 0.23.1rc1.dev354) with this PR's two files overlaid; vllm bench serve, random dataset, OSL=1000, --ignore-eos, request-rate inf. All numbers are vs the baseline (replicated) column and + always means better (higher throughput / lower latency).

The first three columns (baseline / TP_SHARD / TP_SHARD+FOLD) run at vLLM default gpu-mem-util and default chunked-prefill size (8192). The two chunk16k columns combine the shard with a larger prefill chunk (--max-num-batched-tokens 16384) at --gpu-memory-utilization 0.88; this is a serving-side tuning knob (not part of this PR's code) that trades a little short/mid-context TTFT for better long-context TPOT/throughput.

Short context (≤ 8K) is neutral within ±2–3% (the indexer is a tiny fraction of work, and the gate skips sharding for the smallest prefills); the win grows with context length, reaching 2.1× throughput for GLM-5.1 at 199K and +40% for DeepSeek-V4-Pro at 512K.

GLM-5.1-FP8

Output throughput (tok/s)

ISL Conc baseline TP_SHARD TP_SHARD+FOLD TP_SHARD+chunk16k TP_SHARD+FOLD+chunk16k
4000 16 381.9 385.3 (+1%) 385.0 (+1%) 384.2 (+1%) 383.1 (+0%)
4000 64 1000.6 1012.0 (+1%) 1010.2 (+1%) 1018.9 (+2%) 1013.4 (+1%)
4000 256 1615.2 1647.5 (+2%) 1641.4 (+2%) 1661.5 (+3%) 1658.8 (+3%)
16000 4 130.0 130.8 (+1%) 134.7 (+4%) 133.6 (+3%) 134.2 (+3%)
16000 16 281.8 300.6 (+7%) 300.3 (+7%) 301.8 (+7%) 303.0 (+8%)
16000 64 415.3 458.3 (+10%) 457.6 (+10%) 461.3 (+11%) 464.4 (+12%)
100000 4 37.3 56.7 (+52%) 56.9 (+53%) 56.3 (+51%) 57.9 (+55%)
100000 12 42.2 69.7 (+65%) 69.9 (+66%) 69.6 (+65%) 71.6 (+70%)
199000 6 14.5 30.9 (+112%) 30.8 (+112%) 31.3 (+115%) 31.5 (+117%)

Mean TTFT (s)

ISL Conc baseline TP_SHARD TP_SHARD+FOLD TP_SHARD+chunk16k TP_SHARD+FOLD+chunk16k
4000 16 9.61 9.58 (+0%) 9.63 (-0%) 10.19 (-6%) 10.23 (-6%)
4000 64 6.68 6.57 (+2%) 6.69 (-0%) 7.18 (-7%) 7.41 (-11%)
4000 256 23.59 22.77 (+3%) 22.92 (+3%) 22.60 (+4%) 22.70 (+4%)
16000 4 3.56 3.92 (-10%) 3.10 (+13%) 3.33 (+6%) 3.27 (+8%)
16000 16 8.72 7.59 (+13%) 7.62 (+13%) 8.49 (+3%) 8.40 (+4%)
16000 64 29.09 25.31 (+13%) 25.35 (+13%) 25.38 (+13%) 25.11 (+14%)
100000 4 39.58 20.77 (+48%) 20.74 (+48%) 23.37 (+41%) 22.25 (+44%)
100000 12 79.31 41.67 (+47%) 41.60 (+48%) 43.59 (+45%) 41.71 (+47%)
199000 6 150.40 59.70 (+60%) 59.77 (+60%) 61.76 (+59%) 61.71 (+59%)

Mean TPOT (ms)

ISL Conc baseline TP_SHARD TP_SHARD+FOLD TP_SHARD+chunk16k TP_SHARD+FOLD+chunk16k
4000 16 32.3 31.9 (+1%) 31.9 (+1%) 31.5 (+3%) 31.5 (+2%)
4000 64 57.0 56.4 (+1%) 56.4 (+1%) 55.5 (+3%) 55.7 (+2%)
4000 256 133.4 131.1 (+2%) 131.5 (+1%) 130.8 (+2%) 130.9 (+2%)
16000 4 27.2 26.6 (+2%) 26.6 (+2%) 26.6 (+2%) 26.6 (+2%)
16000 16 47.9 45.5 (+5%) 45.5 (+5%) 44.5 (+7%) 44.3 (+7%)
16000 64 123.9 113.2 (+9%) 113.4 (+8%) 112.8 (+9%) 112.2 (+9%)
100000 4 67.5 49.5 (+27%) 49.4 (+27%) 47.6 (+30%) 46.8 (+31%)
100000 12 204.2 129.4 (+37%) 129.0 (+37%) 128.5 (+37%) 125.4 (+39%)
199000 6 261.5 133.9 (+49%) 133.9 (+49%) 129.7 (+50%) 128.4 (+51%)

DeepSeek-V4-Pro

Output throughput (tok/s)

ISL Conc baseline TP_SHARD TP_SHARD+FOLD TP_SHARD+chunk16k TP_SHARD+FOLD+chunk16k
8000 16 370.7 369.3 (-0%) 372.4 (+0%) 374.5 (+1%) 373.5 (+1%)
8000 64 719.3 721.7 (+0%) 720.2 (+0%) 750.2 (+4%) 746.7 (+4%)
8000 256 1089.2 1087.1 (-0%) 1091.0 (+0%) 1107.1 (+2%) 1107.0 (+2%)
64000 4 76.4 78.6 (+3%) 78.6 (+3%) 79.7 (+4%) 79.7 (+4%)
64000 16 129.9 137.0 (+5%) 137.0 (+5%) 139.0 (+7%) 139.3 (+7%)
64000 64 158.5 169.4 (+7%) 169.3 (+7%) 171.4 (+8%) 171.9 (+8%)
128000 6 55.2 60.6 (+10%) 60.8 (+10%) 61.6 (+12%) 61.9 (+12%)
128000 12 64.4 71.7 (+11%) 71.9 (+12%) 72.9 (+13%) 73.2 (+14%)
256000 6 27.2 33.0 (+21%) 33.3 (+22%) 33.9 (+25%) 34.2 (+26%)
512000 4 10.5 14.7 (+40%) 14.9 (+42%) 15.3 (+46%) 15.5 (+48%)

Mean TTFT (s)

ISL Conc baseline TP_SHARD TP_SHARD+FOLD TP_SHARD+chunk16k TP_SHARD+FOLD+chunk16k
8000 16 3.73 3.70 (+1%) 3.71 (+0%) 4.65 (-25%) 4.63 (-24%)
8000 64 13.38 13.31 (+1%) 13.36 (+0%) 12.81 (+4%) 12.94 (+3%)
8000 256 41.96 41.73 (+1%) 41.81 (+0%) 40.37 (+4%) 40.39 (+4%)
64000 4 11.80 10.99 (+7%) 10.99 (+7%) 11.80 (+0%) 11.77 (+0%)
64000 16 28.99 26.99 (+7%) 26.97 (+7%) 27.81 (+4%) 27.69 (+4%)
64000 64 97.87 91.05 (+7%) 91.02 (+7%) 89.45 (+9%) 89.05 (+9%)
128000 6 32.96 28.74 (+13%) 28.64 (+13%) 30.62 (+7%) 30.41 (+8%)
128000 12 52.07 45.57 (+12%) 45.40 (+13%) 47.00 (+10%) 46.67 (+10%)
256000 6 80.21 63.43 (+21%) 62.95 (+22%) 66.92 (+17%) 65.55 (+18%)
512000 4 176.65 120.09 (+32%) 119.06 (+33%) 125.55 (+29%) 124.39 (+30%)

Mean TPOT (ms)

ISL Conc baseline TP_SHARD TP_SHARD+FOLD TP_SHARD+chunk16k TP_SHARD+FOLD+chunk16k
8000 16 39.4 39.5 (-0%) 39.2 (+0%) 38.1 (+3%) 38.2 (+3%)
8000 64 75.1 74.9 (+0%) 75.0 (+0%) 72.3 (+4%) 72.5 (+3%)
8000 256 189.7 190.4 (-0%) 189.5 (+0%) 189.3 (+0%) 189.3 (+0%)
64000 4 40.4 39.8 (+2%) 39.8 (+2%) 38.3 (+5%) 38.4 (+5%)
64000 16 93.4 89.0 (+5%) 89.0 (+5%) 86.9 (+7%) 86.8 (+7%)
64000 64 301.6 282.4 (+6%) 282.6 (+6%) 282.0 (+6%) 281.2 (+7%)
128000 6 75.2 69.8 (+7%) 69.6 (+8%) 66.6 (+11%) 66.3 (+12%)
128000 12 133.0 120.6 (+9%) 120.2 (+10%) 117.0 (+12%) 116.6 (+12%)
256000 6 139.2 117.2 (+16%) 116.3 (+16%) 109.5 (+21%) 109.1 (+22%)
512000 4 203.5 150.3 (+26%) 148.8 (+27%) 135.4 (+33%) 133.4 (+34%)

Notes

Both env vars are read on gfx950 only; sharding additionally requires tp_size > 1 and num_prefill_tokens >= 512 * tp_size.

  • VLLM_ROCM_USE_AITER_MLA_SPARSE_TP_SHARD (default 1): shard the indexer prefill across the TP group on the token dimension. Set 0 to fall back to the original replicated path.
  • VLLM_ROCM_USE_AITER_MLA_SPARSE_TP_SHARD_FOLD (default 0): use the work-balanced fold partition instead of contiguous shards (the fold split point is fixed at P/2).

@mergify mergify Bot added rocm Related to AMD ROCm v1 labels Jun 18, 2026
@github-project-automation github-project-automation Bot moved this to Todo in AMD Jun 18, 2026
@mergify

mergify Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @Concurrensee.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

@mergify mergify Bot added the needs-rebase label Jun 18, 2026
@Concurrensee
Concurrensee force-pushed the glm-indexer-tp-shard branch 3 times, most recently from 6c5d9f8 to ae386c7 Compare June 19, 2026 21:25
@Concurrensee
Concurrensee marked this pull request as ready for review June 19, 2026 23:33
@Concurrensee
Concurrensee force-pushed the glm-indexer-tp-shard branch from f665980 to 0551372 Compare June 20, 2026 00:44
@mergify mergify Bot removed the needs-rebase label Jun 20, 2026
@Concurrensee
Concurrensee force-pushed the glm-indexer-tp-shard branch from 0551372 to b97bbd9 Compare June 20, 2026 00:51
Comment thread vllm/v1/attention/ops/rocm_aiter_mla_sparse.py Outdated
@Concurrensee
Concurrensee force-pushed the glm-indexer-tp-shard branch 6 times, most recently from 0a0718c to 3035d36 Compare June 21, 2026 02:30
Signed-off-by: Yida Wu <yida.wu@amd.com>
Signed-off-by: Claude Opus 4.8
Signed-off-by: Yida Wu <yida.wu@amd.com>
@Concurrensee
Concurrensee force-pushed the glm-indexer-tp-shard branch from 3035d36 to ae68614 Compare June 21, 2026 02:32
Signed-off-by: Yida Wu <yida.wu@amd.com>
@Concurrensee
Concurrensee force-pushed the glm-indexer-tp-shard branch from d482721 to f514565 Compare June 21, 2026 02:46
@Fangzhou-Ai

Copy link
Copy Markdown
Collaborator

Thanks @Concurrensee ! What's the command to run your test here? I saw you have added two envs, what's the best practice to use?

@Fangzhou-Ai

Copy link
Copy Markdown
Collaborator

Hi @dllehr-amd can you please spare some time to look at this PR?

@Concurrensee

Concurrensee commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

Hi @Fangzhou-Ai,
Best practice on the two envs:

Default config: keep the defaults — TP_SHARD on, FOLD off. TP_SHARD self-gates (only active on gfx950 when tp_size > 1 and num_prefill_tokens >= 512 * tp_size), so short prefills fall back to the replicated path and long-context prefills get the speedup automatically.

When tuning chunk size for long-context runs, turn on FOLD (VLLM_ROCM_USE_AITER_MLA_SPARSE_TP_SHARD_FOLD=1). Larger prefill chunks span a wider causal range per step, where the work-balanced fold partition pays off the most.

Since VLLM_ROCM_USE_AITER_MLA_SPARSE_TP_SHARD is on by default, if you observe a regression on a specific case, turn it off (=0) to fall back to the original replicated path.

@Concurrensee

Concurrensee commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

Hi @Fangzhou-Ai,
Commands to run (8×MI355, TP=8):

VLLM_ROCM_USE_AITER=1 VLLM_ROCM_USE_AITER_MLA=1 \
vllm serve $MODEL --tensor-parallel-size 8 \
  --max-model-len $MAXLEN --no-enable-prefix-caching --trust-remote-code
# fold (for long-context / larger chunk): prepend VLLM_ROCM_USE_AITER_MLA_SPARSE_TP_SHARD_FOLD=1
# baseline A/B:                           test against an image without this PR and an image with this PR. 
# contorl SHARD                           prepend VLLM_ROCM_USE_AITER_MLA_SPARSE_TP_SHARD=1/0 to turn on/off shard
 vllm bench serve --model $MODEL --dataset-name random \
  --random-input-len $ISL --random-output-len 1000 --ignore-eos \
  --num-prompts $((2*C)) --max-concurrency $C --request-rate inf \
  --percentile-metrics ttft,tpot --metric-percentiles 50,95
lm_eval --model local-completions --tasks gsm8k --num_fewshot 5 \
  --model_args model=$MODEL,base_url=http://127.0.0.1:8000/v1/completions,num_concurrent=128,tokenized_requests=False

@dllehr-amd
dllehr-amd self-requested a review June 30, 2026 16:05
Comment thread vllm/v1/attention/ops/rocm_aiter_mla_sparse.py Outdated
Check whether pynccl_comm disabled or not

Signed-off-by: Yida Wu <yida.wu@amd.com>
@Concurrensee
Concurrensee force-pushed the glm-indexer-tp-shard branch from 4d943be to b313953 Compare June 30, 2026 22:56
@Concurrensee
Concurrensee requested a review from dllehr-amd June 30, 2026 22:57
@mergify

mergify Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @Concurrensee.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-rebase rocm Related to AMD ROCm v1

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

4 participants