ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

存储分离架构中的 KV Cache 如何传输

2026/8/7 0:50:02 拓冰建站 浏览量
存储分离架构中的 KV Cache 如何传输

在存储分离的大模型推理系统中,Prefill 节点负责处理提示词并生成 KV Cache,Decode 节点通过共享存储复用已有前缀状态,再继续执行后续计算。表面上看,这一过程类似于“Prefill 写入对象、Decode 读取对象”;实际实现还需要解决三个更具体的问题:

  1. vLLM 中的 KV Cache 分散在分页 GPU 内存中,如何形成适合跨节点保存的稳定对象;
  2. Decode 节点如何联合查询本地 GPU Cache、LMCache L1 和远端 L2,确定当前请求真正可复用的连续前缀;
  3. 从远端取回的对象如何恢复到 Decode 节点新分配的 paged KV blocks 中。

这些问题对应三套不同的数据组织方式:

  • vLLM page/block:面向 GPU KV 内存分配和 attention 访问;
  • LMCache token chunk:面向内容寻址、命中查询和对象复用;
  • 远端存储对象:面向网络传输、压缩、量化和后端管理。

KV Cache 在三套组织方式之间流动时,会经历分页地址解析、数据 gather、对象化、序列化、网络传输、反序列化和 scatter 写回。KV Connector 位于推理调度器和外部缓存系统之间,负责把“哪些 token 可以复用”的调度语义,转换成“查询哪些对象、恢复到哪些 GPU slots”的数据传输行为。

本文以 vLLM V1、LMCache Cache Engine 和 LMCache MP 分层存储架构为主线,完整分析两条典型路径:

  • Prefill 节点如何将 paged KV 转换为远端可复用对象;
  • Decode 节点如何扫描多层级 KV Cache,并按需恢复本地缺失的连续前缀。

本文讨论普通的因果前缀缓存,即 prefix caching。CacheBlend 等支持非连续片段复用的机制具有额外的状态校正过程,不属于本文的主流程。


一、理解 KV Cache 传输所需的四种数据形态

存储分离系统中的 KV Cache 会依次表现为 GPU page、内容寻址 chunk、内存对象和远端 payload。四种形态解决的问题不同,彼此之间通过明确的转换接口衔接。

flowchart LRA["Prefill<br/>vLLM Paged KV<br/>GPU 物理 block"] -->|"slot_mapping<br/>GPU gather"| B["LMCache MemoryObj<br/>连续逻辑布局"]B -->|"Serializer<br/>压缩 / 量化 / 加密"| C["L2 Payload<br/>序列化对象"]C -->|"L2 Adapter Store<br/>TCP / RDMA / 文件 I/O"| D["远端存储对象"]D -->|"L2 Adapter Load"| E["Serialized Payload"]E -->|"Deserializer"| F["KV-shaped MemoryObj"]F -->|"slot_mapping<br/>GPU scatter"| G["Decode<br/> vLLM Paged KV"]

1.1 vLLM page:GPU 物理内存管理视角

vLLM 为 attention layers 预分配 KV Cache tensors,并把可用空间划分为固定大小的 blocks。请求的逻辑 token 序列保持连续,但 KV 数据在 GPU 中可以落在离散的物理 blocks 上。

例如:

token   0–15  → physical block 37
token  16–31  → physical block 12
token  32–47  → physical block 81
token  48–63  → physical block 45

vLLM 通过两类结构描述这一关系:

  • block_table 描述请求使用了哪些逻辑或物理 blocks;
  • slot_mapping 描述每个参与本次计算的 token 对应哪个 KV slot。

Prefill 节点与 Decode 节点拥有各自独立的 KV block allocator,因此相同 token 范围在两个节点上通常对应不同的物理 block IDs。

flowchart TBT0["token 0–15"] --> P37["Prefill GPU<br/>block 37"]T1["token 16–31"] --> P12["Prefill GPU<br/>block 12"]T2["token 32–47"] --> P81["Prefill GPU<br/>block 81"]T0 -. "Decode 重新分配" .-> D8["Decode GPU<br/>block 8"]T1 -. "Decode 重新分配" .-> D54["Decode GPU<br/>block 54"]T2 -. "Decode 重新分配" .-> D19["Decode GPU<br/>block 19"]

vLLM page 主要服务以下运行时需求:

  • 快速分配和释放 GPU KV 空间;
  • 支持请求随 decode 过程持续增长;
  • 支持 prefix block 共享和引用计数;
  • 支持 continuous batching;
  • 允许 attention kernel 直接通过 block table 访问分页 KV。

LMCache 的 VLLMPagedMemGPUConnectorV2 会读取各层 KV tensor 指针、block size、KV layout 和 slot_mapping[start:end],再据此执行分页数据的提取或写回。相关实现位于:

lmcache/v1/gpu_connector/gpu_connectors.py

关键调用和数据包括:

slot_mapping[start:end]
kv_cache_pointers
block_size
multi_layer_kv_transfer(...)

这些实现表明,LMCache 通过当前 vLLM 实例的 slot mapping 访问 KV 数据,远端对象无需继承 Prefill 节点的物理 block ID。

1.2 LMCache chunk:内容寻址视角

GPU block ID 的有效范围局限于某个 vLLM 实例和某段运行时间。外部缓存需要稳定的对象名称,以支持跨进程、跨节点和跨请求复用。

LMCache 的 ChunkedTokenDatabase 将 token 序列切分成固定大小的 chunks,并为每个 chunk 生成 CacheEngineKey。当前默认配置通常采用 256-token chunk,实际大小可以通过配置调整。

概念代码如下:

prefix_hash = INITIAL_HASHfor start in range(0, len(tokens), chunk_size):end = min(start + chunk_size, len(tokens))token_chunk = tokens[start:end]prefix_hash = hash(prefix_hash, token_chunk)key = CacheEngineKey(model_name=model_name,world_size=world_size,worker_id=worker_id,chunk_hash=prefix_hash,kv_dtype=kv_dtype,request_config=request_config,)yield start, end, key

这里使用链式 prefix hash:

flowchart LRH0["初始 hash"] --> C0["chunk 0 tokens"]C0 --> H1["prefix hash 0"]H1 --> C1["chunk 1 tokens"]C1 --> H2["prefix hash 1"]H2 --> C2["chunk 2 tokens"]C2 --> H3["prefix hash 2"]

后一个 chunk 的 key 同时依赖此前的前缀 hash。这样能够保证:相同的局部 token 片段出现在不同前缀后时,会形成不同的普通 prefix-cache key。

一个有效的 chunk key需要区分可能影响 KV 内容或布局的因素,例如:

  • 模型标识;
  • tensor parallel world size;
  • worker 或 rank 标识;
  • KV dtype;
  • token prefix hash;
  • request-specific cache config;
  • layerwise 模式中的 layer ID;
  • 与缓存兼容性相关的其他配置。

对应实现位于:

lmcache/v1/token_database.py

关键函数包括:

process_tokens(...)
_prefix_hash(...)

Token Database 的输出不是 KV payload,而是:

(start_token, end_token, CacheEngineKey)

这组信息定义了外部缓存对象的逻辑边界。

1.3 MemoryObj:数据布局转换视角

LMCache 使用 MemoryObj 承载从 paged KV 中提取出的逻辑对象。MemoryObj 同时保存:

  • 实际数据缓冲区;
  • shape;
  • dtype;
  • memory format;
  • 有效长度;
  • 引用和生命周期状态。

对于普通多头 attention,LMCache 常见的中间格式为 KV_2LTD,可以概括为:

[K/V, layer, token, local hidden dimension]

vLLM 端的数据沿物理 page 组织,MemoryObj 沿逻辑 token range 组织。GPU Connector 在两种布局之间执行 gather 和 scatter。

flowchart LRsubgraph GPU["vLLM Paged KV"]L0["Layer 0<br/>离散 blocks"]L1["Layer 1<br/>离散 blocks"]LN["Layer N<br/>离散 blocks"]endMAP["slot_mapping<br/>token start:end"]subgraph OBJ["LMCache MemoryObj"]M["连续逻辑布局<br/>K/V × Layer × Token × Hidden"]endL0 --> MAPL1 --> MAPLN --> MAPMAP -->|"multi_layer_kv_transfer"| M

假设当前 worker 负责:

  • (L) 个 attention layers;
  • 每层 (H_{kv}) 个本地 KV heads;
  • head dimension 为 (D);
  • chunk 包含 (C) 个 tokens;
  • KV dtype 的元素大小为 (S) bytes。

一个包含 K 和 V 的未压缩 chunk,其逻辑数据量约为:

\[B_{\text{chunk}} = 2 \times L \times C \times H_{kv} \times D \times S \]

其中系数 2 对应 K 和 V。

在 layerwise 模式下,一个对象只覆盖一层:

\[B_{\text{layer-chunk}} = 2 \times C \times H_{kv} \times D \times S \]

实际对象还可能包含对齐空间、量化参数、序列化 header 和校验数据。

相关实现位于:

lmcache/v1/cache_engine.py
lmcache/v1/gpu_connector/gpu_connectors.py

关键接口包括:

metadata.get_shapes(num_tokens)
metadata.get_dtypes()
gpu_connector.batched_from_gpu(...)
gpu_connector.batched_to_gpu(...)

1.4 L2 payload:网络和存储后端视角

MemoryObj 进入 L2 adapter 前,可以经过 serde 转换。网络上的实际 payload 可能采用以下形式:

原始 BF16 / FP16 KV
FP8 量化数据 + scale
LZ4 / Zstd 压缩流
CacheGen 风格编码对象
加密字节流

LMCache 的 serde 接口包括:

class Serializer:def serialize(self, src, dst, key) -> int:...def estimate_serialized_size(self, layout_desc) -> int:...class Deserializer:def deserialize(self, src, dst, key) -> None:...

Serializer 将 KV-shaped source 写入目标 buffer,并返回实际有效字节数;Deserializer 根据对象 metadata,把序列化数据恢复为 KV-shaped destination。

L2 adapter 再把以下内容映射到具体后端:

ObjectKey
+ layout metadata
+ serialized payload

远端对象可以表现为:

  • Redis 或 Valkey 中的 key-value;
  • 远端注册内存中的对象;
  • Mooncake 或 NIXL 管理的数据对象;
  • POSIX 文件;
  • NVMe slab;
  • S3 或兼容对象存储中的 object。

因此,存储分离系统中的“传输对象”由两部分共同定义:

  1. Token Database 和 Cache Engine 确定对象代表哪一段 KV;
  2. serde 和 L2 adapter 确定对象在网络及后端中的物理表示。

二、组件分工:谁决定命中,谁搬数据,谁管理远端对象

完整系统中包含多层 Connector 和 Manager。它们分别处理请求语义、GPU 布局、对象索引和后端协议。

flowchart TBS["vLLM Scheduler"] -->|"本地命中、请求状态"| KC1["KV Connector<br/>Scheduler Side"]KC1 -->|"Connector Metadata"| KC2["KV Connector<br/>Worker Side"]TD["Token Database"] -->|"chunk boundaries<br/>CacheEngineKey"| KC1TD -->|"Object Keys"| CE["LMCache Cache Engine"]KC2 -->|"KV tensors、slot_mapping"| GC["GPU Connector"]GC -->|"paged ↔ contiguous"| L1["MemoryObj / L1"]L1 --> SM["Storage Manager"]SM --> SC["StoreController"]SM --> PC["PrefetchController"]SC --> Serde["Serializer"]Serde --> AD["L2 Adapter"]AD --> RS["Remote Storage"]RS --> ADAD --> DeSerdePC --> DeSerde["Deserializer"]

2.1 KV Connector:协调调度与传输

vLLM 的 KVConnectorBase_V1 将 Connector 划分为 scheduler-side 和 worker-side。

Scheduler-side 关注:

  • 本地 KV 命中之后,外部缓存还能提供多少 token;
  • 请求需要分配多少新的 KV blocks;
  • 如何构造本轮 worker 所需的 connector metadata;
  • 异步加载完成后,何时把请求重新放回可运行队列。

Worker-side 关注:

  • 注册各层 KV cache tensors;
  • 在模型执行前启动加载;
  • 在具体 attention layer 前等待 KV 就绪;
  • 在 attention layer 结束后启动保存;
  • 在 GPU page 回收前等待异步保存完成。

核心接口包括:

class KVConnectorBase_V1:def get_num_new_matched_tokens(self,request,num_computed_tokens,):...def update_state_after_alloc(self,request,blocks,num_external_tokens,):...def build_connector_meta(self,scheduler_output,):...def register_kv_caches(self,kv_caches,):...def start_load_kv(self,forward_context,**kwargs,):...def wait_for_layer_load(self,layer_name,):...def save_kv_layer(self,layer_name,kv_layer,attn_metadata,**kwargs,):...def wait_for_save(self):...

相关实现位于:

vllm/distributed/kv_transfer/kv_connector/v1/base.py
vllm/distributed/kv_transfer/kv_connector/v1/lmcache_connector.py

KV Connector 主要完成四项工作:

  • 命中语义转换:把外部 chunk 命中转换成 vLLM 可使用的 token 数;
  • 地址关联:把 token range 关联到本轮请求的 block table 和 slot mapping;
  • 生命周期管理:确保传输完成前,源或目标 pages 保持有效;
  • 执行同步:在模型或某层 attention 使用 KV 前建立完成条件。

2.2 GPU Connector:执行布局转换

GPU Connector 处理:

vLLM paged layout⇅
LMCache contiguous MemoryObj layout

它接收:

  • KV tensor pointers;
  • slot mapping;
  • block size;
  • token 起止位置;
  • KV layout;
  • transfer direction;
  • chunk 内需要跳过的 prefix tokens。

对应实现通常调用:

multi_layer_kv_transfer(...)

传输方向可以是:

D2H:GPU paged KV → CPU MemoryObj
D2D:GPU paged KV → GPU MemoryObj
H2D:MemoryObj → GPU paged KV

具体方向取决于 L1 内存位置、后端能力和部署配置。

2.3 Token Database:定义外部对象边界

Token Database 负责:

  • chunk_size 切分 token;
  • 处理 store/retrieve mask;
  • 生成链式 prefix hash;
  • 构造 CacheEngineKey
  • 返回每个对象对应的 token range。

它决定外部存储以什么粒度建立索引。

2.4 Storage Manager:组织缓存层级

Storage Manager 向 Cache Engine 提供批量对象操作:

batched_allocate(...)
batched_put(...)
batched_contains(...)
batched_get(...)

它把 chunk keys 和 MemoryObj 映射到 L1 或 L2 location,并负责对象的引用、锁定和释放。

2.5 StoreController、PrefetchController 与 L2 Adapter

StoreController 处理写入方向:

L1 新对象
→ Store Event
→ Serialize
→ L2 Adapter
→ Remote Backend

PrefetchController 处理读取方向:

L1 Miss
→ L2 Lookup
→ L2 Load
→ Deserialize
→ 回填 L1

L2 adapter 负责具体后端操作,例如:

submit_store_task(key, data)
submit_lookup_and_lock_task(keys)
submit_load_task(keys, layout_desc)

这一分层使 vLLM 调度逻辑能够保持统一,同时允许存储后端采用 Redis、NIXL、Mooncake、文件系统或对象存储。


三、Prefill 写入路径:如何形成一个可共享的远端 KV 对象

Prefill 写入可以理解为三个连续阶段:

  1. vLLM 在 GPU 中生成分页 KV;
  2. LMCache 根据 token 前缀形成内容寻址对象;
  3. 对象进入 L1,并异步扩散到远端 L2。
sequenceDiagramparticipant S as vLLM Schedulerparticipant M as Prefill Workerparticipant KC as KV Connectorparticipant TD as Token Databaseparticipant GC as GPU Connectorparticipant L1 as LMCache L1participant SC as StoreControllerparticipant SE as Serializerparticipant L2 as L2 Adapterparticipant R as Remote StorageS->>M: block table + slot_mappingM->>M: 执行 Prefill 并写入 paged KVM->>KC: 保存请求或 save_kv_layer()KC->>TD: 生成 chunk ranges 和 keysTD-->>KC: start / end / CacheEngineKeyKC->>GC: KV tensors + slot_mapping + rangesGC->>L1: gather 为 MemoryObjL1-->>SC: ObjectStored 事件SC->>SE: serialize(MemoryObj)SE-->>SC: payload + valid lengthSC->>L2: submit_store_task(key, payload)L2->>R: backend storeR-->>L2: completion

3.1 从模型计算结果到 paged KV

Prefill 请求进入 scheduler 后,vLLM 完成:

  • prompt token 处理;
  • 本地 prefix cache 查询;
  • KV block 分配;
  • block table 和 slot mapping 构造;
  • model worker 调度。

模型逐层计算 Q、K、V,并把 K/V 写入对应 layer 的 paged KV tensor。

概念过程如下:

for layer_id, layer in enumerate(model.layers):query, key, value = layer.project_qkv(hidden_states)paged_kv_cache[layer_id].write(key=key,value=value,slot_mapping=slot_mapping,)hidden_states = layer.attention(query=query,kv_cache=paged_kv_cache[layer_id],block_table=block_table,)

完成 Prefill 后,请求的 KV 内容分布在多层和多个离散 blocks 中:

flowchart TBsubgraph L0["Layer 0 KV Tensor"]A0["block 37<br/>token 0–15"]A1["block 12<br/>token 16–31"]A2["block 81<br/>token 32–47"]endsubgraph L1["Layer 1 KV Tensor"]B0["block 37<br/>token 0–15"]B1["block 12<br/>token 16–31"]B2["block 81<br/>token 32–47"]endsubgraph LN["Layer N KV Tensor"]C0["block 37<br/>token 0–15"]C1["block 12<br/>token 16–31"]C2["block 81<br/>token 32–47"]end

KV Connector 的 register_kv_caches() 向外部系统暴露这些 KV tensors。NIXL 等 Connector 可以在这里完成内存注册;LMCache GPU Connector 则保存 tensor pointers 和布局信息,用于后续 gather/scatter。

代码证据:

vllm/distributed/kv_transfer/kv_connector/v1/lmcache_connector.py
lmcache/v1/gpu_connector/gpu_connectors.py

关键接口:

register_kv_caches(...)
VLLMPagedMemGPUConnectorV2(...)

3.2 从 token 前缀到存储对象

LMCache 保存对象时,首先通过 Token Database 确定对象边界。

chunk_infos = token_database.process_tokens(tokens=tokens,hashes=hashes,offsets=offsets,mask=store_mask,request_configs=request_configs,
)

假设:

prompt length = 900 tokens
chunk size    = 256 tokens

Token Database 可以形成:

chunk 0:token   0–255
chunk 1:token 256–511
chunk 2:token 512–767
chunk 3:token 768–899
flowchart LRP["900-token Prompt"] --> C0["chunk 0<br/>0–255"]P --> C1["chunk 1<br/>256–511"]P --> C2["chunk 2<br/>512–767"]P --> C3["chunk 3<br/>768–899"]C0 --> K0["CacheEngineKey 0"]C1 --> K1["CacheEngineKey 1"]C2 --> K2["CacheEngineKey 2"]C3 --> K3["CacheEngineKey 3"]

是否保存不足一个完整 chunk 的尾部,由 save_unfull_chunk 等配置决定。关闭该行为时,保存范围会截断到完整 chunk 边界。

Store mask 可以跳过已经存在或无需重复保存的前缀。例如,外部缓存已经包含 chunk 0 和 chunk 1,本次只需要处理 chunk 2 与 chunk 3。

已存在:chunk 0、chunk 1
新增:  chunk 2、chunk 3

代码证据:

lmcache/v1/token_database.py
lmcache/v1/cache_engine.py

关键逻辑:

process_tokens(...)
save_unfull_chunk
mask

3.3 从 paged KV 到连续 MemoryObj

Cache Engine 根据每个 chunk 的 token 数计算 shape 和 dtype,然后分配 MemoryObj

for start, end, key in chunk_infos:num_tokens = end - startkv_shapes = metadata.get_shapes(num_tokens)kv_dtypes = metadata.get_dtypes()memory_obj = storage_manager.allocate(shapes=kv_shapes,dtypes=kv_dtypes,fmt=kv_format,)

完成分配后,GPU Connector 执行批量 gather:

gpu_connector.batched_from_gpu(memory_objs=memory_objs,starts=starts,ends=ends,kvcaches=kv_caches,slot_mapping=slot_mapping,
)

随后 Cache Engine 才把对象交给存储层:

storage_manager.batched_put(keys=keys,memory_objs=memory_objs,location=store_location,
)

调用关系可以归纳为:

flowchart LRA["process_tokens()"] --> B["allocate MemoryObj"]B --> C["batched_from_gpu()"]C --> D["batched_put()"]

batched_from_gpu() 使用:

slot_mapping[start:end]

从多个 layer tensors 和离散 physical blocks 中 gather KV 数据,并按逻辑 token 顺序写入 MemoryObj

最终对象形成过程如下:

flowchart LRsubgraph Source["Prefill Paged KV"]P0["Layer 0<br/>blocks 37, 12, 81"]P1["Layer 1<br/>blocks 37, 12, 81"]PN["Layer N<br/>blocks 37, 12, 81"]endSM["slot_mapping<br/>token start:end"]subgraph Target["LMCache Object"]M["连续 MemoryObj<br/>K/V × Layer × Token × Hidden"]endP0 --> SMP1 --> SMPN --> SMSM -->|"GPU gather"| M

远端对象所保存的是 KV 内容、chunk key 和布局 metadata。Prefill block IDs 在完成 gather 后不再承担对象寻址作用。

代码证据:

lmcache/v1/cache_engine.py
lmcache/v1/gpu_connector/gpu_connectors.py

关键调用:

batched_from_gpu(...)
multi_layer_kv_transfer(...)
storage_manager.batched_put(...)

3.4 L1:对象进入分层存储系统的入口

在 LMCache MP 架构中,vLLM worker 向独立 LMCache Server 发出 STORE 请求。Server 在 L1 中预留目标空间,完成 GPU 到 L1 的数据复制,再将对象状态更新为可读。

sequenceDiagramparticipant V as vLLM Workerparticipant RPC as LMCache RPCparticipant L1 as L1 Managerparticipant GT as GPU Transfer Moduleparticipant EV as Event ManagerV->>RPC: STORE(key, token range, GPU metadata)RPC->>L1: reserve_write(key, layout)L1-->>RPC: writable MemoryObjRPC->>GT: paged KV → L1 objectGT-->>RPC: copy completionRPC->>L1: finish_write(key)L1->>EV: ObjectStored event

L1 通常承担:

  • 靠近 GPU 的低延迟对象缓存;
  • GPU copy 的目标和来源;
  • 新对象推入 L2 前的暂存;
  • L2 对象预取回本地后的落点;
  • 对象读写锁和生命周期管理。

在该流程中:

控制面:STORE 请求ObjectKeytoken rangelayout metadatacompletion event数据面:实际 KV tensorGPU transferMemoryObj buffer

控制 RPC 描述对象和状态,GPU Transfer Module 负责大体积 KV 数据搬运。

架构证据:

LMCache MP architecture
L1Manager.reserve_write()
L1Manager.finish_write()
GPUTransferModule

3.5 L2:异步扩散、序列化和远端保存

L1 写入完成后,StoreController 接收对象事件,并向配置的 L2 adapters 提交异步写任务。

sequenceDiagramparticipant L1 as L1 Cacheparticipant SC as StoreControllerparticipant SE as Serializerparticipant A0 as L2 Adapter 0participant A1 as L2 Adapter 1participant R0 as Remote Backend 0participant R1 as Remote Backend 1L1->>SC: ObjectStored(key)par Adapter 0SC->>SE: serialize(object)SE-->>SC: payloadSC->>A0: submit_store_task(key, payload)A0->>R0: storeand Adapter 1SC->>A1: submit_store_task(key, object)A1->>R1: storeend

不同 adapters 可以采用独立 serde。例如:

L2-0 本地 NVMe:原始 BF16 对象L2-1 远端共享存储:FP8 或压缩对象

序列化对象通常包括:

Object Header
├── format version
├── codec ID
├── original dtype
├── tensor shape
├── token count
├── layer information
├── serialized length
└── checksumCodec Metadata
├── scale / zero point
├── codebook
└── chunk indexPayload
└── serialized bytes

LMCache 提供 Serializer/Deserializer 接口,具体 header 和 payload 格式由 serde 实现决定。

若原始对象大小为 (B),序列化 payload 大小为 (B_s),则对象压缩比为:

\[R = \frac{B}{B_s} \]

网络实际发送量还包括:

\[B_{\text{wire}} = B_s + B_{\text{header}} + B_{\text{protocol}} \]

远端后端最终保存的对象形态取决于 adapter:

Key-value 后端

Key:serialized CacheEngineKeyValue:serialized KV payload

文件后端

base_path/
└── model_id/└── worker_id/└── chunk_hash.object

远端内存后端

Object Index:CacheEngineKey → remote address / lengthRegistered Memory:[object 0][object 1][object 2]...

以 RESP backend 为例,一个 LMCache chunk 对应 Redis 或 Valkey 中的 key-value;Mooncake、NIXL 等后端则可以通过远端内存和专用数据传输路径保存对象。

实现与架构证据:

LMCache MP StoreController
LMCache serde API
LMCache L2 Adapter API
RESP backend

关键接口:

submit_store_task(...)
Serializer.serialize(...)
Serializer.estimate_serialized_size(...)

3.6 异步保存与 GPU page 生命周期

StoreController 向 L2 的异步写入可以持续更长时间,但源 paged KV 只需要保持到相关 GPU gather 或 DMA 完成。Connector 必须准确管理这一边界。

sequenceDiagramparticipant KV as Paged KV Blockparticipant Save as Async Saveparticipant Alloc as vLLM AllocatorSave->>KV: 开始读取Note over KV,Save: 源数据处于使用中Save-->>Alloc: source copy completeAlloc->>KV: 允许释放或复用Note over Save: 后续 L1 → L2 可继续异步执行

wait_for_save() 用于防止 vLLM 在源数据尚未复制完成时覆盖对应 pages。L1 到 L2 的后续异步扩散则由 LMCache 自己管理对象生命周期。

代码证据:

vllm/distributed/kv_transfer/kv_connector/v1/lmcache_connector.py

关键接口:

wait_for_save()

3.7 Layerwise 保存:把模型执行与 KV 写出重叠

在 layerwise 模式中,一个 token chunk 的不同层独立形成对象:

(chunk key, layer 0)
(chunk key, layer 1)
...
(chunk key, layer N)

这样可以在模型继续计算后续层时,异步保存前面层的 KV。

sequenceDiagramparticipant GPU as Prefill GPUparticipant KC as KV Connectorparticipant ST as Storage PipelineGPU->>GPU: 计算 Layer 0GPU->>KC: save_kv_layer(layer 0)KC->>ST: 保存 Layer 0 KVparGPU->>GPU: 计算 Layer 1andST->>ST: 传输 Layer 0endGPU->>KC: save_kv_layer(layer 1)KC->>ST: 保存 Layer 1 KVparGPU->>GPU: 计算 Layer 2andST->>ST: 传输 Layer 1end

Layerwise 模式改变对象在 layer 维度上的切分方式:

普通模式:一个对象 = 一个 token chunk × 当前 worker 的多个 layersLayerwise:一个对象 = 一个 token chunk × 一个 layer

Token chunk 的内容寻址关系仍然保持不变。

代码证据:

vllm/distributed/kv_transfer/kv_connector/v1/lmcache_connector.py
lmcache/v1/cache_engine.py

关键接口:

save_kv_layer(...)

四、Decode 读取路径:如何扫描多层缓存并恢复缺失前缀

Decode 端需要先回答两个问题:

  1. 当前 Decode GPU 已经拥有多少连续 KV;
  2. LMCache 的 L1 和 L2 还能把该前缀扩展到哪里。

确定两个边界后,系统才会分配目标 pages,并恢复两者之间的缺失区间。

sequenceDiagramparticipant S as vLLM Schedulerparticipant KM as KV Cache Managerparticipant KC as KV Connectorparticipant TD as Token Databaseparticipant L1 as LMCache L1participant PC as PrefetchControllerparticipant L2 as L2 Adapterparticipant W as Decode Workerparticipant GC as GPU Connectorparticipant GPU as Decode Paged KVS->>KM: 查询本地 computed blocksKM-->>S: local_hit_tokensS->>KC: get_num_new_matched_tokens()KC->>TD: 生成 chunk keysTD-->>KC: key sequenceKC->>L1: 查询 keysL1->>PC: 缺失 keysPC->>L2: lookup / loadL2-->>PC: objectsPC->>L1: prefetch objectsL1-->>KC: external_hit_tokensKC-->>S: 新增可复用 token 数S->>KM: 分配缺失 GPU blocksS->>W: Connector metadataW->>KC: start_load_kv()KC->>L1: retrieve selected chunksL1-->>KC: MemoryObjsKC->>GC: objects + slot_mappingGC->>GPU: scatter 到 paged KV

4.1 本地 GPU prefix 是查询起点

请求进入 Decode scheduler 后,vLLM 先查询当前实例中的本地 prefix blocks。

关键调用关系为:

new_computed_blocks, local_hit_tokens, ... = (kv_cache_manager.get_computed_blocks_for_connector(request)
)num_external_tokens, load_kv_async = (connector.get_num_new_matched_tokens(request,block_aligned_local_hit_tokens,)
)

查询顺序如下:

flowchart LRR["请求 token prefix"] --> G["Decode GPU Paged KV"]G -->|"local_hit_tokens"| KC["KV Connector"]KC -->|"从本地命中终点继续"| E["LMCache L1 / L2"]

num_computed_tokens 表示当前 Decode GPU 已拥有的 KV 前缀。Connector 返回外部缓存在此基础上新增可提供的 token 数。

代码证据:

vllm/v1/core/sched/scheduler.py
vllm/distributed/kv_transfer/kv_connector/v1/base.py

关键调用:

get_computed_blocks_for_connector(...)
get_num_new_matched_tokens(...)

4.2 Chunk 扫描发生在 key 和索引层

LMCache Connector 将请求 token IDs 交给 lookup client:

num_external_hit_tokens = lookup_client.lookup(token_ids,lookup_id=request_id,request_configs=request_configs,
)

Lookup 使用与 Prefill 保存时一致的 Token Database,重新生成 chunk keys:

chunk_infos = token_database.process_tokens(tokens=token_ids,request_configs=request_configs,
)keys = [chunk.key for chunk in chunk_infos]
flowchart LRT["Decode Prompt Tokens"] --> TD["Token Database"]TD --> K0["key 0"]TD --> K1["key 1"]TD --> K2["key 2"]TD --> K3["key 3"]K0 --> IDX["L1 / L2 Object Index"]K1 --> IDXK2 --> IDXK3 --> IDXIDX --> R["hit / miss / location"]

这里的扫描操作处理:

  • CacheEngineKey
  • hit/miss;
  • 对象 location;
  • pin 或 lock 状态;
  • 连续命中终点。

KV payload 在后续 prefetch 和 retrieve 阶段才进入数据传输路径。

代码证据:

lmcache/integration/vllm/vllm_v1_adapter.py
lmcache/v1/token_database.py
lmcache/v1/cache_engine.py

关键调用:

lookup_client.lookup(...)
token_database.process_tokens(...)
storage_manager.batched_contains(...)

4.3 多层级查询:从 L1 延伸到多个 L2

普通 Cache Engine lookup 会调用:

hit_chunks = storage_manager.batched_contains(keys=keys,search_range=retrieve_locations,pin=pin,
)

在 MP 架构中,查询进一步分解为:

flowchart TDQ["Chunk Keys"] --> L1["查询 L1"]L1 -->|"hit"| RES["扩展连续命中前缀"]L1 -->|"miss suffix"| PC["PrefetchController"]PC --> A0["L2 Adapter 0"]A0 -->|"hit"| P0["加载回 L1"]A0 -->|"miss"| A1["L2 Adapter 1"]A1 -->|"hit"| P1["加载回 L1"]A1 -->|"miss"| A2["下一 L2 层"]P0 --> RESP1 --> RES

例如,一个部署可以包含:

L0:Decode GPU paged KV
L1:本地 pinned CPU DRAM
L2-0:本地 NVMe
L2-1:远端共享内存
L2-2:对象存储

一个具体的查询结果可能为:

key 0:GPU 本地已命中
key 1:L1 hit
key 2:L1 miss,L2-0 hit
key 3:L1 miss,L2-0 miss,L2-1 hit
key 4:所有层 miss

有效前缀可以扩展到 key 3。命中的远端对象被 PrefetchController 加载回 L1,供后续 RETRIEVE 使用。

架构证据:

LMCache MP PrefetchController
LMCache L2 lookup flow
StorageManager.batched_contains(...)

4.4 连续前缀约束决定有效命中范围

普通自回归 prefix caching 只使用从 token 0 开始连续存在的 KV 对象。

假设索引状态为:

chunk 0:hit
chunk 1:hit
chunk 2:miss
chunk 3:hit
chunk 4:hit

有效前缀截止到 chunk 1:

flowchart LRC0["chunk 0<br/>hit"] --> C1["chunk 1<br/>hit"]C1 --> C2["chunk 2<br/>miss"]C2 -. "停止扩展" .-> C3["chunk 3<br/>hit"]C3 --> C4["chunk 4<br/>hit"]C0 --> V["有效 prefix"]C1 --> V

vLLM 的 Connector 契约要求返回当前实际可用的最大 prompt prefix。LMCache lookup 根据连续 hit_chunks 更新命中终点,retrieve 在首个缺失对象处终止恢复。

Layerwise 模式还需要确认同一 token chunk 的各层对象均存在。任意必需 layer 缺失,该 chunk 都无法形成完整的 attention KV 状态。

代码证据:

vllm/distributed/kv_transfer/kv_connector/v1/base.py
lmcache/v1/cache_engine.py

关键行为:

hit_chunks
break
layer keys

4.5 本地命中与外部命中共同确定加载区间

命中查询结束后,系统得到两个边界:

local_hit_end:Decode GPU 已有 KV 的连续终点external_hit_end:LMCache 可提供 KV 的连续终点

三段 token 区域如下:

flowchart LRA["0 ... local_hit_end<br/>Decode GPU 已有"]B["local_hit_end ... external_hit_end<br/>从 LMCache 恢复"]C["external_hit_end ... prompt_end<br/>模型计算"]A --> B --> C

新增外部命中量为:

num_new_external_tokens = (num_external_hit_tokens- num_computed_tokens
)

vLLM 随后为需要进入 Decode GPU 的区间分配 blocks,并通过 update_state_after_alloc() 和 connector metadata 把目标 slot 信息传给 worker。

代码证据:

vllm/v1/core/sched/scheduler.py
vllm/distributed/kv_transfer/kv_connector/v1/base.py
lmcache/integration/vllm/vllm_v1_adapter.py

4.6 Chunk-aligned mask 把按需加载落实到对象集合

Worker 侧启动加载时,LMCache Adapter 根据本地命中和外部命中构造 retrieve mask。

token_mask = torch.ones(len(tokens),dtype=torch.bool,
)masked_token_count = (vllm_cached_tokens// lmcache_chunk_size* lmcache_chunk_size
)token_mask[:masked_token_count] = Falselmcache_engine.retrieve(tokens=tokens[:lmcache_cached_tokens],mask=token_mask[:lmcache_cached_tokens],...
)

其含义为:

[0, chunk-aligned local prefix)已完整存在于 Decode GPU,对应 chunks 不读取[chunk-aligned local prefix, external hit end)生成 keys 并加载完整 chunk objects[external hit end, prompt end)LMCache 不提供,由模型计算

Token Database 对普通 retrieve mask 的要求可以表示为:

FFFF FFFF TTTT TTTT

其中 False 形成完整 chunk 对齐的前缀,True 形成连续的待加载后缀。

flowchart LRA["完整本地 chunks<br/>False"] --> B["首个重叠 chunk<br/>True"]B --> C["连续外部命中 chunks<br/>True"]C --> D["外部 miss 区间<br/>不进入 retrieve"]

代码证据:

lmcache/integration/vllm/vllm_v1_adapter.py
lmcache/v1/token_database.py

关键变量:

vllm_cached_tokens
lmcache_cached_tokens
masked_token_count
token_mask

4.7 从远端对象到 MemoryObj

Cache Engine 对 selected keys 执行批量读取。对象通常先按 location 分组:

chunks_by_location = {"LocalCPU": [(key_1, 256, 512),],"RemoteL2": [(key_2, 512, 768),(key_3, 768, 1024),],
}

然后分别调用:

memory_objs = storage_manager.batched_get(keys=keys,location=location,
)
flowchart TDK["Selected Keys"] --> G["按 Location 分组"]G --> L1["L1 batched_get"]G --> L2["L2 / Prefetched Objects"]G --> O["其他 Location"]L1 --> M["按 token range 重排"]L2 --> MO --> MM --> D["Deserialize / GPU Transfer"]

在 MP 模式中,LOOKUP 已经把命中的 L2 objects 预取到 L1。RETRIEVE 从 L1 取得对应 MemoryObj,并在完成 GPU 写入后释放 read lock。

配置 serde 时,读取路径包含反序列化:

flowchart LRR["Remote Object"] --> A["L2 Adapter"]A --> S["Serialized Buffer"]S --> D["Deserializer"]D --> M["KV-shaped MemoryObj"]D -->|"Quantized"| Q["反量化"]D -->|"Compressed"| C["解压"]D -->|"Encrypted"| E["解密 / 校验"]

概念代码如下:

serialized_obj = await adapter.load(key)kv_obj = storage_manager.allocate(shapes=layout.shapes,dtypes=layout.dtypes,fmt=layout.kv_format,
)deserializer.deserialize(src=serialized_obj,dst=kv_obj,key=key,
)

代码与接口证据:

lmcache/v1/cache_engine.py
LMCache serde API
LMCache L2 Adapter API

关键调用:

storage_manager.batched_get(...)
Deserializer.deserialize(...)

4.8 从 MemoryObj scatter 到 Decode paged KV

KV-shaped MemoryObj 恢复后,GPU Connector 根据 Decode 节点当前的 slot mapping 把数据写入新分配的 pages。

gpu_connector.batched_to_gpu(memory_objs=memory_objs,starts=starts,ends=ends,kvcaches=decode_kv_caches,slot_mapping=decode_slot_mapping,skip_prefix_n_tokens=skip_prefix,
)
flowchart LRM["MemoryObj<br/>token 256–511"] --> SM["Decode slot_mapping"]SM --> D0["Decode block 54<br/>token 256–271"]SM --> D1["Decode block 6<br/>token 272–287"]SM --> D2["Decode block 73<br/>token 288–303"]SM --> DN["后续 Decode blocks"]

Prefill 与 Decode 的地址转换可概括为:

flowchart LRP["Prefill Physical Pages"] -->|"gather"| O["Content-addressed Chunk Object"]O -->|"store / network / load"| M["MemoryObj"]M -->|"Decode slot_mapping"| D["Decode Physical Pages"]

稳定关系来自:

CacheEngineKey
+ token range
+ Decode slot_mapping

代码证据:

lmcache/v1/cache_engine.py
lmcache/v1/gpu_connector/gpu_connectors.py

关键调用:

batched_to_gpu(...)
multi_layer_kv_transfer(...)
slot_mapping[start:end]

五、Page 与 Chunk 边界不一致时,系统如何处理重叠

vLLM page size 与 LMCache chunk size通常不同。常见配置中:

vLLM block size    = 16 tokens
LMCache chunk size = 256 tokens

一个完整 LMCache chunk 对应:

\[\frac{256}{16}=16 \]

个 vLLM blocks。

当本地命中终点落在 chunk 中间时,远端读取和 GPU 写入会呈现不同的数据量。

假设:

Decode 本地已有 KV = token 0–287,共 288 tokens
LMCache 连续命中  = token 0–511,共 512 tokens

5.1 调度视角:缺失 224 tokens

Decode 真正缺失:

token 288–511

数量为:

\[512-288=224\text{ tokens} \]

5.2 存储视角:读取完整 256-token chunk

LMCache chunks 为:

chunk 0:token   0–255
chunk 1:token 256–511

Adapter 计算完整本地 chunk 前缀:

\[\left\lfloor \frac{288}{256} \right\rfloor \times 256 = 256 \]

因此:

chunk 0:跳过
chunk 1:读取

远端对象读取量为 256 tokens。

flowchart LRC0["chunk 0<br/>0–255<br/>本地完整命中"] --> X["跳过"]C1["chunk 1<br/>256–511<br/>完整对象"] --> R["远端读取 256 tokens"]

5.3 GPU 写入视角:跳过重叠的 32 tokens

chunk 1 中:

token 256–287:Decode 已有,32 tokens
token 288–511:Decode 缺失,224 tokens

GPU Connector 计算:

skip_prefix_n_tokens = min(chunk_length,local_cached_tokens - chunk_start,
)

代入:

chunk_length        = 256
local_cached_tokens = 288
chunk_start         = 256

得到:

\[\text{skip\_prefix\_n\_tokens} = \min(256, 288-256) = 32 \]

因此,GPU 实际写入 224 tokens。

flowchart LRR["读取 chunk 1<br/>256 tokens"] --> S["跳过前 32 tokens"]S --> W["写入 token 288–511<br/>224 tokens"]W --> D["Decode Paged KV"]

三种统计口径如下:

统计层次 token 数
请求实际缺失 224
远端对象读取 256
GPU 新写入 224

该行为体现了三种并存的粒度:

  • 调度器按 token 前缀计算缺失范围;
  • 外部存储按完整 chunk object 读取;
  • GPU Connector 在 chunk 内按 token offset 跳过重叠数据。

代码证据:

lmcache/integration/vllm/vllm_v1_adapter.py
lmcache/v1/gpu_connector/gpu_connectors.py

关键变量:

masked_token_count
skip_prefix_n_tokens

六、Layerwise Decode:让 KV 加载与模型执行形成流水

普通加载模式需要先恢复所有相关 layers 的 KV,再开始模型 forward。Layerwise 模式把对象沿 layer 维度拆开,使前面 layers 可以提前就绪。

sequenceDiagramparticipant L2 as L2 Storageparticipant L1 as L1 / MemoryObjparticipant GPU as Decode GPUparticipant M as Model ForwardL2->>L1: 加载 Layer 0 KVL1->>GPU: 写入 Layer 0 Paged KVGPU-->>M: Layer 0 Readypar 执行 Layer 0M->>M: Attention Layer 0and 加载 Layer 1L2->>L1: 加载 Layer 1 KVL1->>GPU: 写入 Layer 1 Paged KVendGPU-->>M: Layer 1 Readypar 执行 Layer 1M->>M: Attention Layer 1and 加载 Layer 2L2->>L1: 加载 Layer 2 KVL1->>GPU: 写入 Layer 2 Paged KVend

LMCache Adapter 通过:

retrieve_layer(...)

建立 layerwise retriever。vLLM 在每个 attention layer 使用 KV 前调用:

wait_for_layer_load(layer_name)

该方法推进加载 generator,等待当前层对应的 KV 已写入 paged KV。

Layerwise lookup 同时需要验证一个 token chunk 的必需 layer objects 均可用。任何一层缺失都会使该 chunk 无法形成完整的 KV 状态,因此连续 prefix 会截止在前一个完整 chunk。

实际流水效果取决于:

  • L2 backend 是否支持并发 load;
  • CPU/GPU copy stream 配置;
  • serde 解码并行度;
  • layer object 大小;
  • CUDA Graph 模式;
  • 预取深度。

代码层面可以确认的是,vLLM 和 LMCache 已提供逐层保存、加载和等待 hook。

代码证据:

vllm/distributed/kv_transfer/kv_connector/v1/lmcache_connector.py
lmcache/integration/vllm/vllm_v1_adapter.py
lmcache/v1/cache_engine.py

关键接口:

retrieve_layer(...)
wait_for_layer_load(...)
save_kv_layer(...)

七、存储与读取的对称性:格式可逆,请求范围按需选择

存储路径和加载路径在数据表示上形成一组反向转换:

flowchart LRA["Prefill Paged KV"] --> B["MemoryObj"]B --> C["Serialized Payload"]C --> D["Remote Object"]
flowchart RLD["Remote Object"] --> C["Serialized Payload"]C --> B["MemoryObj"]B --> A["Decode Paged KV"]

使用无损 serde 时,加载结果应恢复与源对象等价的 KV 内容;使用有损量化或专用 KV 编码时,数值误差由 codec 定义。

请求范围由当前 Decode 请求动态决定。

假设 Prefill 保存:

chunk 0
chunk 1
chunk 2
chunk 3

后续请求只匹配到 chunk 2,同时 Decode GPU 已经拥有 chunk 0:

flowchart LRsubgraph Stored["远端已保存"]S0["chunk 0"]S1["chunk 1"]S2["chunk 2"]S3["chunk 3"]endsubgraph Local["Decode GPU 已有"]L0["chunk 0"]endsubgraph Loaded["本次加载"]R1["chunk 1"]R2["chunk 2"]endS0 -. "本地命中" .-> L0S1 --> R1S2 --> R2S3 -. "当前请求未使用" .-> U["保留在远端"]

对应行为可以归纳为:

层次 行为
请求级 根据当前 prompt、本地命中和外部命中选择加载范围
Chunk 级 被选中的普通对象通常完整读取
GPU 写入级 可通过 skip_prefix_n_tokens 跳过 chunk 内重叠部分
数据格式级 Serializer 与 Deserializer 构成正反向转换
物理地址级 Prefill 和 Decode 分别使用自己的 slot mapping

因此,Prefill 写入的对象集合可以被多个后续请求部分复用;每次 Decode 加载的对象集合由该请求当时的缓存状态决定。


八、KV Connector 与存储数据面的边界

KV Connector、GPU Connector、serde 和 L2 adapter 分别位于不同边界。

8.1 KV Connector 处理请求和调度语义

本地命中多少 token
外部命中多少 token
哪些 token 需要分配 GPU blocks
何时启动加载
何时等待某层
何时允许回收 pages

8.2 GPU Connector 处理内存布局

vLLM paged KV⇅
LMCache contiguous MemoryObj

其核心依据为:

KV tensor pointers
slot mapping
block size
token range
layout
transfer direction

8.3 Serde 处理对象内部表示

KV-shaped MemoryObj⇅
quantized / compressed / encrypted payload

8.4 L2 Adapter 处理网络和后端协议

ObjectKey + Payload⇅
Redis / RDMA / NIXL / File / Object Store

完整职责链如下:

flowchart LRS["vLLM Scheduler"] --> KC["KV Connector"]KC --> GC["GPU Connector"]GC --> L1["MemoryObj / L1"]L1 --> SE["Serde"]SE --> AD["L2 Adapter"]AD --> RS["Remote Storage"]

这套边界也决定智能网卡卸载的合理位置:

  • KV Connector 继续负责命中语义、token range、slot mapping 和生命周期;
  • 智能网卡可以承接 serde、网络传输、checksum、远端对象读取和 DMA;
  • GPU Connector 负责最终的 paged KV scatter,或者与支持 GPU direct access 的网卡数据面协作完成写入。

九、代码证据索引

下表汇总本文主要结论及其实现依据。

结论 代码或接口依据
Decode 先查询本地 GPU prefix,再查询外部 Connector vllm/v1/core/sched/scheduler.pyget_computed_blocks_for_connector()get_num_new_matched_tokens() 的调用顺序
Connector 返回本地命中之后的外部新增命中 KVConnectorBase_V1.get_num_new_matched_tokens() 接口定义
LMCache 根据 token 序列生成 chunk keys lmcache/v1/token_database.pyprocess_tokens()_prefix_hash()
Chunk key 使用链式 prefix hash _prefix_hash() 将上一 chunk hash 纳入下一 chunk key
Prefill 保存前先把 paged KV gather 成 MemoryObj LMCacheEngine.store()batched_from_gpu() 先于 batched_put()
Gather/scatter 使用当前实例的 slot mapping VLLMPagedMemGPUConnectorV2multi_layer_kv_transfer()
L1 写入完成后由 StoreController 异步推送 L2 LMCache MP STORE flow 与 StoreController
L1 miss 由 PrefetchController 查询 L2 LMCache MP LOOKUP / PREFETCH flow
外部命中按 chunk keys 扫描 storage_manager.batched_contains(keys, ...)
普通 prefix 只扩展到第一个 miss 前 lookup/retrieve 中的连续 hit_chunks 与失败终止逻辑
Decode 只读取本地命中与外部命中之间的 chunks Adapter 根据 vllm_cached_tokenslmcache_cached_tokens 构造 mask
Mask 以完整 chunk 前缀对齐 masked_token_count = floor(vllm_cached_tokens / chunk_size) × chunk_size
首个重叠 chunk 可以完整读取、部分写入 GPU Connector 的 skip_prefix_n_tokens
远端对象经过 serde 转换 Serializer.serialize()Deserializer.deserialize()
Layerwise 模式支持逐层保存和加载 save_kv_layer()retrieve_layer()wait_for_layer_load()
Prefill 与 Decode 可使用不同 block IDs 两侧分别通过自身 slot_mapping 执行 gather/scatter

十、总结:存储分离 KV Cache 的典型行为

Prefill 路径可以概括为:

flowchart LRA["模型 Prefill"] --> B["vLLM Paged KV"]B --> C["Token Chunk + CacheEngineKey"]C --> D["连续 MemoryObj"]D --> E["L1"]E --> F["Serialize"]F --> G["L2 Adapter"]G --> H["Remote Object"]

Decode 路径可以概括为:

flowchart LRA["Decode Request"] --> B["查询 GPU Local Prefix"]B --> C["扫描 LMCache Chunk Keys"]C --> D["查询 L1 / L2"]D --> E["确定连续外部命中"]E --> F["选择缺失 Chunks"]F --> G["Load + Deserialize"]G --> H["MemoryObj"]H --> I["按 Decode slot_mapping 写回"]

整个系统同时存在四种关键粒度:

GPU 内存按 page/block 管理;
外部索引按 token chunk 查询;
网络和后端按 object 传输;
GPU 恢复按 slot mapping 写入。

Prefill 保存过程将瞬时的 GPU physical pages 转换为稳定的内容寻址对象。Decode 查询过程先利用本地 GPU prefix,再沿 L1 和 L2 扩展外部连续命中。实际加载集合由本地命中终点和外部命中终点共同确定。

KV Connector 贯穿这条路径,负责调度语义、metadata 和生命周期;GPU Connector 完成 paged/contiguous 布局转换;Token Database 定义 chunk 和对象 key;serde 决定网络数据表示;L2 adapter 将对象映射到具体远端后端。

最终形成的典型行为是:

Prefill 将 paged KV 整理成内容寻址 chunks,并通过 L1/L2 写入远端;Decode 先扫描本地 page cache,再扫描外部 chunk index,只恢复当前请求连续命中范围内、本地尚未具备的对象,并按当前 Decode 实例的 slot mapping 写回 paged KV。