Skip to content

[Perf] Optimize grouped topk kernel, 1.2%~2% E2E Throughput improvement - #32058

Merged
simon-mo merged 5 commits into
mainfrom
wentao-optimize-grouped-topk
Jan 13, 2026
Merged

simon-mo merged 5 commits into
mainfrom
wentao-optimize-grouped-topk

Conversation

@yewentao256

@yewentao256 yewentao256 commented Jan 9, 2026 •

Copy link
Copy Markdown
Member

Purpose

Part of the #31755

Here we combine the two kernels together, reducing global write and read

Test

Test

export MODEL="zai-org/GLM-4.7-FP8"

vllm serve $MODEL -tp 8 --port 9256 --enable-expert-parallel --max_num_seqs 128

Acc

lm_eval --model local-completions --model_args "base_url=http://127.0.0.1:9256/v1/completions,model=$MODEL,num_concurrent=1024" --tasks gsm8k --limit 100

|Tasks|Version|     Filter     |n-shot|  Metric   |   |Value|   |Stderr|
|-----|------:|----------------|-----:|-----------|---|----:|---|-----:|
|gsm8k|      3|flexible-extract|     5|exact_match|↑  | 0.98|±  |0.0141|
|     |       |strict-match    |     5|exact_match|↑  | 0.97|±  |0.0171|

Perf

Decode

vllm bench serve --model $MODEL --dataset-name random --host 127.0.0.1 --port 9256 --random-input-len 2 --random-output-len 128 --request-rate inf --num-prompts 128

# Now
============ Serving Benchmark Result ============
Successful requests:                     128       
Failed requests:                         0         
Benchmark duration (s):                  4.73      
Total input tokens:                      256       
Total generated tokens:                  16384     
Request throughput (req/s):              27.03     
Output token throughput (tok/s):         3460.20   
Peak output token throughput (tok/s):    3584.00   
Peak concurrent requests:                128.00    
Total token throughput (tok/s):          3514.26   
---------------Time to First Token----------------
Mean TTFT (ms):                          166.62    
Median TTFT (ms):                        158.52    
P99 TTFT (ms):                           194.78    
-----Time per Output Token (excl. 1st token)------
Mean TPOT (ms):                          35.73     
Median TPOT (ms):                        35.76     
P99 TPOT (ms):                           35.79     
---------------Inter-token Latency----------------
Mean ITL (ms):                           35.73     
Median ITL (ms):                         35.70     
P99 ITL (ms):                            40.22     
==================================================
# main
============ Serving Benchmark Result ============
Successful requests:                     128       
Failed requests:                         0         
Benchmark duration (s):                  4.79      
Total input tokens:                      256       
Total generated tokens:                  16384     
Request throughput (req/s):              26.71     
Output token throughput (tok/s):         3419.28   
Peak output token throughput (tok/s):    3584.00   
Peak concurrent requests:                128.00    
Total token throughput (tok/s):          3472.71   
---------------Time to First Token----------------
Mean TTFT (ms):                          165.77    
Median TTFT (ms):                        158.45    
P99 TTFT (ms):                           187.77    
-----Time per Output Token (excl. 1st token)------
Mean TPOT (ms):                          36.17     
Median TPOT (ms):                        36.17     
P99 TPOT (ms):                           36.22     
---------------Inter-token Latency----------------
Mean ITL (ms):                           36.17     
Median ITL (ms):                         36.17     
P99 ITL (ms):                            39.66     
==================================================

Prefill

vllm bench serve --model $MODEL --dataset-name random --host 127.0.0.1 --port 9256 --random-input-len 1024 --random-output-len 0 --request-rate inf --num-prompts 128

