docs: update supported models - #719
Merged
Merged
Conversation
Ying1123
approved these changes
Jul 24, 2024
timethink
pushed a commit
to timethink/sglang
that referenced
this pull request
Mar 9, 2025
cherryblo
added a commit
to cherryblo/sglang-project
that referenced
this pull request
Jul 2, 2026
modify bench_serving param
efschu
added a commit
to efschu/htsglang
that referenced
this pull request
Aug 17, 2026
…t the cutover; bound the read spike sgl-project#719 -- THE REBIND, and the finding that shapes it. A REBIND IS NOT A POINTER SWAP. The host pool is CONSTRUCTED FROM the device pool (hybrid_pool_assembler.build_kv_host_pool(kv_pool=...)), so its layer_num and buffer sizes are that phase's. The two phases have different per-rank layer counts -- a PP stage holds 7/5/4 of the 16 attention layers, the TP stack holds all 16 -- so repointing the controller at the other phase's device pool while its host pool still describes this phase's is a NEW corruption wearing the fix's clothes: matching row ids, mismatched widths, and the copy RUNS. The rebind therefore refuses unless handed a host pool whose shape matches the incoming device pool, and phase_pools_for refuses when the boot did not build one -- which is today's real state, reported with its reason instead of proceeding onto the wrong pool. Supplying that second host pool is a boot-time question (it costs host RAM, the binding constraint per DESIGN_706 C1) and is named, not mocked away. THREE READERS, ALL OR NONE. The pool identity is captured in three places (controller: mem_pool_device / _hybrid / _allocator; radix cache: hybrid_kv_cache / kvcache / host pool; scheduler: token_to_kv_pool_allocator). A rebind that moves some and not others is strictly worse than none: the readers then name different memory for the same row id and EVERY CALL STILL SUCCEEDS. So the rebind stamps a generation on each reader and a coherence check compares them afterwards -- the failure is invisible to inspection, so it is verified by counter. A stamp that fails mid-way rolls the state to a generation nothing can match, so the tier cannot re-arm onto a torn binding. THE PAYOFF, wired through sgl-project#718's predicate: "disarmed" now asks whether the ACTIVE phase is the BOUND phase, not whether TP is active. With no rebind those are the same question (the binding is always the boot phase), so the default path is byte-identical -- sgl-project#718's 12 tests pass unchanged. After a coherent rebind the device tier is usable in the phase it moved to. Call site: after the stack swap in _cutover (the mirror of sgl-project#703's writeback, which runs before anything moves because it reads the OUTGOING pools). Refusal is logged, not raised: a refused rebind is SAFE by construction because the binding does not move and sgl-project#718 keeps the tier disarmed, while a raise at the seam takes down an instance that was serving fine. sgl-project#720 -- THE READ SPIKE, bounded and charged. Every storage read took its target from host_pool.get_dummy_flat_data_page(), a fresh pinned tensor per read that the joint budget cannot see (it accounts pools declared at attach; this is neither). ReadBufferPool is a fixed ring, allocated once, declared through check_and_register_pinned_post and unregistered on close. Exhaustion falls back to a fresh allocation -- today's behaviour, counted -- rather than blocking, because stalling a prefetch worker to save memory trades a bounded spike for unbounded latency. SGLANG_HICACHE_READ_BUFFERS=0 (default) keeps the current path exactly. Tests (hermetic, CUDA_VISIBLE_DEVICES=""), 18 new, 137 in the family: * test_hicache_rebind_719.py (11) -- all three readers move together; a reader left behind is CAUGHT (planted deliberately, since without generations that state is invisible); an absent reader refuses the whole rebind; a failing stamp leaves the set unusable rather than half-moved; shape mismatch and unmeasurable shapes refused; a phase with no host pool refused WITH the reason; the disarm lifts only after a coherent rebind and re-arms on the return leg; unarmed is byte-identical. * test_read_buffer_pool_720.py (7) -- the falsifier pair (25 reads = 25 allocations today, 4 with a ring of 4), bounded overflow, a raised read still returns its buffer, the registry sees the ring and stops seeing it on close, off by default. Regression, same env, base c3e9487 vs this commit: unit/mem_cache 940 failed / 779 passed -> 940 failed / 894 passed (+115) unit/server_args 1 failed / 626 passed -> 1 failed / 639 passed (+13) unit/managers 4 failed / 2145 passed -> 4 failed / 2145 passed scheduler/test_phase_flip_runtime.py: 67 passed (the file the call site edits) phase_flip_runtime.py diff: 23 insertions, 0 deletions. Live validation of both joins F4-r4's window list; the flip+hicache boot they need is exactly the boot sgl-project#719 makes safe -- and sgl-project#719 cannot arm until that boot also builds the second phase's host pool. CAN-FAIL PROOF (mutation applied, suite re-run, reverted): Q1 coherence check always passes -> test_a_reader_left_behind_is_caught, test_a_failing_reader_leaves_the_set_unusable_not_half_moved Q2 shape check removed (the pointer-swap trap) -> test_shape_mismatch_is_refused, and ONLY that one Q3 partial rebind allowed (absent readers ignored) -> test_an_absent_reader_refuses_the_whole_rebind, and ONLY that one Q4 predicate ignores the binding (reverts to the raw TP-active test) -> 6 failures spanning BOTH suites, incl. test_disarm_lifts_only_after_a_coherent_rebind, test_the_return_leg_rebinds_back, and sgl-project#718's own test_disarmed_is_false_without_the_flip -- i.e. the tie between the two features is load-bearing in both directions Q5 ring never reuses (release drops every buffer) -> test_green_the_ring_allocates_once_and_reuses, test_concurrent_borrows_beyond_the_ring_fall_back, test_a_raised_read_still_returns_its_buffer Q6 ring not declared to the pinned registry -> test_the_ring_is_declared_to_the_pinned_registry, and ONLY that one Restored tree re-verified green after every mutation (30 passed).
efschu
added a commit
to efschu/htsglang
that referenced
this pull request
Aug 17, 2026
…the manifest desk-build DESIGN FIRST, and the design's first move is to notice what is already built. The sgl-project#706 canonical store IS a per-token-content snapshot of exactly the two things a conversation checkpoint needs: {hash} for a token's KV (all attention layers, geometry-free, cut at read time) and {hash}.mamba for the GDN state, both keyed by content alone and therefore already surviving a phase flip and a reboot. So slice 1 designs no payload format. What was missing is the SESSION level: which pages, which blob, at which position, under which sampling and template state. DESIGN_410_session_checkpoints.md carries the full document. THE sgl-project#212 LESSON IS THE LOAD-BEARING CONSTRAINT, not a caveat. A KV-only prefix is worth ZERO usable tokens on this hybrid checkpoint (sgl-project#703 re-derived it structurally and pinned it: the storage hit is the MINIMUM across pools with mamba registered TRAILING_PAGES, and the device-side match advances only at nodes carrying mamba_value). Three rules follow, and the module ENFORCES all three rather than documenting them: 1. the GDN anchor is REQUIRED on a hybrid model and refused at WRITE time -- a manifest describing a prefix that resolves to nothing is a bug best caught where it is created, not at branch; 2. a checkpoint may only claim a position where state exists. With --mamba-checkpoint-interval that is an absolute grid, so the position is SNAPPED DOWN and BOTH positions are recorded: a checkpoint that quietly covers fewer tokens than asked for is a wrong answer, not a rounding. A request that snaps to 0 is refused outright; 3. completeness is VERIFIED against the store before a branch seeds anything, because a manifest holds references and the tiers evict. FORMAT: versioned (refuse unknown versions rather than best-effort parse -- that refusal is what makes sgl-project#411 a converter question instead of a silent field-drop), deterministic serialisation so a manifest hashes reproducibly, and CONTENT-ONLY: a test asserts the serialised form contains no tp_rank, tp_size, pp_rank, pp_size or layer anywhere. That is what sgl-project#706 earned and what makes a manifest portable across phases, rigs and reboots. API + REFUSALS (sgl-project#510 canon, ADMIN_OPTIONAL on all four -- the level 51 existing state-changing routes use): POST /session_checkpoint, /session_branch, /session_rewind, GET /session_checkpoints. Mid-generation policy defaults to REFUSE (a checkpoint taken while tokens are appended describes a moving target and its tail pages may not be in the store yet); the permissive mode quiesces first, never snapshots concurrently. "Branch from a non-checkpoint" is impossible by construction rather than by check: branch takes a checkpoint_id, and those exist only for manifests that were written and verified. TIER TARGET: the manifest is metadata and does NOT live in the KV tiers; the payload it references follows the existing sgl-project#407 ladder with no new mechanism, with TieredGdnBlobStore (sgl-project#711) as the blob-half precedent. Named consequence, not smuggled: a checkpoint stays branchable only while its references survive eviction. PINNING them is slice 2 work. DESK BUILD (mem_cache/session_manifest.py): the versioned manifest, build with grid-snapping and the stateless refusal, deterministic dumps/loads, verify_against_store, verify_model_identity, and seed_plan -- the ordered fetch list a branch executes, verified UP FRONT, with the recurrent state fetched LAST so its absence can never look like a completed seed. Not built and not pretended: the radix prefix insert and blob restore themselves need live pools. Tests (hermetic, CUDA_VISIBLE_DEVICES=""), 18 new, 168 in the family. The completeness half runs against the REAL backend in canonical mode, three PP stages writing real pages, so sgl-project#706's invisible-until-complete rule is part of what is tested: a page only two of three stages wrote counts as MISSING, not as partial. The named red-first case -- a manifest whose page was evicted must refuse at branch rather than seed a silent partial prefix -- is test_a_manifest_whose_page_was_evicted_refuses_at_branch, with its sgl-project#212 twin test_a_missing_gdn_blob_refuses_too. BYTE-GATE SPECED, NOT RUN (section 7 of the design): A-vs-A first to establish the determinism floor, then straight-through generation vs checkpoint-branch- continue, compared as token IDS not text -- a divergence at P+1 is a state-restore bug, a later one is a sampling/template bug, which is why the comparison is per-token. Judge ids, never timing: the draft pool starts cold after a branch by design (sgl-project#706 C7). BOOT DEPENDENCIES, explicit (design section 8): canonical store flags + file backend + page_size 1; hierarchical cache with the disk tier; --mamba-checkpoint-interval for the grid; the flip+hicache boot sgl-project#719 makes safe (plus its second host pool) if checkpoints cross phases; deterministic sampling for the byte-gate itself. CAN-FAIL PROOF (mutation applied, suite re-run, reverted): S1 the sgl-project#212 rule dropped (a stateless hybrid manifest is allowed) -> test_a_hybrid_checkpoint_without_state_is_refused_at_build S2 no grid snapping (the checkpoint claims an off-grid position) -> test_the_position_snaps_down_to_the_grid_and_says_so, test_a_checkpoint_before_the_first_grid_point_is_refused S3 branch skips verification -> test_a_manifest_whose_page_was_evicted_refuses_at_branch, test_a_missing_gdn_blob_refuses_too (the named red-first case and its sgl-project#212 twin, and only those) S4 unknown format_version parsed best-effort -> test_an_unknown_version_is_refused_not_best_effort_parsed S5 the recurrent state fetched FIRST instead of last -> test_the_plan_is_the_branch_order, test_a_complete_checkpoint_verifies_and_plans S6 model identity mismatch tolerated -> test_a_foreign_model_identity_is_refused, and ONLY that one Restored tree re-verified green after every mutation (18 passed). Regression, same env, base c3e9487 vs this commit: unit/mem_cache 940 failed / 779 passed -> 940 failed / 925 passed (+146)
efschu
added a commit
to efschu/htsglang
that referenced
this pull request
Aug 17, 2026
…the first Flip+HiCache boot Everything mechanical is landed (canonical format 04c6736, GDN blob a38f39f, sharded backend 19f4c68, cutover rebind ec117fa, ReadBufferPool, sgl-project#718 disarm). This is the boot that composes them, and its one open cost, priced. THE OPEN COST, stated first and found by inspection: sgl-project#719's rebind refuses unless the incoming phase owns a shape-matched host pool, and that pool HAS NO BUILDER -- phase_pools_for reads scheduler.phase_flip_host_pools (hicache_phase_binding.py:287) and nothing in the tree writes it. So the rebind refuses at every cutover today, logged and never raised, with sgl-project#718 keeping the device tier disarmed in the phase that did not build the binding. That is a safe state, and the design recommends booting IN it. HOST-RAM BUDGET, from the deployed [32,16,16] cut. A host row is that rank's OWN layers x 2048 B, so PP rows are 16,384 / 8,192 / 8,192 B while a TP row is 32,768 B on every rank -- multipliers 2.00x / 4.00x / 4.00x, i.e. 16/own_layers. Against the MEASURED pinned load (C1 at 591add2: 25.87 GB usable, 20.50 GB of flip weight images, 5.37 GB remainder): rows fitting in 5.37 GB rank0 rank1/2 PP pool only (today) 327,759 655,518 BOTH pools 109,253 131,104 So the second pool cuts the host tier to a third on rank0 and a fifth on ranks 1-2 at fixed budget; at the 9.01 GB the C1 boot actually requested it needs ~27 GB on rank0's ratio against 5.37 available -- it does not fit, and not marginally. WHICH INSTRUMENT BINDS, because reasoning from free -g gives the wrong answer: the PINNED budget refuses first (it already refused 29.51 GB at C1), while the sgl-project#721 available floor of 24.3 G has ~12.7 G of slack today (available 37 G). A second host pool is refused by the pinned check long before it threatens the OOM floor. MARKED ABSENT rather than estimated: whether the 9.01 GB host-pool figure is one rank's or an aggregate, and the per-rank split of the three weight images. The refusal message does not say and no ledger entry resolves it, so every number is given per rank AND against the shared 5.37 GB remainder, so the conclusion does not depend on the ambiguity. RECOMMENDATION: the first boot does NOT add the second pool. It does not fit, and the cross-phase path does not need it -- sgl-project#706 made the DISK tier geometry-neutral, sgl-project#703 pushes warm prefixes there before the cutover, and both phases resolve the same content key (100 GB disk = 3,051,758 canonical-page tokens against a 5.37 GB host staging tier). The safe states are the DEFAULT states, so nothing has to be remembered. Also in the document: the exact flag set and env (including why --phase-flip-rebind-hicache is deliberately NOT set), the boot-before-hold sequence with the two log lines whose ABSENCE is the stop condition, the sgl-project#630 PP=3 x disk-HiCache warmup wedge with its root fix (9da9dfd) and what to watch anyway, acceptance (byte-identical cross-phase continuation with A-vs-A first -- the sgl-project#718 shape is a WRONG ANSWER, not a miss; hits counted from log lines because cache_hit_rate reads 0.0 with real hits; ReadBufferPool overflow counter zero; the sgl-project#720 falsifier pair), and the ranked risk list. Design only. No boot, no GPU. The boot goes on the window list. Symlinked beside the other DESIGN_706 docs in evidence-665-f1/.
efschu
added a commit
to efschu/htsglang
that referenced
this pull request
Aug 17, 2026
…cket Two docs, both prep: no branch pointer on a real line moves here. MERGE_TRAIN_2_FOLLOWUP.md -- the follow-up train, measured against the head train 1 projects (integration/r2 + 621 + 699 + 673-lockstep + 4c84637 + 67572ce + 677 = 6bab764c33), not against r2. Order and trial-merge result: reconcile/cluster-b-seam-model clean; fix/673-teardown-stack ONE conflict, docs/dev/MERGE_TRAIN_2026-08-17.md, doc only, no source conflict anywhere in the train; fix/728-max-bytes-uniform clean. Assembled head c5a4c1bca8. Ancestry finding, which moves the kernel rebuild: 4512136 and a8b068b are ancestors of reconcile/cluster-b-seam-model (YES) and of nothing else in the train -- not of 6bab764c33, 887a6d4 or a66f5e2. So the separately listed "sgl-project#441 kernel commits" step is a NO-OP and should be struck, the sgl-kernel rebuild is triggered by step 1, and train 1 is unaffected (it merges 67572ce, which carries neither). The rebuild step is named with the runbook's wheel-pin discipline (arch list 86;120, MAX_JOBS=4, nvcc from the venv cu13 toolkit) and its acceptance is boot-gated, not desk-gated. Baselines on c5a4c1bca8, hermetic (CUDA_VISIBLE_DEVICES=""): mem_cache 1086 passed / 0 failed (1651 skipped); managers 19 failed / 2367 passed; planner 8 failed / 2842 passed (all test_webui/chess, missing optional dep); distributed 27 failed / 2764 passed vs 21 on train 1; test_scheduler_teardown_673.py 10 passed. The +6 on distributed is attributed rather than asserted. On the three implicated files: 887a6d4 9 failed, a66f5e2 9, feat/704 9, fix/602 9, fix/701-ledger-wiring 15, reconcile 15, train-2 head 15. The extra failures are pre-existing on fix/701-ledger-wiring and inherited unchanged; no merge in this train creates one. Reported as "21 pre-existing + 6 inherited from sgl-project#701", not as green. DESIGN_706_BOOT.md -- the boot-side open items are closed as a decision, and section 5 is a run-card F4-r4 can execute: preconditions, verbatim flag set, ordered steps, pass/fail by greppable log string, abort conditions. Boot WITHOUT the second host pool. The phase_flip_host_pools builder stays unwritten deliberately: it does not fit (5.37 GB remainder, both pools need ~27 GB on rank 0's ratio) and the cross-phase path does not need it, since sgl-project#706 made the disk tier geometry-neutral. One code-verified correction to this doc's own earlier framing: with --phase-flip-rebind-hicache OFF, rebind_for_cutover returns None on the flag check BEFORE phase_pools_for is called, so the recommended boot logs no sgl-project#719 line AT ALL -- the previous text said the unarmed rebind refuses at every cutover. Armed without a second pool it does refuse, exactly once per cutover, logged-never-raised at ERROR level. Both expectations are stated, because an expected ERROR line is what stops a boot for no reason. Acceptance counts the prefill log line (#cached-token > 0), never cache_hit_rate, which reports 0.0 despite real hits.
efschu
added a commit
to efschu/htsglang
that referenced
this pull request
Aug 17, 2026
…cket Two docs, both prep: no branch pointer on a real line moves here. MERGE_TRAIN_2_FOLLOWUP.md -- the follow-up train, measured against the head train 1 projects (integration/r2 + 621 + 699 + 673-lockstep + 4c84637 + 67572ce + 677 = 6bab764c33), not against r2. Order and trial-merge result: reconcile/cluster-b-seam-model clean; fix/673-teardown-stack ONE conflict, docs/dev/MERGE_TRAIN_2026-08-17.md, doc only, no source conflict anywhere in the train; fix/728-max-bytes-uniform clean. Assembled head c5a4c1bca8. Ancestry finding, which moves the kernel rebuild: 4512136 and a8b068b are ancestors of reconcile/cluster-b-seam-model (YES) and of nothing else in the train -- not of 6bab764c33, 887a6d4 or a66f5e2. So the separately listed "sgl-project#441 kernel commits" step is a NO-OP and should be struck, the sgl-kernel rebuild is triggered by step 1, and train 1 is unaffected (it merges 67572ce, which carries neither). The rebuild step is named with the runbook's wheel-pin discipline (arch list 86;120, MAX_JOBS=4, nvcc from the venv cu13 toolkit) and its acceptance is boot-gated, not desk-gated. Baselines on c5a4c1bca8, hermetic (CUDA_VISIBLE_DEVICES=""): mem_cache 1086 passed / 0 failed (1651 skipped); managers 19 failed / 2367 passed; planner 8 failed / 2842 passed (all test_webui/chess, missing optional dep); distributed 27 failed / 2764 passed vs 21 on train 1; test_scheduler_teardown_673.py 10 passed. The +6 on distributed is attributed rather than asserted. On the three implicated files: 887a6d4 9 failed, a66f5e2 9, feat/704 9, fix/602 9, fix/701-ledger-wiring 15, reconcile 15, train-2 head 15. The extra failures are pre-existing on fix/701-ledger-wiring and inherited unchanged; no merge in this train creates one. Reported as "21 pre-existing + 6 inherited from sgl-project#701", not as green. DESIGN_706_BOOT.md -- the boot-side open items are closed as a decision, and section 5 is a run-card F4-r4 can execute: preconditions, verbatim flag set, ordered steps, pass/fail by greppable log string, abort conditions. Boot WITHOUT the second host pool. The phase_flip_host_pools builder stays unwritten deliberately: it does not fit (5.37 GB remainder, both pools need ~27 GB on rank 0's ratio) and the cross-phase path does not need it, since sgl-project#706 made the disk tier geometry-neutral. One code-verified correction to this doc's own earlier framing: with --phase-flip-rebind-hicache OFF, rebind_for_cutover returns None on the flag check BEFORE phase_pools_for is called, so the recommended boot logs no sgl-project#719 line AT ALL -- the previous text said the unarmed rebind refuses at every cutover. Armed without a second pool it does refuse, exactly once per cutover, logged-never-raised at ERROR level. Both expectations are stated, because an expected ERROR line is what stops a boot for no reason. Acceptance counts the prefill log line (#cached-token > 0), never cache_hit_rate, which reports 0.0 despite real hits. (cherry picked from commit 60414e9)
efschu
pushed a commit
to efschu/htsglang
that referenced
this pull request
Aug 24, 2026
…t inventory USER DECISION 2026-08-24, binding, verbatim: "das kv soll niemals vom layer flip her stammen, einfach aus dem hicache laden fertig." Recorded BEFORE the build so the build cannot quietly become something else, and so the ledger that follows reads as validation rather than as a vote. THE TREE ALREADY AGREED, which is the part worth landing on its own. hicache_flip_writeback.py:21-23 states the premise in its own words: "a prefix's only way across the flip is the geometry-free STORE (sgl-project#706): the disk tier, whose keys carry content alone and whose pages are cut at read time for whichever geometry asks." And it names exactly why a mover exists today: "device rows survive the flip, because the live row set (radix tree values UNION parked requests' rows) is relocated between the two phase pools BY ROW ID." That relocation IS the wave mover. THE BLOCKER THAT SHAPES THE BUILD, found before writing any code. phase_flip_resident_carry.py:64-76 -- a carried Req keeps its req_pool_idx across the swap "by construction", and the reason it stays valid is that "the bytes behind those ids are what the KV and GDN movers relocate". So PHASE-FLIP-CARRY is NOT a KV mover and NOT a retirement candidate, but its correctness today DEPENDS on the mover. Retire the mover alone and a resident request's req_pool_idx points at unwritten memory. The fence and the retirement must therefore land together, and the cutover must leave the new phase's device tier in a state where a lookup MISSES rather than returning stale rows. That is the correctness core, and it is where the red-first tests must bite hardest. WHY THE PRIZE IS FUNDING, NOT LATENCY -- stated plainly because the opposite is the natural assumption. The whole KV+GDN movement is 901 ms of an 11.6 s seam (W25 epoch 11, 116502 live slots). Removing it leaves ~10.5 s. What it DOES remove is wave_peak = incoming + max(outgoing, local) + one_layer_window + backing_slack (phase_flip_runtime.py:7314) -- every term a KV quantity -- which is the 2339.11 MiB tp_to_pp staging reserve behind W25's 33 refused arms, 25 of them on the staging rate limit, and 17 FLIP ABANDONED. RETIREMENT INVENTORY, scoped to the flip path, REPLACE/RETIRE/KEEP-WITH-REASON with file:line, in the note. Rules held to: "hardened against corruption" does not count as reconciled; no bulk deletion; shared machinery with a named other consumer is KEPT (kv_reshard's sgl-project#297 domain, gdn_flip_preconditions -- whose "no other consumer found" is recorded WITH the narrow search set that failed, not as a licence to delete); anything merely deletable-later is a separate section and marked un-re-verified. EXISTING MACHINERY TO EXTEND, NOT REBUILD: hicache_flip_writeback.py already IS the fence (stage + bounded ack drain, deadline 2.0 s); hicache_demotion.py already covers evict-before-persist WITH counters and is merely off by default; sgl-project#719 rebind + sgl-project#718 disarm already own the read-path switch; mamba_ckpt_utils' anchor grid is already what anchor-resume needs. NO FALLBACKS, per standing doctrine: every gap is fixed inside the HiCache route. A flip enabled without hierarchical cache becomes a validate-early launch refusal on the sgl-project#806 precedent (c0a6347, ServerArgs.__post_init__ after materialize_declarations) -- NOT a silent mover revival. That refusal is deliberately NOT landed yet: it only becomes true once the flip carries no KV, and shipping it early would reject launches that work correctly today. Forced build order, recorded rather than discovered later. VALIDATION METRIC CHANGED, and one half of it does not exist yet: cutover- blocking time (fence + weights refill) can reuse the existing seam_census.mark("flip_writeback") and the DONE stats dict, but there is NO instrument for post-cutover warm-up cost as served-request latency -- searched and named as a build item rather than assumed present. OPEN GAPS ARE LISTED AS GAPS, with the search sets that failed: sgl-project#735's "sgl-project#706 rows on the full plan" could not be located (both greps resolve to a different topic -- non-contiguous PP placement), fp8 kv_cache_dtype against the canonical page format is unverified, and the worst-case un-hashed resident tail at the quiescent-flip instant is reasoned from the quiescence predicate rather than measured. No code changes. Documentation only.
efschu
pushed a commit
to efschu/htsglang
that referenced
this pull request
Aug 25, 2026
…roject#718 rebind can arm MECHANISMUS VORHANDEN, AKTUATOR FEHLT. The whole sgl-project#718 rebind chain already existed and was already wired: `rebind_for_cutover` is called at the cutover, the sgl-project#719 generation stamp and `coherence_check` are built, and `phase_pools_for` knows exactly what it wants. It wanted `scheduler.phase_flip_host_pools[phase]` -- and across the entire tree that name appeared ONLY in its own docstring and its own refusal message. Nothing ever wrote it, so the rebind could never arm. W32 measured the consequence end to end: no host pool -> RebindRefused -> the rebind never arms -> bound_phase() stays "pp" -> device_tier_disarmed("load") is True for the whole TP phase -> HiCacheController.load() returns None -> ZERO tokens reach the device. The one transport prefill logged `#cached-token: 0` on what should have been a perfect disk hit, beside 6 `sgl-project#718 hicache-phase-guard` warnings. `build_phase_flip_host_pools` runs in `init_model_worker`, right after `build_phase_flip_tp_stack` -- the first point where BOTH device pools exist, which is required because a host pool is allocated FROM its device pool (DESIGN_706 C1) and cannot be derived after the fact. FLAG-GATED: without --phase-flip-rebind-hicache it returns {} and allocates nothing, so every other boot is byte-identical. A STAGING PIN, NOT A MIRROR (sgl-project#810). `pp` maps to the tier the boot already built; the rebind needs a HANDLE per phase, not a second pp pool. Only `tp` is new and it is sized to the WORK, never to the pool: chunked_prefill_size x max_running_requests x PHASE_FLIP_STAGING_CHUNKS tokens times the pool's own measured per-token cell. `ratio=0` is passed explicitly, because a ratio is the mirror-shaped answer sgl-project#810 forbids -- it would duplicate retention the pp tier already provides and charge the pinned host budget for capacity nothing reads. Pinned by a test: doubling the device pool changes the pin not at all; doubling in-flight work doubles it. HOST-LEDGER POST (sgl-project#721): the GB taken, the derived GiB, the token count it came from and host free after, logged AT the allocation so the ledger carries the number actually taken rather than an intention. The POST shrinks if it does not fit; the FLOOR never does. REFUSAL CONVERTED, NOT DELETED (sgl-project#847). `phase_pools_for` still raises for a genuinely absent or mis-shaped pool, pinned against the REAL guard rather than a restatement: no TP device pool -> no `tp` entry -> the cutover refuses, exactly as in W32. A constructor that throws is caught, reported loudly, and leaves the phase unbound rather than taking the boot down. TESTS (13): default boot untouched; both phases registered; the pin comes from the TP device pool; staging-not-mirror in both directions; and the can-fail set -- no host tier, no TP pool, the real guard still raising, and a throwing constructor. Clean SERIAL gate (one suite at a time): 8 failed / 4071 passed / 2 skipped -- the same pre-existing 8, +13 matching the tests added here. METHOD CORRECTION: my earlier per-file "ruff delta vs HEAD" checks compared a copy under /tmp, where ruff resolves a DIFFERENT config, so those readings were invalid. Re-done in place: the single F401 in phase_flip_boot.py is pre-existing and this change adds zero.
efschu
pushed a commit
to efschu/htsglang
that referenced
this pull request
Aug 25, 2026
…moves W35 armed the sgl-project#718 rebind for the first time on this tree, then died under load on all three ranks: AssertionError: Double-free detected: slots not currently allocated: [0, 1, 2, ...] check_hicache_events -> drain_storage_control_queues -> _drain_release -> HostPoolGroup.free `cc.host_mem_release_queue` holds bare index tensors naming slots allocated from the OUTGOING pool. `rebind` re-points `mem_pool_host`. The next ordinary scheduler round drains those entries against a pool that never handed the ids out. The assertion is correct and caught a real corruption loudly. THE CRITERION, DECIDED BY READING. Dropping stale entries is right only if the outgoing pool dies with them. It does not: `_stamp` only re-points readers and tears nothing down, nothing in this module destroys a pool, and `phase_flip_host_pools` holds BOTH phases for process life because the flip ALTERNATES -- the outgoing pool is the next cutover's incoming pool. It survives with live allocations, so a dropped release is a host-slot leak that recurs once per cycle. Route, do not drop. SETTLE BEFORE THE SWAP, rather than route at drain. Routing later would need a per-entry generation stamp plus a generation->pool map -- a second bookkeeping scheme beside the sgl-project#719 generation, which is exactly the second-copy defect that cost W32. Settling makes the invariant true by construction: the binding changes in exactly one place, so at that instant every queued entry belongs to the binding still installed. The sgl-project#719 generation stays the single coherence primitive and gains its second CONSUMER instead of a rival. LOUD IN BOTH WRONG DIRECTIONS. A failing settle raises RebindRefused (safe by construction: the binding does not move, so sgl-project#718 keeps the device tier disarmed -- the pre-feature state). A non-empty auxiliary `extra_host_mem_release_queues` REFUSES and NAMES the queue rather than being skipped: those entries route through per-component allocators this step does not resolve, and guessing routing on a free path turns a loud crash into a silent corruption. There is no drop path at all. TESTS (12): the specimen settled against the outgoing pool; end-to-end with a disjoint incoming id space; RED-FIRST -- without settling, swap-then-drain reproduces the metal double-free; both loud directions incl. the aux queue naming itself; ORDER pinned (settle precedes rebind); one-authority pinned (no rival stamp/map names); and REAL-CLASS asserts on the actual drain line, the actual raiser in pool_host/base.py, and `_stamp` re-pointing `mem_pool_host` -- which is why settling must come first. Clean SERIAL gate: 8 failed / 4092 passed / 2 skipped -- same pre-existing 8.
efschu
pushed a commit
to efschu/htsglang
that referenced
this pull request
Aug 25, 2026
…ion; give all four consume points one gate Three classes from the cutover-participant sweep. 1. THE PRODUCER CHAIN IS STAMPED, replacing my own one-shot settle as the durable answer. `settle_pending_releases` drained the queue at the rebind instant and cannot see entries that do not exist yet -- and THREE producers keep filling it afterwards: `_drain_revoke`, the prefetch transfer thread, and the direct path. Each manufactures a release naming slots from the pool bound when the OPERATION was opened. `StorageOperation` now stamps itself at construction (where its host slots are allocated) from sgl-project#719's `current_generation` -- one authority, no parallel scheme -- and `BindingState.advance` records generation -> host pool, so a stale release can be freed against the pool it came from. Routed at PRODUCE time, the only point where the generation is still known without changing what the queue carries: a stale batch is freed immediately against its own pool, so nothing on the queue can outlive its binding by construction. A stale batch whose pool is UNKNOWN is refused loudly and neither queued nor freed -- both would corrupt. THE ONE-SHOT SETTLE STAYS, as a documented belt: not every `append_host_mem_release` caller passes a generation yet, so it still covers the unstamped remainder. Decided by reading, not assumed. AND THE OVERRIDE HAD TO BE ROUTED TOO. `HybridCacheController. append_host_mem_release` shadows the base with a different signature; an override that silently dropped `generation` would leave this fix installed and unreachable on the mamba/hybrid lane -- the only lane this rig runs. Same shape as W31/W32/W33, caught at the desk this time. 2. `start_loading` GETS BOTH CONSUME-TIME CHECKS. `load()` asks the phase question at enqueue and is right there; the cutover lands between enqueue and consume, which are separate calls. A stale load fills device rows from host slots this phase does not own and the tree marks the prefix RESIDENT -- attention reads KV nobody wrote, with no assertion anywhere. Checked before a producer is allocated. 3. `HybridCacheController` GETS THE CONSUME HALF in BOTH `start_writing` and `start_loading`. Its `write()`/`load()` carried the enqueue-time checks only, making the live path the one lane where a queued copy is consumed after a cutover -- the shape that file's own sgl-project#760 note describes. ONE AUTHORITY, FOUR CALLERS: the rule now lives in `consume_gate` and the four consume points call it; the base's inline copy is gone. Four copies of one rule is what cost W32. TESTS (11 new, 23 with the W35 release suite): the stamp arm and the disarm arm isolated and BOTH exercised (in a bare process the phase predicate reads "disarmed", which would have hidden the stamp arm -- pinned explicitly); refusals counted by name so a boot can tell clean from blind; and CAN-FAILs that every consume point calls the gate and that none reimplements it inline. Clean SERIAL gate: 8 failed / 4103 passed / 2 skipped -- same pre-existing 8.
efschu
pushed a commit
to efschu/htsglang
that referenced
this pull request
Aug 25, 2026
…s queued Class 4's consumer half -- the last silent, DURABLE failure in the sweep. `backup_queue` is consumed by an always-running background thread that does not pause across the flip. After a rebind, `_page_backup` reads `mem_pool_host.get_data_page(...)` -- the INCOMING pool -- and writes those bytes to a CONTENT-ADDRESSED store under a hash computed from the tokens the operation was opened with. The hash does not match the payload, every later reader trusts it, and the corruption OUTLIVES THE PROCESS. Unlike the W35 double-free, which was loud, nothing catches this one. REFUSAL, NOT ROUTING, and the asymmetry against class 1 is the point. A stale RELEASE is routed to the pool its generation names, because that pool still owns those slots. A stale BACKUP cannot be: its host slots may belong to a pool that has since been repurposed, so there is no pool whose bytes are the right bytes. Declining is the only safe verb. A declined backup is a correct NON-PERSIST -- the prefix misses later and is recomputed, the same cheap failure the sgl-project#718 disarm and the sgl-project#760 write refusal already accept -- and it is acked either way, because an unacked operation stalls the queue. `operation_is_stale` is the sibling of `consume_gate` and lives beside it: one authority, two shapes (a queued batch at a consume point; a single operation on a background thread). A third copy of the rule is what cost W32. THREAD BOUNDARY: both generations are read EXACTLY ONCE, at the decision point, pinned by a test. The consumer runs on a background thread while the cutover mutates the current generation on another; a second read mid-persist could straddle a rebind and answer two different questions about one operation. TESTS (11) ASSERT ON STORE CONTENT, not on the counter -- a fix that counts and still writes is no fix. Includes the can-fail modelling the pre-fix path (remove the gate and it persists again), the read-once pin, the ack-anyway pin, the unstamped-op compatibility case, and a pin that the hybrid subclass does NOT override this loop -- the standing warning after the `append_host_mem_release` override shadow. NOT CLOSED, and filed rather than rushed: the PREFETCH consumer. Its loop runs `_all_reduce_prefetch_groups`, a COLLECTIVE, so a per-operation refusal placed before it risks splitting the group across ranks. That needs a rank-uniform formulation (most likely routing through the existing revoke path, which is already uniform), and guessing it on a collective is how a silent corruption becomes a hang. It is also the non-durable half: a stale prefetch loads into host memory, it does not persist to the store. Full SERIAL gate: 8 failed / 4114 passed / 2 skipped -- same pre-existing 8.
efschu
pushed a commit
to efschu/htsglang
that referenced
this pull request
Aug 25, 2026
…nd a gate that can say "checked"
Two desk items from W36.
1. THE 22-ROW DOUBLE-COUNT. W36 aborted with the pool checker reading
available OVER total by exactly 22:
total=468981 available=108565 evictable=1 withheld=360437 -> 469003
The census names it one line earlier:
size=468981 free=108544 withheld=360437 available=108566
`withheld + free == size` EXACTLY -- the id space is fully owned -- while
`available` is `free + 22`, because `available_size()` is
`len(free_pages) + len(release_pages)` and 22 ids had re-entered the
RELEASE buffer while `_withheld` still counted them. Two owners, one row.
MY W36 WRITE-UP CALLED `_withheld` A RANGE. It is not: it is an explicit
id TENSOR, and `_apply` already scans both free lists. So the defect is
not range-vs-buffer arithmetic but a re-entry window -- a high id freed
between two `_apply` calls lands in `release_pages` while still withheld
(the "allocator has no free listener" path warns about exactly that and
then proceeds). Corrected here rather than left standing.
WHICH OWNER WINS, by reading: the ids are ABOVE THE CAP and `_apply`'s own
docstring is "move ids above the cap out of every free list". The withhold
is authoritative; the free list is stale. `_settle_free_lists` drops them
from the free side at `_publish` -- the single point where the withheld
count is published, so the number the checker reads and the lists
`available_size()` reads are reconciled in ONE place. Counted and named.
PRIOR-ART GATE: `kv_row_ownership.read_free_rows` is the existing one
authority for "free rows in the allocator's own shape" and already unions
free_pages|release_pages, so the census side was correct and nothing new
was built there.
THE CHECKER IS NOT SOFTENED. It has now fired correctly in BOTH
directions -- one row SHORT in W29, twenty-two OVER in W36 -- and both
signs are pinned as tests. Both being right is what proves it measures.
2. RUNG 3 OBSERVABILITY. Every stale-generation gate logged only on REFUSAL,
so W36's eight cutovers with zero refusals were byte-identical to eight
cutovers whose gates were never reached, and the rung was INCONCLUSIVE --
an ambiguity created by the very lines meant to detect it. The gates now
count CHECKS as well as refusals and the SEAM prints "checked N refused M"
once per cutover. Emitted from the seam because the seam always runs: an
unreachable gate now produces `checked=0` instead of silence. That is the
can-fail, and it is a test.
TESTS (12): the specimen arithmetic red-first; one-row-one-owner after
settling; no-overlap left untouched; the reclaim counted; the checker still
aborting on BOTH W29's under-count and W36's over-count and still able to
pass a balanced pool; heartbeat resets per epoch; unreachable gate reads
checked=0; and pins that the seam emits it and that both gates count checks.
Tier-1 scoped gate: 103 passed (touched modules + consumers + this round's
specimen tests) and 100 passed across the five kv_backing_relief consumer
suites.
efschu
pushed a commit
to efschu/htsglang
that referenced
this pull request
Aug 25, 2026
…olidated (W37-B batch) Batches R4's fix/861-draftkv-hicache @ 92b6410 (4 commits off e346963) with this line's W36/W37-A census and observability work. R4's half: the HiCache draft pool is registered on phase-flip boots, stamped from sgl-project#719's current_generation at rebind_for_cutover AFTER coherence_check, the host pool allocated once and re-stamped per cutover (DESIGN_706 C1-safe), six consume points behind one `draft_tier_armed` gate including BOTH HybridCacheController overrides -- the override-shadow lesson this strand paid for twice -- and a drafter-identity key suffix guard that refuses Mooncake v2 by name. Requires --phase-flip-rebind-hicache, which this recipe has carried since W33. This line's half: an id may live in at most one free list (W37-A falsified the W36 root; the real pair is free_pages ∩ release_pages), and every stale-generation gate now reports "checked=N refused=M" once per cutover, which decided W36's rung 3 on metal. Gate point is THIS TIP, not either half.
efschu
pushed a commit
to efschu/htsglang
that referenced
this pull request
Aug 26, 2026
…o the guard can arm sgl-project#718/sgl-project#847 built every part of the rebind except one: the 'tp' staging pin was assembled with a SINGLE entry, PoolName.KV. On a hybrid model the live tier carries KV *and* MAMBA, so `check_pool_coverage` computed `missing={MAMBA}` and refused -- correctly, on every cutover. A refused rebind leaves the sgl-project#718 device tier DISARMED, `load()` returns None, every read-through misses, and every prefix a cutover retracted is recomputed in full. Measured on the W40 sgl-project#857 acceptance boot (boot_w40_857strict_0825_2342.log): 60 `sgl-project#719 HiCache rebind refused`, ZERO arms, and `#cached-token: 0` on all 243 prefill batch lines -- one bucket, no exceptions. THE PRECONDITION WAS ALREADY WRITTEN DOWN, in the guard's own docstring: "A phase host tier has to be built with the FULL POOL SET before this rebind can arm; until then the sgl-project#718 disarm is the correct state and a read-through miss is the correct cost." This is that precondition, met. Not a new finding -- sgl-project#856 answers whether the retraction is necessary (it is: carry moves Req scheduling metadata, never KV bytes, and sgl-project#856 deliberately retired the movers), and sgl-project#718/sgl-project#847 already named this remedy. REFUSAL CONVERSION, NOT GUARD DELETION. `check_pool_coverage` is untouched. It must stop firing because its precondition is MET, never because it was removed, and the test asserts BOTH directions -- a full pool set arms, a narrowed one still refuses. Neither assertion alone can tell a fix from a disarm. DERIVED FROM THE BOUND TIER, not from the model config: the set that must be covered is whatever the READER names, which is the same quantity the guard compares. Reading the config would be a second opinion about one fact. MIRRORS `build_hybrid_mamba_stack` rather than re-deriving it -- same primitives, same layer mappings, same transfer_layer_num rule. The controller is deliberately NOT reused: this pin needs a host VIEW, and a second HybridCacheController would be a second writer against one device pool. Both entries are rebuilt in the hybrid case. The KV-only pin used an identity map over range(layers), which is right while KV is the only entry and wrong the moment a second pool shares the transfer index space -- the two maps collide at index 0. SIZING IS PER-SLOT, NOT PER-GB, and it is the one place the mamba half must not copy the KV half. MambaPoolHost reads host_size in GB only when > 0, else `device_pool.size * ratio`. The KV pin's GB figure is derived from a token count; mamba is allocated per request slot. Ratio 1.0 with host_size 0 mirrors the device pool, which is what a phase-matched staging pin means. The mamba half is a NAMED HOST-LEDGER POST, priced from what was allocated rather than from the intention. An unpriced pinned pool is what the ledger exists to prevent. A pool set this builder cannot mirror (SWA, indexer, DeepSeek) is logged as an ERROR at the cause and still refused by the guard -- named, not swallowed. THE CHECK (sgl-project#871, third scope item): `advance_fence_blind_streak` + FENCE_BLIND_STREAK=4, aggregating the EXISTING `persisted_nothing` instrument. One empty fence is legitimate; every empty fence means the canonical store can never populate, and that shows up only as latency. Gated on `released`, mirroring sgl-project#719's busy gate at the stale-gate streak: a fence over an empty tree is correct to persist nothing, so counting quiet cutovers would build a crying-wolf alarm out of the instrument written to replace one. NO SECOND COUNTER -- a parallel "recomputed prefix tokens" counter would measure what this and #cached-token already measure between them. Extracted as a pure function so it is falsifiable without booting. A guard whose logic can only be exercised by booting is a guard that ships unexercised, which is the failure mode this ticket is about. CLASS (unchanged, carried from the sweep): a store whose only writer is a lifecycle event another mechanism systematically preempts. Retention is finish-only; the cutover retracts before finish; the store can never populate, so every recovery path reading it can never fire. TEST RESULTS test_phase_tier_full_pool_set_871.py (new): 15 passed. FALSIFIED IN THREE DIRECTIONS BEFORE THE GREEN WAS CLAIMED, count gate held on each: guard removed (disarm) -> 3 failed, and they are exactly the refusal tests. 3 extracted == 3 in summary. guard fires unconditionally -> 6 failed, exactly the arming tests. 6 == 6. streak loses its `released` gate -> 1 failed, the idle test. 1 == 1. restored -> 15 passed. Targeted regression set (7 files: sgl-project#847 writer, sgl-project#783 fence x3, sgl-project#856 empty wave, counters, and the new file): 97 passed, 0 failed, 2 subtests passed. Count gate 0 extracted. Hermetic, CUDA_VISIBLE_DEVICES verified EMPTY at the PROCESS (/proc/<pid>/environ), not at the command. AN EXISTING TEST CAUGHT A REAL BUG OF MINE and the first run of that set was 3 failed: the new HOST-LEDGER line read `tp_host.entry_map` directly, but that writer is driven in tests by stand-ins where HostPoolGroup itself is patched. Fixed with the getattr discipline this module states at its other probes -- an instrument may never be the thing that breaks a boot. Lint: ruff 0 before and after on phase_flip_runtime.py and the new test. phase_flip_boot.py reads 1 before AND after -- a pre-existing F401 on `pack_into_arena`, present in HEAD, not mine. codespell clean. Flag-off path verified byte-identical: without --phase-flip-rebind-hicache the writer still returns {} and allocates nothing. NOT CLAIMED, AND DELIBERATELY NOT GUESSED: no metal run. Whether the kv+mamba pin can actually be ALLOCATED on this box, what its real HOST-LEDGER post comes to against the 16G floor, and whether #cached-token becomes non-zero are decidable only on hardware. They belong to a boot window. The sgl-project#857 acceptance instance was left running and untouched throughout -- it is the standing proof and the operator declared it taboo.
efschu
pushed a commit
to efschu/htsglang
that referenced
this pull request
Aug 26, 2026
…the refusal's false leg User order: fix sgl-project#875, do not merely refuse it. The refusal in `restore_seam_state` is correct -- it is the only thing between the tree and the W40 IndexError one way and a silent wrong-layer write the other -- but its own counter comment states the cost: every flip in that direction loses its prefixes. This is the first axis of the answer, plus a correction that should have come first. THE REFUSAL'S JUSTIFICATION WAS PUT ON TRIAL AND ONE LEG DIED. `check_cpu_copy_layers` claimed "the KV head sharding also differs between the phases (PP holds all heads of its stage, TP a head shard of every layer), so even the overlapping entries are not interchangeable". FALSE on this rig, read in the code rather than assumed: under sgl-project#345 uneven-DCP replication the full-attention cache is TOKEN-sharded and every rank stores the FULL replicated kv-heads (model_runner_kv_cache_mixin.py:3155-3160; the boot line at :3228 prints `get_total_num_kv_heads()`), and the PP pool takes `get_num_kv_heads(attn_tp_size)` with `attn_tp_size == 1` -- the same total. Identical widths, interchangeable entries. That sentence was my own and it would have stopped the next reader from looking; it is removed rather than softened. THE SURVIVING LEG IS NARROWER THAN IT SOUNDED. A remap needs every layer the destination holds and rank-locally under PP it cannot have them -- 8 of 16 on this rig's `pp_attn_stage_ratio=[8,4,4]`. But the union over the three stages IS every layer and PP holds all tokens, so the data exists on a peer. The honest statement was never "a remap is impossible", it was "a RANK-LOCAL remap is impossible, and nobody asked whether a collective one was available". THREE AXES, NOT ONE -- the finding that decides the scope: LAYER PP stage-sharded vs TP complete. BUILT HERE, both directions proven. HEAD replicated in both phases. No remap needed. Settled above. TOKEN PP holds every token at allocator slots; TP holds an owner-rule SUBSET at compacted rows, `(L // cp_S) * cp_ratio + (L % cp_S - cp_lo)` (layers/dcp/owner.py:159). A SECOND collective-shaped remap, NOT solved. I had not named this axis at all before this round. SO THE CARRY IS NOT WIRED, AND THE REFUSAL STAYS. A layer-correct token-wrong carry produces matching row ids at mismatched widths -- the exact shape sgl-project#719 already walked into. `seam_layer_carry.py` is a pure module with the collective INJECTED by its caller: nothing in it communicates, because a collective on the per-request restore path is the sgl-project#630 wedge shape and the place a real carry belongs is the flip's own `pre_cutover_fns`, beside `gdn_state` and `weights_refill`, where the group is already synchronised. The refusal is annotated at both sites with what it is waiting for, so it reads as a staged non-answer rather than as a verdict. BOTH DIRECTIONS, SEPARATE FALSIFIERS, because they fail differently: PP -> TP the loud arm. Asserted as a refusal that NAMES the missing global layers. One stage alone refuses; three stages together assemble all 16 exactly; two of three refuses rather than restoring 12 right and 4 stale. TP -> PP the SILENT arm. No exception exists to assert on, so asserted on CONTENT: each destination slot must hold its own global layer, value by value, and all three stages together must tile the model exactly once. An exception-only test passes this arm by doing nothing. CAN-FAIL, and the first mutant is the one that matters: replacing the carry with the OLD naive positional prefix -- i.e. a no-op -- reds SEVEN tests across both arms. Filling missing layers instead of refusing reds three. Ignoring `start_layer` reds three. Dropping this rank's own copy from the union reds six. Same-layout is deliberately NOT special-cased: a PP->PP or TP->TP restore goes through the same path and comes out positionally identical, so the case that already works anchors the case that did not.
efschu
pushed a commit
to efschu/htsglang
that referenced
this pull request
Aug 28, 2026
window-958-boot died 25 s into the chunked acceptance load at scheduler.py:7010, `AttributeError: 'NoneType' object has no attribute 'end'`, one line after `sgl-project#946 PREMISE RECOMPUTE`. THE ATTRIBUTION IN THE WINDOW CLOSEOUT IS REFUTED, BY TWO INDEPENDENT INSTRUMENTS. It put the null on the sgl-project#906 seam-refusal branch (scheduler.py:8916). * LOG. The full-phrase counter `[sgl-project#906] SEAM CHUNK REFUSED rid=` is 0 in BOTH boots, and `_note_seam_chunk_refused` logs its first three occurrences unconditionally (scheduler.py:5434-5446), so the zero is a measurement and not a rate limit. Other bracketed INFO tags from the same process are present in the same file, so the sink is not the explanation. * COVERAGE, which does not depend on any logging decision. The boot rode `SGLANG_949_COVERAGE=1`; boot 1's three rank databases (evidence-665-f1/trace949_0828/.coverage.24735{12,13,14}) all report scheduler.py:8916 **not executed**, while :8911 (the truncation), :8918 (the adder), :7010 (the reader) and scheduler_pp_mixin.py:2159/:2161 (the ring actuator and the call below it) are all executed on all three ranks. A fix at that branch alone would not have touched this crash. The junction took the ELSE branch every time and re-derived, exactly as sgl-project#946 argued. THE PRODUCER IS IN THE TRACEBACK, two statements above the reader. scheduler_pp_mixin.py:2159 calls `pp_apply_dead_premise_anywhere` -- sgl-project#948's relocated actuator, armed for that boot by `SGLANG_946_ACT_AT_RING=1` in the window's own recipe -- whose terminator runs `truncate_prefix_to(0)`; :2161 then calls `get_next_batch_to_run`. Nothing re-derives in between. sgl-project#946 had justified the truncation by its NEIGHBOURHOOD ("`add_chunked_req` below derives everything from `len(req.prefix_indices)` and only THEN calls `set_extend_range`"); sgl-project#948 moved the act to a site that RUNS, for a measured reason recorded at scheduler_pp_mixin.py:2100-2109 (the old site was entered ~6 times while 9471 passes voided), and the legality argument did not travel with it. FIX AT THE WRITER, ONE PLACE. `Req.truncate_prefix_to` leaves `Range(told, told)` -- zero rows at the prefix that now exists -- instead of `None`. This satisfies `_executed_extent`'s invariant `extend_range.start == len(prefix_indices)` by construction at the only place that can break it, and closes four producers in one cut instead of one branch per boot: the ring actuator (:2159), the seam refusal (scheduler.py:8916), `add_chunked_req`'s hybrid-SWA zero-budget return (schedule_policy.py:1396, which unlike the sgl-project#679 park at :1434-1436 returns without `set_extend_range`), and the sgl-project#791 clamp sites on a `NO_TOKEN` break. None of the four can now receive a null geometry, because none is produced. ONE EDIT WAS WRITTEN AND WITHDRAWN, recorded in the code rather than dropped. Making that hybrid-SWA branch write the park geometry -- so the two park branches say the same thing -- broke `test_prefill_adder.py::test_add_chunked_req_hybrid_swa_defers_when_swa_ below_page`, which pins "returned unchanged" via `set_extend_range.assert_not_called()`. With the writer fixed the branch is no longer a producer, so the edit would have been consistency rather than a fix, and it is not free: it would overwrite the PREVIOUS chunk's range on any path reaching this branch before that chunk is stashed. In production the stash runs earlier in the same pass, so the write would be value-neutral -- but that is an argument, not a measurement, and hybrid SWA is not a configuration this fork boots. Reverted, and the divergence between the two park branches is named at the site as open. sgl-project#958's ARGUMENT IS HONOURED, NOT REVERSED. Its "NONE, NOT A RECOMPUTED RANGE" paragraph refuses `Range(told, old_end)` because keeping the old end would INVENT a pass. `Range(told, told)` invents nothing, and it is not a new state: `_park_chunked_prefill_chunk` writes `Range(start, start)`, the sgl-project#679 park writes it, and `_executed_extent` declares zero-length ranges first-class. The offer still moves -- now WITHOUT the adder: `_executed_extent` reads (0, 0), so PP0 offers told=0, the value `reconcile_pp_admission_decision` admits unconditionally. `reset_for_retract`'s `None` is deliberately untouched: two disposal sites key off that sentinel (scheduler_pp_mixin.py:6061-6075, :7222-7236) and flipping it would have silenced them. The refused-geometry exit stays reachable from that producer and is now pinned by its own test. THE COMMIT'S OWN SAFETY NET WAS DOWNSTREAM OF THE CRASH. `PPScheduleRefused` / `require_executed_geometry` fired 0 times on metal while the unguarded dereference killed the process, because it iterates `can_run_list` and a resident continuation the adder did not add is never in it. It is not made reachable here; it is made unnecessary, and the structural reason is asserted rather than argued. SIBLING, same class, fixed here so this change does not widen it: `_park_chunked_prefill_chunk` handed back the `inflight_middle_chunks` increment whenever it got past its `end is None` gate rather than only when a chunk was actually prepared. Already reachable before this change via the sgl-project#679 park's `Range(prefix, prefix)`. The predicate is now the same `end > start` the KV release beside it already used -- one expression, not two. #962a: THE SEAM PROBE COULD NOT PROVE ITS HOOK RAN. The reachability probe `cutover_participants.py` registers for `latched_batch_flags` was emitted only `if any(_stale.values())`, so "ran and found nothing" and "never ran" were byte-identical -- the sgl-project#719 shape the registry's own docstring forbids. It is now unconditional and reports `reached=`, because W37-C already showed a bare zero is not enough (it logged `checked=0` eighteen times and was still blind). sgl-project#962 ITSELF IS REFUTED, no code change warranted. `batch_is_full` does not survive the tp_to_pp cutover: the hook is unconditional in `_cutover` with no early return before the completion log; it provably ran (`cutover complete: active stack` 6 and `[sgl-project#690] CUTOVER SUB-STEPS` 6 in both boots); it cleared nothing (`#861c cleared latched batch flag(s)` 0/0); PP0 admits 8 times (boot 1) / 4 times (boot 2) after the cutover before the first latched decline; and boot 1 alternates DECLINE/ADMIT eight times in one second while having MORE latched declines (5 vs 3) and NO livelock. #962b registered, not fixed: #888b's `parked_carrier_relief` re-derivation is on the post-flip path (scheduler.py:8587) but inert, because its gate reads `_parked_decode_verdict`, whose only writer (`_note_parked_carriers`, called at scheduler.py:7675) sits behind `not running_batch.is_empty()` and is unreachable at running=0 -- the state the relief exists for. Measured 0/0 against 8 latched declines. Needs its own danger-direction analysis. TESTS. `test_truncation_geometry_961.py`, 15 tests, RED FIRST at the pin (8 failed / 7 passed before the fix). The `:7010` reader is driven through the REAL `Scheduler.get_next_batch_to_run` on an uninitialised instance carrying the five attributes that line needs, so it reproduces the production AttributeError on the production line rather than on a copy of it; `_Req` borrows the real `Req.truncate_prefix_to`. Five CANFAIL mutants pin each reader to the invariant and pass before AND after. Two further readers are driven for real (`_compute_chunked_req_next_prompt_token`, `pp_chunked_local_match`) plus the real producer (`build_pp_admission_decision`). `test_offer_delivery_958.py`: its EXIT_3 test required the refusal this fix makes unproducible; corrected to assert the moved offer, and split so EXIT 3 stays pinned against the `reset_for_retract` producer that still reaches it. `test_latched_batch_flags_861c.py`: 3 tests for the #962a receipt, including a can-fail that a blind seam is not reported as an all-clear. DESK GATE, /spinning/htsglang-gpu/.venv, CVD="". BEFORE (frozen at the pin 78d030e): serial 895 passed / 2 failed, wide 3701, narrow 202. AFTER: see NOTE below. Failure set unchanged: the two pre-existing test_collective_family_siblings_610.py failures, untouched. sgl-project#954 (test_prefetch_progress_symmetry_580.py) is outside gate scope, as before. ruff: no finding on any of the 273 changed lines (all 64 pre-existing); new test file clean and ruff-formatted. codespell: new file clean; the one hit in phase_flip_draft_bootstrap.py:558 is pre-existing. No boot was run. /spinning/gpu-arb/TICKET_961_WINDOW.md carries the boot acceptance and is drivable from that file alone.
efschu
pushed a commit
to efschu/htsglang
that referenced
this pull request
Aug 28, 2026
…y what it owns Boot 18 (ecedf3f) is the specimen and it is the important one: plain PP=3 with the flip PROVABLY off (`PHASE-POLICY arming` 0, `PHASE-FLIP DONE` 0, chain-breaker check NONE, `command not found` 0), 31 batches, then PP0 died on AssertionError: Double-free detected: slots not currently allocated: [60934, 60935, ... 60971+] reached with no flip link in the chain at all: _event_loop_pp_body -> get_next_batch_to_run -> get_new_batch_prefill -> check_hicache_events -> drain_storage_control_queues -> _drain_release -> mem_pool_host.free. CLASS, and it is the FOURTH instance today: many producers, one give-back, no provenance. sgl-project#990 (lock_ref), sgl-project#991 (mamba slot), sgl-project#993 (req-pool row), now the host region. `cc.host_mem_release_queue` has FIFTEEN producers -- eight in unified_radix_cache.py, plus swa_component, mamba_component, hiradix_cache (x3), hi_mamba_radix_cache (x2) and cache_controller itself -- and `_drain_release` freed whatever arrived, with no dedup and no ownership test. TWO CHANGES, and the first matters more than the second. 1. PROVENANCE AT THE ENQUEUE. `append_host_mem_release` stamps the caller's `module:lineno` per page into `host_release_provenance`. Taken from the caller frame rather than added as a parameter to all fifteen sites, so a producer added LATER is stamped without anyone remembering to. This is what makes the next 90-second boot identify the producer PAIR itself instead of us picking the likeliest of fifteen. 2. OWNERSHIP AT THE GIVE-BACK. `_drain_release` now frees the unique, owned subset and reports the rest with the site that queued it. Two shapes are covered: the same span queued twice, and a span queued again after it was already freed. Freeing an unallocated slot is never correct, so dropping those is not a heuristic -- it is the give-back doing what it always should have done. WHY THE W35 GUARD DID NOT COVER IT, which is a coverage answer and not a retrofit. `append_host_mem_release` already carries a sgl-project#719/W35 guard whose own text says "freeing them against the current pool is the W35 double-free". That guard routes by BINDING GENERATION -- it catches a producer naming slots from the pool bound before a cutover. Two producers queueing the same span WITHIN one generation are, to it, two ordinary current-generation batches: structurally invisible, not merely missed. The guard names the hazard, not the gap. SCOPE, stated plainly: this is an INSTANCE fix. It closes the host-region give-back. The CLASS -- an ownership authority over every pooled resource, so a give-back can always ask "is this mine" -- stays open, and its docking points are sgl-project#902 (ownership authority beyond KV rows, where host regions are already listed as a sibling) and #888b's seed "every participant declares the paths of its release sites". Four instances in one day is the argument for doing it once, centrally, rather than a fifth time. FUTURE CHECK: a sixteenth producer needs no edit to be stamped, and a slot it double-queues is named with its own file and line on the first occurrence instead of killing rank 0. Evidence: desk. py_compile on both files; enqueue stamp, accessor and drain check verified in the loaded modules; behavioural probe of the drain algebra on the boot-18 shape (span 60934/5 queued twice + one already-free slot) showing dupes/unowned/freed split correctly AND that the unfixed form would have called free() on the unowned slot -- i.e. the probe can fail. Belegstufe: DESK-BEWIESEN. Boot 19 on the 90-second control arm is the test.
efschu
pushed a commit
to efschu/htsglang
that referenced
this pull request
Aug 31, 2026
… while the peers spin
BOOT-PROVEN (boot 22, boot_855_1033b_0840f82601_0831_131955, 13:24-13:26).
The first TP forward after a pp_to_tp cutover touches a shape specialisation
never loaded in this process. Rank 0's crash-time stack (log line 53248 ff.):
_dcp_write_scatter (flashinfer_backend.py:2574)
run (triton/runtime/jit.py:743) -> _init_handles (compiler.py:466)
loadBinary -> cuModuleLoadData (libcuda.so) + 11 libcuda frames
NOT the compiler -- cuModuleLoadData. The module is built; LOADING it into the
CUDA context blocks, because the load needs the device and the device is
saturated by the peers' barlink BAR1 spin kernels, which are waiting in
all_gather for this very rank. The cycle closes and the spin deadline fires:
Bar1CollectiveAborted (ranks 1/3 and 2/3, group flip_dcp:0) -> SIGQUIT.
WHY THE EXISTING MECHANISM MISSED IT, measured: 352 build-window lines in that
boot, ZERO in 13:24-13:26, all 352 carrying 'full cuda-graph capture warmup'.
cold_build_window had exactly three production callers (barlink BAR1 build,
sampling warmup #603b, capture warmup) and none is on the path a cutover
re-dispatches into. sgl-project#640 on a path sgl-project#615 never saw. The fix makes that path a
caller rather than building a second mechanism beside it.
A STALE COMMENT NEARLY REFUTED THIS FIX, and the correction ships with it.
sampler_warmup.py said wrapping a lazy build in cold_build_window "does NOT
work ... the window is PROCESS-LOCAL". True when written (8bddb93,
2026-08-06); falsified ONE DAY LATER by sgl-project#615 (38ec4fb, 2026-08-07), which
hooked publication into cold_build_window so "every existing call site
therefore becomes group-visible without moving". Never revised, both ancestors
of this pin, verified with git log -S rather than assumed. The paragraph is
annotated in place rather than deleted, because it is still right about ITS
module (warm-at-boot + barrier REMOVES the race; a window only EXTENDS it).
WHY A WINDOW OVER THE REAL FORWARDS, NOT AN ENUMERATED WARM SET. The warm set
is not knowable by inspection -- it varies with direction, spec-decode, the
sgl-project#887 one-chunk grant and any backend swap -- and missing one member reproduces
the wedge exactly, at the next first-loader. Whatever loads, loads under the
window. Residual stated in the code: this EXTENDS deadlines (900 s cap) rather
than removing the race; boot 22's stall was ~150 s, so the cap is not binding.
FIRST-LOADER CENSUS, as a standing table: devtools/CENSUS_1033c_first_loaders.md
-- every first-loader site x triggering state change x covering window x
collective proximity. Rows 1-6 verified at file:line (row 5 on metal); rows 7
(runtime recapture / drafter switch) and 8 (rung change sgl-project#704, resume-restore
sgl-project#89) are written UNVERIFIED, so the table is a lower bound on the covered set
and never a proof that nothing else is exposed. It also names the inverse
column (armed windows that may cover paths that no longer first-load -- the
352 are a count of WARMUPS, not of BUILDS, and the window carries no
modules-loaded counter, so live and dead coverage are indistinguishable from
the log) and answers the upstream-minimal question: there are not four window
mechanisms but two plus a front door, with one site (barlink_device.py:865)
bypassing the front door. Named, not rebuilt.
#1033d, same boot, INDEPENDENT DEFECT, minimal hardening only: the prefetch IO
aux thread caught only Empty, so a page whose geometry did not match the
incoming binding (mha.py:556 reshape, '[2,16,1,4,256]'=32768 against a 16384
page) ended the thread three seconds before the cutover -- killing no process,
setting no exit code, appearing in no health probe, and leaving storage
prefetch dead for the rest of the boot. It now fails the OPERATION (host slots
released, so the requester gets a refusal instead of an unreachable
completion), logs loudly with a counter, and stays alive. The broad except is
defensible only because the alternative is silent thread death with no
supervisor above the loop; the underlying two-geometry host pool across a flip
(sgl-project#718/sgl-project#719/sgl-project#875 family, and the fork's own 'sgl-project#939 RE-HOME VIA RE-READ ... source
page 16384 elems vs destination page 32768 elems' line names the same mismatch
two lines earlier while handling it correctly) is NOT fixed here and is its own
posten.
DESK CHECKS, matched to each edit's failure class:
* devtools/check_1033c_cutover_window.py -- 19 cases, hermetic, driving the real
run_batch wrapper: RED-FIRST arm reproducing the boot-22 condition (counter
unarmed -> no window -> nothing published), first forward covered AND
published, window closes (the sgl-project#431 open-without-close shape), budget finite
and steady state byte-identical, window closed and budget spent even when the
forward raises, and an AST check that the arming sits under no `if` (the naive
grep form of that assertion failed on a COMMENT -- fixed to ask the AST).
* AST check on the aux thread: broad handler present, logs at ERROR, releases
the failed operation's host slots, and continues.
* Method-split check: wrapper delegates, window only on the armed path, lazy
import off the fast path, inner body intact.
ruff F401/F821/UP037 clean.
DESK-PROVEN. Metal proof is boot 23.
efschu
pushed a commit
to efschu/htsglang
that referenced
this pull request
Sep 4, 2026
The flip aliased both stacks' request-index space at boot and never
rebound the scheduler at the cutover: `tp_req_pool.req_to_token` was
pointed at the PP pool's tensor (and the mamba index map with it), so
every row id the TP phase used had been minted by the PP allocator, and
a row freed in one phase was still named by the other. This is CUT 1 of
the WEG1 family spec -- the request axis (R), which is independent of
the KV-token axis (K) and buys correctness, not tokens.
Five edit sites, one cut:
C1.1 phase_flip_boot.py -- the two alias ASSIGNMENTS are deleted. The
three shape/space checks around them STAY and the `5a.` premise
comment is rewritten to the reason they are load-bearing AFTER
the deletion: three consumers cache `req_to_token`'s SHAPE once,
at construction (hisparse_coordinator.py:109, overlap_utils.py:296
and :299), and now see the other phase's pool after a rebind.
C1.2 phase_flip_runtime.py -- `rebind_req_pool_for_cutover` runs in
`_cutover`, immediately BEFORE the sgl-project#719 HiCache rebind and OUTSIDE
its try/except, on EVERY cutover. Deliberately NOT gated on
--phase-flip-rebind-hicache: sgl-project#719's "a refused rebind is SAFE"
holds because a stale HiCache binding has a disarmed state (sgl-project#718);
a request pool has none, so a refusal here RAISES.
C1.3 memory_pool.py -- `ReqToTokenPool` gains `binding_tag` (minted at
construction, re-minted by `clear()`); `Req` gains
`req_pool_binding`, stamped beside `req_pool_idx` in `alloc`; the
existing `reusing` branch refuses a row minted under another
binding. Both pools hold the same row count, so a carried id lands
IN RANGE on someone else's row -- silent, not a device assert.
`SessionSlot` carries the binding with the parked row.
C1.4 phase_req_pool_binding.py -- the sgl-project#919 census on the REQUEST axis:
the outgoing pool is counted before the rebind, the line is
emitted on every cutover whatever it found (indicator law), and a
non-zero escapee count raises with the rids and rows named.
C1.5 kv_session_offload.py -- the only tree-wide cacher of the pool
OBJECT now reads `self.scheduler.req_to_token_pool` through a
property, so its ~20 `req_to_token[...]` writes cannot land on the
outgoing phase's tensor.
Evidence
red-first on the parent 8fe7b60:
17 failed / 4 passed. The 4 green are exactly the must-not-change
and already-true ones (the three shape checks survive; reuse within
one binding stays allowed; the two default-path pins).
green after: 21 passed.
mutants, each verified to have actually applied before running:
M1 DANGER gate the rebind on --phase-flip-rebind-hicache -> 2 failed
M2 drop the binding_tag clause from the guard -> 2 failed
M3 census counts only rows a request still names -> 1 failed
M4 restore the req_to_token alias in phase_flip_boot -> 1 failed
M5 skip clear() of the incoming pool -> 1 failed
M6 re-cache the pool object in kv_session_offload -> 1 failed
(a first M6 attempt cached under a DIFFERENT attribute name and
survived -- an equivalent mutant, dead code nothing reads, not a
test gap; the faithful form is the one counted.)
ruff: per-file counts identical to HEAD on all seven modified files
(1/0/8/43/0/0/0); the new module and the new test file are clean.
C1.5 follow-on: four test modules set the pool ON THE MANAGER
(`mgr.req_to_token_pool = ...`), which the read-at-use property makes
read-only. They now set it where it lives -- on the scheduler stand-in.
Found by the desk gate (test_host_finish_stream_659, +3 failures vs the
HEAD baseline); the other three sites are OUTSIDE the gated directory and
were found by a tree-wide sweep of the same assignment shape. Two files
matching the same grep are deliberately NOT changed:
test_kv_spill_destination_unit builds a bare SimpleNamespace (no class, no
property, the attribute is the pool), and test_specv2_kvcache_offloading
sets it on DecodeKVCacheOffloadManager -- a different class.
efschu
pushed a commit
to efschu/htsglang
that referenced
this pull request
Sep 4, 2026
…lders; the cutover moved one WHAT WAS RED (parent a890b8a, test_1201_phase_stamped_handles.py): 13 failed, 3 passed. The 3 green are the default-path parity cases (an unowned cache still answers with its constructor pool; no counter to carry is not an error; a plain ReqToTokenPool without the hooks is not an error). After the cut: 16 passed; with the two sibling files (test_1040_req_pool_per_phase, test_cutover_participants_859) 82 passed. THE DEFECT. ReqToTokenPool is stamped onto four holders at CONSTRUCTION and rebind_req_pool_for_cutover moves exactly one, the scheduler's: * UnifiedRadixCache.req_to_token_pool (unified_radix_cache.py:429) * the pool's tree_cache back-reference (memory_pool.py:1871), whose only two bind_tree_cache callers are tree-cache constructors (unified_radix_cache.py:514-515, mamba_radix_cache.py:502-503), so the incoming pool keeps tree_cache=None for the whole phase and the evict-then-retry at memory_pool.py:2019-2024 never arms -- the sgl-project#581/sgl-project#773 regression that unified_radix_cache.py:507-513 was written to close * the pool's layer_transfer_counter (memory_pool.py:1876) * FutureMap.pool (overlap_utils.py:339), NOT moved by this cut Rank-uniform, so no ballot, digest or MIN can catch it: every rank is wrong the same way. Both phases' pools hold the same row count, so the divergence lands IN RANGE. Loud half: common.py:1849 -> free_slot's double-return refusal (memory_pool.py:492-497). Silent half, which runs FIRST: common.py:1836 reads the wrong pool's req_to_token row and hands those indices to token_to_kv_pool_allocator.free(). THE CUT. (a) UnifiedRadixCache.req_to_token_pool is a read-at-use property onto a registered owner, following kv_session_offload.py:2921-2933, whose docstring names this exact hazard. Unbound = byte-identical fallback to the constructor pool, so no non-flip boot changes. (b) _restamp_phase_handles re-stamps tree_cache, the pool's back-reference and the layer-transfer counter onto the incoming pool at the seam. (c) assert_req_pool_identity refuses at the end of the cutover (phase_flip_runtime, after the sgl-project#719 HiCache rebind, outside its try/except) when any holder still names a different pool. (d) cutover_participants: two new REGISTRY rows (request_pool_phase_ownership, req_pool_back_references) and req_to_token_pool added to MUTATED_STATE -- a HANDLE the cutover REPLACES, next to ten quantities it recomputes. future_map is on the registry as an explained gap, not moved. BRIEF PREMISE FALSIFIED -- (c) as briefed cannot be built. "Turn the two silent returns at memory_pool.py:1995-1998 / :3992-3994 into refusals" would refuse the DEFAULT path. layer_transfer_counter is None on every boot without a hierarchical cache: it is only ever set by register_layer_transfer_counter, whose callers are all HiCache/cache- controller paths, and swa_memory_pool.py:124-125 registers None on purpose; register_layer_transfer_counter's own docstring calls mamba_transfer_frame =None "the historic no-wait behaviour, unchanged". So None is a legitimate steady state, not a defect. What IS a defect is the handle failing to cross the seam, and that is decidable where both pools are in hand. The refusal therefore moved to the seam and the carry was built; the join sites are untouched. ANCHOR DRIFT (three, all reported rather than worked around): * "phase_req_pool_binding.py:180 is the only runtime rebind" -- :180 is the def line at the tree; the assignment is at :230, moved by CUT C (a890b8a). devindex still prints :180 because its store is pinned one commit back. * memory_pool.py:3992-3994 -- the guard is at :3993-3994; :3992 is a comment line. * memory_pool.py:496 -- the enclosing method free_slot is right, but :496 is a fragment of the double-return message; the line every finish hits is :506 (free -> free_slot), and :492-497 is the guard. All other anchors verified verbatim at the tree: unified_radix_cache.py:429 /507-513/514-515, mamba_radix_cache.py:502-503, memory_pool.py:1871/1876/ 1995-1998/2019-2024, mamba_component.py:480, common.py:1780/1836/1847/1849, kv_session_offload.py:2921-2924, cutover_participants.py:92/314-325. MUTANTS (scratch copies in /tmp/wt1201-orig, all restored): 1 DANGEROUS DIRECTION -- bind_req_pool_owner stores None, i.e. the pre-cut cached reference, which answers wrongly instead of refusing. Killed by 4: test_a_bound_cache_follows_the_owner, test_the_cutover_leaves_cache_and_scheduler_on_one_pool, test_the_kv_read_sees_the_rows_the_running_phase_wrote (the silent one), test_a_clean_cutover_passes. 2 DANGEROUS DIRECTION -- bind_tree_cache re-stamp removed, so the pool's evict-then-retry stays disarmed silently. Killed by 2: test_the_incoming_pool_is_bound_to_the_tree_cache, test_a_clean_cutover_passes. 3 assert_req_pool_identity returns early (#505a shape). Killed by 2: test_a_cache_left_on_the_outgoing_pool_is_refused, test_a_pool_left_bound_to_nothing_is_refused. 4 counter carry dropped. Killed by 1: test_the_layer_transfer_counter_is_carried_to_the_incoming_pool. VERIFIED: red-then-green tallies above; ruff parity on all four touched files (unified_radix_cache.py had 1 pre-existing F401 typing.Sequence before and after, zero new); import smoke of all four modules; no external writer to a tree cache's req_to_token_pool and no __dict__ access to it (grep, none); UnifiedRadixCache has no subclasses (devindex symbol_profile), so the property cannot be shadowed. NOT VERIFIED: no boot. Whether the layer-transfer counter carry ever fires on the standing flip boot is UNMEASURED -- it depends on whether the assembler registers a counter on both stacks' request pools, which only a boot decides. MambaRadixCache.req_to_token_pool (mamba_radix_cache.py:498) is the same cached-reference shape and is NOT converted here. ANSWER TO THE COMPLETENESS CRITIC, recorded as asked: this registry axis covers HANDLES THAT MOVED -- objects the cutover replaces and every holder that cached one. It does NOT cover WHAT WAS STILL OWED at the arm: launched-unreaped passes, outstanding receives, ring slots, lap counters. Nothing here detects a debt; MUTATED_STATE's read-window axis and the new handle axis are both about identity, not about obligations in flight. That second axis remains unbuilt and unclaimed.
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.
Thank you for your contribution, we really appreciate it. The following instructions will help improve your pull request and make it easier to receive feedback. If there are any items you don't understand, don't worry. Just submit the pull request and ask the maintainers for help.
Motivation
Please explain the motivation behind this PR and the goal you aim to achieve with it.
Modification
Briefly describe the changes made in this PR.
Checklist
pre-commit run --all-filesor other linting tools are used to fix potential lint issues.