Repository navigation
Conversation
RuneFang
requested review from
Ying1123,
alphabetc1,
hanming-lu,
hnyls2002,
hzh0425,
ispobock,
merrymercy,
xiezhq-hermann and
yizhang2077
as code owners
July 31, 2026 06:42
Contributor
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
This was referenced Aug 8, 2026
detain
added a commit
to detain/sglang
that referenced
this pull request
Oct 29, 2026
… / qsaring / hicachectrl), header bump to qwennext-stack-295-2026-10-28, tip 44134b3, CAVEATS re-pin table + sgl-project#43216 boot-needle bullet + sgl-project#33029 NOT-NEEDED bullet + merge-sgl-project#5 owed refresh 21 picks, ZERO conflicts at the integrator; per-wave file-set and byte-parity proofs, entry-209 subsumption map (290), the QSA remedy adjudication with the layout-equivalence proof (285/286/287), the entry-199 non-interference machine check (294), the flag-collision record (295) and the full battery attribution are in the entries. Nothing pushed.
detain
added a commit
to detain/sglang
that referenced
this pull request
Oct 29, 2026
…amilies' blocks deleted), sgl-project#38355 moved to Section R as MISDIAGNOSED (C11 entry closed, five-leg citation), sgl-project#33029 moved to Section R as NOT-NEEDED, S-RACE tripwire queued post-merge, sgl-project#46191 ring-decision cross-reference updated
Contributor
|
Thanks @RuneFang. Closing this because it has had no updates in 91 days. Reopen it if the work is still relevant. Some directories moved recently, so an older branch may need retargeting: |
3 of 5 tasks
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.
Fix HiCache TP
_all_reduce_attn_groupsdeadlock in_get_new_batch_prefill_rawTL;DR
HiRadixCache.check_prefetch_progress()issues a TP-group collective(
_all_reduce_attn_groups) but is called from inside thefor req in self.waiting_queueloop ofScheduler._get_new_batch_prefill_raw(). That loop's trip count is a functionof per-rank local state (
get_num_allocatable_reqs()→available_size()of the KV-cache token pool), so different TP ranks can call the collective a
different number of times in the same scheduler tick. Once the NCCL collective
sequence desyncs, the whole TP group deadlocks (see the exact stack in
#30760).
Fix: pre-compute prefetch progress once per tick for the entire (already
broadcast)
waiting_queue, in a newHiRadixCache.bulk_check_prefetch_progresshelper. The scheduler loop body then only does a local
dict.get()and neverenters a collective, so its per-rank trip count is irrelevant to collective
ordering.
Closes #30760.
Root cause (with code pointers on
main)Scheduler._get_new_batch_prefill_raw()(
scheduler.py:~3030) walksthe waiting queue and short-circuits on
batch_is_full:get_num_allocatable_reqs()returnsreq_to_token_pool.available_size() // …, which is not synchronizedacross TP ranks — slot allocation and release happen locally per rank, so
the value can drift by one for a single tick.
check_prefetch_progress→can_terminate_prefetchunconditionally calls_all_reduce_attn_groups(seehiradix_cache.py:can_terminate_prefetch).So the number of collectives fired per tick is
min(len(waiting_queue), get_num_allocatable_reqs_for_this_rank).If rank 0 sees one more allocatable slot than rank 1 in a given tick,
rank 0 executes the loop body one more time and fires the collective one
more time. NCCL blocks rank 0 waiting for the peers to join the
(N+1)-th collective; the peers meanwhile move on to the next schedulertick and call
broadcast_pyobj/ forward. Permanent hang.Observed py-spy stack on the hung rank (matches the issue verbatim):
Fix
python/sglang/srt/mem_cache/hiradix_cache.pyNew public method on
HiRadixCache:The collective count is now exactly
len(req_ids)on every rank becausewaiting_queueis already TP-broadcast, soreq_idsis identical everywhere.Semantics of
check_prefetch_progress(and its side effects — poppingongoing_prefetch, updatingprefetch_loaded_tokens_by_reqid, etc.) arecompletely unchanged.
python/sglang/srt/managers/scheduler.py::_get_new_batch_prefill_rawCall
bulk_check_prefetch_progressbefore the loop, then convert theloop's collective call into a pure dict lookup:
Why this is correct
check_prefetch_progresscalls per tick as before(one per req in
waiting_queue), just moved out of the loop.prefetch_doneboolean returned to the loop body.early on
batch_is_fulland skippedcheck_prefetch_progressfor tailreqs; now we always call it for the whole queue. This is intentional — it
is the invariant that fixes the deadlock. The extra calls do not change
batch composition and are bounded by
len(waiting_queue).Impact analysis (why this is safe)
enable_hicache_storage=False)pop_prefetch_loaded_tokensorder / storage_hit_lengthHiRadixCachetree_cache implementations (UnifiedRadixCache,HiMambaRadixCache, …)hasattr(...)guardlen(waiting_queue); same big-O as beforebatch_is_fullReproduction
The scenario described in #30760 is a per-tick race between per-rank
available_size()drift and a collective insidecheck_prefetch_progress.Waiting for the race to occur naturally requires long-running production
traffic, which is why the original issue has no minimal repro. To make the
race deterministic and reviewable, a companion branch ships a
zero-source-change reproducer:
That branch contains only the
repro_30760/directory (launch script,usercustomize.pyinjector, evidence logs and py-spy stacks). It does nottouch any source under
python/sglang/. Applying this PR on top of therepro branch turns the reproducer's verdict from
DEADLOCKEDtoALL REQUESTS SUCCEEDED.Key idea of the injector (why it faithfully reproduces #30760)
The injector monkey-patches a single method and gates the extra collective on
the call site, not on any timing:
The stack-based gate directly encodes the causal chain from the root cause:
check_prefetch_progress)_get_new_batch_prefill_raw's per-req loopbulk_check_prefetch_progress(new)Batch composition is not perturbed (no
get_num_allocatable_reqspatching, nois_emptyoverride), which is why the fixed run does not exhibit anyunrelated forward-time hangs.
Evidence (captured on the repro branch)
Pre-fix, rank 0 (py-spy dump — matches the stack reported in #30760):
Pre-fix, other TP ranks: already advanced one tick ahead into
event_loop_overlap(running the nextforward/broadcast_pyobj), so theNCCL collective sequence is desynchronized -> permanent hang.
Post-fix, all ranks: every request in the 12-request burst returns 200 OK
with sub-second wall time;
/healthresponsive throughout the run; noheartbeat loss in the server log.
FAQ: Why not just synchronize
available_size()across ranks?available_size()is by design a per-rank local counter(
len(free_slots)). Turning it into a globally consistent value meansinjecting a new collective into the scheduler's per-tick inner loop and
polluting the semantics of a deliberately local counter.
It would also only paper over the symptom — the root cause is the pattern
"a per-rank-state-gated loop containing a collective", which exists
elsewhere too (e.g.
_try_hicache_queue_load_back).This PR adds zero new collectives: it just moves the collective that
already lives inside
check_prefetch_progressinto a loop whose input(the broadcast
waiting_queue) is already cross-rank consistent,removing the coupling structurally.
CI States
Latest PR Test (Base): ❌ Run #30612117737
Latest PR Test (Extra): ❌ Run #30612117623