[KV Offload] Deduplicate replicated MLA KV in the shared CPU region - #48906
Conversation
|
Thanks for taking this on. We independently built and qualified the same rank-0-store / all-rank-load ownership model on two independent TP=2 hosts, each with 2x RTX PRO 6000 Blackwell GPUs, using a downstream compact CPU representation for a hybrid DeepSeek V4 layout. This is not a test of this PR's exact commit; it is production evidence for the architecture and for a likely follow-on generalization beyond the pure-MLA MVP. Real TP payload identityWe checksummed logical CPU payload bytes after GPU→CPU CUDA completion and before transfer-resource recycling. Across one real TP=2 request, both ranks emitted 126 exactly comparable offload keys spanning all five model-produced KV groups:
The hashes covered logical payload bytes only and excluded page padding. Every observed full-history MLA, sliding-window/tail, compressor-state, and indexer payload was byte-identical across TP ranks. That supports this PR's conservative pure-MLA MVP and suggests the eventual canonical-mapping consumer can generalize replica reduction per layer/group once #48408 supplies authoritative mappings. End-to-end ownership proofOur downstream implementation kept scheduler job dispatch/completion accounting unchanged:
On the final immutable image, a deterministic 52,173-token semantic test produced two byte-identical cold answers. After GPU-only reset, replay restored 51,968 source-attributed external tokens with zero local GPU-cache hits, 206 recomputed tokens, 431,169,920 aggregate H2D bytes across both ranks, and exactly identical six-answer output in 0.872 s. The cold store wrote 216,407,232 bytes—one physical TP copy rather than two. We also exercised two active ~171K-token requests while repeatedly attempting cache reset. Resets were refused while transfers were live, then succeeded after drain; a fresh post-reset request completed with the same process and no stale transfer-job references. Shared-region lifecycle findingWe hit one operational failure worth carrying into review: after an ungraceful EngineCore exit, the shared mmap remained and blocked the next startup. A generic hint to The durable downstream fix uses an owner-scoped That lifecycle work is separable from replica dedup, but I strongly recommend replacing wildcard/manual cleanup before treating the shared primary as unattended-production safe. The capacity result matched the model: canonical TP1 storage nearly halved resident bytes and D2H write traffic versus the replicated TP2 layout. Our measured compact density projected about 16.7M unique tokens per 64 GiB shared CPU region versus 8.35M with two private copies. Happy to share focused receipt/recovery tests or review a follow-on against #48408. I am deliberately not opening a competing PR because #48906 owns this path. |
|
This pull request has merge conflicts that must be resolved before it can be |
|
@Change72 — thank you for carrying the shared single-copy CPU-primary design forward and for tightening the stale-region cleanup after our production note. I want to make one important boundary in our earlier evidence explicit, because our approximately 40× end-to-end result has two separate components and your current work covers one of them:
Combined, compact storage and TP=2 replica deduplication reduced measured server-wide density to approximately 4.1 KiB per unique logical token—roughly 38×, reasonably summarized as about 40×, versus the original replicated packed layout. This distinction matters because the approximately 19× component is not a general claim for ordinary uniform transformer KV caches. It comes from DeepSeek V4’s heterogeneous hybrid layout: full-history MLA, sliding-window groups, MTP/EAGLE state, compressor state, and indexer state with very different physical payload shapes forced into one coarse representation. The generic fixed-page mechanism may benefit future heterogeneous models, but the measured amplification is specific to this geometry. Production results from the complete design:
The compact implementation also replaces contiguous variable-size extents with fixed 64 KiB pages. That removed the fatal spatial-fragmentation behavior we observed when long histories filled the CPU tier. Compact scatter transfers use per-copy asynchronous CUDA operations because the batched-copy API reproducibly crashed both TP workers above roughly 32,000 descriptors. Our final compact/shared implementation currently lives in a downstream branch based on the pre-current-main scheduler lineage, so its commit hashes are not yet a useful public handoff. I do not want to dump an opaque downstream patch onto your work or open a competing implementation. If you’re interested, we can publish a clean source branch or patch series, write a precise old-to-current scheduler mapping, and collaborate on porting the compact per-group page storage into the CPU-primary architecture you already own. We can carry the DeepSeek V4 TP=2 and TP=4 hardware qualification and provide the fragmentation, transfer-descriptor, lifecycle, reset, and semantic-replay regressions. The clean ownership split seems to be:
Would that direction be useful to you? If so, we’ll prepare the public branch and implementation map rather than making you reconstruct it from scattered benchmark comments. |
|
This pull request has merge conflicts that must be resolved before it can be |
e8b7410 to
86ad4af
Compare
|
Thanks @coltonottley. The 2x vs. 19x split is clear. Replica dedup is general; the 19x result is specific to the V4 heterogeneous layout. Please go ahead. I suggest starting with a short RFC or a comment on #38260 covering the fixed-page pool, fail-closed fallback, and scheduler mapping. Please cc @orozery and include a clean reference branch. The implementation should build on #48408 and fall back to the current layout when those mappings are unavailable. Please also file the crash above 32K copy descriptors as a separate bug. My next work is FS/P2P reuse of the shared single-copy rows, as scoped on #47929 , so it should not overlap with per-group compaction. |
|
@orozery One design choice I’d like your input on: The existing size check cannot detect different layouts with the same total size. For example:
I added one JSON sidecar per engine in Would you keep this validation, or avoid the extra file? |
Co-authored-by: OpenAI Codex <noreply@openai.com> Signed-off-by: Change72 <changg@nvidia.com>
Co-authored-by: OpenAI Codex <noreply@openai.com> Signed-off-by: Change72 <changg@nvidia.com>
|
Hi @Change72, the pre-commit checks have failed. Please run: uv pip install pre-commit>=4.5.1
pre-commit install
pre-commit run --all-filesThen, commit the changes and push to your branch. For future commits, |
Co-authored-by: OpenAI Codex <noreply@openai.com> Signed-off-by: Change72 <changg@nvidia.com>
|
@Change72 — the requested package is published:
The reference branch inherits your writer gate and shared-primary ownership directly; it does not duplicate them. It consumes #48408 mappings, permanently falls back to the existing CPU representation when compact geometry cannot be certified, and keeps secondary-tier/P2P orchestration out of scope. The full reference is intentionally larger than a comfortable single PR. The cover note maps it into three owner-aligned review units: fixed-page manager, mapping/scatter consumer, and scheduler admission/lifecycle. The exact immutable image passed full TP=2 production acceptance and fresh TP=4 qualification; refreshed-source validation and human approval are complete. |
The replicated_layout gate introduced in vllm-project#48906 required exactly one KV cache group, so all-MLA models that end up with several groups (such as DeepSeek V3.2) kept one host copy per TP rank even though every rank holds the same latent KV. Accept a cache whose groups are all MLA, unwrapping UniformTypeKVCacheSpecs and checking each layer with an exact type so wrappers and sliding-window variants still fail closed. Page accounting now sums one MLA page per layer across groups, and UniformTypeKVCacheSpecs is not multiplied by its layer count since its page_size_bytes already covers them. This is the whole-row single copy relaxation discussed in vllm-project#48414, with no new layout machinery, and is subsumed by the canonical layout once that lands. Signed-off-by: almogtavor <almogtavor@gmail.com>
Purpose
This implements the first shared CPU-region replica-reduction path from #47929.
For pure MLA tensor parallelism, each TP rank holds a replica of the latent KV payload. This duplicate host-copy path is present in both V1 and V2: both runners hand per-rank canonical MLA tensors to the same offloading connector, which previously reserved and stored one host slot per rank. The runners organize their GPU tensors differently, but replica ownership is the same.
This PR:
replicated_layoutdecision in the offloading connector fromKVCacheSpecand the parallel configuration;At TP=N, this lets the configured CPU capacity hold approximately N times as many replicated MLA blocks and reduces D2H store traffic by a factor of N. Scheduler dispatch and completion accounting remain unchanged.
Scope
The replicated layout is enabled only for:
MLAAttentionSpecgroup with matching per-layer page accounting;PP = PCP = DCP = 1andworld_size == TP; andmpexecutor.Both V1 and V2 model runners are supported under this gate. Mixed or wrapped cache layouts, sliding-window MLA, multi-node execution, and other parallel-axis combinations fail closed to the existing per-rank layout.
The default
CPUOffloadingSpecremains unchanged because its buffers are process-private. This PR also does not add XPU shared-region pinning, change writer selection beyond rank 0, or change scheduler routing and completion accounting.Shared CPU layout
SharedOffloadRegionremains unchanged.TieringOffloadingSpecselects a one-copy row size and maps every replicated worker to slot 0. Normal layouts still use one slot per worker. The existing mmap create, open, size-wait, and cleanup behavior is unchanged, and no metadata sidecar is added.Compatibility notes
This PR changes replica ownership and sizing only in the shared CPU region. It does not make FS/OBJ/P2P ownership, naming, or cache identity parallel-agnostic. Persistent entries should be cleared when testing across the old TP-wide and new single-copy layouts. Secondary-storage identity and cleanup remain follow-up work.
V1 and V2 are supported independently under the same pure-MLA gate. This PR does not make persistent cache entries interchangeable between the two runners. #49440 addresses runner-specific persistent-cache namespacing separately.
Duplicate-work check
I checked #47929 and searched open PRs by issue number and MLA/offloading replication keywords. No open PR implements this shared CPU-region replica-reduction path.
#46954 was closed without merging in favor of #48408. That PR adds per-layer canonical KV page mappings for parallelism-agnostic offload. This PR instead needs an aggregate layout decision before canonical KV refs are registered, and the scheduler cannot see those refs. The changes are complementary; this PR does not depend on or modify #48408.
#49440 namespaces persistent cache entries by model runner. It does not deduplicate TP replicas or change shared-region ownership, so it is also separate from this PR.
Test plan
Workstation unit and GPU tests
TP=2 A100 model evaluation
The committed evaluation uses
TieringOffloadingSpec, TP=2, and the default V2 model runner. The V1 run used the same case withVLLM_USE_V2_MODEL_RUNNER=0.Test results
The branch was rebased onto upstream main at
6bcda970f.219 passed;32 passed;git diff --checkpassed.The forced-V1 model evaluation ran on 2x NVIDIA A100-SXM4-80GB and passed:
1 passed, 8 deselectedin398.14s.0.3600, invalid rate0.005, latency34.6s;0.3450, invalid rate0.005, latency25.2s;8,589,549,568-byte region with17189blocks.The default-V2 model evaluation also ran on 2x NVIDIA A100-SXM4-80GB. The logs confirmed
Using V2 Model Runner, and the probe-free PR tree passed:1 passed, 8 deselectedin379.99s.0.3250;0.3500;438,441,984D2H store bytes and876,883,968H2D load bytes, matching one shared store and one load per TP rank; and1b361a83278d97779bd65318aee1008c30480e50e5176c28244c1fd72a2dadac.The A100 model evaluations ran before the metadata sidecar was removed. The sidecar did not control replica sizing, store ownership, or loading. The latest head was validated by the workstation unit and GPU suites above after restoring the original mmap lifecycle.
Source inspection and these runner-specific A100 runs confirm that the TP replica-ownership problem exists in both V1 and V2 and that the single-copy shared-region path works for each runner independently.
AI assistance
AI assistance was used for design exploration, implementation, test creation, and adversarial review. The human submitter reviewed every changed line and owns the change.