You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
For MLA models, the latent KV cache is logically replicated across TP ranks β and expected to be byte-identical in homogeneous TP setups without context parallelism. MLA hardcodes num_kv_heads=1 (vllm/model_executor/layers/attention/mla_attention.py:385) and the latent projection is not TP-sharded (ReplicatedLinear, or disable_tp=True in the fused-projection paths). The codebase states this directly:
"MLA is always replicated as the hidden dim can't be split" β vllm/distributed/kv_transfer/kv_connector/utils.py:562
The native KV offloading path (OffloadingConnector + vllm/v1/kv_offload/) does not exploit this. In the pure-MLA, DCP=PCP=1 baseline configuration (refs against main @ 7bd154375):
Native CPU/tiering offload materializes one copy of the logically replicated latent KV per TP worker. CPUOffloadingSpec prices each logical block at per-worker bytes Γ world size (vllm/v1/kv_offload/cpu/spec.py:86), so at TP=N the unique-content capacity is roughly 1/N of the configured --kv-offloading-size budget.
Default CPUOffloadingSpec: each rank allocates its own private CPU buffer (pinned when supported) (vllm/v1/kv_offload/cpu/gpu_worker.py:501).
TieringOffloadingSpec: on single-node TP, the copies live in different per-worker slots of the same shared-mmap row (worker0_block0 | worker1_block0 | ..., vllm/v1/kv_offload/cpu/shared_offload_region.py:123); the FS secondary tier then writes the entire row as one layout-specific block file per key, so the duplicate copies sit inside that file as distinct worker slots (vllm/v1/kv_offload/tiering/fs/manager.py:159). These observations are verified for single-node TP; multi-node tiering β where both /dev/shm and the scheduler's memory view are node-local β is treated as unsupported/unverified here.
The fs tier's parallel-agnostic mode acknowledges the replication but excludes MLA from it: "MLA is excluded: its latent KV is replicated per rank, never head-sharded" (vllm/v1/kv_offload/file_mapper.py:85, from [KV offload] Parallel-agnostic fs-tier cache for single full-attention groupΒ #44733). Note that relaxing this predicate alone would not be enough β because the file payload is the full multi-worker row, files produced under different TP sizes differ in length and layout; a compact, TP-independent payload is a prerequisite.
Net effect: --kv-offloading-size 100 (GiB) with TP=8 on a pure-MLA model yields only ~12.5 GiB of unique cached KV. Neither the kv_offloading_size docstring ("When TP > 1, this is the total buffer size summed across all TP ranks", vllm/config/cache.py:182) nor docs/features/kv_offloading_usage.md mentions this caveat.
MLA is the best case for deduplication (replicated content, no reshard needed), yet it is the one case excluded from the existing single-copy machinery.
Scope and correctness notes
In general the duplication factor for pure-MLA is about TP / DCP, not always TP: DCP shards tokens across context-parallel ranks, but each DCP shard is still replicated across the TP ranks that share it. PCP is outer to TP, so TP-dedup can in principle be applied within each PCP namespace independently.
world_size also includes PP, and PP stages hold different layers β that dimension is real sharding, not replication, and must remain untouched by any dedup.
Proposed initial scope: pure-MLA models with PP=PCP=DCP=1 β a safe MVP boundary where the replica factor is exactly TP; this is an implementation simplification, not a theoretical requirement. Extensions (DCP-aware factor, per-PCP-namespace dedup, PP composition) can follow.
Hybrid MLA + full-attention models may place both kinds of layers in the same KV cache group, so gating purely at group level is insufficient; generalization needs per-layer/region (canonical tensor) granularity. The initial version would simply not engage for hybrid models.
Scheduler-side block accounting (num_cpu_blocks, medium/events) must stay consistent with the single-copy worker-side layout.
LMCache (LMCacheConnectorV1): for MLA, save_only_first_rank defaults on β non-leader ranks neither store nor read; the leader retrieves and performs a TP-group device broadcast (v0.5.1 cache_engine.py L113-117). Cache keys collapse world_size to 1 (TP-size-agnostic) since LMCache#2098 (first released in v0.3.10). A separate remote-only scheme, remote_enable_mla_worker_id_as0, serves as an alternative when save_only_first_rank is disabled (each rank reads the single rank-0 copy from remote storage directly). The built-in single-server LMCacheMPConnector fallback collapses only the TP dimension and suppresses MLA stores on ranks where rank % tp_size != 0 ("Tensor parallel does not change the KV caches for MLA models", vllm/distributed/kv_transfer/kv_connector/v1/lmcache_mp_connector.py); in a TP-only deployment this yields one stored copy while every TP rank retrieves directly. The external connector preferred by current vLLM has separate multi-server writer semantics.
(The NIXL connector's single-source MLA handling is a transfer optimization for replicated KV, not storage dedup β noted only to delimit scope.)
The native CPU/tiering layout currently lacks an equivalent MLA replica-reduction path.
Proposed direction (candidates β feedback requested)
Sizing and single-copy layout must land together. The per-block cost in cpu/spec.py should drop only the confirmed TP replica factor, leaving PP (different layers per stage) and any context-parallel sharding intact. Adjusting the sizing alone is unsafe: with per-worker private buffers still in place, simply growing num_cpu_blocks would multiply actual memory use by roughly world_size beyond the configured budget β the single-copy physical layout is a prerequisite for the sizing change.
Candidate A β single-node tiering shared-mmap single slot: store one worker slot per block row; the TP leader performs the d2h write, all node-local ranks read the same slot. Open questions to resolve: store-completion synchronization across ranks, pinning and ownership of the shared region, row-stride/layout change, and the fact that /dev/shm is node-local β multi-node TP cannot share the slice without communication.
Candidate B β leader store + TP-group broadcast on load (LMCache-style): works uniformly across single- and multi-node TP; adds TP-group broadcast traffic on load.
FS tier: produce a compact, TP-independent payload (single logical copy) and then admit MLA into the parallel-agnostic naming of file_mapper.py. Note that this naming mode erases all parallel dimensions (TP/PP/DCP/PCP) and rank, not just TP ("tp/pp/pcp/dcp are forced to 1 and rank to 0", vllm/v1/kv_offload/file_mapper.py:43), so the admission stays gated to the PP=PCP=DCP=1 MVP; extending beyond that requires TP-only naming or canonicalizing the remaining axes first. This aligns naturally with the canonical KV layout direction in [KV Connector] Add canonical KV layout fields for TP-agnostic offloadΒ #46954.
Happy to converge on whichever direction fits the multi-tier design; a hybrid (A for node-local tiers, B for the default spec) is also plausible.
Coordination
This slots under RFC #38260 (its "canonical TP1 form" principle implies exactly one MLA copy in storage) and relates to #46954 (canonical KV layout fields β MLA latent modeled head-less) and #44865 (transfer data model refactor touching the same paths).
Not a duplicate of #38395 ("Fix/mla kv offloading", open draft): that patch keeps the world_size multiplication and per-TP-rank copies β it addresses MLA/HMA correctness and early disk-tier experimentation, not cross-rank replica dedup.
Document the caveat only (docs + kv_offloading_size docstring): cheap, but leaves a ~TPx capacity loss for exactly the models (DeepSeek-class) where KV offloading matters most.
π The feature, motivation and pitch
Problem
For MLA models, the latent KV cache is logically replicated across TP ranks β and expected to be byte-identical in homogeneous TP setups without context parallelism. MLA hardcodes
num_kv_heads=1(vllm/model_executor/layers/attention/mla_attention.py:385) and the latent projection is not TP-sharded (ReplicatedLinear, ordisable_tp=Truein the fused-projection paths). The codebase states this directly:The native KV offloading path (
OffloadingConnector+vllm/v1/kv_offload/) does not exploit this. In the pure-MLA,DCP=PCP=1baseline configuration (refs againstmain@7bd154375):CPUOffloadingSpecprices each logical block at per-worker bytes Γ world size (vllm/v1/kv_offload/cpu/spec.py:86), so atTP=Nthe unique-content capacity is roughly1/Nof the configured--kv-offloading-sizebudget.CPUOffloadingSpec: each rank allocates its own private CPU buffer (pinned when supported) (vllm/v1/kv_offload/cpu/gpu_worker.py:501).TieringOffloadingSpec: on single-node TP, the copies live in different per-worker slots of the same shared-mmap row (worker0_block0 | worker1_block0 | ...,vllm/v1/kv_offload/cpu/shared_offload_region.py:123); the FS secondary tier then writes the entire row as one layout-specific block file per key, so the duplicate copies sit inside that file as distinct worker slots (vllm/v1/kv_offload/tiering/fs/manager.py:159). These observations are verified for single-node TP; multi-node tiering β where both/dev/shmand the scheduler's memory view are node-local β is treated as unsupported/unverified here.vllm/v1/kv_offload/file_mapper.py:85, from [KV offload] Parallel-agnostic fs-tier cache for single full-attention groupΒ #44733). Note that relaxing this predicate alone would not be enough β because the file payload is the full multi-worker row, files produced under different TP sizes differ in length and layout; a compact, TP-independent payload is a prerequisite.Net effect:
--kv-offloading-size 100(GiB) with TP=8 on a pure-MLA model yields only ~12.5 GiB of unique cached KV. Neither thekv_offloading_sizedocstring ("When TP > 1, this is the total buffer size summed across all TP ranks",vllm/config/cache.py:182) nordocs/features/kv_offloading_usage.mdmentions this caveat.MLA is the best case for deduplication (replicated content, no reshard needed), yet it is the one case excluded from the existing single-copy machinery.
Scope and correctness notes
TP / DCP, not alwaysTP: DCP shards tokens across context-parallel ranks, but each DCP shard is still replicated across the TP ranks that share it. PCP is outer to TP, so TP-dedup can in principle be applied within each PCP namespace independently.world_sizealso includes PP, and PP stages hold different layers β that dimension is real sharding, not replication, and must remain untouched by any dedup.PP=PCP=DCP=1β a safe MVP boundary where the replica factor is exactlyTP; this is an implementation simplification, not a theoretical requirement. Extensions (DCP-aware factor, per-PCP-namespace dedup, PP composition) can follow.num_cpu_blocks, medium/events) must stay consistent with the single-copy worker-side layout.Prior art
num_kv_head < tp_size(MLA β 1) anddcp_size <= 1, TP ranks collapse to one shared key namespace and stripe PUTs across ranks so each block is stored once (vllm/distributed/kv_transfer/kv_connector/v1/mooncake/store/worker.py:1004). The DCP guard was added in [Bugfix][KV Connector] Disable Mooncake TP put-striding when DCP > 1Β #45371; PCP needs no guard since it is outer to TP.LMCacheConnectorV1): for MLA,save_only_first_rankdefaults on β non-leader ranks neither store nor read; the leader retrieves and performs a TP-group device broadcast (v0.5.1cache_engine.pyL113-117). Cache keys collapseworld_sizeto 1 (TP-size-agnostic) since LMCache#2098 (first released in v0.3.10). A separate remote-only scheme,remote_enable_mla_worker_id_as0, serves as an alternative whensave_only_first_rankis disabled (each rank reads the single rank-0 copy from remote storage directly). The built-in single-serverLMCacheMPConnectorfallback collapses only the TP dimension and suppresses MLA stores on ranks whererank % tp_size != 0("Tensor parallel does not change the KV caches for MLA models",vllm/distributed/kv_transfer/kv_connector/v1/lmcache_mp_connector.py); in a TP-only deployment this yields one stored copy while every TP rank retrieves directly. The external connector preferred by current vLLM has separate multi-server writer semantics.The native CPU/tiering layout currently lacks an equivalent MLA replica-reduction path.
Proposed direction (candidates β feedback requested)
cpu/spec.pyshould drop only the confirmed TP replica factor, leaving PP (different layers per stage) and any context-parallel sharding intact. Adjusting the sizing alone is unsafe: with per-worker private buffers still in place, simply growingnum_cpu_blockswould multiply actual memory use by roughlyworld_sizebeyond the configured budget β the single-copy physical layout is a prerequisite for the sizing change./dev/shmis node-local β multi-node TP cannot share the slice without communication.file_mapper.py. Note that this naming mode erases all parallel dimensions (TP/PP/DCP/PCP) and rank, not just TP ("tp/pp/pcp/dcp are forced to 1 and rank to 0",vllm/v1/kv_offload/file_mapper.py:43), so the admission stays gated to thePP=PCP=DCP=1MVP; extending beyond that requires TP-only naming or canonicalizing the remaining axes first. This aligns naturally with the canonical KV layout direction in [KV Connector] Add canonical KV layout fields for TP-agnostic offloadΒ #46954.Happy to converge on whichever direction fits the multi-tier design; a hybrid (A for node-local tiers, B for the default spec) is also plausible.
Coordination
This slots under RFC #38260 (its "canonical TP1 form" principle implies exactly one MLA copy in storage) and relates to #46954 (canonical KV layout fields β MLA latent modeled head-less) and #44865 (transfer data model refactor touching the same paths).
Not a duplicate of #38395 ("Fix/mla kv offloading", open draft): that patch keeps the
world_sizemultiplication and per-TP-rank copies β it addresses MLA/HMA correctness and early disk-tier experimentation, not cross-rank replica dedup.cc @orozery @Etelis @dannyharnik
Alternatives
kv_offloading_sizedocstring): cheap, but leaves a~TPxcapacity loss for exactly the models (DeepSeek-class) where KV offloading matters most.kv_offloading_backend="lmcache", which already dedups (MLA TP collapsing added in [DeepSeek + LMCache Multiprocess] handle MLA for deepseek model + LMCache Multiprocess connectorΒ #29039; the backend was wired to theLMCacheMPConnectorin [KV Connector] Update lmcache kv_offloading_backend to use LMCacheMPConnectorΒ #42865): a valid workaround, but the native backend should not silently spend(TP-1)/TPof its budget on replicas.Additional context
Duplicate-work check: searched open/closed issues and PRs (keyword sweeps,
vllm/v1/kv_offload/git history, and the comment threads of #19854 / #38260 / #33689). No existing issue or PR implements native MLA TP replica dedup. Closest items: #44733 (explicitly excludes MLA from parallel-agnostic caching), #46954 (layout schema only, no write-side dedup semantics), #44865 (overlapping-path refactor), #38395 (see above β retains per-rank copies).I intend to follow up with a PR implementing the initial scope above, pending feedback on the preferred direction.