Skip to content

[Bugfix] Max-load throughput cliff when max_num_seqs is not a multiple of 8 - #57355

Merged
benchislett merged 3 commits into
vllm-project:mainfrom
andylolu2:fix/cudagraph-max-num-seqs
Sep 17, 2026
Merged

benchislett merged 3 commits into
vllm-project:mainfrom
andylolu2:fix/cudagraph-max-num-seqs

Conversation

@andylolu2

@andylolu2 andylolu2 commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Why

By default, cudagraphs are captured in (mostly) multiples of 8. When max_num_seqs is not set to a multiple of 8, it has the unfortunate side effect that the last bucket of batch sizes doesn't fall into a FULL cuda graph. For example, with max_num_seqs=100 (and no spec decode), we get:

Num decodes Cudagraph mode
95 FULL
96 FULL
97 PIECEWISE
98 PIECEWISE
99 PIECEWISE
100 PIECEWISE

This is quite bad practically, because servers can get stuck in the slow PIECEWISE mode when it's overloaded (num_running_reqs = max_num_seqs).

In a TP4/B200 reproduction this was not a small boundary effect: batch 97 started behind batch 96 and progressively slowed over a 2,000-token decode, eventually falling below 1,000 tok/s/server. Adding the legal FULL-100 shape removed the cliff.

Fix

This is actually already correctly handled when spec decode is turned on by #50488, but not when spec dec is off. The fix is to simply piggy-back off the PR's changes: when the inferred default is used without speculative decoding, add max_num_seqs as the widest uniform-decode shape if it fits under the existing capture ceiling:

elif max_num_seqs <= max_cudagraph_capture_size:
    uniform_decode_sizes = [max_num_seqs]

For the example above, the ladder becomes:

..., 88, 96, 100, 104, 112, ...

The unchanged graph manager then produces FULL descriptors through 100, while later sizes remain PIECEWISE-only.

Benchmark evidence

The reproducer used Mistral-Small-4-119B-2603 with TP4 on B200, max_num_seqs=100, logprobs enabled, 2,000 generated tokens, and a production-shaped prompt distribution with roughly equal mass around 8k, 16k, 32k, 64k, and 127k input tokens.

Batch Before With FULL 100
96 3,753 tok/s 3,760.78 tok/s
97 1,523 tok/s 3,876.85 tok/s

Batch 97 improved 2.55× (+154.6%) and became comparable to batch 96. Before the fix its server-throughput windows decayed from 2,922 tok/s near output token 302 to 829 tok/s in the final window. After the fix they stayed flat at 3,923, 3,890, 3,862, and 3,824 tok/s. Total time fell from 129.72 s to 52.41 s.

An Nsight trace showed that the PIECEWISE path accumulated tensor-parallel rank drift: attention remained around 0.58 ms, while graph-launch gaps grew from about 0.69 ms to 5.5 ms and MNNVL all-reduce wait medians grew from about 10 µs to 3.6–4.7 ms. This PR avoids that path for a legal decode batch; it does not attempt to independently repair PIECEWISE rank drift.

Test results

VLLM_TARGET_DEVICE=cpu .venv/bin/python -m pytest -q \
  tests/compile/test_config.py \
  -k default_cudagraph_capture_size_respects_platform_ceiling
11 passed, 61 deselected

VLLM_TARGET_DEVICE=cpu .venv/bin/python -m pytest -q \
  tests/v1/cudagraph/test_cudagraph_manager.py \
  -k 'uniform_decode_pads_up_to_full_graph or uniform_decode_exact_match_is_not_over_padded or uniform_decode_beyond_capture_ladder_falls_back or divisor_query_len_dispatch_is_unchanged'
6 passed, 5 deselected

.venv/bin/pre-commit run --files \
  vllm/config/vllm.py tests/compile/test_config.py
all hooks passed, including Ruff, mypy, SPDX, and repository checks

Revert check: with the production hunk reverted while retaining the new test, both cases fail (2 failed): neither 100 nor 101 is present in the inferred capture sizes.

No model evaluation was run because this changes only which execution graph serves an already-valid shape; model operations and outputs are unchanged.

Not a duplicate

I searched open PRs for max_num_seqs, cudagraph, capture sizes, off-stride, and uniform decode. The nearby work addresses different contracts:

No open PR covers an off-stride, non-speculative max_num_seqs boundary.

Reviewer guide

  • vllm/config/vllm.py: the two-line behavior change reuses the uniform-decode capture-size path; the nearby comment is updated to match its broadened scope.
  • tests/compile/test_config.py: one new parameter exercises the existing capture-boundary contract at max_num_seqs=100.

There are no moves, generated files, or mechanical propagation changes.

AI assistance disclosure

OpenAI Codex assisted with the investigation, patch, tests, duplicate-work audit, and PR write-up. I reviewed every changed line and the benchmark evidence before submission.

Co-authored-by: OpenAI Codex <noreply@openai.com>
Signed-off-by: Andy Lo <andy@mistral.ai>
@andylolu2 andylolu2 changed the title 100 looks round to humans, 96 looks round to CUDA graphs [Bugfix] Capture off-stride max_num_seqs by default Sep 17, 2026
@mergify mergify Bot added the bug Something isn't working label Sep 17, 2026
Co-authored-by: OpenAI Codex <noreply@openai.com>
Signed-off-by: Andy Lo <andy@mistral.ai>
@andylolu2 andylolu2 changed the title [Bugfix] Capture off-stride max_num_seqs by default [Bugfix] Max-load throughput fall off cliff when max_num_seqs is not a multiple of 8 Sep 17, 2026
@andylolu2 andylolu2 changed the title [Bugfix] Max-load throughput fall off cliff when max_num_seqs is not a multiple of 8 [Bugfix] Max-load throughput cliff when max_num_seqs is not a multiple of 8 Sep 17, 2026
@andylolu2 andylolu2 changed the title [Bugfix] Max-load throughput cliff when max_num_seqs is not a multiple of 8 [Bugfix] Max-load throughput cliff when max_num_seqs is not a multiple of 8 Sep 17, 2026
@andylolu2
andylolu2 marked this pull request as ready for review September 17, 2026 11:35

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

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

LGTM

@github-project-automation github-project-automation Bot moved this to Ready in NVIDIA Sep 17, 2026
@benchislett
benchislett enabled auto-merge (squash) September 17, 2026 14:56
@benchislett benchislett added ready ONLY add when PR is ready to merge/full CI is needed and removed torch.compile labels Sep 17, 2026
@github-actions

Copy link
Copy Markdown

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

@benchislett

Copy link
Copy Markdown
Member

/ci run

@github-actions

Copy link
Copy Markdown

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

@andylolu2

Copy link
Copy Markdown
Contributor Author

/ci run

@github-actions

Copy link
Copy Markdown

✅ Triggered Buildkite CI #89668 for commit 91d92cc82081.

@benchislett
benchislett merged commit 09c379a into vllm-project:main Sep 17, 2026
143 checks passed
@github-project-automation github-project-automation Bot moved this from To triage to Done in torch.compile integration Sep 17, 2026
@github-project-automation github-project-automation Bot moved this from Ready to Done in NVIDIA Sep 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working nvidia ready ONLY add when PR is ready to merge/full CI is needed torch.compile

Projects

Status: Done
Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants