fix(server): capture legal multi-request prefill CUDA graph batches - #30206
Conversation
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
|
[by Codex] |
|
/tag-and-rerun-ci |
|
cc @janbernloehr @hnyls2002 for vis |
|
Note: I'm currently traveling without my laptop, so I asked Claude to dig into this — the analysis below was created with Claude Code, and I haven't been able to run anything locally myself. Thanks for the PR — the perf observation is real (with the current clamp, a multi-request prefill batch whose total tokens exceed 1.
self.context_length * self.max_running_requestsraises 2. Even with The clamp exists because of how prefill graph capture builds its dummy batch, not because of what replay batches can contain. bs = 1
"seq_lens": torch.tensor([num_tokens], device=self.device),so the attention backend's metadata init sees one sequence of length Concretely: on H100 with (Minor additional wrinkle: for the Suggested direction The bottleneck is the capture path, so the fix that would actually unlock this is in Until then I think the conservative clamp has to stay, since the alternative is trading an eager-fallback slowdown for an illegal-memory-access crash at startup. |
|
I think I know what might be the issue. First of all, Setting that aside, the main issue here is: what kind of dummy input batch does SGLang use to capture the PCG/BCG for prefill? Currently, it appears that SGLang just sends in one request with seqLen= The original PR #22516 fixed this issue by lowering the max_num_tokens (aka max_bs) setting to match context-length, but that is too restrictive when prefill can run with multiple requests. The "correct" fix seems to be relaxing the max_num_tokens to context-length * max_running_requests, but when SGLang sends in dummy input batch for BCG/PCG graph capturing, it should be aware of context length and should NOT use a request with |
e87c217 to
ca741f7
Compare
|
[by Codex]
/tag-and-rerun-ci |
ca741f7 to
1716dee
Compare
1716dee to
a36af43
Compare
|
[by Codex] /tag-and-rerun-ci |
a36af43 to
e689f49
Compare
|
(Still traveling without my laptop — this follow-up review was again put together with Claude Code.) Thanks for the rework — this is the right approach now, and the tc_piecewise path looks correct to me: the static per-request buffers are allocated at Two remaining points before I'd consider this mergeable: 1. The "no bucket fits" path warns, then crashes anyway. cuda_graph_runner._run_dummy_forward(
num_tokens=cuda_graph_runner.capture_num_tokens[0]
)which raises 2. The Breakable backend's bs=1 capture contract. The multi-request split applies to both prefill backends, but the BCG replay path explicitly documents "the captured graph is a bs=1 replay of |
|
[by Codex] Addressed in 8b1b4ee.
Validated with |
| # tc_piecewise supports capture with multiple synthetic requests, so | ||
| # its bucket represents aggregate tokens. Breakable captures a bs=1 | ||
| # graph and must retain the per-request context-length cap. | ||
| max_capture_tokens = ( |
There was a problem hiding this comment.
# Breakable captures a bs=1 # graph and must retain the per-request context-length cap.
I don't like this. Could we make breakable also captures with multiple requests? If not, why can't we do so?
There was a problem hiding this comment.
[by Codex] Addressed in 6d07765. Breakable prefill now captures the same synthetic multi-request batches as tc_piecewise, and both backends admit buckets through request_pool_size × context_length.
This is safe because Breakable already owns stable request-axis buffers; attention runs at Breakable graph boundaries with replay-time metadata, and the request-layout-dependent output tail remains eager. The captured transformer segments are token-axis operations, so replay continues to support the live request layout.
Validated with git diff --check and Python syntax compilation for both changed modules.
3eff076 to
d22b274
Compare
|
/rerun-failed-ci |
d22b274 to
8f7e5f4
Compare
|
/rerun-failed-ci |
be96089 to
443faf7
Compare
|
/rerun-failed-ci |
f475d3f to
136efa9
Compare
|
/rerun-failed-ci |
43b8bdc to
334b1d3
Compare
|
/rerun-failed-ci |
|
All NV pipelines have passed. @Oasis-Git could you help us to review this since this is related to BCG? thanks! |
[by Codex]
Motivation
When
--context-lengthis smaller than a prefill CUDA-graph capture bucket, the old capture path creates one synthetic request withseq_len=num_tokens. That exceeds the per-request context limit and can cause the #21112 illegal-memory-access failure during graph warm-up.Clamping the aggregate prefill token ceiling to
context_lengthavoids that crash, but unnecessarily disables graph replay for legal multi-request prefills whose total token count exceeds one request's context length.Design
This PR fixes the capture shape rather than applying a per-request cap to an aggregate-token setting:
ModelRunner.init_prefill_cuda_graph()and write the filtered list back tocuda_graph_config.prefill.bsbefore constructing the runner.context_length * max_capture_requests: request-pool size fortc_piecewise/breakable, or the resolved fixed request-slot count forfull.num_tokensinto the fewest synthetic requests such that every request hasseq_len <= context_length.full, preserve its fixed request-axis contract by padding any unused request slots with zero-length sentinels after the context-bounded synthetic requests.context_length * max_running_requestscalculation becausemax_running_requestsis not always resolved at that stage.attn_mhacompanion on CUDA as well as HIP. This ensures the captured attention op receives MHA head metadata instead of the same-layer-idattn_mqametadata.This preserves graph coverage for multi-request prefills while keeping every synthetic request within the per-request context bound.
Validation
git diff --checkpython -m py_compileon all five changed Python fileslmsysorg/sglang:dev-cu13: all five changed files left unchanged4xGB200 DSR1 NVFP4 verification
Passed on four NVIDIA GB200 GPUs at commit
aec6c63161using a staged DeepSeek-R1-0528 ModelOpt NVFP4 checkpoint:context_length=2048,max_running_requests=8,chunked_prefill_size=4096, decode CUDA graphs disabled, and FlashInfer autotuning disabled.backend=breakable,bs=[4096]. The only capture bucket is therefore larger than one request's context and is constructed as two legal synthetic requests.Capture target prefill CUDA graphfor 4,096 tokens in about 20.9 seconds; capture used about 1.41 GiB per GPU./generaterequest returned four completion tokens for each prompt; server-reported prompt lengths were 1,301 and 1,301 tokens (2,602 aggregate, greater than the 2,048-token per-request context).cuda graph: True.4272943.2completed with exit code0:0.Earlier H100 verification
Passed on one H100 80GB at commit
e689f496ba:Qwen/Qwen2.5-1.5B-Instructwith--context-length 2048 --mem-fraction-static 0.7 --attention-backend flashinfer --cuda-graph-backend-prefill tc_piecewise.4 * context_length)./generaterequest successfully.Related
CI States
Latest PR Test (Base): ✅ Run #30425272323
Latest PR Test (Extra): ❌ Run #30425272188