Skip to content

fix(sglang): split --hicache-size between host pools as native SGLang does - #600

Merged
shipiyouniao merged 3 commits into
mainfrom
fix/sglang-hicache-host-split
Oct 27, 2026
Merged

shipiyouniao merged 3 commits into
mainfrom
fix/sglang-hicache-host-split

Conversation

@RixinLiu

@RixinLiu RixinLiu commented Oct 25, 2026 •

Copy link
Copy Markdown
Collaborator

#599 is merged; this PR is based on main, including the direct-backend index-device fix.

Problem

SGLang splits --hicache-size between a hybrid model's device pools in proportion to get_kv_size_bytes(). kvcached's elastic MHA pools report the size of their buffers, which span the whole virtual reservation (about a GPU per pool), while Mamba pools report their slots. The MHA host pool therefore takes almost all of the host memory.

On an A100 with SGLang 0.5.20, Qwen3.5-4B, --enable-hierarchical-cache --hicache-size 14:

Allocating kv hierarchical KV host pool: 362063 tokens, 11.86 GB host memory.
HiCache host KV pool (42 tokens) is smaller than the device pool (147 tokens);L2 cache effectiveness is reduced.
Allocating 2.16 GB host memory for hierarchical Mamba cache (layout=page_first).

Native SGLang gives the same model a 7.40 GB MHA host pool and 129 Mamba slots. With sync scheduling (--disable-overlap-schedule), 16 prompts sent twice did not hit the host cache once on the second pass. A separate run that sent only 4 prompts, with everything else the same, hit 4096 tokens for each prompt on the second pass, so the 42 Mamba slots are too few for 16 prompts.

Change

The split counts an elastic pool's logical capacity: its reported bytes scaled by its token capacity over its buffers' length. Native pools and Mamba pools are counted as before, and get_kv_size_bytes() itself is unchanged for its other callers.

Validation

  • At 0288c809, tests/test_sglang_hicache.py has 24 cases, including MHA/MLA capacity bounds, zero-layer pools, budget conservation and native fallback. The CPU suite passes on Python 3.10–3.13: 1588 passed, 15 skipped per version. Pre-commit, mypy for all four versions, and GPU-free C++ tests pass. These are constructed regressions; no new hybrid serving run was performed.
  • Original A100 validation, with the same setup as fix(sglang): make HiCache copy MHA, MLA and Mamba states correctly with kvcached #599 (KVCACHED_MAX_CACHED_TOKENS=0, 16 prompts of 4.5k-7.5k tokens sent twice):
Case strided copies alone this change
MHA / Mamba host pools 11.86 GB / 42 slots 7.41 GB / 129 slots (native: 7.40 GB / 129)
second-pass host hits, sync scheduling 0 of 16 16 of 16, 4096 tokens each*
second pass vs first pass, sync scheduling - identical
async scheduling, vs native identical identical
  • 4096 tokens is where the first prefill chunk ends (--chunked-prefill-size 4096); every prompt is longer. Native SGLang with sync scheduling also hits exactly 4096 tokens per prompt.

@RixinLiu

Copy link
Copy Markdown
Collaborator Author

/ci run

@github-actions

Copy link
Copy Markdown

GPU CI (basic) on 939da65, requested by @RixinLiu: run

@github-actions

Copy link
Copy Markdown

Basic correctness · 2026-10-26 · DID NOT RUN

Run · kvcached 939da65 (merged into main at 3170a0a)

The tests did not finish; the run log has the details.

@RixinLiu
RixinLiu requested a review from shipiyouniao October 26, 2026 01:48
kvcached's default layout interleaves all layers (and, for MHA, K with
V) in one buffer, so a layer's MHA or MLA view has a token stride that
spans every layer, and a Mamba state view has a slot stride that spans
every layer's states. SGLang HiCache backs MHA and MLA pools up through
per-layer pointer tables that assume dense rows, and copies Mamba states
assuming dense slots. The elastic MHA pool leaves the pointer tables
unset, so the first backup fails; the elastic MLA pool publishes them,
so MLA backups copy rows of other layers and tokens without any error.

Seen on SGLang 0.5.20 with --enable-hierarchical-cache on an A100:
Qwen2.5-7B and Qwen3.5-4B stop at the first prefill with

    File ".../pool_host/mha.py", line 393, in _resolve_device_transfer_buffers
      device_pool.k_data_ptrs,
  AttributeError: 'ElasticMHATokenToKVPool' object has no attribute 'k_data_ptrs'

