Conversation
… per-request ring gate sgl-project#38269 (822e73c) replaced the SWA match validator's gate swa_device_only_hicache = not has_swa_host_pool and enable_hicache with `is_swa_req_ring(allocator)`. On a hybrid-SWA model served with a paged (or request-window) SWA pool that predicate is False, so a host-only Full-KV tombstone (Full KV backed up on the host, SWA neither on the device nor on the host, because SWA never leaves the device on that layout) stopped being a valid match boundary. The validator returned False on every such node, `host_hit_length` stayed 0, `needs_host_load_back()` was never true and `init_load_back` never ran: every replay of a host-backed prefix matched 0 tokens and was prefilled cold. Seen on DeepSeek-V4.1-Flash, TP4, 4x RTX PRO 6000, HiCache on, paged SWA (`DSV4 SWA storage: worker=target, storage=paged` / `storage=request_window`): `hicache_backup_tokens_total` grew normally but `load_back_tokens_total` and `cached_tokens_total{cache_source="host"}` never appeared in /metrics, and a 36 x 64k replay pass restored 0/36 with TTFT 10.3 s (cold) against 36/36 and 0.37 s before sgl-project#38269. Keep upstream's ring gate and restore the device-only-HiCache clause next to it: both describe a layout where the SWA rows are not stored in the tree, so a host-backed Full-KV prefix is matchable and load_back plus the scheduler's window rebuild take it from there. With the fix the same box reads 36/36 host hits, replay TTFT 0.366 s, `load_back_tokens_total` 9.40M and `cached_tokens_total{cache_source="host"}` 2.35M, equal to the pre-sgl-project#38269 image; the device-hit and fresh-pool cells are unchanged. Test: test/registered/unit/mem_cache/test_swa_host_match_gate.py (CPU, 7 cases) drives the validator closure with stand-ins for the component, the tree core and the nodes; the paged-SWA-with-HiCache case fails on the ring-only gate and passes here, the ring case and the SWA-host-pool miss case pin the behaviour sgl-project#38269 wanted. Signed-off-by: Avi Fenesh <aviarchi1994@gmail.com>
avifenesh
requested review from
Ying1123,
alphabetc1,
hanming-lu,
hnyls2002,
huangtingwei9988,
hzh0425,
ispobock,
merrymercy,
xiezhq-hermann and
yizhang2077
as code owners
September 15, 2026 05:35
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
#38269 (822e73c) changed the SWA match validator in
python/sglang/srt/mem_cache/unified_cache/components/swa.py::SWAComponent.create_match_validator:The ring gate is right for the unified-KV ring, but it is narrower than the clause it replaced. With HiCache on and no SWA host pool (a paged or request-window SWA pool:
DSV4 SWA storage: worker=target, storage=paged,storage=request_window), SWA never leaves the device, so every host-backed prefix is a host-only Full-KV tombstone:cd.value is None,cd.host_value is None,node.backuped. Before #38269 that node was a valid match boundary andload_backrestored the Full KV. After itis_swa_req_ring(allocator)is False on that layout (swa_req_ring = ring_size is not None, only set on the ring path), the validator returns False on every such node,host_hit_lengthstays 0,Req.needs_host_load_back()is never true andinit_load_backnever runs. The host tier fills and is never read.Where I hit it: DeepSeek-V4.1-Flash, TP4, 4x RTX PRO 6000 (SM120), HiCache on, paged SWA. Same box, same launch, upstream
dsv4.1before and after the commit that brought #38269 in:cached_tokens_total{cache_source="host"}after a 36 x 64k replay passload_back_tokens_totalhicache_backup_tokens_total/hicache_host_used_tokensThe backup side kept working, which is why it looks like a cache that is full and never hits. Nothing in the server args or the HiCache defaults changed between the two boot logs; the bisect over the 353-commit range landed on this hunk, and restoring the clause as a one-hunk derivative image brought the numbers back.
Modifications
One hunk in
create_match_validator: keep upstream's ring gate and put the device-only-HiCache clause back beside it.Both describe the same thing for the validator: the SWA rows are not in the tree, so a host-backed Full-KV prefix is matchable and load_back plus the scheduler's window rebuild take it from there. The ring path is unchanged. A layout with an SWA host pool is unchanged too: there a node with no SWA host value is a real miss and still reads False.
Not a duplicate: preflight against
upstream/main(ddd4600) applies clean, none of the added lines is on HEAD, and the open PRs that touchswa.py(#36729 shared byte budget for hybrid-SWA memory, #39283 buffer-mode prefetch pipeline) share no added line with this one and do not touch the validator.Accuracy Tests
test/registered/unit/mem_cache/test_swa_host_match_gate.py(CPU, 7 cases,base-a-test-cpu): drives the validator closure with stand-ins for the component, the tree core and the nodes, no pools built.test_paged_swa_with_hicache_and_no_swa_host_pool_accepts_tombstonefails on the ring-only gate and passes with this fix; the ring case (with and without an SWA host pool), the SWA-host-pool miss, HiCache off, the evicted-and-not-backed-up node, the device-only match and the window count are pinned so #38269's behaviour stays.On the box: the 12c cache cell (device hits under HiCache) and the fresh-pool prefill / prodshape cells on the fixed image are at parity with the reference (8b 524k +1.6%, 9b c=1..8 -4%, 9c cached_ratio 0.993, 9d 2.627 tokens/verify), gsm8k unchanged within its draw.
Benchmarking and Profiling
Above: replay TTFT of a host-backed 64k prefix 10.3 s -> 0.366 s (reference 0.373 s), host restores 0/36 -> 36/36,
load_back_tokens_total0 -> 9.40M. No other cell moved.Checklist
test/registered/unit/mem_cache/test_swa_host_match_gate.py.run-cilabel if you want the CI to run.CI States
Latest PR Test (Base): ❌ Run #34933368381
Latest PR Test (Extra): ❌ Run #34933368250
Latest PR Test (AMD ROCm 10): ❌ Run #34933368332