[AMD][DSV4] Skip the paged SWA page return under the per-request ring - #38571
Merged
HaiShaw merged 3 commits intoSep 9, 2026
Merged
Conversation
free_group_end and _free_swa_pages return SWA pages to the paged swa_attn_allocator unconditionally. Under the per-request SWA ring the paged allocator is vestigial -- available_size() reads the full-attn allocator and swa_available_size() counts req slots * ring cost, so nothing consults its internal counter -- and pages are owned by the req slot rather than lent per free. Returning them over-credits available_size() past size and trips the assert at the end of free_group_end. Skip the page return when the ring is active, at both the grouped drain and the direct free. The full_to_swa mapping clear still runs in ring mode: skipping it would leave stale peer indices for translate_loc_from_full_to_swa. Repro: DeepSeek-V4-Flash on 2x MI45x (gfx1250), --attention-backend dsv4 --page-size 256 --tp 2. Both TP ranks die in the warmup decode right after 'The server is fired up and ready to roll', 3/3 runs. Needs page_size > 1 and the ring at once, which is why the two paths landing a day apart did not collide in CI. Verified on that config: 5 greedy prompts, including two over 600 tokens that force the 256-token ring to wrap, produce output_ids bit-identical to a build without the page-return path; 32 concurrent 600+-token requests all return correct answers. max_total_num_tokens stays at 44482560 (the sizing without the fix's build is 28960000).
…est-cpu, no GPU) covers both free paths in ring and non-ring mode. The two ring cases fail on the pre-fix code with the same swa.py assert seen on the GPU and pass on the fix; the non-ring cases guard against disabling the page return outright, and one case pins the mapping clear that must still run in ring mode.
ankith117
requested review from
alphabetc1,
hnyls2002 and
ispobock
as code owners
September 8, 2026 22:30
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
5 tasks
Collaborator
1am9trash
approved these changes
Sep 9, 2026
Collaborator
There was a problem hiding this comment.
Add a guard to avoid non-existed page-id free.
Only impact the amd side.
# python/sglang/srt/mem_cache/deepseek_v4_memory_pool.py
if self._unified_kv:
self.swa_req_ring_size = self.unified_swa_ring_size
# python/sglang/srt/mem_cache/allocator/swa.py
ring_size = kvcache.swa_req_ring_size
self._swa_req_ring = ring_size is not None
if self._swa_req_ring:
# ......LGTM.
Contributor
Contributor
|
Looked through the allocation and free paths. This fix makes sense—the ring owns these SWA pages, so they shouldn’t be returned to the paged allocator. The mapping is still cleared, and the non-ring path is unchanged. LGTM. |
amd-danli103
added a commit
to amd-danli103/sglang
that referenced
this pull request
Sep 11, 2026
…covers it Ours returned before clear_full_to_swa_mapping; the landed version clears the mapping first, which test_direct_free_clears_mapping_in_ring_mode pins. allocator/swa.py is now byte-identical to main.
mqhc2020
pushed a commit
to mqhc2020/sglang
that referenced
this pull request
Sep 15, 2026
…sgl-project#38571) Co-authored-by: HaiShaw <hixiao@gmail.com>
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
--attention-backend dsv4with--page-size 256crashes both TP ranks in thewarmup decode, immediately after the server reports it is ready:
Deterministic, 3/3 runs, both ranks identically. The HTTP layer binds and then
the scheduler dies underneath it, so the process exits via SIGQUIT rather than
failing at startup.
Under the per-request SWA ring, the paged
swa_attn_allocatoris vestigial:available_size()returns the full-attn allocator's size andswa_available_size()computesreq_slots * ring_cost, so nothing consults thepaged allocator's internal counter. Ring slots are owned by the req slot for its
lifetime and recycled with it -- they are never lent out per free. Returning
pages to the paged allocator therefore credits capacity it never lent, its
available_size()climbs pastsize, and the assert at the end offree_group_endfires.The two halves reached
maina day apart and each is correct alone:_free_swa_pages->free_page_idspath forpage_size > 1([mem_cache] Free hybrid SWA pages by one representative per page onpage_size > 1#38159).allocation and translation paths (
_swa_req_ringguards inalloc_extend,alloc_decode,available_size,swa_available_size) but not the two freesites.
Triggering it requires
page_size > 1and the ring simultaneously, which isexactly DSV4 on ROCm -- the backend forces
page_sizeto 256 and the ring comesfrom the model's
sliding_window. No CI lane covers that intersection, and thereland landed the day after the free path changed underneath it.
Modifications
Skip the page return when the ring is active, at both free sites in
SWATokenToKVPoolAllocator:_free_swa_pages: return early in ring mode, afterclear_full_to_swa_mapping. The mapping clear must still run -- skipping itleaves stale peer indices for
translate_loc_from_full_to_swa, which wouldturn a clean crash into wrong KV reads.
free_group_end: guard the grouped drain withnot self._swa_req_ring. Thelist is still drained; only the page return is skipped.
test/registered/unit/mem_cache/test_swa_ring_page_return.py(base-a-test-cpu,no GPU) covers both free paths in ring and non-ring mode. The two ring cases
fail on the pre-fix code with the same
swa.pyassert seen on the GPU and passon the fix; the non-ring cases guard against disabling the page return outright,
and one case pins the mapping clear that must still run in ring mode.
+7 -1 in
swa.py, no API or behavior change off the ring path.Accuracy Tests
DeepSeek-V4-Flash, 2x MI45x (gfx1250),
--tp 2 --attention-backend dsv4 --page-size 256 --kv-cache-dtype fp8_e4m3, ROCm 10.1.Booting is not sufficient evidence here: the warmup request is 19 tokens and
never wraps the 256-token ring. Two checks that do exercise it, both against a
reference build that predates #38159/#38192 (
v0.5.19.dev20260906+g5bebe7a033):temperature=0), including two of 614 and 620 tokens thatforce the ring to wrap:
output_idsbit-identical to the reference on all5. Greedy decoding is deterministic, so this is an exact-match oracle on the
KV path, not a smoke test.
256 slots: 32/32 correct answers, zero asserts or exceptions in the log.
End-to-end on the fixed build, GSM8K full set (1319 questions, 5-shot,
temperature=0,--parallel 128):Unit coverage reproduces the failure without hardware: the two ring-mode cases
in
test_swa_ring_page_return.pyfail on the pre-fix code at the sameswa.py:559assert seen on the GPU, and pass on the fix (5 passed, 7 s, CPU).Without the fix the same config cannot serve a single request.
Speed Tests and Profiling
No performance work; the change removes calls on the ring path and touches
nothing else. Worth noting the fix restores the intended sizing rather than
degrading it --
max_total_num_tokensis 44,482,560 on the fixed build vs28,960,000 on the pre-#38192 build used as the accuracy reference (+54%), since
falling back to that build also gives up the unified-KV pool sizing.
Serving throughput on the fixed build, from the GSM8K run above: 265.407 token/s
output at
--parallel 128.Checklist
cc @hnyls2002 @ispobock @alphabetc1 (allocator CODEOWNERS), @yuttian1 (#38192)
CI States
Latest PR Test (Base): ❌ Run #34306364007
Latest PR Test (Extra): ❌ Run #34306363799
Latest PR Test (AMD ROCm 7.2): ❌ Run #34306363963