and DeepSeek-V2-Lite serves all 30 prompts, but every one that is loaded
back from the host diverges from native SGLang at its first token, with a
token outside native's top 5. With KVCACHED_CONTIGUOUS_LAYOUT=false,
where each Mamba layer has a buffer of its own, Qwen3.5 stops at the
first copy of a Mamba state to the host with

  RuntimeError: ... transfer_mamba: item_size must be 16-byte aligned (uint4)

The host pools now copy through SGLang's one-layer JIT HiCache kernel,
which reads each view's own stride: MHA and MLA backups one layer at a
time (their loads already used that kernel), and Mamba states in both
directions. The kernel copies fixed-size rows and is compiled for each
row size, so a Mamba state is copied as rows of at most 4 KiB of
kvcached's flat state buffer rather than as one megabyte-sized row.
Host layouts, MTP draft pools, layer sharding and decode context
parallelism that these copies do not cover are refused at startup. MHA
and MLA pools with dense views (KVCACHED_CONTIGUOUS_LAYOUT=false) keep
SGLang's copies. The Mamba host pool sizes itself from one tensor of all
layers, so with per-layer buffers it is shown the layers stacked, and the
states are copied one layer at a time.
SGLang splits --hicache-size between a hybrid model's device pools in
proportion to get_kv_size_bytes(). kvcached's elastic MHA pools report
the size of their buffers, which span the whole virtual reservation,
about a GPU per pool, so the MHA host pool takes almost all of the host
memory.

Seen on SGLang 0.5.20 with Qwen3.5-4B, --enable-hierarchical-cache and
--hicache-size 14 (with the strided HiCache copies applied): the Mamba
host pool gets 42 slots (2.16 GB), where native SGLang gives it 129.
With sync scheduling (--disable-overlap-schedule) each prompt keeps a
Mamba state per prefill chunk, and 16 prompts sent twice did not hit the
host cache once on the second pass.

The split now counts an elastic pool's logical capacity: its reported
bytes scaled by its token capacity over its buffers' length. Native pools
and Mamba pools are counted as before. The same 16 prompts then hit 4096
tokens each, the prefill chunk boundary that native SGLang also hits in
this mode.
@RixinLiu
RixinLiu force-pushed the fix/sglang-hicache-strided branch from b9aca45 to ceadcb0 Compare October 26, 2026 02:58
@RixinLiu
RixinLiu force-pushed the fix/sglang-hicache-host-split branch from 939da65 to 51950c2 Compare October 26, 2026 03:04
@RixinLiu RixinLiu changed the title fix(sglang): split HiCache host memory by logical pool size fix(sglang): split --hicache-size between host pools as native SGLang does Oct 26, 2026

@rishabhsinha17 rishabhsinha17 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. the split counted the virtual reservation, which starved the Mamba host pool, and the fix counts the capacity SGLang sized its tree with.

i verified which pool the split sees and the number it gets. build_hybrid_mamba_stack hands kvcache.full_kv_pool itself to _split_hicache_size (hybrid_pool_assembler.py:1485). scaling by (size + page_size) over k_buffer[0].shape[0] equals get_kv_size_bytes_phy() in both layouts, caps at the native total for a request larger than the reservation, returns 0 for a zero-layer pool with no division, and leaves Mamba pools alone. pool.size is only set in init, so a shrink does not move the split.

51950c2 is the same delta as 939da65 rebased onto ceadcb0. the basic gpu run on 939da65 ended in a cancel 28 seconds in, right after the image pulls and before any test ran, so the new head still needs a /ci run.

cpu manifest at 51950c2 1518 passed, failure set identical to main. 11 tests in the file pass. lint and mypy clean.

@shipiyouniao shipiyouniao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM for the sizing change. Using logical pool capacity rather than the virtual reservation matches the native host-budget calculation, while native and Mamba pools keep their existing accounting. The direct-backend issue in #599 still needs fixing in the base.

- Preserve logical pool sizing on the latest HiCache copy fixes.
- Cover pool capacity bounds, budget conservation and native fallback.
@shipiyouniao
shipiyouniao changed the base branch from fix/sglang-hicache-strided to main October 27, 2026 11:46

@shipiyouniao shipiyouniao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM at 0288c80. The baseline sync preserves the sizing change; current-head CPU, lint/type checks and private Codex review pass.

@shipiyouniao
shipiyouniao merged commit fcadb7b into main Oct 27, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants