Mooncake 和 HiCache 总览:KV 缓存如何变成系统资源
从 SGLang HiCache、Mooncake Store 和 Transfer Engine 的职责边界出发,说明一段 KV 缓存如何成为可跨节点复用的系统资源。
这组文章的目标不是介绍 Mooncake 有多快,而是回答一个工程问题:一段前缀 KV Cache 怎样从单个工作进程中的 GPU 张量,变成可以逐级备份、查询、传输和跨节点复用的系统资源。
这个问题值得单独拆开,因为远端 KV 缓存很容易被一句“接入 Mooncake”含糊带过。真正影响系统行为的不是一个后端名称,而是推理引擎中的缓存状态、Store 元数据可见性和 Transfer Engine 数据搬运之间的边界。
要把这条链路讲清楚,需要同时看三层:
SGLang HiCache
负责前缀缓存、GPU/主机/存储层状态和调度时机
Mooncake Store
负责对象、切片、副本、租约、元数据和可读性判断
Mooncake Transfer Engine
负责 Segment、注册内存、批量传输和传输方式选择
layer view
KV 缓存从引擎状态变成分布式资源,需要经过三层语义
HiCache 决定缓存什么时候写、什么时候读;Store 决定对象是否存在、是否可读;Transfer Engine 只负责搬运已知地址上的字节。
SGLang HiCache
推理引擎中的缓存策略
Mooncake Store
对象元数据与可见性
Mooncake Transfer Engine
远端内存传输
这三层经常被一句“把 KV 缓存放到 Mooncake”混在一起。实际阅读源码时必须拆开,否则很容易把 Store 当作普通 KV 服务,或者把 Transfer Engine 当成完整缓存系统。
源码版本与三层边界
本文是 Mooncake / HiCache 系列的入口篇,基于 SGLang v0.5.14 的 HiCache 相关源码路径,以及 kvcache-ai/Mooncake 稳定 release tag v0.3.11.post1(commit e9c61075720039bcfc5fffd19f847608402be3d0)的 Store / Transfer Engine 源码路径阅读。下文重点放在可复核的源码路径、接口职责和系统边界。
HiCache 是 SGLang 推理引擎内部的分层缓存机制。它知道请求、前缀、基数树节点、token 索引、GPU KV 池和主机 KV 池,也知道调度器什么时候应该预取或回载。它的核心边界在 SGLang 代码里,SGLang v0.5.14 主要入口包括:
python/sglang/srt/mem_cache/hicache_storage.py
python/sglang/srt/mem_cache/unified_radix_cache.py
python/sglang/srt/mem_cache/hiradix_cache.py
python/sglang/srt/mem_cache/storage/mooncake_store/mooncake_store.py
Mooncake Store 是对象和副本的语义层。它不知道 SGLang 的基数树,也不决定某个请求是否能跳过预填充。它看到的是对象键、切片、副本、Segment、状态和租约。Mooncake 侧主要入口是:
mooncake-store/include/client_service.h
mooncake-store/src/client_service.cpp
mooncake-store/src/master_service.cpp
mooncake-store/include/replica.h
mooncake-store/include/types.h
Mooncake Transfer Engine 是数据搬运层。它不理解 KV 缓存,也不理解对象是否可读。它处理的是本地地址、远端 Segment、偏移、长度、读写方向、BatchID 和传输方式。主要入口是:
mooncake-transfer-engine/include/transfer_engine.h
mooncake-transfer-engine/include/transport/transport.h
mooncake-transfer-engine/src/transfer_engine_impl.cpp
mooncake-transfer-engine/src/multi_transport.cpp
本文关注 KV 缓存从推理引擎状态进入 Store 和传输层的系统边界;不覆盖完整基准测试和模型计算内核细节,也不会把 Mooncake 的长期愿景写成当前已经具备的能力。
一段 KV 缓存的生命周期
从 SGLang 角度看,一段可复用前缀会经历几种状态:
GPU KV 池
-> 主机 KV 池
-> HiCacheStorage 后端
-> Mooncake Store 对象
-> Segment 上的 Mooncake 副本
-> Transfer Engine 传输链路
反向读取时顺序也不是直接从 Mooncake 回 GPU:
Mooncake Store 对象
-> 零拷贝读取到主机 KV 池
-> HiCache 回载
-> GPU KV 池
-> 注意力内核使用设备 KV 槽位
call path
一页远端 KV 从写出到读回的完整路径
Mooncake 只把数据读回主机内存池;要让注意力计算真正使用这些数据,还要由 HiCache 回载到 GPU。
- 1
GPU KV 池
HiCache
write_backup(node)
先把设备 KV 槽位中的数据备份到主机端 KV Cache 页
- 2
HiCache
Mooncake Store
batch_set(page key, buffer pointer)
HiCacheStorage 后端把 page key 和主机端缓冲区信息传给 Store
- 3
Mooncake Store
Transfer Engine
写入对象切片
Store 根据副本的 Segment、偏移和长度生成传输请求
- 4
Mooncake Store
主机 KV 池
batch_get_into(...)
远端对象读回时先落到主机 KV 缓存页
- 5
主机 KV 池
GPU KV 池
load_back(best_match_node)
HiCache 分配设备槽位后,前缀才真正回到 L1
这条路径说明两个事实。
第一,HiCache 的 L3 后端不直接替代 GPU 缓存。它只是让已经离开 GPU 的 KV 缓存仍有机会被找回。真正参与注意力计算的仍然是 GPU KV 槽位。
第二,Mooncake Store 存的不是“一个请求”,而是 SGLang 按页组织出的缓存对象。MHA 通常会拆成 K/V 两类对象,MLA 则使用另一套键和缓冲区组织方式。
flow
资源生命周期:一段 KV 从计算结果变成系统资源
这篇总览更关心生命周期和所有权边界,而不是某个函数调用是否漂亮。
- 1
生成
GPU KV 池
预填充产生的 KV 先属于推理引擎的设备槽位。
设备内存基数树节点请求上下文 - 2
主机内存备份
主机 KV 池
HiCache 把可恢复的前缀备份到主机内存,并用引用计数保护其生命周期。
host_valuehost_ref_counterPoolTransfer - 3
发布
Store 元数据
Store 把对象、切片和副本状态推进到可读边界。
对象键PROCESSINGCOMPLETE - 4
搬运字节
Transfer Engine
Transfer Engine 只搬运已知地址上的字节,不判断 KV 语义。
Segment偏移长度传输方式 - 5
恢复
主机内存 -> GPU
读回后还要由 HiCache 回载,才重新成为注意力计算可以使用的资源。
batch_get_into回载设备侧提交
为什么不能只看 Store 或只看 Transfer Engine
Store 解决的是“这份数据是否存在、在哪里、能不能读”。它需要维护:
- 对象键到元数据的映射
- 对象被拆成哪些切片
- 每个副本所在的 Segment、偏移量和长度
- 副本处于
PROCESSING、COMPLETE还是已被移除 - Client 拿到的元数据租约何时失效
Transfer Engine 解决的是“知道远端地址以后,字节怎么过去”。它需要处理:
- 本地内存是否已经注册
- 远端 Segment 是否已经打开
- 这批传输是 READ 还是 WRITE
- 应该使用 TCP、RDMA、EFA、NVLink 还是其他传输方式
- BatchID 的生命周期和传输状态
如果只看 Store,会知道对象语义,却不知道大块 KV 数据如何跨节点搬运;如果只看 Transfer Engine,会知道数据如何搬运,却不知道这些字节为什么已经可读。要理解完整链路,必须把两边接起来。
HiCache 把缓存问题变成状态机
普通前缀缓存里,基数树节点的 value 指向 GPU KV 索引:命中就复用,没有命中就重新计算。
HiCache 多了一层状态:
value GPU KV 索引
host_value 主机 KV 索引
hash_value L3 存储使用的 page key
backuped 是否已经有主机内存备份
backuped_storage 是否已经写到 L3
lock_ref GPU 侧引用保护
host_ref_counter 主机侧引用保护
因此前缀匹配不再只是“命中了多少 token”,还要区分:
哪些 token 已经在 GPU
哪些 token 只在主机内存
哪些 token 可能在存储层
哪些 token 需要重新执行 prefill
这就是 HiCache 与 Mooncake Store 接起来的原因。HiCache 负责把前缀缓存分页、计算哈希并纳入调度;Mooncake Store 则让这些页能在分布式环境中查询、传输和复用。
一个实用读法
后续章节可以按这个顺序读:
- HiCache 总览和写路径,先理解 SGLang 为什么需要 L1/L2/L3。
- HiCache 读取路径,理解预取和回载怎样进入调度。
- Mooncake Store 对象模型,理解对象、切片、副本和租约。
- Store Put/Get,理解元数据控制面和数据面如何分离。
- Transfer Engine,理解 Segment、内存注册和 BatchTransfer。
- SGLang 到 Mooncake Store 串联篇,说明
page key如何映射为 Store 对象键并关联零拷贝批量 API。 - 共享 Transfer Engine 和部署边界,理解哪些是性能优化,哪些属于缓存语义。
读完以后,应该能画出一段前缀 KV Cache 从 GPU 写到远端、再从远端读回 GPU 的完整路径,并知道每个阶段该去哪个源码文件里验证。
职责与边界
system boundary
KV 缓存资源的所有权分散在引擎、元数据和传输三层
每一层都有自己的所有权和失败语义。把它们混成一个缓存服务,会让调试结论过度外推。
推理引擎决定是否复用
SGLang / HiCache
Status
Store 管理可见性
Mooncake Store
Status
传输层负责搬运字节
Transfer Engine
Status
本文结论边界
不能据此推断的内容
Status
结论:远程 KV 缓存是三层状态机,不是一个远端字典
理解 Mooncake / HiCache 的核心价值,不是记住某个 API 名字,而是能回答这几个边界问题:
- HiCache 为什么不能直接把 GPU KV 张量存进 Mooncake Store?
- Mooncake Store 的 Master 为什么不负责搬运大块数据?
PROCESSING副本和COMPLETE副本的读取语义有什么区别?- Transfer Engine 为什么不需要知道对象键?
- 一次远端 KV 命中为什么仍然需要回载到 GPU?
这些问题背后遵循同一条原则:推理引擎负责判断 KV Cache 是否值得复用,Store 负责判断对象是否可读,Transfer Engine 只负责在确定的地址之间传输数据。 把这三层混在一起,调试时就会把元数据命中、传输成功和 GPU 可复用误认为同一件事。