Skip to content

feat(ds4): ondisk prefix cache export/adopt; fix continued checkpoint keying - #716

Merged
davide221 merged 4 commits into
mainfrom
ds4/disk-prefix-cache
Sep 8, 2026
Merged

davide221 merged 4 commits into
mainfrom
ds4/disk-prefix-cache

Conversation

@davide221

@davide221 davide221 commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor

Summary

DeepSeek V4 snapshots were memory-only: the backend left snapshot_ref / snapshot_adopt at their defaults, so DiskPrefixCache detected a memory-only backend and disabled itself for this model (the gating added in #579). This PR implements the two hooks for DeepSeek V4 on the same shared disk cache used by Qwen3 / Qwen3.5 / Laguna, and fixes a keying inconsistency in the shared continued-checkpoint path.

DeepSeek V4 ondisk prefix cache (server/src/deepseek4)

  • Every snapshot tensor gets a stable name (ds4_snap_*), so the disk cache can serialize the context and rebind it after a reload.
  • The host-side decode state (last logits, DSpark feature tail) and the per-layer compressed row counts travel as sidecar tensors in the same context. Variable lengths live in ne[1], which the layout fingerprint normalizes, so snapshots of different lengths share one layout id.
  • deepseek4_snapshot_bind() rebinds a deserialized context by name, with an optional per-shard prefix and shared-storage ownership.
  • DeepSeek4Backend::snapshot_ref / snapshot_adopt: adopt validates against the compression schedule of the loaded weights (not the live cache), so a valid file is never dropped as corrupt while the target is parked. Paged concurrent serving stays memory-only, as in the Qwen3.5 backend.
  • DeepSeek4LayerSplitAdapter: shard snapshots are merged into one named context (ls<i>_ prefix plus adapter-level meta / HC state / prefill logits), mirroring the Qwen3.5 adapter. Mixed remote-shard splits stay memory-only.

Continued checkpoint keying (server/src/server/disk_prefix_cache.cpp)

A disk entry is keyed by the hash of its prompt prefix and a hit restores the whole snapshot. maybe_store_continued() keyed files by the interval-aligned prefix while the snapshot extended to the generation's final position, so a later prompt sharing only the key prefix could restore a snapshot covering tokens it never contained. Now:

  • continued checkpoints are keyed by the full token prefix the snapshot covers; the interval only paces them;
  • save() refuses a snapshot whose position exceeds its key, and read_file() rejects such files so existing ones are dropped on lookup.

Validation

Unit (lucebox5, HIP gfx1151;gfx1201 build on top of #598):

  • test_deepseek4_unit: all cases pass, including new test_monolithic_snapshot_disk_roundtrip (real DiskPrefixCache save → lookup → adopt → restore → exact-hit decode, layout stability across lengths, rejection of a bogus context) and test_layer_split_snapshot_disk_roundtrip.
  • test_server_unit: 453 passed, 0 failed, including new test_disk_cache_rejects_snapshot_past_key and test_disk_cache_continued_keys_full_prefix.

End to end (lucebox5, DeepSeek-V4-Flash-0731 ROCmFPX on R9700 + Strix, long-context profile, greedy, two-turn conversations of ~950 tokens, --kv-cache-dir set, prefix cache slots enabled):

  • Files: 29.7 MB per 930-position snapshot; layout id identical across restarts.
  • After a server restart, a new last turn on the cached conversation hits the disk entry (--disk-prefix-cache auto), is adopted, and prefills only the new turn: 17 to 28 tokens in 0.4 to 1.4 s (including the file read) versus 7 to 9 s for a cold prefill of ~950 tokens.
  • Disk-restored outputs matched cold outputs in every cross-process comparison run (3/3, plus one repeat on the final build).

Notes for reviewers

  • --disk-prefix-cache full keys the exact full prompt, so a conversation with a new last turn misses; cross-session reuse needs auto (per request via prefix_cache.scope, or as the server default).
  • DeepSeek chat markers produce a reusable boundary only after a completed assistant turn (BOS / EOS / User / Assistant markers), so single-turn prompts do not snapshot.
  • Not covered: mixed remote-shard target split (memory-only), PFlash-compressed disk snapshots (DeepSeek V4 has no PFlash yet).

Review in cubic

DeepSeek V4 snapshots were memory-only: the backend left the ModelBackend
snapshot_ref/snapshot_adopt hooks at their defaults, so DiskPrefixCache
detected a memory-only backend and disabled itself for this model.

- Name every snapshot tensor (ds4_snap_*) so the disk cache can serialize
  the context and rebind it after a reload.
- Store the host-side decode state (last logits, DSpark feature tail) and
  the per-layer compressed row counts as sidecar tensors in the same
  context. Variable lengths live in ne[1], which the layout fingerprint
  normalizes, so snapshots of different lengths share one layout.
- Add deepseek4_snapshot_bind() to rebind a deserialized context by name,
  with optional per-shard prefix and shared-storage ownership.
- Implement snapshot_ref/snapshot_adopt on DeepSeek4Backend. Adopt
  validates against the compression schedule of the loaded weights, so a
  valid file is never dropped as corrupt while the target is parked.
  Paged concurrent serving stays memory-only.
- Implement the same hooks on DeepSeek4LayerSplitAdapter by merging the
  shard snapshots into one named context; mixed remote-shard splits stay
  memory-only.
- Unit tests: real DiskPrefixCache save/lookup/adopt/restore round trip
  with exact-hit decode and layout stability across lengths (monolithic),
  and a merged-context round trip for the layer-split adapter.
A disk entry is keyed by the hash of its prompt prefix and a hit restores
the whole snapshot. maybe_store_continued() keyed files by the
interval-aligned prefix while the snapshot extended to the generation's
final position, so a later prompt sharing only the key prefix could
restore a snapshot covering tokens it never contained.

- Key continued checkpoints by the full token prefix the snapshot covers;
  the interval only paces them.
- Refuse to write a file whose snapshot position exceeds its key, and
  reject such files on read so existing ones are dropped.
- Tests cover both guards and the new keying; the identity-salt tests now
  use a key that covers the mock snapshot.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 9 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread server/src/deepseek4/deepseek4_layer_split_adapter.h Outdated
Comment thread server/src/deepseek4/deepseek4_internal.h Outdated
Comment thread server/src/deepseek4/deepseek4_layer_split_adapter.cpp Outdated
Comment thread server/test/test_server_unit.cpp
Comment thread server/tests/test_deepseek4_unit.cpp Outdated
…ss review

Move the DeepSeek V4 snapshot code out of deepseek4_graph.cpp into
deepseek4_snapshot.{h,cpp} with a small API: declare (tensors only, into a
caller-owned context), fill, save (declare + allocate + fill), restore, bind
and validate. A DeepSeek4LayerGeometry derived from the weights is the single
source of truth for cache allocation, snapshot declaration and validation.

- Adopt validates every tensor's type and shape against the weights'
  geometry (and capacity when the live cache exists), so a malformed or
  foreign file is rejected on lookup and removed instead of failing every
  later restore.
- Layer-split adapter: one merged host context per slot holds the shard
  snapshots (aliased, owns_storage=false) plus the adapter-level tensors,
  and is what the disk cache serializes. No second copy per slot, no
  rebuild step, no reader backend to hand over.
- DiskPrefixCache::read_file() allocates from the CPU buffer type instead of
  a CPU backend object, removing the per-lookup backend leak for all
  backends.
- Tests: continued checkpoints now cover crossing into a new interval
  bucket; DeepSeek tests use std::filesystem instead of a shell, exercise
  geometry rejection on both paths, and assert the split snapshot aliases
  the merged context.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 13 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread server/src/deepseek4/deepseek4_layer_split_adapter.cpp
Comment thread server/src/deepseek4/deepseek4_snapshot.cpp
@davide221
davide221 merged commit 561a7e9 into main Sep 8, 2026
7 of 9 checks passed
@davide221
davide221 deleted the ds4/disk-prefix-cache branch September 18, 2026 15:50
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.

1 participant