You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Enabling --grammar-backend xgrammar (default capturable grammar) makes the server hang forever during decode CUDA-graph capture when the largest capture batch size times --speculative-num-draft-tokens exceeds 128 logits rows. The hang is 100% reproducible (5/5 starts) on an 8x B300 node (TP=8, NVLink with fabric manager / NVLS) and does not reproduce on H100 or H200 nodes whose configurations stay at or under 128 rows. The same B300 configuration is healthy without the grammar backend, and healthy with --grammar-backend xgrammar --disable-capturable-grammar.
The variable that matters is max-num-seqs 64: capture sizes are derived from it, so the graph list becomes [1, 2, 4, 8, 16, 24, 32, 40, 48, 56, 63, 64] and the MTP verify captures are 63 x 4 = 252 and 64 x 4 = 256 logits rows.
What happens
Capture runs largest-first. The bs=64 graph captures fine (~14 s). The bs=63 capture never completes: no further engine log lines, all 8 ranks spin at ~1 core each, and the process never becomes ready (our startup probe eventually kills it; every restart hangs at the same point). The last log lines are always (the FutureWarning is emitted once per rank during the bs=64 warmup forwards):
[AutoTuner]: No tuned config covers trtllm_batch_decode_mla input_shapes=((252, 1, 8, 512), (252, 1, 2052), (252,), (252, 1, 8, 512), (252,)); falling back ...
[AutoTuner]: No tuned config covers trtllm_batch_decode_mla input_shapes=((63, 1, 8, 512), ...); falling back ...
torch/distributed/c10d_logger.py:83: FutureWarning: `torch.distributed.all_gather_into_tensor` is deprecated.
That FutureWarning never appears on the H100/H200 configurations. It comes from LogitsProcessor._get_logits — the full-vocab logits gather uses the Triton RSAG symmetric-memory kernel only up to _LOGITS_AG_MAX_TOKENS = 128 rows (runtime/layers/logits_processor.py) and falls back to torch.distributed.all_gather_into_tensor (NCCL) above that. With a grammar backend enabled that gather is recorded into the captured decode graph. The node's fabric manager logs NVLink multicast team setup/teardown (NVLS registration) in the same minute as every stall.
Working / failing matrix (same image, same model, same flags except where noted):
GPUs
max-num-seqs
rows (x4 draft)
grammar
result
8x H100
16
64
xgrammar
healthy
8x H200
32
128
xgrammar
healthy
8x B300
64
252/256
xgrammar
hangs at bs=63 capture, 5/5
8x B300
64
252/256
none
healthy
8x B300
64
252/256
xgrammar + --disable-capturable-grammar
healthy: all 12 graphs captured in 76 s, NCCL fallback included
8x B300
64 capped: --max-cudagraph-capture-size 32
128
xgrammar
healthy
What we ruled out
The hostfunc load_inline extension and the xgrammar Triton bitmask kernel: the bs=64 graph, which contains both, captures and completes.
A Triton compile problem for a non-multiple-of-16 batch: the Hopper configurations capture bs=15/31 with the same kernels.
The NCCL fallback inside capture on its own: with --disable-capturable-grammar (eager grammar buffers, everything else identical) the same 252/256-row graphs capture and the server is healthy.
So the hang needs both the capturable grammar path (CapturableGrammarExecutor: the side stream forked from the captured forward plus the cudaLaunchHostFunc nodes) and the >128-row graphs whose logits gather is the c10d/NCCL fallback rather than the RSAG symmetric-memory kernel. The bs=64 graph, which already contains both, captures; the second such graph (bs=63) is the one that never returns. We have not captured a stack of the hung ranks, so we cannot say which call blocks.
Workaround
Either --max-cudagraph-capture-size 32 (keeps every captured graph at or under 128 rows, so the gather stays on the RSAG kernel; batches above 32 run eager) or --disable-capturable-grammar (keeps full-size capture, loses the overlapped grammar fill).
Possible fixes
Size _LOGITS_AG_MAX_TOKENS from the capture configuration so captured graphs never take the c10d fallback when a grammar backend is on, or
make the capturable grammar path safe alongside the c10d fallback (or refuse that combination with a clear error instead of hanging).
Summary
Enabling
--grammar-backend xgrammar(default capturable grammar) makes the server hang forever during decode CUDA-graph capture when the largest capture batch size times--speculative-num-draft-tokensexceeds 128 logits rows. The hang is 100% reproducible (5/5 starts) on an 8x B300 node (TP=8, NVLink with fabric manager / NVLS) and does not reproduce on H100 or H200 nodes whose configurations stay at or under 128 rows. The same B300 configuration is healthy without the grammar backend, and healthy with--grammar-backend xgrammar --disable-capturable-grammar.Environment
Reproduction
The variable that matters is
max-num-seqs 64: capture sizes are derived from it, so the graph list becomes[1, 2, 4, 8, 16, 24, 32, 40, 48, 56, 63, 64]and the MTP verify captures are 63 x 4 = 252 and 64 x 4 = 256 logits rows.What happens
Capture runs largest-first. The bs=64 graph captures fine (~14 s). The bs=63 capture never completes: no further engine log lines, all 8 ranks spin at ~1 core each, and the process never becomes ready (our startup probe eventually kills it; every restart hangs at the same point). The last log lines are always (the FutureWarning is emitted once per rank during the bs=64 warmup forwards):
That FutureWarning never appears on the H100/H200 configurations. It comes from
LogitsProcessor._get_logits— the full-vocab logits gather uses the Triton RSAG symmetric-memory kernel only up to_LOGITS_AG_MAX_TOKENS = 128rows (runtime/layers/logits_processor.py) and falls back totorch.distributed.all_gather_into_tensor(NCCL) above that. With a grammar backend enabled that gather is recorded into the captured decode graph. The node's fabric manager logs NVLink multicast team setup/teardown (NVLS registration) in the same minute as every stall.Working / failing matrix (same image, same model, same flags except where noted):
--disable-capturable-grammar--max-cudagraph-capture-size 32What we ruled out
load_inlineextension and the xgrammar Triton bitmask kernel: the bs=64 graph, which contains both, captures and completes.--disable-capturable-grammar(eager grammar buffers, everything else identical) the same 252/256-row graphs capture and the server is healthy.So the hang needs both the capturable grammar path (
CapturableGrammarExecutor: the side stream forked from the captured forward plus thecudaLaunchHostFuncnodes) and the >128-row graphs whose logits gather is the c10d/NCCL fallback rather than the RSAG symmetric-memory kernel. The bs=64 graph, which already contains both, captures; the second such graph (bs=63) is the one that never returns. We have not captured a stack of the hung ranks, so we cannot say which call blocks.Workaround
Either
--max-cudagraph-capture-size 32(keeps every captured graph at or under 128 rows, so the gather stays on the RSAG kernel; batches above 32 run eager) or--disable-capturable-grammar(keeps full-size capture, loses the overlapped grammar fill).Possible fixes
_LOGITS_AG_MAX_TOKENSfrom the capture configuration so captured graphs never take the c10d fallback when a grammar backend is on, or