Fix memory pool index error - #616
Merged
Merged
Conversation
timethink
pushed a commit
to timethink/sglang
that referenced
this pull request
Mar 9, 2025
vschandramourya
pushed a commit
to vschandramourya/sglang
that referenced
this pull request
Feb 3, 2026
* Add private KimiK2Detector with parser fixes Key fixes: - Support hyphens in function names (e.g., agent__hf-search) - Support newlines in JSON arguments (re.DOTALL) - Fix concatenated tool calls parsing (use [^<]+ to prevent matching across <| token boundaries) - Add reset() method to prevent memory leaks between requests - Disable structural_tag support as workaround for grammar issues * Apply black formatting to kimik2_detector.py --------- Co-authored-by: Chenxi Li <41864925+ConnorLi96@users.noreply.github.com>
efschu
added a commit
to efschu/htsglang
that referenced
this pull request
Aug 6, 2026
…uble-free producers fixed + named free_slot refusal; residual crasher re-scoped to overlap-scheduler cross-stream race)
efschu
pushed a commit
to efschu/htsglang
that referenced
this pull request
Aug 6, 2026
…named via GPU coredump — int32 accept_index gather, size disagreement not corruption; default-off stability guard + hermetic tests; wedge family proven not LXC-caused on bare-metal host)
efschu
pushed a commit
to efschu/htsglang
that referenced
this pull request
Aug 6, 2026
Third wedge, first one measured with nvidia-smi while live: all three cards at 100% SM utilisation with 0% memory utilisation and power far below cap (176/130/162 W). That is a spin-wait kernel, not long-running work, which falsifies this note's own section-13 hypothesis that the queue was busy. After the cycle cap expired the cards fell to 0% / ~20 W and Bar1CollectiveAborted fired. All three ranks were host-blocked at the same line, all three GPUs spinning, and all three reported byte-identical barlink last-ops (broadcast:32, broadcast:24, all_gather:192512). Every 'the ranks disagree' explanation is now falsified three ways: census counts, barlink last-ops, and this. What remains is below the Python level - a sequence-number mismatch on the spinning collective, or a BAR1 flag write not becoming visible to the waiting peer - and separating those needs the device flag words, i.e. a coredump at abort time rather than a hot-path probe. Scope: this wedge is pre-existing (hunter-4 saw it; wedge 1 of this window hit the un-fixed build) and is a different failure from the sgl-project#616 index assert. Soak 2 of the fixed build: 15 min, 277 completions, zero index asserts, against a pre-fix envelope of 192 s.
efschu
pushed a commit
to efschu/htsglang
that referenced
this pull request
Aug 6, 2026
…arried predict via collective pairing shift — fused single broadcast at both sites incl. weightless-KV receive path; 23.5 min soak clean vs 192 s envelope; wedge-family instruments: flag snapshots, 4096-entry collective history, aligner, triage collector)
efschu
pushed a commit
to efschu/htsglang
that referenced
this pull request
Aug 6, 2026
…lution fixed — bare py-spy via shell PATH silently disabled every automatic wedge dump; wedge harvest: abort gate stages on time but raise is unbounded inside graph replay, single-cell round skew with rank 0 one collective ahead)
efschu
pushed a commit
to efschu/htsglang
that referenced
this pull request
Aug 6, 2026
…was structurally blind — status staged on the compute stream joined the wedge, and poll_status_word was never defined on the BAR1 transport so the watchdog's private-stream read silently skipped it; bounded staged wait + ported poll + Bar1CollectiveStalled after sustained expiries; wedge evidence: three ranks stuck in the same all_reduce with rank 0 at layer 6/3420160B vs ranks 1/2 at layer 5/12584960B against identical-size pre-wedge control)
efschu
pushed a commit
to efschu/htsglang
that referenced
this pull request
Aug 7, 2026
…DATED: rank-divergent prefix-cache evolution — evict_from_tree_cache + UnifiedRadixCache.load_back pinned to the binding rank's threshold so match_prefix stays rank-uniform; on-card: combined reuse+pressure load kills unfixed in 91 s with rank 0 at 265 vs peers 2048 tokens and a different op on dcp:0, fixed tree clean for 25 min (16x) with both triggers demonstrably live (459940 cached tokens / 1079 prefills); nine-run validation table incl. the two null arms documented in NOTE_616g)
efschu
pushed a commit
to efschu/htsglang
that referenced
this pull request
Aug 7, 2026
…p/owner.py:548 inside the collective window wedged all three ranks — build_dcp_weighted_kv_indices blocked on int(full_indptr[bs].item()) with no timeout while peers spun to the BAR1 cycle deadline (crash 2026-08-07 02:02, plus pre-fix specimens 08-06 16:40/17:46; desync falsified: census counters and 4096-entry collective histories byte-identical across ranks); fix wires the dead total_tokens bypass by forwarding the extend_prefix_lens_cpu mirror through update_single_wrapper and deriving the host total via _dcp_host_total_tokens — the crash dump's own locals carried the exact input [39166]; can-fail proven against the crashed tree; 5 owner-rule suites 58 passed/117 subtests identical base-vs-fixed; deployed sgl-project#616-B floor exonerated by diff scope + prior specimens)
efschu
pushed a commit
to efschu/htsglang
that referenced
this pull request
Aug 8, 2026
…raph replay ISOLATED_PREP moves out-of-graph attention prep onto a private stream. That prep WRITES buffers the captured graph READS -- cuda_graph_kv_indices and kv_indptr. Today the write-after-read ordering is guaranteed for free, because prep and replay share one FIFO compute stream: prep for step N cannot begin before replay for step N-1 has retired. Moving prep off that stream is precisely what removes the guarantee. The resulting failure does not crash and does not hang. It writes attention indices while the previous replay is still reading them, producing silently wrong output. A stability test scores such a build as a complete success, because the wedge it was built to remove is genuinely gone. Stability evidence is therefore necessary and not sufficient, and this bracket is the missing half. Uses the existing sgl-project#616 index_race_guard: snapshot() before the replay launch, check_stable() after. Both are enqueued on the current stream, so they are ordered around the graph by construction; per the guard's own contract a same-stream comparison must report zero, and any non-zero count is positive proof that another stream wrote the tensor in between. Counting is device-side, so the instrument adds no host sync and cannot perturb the ordering it is measuring. Expected readings, the middle arm being the one that makes the instrument trustworthy rather than merely reassuring: baseline, no isolated prep -> 0 (no false positives) isolated prep, WAR event omitted -> >0 (proves it can fail) isolated prep, WAR event in place -> 0 (the fix is ordered) An instrument never shown to fire is not evidence. Inert unless SGLANG_INDEX_RACE_GUARD=1, which defaults to False. Buffer resolution degrades to guarding fewer tensors on any missing attribute rather than raising, since a falsifier that can take down serving is worse than no falsifier. Tested (CPU, no GPU held; all three cards were held by another session): - AST parse + py_compile of the modified module: OK - helper returns [] for no backend and for a backend with no attributes - single-backend shape yields cuda_graph_kv_indices + kv_indptr - multi-backend shape additionally yields per-step kv_indptr[i] - zero-numel tensors and non-tensor attributes are skipped - no degradation path raises NOT yet exercised on a GPU: the three-arm table above is unrun, so this commit adds the instrument only and claims no result from it.
efschu
added a commit
to efschu/htsglang
that referenced
this pull request
Aug 9, 2026
…trol message
Reverts the request-injection vehicle entirely. Injection put a SECOND
blocking channel -- a control message on the PP point-to-point chain --
alongside the flip's group reduction, with no global order between them,
and it deadlocked twice on metal:
async forward, arm in the same pass:
rank0 on_round -> bounded_collective, ranks1-2 -> _pull_raw_reqs
synchronous forward (the "fix"):
rank0 -> point_to_point_pyobj send, ranks1-2 -> hidden-states recv
Both are instances of the fork's standing collective family (sgl-project#431, sgl-project#616,
sgl-project#639): two independent blocking channels with no order between them.
Patching the ordering case by case (defer-by-one was the third attempt)
fights the pattern; removing the channel ends it by construction. A
control message riding the PP data chain can always be scheduled against
a rank that is currently blocked in the other channel.
The channel was never needed, because THE SIGNAL IS ALREADY REPLICATED.
Under PP every stage receives the same request stream over the chain and
runs the same scheduling decisions on it, so waiting_queue and
running_batch hold the same values on every rank -- visible directly in
the scheduler's log, where the three stages print identical
#running-req, #queue-req and #pending-token at the same timestamp. decide
is a pure function of those values, so every rank computes an identical
verdict and arms ITSELF via the same arm_phase_flip the RPC path calls.
Zero messages, no delivery, nothing to order.
The existing machinery then does what it was built for: ranks arm at
slightly different rounds, an armed rank enters the reduction only once
locally PARKED (owing no pipeline send), and peers converge on their own
arm+drain -- "MIN-skew is legal" (PhaseFlipRuntime.on_round). Skew is
absorbed; only DISAGREEMENT is fatal, and disagreement cannot arise from
identical inputs. The reduction's equality family (epoch, config_fp,
direction once armed) is the standing loud check on that assumption.
Evaluated from _phase_flip_on_round, immediately before the consensus
round that carries the arm, on every rank and in both loop families.
A refused arm releases the dwell clock so the policy can re-decide
rather than sit out a window it never used.
request_receiver.py and scheduler_pp_mixin.py are byte-identical to
their pre-policy state again; a test asserts that by source inspection,
so neither can regrow a policy hook or an internal-arm special case.
Tests: 313 passed in the sgl-project#631 flip family, CPU-only. New: three ranks
with identical replicated inputs produce identical arms; the policy
returns no request; a refusal releases the dwell.
Operational acceptance under load NOT yet achieved -- boot with this
design is running.
efschu
pushed a commit
to efschu/htsglang
that referenced
this pull request
Aug 16, 2026
…oes not Written against the code with no changes made, so engine work is ready-to-build when the boot cycle frees. ## The membership is not the problem Confirmed from the restored boot: tp_size=1, pp_size=3, dcp_size=1. So world_size=3 and the PP group is built as range(pp_group_idx, world_size, world_size // pp_size) = range(0,3,1) = [0,1,2] (parallel_state.py:3355-3372) -- already every stage. Generally, for tp_size>1 there is one PP group per TP position whose members are that position's pipeline ranks, which is exactly the set across which KV should be token-sharded. No new rank layout has to be invented at any topology. ## But _PP cannot be used as-is, and the reason is silent wrongness Attention backends read attn_dcp_size/attn_dcp_rank ONCE in the constructor and cache them; dcp_enabled is `get_dcp_group_no_assert() is not None and dcp_size > 1` (dcp_group_guard.py:14-19). Given a communicator that is not a DCP-TYPED group they cache dcp_size=1, uneven_dcp_owner_bounds() returns None on EVERY rank, the owner rule is bypassed, and every rank treats every global slot as its own local row -- all ranks write the same token to the same row, last write wins, each reads the whole sequence as local. dcp_group_guard.py:21-27 states it exactly: "The result is silently wrong output. There is no error and no hang to follow." So B1 is a DCP-typed group whose rank sets come from the PP dimension instead of from chunking TP groups. ## Change point and the ordering constraint parallel_state.py:3143-3159 builds _DCP by chunking each TP group; with tp_size=1 every TP group is one rank, so every DCP group is one rank -- which is why PP prefill runs dcp_size=1 with no usable group. LOAD-BEARING ORDERING: _DCP is built at :3152, _PP at :3365, AFTERWARDS. The new construction therefore MUST NOT read _PP; it must recompute the same pure arithmetic inline. Reading a group that does not exist yet is precisely the ordering class the DCP guard exists to catch. The group must exist before attention-backend construction, and server_args.dcp_size must equal the participating stage count, so that assert_dcp_group_formed (dcp_group_guard.py:63) is a no-op -- and if the ordering is ever broken it names the construction step rather than failing downstream. ## Refusal conditions, each traced to an existing guard dcp_size != participating stages -> dcp_group_guard.py:63-100 page_size != 1 -> dcp_group_guard.py:170-177 PD decode without uneven-TP+mooncake -> dcp_group_guard.py:139-166 tp_size>1 with ambiguous shard set -> NEW, no existing guard page_size==1 is already mandatory for Option A independently (sgl-project#706: a multi-token page would span owner ranks), so the two constraints agree rather than compete. ## Recorded as unknown rather than assumed benign The sgl-project#616 group-MIN-floor interaction, whether an extra communicator carries a budgeted per-group cost, and whether any census/registry must be told about a new group. Survey in flight. 190 tests green, hermetic, codespell clean. No code changes to engine paths.
efschu
pushed a commit
to efschu/htsglang
that referenced
this pull request
Aug 17, 2026
…nd fold the B1 survey ## sgl-project#545 -- attach, resize and detach the HiCache disk tier at runtime mem_cache/disk_tier_control.py, flag-gated, 14 hermetic tests. Built through the existing HiCacheStorage/HiCacheFile abstraction: the controller owns the BUDGET AND LIFECYCLE, the bytes stay with the backend. No second directory layout is introduced, so the sgl-project#706 canonical-page store (not on this branch yet) shares the same backend rather than fighting it. Three rules, each because its violation is SILENT: 1. SHRINK EVICTS DOWN TO THE BOUND; IT NEVER TRUNCATES. A truncated page is not a miss -- it reads back short and wrong, which no cache-hit metric shows. Test: 90 MB across nine pages shrunk to 50 MB evicts four WHOLE pages; every surviving page keeps its full size. 2. DETACH IS REFUSED WHILE ANY PAGE IS ONLY-COPY HERE, naming the count and an example key. Detaching would destroy them and the later miss would have no visible cause. force=True exists for an operator who has decided the loss is acceptable, and says so loudly in the log. 3. A SHRINK THE EVICTOR CANNOT HONOUR IS REFUSED, NOT PARTIALLY APPLIED. Accepting a bound the tier is not under would make the reported capacity a lie, and the only way to reach it would be truncation. Capacity is left unchanged on refusal. Also: a second attach is refused (two directories under one controller would give the evictor two budgets and one bound), operations on an unattached tier refuse, and the flag gates every mutation because attaching or resizing a live L3 changes what the evictor is allowed to delete. ## sgl-project#616 -- B1 survey folded into DESIGN_704, revising three things I verified the survey's two load-bearing claims at file:line before folding. 1. "A DCP-TYPED GROUP" WAS IMPRECISE. There are no typed group classes: every group is a bare GroupCoordinator (parallel_state.py:564) via init_model_parallel_group (:2550). "Typed" here means exactly a module global, a named getter, and a routing flag. B1 subclasses and registers nothing -- a smaller and more precise change than the earlier draft implied. 2. THE PRECEDENT ALREADY EXISTS IN-TREE; REUSE IT. initialize_phase_flip_secondary_groups (:3422-3517) ALREADY creates a DCP group in the same world as a PP group. Verified at :3470-3482: it plans flip_tp -> flip_dcp -> flip_pp in fixed order, then :3484-3497 verifies a world-wide manifest by all_gather with an equality check BEFORE creating anything. My earlier draft proposed reconstructing PP rank arithmetic inline; that is unnecessary -- the precedent computes the same range(idx, world_size, num_pp_groups) layout and already sequences it. 3. THE ORDERING WINDOW IS NARROWER THAN FEARED. _DCP at :3152, _PP at :3365, and only the kvso-spill block (:3185-3188) reads _DCP between them. Routing precedence must also be chosen deliberately: get_dcp_group resolves flip route first (:2709), then kvso-spill (:2718), then primary (:2720). Inheriting whichever branch matches first is how a decoupled read would silently land on the flip's secondary group during a phase change. ## NAMED PREREQUISITE, before B1 rather than inside it pp>1 AND dcp>1 is not refused anywhere in the group path, and ParallelContext.dcp_enabled (runtime_context.py:331-337) returns True whenever a DCP group exists with dcp_size>1. Creating B1's group therefore makes dcp_enabled TRUE ON PP PREFILL RANKS, contradicting the assumption dcp_group_guard.py:38-42 states in prose. The failure shape is what makes this worth naming: the guard itself would still PASS, because both sides would read the same new size. What breaks is every OTHER consumer of the documented invariant. The combination is unreachable today, so the contradiction is latent -- and B1 is exactly the change that makes it live. Decide it before building: either accept pp>1 and dcp>1 deliberately and audit the dcp_enabled consumers, or refuse the combination and give B1 its own flag instead of overloading dcp_size. 35 mem_cache tests green, hermetic, ruff + codespell clean.
efschu
pushed a commit
to efschu/htsglang
that referenced
this pull request
Aug 17, 2026
…uting slot Built per the corrected spec after the sgl-project#616 survey. Reuses the phase-flip precedent instead of inventing a pattern; 17 hermetic tests (CVD=""), no CUDA, no torch.distributed, no process group. ## Own global + getter + routing flag, NOT dcp_size Per the operator decision. There are no "typed group" classes in this tree -- a type IS a module global, a named getter and a routing flag (sgl-project#616 survey, parallel_state.py:564 / :2550). So B1 adds _DECOUPLED_KV, _DECOUPLED_KV_ACTIVE, get_decoupled_kv_group{,_no_assert} and set_decoupled_kv_active. Overloading dcp_size would have made ParallelContext.dcp_enabled (runtime_context.py:331-337) report True on PP prefill ranks, contradicting the invariant dcp_group_guard.py:38-42 documents -- and the guard would still PASS, so nothing would announce it. The separate flag leaves that invariant TRUE and needs no consumer audit. ## Creation reuses the precedent, verbatim in shape plan -> world-wide manifest all_gather -> equality check -> create, matching initialize_phase_flip_secondary_groups (:3466+). Nothing is created before every rank agrees on the same plan; a divergent create order is the rank-divergent-collective family and dying at the check is the cheap failure. _manifest_salt is carried for the same reason the precedent carries it: so a test can prove the check CAN fail. Membership is the PP layout -- range(idx, world_size, world_size // pp_size) -- computed INLINE. That is load-bearing: _DCP is created at :3152 and _PP only at :3365, so _PP does not exist yet at B1's creation point. A test forces _PP to None and asserts the plan is unchanged. ## The sgl-project#616 labeled gap, closed loudly refuse_pp_dcp_combination fires in the group path BEFORE any group is created: pp>1 with dcp>1 now raises by name instead of staying latent-but-unexercised. ## Routing precedence, pinned rather than read B1 slots deliberately LAST before the primary fall-through: flip (:2709) -> kvso-spill (:2718) -> B1 -> _DCP. It claims only what would otherwise reach _DCP, so the flip keeps the TP decode phase (B1 is a PP PREFILL mechanism and cannot be legitimately active there) and a spill forward keeps its dedicated serial communicator. Inactive is byte-identical to the pre-#704b path. CAN-FAIL PROVEN, not asserted: moving the B1 branch above flip/spill in the source makes exactly three tests fail (flip-outranks, spill-outranks, and the full precedence table); reverting restores 17 passed. First-match routing is the failure mode my own fold named, so the position is pinned by a table rather than by reading order. ## Notes Structure verified by ast membership, not by "it parses" -- the lesson from the sgl-project#464 near-miss where a helper insertion silently turned a method into a nested function. All six new helpers confirmed module-level and the precedent function intact. parallel_state.py deliberately NOT run through ruff format (it reformats unrelated legacy code); diff is 162 insertions, 1 deletion. New annotations match the file's existing typing.List/Optional style rather than mixing in collections.abc. Still NOT wired to a caller: nothing arms set_decoupled_kv_active yet, and the attention path does not read the group. That is the next slice and it needs the #704b pool work (R6) first.
efschu
pushed a commit
to efschu/htsglang
that referenced
this pull request
Aug 28, 2026
…request Boot 12 (7b855f6) died on PP0 at 21:46:06 with `AssertionError: reusing request must be chunked or have committed KV` (memory_pool.py:395), via get_new_batch_prefill -> prepare_for_extend -> alloc_for_extend -> alloc_req_slots -> HybridReqToTokenPool.alloc. ROOT, from the ledger and not from inference. One second earlier the same rank logged, for the same rid: sgl-project#969 voided-request release failed for 5708abdd579e4f7097ab97ed796481ea: Committed KV cache already freed (self.kv_committed_len=4096) `_release_voided_request`'s never-raise contract caught that, logged it, and fell through to `reset_for_retract`. The contract is right -- an instrument that raises while cleaning up after a divergence turns one defect into two -- but it only decided that this frame does not PROPAGATE the failure. It never decided what the half-released request IS afterwards. The answer was: a request still holding `req_pool_idx`, whose `inflight_middle_chunks` and `kv_committed_len` the reset then zeroes -- and those two fields are the allocator's only evidence that a retained row is legitimate. It was re-queued, re-admitted one second later, and killed rank 0. The assert is correct and is the only thing that caught this. The defect is upstream of it. CLASS: warn-then-continue. The compensator converts a partial failure into a live, corrupt, re-admissible object. This is the catalogued `warn-then-continue swallow` shape, and the cure is not to remove the swallow but to make the post-failure STATE explicit. FIX, at the junction: on a failed release, hand the row back -- mamba slot first, then the req-pool row, the ordering `_release_dynamic_chunk_probe` already documents. The reset below drops prefix, pages and geometry anyway, so the request re-prefills regardless; the row it keeps buys nothing and justifies nothing. `free_slot` carries its own membership scan and REFUSES an already-free row (sgl-project#616), so a release that failed AFTER returning the row is caught by name and logged instead of corrupting the free list -- both outcomes are reported with the rid. NOT CLOSED, and deliberately not guessed at: why `pop_committed_kv_cache` found `kv_committed_freed` already True at `kv_committed_len=4096`. That producer is a second defect. This change contains it and names it in the log (`sgl-project#993 INCOMPLETE RELEASE DISOWNED`), so the next boot measures how often a release fails at all -- a number no boot of this family has ever had. FUTURE CHECK: any new raise inside `release_req` now ends in a disowned row plus a named line, not in an allocator assert three passes later. Evidence: desk. py_compile + import smoke + wiring assertion on `_release_voided_request`. Belegstufe: DESK-BEWIESEN.
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.
No description provided.