Skip to content

[Fix] mamba: Retry queued prefills when cache capacity may have recovered - #37612

Open
ormandj wants to merge 3 commits into
sgl-project:mainfrom
ormandj:pr/hybrid-ssm-admission-latch
Open

ormandj wants to merge 3 commits into
sgl-project:mainfrom
ormandj:pr/hybrid-ssm-admission-latch

Conversation

@ormandj

@ormandj ormandj commented Sep 2, 2026 •

Copy link
Copy Markdown
Contributor

Motivation

A hybrid SSM server can leave requests queued even after Mamba-state capacity becomes available. Once admission marks the running batch as full, the scheduler does not retry while that batch remains unchanged. Cached states can become evictable during that time, so a previous capacity failure is not a valid reason to stop checking.

Retrying exposes a second problem. Each admission attempt re-runs the prefix match, and a match that hits a cached Mamba state allocates a per-request slot for the copy-on-write before PrefillAdder checks capacity. If no slot can be freed (for example, decode has consumed a shared Mamba/attention pool since the earlier refusal and the remaining cached states are locked), that allocation asserted and terminated the scheduler instead of leaving the request queued.

This PR retries admission for hybrid SSM models whose cache supports Mamba state, and makes a failed copy-on-write allocation an ordinary admission refusal. It does not bypass capacity checks or enlarge the pool.

Modifications

  • Clear the full-batch admission latch each scheduler round for that configuration, alongside the existing hybrid-SWA and priority-preemption resets. Other configurations retain their existing behavior.
  • When the Mamba copy-on-write slot cannot be allocated even after eviction, raise a dedicated MambaCowAllocError instead of asserting. The matched node's lock is still released and the request is left without a slot.
  • Prefill admission catches that error and treats it as a NO_TOKEN refusal: the request stays in the waiting queue and is retried in a later round. Other callers of the prefix match are unchanged apart from the exception type.

Accuracy Tests

All tests below were run with real pytest 9.1.1 via uv run in a CPU-only PyTorch 2.14.1 environment (no GPU), from the repository root with PYTHONPATH=python:test. The two new files pass 8 of 8 with python -m pytest test/registered/unit/managers/test_scheduler_hybrid_ssm_admission_latch.py test/registered/unit/managers/test_scheduler_hybrid_ssm_admission_retry.py. These tests call scheduler and cache methods directly with stubbed collaborators; no model is loaded.

  • Four latch tests check which configurations clear batch_is_full each round.
  • A three-round scheduler test with an adder stand-in that always returns NO_TOKEN verifies admission is retried each round for hybrid SSM; the non-hybrid case attempts admission once.
  • A component test drives MambaComponent.finalize_match_result_in_cache with an allocator that has no free slot and an eviction that frees nothing. It raises MambaCowAllocError with balanced lock calls and no slot assigned (the previous code raised AssertionError).
  • A scripted scheduler test covers refusal and retry: the adder stand-in refuses the request in round 1, its stubbed init_next_round_input raises MambaCowAllocError in round 2, and in round 3 the stub succeeds and the adder stand-in accepts. Round 2 returns no batch and keeps the request queued without calling add_one_req (on the previous code the error propagated out of the scheduler); round 3 passes the request to the (mocked) ScheduleBatch.init_new and empties the waiting queue. No real allocator capacity is consumed or recovered in this test.

Existing related unit tests in the same environment: test/registered/unit/mem_cache/test_mamba_donated_alloc_ratio.py, test_mamba_path_state_cap.py, test_mamba_unittest.py, test/registered/unit/managers/test_prefill_adder.py, test_schedule_policy.py and the 18 other test/registered/unit/managers/test_scheduler_*.py files give 197 passed, 5 skipped (the test_mamba_path_state_cap.py pool fixtures need CUDA) and 596 subtests passed. A successful copy-on-write allocation after real capacity recovery, with a real Mamba cache on GPU, has not been tested.

Speed Tests and Profiling

Adds an admission attempt each round while capacity remains unavailable. Overhead was not measured.

Checklist

  • Format changed code.
  • Run regression tests described above.

Review and Merge Process

  1. Ping Merge Oncalls to start the process. See the PR Merge Process.
  2. Get approvals from CODEOWNERS and other reviewers.
  3. Trigger CI tests with comments or contact authorized users to do so.
    • Common commands include /tag-and-rerun-ci, /tag-run-ci-label, /rerun-failed-ci
  4. After green CI and required approvals, ask Merge Oncalls or people with Write permission to merge the PR.

Developed with AI assistance.


CI States

Latest PR Test (Base): ❌ Run #38053460648
Latest PR Test (Extra): ❌ Run #38053460464
Latest PR Test (AMD ROCm 10): ❌ Run #38053460574

@ormandj ormandj changed the title Re-evaluate prefill admission every round on hybrid SSM radix caches Retry latched prefill admission for Mamba-aware hybrid SSM caches Sep 2, 2026
@ormandj ormandj changed the title Retry latched prefill admission for Mamba-aware hybrid SSM caches Retry queued prefills when Mamba cache capacity may have recovered Sep 6, 2026
@ormandj
ormandj force-pushed the pr/hybrid-ssm-admission-latch branch 2 times, most recently from 5d8f335 to 7624e35 Compare September 8, 2026 21:44
@ormandj
ormandj force-pushed the pr/hybrid-ssm-admission-latch branch from 7624e35 to e2a2879 Compare September 30, 2026 01:20
@ormandj

ormandj commented Sep 30, 2026

Copy link
Copy Markdown
Contributor Author

Updated the code and PR body. The scheduler clears the full-batch admission latch each round for the affected Mamba configuration so queued prefills can retry when capacity recovers.

Retrying admission re-runs the prefix match, whose Mamba copy-on-write
allocation asserted when no slot could be freed. Capacity can shrink after
an earlier refusal, so raise a dedicated error there and treat it as a
NO_TOKEN refusal in prefill admission.
@ormandj
ormandj force-pushed the pr/hybrid-ssm-admission-latch branch from df1a10c to 2dd9bc5 Compare October 10, 2026 12:50
detain added a commit to detain/sglang that referenced this pull request Oct 15, 2026
…ests queued instead of asserting [batch-apply-h 2026-10-15]

MambaCowAllocError(RuntimeError) in base_prefix_cache; unified mamba.py
finalize_match_result_in_cache COW site raises instead of asserting (:226
crash class). Scheduler _get_new_batch_prefill_raw: try/except around
init_next_round_input -> res=NO_TOKEN (falls into existing revert block,
then alloc_group_end still runs; raise happens pre-pool_idx-set so holds_mamba
False -> no double-free); batch_is_full per-round reset extended with
is_hybrid_ssm and tree_cache.supports_mamba(). Union: our staging hunk keeps
observe_storage_staging_ready inside the else; our is_empty()/has_chunked/
min_free_slots_delayer/QSA truncation_align_size blocks intact.
Test fixtures adapted to our drifted scheduler: running_batch.is_empty stub
(our empty-batch reset block), adder.preempt_list (our preemption requeue),
s.hisparse_coordinator (our post-init_new assign). Squash 8f55e71+
f648184+2dd9bc5902.
detain added a commit to detain/sglang that referenced this pull request Oct 15, 2026
- test_hisparse_unit.py: module-level get_device() crashed collection on
  accelerator-less hosts (register_cuda_ci file); skip allow_module_level
  instead, matching the all-GPU-files convention.
- test_scheduler_batch_is_full_latch.py: the __new__ Scheduler stub lacked
  is_hybrid_ssm, which the sgl-project#37612 admission-gate arm now reads; set False so
  the gate short-circuits before tree_cache (stub stays non-hybrid, same
  semantics under test).

Both byte-identical failures verified pre-existing at 64f204b in a temp
worktree. 1E->1S, 1F->1P.

Co-Authored-By: opencode <noreply@opencode.ai>
@ormandj ormandj changed the title Retry queued prefills when Mamba cache capacity may have recovered [Fix] mamba: Retry queued prefills when cache capacity may have recovered Oct 19, 2026
detain added a commit to detain/sglang that referenced this pull request Oct 29, 2026
[Fix] scheduler: keep chunked prefill within allocatable KV under memory
pressure. Three admission bugs: (1) a continuation chunk could ask
alloc_extend for more KV than is free -- PrefillBudget.
available_chunk_tokens now caps by real free KV minus the one-page
over-ask and defers (None) instead of forcing a full chunk; (2) the
single-unfinished-chunk guard now applies to ALL policies, not just
shortest-prefill-first (a second unfinished chunk was untrackable);
(3) batch_is_full is cleared unconditionally each round -- a latched
flag was otherwise only cleared when the running batch shrinks,
stalling the waiting queue until an unrelated finish (absorbs closed
sgl-project#35609).

UNION / DEVIATIONS:
- scheduler.py hunk hand-merged: our region carried the entry-198
  hybrid-SSM reset arm + is_empty arm; both are strict subsets of the
  unconditional clear and were removed. Entry-198's COW NO_TOKEN-retry
  semantics are unaffected (admission re-runs every round anyway).
- Consequently the two entry-198 gate tests were re-pinned to the new
  unconditional behavior (non-hybrid now retries every round too);
  their mamba-arm and COW-recovery cases pass unchanged.
- PR's new test_scheduler_batch_is_full_latch.py NOT imported: the
  file already exists in-tree (entry-222 wave) with fork stubs; kept
  ours, refreshed the stale sgl-project#37612-arm comment.
- AITER backend hunk + test_aiter_attention_backend_handler.py SKIPPED
  per instruction (ROCm aiter path irrelevant to this stack).
- test_prefill_memory_budget import hunk conflicted (our tree lacks the
  upstream ChainAllocator test rework): resolved as
  PrefillBudget + SWAPrefillBudget union; new TestContinuingChunkCap
  verbatim.
- Chunk-guard hunk applied verbatim: our _select_prefill_admission
  matched the PR base exactly; no mixed-chunk adaptation fights it
  (mixed-chunk tracks its rows via the scheduler, not new_chunked_req).

CPU: targeted (prefill_adder + memory_budget + 3 latch/retry +
held_row) 83P/67sub; full managers dir 993P/2F/3skip/904sub == the
2F are pre-existing order-dependent test_mm_process_config failures
(verified identical on the stashed baseline, 992P/2F).

(cherry picked from commit 0954308)
(cherry picked from commit d2bf2d29c4b5382291e672a3a1f0ac844cba196b)
detain added a commit to detain/sglang that referenced this pull request Oct 29, 2026
… (+test re-anchors)

Re-plants the fork-hunks patch (replant/...components__mamba.py.fork-hunks.patch)
onto the #46970x component rewrite. B3 marker consumed in
unified_cache/components/mamba.py.

Per-hunk state:
- entry 216 (prefix-lock release before the donation retry): ALREADY ABSORBED
  at the merge inside the old _alloc_mamba_slot; the replant keeps it verbatim
  while renaming to _try_alloc_mamba_slot -> Optional (test-verified shape).
- entry 198 (sgl-project#37612): match-path COW failure raises MambaCowAllocError
  (class definition survived the merge; the RAISER was the dropped half).
  CROSS-NOTE FOR B4: the prefill-admission CATCHER (the merge dropped it too;
  test_scheduler_hybrid_ssm_admission_retry.py in managers awaits it — B4 zone).
- entry 197 (sgl-project#37943): donation with a pinned pool SKIPS the boundary
  checkpoint + rate-limited capacity warning (_record_skipped_checkpoint;
  available-count via req_to_token_pool.mamba_allocator, the fork's accessor —
  the merge's storage abstraction offers no equivalent that survives the
  duck-typed harnesses).
- entry 196 (sgl-project#39209): _mamba_value_misaligned + mamba_value_seqlen stamped at
  BOTH prepare sites (the InsertParams field survived the merge unused);
  commit refuses to stamp, cleanup frees the misaligned value.
- entry 30 (sgl-project#39830 fencing, the L2 half): _token_depth/_record_host_ckpt_pos/
  _host_ckpt_position_ok/_poison_stale_host_ckpt + prepare_load_back
  PrepareLoadBackResult(rejected=True) (the URC unwind rode B3-3) + the two
  record sites (BACKUP_HOST attach, LOAD_BACK attach). Host-pool surface
  (ckpt_pos_buffer/record_ckpt_pos/get_ckpt_pos in pool_host/mamba.py)
  verified PRESENT (survived) — not duplicated. _poison call adapted to the
  widened _evict_component_and_detach_lru signature (cache_actions positional).
- entry 90: alloc_prefetch_staging shortfall reclaim (MAMBA half; the SWA half
  rode B3-4; the host-group half rode B3-4).
- class-level tallies (_ckpt_pos_rejects/_skipped_checkpoint_count) follow the
  established __new__-harness convention.

Test re-anchors (B2 owns this file's markers; these are the fork-case
mechanical repairs its re-home left needing after the component landed):
- test_mamba_donated_alloc_ratio.py: _RatioCache stub gained the
  req_to_token_pool the storage now routes donate through (+ the lazy-boundary
  batch stub carries token_to_kv_pool_allocator, schedule_batch.py:3661).
- test_hicache_mamba_ckpt_pos_fence.py: the evict_and_detach mirror updated to
  the post-#4697x positional cache_actions.

ACTUALS (damage -> after):
  test_mamba_donated_alloc_ratio   13P/8F/10sub -> ** 17P/0F/14sub ** (295 pin 16P/14sub: AT OR ABOVE)
  test_hicache_mamba_ckpt_pos_fence 12P/3F -> ** 15P/0F ** (entry-30 pin "fence 15P" EXACT)
  test_mamba_value_alignment        2P/1F -> 3P/0F (entry-196 guard)
  test_mamba_path_state_cap         5P/5S unmoved (honest CUDA skips; entry 199 half rides the URC already landed)
  test_hicache_mamba_sizing          30P/0F/17sub (unmoved, B3-4 verified)
Lint: ruff format+F821/E9/F401 clean; py_compile+import OK. No GPU; no push.
detain added a commit to detain/sglang that referenced this pull request Oct 30, 2026
…entries 122,198,200,211,215,220,267,268,305

Re-plants the scheduler.py fork-hunks (replant/...managers__scheduler.py.fork-hunks.patch)
onto the post-sgl-project#46977 admission structure. tp_worker marker consumed (15/23 B4 markers
still on the tree; per-file counts, damage-state from entry 310's map).

PER-HUNK DECISIONS (all VERIFIED against the merged tree, not taken on faith):
- entry 122 (sgl-project#40458): grammar-sync overlap disable RE-PLANTED for mixed mode
  (is_decode() OR is_mixed()) + the fork's trimmed comments; upstream's
  host-draft-only arm was the pre-sgl-project#40458 form (origin/main verified: absent).
- entry 220 (sgl-project#39765, OPEN port): _has_pending_mamba_cache_update + the early
  pop_and_process/short-circuited disable-overlap RE-PLANTED into
  event_loop_overlap; the require_mlp_sync/disable/extra-buffer gate verbatim.
- entry 198 (sgl-project#37612): THE CATCHER — B3-5 landed the raiser; the
  try/except MambaCowAllocError around init_next_round_input (res=NO_TOKEN,
  request stays queued; the revert block + unconditional alloc_group_end were
  already in the merged tree = the no-leak halves VERIFIED PRESENT) RE-PLANTED
  with the import. COW component test re-anchored to the #46970x kv_storage
  seam the B3-5 replant moved _state() behind (stub was the pre-split
  mamba_allocator shape).
- entry 268 (sgl-project#46643, OPEN port -- VERIFIED NEVER MERGED: git log
  12dc623..2a6c204 --grep 46643 = ZERO; only sgl-project#45491/sgl-project#45435/sgl-project#45472/sgl-project#45670
  of the briefed merged-list arrived in-range): the UNCONDITIONAL batch_is_full
  clear RE-PLANTED on the scheduler side, subsuming the entry-198 hybrid-SSM
  arm and the priority-preemption/is_empty arms (policy side lands in B4-3).
- entry 267 (sgl-project#46496): retract_decode new_token_ratio FLOOR clamp RE-PLANTED.
- entry 211: triton_load_watch.prewarm_device_loads(self.model_worker) line
  RE-PLANTED between install() and mark_serving_started (the utils-side
  function survived the merge; only the call site was dropped).
- entry 200 (sgl-project#43527) CAPACITY PAIR RE-PLANTED: model_runner
  .logical_max_total_num_tokens passes allocator= again (the configurator's
  PageInterleavePoolAllocator -> allocator.size branch SURVIVED the merge
  byte-identical, so the merged tree had the fork predicate with upstream's
  consumer-side multiplications = DOUBLE-COUNT); the three scheduler
  * kv_shard_widening multipliers back to the fork form (pool-stats shape,
  load_inquirer, and the init_req one-page-per-shard subtraction);
  tp_worker drops page_interleave_shard_size x + import. Upstream's
  invariant-checker hunk: OBSOLETE (the constructor no longer takes
  max_total_num_tokens -- absorbed by refactor).
- entry 215 (sgl-project#44351/sgl-project#44401/sgl-project#44420): VERIFIED ALREADY PRESENT at the merge
  (filter_batch-every-step + reset ride, num_held_rows, budget_state FIRST
  statement, PD-gate deletion all byte-compare to the fork form) -- DEDUPED,
  no edit. Entry 107's local early-revision of sgl-project#40940 stays superseded
  (entry 215 rule), and its re-plant here would double-count.
- entry 220's/305's pause-window busy accounting + write_fence_stream +
  is_empty arm: VERIFIED PRESENT (entry 305 landed patch-identical via
  sgl-project#46127; the fence assignment is the B3-2 controller half) -- DEDUPED.

TEST FIXTURE RE-ANCHORS (not B2's: no markers, pure new-upstream-contract
drift, per the entry-198 fixture-adaptation precedent): the two hybrid-ssm
admission stubs gain disagg_prefill_bootstrap_queue=None (upstream's
routing-key bootstrap_queue arg in calc_priority arrived in-range).

PRUNE vs BREAKAGE (managers −523sub audit): the three managers test files
vanished in-range (test_schedule_policy_dfs_weight, test_scheduler_batch_is
_full_latch, test_uno_token_accounting) were ALL deleted by upstream's OWN
sgl-project#45672 prune (3438ed6). Two are byte-identical upstream copies = zero
fork delta. The third carried a 3-line fork comment/stub delta whose
SEMANTICS the re-planted unconditional clear pins: its case was the
is_empty-arm mirror of test_scheduler_hybrid_ssm_admission_latch (present,
green, hybrid_swa/non-hybrid arms included) -- NOT re-homed, recorded.

ACTUALS (damage -> after, CVD=9, prod venv, PYTHONPATH=python, per-file):
  test_scheduler_hybrid_ssm_admission_retry   4F -> ** 4P **
  test_scheduler_hybrid_ssm_admission_latch   3F -> ** 4P **
  test_mamba_prefill_cache_boundary  2F/12subF -> ** 5P/12sub EXACT (220 pin) **
  test_scheduler_retract_ratio                1F -> ** 2P EXACT (267 pin) **
  test_scheduler_init_req_max_new_tokens      1F -> ** 10P/6sub **
  test_scheduler_grammar_mixed_tail_sync      1F -> ** 4P **
  adjacent scheduler-stub files (held_row, running_batch_finished,
  pause_generation, disagg_idle, dp_converted, hicache_events) 56P/64sub
  UNMOVED -- no regression from the clear/catcher/capacity edits.
Lint: ruff format + F821/E9 clean on all five files; imports OK (no GPU
execution, no push).

Co-Authored-By: OpenCode <noreply@opencode.ai>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant