Repository navigation
[DSA] k-pool: 256-token logical page so index page id = logical page id - #42178
Conversation
For DSA with index_kpool > 1 (GLM-5.3-Flash), the allocator / radix tree page is now 64 * index_kpool (256) while KV kernels keep 64-token physical pages. Pooled key j of a request lives at index slot loc(j * index_kpool) // index_kpool, so index page id == logical page id. Prefix-cache hits are therefore whole pooled-key groups, and a request never writes pooled keys into a page owned by another request's cached prefix (sgl-project#41494). The index pool shrinks to one pooled key per index_kpool tokens, freeing KV capacity. DCP with index_kpool > 1 is rejected at startup. All changes are no-ops for index_kpool == 1. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
|
ref: #40134 |
Co-authored-by: Cursor <cursoragent@cursor.com>
|
Comparison with #40134: #40134 (comment) |
|
/rerun-test test/registered/e2e/models/test_glm53_flash_h200.py |
|
🚀 |
| overrides["page_size"] = 64 | ||
| logger.warning("Setting page size to 64 for DeepSeek DSA.") | ||
| index_kpool = get_dsa_index_kpool(hf_config) | ||
| if index_kpool > 1 and getattr(cfg, "dcp_size", 1) > 1: |
There was a problem hiding this comment.
dcp_size is a declared server arg, use cfg.dcp_size directly instead of getattr.
| f"index_kpool={index_kpool}: a pooled index key spans " | ||
| "positions owned by different DCP ranks." | ||
| ) | ||
| page_size = 64 * index_kpool |
There was a problem hiding this comment.
add a comment on why * index_kpool: a logical page has to own a whole pooled index group, so a prefix hit never ends inside one.
| "positions owned by different DCP ranks." | ||
| ) | ||
| page_size = 64 * index_kpool | ||
| overrides["page_size"] = page_size |
There was a problem hiding this comment.
validate_mamba_extra_buffer checks mamba_track_interval % page_size before this override sets page_size, so --mamba-track-interval 64 or 128 now passes startup. maybe check it here next to the dcp check.
| assert self.page_size % index_kpool == 0, ( | ||
| f"page_size {self.page_size} must be a multiple of index_kpool {index_kpool}" | ||
| ) | ||
| self.slots_per_page = self.page_size // index_kpool |
There was a problem hiding this comment.
nit: slots_per_page does not say which buffer or unit. maybe index_page_size (pooled keys per index page). fine as a follow-up rename.
| 0, page_table_64.shape[-1], pool_size, device=page_table_64.device | ||
| ) | ||
| return page_table_64[..., idx] | ||
| return page_table_64[..., idx] // pool_size |
There was a problem hiding this comment.
nit: pool_size reads like a memory pool size, it is the k-pool ratio. maybe kpool, same follow-up.
| ) | ||
| self.dsa_index_topk = get_dsa_index_topk(hf_config) | ||
| self.dsa_index_kpool = get_dsa_index_kpool(hf_config) | ||
| self.real_page_size = model_runner.page_size // self.dsa_index_kpool |
There was a problem hiding this comment.
nit: maybe physical_page_size instead of real_page_size, matching the local in DSATokenToKVPool.__init__ and the physical/virtual page naming DCP already uses. same follow-up.
|
Verified on GLM-5.3-Flash. A prompt is cached up to the middle of a 256-token group, then two requests extend it with different suffixes. The scored continuation copies a span from the first request's suffix, so it depends on the index keys the second request overwrites on main. HiCache host restore of the indexer on hybrid models is still missing, as noted in the known issues. With #40915 on top of this PR: |
|
/rerun-failed-ci |
Since sgl-project#42178, slots_per_page = page_size // index_kpool, but the sharded pool translates indexer locations in token units. With index_kpool > 1 that would address the wrong rows, so fail at construction instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Motivation
Fixes #41494: GLM-5.3-Flash (DSA with
index_kpool=4) loses the NIAH needle at 16K when prefixes are shared. Supersedes #41645.A pooled index key covers 4 consecutive tokens, i.e. 4 physical KV pages of 64. Before this PR the radix tree page was 64, so a prefix hit could end inside a pooled-key group. The resuming request then wrote its pooled keys into the group-head page owned by another request's cached prefix, and later readers of that prefix saw the wrong keys.
Modifications
index_kpool(256) for the allocator, radix tree, PD and HiCache. KV kernels and the DSA backend'sreal_page_sizekeep 64-token physical pages. Logical pageLowns physical pages4L .. 4L+3.slots_per_page). Pooled keyjof a request lives at index slotreq_to_token[r, 4j] // 4, and the index page table ispage_table_64[:, ::4] // 4. Every group-head lookup in the k-pool kernels and planners now divides byPOOL_SIZE.index_kpooltokens, which frees about 9% more KV capacity on H200 (see below). The HiCache host declaration is sized the same way.--dcp-size > 1withindex_kpool > 1is rejected at startup, because a pooled key spans positions owned by different DCP ranks.index_kpool == 1. DeepSeek-V4's pool falls back topage_size(_index_page_slots).Accuracy Tests
H200×8, TP8, tilelang DSA, bf16 KV, same flags for both builds.
max_total_num_tokenstest_glm53_flash_h200.pyLowLatency (EAGLE) GSM8K, repeated runsThe GSM8K difference is within run-to-run noise (one run's std ≈ 0.011);
mainalso dips below the 0.93 threshold.Unit tests: the new
test_kpool_index_addressing.pyfails 3/3 onmainand passes here. The existing k-pool kernel and unit tests pass with the updated page geometry.Known issues, also present on
main_MambaStrategynever assembles theindexerhost pool and logsHybridLinearKVPool declares host pools ['indexer'] that _MambaStrategy did not assemble. NIAH after a cache flush loses needles on host-restored prefixes. This PR does not change that path.copy_done.synchronize). Intermittent on bothmainand this PR, about 1 in 3 shuffled-NIAH runs with EAGLE, and also seen once without EAGLE onmain; not tied to a specific sample or length.Checklist
CI States
Latest PR Test (Base): 🚫 Run #37269693427
Latest PR Test (Extra): ⏳ Run #37289097492
Latest PR Test (AMD ROCm 10): ❌ Run #37269693516