Repository navigation
[Bugfix] Graph-capture trace export crashes on DSA dual-graph models - #37780
Closed
fengshaoyi-amd wants to merge 1 commit into
Closed
fengshaoyi-amd wants to merge 1 commit into
fengshaoyi-amd wants to merge 1 commit into
Conversation
fengshaoyi-amd
requested review from
Fridge003,
Ying1123,
hnyls2002,
ispobock and
merrymercy
as code owners
September 3, 2026 10:29
fengshaoyi-amd
force-pushed
the
fix/graph-capture-trace-naming
branch
from
September 3, 2026 10:38
a4f6cf2 to
385019a
Compare
fengshaoyi-amd
force-pushed
the
fix/graph-capture-trace-naming
branch
from
September 16, 2026 11:06
385019a to
bc65b79
Compare
The bs-indexed trace exporter assumed one profiler callback per batch size, but capture() runs one per (bs, lora, attention) combination. DSA dual-graph models therefore overran the list and crashed partway through capture, before the server became ready. The traces written before the crash were mislabeled too: the name came from the callback's position in the list rather than from the shape being captured, so all but the first carried a bs that did not match the graph inside. Record the combination being captured and name the trace after it. Models with neither variant dimension keep byte-identical filenames.
fengshaoyi-amd
force-pushed
the
fix/graph-capture-trace-naming
branch
from
September 16, 2026 11:10
bc65b79 to
f4fb632
Compare
Author
|
Closing in favor of #39840, which fixes the same |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
With
SGLANG_GRAPH_BATCH_CAPTURE=1and--enable-profile-cuda-graph, startinga server for a DSA model on ROCm dies with an
IndexErrorpartway through CUDAgraph capture, before the server ever becomes ready.
_init_profile_context_and_memory_recordnames each capture trace by walking alist sized for batch sizes only, advancing one slot per profiler callback:
That assumes exactly one callback per batch size. But
capture()loops overthree dimensions, and each innermost iteration runs its own full profiler cycle
(
FullCudaGraphBackend.capture_onesteps thewait=2, warmup=0, active=1schedule three times, so one
capture_one_shapeproduces exactly onecallback):
So the callback count is
len(capture_bs) * len(lora_variants) * len(attention_variants)while the list holdslen(capture_bs)entries. Anymodel that turns on an extra dimension overruns the list.
Both extra dimensions are live on main today, for different reasons.
create_attention_graph_variantsreturnsDsaGraphVariantsfor every HIP hostrunning one of the architectures in
is_deepseek_dsa(DeepSeek-V3/V3.2,GLM-MoE-DSA, MistralLarge3, Pixtral, LongcatFlash, Dots3Note, ...), whose
capture_labelsis("dense", "sparse")— a dense and a sparse graph perbucket. It just happens to be off for the models this path was originally
exercised against, which is why it has looked healthy.
The LoRA dimension is a second trigger, and it is not HIP-gated:
UnoDecodeCudaGraphRunnerpassesrecord_nolora_graph=forward_width > 1toDecodeCudaGraphRunner.__init__, so linear UNO withforward_width > 1expands
lora_variantsto[("lora", True), ("nolora", False)]and overrunsthe list on any accelerator. It inherits
capture()and_init_profile_context_and_memory_recordunchanged, so it hits exactly thecrash below.
The traces written before the crash are mislabeled
The crash is the visible half of the problem. Because the filename comes from
the callback's position in the list rather than from the shape being
captured, every trace after the first carries a
bsthat does not match thegraph inside it. For 8 buckets with dual-graph on:
bs_8bs_8_densebs_7(wrong bs)bs_8_sparsebs_6(wrong bs)bs_7_densebs_5(wrong bs)bs_7_sparsebs_1(wrong bs)bs_5_sparseIndexErrorbs_4_denseSo of the eight files each rank writes before dying, only the first is labeled
correctly. Anything that consumes these traces per batch size — roofline
analysis, kernel attribution — silently analyzes the wrong shape. That is
harder to notice than the crash.
Observed failure
Seen on 8x MI355X, TP8, a
GlmMoeDsaForCausalLMcheckpoint(
index_topk=2048), with--enable-profile-cuda-graphandSGLANG_GRAPH_BATCH_CAPTURE=1:followed by
Received sigquit from a child processand teardown of the processtree — the server never reaches ready.
The stack pins the mechanism down without any guesswork. On the v0.5.18 tag
this was captured on (
71de97b),decode_cuda_graph_runner.pyreads:The frame is at line 1113, inside the
else, which is only reachable whendsa_variant is not None. So dual-graph capture was active anddsa_variantshad two entries: 8 buckets x 2 variants = 16 callbacks into an 8-element list.
(#38993 has since generalized
dsa_variant/dsa_variantsintoattention_variant/attention_variantsand moved the label set behindAttentionGraphVariants.capture_labels. The loop structure, the callbackcount, and the overrun are unchanged; this PR is written against the renamed
form, the trace above against v0.5.18.)
Measured against that:
capture_bswas[1..8](8 buckets), 64 sidecar traceswere written (8 buckets x 8 TP ranks) before the failure, and every rank raised
on its 9th callback with
_profile_bs_idx == 8. Each rank keeps its own index,so all of them fail the same way.
Note that the speculative-decoding config in that run (EAGLE, 5 steps) is not
involved:
EAGLEDraftCudaGraphRunneris a separate instance with its own_profile_bs_list, so it cannot advance this runner's index. Consistent withthat, only
DecodeCudaGraphRunnertraces were produced.Reproduced in three profiling rounds across two independent sessions, so it is
not intermittent. Environment: sglang v0.5.18 (
71de97b), torch2.9.1+rocm7.2.0, ROCm 7.2.0, Python 3.10.12, 8x MI355X (gfx950).
Reproduction
Models with neither variant dimension produce exactly
len(capture_bs)callbacks, which is why this path has looked healthy so far.
Modifications
One file,
python/sglang/srt/model_executor/runner/decode_cuda_graph_runner.py.Name the trace after the shape actually being captured instead of deriving it
from a monotonic counter.
capture()records the current(bs, lora, attention)combination right before eachcapture_one_shape, andon_trace_readyreadsit. There is no index left to overrun, and each trace is labeled with the shape
it actually contains. A sequence-number fallback keeps the callback safe if it
ever fires without a tag set.
Choosing a tag over "make the list the right length" is deliberate: the list
approach would need the variant lists computed twice, in
_init_profile_context_and_memory_recordand again incapture(), and the twowould have to stay in agreement as dimensions are added. That is the failure
mode this bug already is.
Backward compatibility
For models with neither variant dimension the emitted names are byte-identical
to before (
DecodeCudaGraphRunner_bs_52_rank0.json.gz), so tooling that parsesthese filenames is unaffected. Names only gain a suffix in the configurations
that previously crashed:
bs_8_dense/bs_8_sparseforward_width > 1)bs_8_lora/bs_8_noloraDSA models now emit twice as many trace files as there are buckets. That is the
point of the fix — previously the run crashed, so there was no smaller-output
alternative — but it is a visible change in disk usage for anyone using this
flag on a DSA model.
Scope
SGLANG_GRAPH_BATCH_CAPTUREandSGLANG_ENABLE_CUDA_GRAPH_CAPTURE_TRACEdefault to
Falseinenviron.py, and the block additionally requires--enable-profile-cuda-graph. Serving without profiling never reaches thiscode.
producer and there is no consumer.
_set_profile_capture_tagonly reads the env flags and assigns, so it is safeon every runner that inherits
capture(). The EAGLE / frozen-KV /multi-layer draft runners all set
record_nolora_graph = Falseandattention_graph_variants = None, so both dimensions collapse to a singleentry and their filenames stay
bs_{bs}exactly as before.UnoDecodeCudaGraphRunneris the one inheritor that opts a dimension in, andit is the one that needs the fix.
PrefillCudaGraphRunnerderives fromBaseCudaGraphRunnerand has nograph-capture profiling block, so it is untouched.
MultiLayerEagleMultiStepDraftExtendCudaGraphRunnercallsbackend.capture_one()directly, bypassingcapture(). It never sets_profiler, so it produced no traces before this change and produces noneafter — unchanged behavior.
Accuracy Tests
Not applicable: the change only affects trace filenames on the graph-capture
profiling path. No kernel or model forward code is touched, and the modified
block does not execute unless profiling is explicitly enabled.
Speed Tests and Profiling
Not applicable to serving throughput, for the same reason. The added work is one
env-flag read and a string join per captured shape (tens of calls per server
start), inside a profiling-only path that already runs a full torch profiler
cycle per shape.
Testing
three profiling rounds across two independent sessions.
capture()for all four shapes in thecompatibility table and confirmed the new scheme yields one unique, correctly
attributed filename per callback, and that the no-variant case reproduces the
old names byte for byte.
GlmMoeDsaForCausalLMcheckpoint enters thedual-graph path: startup logs
[dense-decode] DSA dual-graph enabledbeforecapture begins.
pre-commit run --files python/sglang/srt/model_executor/runner/decode_cuda_graph_runner.pypasseswith no modifications.
No unit test is added: exercising this path needs a multi-GPU ROCm host and a
DSA-architecture checkpoint, which CI does not cover. Happy to add one if
reviewers would like the naming logic factored out far enough to test on CPU.
Checklist
CI States
Latest PR Test (Base): ❌ Run #35088934593
Latest PR Test (Extra): ❌ Run #35088934548
Latest PR Test (AMD ROCm 10): ❌ Run #35088934880