Skip to content

[DCP] [HiCache] Support role-local DCP storage in prefill/decode serving - #41695

Draft
Venkat2811 wants to merge 26 commits into
sgl-project:mainfrom
Venkat2811:venkat/dcp-hicache-l3-3-pd
Draft

Venkat2811 wants to merge 26 commits into
sgl-project:mainfrom
Venkat2811:venkat/dcp-hicache-l3-3-pd

Conversation

@Venkat2811

@Venkat2811 Venkat2811 commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Stack 4/4. Depends on #41694. Targets main, so the full diff is cumulative. Incremental diff. Refreshed onto 4cca7fcd3e on 16 October; remains draft. The reviewed recurrent-file dependency was replayed locally; #41692 itself was not changed.

Motivation

Decode must honor the prefix it promised to prefill without replacing the live recurrent state received through P-to-D transfer. Enable role-local DCP L3 reuse while preserving that contract.

This work grew out of experiments with Kimi-K3 prefill/decode serving configurations.

Modifications

  • Restore canonical cached state separately from live request state; reuse main's capacity and lock ownership paths.
  • Reserve outstanding checkpoint slots and advertise only admitted prefetch coverage.
  • Require effective wait_complete on DCP decode and reject decode KV offload with DCP storage. Runtime attachment forwards the selected policy through both controller layers.

Live-state preservation and admission fixes also affect DCP1. Roles can have different layouts, but each reads compatible role-local objects. Persisted cross-layout conversion is unsupported. Losing promised data fails the request safely; it does not turn an incomplete restore into a hit. Broader timeout/liveness redesigns are outside this PR.

Accuracy Tests

Before = hybrid stage 39e4d07b34, which rejects DCP P/D storage. Final stage = ed850058f8. Kimi-Linear uses TP4/DCP1 prefill and TP4/DCP4 decode on one eight-GPU B300 node, with live RDMA transfer and separate role-local namespaces.

Scenario Before After
File L3, live P/D Rejected 22 requests pass: main cache-off control, role-local reuse, three concurrent restores, cancellation during I/O, lost promised state and recovery
Mooncake DRAM L3, live P/D Rejected 16 requests pass, including main control, fresh readers and missing-state fallback; both roles attach through the real HTTP API
Start with default timeout, attach using wait_complete Rejected incorrectly by this draft's old controller check Real controller regression is red before the fix and green after; HTTP attachment and inference pass
HiCache SSD offload via file L3 Rejected Physical pressure verified on the hybrid parent; not repeated as live P/D in this refresh
Mooncake SSD offload Rejected Disk-only readback verified on the hybrid parent; not repeated as live P/D in this refresh

File P/D ran before the final attach-policy fix; that fix changes runtime attachment, not its startup configuration. Mooncake P/D uses the final runtime code. Compared output IDs match the relevant control. The largest concurrent/retry logprob delta is 0.01291 (threshold 0.2); fresh-reader deltas against the matching warm control are zero. A fresh process can acquire a 64-token shared device prefix during successive requests, so P/D cache counters are not represented as universally zero L1.

25 CPU files plus two scheduler-attach tests pass with CUDA hidden. Four Python/Rust GPU restore tests pass, including preserving live state. Reservation and effective-policy regressions have failing-before/passing-after evidence. Pre-commit and docs checks pass. Hosted CI, B200, Kimi-K3, cross-node P/D, speculative L3 and live P/D SSD pressure were not rerun.

Run the committed regression tests

Use an x86 NVIDIA host. The GPU results below were obtained on B300, with driver 580.178.04. This container pins the tested dependency image. These are portable equivalents of the commands run in a Kubernetes pod, not a claim that this Docker wrapper was itself executed. Model tests download pinned public snapshots; allow approximately 100 GB for Kimi-Linear weights.

docker run --rm -it --gpus all --ipc=host --network=host \
  --cap-add IPC_LOCK lmsysorg/sglang@sha256:0a1ba33aa4e032b8c5bcebdc69c45664074c032074add4f7db01629ec1fcc381 bash

