Conversation
…er write_through Under write_through with a storage backend enabled, every inserted node immediately stages a host mirror, but the only reclaim path (evict_host) skips any node whose device copy is still resident, and the storage backup ack merely drops a refcount without freeing the slot. The mirror therefore stays pinned until the device copy is evicted. Under sustained load the device pool stays full, so pinned mirror demand approaches the full device pool size: whenever the host pool is smaller than the device pool (hicache_size below device KV capacity), the host pool fills with irreclaimable mirrors, every new staging and every storage prefetch fails with host_alloc_failed, and the storage tier starves in both directions. The default hicache_ratio=2.0 hides this by capping mirror demand at half the pool -- at the cost of a device-pool-sized slice of host memory that is permanently pinned. This change makes the backup ack meaningful: pages confirmed written to storage are marked durable (bounded LRU keyed by page hash, so mid-backup node splits are covered), and a node whose device copy is resident and whose pages are all durable becomes a release candidate. Candidates are released only under host-pool pressure, through a prepare/commit protocol that rides the existing storage-drain collective: each rank contributes its pool deficit and last round's release plan (count/tokens/digest, negated pairs so a single MIN recovers min and max), and a plan executes only after every rank reports the identical triple -- mutation never precedes consensus. Repeated mismatch fail-closes the feature back to today's behavior. Acked completed-token counts are also carried in the collective so MLA-style deployments where only one rank writes storage (backup_skip) still converge. Opt-in and default-off: set SGLANG_HICACHE_MIRROR_RELEASE=1 to enable (SGLANG_HICACHE_MIRROR_RELEASE_FREE_FRAC tunes the free-space target, SGLANG_HICACHE_DURABLE_CAPACITY the durable-hash LRU, SGLANG_HICACHE_MIRROR_REFRESH_ROUNDS the re-stage age gate). With the flag unset a single added branch runs per drain round. Measured on a 16-rank deployment with hicache_size ~0.25x the device pool and a mooncake backend (128-session shared-prefix load): storage write-out rate went from 11.3% to 100%, host_alloc_failed write rejections from 16.7M tokens to zero, and warm-round storage hit rate from 0.53% to 3.17% (12.87% across two engines sharing the backend). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
phlv
requested review from
Ying1123,
alphabetc1,
hanming-lu,
hnyls2002,
hzh0425,
ispobock,
merrymercy,
xiezhq-hermann and
yizhang2077
as code owners
August 6, 2026 09:30
A durable node demoted to host-only and later restored on device via the insert() KV-recomputation paths holds a redundant mirror again, but neither recompute branch re-registered it -- the mirror stayed pinned forever, quietly rebuilding the leak this feature removes under evict-then-recompute churn. Register both the full-match and the post-split recompute branches, mirroring what load_back already does, and add a regression test. Found by adversarial review. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Leoyzen
added a commit
to Leoyzen/sglang
that referenced
this pull request
Aug 8, 2026
…project#32035 sgl-project#33656 sgl-project#32183 sgl-project#33145) Applied PRs (latest from GitHub): sgl-project#33288 Indexer logits OOM fix sgl-project#30393 HiCache packed/sidecar draft caches sgl-project#31170 DPA prefix_affinity load balancing sgl-project#33795 DSpark compact ragged-verify CUDA graph JIT race sgl-project#32467 C128 plan-kernel warp barrier sgl-project#33865 DSpark x prefill CP unblock sgl-project#30371 SWA state pool sizing (storage page) sgl-project#33358 FlashMLA norm-rope K-tokens-per-block ILP sgl-project#33872 num_draft_tokens clamp + extend_len==0 skip (supersede sgl-project#32183) sgl-project#34002 Sidecar backup vacuously-successful fix (replaces sgl-project#33656, with tests) sgl-project#33862 Reclaim redundant host mirrors after storage backup sgl-project#31315 Avoid repeated Mooncake gets after stale hits sgl-project#32327 Q8KV8 sparse MLA prefill backend (flashmla_sparse_q8) sgl-project#31668 Fix sidecar pool life-time (use-after-free on prefetch abort) sgl-project#31195 TP0 verify-token-budget broadcast (adapted to get_schedule() API) Dropped (per user request or superseded): sgl-project#32771 IndexCache C4 top-k reuse — has bug sgl-project#32035 DSpark C128 online compressor — has bug sgl-project#33656 Superseded by sgl-project#34002 (same fix + unit tests) sgl-project#32183 Superseded by sgl-project#33872 (included in supersede PR) sgl-project#33145 Base f01f706 already has superior reasoning-effort profile system Conflicts resolved: sgl-project#31195: adapted to base get_schedule().disable_overlap_schedule API sgl-project#32327: path remapped jit_kernel/ -> kernels/jit/ and kernels/ops/attention/ sgl-project#31668: applied cleanly on top of sgl-project#30393+sgl-project#34002+sgl-project#33862 modifications
Leoyzen
added a commit
to Leoyzen/sglang
that referenced
this pull request
Aug 25, 2026
…1425) not active on unified path Production DSV4-Flash runs UnifiedRadixCache (hybrid_swa), not HiRadixCache. These two cherry-picks only modified hiradix_cache.py (+ cache_controller two-phase API consumed solely by hiradix) and thus never take effect in production, misleading future readers: - sgl-project#33862 Host mirror release: hiradix-only (unified path has no equivalent; SGLANG_HICACHE_MIRROR_RELEASE env was a no-op). Also staged 248 lines of unrelated pyproject.toml dep churn. - sgl-project#31425 TP/PP write-through & load-back consensus: the all_reduce(MIN) consensus lives in hiradix_cache.py only; unified's _execute_kv_backup calls hybrid_cache_controller.write() and never triggers it. Keep: sgl-project#30689 (base_prefix_cache InsertParams.is_finished field is a dependency of the unified is_finished guard) and sgl-project#26837 (metrics, shared). Keep: 072c953 unified is_finished guard (the actual production fix). Reverts commits 4266d89 and 29b0753.
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.
Motivation
Under
--hicache-write-policy write_throughwith a storage backend (--hicache-storage-backend mooncakeetc.), the host (L2) pool deadlocks whenever it is smaller than the device pool, silently starving the storage tier. Three code facts combine into this:write_backup()allocates host slots for every node that enters the device radix cache (threshold 1 underwrite_through)._drain_backup()only callsentry.release_host(), which decrementshost_ref_counter; the memory stays attached tonode.host_value.evict_host()guards withif not x.evicted: continue, so a mirror can only be reclaimed after the device copy is evicted. The in-band rescue insidewrite_backup()(evict_hostthen retry) is a no-op for the same reason.Under sustained load the device pool stays full, so pinned-mirror demand approaches the full device pool size. With
hicache_sizebelow device KV capacity the host pool fills with irreclaimable mirrors, and both staging (hicache_write_rejected_tokens_total{reason="host_alloc_failed"}) and storage prefetch (same reason,stage="storage_prefetch") are rejected wholesale — storage starves in both directions. The defaulthicache_ratio 2.0hides the bug by capping mirror demand at half the pool, at the cost of a device-pool-sized slice of host DRAM that is permanently pinned and never reused.On a production 16-rank deployment (hicache_size ≈ 0.25× device pool, mooncake backend) we measured 329M tokens of write rejections vs 21.6M actually written (94% rejected),
hicache_host_reserved_tokens{purpose="write"}pinned at 0, and a storage hit contribution of 0.55%.Modifications
Make the backup ack meaningful, so the host pool works as a channel into storage instead of a second permanent copy:
completed_tokensprefix is marked; hash-keying also covers nodes split mid-backup).lock_ref/host_ref_counter) are kept, not lost.backup_skip) convergent.Opt-in, default off:
SGLANG_HICACHE_MIRROR_RELEASE=1enables the feature (_FREE_FRAC,SGLANG_HICACHE_DURABLE_CAPACITY,_REFRESH_ROUNDStune it). With the flag unset, one added branch runs per drain round and behavior is unchanged. When enabled,check_hicache_eventsroutes storage draining throughdrain_storage_control_queues(which carries the protocol fields) instead of the fused ready-counts collective.Results on the same 16-rank deployment, same load, only the flag flipped: storage write-out 11.3% → 100%,
host_alloc_failedwrite rejections 16.7M tokens → 0, warm-round storage hit 0.53% → 3.17% (12.87% across two engines sharing one mooncake backend, with 2× wall-clock speedup on the warm round).The same pinning pattern exists in
unified_radix_cache.py/hi_mamba_radix_cache.py; this PR scopes toHiRadixCacheand the others can follow once the approach is agreed on.Accuracy Tests
Not applicable — no model-output path is touched. Correctness of the cache state machine is covered by the unit tests below; a released mirror is only ever dropped after the storage backend acked the covering pages, and any uncertainty (partial ack, consensus mismatch, busy nodes) keeps the mirror.
Benchmarking and Profiling
Numbers above; measured with the hicache storage metrics (
write_rejected/storage hit sourcedeltas per benchmark arm) on 128-session × 2-round shared-prefix load, 8-way concurrency, cold + warm arms. Feature off shows no measurable drain-round overhead (single boolean branch).Checklist
test/registered/unit/mem_cache/test_hicache_mirror_release.py— 26 falsifiable cases (they fail on the unfixed code) covering durable marking with partial acks, split-during-backup, TP consensus commit/mismatch/fail-close, MLA single-writer, released-parent eviction progress, churn guard, and a concurrency regression for the ack-queue snapshot.🤖 Generated with Claude Code
CI States
Latest PR Test (Base): ❌ Run #31091637794
Latest PR Test (Extra): ❌ Run #31091637176