# now
============ Serving Benchmark Result ============
Successful requests:                     128       
Failed requests:                         0         
Benchmark duration (s):                  0.45      
Total input tokens:                      131072    
Total generated tokens:                  128       
Request throughput (req/s):              282.37    
Output token throughput (tok/s):         282.37    
Peak output token throughput (tok/s):    128.00    
Peak concurrent requests:                128.00    
Total token throughput (tok/s):          289427.29 
---------------Time to First Token----------------
Mean TTFT (ms):                          390.43    
Median TTFT (ms):                        431.06    
P99 TTFT (ms):                           443.86    
-----Time per Output Token (excl. 1st token)------
Mean TPOT (ms):                          0.00      
Median TPOT (ms):                        0.00      
P99 TPOT (ms):                           0.00      
---------------Inter-token Latency----------------
Mean ITL (ms):                           0.00      
Median ITL (ms):                         0.00      
P99 ITL (ms):                            0.00      
==================================================
# main
============ Serving Benchmark Result ============
Successful requests:                     128       
Failed requests:                         0         
Benchmark duration (s):                  0.46      
Total input tokens:                      131072    
Total generated tokens:                  128       
Request throughput (req/s):              277.01    
Output token throughput (tok/s):         277.01    
Peak output token throughput (tok/s):    128.00    
Peak concurrent requests:                128.00    
Total token throughput (tok/s):          283932.75 
---------------Time to First Token----------------
Mean TTFT (ms):                          397.54    
Median TTFT (ms):                        439.45    
P99 TTFT (ms):                           452.66    
-----Time per Output Token (excl. 1st token)------
Mean TPOT (ms):                          0.00      
Median TPOT (ms):                        0.00      
P99 TPOT (ms):                           0.00      
---------------Inter-token Latency----------------
Mean ITL (ms):                           0.00      
Median ITL (ms):                         0.00      
P99 ITL (ms):                            0.00      
==================================================

Note

Cursor Bugbot is generating a summary for commit 3a5eda1. Configure here.


Note

Fuses grouped top‑k into a single CUDA kernel and removes the intermediate buffer and second pass.

  • Introduces grouped_topk_fused_kernel that computes per‑group scores and final expert topk in one pass; eliminates previous topk_with_k2_kernel + group_idx_and_topk_idx_kernel pipeline
  • Updates invokeNoAuxTc to launch the fused kernel (one block per token, one warp per group) with dynamic shared memory; drops group_scores parameter and associated allocations/calls
  • Adds lightweight accessors to WarpSort and minor sync cleanup; adjusts shared‑memory staging for WarpSelect
  • Tightens input validation in grouped_topk (positivity checks, topk_group <= n_group, and topk <= topk_group * (num_experts / n_group)), and removes the temporary group_scores tensor
  • Keeps output dtypes (float32 values, int32 indices) and existing scoring options; fills uniform defaults when selection is not possible

Written by Cursor Bugbot for commit 3a5eda1. This will update automatically on new commits. Configure here.


Note

Fuses grouped top‑k into a single CUDA kernel and simplifies API/launch.

  • Introduces grouped_topk_fused_kernel to compute per‑group scores and final expert topk in one pass; removes topk_with_k2_kernel and group_idx_and_topk_idx_kernel and their launch plumbing
  • Updates invokeNoAuxTc (one block per token, one warp per group) and drops the group_scores parameter; sets dynamic shared‑memory layout for WarpSelect staging + group scores
  • Adds lightweight accessors in WarpSort, cleans up a sync, and adjusts shared‑memory staging
  • Tightens grouped_topk validation (positivity, bounds) and removes temporary group_scores tensor; outputs remain float32 values and int32 indices
  • Provides uniform fallback when selection is not possible; keeps existing scoring options

Written by Cursor Bugbot for commit c6a40fb. This will update automatically on new commits. Configure here.


Note

Fuses grouped top‑k into a single CUDA pass

  • Introduces grouped_topk_fused_kernel that computes per‑group scores and final expert topk in one block (one warp per group); removes topk_with_k2_kernel and group_idx_and_topk_idx_kernel
  • Simplifies invokeNoAuxTc (drops group_scores, sets dynamic smem layout for WarpSelect staging + group scores) and updates instantiations
  • Adds lightweight accessors in WarpSort, removes an unnecessary sync, and adjusts shared‑memory staging
  • Tightens grouped_topk input checks (positivity/bounds) and removes temporary group_scores tensor; outputs remain float32 values and int32 indices
  • Test utils: support num_expert_group and num_experts_per_token in dummy config to ensure MoE setup

Written by Cursor Bugbot for commit 9eba6cc. This will update automatically on new commits. Configure here.


Note

Combines grouped top‑k into a single pass to reduce memory traffic and simplify launches.

  • Introduces grouped_topk_fused_kernel (one block per token, one warp per group); removes prior two‑kernel pipeline and group_scores
  • Updates invokeNoAuxTc signature and launch (dynamic smem layout for WarpSelect + per‑group scores); updates explicit instantiations
  • Adds WarpSort accessors, adjusts shared‑memory staging, and removes an unnecessary sync
  • Tightens grouped_topk input checks (positivity/bounds) and eliminates temporary tensor; outputs remain float32 values and int32 indices
  • Tests: dummy_hf_overrides now recognizes num_expert_group and num_experts_per_token for MoE setup

Written by Cursor Bugbot for commit af26c8b. This will update automatically on new commits. Configure here.

Signed-off-by: yewentao256 <zhyanwentao@126.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a significant performance optimization by fusing two kernels in the grouped top-k operation into a single grouped_topk_fused_kernel. This change effectively reduces global memory traffic by using shared memory to pass intermediate results (group_scores) between stages. The overall approach is solid and should lead to performance gains as intended. My review includes one high-severity comment regarding shared memory allocation, which could be tightened to potentially further improve performance by increasing kernel occupancy.

Comment thread csrc/moe/grouped_topk_kernels.cu Resolved
Signed-off-by: yewentao256 <zhyanwentao@126.com>
@yewentao256 yewentao256 added the ready ONLY add when PR is ready to merge/full CI is needed label Jan 9, 2026
Comment thread csrc/moe/grouped_topk_kernels.cu Resolved
@robertgshaw2-redhat

Copy link
Copy Markdown
Collaborator

nice job wentao

Signed-off-by: yewentao256 <zhyanwentao@126.com>
@raihan0824

Copy link
Copy Markdown

I ran zai-org/GLM-4.7-FP8 on 8xH100 with this args:

  - "--gpu-memory-utilization=0.9"
  - "--tensor-parallel-size=8"
  - "--max-num-batched-tokens=65536"
  - "--max-num-seqs=512"
  - "--enable-auto-tool-choice"
  - "--tool-call-parser=glm47"
  - "--reasoning-parser=glm45"
  - "--speculative-config.method=mtp"
  - "--speculative-config.num_speculative_tokens=1"
  - "--served-model-name=glm-4.7-fp8"
  - "--enable-expert-parallel"

The inference starts without any issue, however when doing the benchmark:

vllm bench serve --model glm-4.7-fp8--tokenizer zai-org/GLM-4.7-FP8  --dataset-name random --host <HOST> --port 80  --random-input-len 2048 --random-output-len 128 --request-rate inf --num-prompts 128

the server crashed with this error:

(APIServer pid=1) ERROR 01-10 07:09:06 [serving_completion.py:512]                             ^^^^^^^^^^^^^
(APIServer pid=1) ERROR 01-10 07:09:06 [serving_completion.py:512]   File "/usr/local/lib/python3.12/dist-packages/vllm/v1/engine/output_processor.py", line 73, in get
(APIServer pid=1) ERROR 01-10 07:09:06 [serving_completion.py:512]     raise output
(APIServer pid=1) ERROR 01-10 07:09:06 [serving_completion.py:512]   File "/usr/local/lib/python3.12/dist-packages/vllm/entrypoints/openai/serving_completion.py", line 352, in completion_stream_generator
(APIServer pid=1) ERROR 01-10 07:09:06 [serving_completion.py:512]     async for prompt_idx, res in result_generator:
(APIServer pid=1) ERROR 01-10 07:09:06 [serving_completion.py:512]   File "/usr/local/lib/python3.12/dist-packages/vllm/utils/async_utils.py", line 278, in merge_async_iterators
(APIServer pid=1) ERROR 01-10 07:09:06 [serving_completion.py:512]     async for item in iterators[0]:
(APIServer pid=1) ERROR 01-10 07:09:06 [serving_completion.py:512]   File "/usr/local/lib/python3.12/dist-packages/vllm/v1/engine/async_llm.py", line 439, in generate
(APIServer pid=1) ERROR 01-10 07:09:06 [serving_completion.py:512]     out = q.get_nowait() or await q.get()
(APIServer pid=1) ERROR 01-10 07:09:06 [serving_completion.py:512]                             ^^^^^^^^^^^^^
(APIServer pid=1) ERROR 01-10 07:09:06 [serving_completion.py:512]   File "/usr/local/lib/python3.12/dist-packages/vllm/v1/engine/output_processor.py", line 73, in get
(APIServer pid=1) ERROR 01-10 07:09:06 [serving_completion.py:512]     raise output
(APIServer pid=1) ERROR 01-10 07:09:06 [serving_completion.py:512]   File "/usr/local/lib/python3.12/dist-packages/vllm/v1/engine/async_llm.py", line 495, in output_handler
(APIServer pid=1) ERROR 01-10 07:09:06 [serving_completion.py:512]     outputs = await engine_core.get_output_async()
(APIServer pid=1) ERROR 01-10 07:09:06 [serving_completion.py:512]               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
(APIServer pid=1) ERROR 01-10 07:09:06 [serving_completion.py:512]   File "/usr/local/lib/python3.12/dist-packages/vllm/v1/engine/core_client.py", line 899, in get_output_async
(APIServer pid=1) ERROR 01-10 07:09:06 [serving_completion.py:512]     raise self._format_exception(outputs) from None
(APIServer pid=1) ERROR 01-10 07:09:06 [serving_completion.py:512] vllm.v1.engine.exceptions.EngineDeadError: EngineCore encountered an issue. See stack trace (above) for the root cause.
(APIServer pid=1) INFO:     Shutting down
(APIServer pid=1) INFO:     Waiting for application shutdown.
(APIServer pid=1) INFO:     Application shutdown complete.
(APIServer pid=1) INFO:     Finished server process [1]

I tried tweaking the vllm args such as removing max-num-batched-tokens or disabling speculative decoding, it's still crashed when load tested.

@yewentao256

yewentao256 commented Jan 10, 2026 •

Copy link
Copy Markdown
Member Author

I ran zai-org/GLM-4.7-FP8 on 8xH100 with this args:

@raihan0824 What's the full error log? Seems like an OOM issue I met before, H100 is usually not enough to hold the GLM4.7 FP8

@raihan0824

Copy link
Copy Markdown

I ran zai-org/GLM-4.7-FP8 on 8xH100 with this args:

@raihan0824 What's the full error log? Seems like an OOM issue I met before, H100 is usually not enough to hold the GLM4.7 FP8

yeah its OOM issue, the error vanished after i put max-num-seqs to 64. However why is this the case? i ran bigger model (qwen 3 coder fp8) and its not OOM even if i put max-num-seqs to 512.

anw, in my opinion, the server shouldn't be even started if it's gonna be crashed when handling heavy task

@yewentao256

Copy link
Copy Markdown
Member Author

yeah its OOM issue, the error vanished after i put max-num-seqs to 64. However why is this the case? i ran bigger model (qwen 3 coder fp8) and its not OOM even if i put max-num-seqs to 512.

anw, in my opinion, the server shouldn't be even started if it's gonna be crashed when handling heavy task

I think the kv cache / activations count for memory as well, not just for the model weights.

There might be some cases that you can setup model but OOM in requests.

Feel free to submit an issue and discuss there, seems out of this PR's scope

@mgoin mgoin added the performance Performance-related issues label Jan 12, 2026

@mgoin mgoin left a comment

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.

Took a relatively careful look through and seems solid, I guess there is space to improve over TRTLLM after all :)

I think the two kernel design caused SM underutilization for small numbers of tokens. So while phase 2 only uses a single warp per block, it is only dealing with a single token. This has me a bit concerned about regression for prefill/large numbers of tokens, so I would like to see a prefill-heavy benchmark to confirm.

Comment thread tests/models/utils.py
Comment on lines +455 to +457
# Kimi uses `num_expert_group` instead of `n_group`.
if n_group is None:
n_group = getattr(text_config, "num_expert_group", None)

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 did you have to update this?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Comment on lines -717 to -738
switch (n_group) {
case 4: {
launch(&group_idx_and_topk_idx_kernel<T, BiasT, IdxT, SF, 4>);
break;
}
case 8: {
launch(&group_idx_and_topk_idx_kernel<T, BiasT, IdxT, SF, 8>);
break;
}
case 16: {
launch(&group_idx_and_topk_idx_kernel<T, BiasT, IdxT, SF, 16>);
break;
}
case 32: {
launch(&group_idx_and_topk_idx_kernel<T, BiasT, IdxT, SF, 32>);
break;
}
default: {
launch(&group_idx_and_topk_idx_kernel<T, BiasT, IdxT, SF>);
break;
}
}

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.

nit: it could still be valuable to template on num groups for loop unrolling

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Originally we have for (g = 0; g < n_group; ++g), which could get perf from unrolling n_group.
Now for (g = 0; g < topk_group; ++g) maybe I can explore unrolling for topk_group in following up PRs.
Great suggestion, thanks!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Give it a try and seems no improvement, topk_group usually small I suppose

@yewentao256 yewentao256 changed the title [Perf] Optimize grouped topk kernel, 1.2% E2E Throughput improvement [Perf] Optimize grouped topk kernel, 1.2%~2% E2E Throughput improvement Jan 12, 2026
@yewentao256

Copy link
Copy Markdown
Member Author

Took a relatively careful look through and seems solid, I guess there is space to improve over TRTLLM after all :)

I think the two kernel design caused SM underutilization for small numbers of tokens. So while phase 2 only uses a single warp per block, it is only dealing with a single token. This has me a bit concerned about regression for prefill/large numbers of tokens, so I would like to see a prefill-heavy benchmark to confirm.

Added the benchmark of prefill in the PR description, also faster for prefill

@yewentao256
yewentao256 enabled auto-merge (squash) January 12, 2026 22:30
Comment thread csrc/moe/grouped_topk_kernels.cu Resolved
@simon-mo
simon-mo disabled auto-merge January 13, 2026 18:58
@simon-mo
simon-mo merged commit f28125d into main Jan 13, 2026
95 of 98 checks passed
@simon-mo
simon-mo deleted the wentao-optimize-grouped-topk branch January 13, 2026 18:58
sammysun0711 pushed a commit to sammysun0711/vllm that referenced this pull request Jan 16, 2026
akh64bit pushed a commit to akh64bit/vllm that referenced this pull request Jan 16, 2026
mystous pushed a commit to mystous/vllm_hybrid that referenced this pull request May 10, 2026
my-other-github-account pushed a commit to my-other-github-account/vllm that referenced this pull request May 15, 2026
0826joyce pushed a commit to 0826joyce/vllm-serving-optimization that referenced this pull request May 19, 2026
plasticchris pushed a commit to plasticchris/vllm that referenced this pull request Jul 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

performance Performance-related issues 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.

5 participants