# Inside the container:
git clone --filter=blob:none https://github.com/Venkat2811/sglang.git /tmp/sglang-check
cd /tmp/sglang-check
git checkout --detach ed850058f8397ebd371856562dd9ed052110642d
python -m pip install parameterized==0.9.0
export PYTHONPATH="$PWD/python:$PWD"
CUDA_VISIBLE_DEVICES=999 python test/registered/unit/disaggregation/test_decode_queue_cleanup.py
CUDA_VISIBLE_DEVICES=999 python test/registered/unit/mem_cache/test_mamba_capacity.py
CUDA_VISIBLE_DEVICES=999 python test/registered/unit/mem_cache/test_hicache_dcp_storage_guards.py
CUDA_VISIBLE_DEVICES=999 python test/registered/unit/managers/test_scheduler_hicache_attach.py

# Actual H2D state copies, both tree cores, cancellation and live-state checks.
CUDA_VISIBLE_DEVICES=0 python test/registered/unit/mem_cache/test_hybrid_l3_restore.py

The live P/D campaign was a separate local integration harness, not a registered test in this diff. It used page size 64, BF16 weights, FP8 KV, context 4096, chunked prefill 1024, tokenspeed_mla, Triton linear decode, 10 GB HiCache, page_first, write_through, eager prefill and decode radix caching. The roles use separate file directories or Mooncake extra_backend_tag values. Decode needs --hicache-storage-prefetch-policy wait_complete. A working RDMA setup is required; the local run used DMA-BUF and explicit IB devices. The commands above validate the committed contracts, not that full live deployment or its fault injection.

Venkat2811 and others added 20 commits October 16, 2026 00:54
Read shared KV through a registered MLA replica before applying state eviction pressure. The existing survival check fails when touch loses its namespace guard: the replica deletes the shared KV file, causing avoidable cache misses rather than wrong data.

Clarify namespace ownership in reserve and document that topology changes can leave old state files outside eviction accounting.
Select complete logical pages when some objects already exist. Preserve upstream worker exception recovery and limit native shard validation to DCP. Share the fresh-process file restore fixture and update host-pool fixtures for current constructors.
…agreement

Keep sparse endpoint intersection in the existing prefetch collective and preserve deferred per-pool beliefs. Drop the obsolete chunked write-through implementation and its claim from this stage.
…nt state

Reuse upstream Mamba admission with reserved checkpoint slots, decline unregistered prefetch hits before advertising them, and keep incoming recurrent state separate from cache restoration. Require wait-complete prefetch and reject unqualified decode offload for DCP L3.
Use upstream restore locks for pending capacity and GPU assertions. Drop the obsolete pre-gate failure test already covered by upstream gated receiver tests.
Pass the selected policy through both controller layers instead of rechecking the startup default. Exercise real controller validation in the attach regression; timeout-to-wait_complete and retained-policy reattach fail before this change.
@Venkat2811
Venkat2811 force-pushed the venkat/dcp-hicache-l3-3-pd branch from 12efa25 to ed85005 Compare October 16, 2026 00:10
Venkat2811 added a commit to datacrunch-research/sglang that referenced this pull request Oct 19, 2026
… headroom // ranks (upstream sgl-project#38157)

Squash of the kimi-k3-v0.5.22 integration PR #14 (layer 0).

The kimi-k3 v0.5.22 prefill/decode line: v0.5.22 plus this one layer. It is 331f644 (the v0.5.21 kimi-k3
line's layer 5b, fork PR #13) rebased onto v0.5.22, with the two conflicts against v0.5.22's pool_host changes
resolved.

host_memory_budget_bytes() outside a budget scope returns free // ranks_per_host(), where free is the host headroom
left when the rank asks. On an 8-GPU pod the ranks arrive at different times, so a rank that checks after its peers
have pinned their pools divides a smaller headroom by 8 again and is refused although every pool fits (seen
2026-10-01 on Kimi-K3: "Requesting 24.77 GB but only have 22.74 GB free"). v0.5.22 still does this with an explicit
--hicache-size (hicache_auto_size.py opens no budget scope; pool_host/base.py and pool_host/mamba.py check against
free // ranks_per_host()). The guard now checks a pool against the whole remaining headroom; the two callers that
size a pool (HiCache auto-sizing, the unified page-envelope pool when --hicache-size and --hicache-ratio are 0) keep
this rank's share through host_memory_budget_bytes_per_rank(). The unit tests that pinned the split are updated.

Not carried from the v0.5.21 line, for the prefill/decode copy (prefill at DCP 1 with HiCache and Mooncake L3,
decode without HiCache): sgl-project#41692 to sgl-project#41695 (file L3 and DCP > 1 L3 only) and sgl-project#42039 (in v0.5.22 as sgl-project#42420 and
sgl-project#42788). The aggregated copy (Mooncake L3 at DCP 8) stays on verda/kimi-k3-2026-10-03.

Validated on HGX B300 with Kimi-K3 (19 Oct 2026, v0.5.22 image + this layer as a Python overlay): same-time A/B
against the v0.5.21 line on a 1 prefill + 2 decode copy (weka median 24 twice, AgentX 48), and a 2 prefill + 2
decode copy (AgentX 56 twice, 64): level or better, no cross-request leaks, no broken answers.

Upstream: sgl-project#38157
Issue: sgl-project#38156

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Venkat2811 added a commit to datacrunch-research/sglang that referenced this pull request Oct 19, 2026
…ing (upstream sgl-project#41695)

Squash of the kimi-k3-v0.5.22 integration PR #19 (layer 5), a mirror of the upstream pull request, rebased onto v0.5.22 (baa9bef).

Upstream: sgl-project#41695

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Venkat2811 added a commit to datacrunch-research/sglang that referenced this pull request Oct 27, 2026
…e slots (upstream sgl-project#41695)

Layer 12 of the Kimi-K3 nightly line on upstream main 5884e4f.

The prefill/decode part of upstream sgl-project#41695 that 5884e4f does not have. A
decode server with --disaggregation-decode-enable-radix-cache and HiCache
restores the cached prefix locally while prefill sends the rest:

- The transfer-side load-back handed the request to init_load_back, so for a
  hybrid (Mamba/KDA) model it also copied the host checkpoint at the prefix
  end into the request's own state slot. That slot receives the live state
  prefill computes for the whole prompt; whichever copy lands last wins.
  InitLoadBackParams.restore_request_state=False restores the canonical
  cache slot only.
- A hybrid restore needs one canonical state slot besides the request's live
  slot. Admission keeps a slot free for each queued restore
  (ensure_mamba_capacity(..., reserved_slots=)) and promises prefill only
  the device-resident prefix when no slot can be reclaimed; before, the
  promise went out and the restore failed after prefill had skipped those
  tokens.
- Under DCP, a decode server with an L3 backend requires
  --hicache-storage-prefetch-policy wait_complete (decode promises the probed
  L3 span before the read completes) and refuses
  --disaggregation-decode-enable-offload-kvcache, as sgl-project#41695 did. Checked at
  startup (resolve_hicache_dcp_compatibility) and on runtime attach,
  including the same-backend policy update (StorageAttachment.attach;
  HiCacheController.attach_storage_backend never sees the effective policy).

Source: 91ecb13 on the kimi-k3-nightly-3c560089 line, which equals
upstream sgl-project#41695's own commits over sgl-project#41694 (head ed85005). Dropped because
5884e4f has it: starting the L3 prefetch before preallocation and
promising only the span it took (sgl-project#43548). The sgl-project#41693/sgl-project#41694 ports that
91ecb13 sat on are superseded by sgl-project#41187 (no P/D refusal, no
decode-offload case), so the guards are re-expressed on upstream's structure
and the tests are rewritten on 5884e4f's fixtures. No change for a decode
server without the decode radix cache, which is every K3 recipe today.

Tests (CPU), this commit's test files on the parent: test_server_args.py
TestHiCacheArgs::test_dcp_decode_storage_requires_wait_complete_without_offload
3 subtests fail (NotImplementedError not raised for timeout, best_effort and
decode offload); test_hicache_dcp_host_pool.py TestDcpDecodeStorageAttach 6
subtests fail (attach accepted an early-stopping policy, attached or not);
test_mamba_capacity.py test_pd_restore_promise_keeps_a_canonical_state_slot
2 subtests fail (full and pending_restore promised 6 tokens, not 2). With
this commit all pass.
Tests (GPU, one B300): test_unified_radix_cache_load_back.py
Test_FULL_MAMBA_ps1::test_pd_decode_restore_keeps_the_live_request_state
fails on the parent (the live slot ends with the checkpoint's marker 11,
not the live 37) and passes with this commit.

Fork PR (v0.5.22 line): #19
Upstream: sgl-project#41695

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Venkat2811 added a commit to datacrunch-research/sglang that referenced this pull request Oct 27, 2026
…e slots (upstream sgl-project#41695)

Layer 12 of the Kimi-K3 nightly line on upstream main 5884e4f.

The prefill/decode part of upstream sgl-project#41695 that 5884e4f does not have. A
decode server with --disaggregation-decode-enable-radix-cache and HiCache
restores the cached prefix locally while prefill sends the rest:

- The transfer-side load-back handed the request to init_load_back, so for a
  hybrid (Mamba/KDA) model it also copied the host checkpoint at the prefix
  end into the request's own state slot. That slot receives the live state
  prefill computes for the whole prompt; whichever copy lands last wins.
  InitLoadBackParams.restore_request_state=False restores the canonical
  cache slot only.
- A hybrid restore needs one canonical state slot besides the request's live
  slot. Admission keeps a slot free for each queued restore
  (ensure_mamba_capacity(..., reserved_slots=)) and promises prefill only
  the device-resident prefix when no slot can be reclaimed; before, the
  promise went out and the restore failed after prefill had skipped those
  tokens.
- Under DCP, a decode server with an L3 backend requires
  --hicache-storage-prefetch-policy wait_complete (decode promises the probed
  L3 span before the read completes) and refuses
  --disaggregation-decode-enable-offload-kvcache, as sgl-project#41695 did. Checked at
  startup (resolve_hicache_dcp_compatibility) and on runtime attach,
  including the same-backend policy update (StorageAttachment.attach;
  HiCacheController.attach_storage_backend never sees the effective policy).

Source: 91ecb13 on the kimi-k3-nightly-3c560089 line, which equals
upstream sgl-project#41695's own commits over sgl-project#41694 (head ed85005). Dropped because
5884e4f has it: starting the L3 prefetch before preallocation and
promising only the span it took (sgl-project#43548). The sgl-project#41693/sgl-project#41694 ports that
91ecb13 sat on are superseded by sgl-project#41187 (no P/D refusal, no
decode-offload case), so the guards are re-expressed on upstream's structure
and the tests are rewritten on 5884e4f's fixtures. No change for a decode
server without the decode radix cache, which is every K3 recipe today.

Tests (CPU), this commit's test files on the parent: test_server_args.py
TestHiCacheArgs::test_dcp_decode_storage_requires_wait_complete_without_offload
3 subtests fail (NotImplementedError not raised for timeout, best_effort and
decode offload); test_hicache_dcp_host_pool.py TestDcpDecodeStorageAttach 6
subtests fail (attach accepted an early-stopping policy, attached or not);
test_mamba_capacity.py test_pd_restore_promise_keeps_a_canonical_state_slot
2 subtests fail (full and pending_restore promised 6 tokens, not 2). With
this commit all pass.
Tests (GPU, one B300): test_unified_radix_cache_load_back.py
Test_FULL_MAMBA_ps1::test_pd_decode_restore_keeps_the_live_request_state
fails on the parent (the live slot ends with the checkpoint's marker 11,
not the live 37) and passes with this commit.

Fork PR (v0.5.22 line): #19
Upstream: sgl-project#41695

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Venkat2811 added a commit to datacrunch-research/sglang that referenced this pull request Oct 30, 2026
…attach check

sgl-project#42973 made StorageAttachment ask page_interleave_shard_size() about
cache.token_to_kv_pool_allocator before attaching a storage backend. The
role-local DCP attach tests (ported with sgl-project#41695) build their cache as a
SimpleNamespace without that attribute, so 14 of them stopped at an
AttributeError. Give the fake a plain namespace there: a stock allocator,
shard size 1, which is what those tests mean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation hicache Hierarchical Caching for SGLang Multi-modal multi-modal language model npu unified-radix-cache

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants