跳转至

Mooncake Store Design

导言

Mooncake Store 的 KV Cache 管理很像 PagedAttention:两者都采用 切块、间接寻址、按块复用与淘汰。但它们解决的不是同一层问题。PagedAttention 管理单个推理实例内的 GPU KV Block;Mooncake Store 管理跨请求、跨实例、跨节点的 DRAM 与 SSD 副本。

理解二者关系的关键,是先区分 页表KV 数据页:页表记录当前请求的逻辑块对应哪个 GPU 物理块,真正跨显存、内存和 SSD 分层迁移的是 K/V 张量数据,而不是页表本身。

但只理解“Master 管元数据、Client 传数据”仍然不够。本文沿 Mooncake 固定版本 bfca1ce2af8419c50dc8d464820a95d97d43c930 追到 store_c.cppreal_client.cppclient_service.cppmaster_service.cpptransfer_task.cpp,解释每个设计判断究竟由哪段代码实现。

一句话结论:Mooncake Store 是一个面向不可变对象的双平面分布式缓存:Master 分配空间并原子发布副本元数据,Client 贡献注册内存或 SSD 容量并通过 Transfer Engine 直传字节;用于 KV Cache 时,外部命中仍需装入 GPU Block,再更新 PagedAttention Block Table。

先区分页表与数据页

讨论 KV Cache 的“页”时,最容易把三个不同对象混在一起:

  1. 逻辑 KV Block:请求按 Token 顺序切分出的第 0、1、2 个块。
  2. GPU 物理 Block:K/V Tensor 在本次运行中实际占用的显存块。
  3. Block Table:保存“逻辑块序号 → GPU 物理 Block ID”的映射。

设请求的逻辑块序列为 \(L_0,L_1,L_2,\ldots\),PagedAttention 为它们分配 GPU 物理块 \(P_0,P_1,P_2,\ldots\),页表保存的是:

\[ L_i \rightarrow P_i \]

严格来说,页表不是 KV Cache 数据本身。vLLM Scheduler 在 CPU 侧保存请求、引用计数、Block Hash 与所有权等调度元数据;Attention Kernel 使用的 Block Table 则位于设备侧,内容是 GPU Block ID。SSD 不保存这张临时页表,页表也不会直接指向 SSD 文件。

同一段前缀下一次被其他请求命中时,内容身份仍可由相同的 Block Hash 表示,但本次分配到的 GPU Block ID 可能完全不同。新的页表只需重新建立逻辑块到新物理块的映射。

不是透明缺页

这套机制与操作系统虚拟内存有相似直觉,但 Mooncake 与 vLLM 没有让 Attention Kernel 透明访问 SSD 的硬件缺页机制。外部缓存命中后,调度器和 Connector 必须显式完成查询、GPU Block 分配、数据传输和同步,之后才能执行 Attention。

两级管理全景

下图把两套映射放在一起:上层是 PagedAttention 的 GPU 执行地址,下层是 Mooncake Store 的内容身份与副本位置。绿色路径表示真正的 KV 数据传输,蓝色与紫色路径表示地址和元数据查询。

PagedAttention 与 Mooncake Store 的两级 KV Cache 管理

根据本次会话内容整理的自绘示意图:页表只映射 GPU Block;Mooncake 使用 Block Hash 与副本元数据定位 DRAM/SSD 中的 KV 数据。

图中最重要的是两组映射彼此独立:

PagedAttention:逻辑块序号 → 本次运行的 GPU Block ID
Mooncake Store:Block Hash / Key → MEMORY 或 LOCAL_DISK 副本

二者通过 MooncakeStoreConnector 衔接。Connector 把 GPU 中已经计算好的 KV Block 保存到外部 Store,也把外部命中的 KV Block 装载到 vLLM 新分配的 GPU Block。

对比项 PagedAttention Mooncake Store
管理范围 单个推理实例的 GPU 显存 跨请求、实例、节点的 DRAM/SSD 缓存池
逻辑标识 请求的第几个逻辑 Token Block Block Hash / Mooncake Key
物理位置 GPU KV Tensor 的 Block ID MEMORY、LOCAL_DISK 等副本位置
映射结构 GPU Block Table Master 中的对象与副本元数据
直接服务对象 Attention Kernel KV Connector 与 Transfer Engine
主要目标 减少显存碎片并复用 GPU Block 扩大可复用 KV Cache 的容量与范围

代码里的系统边界

官方设计文档把 Mooncake Store 定义为面向大语言模型 KV Cache 的分布式键值存储,Key 由内容派生,Value 写入后不可变。这个定义在代码里展开成两条彼此分离的路径:控制面决定对象放置、状态与生命周期,数据面负责对象字节的实际移动

Mooncake Store 控制面与数据面架构

Mooncake Store 架构图:Master 不经过对象数据,双角色 Client 既可调用对象 API,也可挂载 Segment 向全局缓存池贡献容量。

控制面与数据面

  1. Store Master 是控制面。 MasterService 保存对象元数据、选择 Segment、分配副本位置、管理 Lease,并把副本从 PROCESSING 发布为 COMPLETE。正常 Put/Get 的对象字节不进入 Master。
  2. Store Client 同时是调用方和存储节点。 同一个 Client 可以向 Master 发起 Put/Get,也可以调用 MountSegment 把本进程内存注册为其他 Client 可访问的存储空间。
  3. Transfer Engine 是数据面。 Client 根据 Master 返回的 endpoint、远端基址和 offset,提交 RDMA、TCP 或本地 memcpy 请求,直接在源 Slice 与目标 Segment 之间搬运字节。
  4. Transfer Engine metadata service 不是 Store Master。 前者维护传输端点和连接所需的元信息,后者维护对象、空间、副本与租约。两者可以同时出现在部署参数中,但职责不能合并理解。

Master 不转发数据

PutStart 返回的是“可以写到哪里”,GetReplicaList 返回的是“可以从哪里读”。之后的 TransferWriteTransferRead 都由 Client 侧执行。把 Master 画在数据箭头上,会误判它的带宽瓶颈、故障影响和扩容方式。

两种内存不是一回事

RealClient::setup_internal 接收两个容易混淆的容量参数:

参数 代码动作 是否贡献 Store 容量 主要用途
local_buffer_size 创建 ClientBufferAllocator,并把本地缓冲区注册到 Transfer Engine put 的 gather/staging、get 的本地目标缓冲区
global_segment_size 分块分配内存,逐块调用 Client::MountSegment 作为远端 Client 可分配、可读写的 MEMORY Segment

由此可以组合出三种角色:

  • 双角色节点:两个值都大于 0,既能调用 Put/Get,也向全局池贡献内存。
  • 纯调用节点global_segment_size = 0,只消费其他节点挂载的容量。
  • 纯存储节点local_buffer_size = 0,按设计文档语义只提供 Segment,不执行依赖本地缓冲池的 Put/Get。

Store 还提供三种接入形态:直接链接 Clientembedded 模式;应用连接本机共享内存代理的 dummy-real 模式;以及独立 Store 服务进程。三者改变进程边界,却不改变 Master 控制面与 Client 数据面的分工。

核心类如何分工

从 C API 入口向下看,Mooncake Store 不是一个承担所有职责的“大 Store 类”,而是 API 适配、Client 编排、传输执行和 Master 元数据四组对象的组合。

Mooncake Store 核心类图

核心类图:实线表示持有或协作,虚线表示跨进程 RPC。MasterClient 是代理,不是 Master 本体。
类或结构 所在代码 责任
StoreHandle mooncake-store/src/store_c.cpp C ABI 句柄,持有 shared_ptr<RealClient>
RealClient include/real_client.hsrc/real_client.cpp 把连续 Buffer 转为 Slice,管理本地缓冲池,兼容 real/dummy-real 两种接入
Client include/client_service.hsrc/client_service.cpp 编排对象 API、Master RPC、Transfer Engine、Segment 与存储后端
MasterClient include/master_client.hsrc/master_client.cpp MasterService 的 RPC 代理,封装 BRPC 调用;Client::ConnectToMaster 负责地址连接与 HA leader 发现
TransferSubmitter include/transfer_task.hsrc/transfer_task.cpp 把 Replica 读写转换为 local memcpy、Transfer Engine、SPDK 或文件任务
MasterService include/master_service.hsrc/master_service.cpp 管理 Segment、分配策略、对象元数据、副本状态、Lease、配额与淘汰
ObjectMetadata include/master_service.h 对象账本:不可变尺寸和类型、writer、Lease、Pin、Replica 列表
Replica include/replica.h 一个物理副本的位置、类型、引用计数与生命周期状态

MasterService 按租户维护 TenantState,对象元数据被分散在多个 shard 中;processing_keys 记录尚未完成发布的 Key。每个 ObjectMetadata 再持有多个 Replica。Replica 不只是一个地址,它还包含 MEMORY、NoF、LOCAL_DISK、DFS 等位置描述,以及 PROCESSINGCOMPLETEFAILEDREMOVED 等状态。

源码导航:C API 与 StoreHandleRealClientClientMasterServiceObjectMetadataReplica

一致性的真正载体

Mooncake 的强一致读不是由“Master 代理所有副本写入”实现的,而是由 不可变 Value、写入期间不可读、成功后原子发布 共同实现。GetReplicaList 只返回满足可读条件的 COMPLETE 副本。

初始化如何建立资源池

生命周期先由 mooncake_store_create 创建 C ABI 句柄,再由 mooncake_store_setup 初始化内部 Client:

mooncake_store_create
  → RealClient::create
  → StoreHandle{shared_ptr<RealClient>}
mooncake_store_setup
  → RealClient::setup_real
  → RealClient::setup_internal
  → Client::Create
      → ConnectToMaster
      → 初始化 Transfer Engine
      → InitTransferSubmitter
  → 注册 local buffer(若 local_buffer_size > 0)
  → MountSegment(若 global_segment_size > 0)

这里有两个先后依赖:Client 必须先连接 Master,才能取得 Store 配置并挂载 Segment;本地内存必须先向 Transfer Engine 注册,远端 Client 才能用 endpoint 与地址执行零拷贝传输。 Client::MountSegmentAndGetId 会先注册可远端访问的内存,再构造 Segment,最后通过 MasterClient::MountSegment 把容量加入 Master 的逻辑池。

对应实现:RealClient::setup_internalClient::CreateClient::MountSegmentAndGetId

“零拷贝”需要加边界条件:当调用方直接使用已注册内存并走 RDMA 路径时,可以避免中间复制;便利接口 RealClient::put 仍可能先把输入 gather 到 ClientBufferAllocator。因此更准确的说法是 数据路径支持零拷贝,而不是每个 API 调用天然零拷贝

Put 为什么分成两阶段

Put 的核心不是一次 RPC,而是 控制面预留 → 数据面写入 → 控制面发布。这类似数据库的 prepare/commit,但提交对象是不可变副本元数据。

Mooncake Store Put 两阶段流程图

Put 流程与副本状态:`PROCESSING` 阶段对 Get 不可见;满足提交条件后由 `PutEnd` 统一变为 `COMPLETE`。

第一阶段:预留位置

Client::Put 先计算 Slice 长度和可选 checksum,再调用 MasterClient::PutStart。Master 侧完成四件事:

  1. 校验不可变约束。 同一 Key 已存在且尺寸或属性不兼容时,拒绝覆盖。
  2. 检查配额与分配策略。 Allocation Strategy 从不同 Segment 中选择副本位置,避免把多个内存副本放到同一个 Segment。
  3. 写入占位元数据。 创建 ObjectMetadata,把 Replica 标记为 PROCESSING,并把 Key 加入 processing_keys
  4. 返回物理描述。 MEMORY 副本包含 Transfer Engine endpoint、远端地址与长度;磁盘副本包含对应的存储描述。

内存副本的“多副本”是 分配层面的 best-effort:若配置要求多个副本但容量不足,只要至少成功分配一个,Put 仍可能继续。这个语义不等于传输阶段可以随意丢副本;在普通模式下,已经分配的副本都必须成功写完,混合 MEMORY + NoF 的弹性模式才有不同的提交判定。

第二阶段:直传并发布

Client 遍历已分配副本。MEMORY/NoF 路径进入 TransferWrite,LOCAL_DISK/DFS 路径进入对应存储后端。TransferSubmitter::submit 再根据位置类型路由:

MEMORY 且本地  → LOCAL_MEMCPY
MEMORY 且远端  → TRANSFER_ENGINE
NoF            → SPDK / NoF 任务
LOCAL_DISK/DFS → 文件或分布式存储任务

远端 MEMORY 路径会分配 BatchID、按 endpoint 打开远端 Segment、构造本地源地址与远端基址加 offset 的 TransferRequest,提交后等待完成。全部结果交给 DetermineFinalizeDecision

  • 满足提交条件:调用 PutEnd。Master 只允许原 writer 提交,把对应 Replica 从 PROCESSING 改为 COMPLETE,移除 processing 标记,授予 Lease,并按配置触发 SSD Offload。
  • 不满足提交条件:调用 PutRevoke,撤销未发布对象并释放预留空间。

因此,读取者不会看到“只有一半字节写好”或“只有部分已分配副本写好”的对象。可见性发生在 PutEnd,不是第一次 DMA 完成时。

对应实现:Client::PutMasterService::PutStartMasterService::PutEndTransferSubmitter::submit

Get 为什么绕开 Master

Get 被拆成 Query 和 TransferRead 两步。第一步拿可读副本清单与 Lease,第二步才搬运对象字节。

Mooncake Store Get 时序图

Get 时序图:蓝色是控制消息,绿色是对象字节。完成副本选择后,Master 已离开数据路径。

RealClient::get_into 先为目标 Buffer 建立 ranged-read metadata,再调用 Client::QueryMasterService::GetReplicaList 过滤掉不可读副本,只返回有效的 COMPLETE Replica,同时更新对象或 Group 的读 Lease,并返回 TTL、checksum 等信息。

Client 随后按实现中的固定优先级选择副本:本地 MEMORY、任意 MEMORY、本地 NoF、任意 NoF、LOCAL_DISK、DFS、DISK。Client::Get 再执行 hot-cache、MEMORY/NoF、DFS 等具体读路径;远端内存使用与 Put 对称的 TransferRead。传输结束后还会执行两个检查:

  1. 数据完整性。 启用 checksum 时,目标内容必须与元数据一致。
  2. 租约有效性。 如果传输期间 Lease 已过期,这次结果不能被无条件视为有效。当前单次 Client::Get 会返回失败;上层可以重新 Query,再选择其他 COMPLETE 副本重试。

这也解释了 Master 的扩展边界:对象带宽随 Client 和网络扩展,Master 主要承受元数据 QPS、空间分配与 Lease 管理压力。 默认单 Master 仍是控制面单点;高可用模式通过 etcd 选主和 Master 发现恢复控制面,但不会把 Master 变成数据代理。

对应实现:Client::QueryClient::GetMasterService::GetReplicaListTransferSubmitter::submitTransfer

特性如何落到代码

设计特性 实现机制 关键代码 使用边界
对象语义 内容派生 Key、不可变 Value、Put/Get/Remove/Query ClientObjectMetadata 覆盖写应换 Key 或先 Remove
多副本 Allocation Strategy 选择不同 Segment,Replica 列表记录位置 AllocateAndInsertMetadataReplica 分配数量 best-effort,实际持久性取决于成功副本与介质
强一致读 PROCESSING 不可读,PutEnd 原子发布 COMPLETE PutStartPutEndGetReplicaList 强调单对象发布,不代表跨 Key 事务
高带宽 Slice 并行、Transfer Batch、Client-to-Client 数据路径 TransferSubmitterTransferEngine 仍受注册内存、链路和目标介质限制
零拷贝能力 注册内存与 RDMA 直接访问 RegisterLocalMemorysubmitTransfer RealClient::put 的便利路径可能有 staging copy
动态扩缩容 Client 挂载或卸载 Segment,Master 更新逻辑池 MountSegmentUnmountSegment 缩容需处理活跃 Replica 与 Lease
故障容忍 多个 COMPLETE Replica、失败副本过滤、HA Master IsReplicaReadable、etcd leader coordinator 至少要有可读副本;默认单 Master 并非 HA
DRAM/SSD 分层 MEMORY Replica、LOCAL_DISK Replica、Offload 与独立淘汰 FileStorage、后台 eviction/offload SSD 副本可能立即生成,不必然只存冷数据

不要把宣传词当成无条件保证

“任意节点失败仍可用”“零拷贝”“配置 N 副本就一定得到 N 副本”都需要部署前提。源码真正保证的是:只发布满足提交条件的对象,只向读者返回有效完整副本,并为注册内存提供绕过 Master 的传输路径。

典型操作执行路径 SOP

下面的 SOP 不是 API 使用教程,而是 出现问题时按源码顺序定位责任组件 的执行清单。所有路径均对应固定 revision bfca1ce2

SOP 1:Setup 与挂载容量

入口mooncake_store_create(),随后调用 mooncake_store_setup(...)

  1. store_c.cpp 确认 mooncake_store_create 已生成持有 RealClientStoreHandlesetup 参数随后进入 RealClient::setup_real
  2. RealClient::setup_internal 确认 Client::Create 成功,Master 地址、Transfer Engine metadata 地址与协议一致。
  3. 检查 ConnectToMaster 是否取得 Store 配置;HA 部署还要确认 leader discovery 结果。
  4. local_buffer_size > 0 时,确认本地缓冲池分配和 RegisterLocalMemory(..., remote_accessible=false) 成功。
  5. global_segment_size > 0 时,确认每块内存先注册为可远端访问,再经 MountSegmentAndGetId 出现在 Master 的 Segment 表中。

成功标志:Client 拥有有效 ID;需要的 Segment 已挂载;TransferSubmitter 已初始化。常见断点:Master 不可达、Transfer Engine endpoint 不可解析、内存注册失败、Segment 名称或容量冲突。

SOP 2:Put 一个对象

入口mooncake_store_put(handle, key, value, length, ...)

store_c.cpp::mooncake_store_put
  → RealClient::put
  → RealClient::put_internal
      → ClientBufferAllocator::allocate
      → gather_maybe_device_to_host
      → split_into_slices
  → Client::Put
      → MasterClient::PutStart
      → MasterService::PutStart
      → AllocateAndInsertMetadata [Replica=PROCESSING]
      → Client::TransferWrite
      → TransferSubmitter::submit
      → DetermineFinalizeDecision
      → MasterClient::PutEnd 或 PutRevoke

排障时按三个检查点推进:

  1. PutStart 前失败:优先看 Key、长度、租户与参数校验。
  2. PutStart 后传输失败:检查返回 Replica 的 endpoint/address、内存注册、Batch 状态与具体 transport 错误。
  3. 数据写完但对象查不到:检查 DetermineFinalizeDecision 是否选择 PutEnd、writer ID 是否匹配、Replica 是否最终变为 COMPLETE

成功标志:Master 已移除 processing_keys 中的 Key,至少一个满足策略的 Replica 为 COMPLETE,Query 可返回该对象。不要只以某个 Transfer Batch 完成为 Put 成功。

SOP 3:Get 到已有 Buffer

入口mooncake_store_get_into(handle, key, buffer, length, ...)

store_c.cpp::mooncake_store_get_into
  → RealClient::get_into
  → Client::Query
      → MasterClient::GetReplicaList
      → MasterService::GetReplicaList [只返回可读 COMPLETE 副本]
  → RealClient::SelectBestReplica
  → Client::Get
      → TransferRead / DFS Read / hot cache
      → checksum 校验
      → Lease deadline 校验
  1. Query Miss:先确认 Key 是否完全一致,再检查对象是否仍为 PROCESSING、Lease/淘汰是否移除全部副本。
  2. 有 Replica 但读失败:逐个核对副本类型、endpoint 可达性、refcount、Batch 完成状态和目标 Buffer 长度。
  3. 读完仍失败:检查 checksum 与本地计算是否一致,以及 Lease 是否在慢传输期间过期。

成功标志:目标 Slice 全部填充,checksum 与 Lease 检查通过。Master 只参与 Query,不应在网络抓包中承载 Value 大流量。

SOP 4:Remove 一个对象

入口mooncake_store_remove(handle, key)

mooncake_store_remove
  → RealClient::remove
  → Client::Remove
  → MasterClient::Remove
  → MasterService::Remove
      → 检查 Lease、processing 与 replication task
      → 移除对象元数据
      → Replica 析构/回收释放物理空间

默认 Remove 会拒绝仍受 Lease 保护或尚未完成写入/复制的对象;force 也不是无条件跳过所有安全检查。成功标志是元数据查询不再返回对象,并且相应 Segment 或存储后端空间最终被回收。若 Remove 被拒绝,应先看 Lease 和 Replica 状态,而不是直接怀疑文件删除失败。

SOP 5:Batch Put/Get

Batch API 最终仍复用单对象的分配、传输和发布语义,只是把请求组织成批次以减少 RPC 与提交开销。排障时先拆出第一个失败 Key,按 SOP 2 或 SOP 3 追踪;不要把“Batch 返回部分成功”误读为单个对象允许部分发布。

回到两级缓存:PagedAttention 管理显存

放入依据

一段 KV Cache 只要要参与当前或下一次 Attention 计算,就必须位于 GPU 显存中。vLLM Scheduler 为请求分配 GPU Block,把物理 Block ID 写入 Block Table;Attention Kernel 再根据表中的 ID 读取对应 K/V。

GPU 中还可能暂时保留已经完成计算、具有 Prefix Cache Hash、但当前没有活动请求引用的 KV Block。这些块能够继续提供本地前缀命中,但也已经是可回收候选。

何时回收

GPU Block 是否可回收首先取决于引用计数:

  • ref_cnt > 0:仍被活动请求使用,不能复用。
  • ref_cnt == 0:不再被请求引用,可以进入 Free Block Queue。

新请求需要更多 GPU Block 时,vLLM 从 Free Block Queue 取出候选块并复用物理空间。带 Prefix Cache Hash 的缓存块采用 FIFO 复用顺序形成近似 LRU 效果;没有缓存身份的块倾向于 LIFO 复用,以改善局部性。

这里的“GPU 淘汰”不等于一定执行 GPU → DRAM 写回

  • 如果 Mooncake 已经保存了外部副本,GPU 可以直接复用该物理块,未来仍能从 Store 恢复数据。
  • 如果没有外部副本,旧数据会消失;后续相同前缀只能重新计算。

Mooncake 管理外部副本

Mooncake Store 不管理 vLLM 当前请求的 Block Table。它看到的是一个由 Block Hash 或 Key 标识的对象,以及这个对象对应的一个或多个副本:

Block Hash / Key
Master Replica Metadata
MEMORY 副本 / LOCAL_DISK 副本

Master 管理键、位置、副本状态和生命周期,正常 KV 载荷由 Client 与 Transfer Engine 搬运,不经过 Master 中转。

DRAM 放置与淘汰

KV Block 由 Connector 保存到 Mooncake 时,Store 从可用的 MEMORY Segment 中分配空间。这个初始放置主要依据可用 Segment、分配策略和副本配置,不是先预测冷热再决定是否进入 DRAM

对象被读取或查询时,Mooncake 更新其 lease_timeout。后台内存淘汰通常在两种情况下触发:

  1. MEMORY 使用率超过高水位,默认是 90%。
  2. 新对象分配失败,设置内存淘汰标志。

候选对象还必须满足:

  • Lease 已过期;
  • 没有 Hard Pin;
  • 存在完整、可读的 MEMORY 副本;
  • 副本引用计数为 0;
  • 第一轮优先跳过 Soft Pin,必要时第二轮再考虑。

Mooncake 比较对象的 lease_timeout,截止时间越早,表示越久没有访问,再用 nth_element 找到淘汰边界。因此这是对象级 Lease 近似 LRU,不是经典双向链表 LRU。

SSD 下沉与淘汰

只有启用 SSD Offload 后,KV Block 才会形成 LOCAL_DISK 副本。存在两种下沉时机:

  1. 立即下沉PutEnd 成功后,将完整 MEMORY 副本加入 SSD 下沉队列。因此 SSD 中不一定只有冷数据。
  2. 淘汰时下沉:DRAM 准备淘汰对象时,先保留并固定一个 MEMORY 副本,写入 SSD;成功生成 LOCAL_DISK 副本后,再释放 MEMORY。

在淘汰时下沉模式中,如果 Offload Queue 暂时不可用,默认行为是跳过本轮并保留数据;只有显式启用强制淘汰,才会在没有 SSD 副本时直接释放 MEMORY。

SSD 容量达到限制或高水位时,FileStorage 还会执行自己的淘汰:

  • 默认 fifo:优先删除最早创建的 Bucket。
  • 可选 lru:优先删除最久没有读取的 Bucket。
  • 淘汰粒度是 Bucket:一个 Bucket 中的多个 KV 对象会一起删除,而不是精确淘汰单个 GPU 风格的 KV Page。

删除 SSD 数据前,FileStorage 先通知 Master 移除 LOCAL_DISK 副本元数据;通知失败则恢复本地索引。Master 确认后,FileStorage 等待正在进行的读取结束,再删除元数据文件和 Bucket 数据文件。

一次外部命中如何完成

当新请求到达时,Mooncake Store 与 PagedAttention 的协作顺序可以概括为:

  1. 计算内容身份。 按 Token Block 计算 Block Hash。
  2. 检查本地 GPU Cache。 如果已有可用 GPU Block,直接建立或复用 Block Table 映射。
  3. 查询 Mooncake。 本地未命中时,根据 Block Hash 查询 Master 中的副本元数据。
  4. 分配 GPU Block。 外部 DRAM/SSD 命中后,vLLM 仍要先申请新的 GPU 物理块。
  5. 传输 KV 数据。 Connector 与 Transfer Engine 将命中副本写入这些 GPU Block。
  6. 更新 Block Table。 将逻辑块映射到本次分配的 GPU Block ID。
  7. 执行 Attention。 Kernel 此时才能从显存读取 K/V。
新请求
Token Blocks → Block Hash
查询本地 GPU Prefix Cache
  ↓ 未命中
查询 Mooncake MEMORY / LOCAL_DISK
  ↓ 命中
分配新的 GPU Blocks
装载 KV 数据并更新 Block Table
执行 Attention

外部命中仍有成本

Mooncake 命中意味着可以避免对应前缀的重复 Prefill,但并不意味着数据已经位于 Attention 可直接访问的位置。DRAM/SSD 查询、数据传输、GPU Block 分配和同步仍然存在,因此外部命中不是免费的。

为什么两者如此相似

Mooncake Store 与 PagedAttention 都采用四个共同思想:

  1. 固定粒度切块。 不要求为整个请求分配一块连续空间。
  2. 逻辑与物理解耦。 逻辑顺序不要求物理位置连续。
  3. 间接寻址。 先查询映射,再找到真实数据。
  4. 按块复用与淘汰。 用有限容量保留更可能再次使用的数据。

但二者位于不同层级:

  • PagedAttention 是执行期页式管理。 它解决 GPU 内 KV Block 的分配、映射、碎片与复用。
  • Mooncake Store 是跨请求的外部缓存。 它解决 KV Block 如何按内容标识、保存副本、跨节点传输并在 DRAM/SSD 间管理生命周期。

从概念上可以把 Mooncake 看成 PagedAttention 外面的 L2/L3 缓存系统,但这不是说 Mooncake 扩展了同一张页表。它们拥有各自的元数据和淘汰策略,中间通过 Connector 显式搬运数据。

三套策略不要混用

层级 放置内容 放入依据 回收时机 策略
GPU Block Table 逻辑块到 GPU Block ID 的映射 当前调度批次与 GPU 分配结果 请求结束或槽位复用 重新构造,不下沉 SSD
GPU KV 数据页 活动请求与本地 Prefix Cache Scheduler 分配、外部缓存装载 ref_cnt=0 且需要新块 vLLM Free Queue,缓存块近似 LRU
Mooncake DRAM 可跨请求、跨实例复用的 KV 副本 Connector 保存,Store 分配 MEMORY Segment 超过高水位或分配失败 Lease 近似 LRU
Mooncake SSD MEMORY 的下层副本 立即下沉或 DRAM 淘汰时下沉 SSD 超容量或高水位 默认 FIFO,可选 Bucket LRU

最容易出现的误解包括:

  1. 把页表当成 KV 数据。 页表很小,真正占据容量的是各层 K/V Tensor。
  2. 认为页表可以直接指向 SSD。 Attention 必须先看到 GPU Block,外部数据要显式装载。
  3. 认为存在统一的三级 LRU。 GPU、Mooncake DRAM 与 SSD 分别由不同组件管理。
  4. 认为 GPU 回收必然写回。 只有外部副本已经保存,数据才能在未来恢复;否则只能重算。

总结

PagedAttention 与 Mooncake Store 看起来相似,是因为它们都把连续的 KV Cache 切成块,并用间接映射代替固定物理位置。区别在于优化范围:PagedAttention 管理当前计算所需的 GPU 物理块,Mooncake Store 管理暂不在 GPU 中但值得跨请求复用的 DRAM/SSD 副本。

完整的数据生命周期不是一张页表在三级介质间移动,而是:GPU Block Table 持续描述当前执行地址;Connector 根据 Block Hash 查询外部副本;命中后将数据装入新 GPU Block,再重建执行映射。理解这一层次,才能正确区分显存复用、DRAM 淘汰、SSD 下沉和 SSD 自身淘汰。

参考资料

  1. Mooncake 开源仓库,本文涉及的 Mooncake 源码语义固定于 bfca1ce2af8419c50dc8d464820a95d97d43c930
  2. Mooncake Store 设计
  3. Mooncake 内存 near-LRU 定义
  4. Mooncake 内存候选筛选
  5. Mooncake SSD Bucket LRU
  6. vLLM GPU Block Table
  7. vLLM GPU BlockPool
  8. vLLM MooncakeStoreConnector
  9. Mooncake Store C API
  10. RealClient 初始化与 Buffer 适配
  11. Client 对象 API 与传输编排
  12. MasterService 元数据与副本状态
  13. TransferSubmitter 数据任务
  14. Replica 类型与状态

评论