一段关于 lmcache multiprocess mode + MooncakeStore 的协作故事 —— 从 0 到 1,从能用到好用。
一、写在前面
最近,LMCache 社区和Mooncake 社区围绕 LMCache MP (multiprocess) 模式下的 Mooncake Store L2 adapter 做了一系列相当有价值的开源合作。
主要参与的小伙伴有:
- maobaolong(LMCache 社区维护者):从 0 到 1,初版打通 LMCache(MP mode) -> MooncakeStore。
- fangchizheng(fcczzz)(来自 Mooncake 开源社区):贡献了 MooncakeStore RDMA 预注册、batch 操作支持、per-op worker pools 等一连串关键优化。
- chunxiaozheng(LMCache 社区维护者):在每一次 PR 中提出关键 review意见,把控代码质量。
这不是某个公司单方面的产品交付,而是两个开源社区之间真正意义上的”联合开发”—— 一边出框架与场景,一边出底层引擎与性能优化。本文就来回顾这段合作,并介绍一下最终落地的技术成果。
二、先认识一下两位主角
2.1 LMCache 是什么?
LMCache 是 vLLM 生态里专注于 KV Cache 复用与持久化 的一层中间件。 它的核心目标是:把 LLM 推理过程中的 KV Cache “沉淀” 下来, 让相同前缀的 prompt 不必重复计算 prefill,直接复用历史 KV, 显著降低 TTFT(Time-To-First-Token)和算力消耗。
LMCache 提供两层存储:
- L1:CPU 共享内存中的 KV Cache 池(高速、易失)。
- L2:可持久化的二级存储(NVMe / 文件系统 / 分布式 KV 存储 / 对象存储等),由各种 L2 Adapter 接入。
2.2 Mooncake Store 是什么?
Mooncake 是一个面向分布式大模型推理场景的开源infra。其中,Mooncake store 是分布式 KV 缓存存储引擎,原生支持 RDMA、零拷贝传输、多副本元数据管理,定位是为 “PD 分离 / KV-centric 推理架构” 提供高性能存储底座。
简单一句话总结两者的定位:
| 项目 | 定位 |
| LMCache | KV Cache 的 管理者与调度器(决定谁存、谁取、放哪一层) |
| Mooncake Store | KV Cache 的 高性能远端存储引擎(决定如何高速、可靠地存取) |
三、LMCache MP Mode 整体架构
在介绍这次合作的具体内容之前,先简单看一下 LMCache MP 模式的整体形态。
MP(multiprocess)模式:LMCache 不再以”库”的形式被 vLLM in-process 加载,而是作为一个独立进程通过 RPC(ZMQ)与 vLLM worker 通信,把 KV Cache 的管理彻底独立出来。这样做带来三大好处:
1. 进程隔离:LMCache 故障不会影响 vLLM 主进程。
2. 资源解耦:可以独立分配 CPU、内存、I/O 资源给 LMCache。
3. 跨实例共享:多个 vLLM 实例可以共用同一个 LMCache 服务,形成机器级甚至集群级的 KV Cache 池。
3.1 架构图

几个核心点:
- L1 Manager 维护一个 CPU 共享内存池,所有 vLLM worker 通过 RPC读写同一份 L1。
- StoreController 在后台异步把 L1 中新产生的 KV chunk 复制到 L2。
- PrefetchController 在 LOOKUP 命中 L2 时,异步把数据从 L2 拉回 L1,再让 vLLM 通过 RETRIEVE RPC 拿走。
- L2 Adapter 层是完全插件化的:每一种存储后端都是一个独立的 adapter,通过统一的
L2AdapterInterface接入。本文的主角mooncake_storeadapter 就是这一层的一个新成员。
四、合作之前:LMCache 已经铺好的路
这次 LMCache × Mooncake 合作能这么顺畅,离不开 LMCache 社区在前期搭好的基础设施。简单说就是 三块拼图,缺一不可。
4.1 拼图一:Native Connector 框架(#2642)
*[Southbound]: Create a Native Protocol for MP and non-MP* —— by Samuel Shen (TensorMesh)
LMCache 早期只有 Redis 一个 C++ connector,代码混在 csrc/redis/ 里、 和 RESP 协议强耦合。这次 PR 做了一次彻底的抽象重构:
- 把
csrc/redis提升为通用的csrc/storage_backends/。 - 抽出了
IStorageConnector接口和ConnectorBase<T>CRTP 模板。 - 提供了
connector_pybind_utils.h一组 pybind 宏,让新后端只需写几十行就能完成 Python 绑定。 - 在 Python 侧引入了
NativeConnectorL2Adapter,作为”任何 native connector”到”L2AdapterInterface”的通用桥接器。 - 同时支持 MP 和非 MP 模式 —— 一份 C++ 代码,两个场景复用。
简单说,LMCache 给所有未来的 native 后端留好了一个标准的插槽。
4.2 拼图二:第一个 Native FS Connector(#2779)
*Introduce native fs connector* —— by maobaolong
Native 框架抽象出来后,第一个适配的 L2 Native Adapter —— 本地文件系统:
- 在
csrc/storage_backends/fs/下实现了一个 C++ 文件系统 connector。 - 它既是个能用的功能(在没有 NIXL/Mooncake 等依赖的环境里,FS adapter
足以跑通整个 L2 链路),也是一个最小工作示例:后人想接入新后端,可以提供参考。此外,一些分布式文件系统,都能用 nfs 或 mount fs 方式配合这个 fs adapter 接入 lmcache。
4.3 拼图三:动态加载第三方 Native Connector(#2851)
*[MP] Refactor l2 plugin framework to support dynamic load third-partynative l2 connector* —— by maobaolong
最后一块拼图,也是最关键的一块:让第三方 connector 不必合入 LMCache 主仓也能被加载。
这个 PR 做了:
- 把 L2 plugin 框架改造成基于 registry + 动态发现 的模式。
- 提供了一个完整的”外部插件包”模板(
lmc_external_native_connector/),
外部用户可以把自家的 C++ 后端打成独立的 pip 包,安装后被 LMCache 自动发现并加载。
- 配套的设计文档
docs/design/l2_adapters/plugin.md也一并完善。
在这次重构之前,添加一个新后端要在工厂函数里改 if/elif链、改注册表、改 import。重构之后是真正的”零修改扩展”——这才是开源插件该有的样子。
至此,”地基”全部铺好。下一步就是请重量级嘉宾上场了。
五、合作正片:MooncakeStore L2 Adapter 从 0 到 1
5.1 第一步:把 Mooncake 接进来(#2911)
*[MP] Introduce l2 mooncake adapter* —— by maobaolong, 2026-04-03
这是合作的第一发子弹。基于上一节铺好的 native 框架,新增了 `csrc/storage_backends/mooncake/`:
- C++ 侧:
connector.cpp / connector.h / pybind.cpp实现了对Mooncake 客户端的封装,整个文件加起来才 140 行左右。 - Python 侧:`mooncake_store_l2_adapter.py`
提供了 MooncakeStoreL2AdapterConfig 和工厂函数。
关键设计亮点 —— 配置透传:除了 LMCache 自己关心的几个配置项(如 num_workers),所有 Mooncake 自家的配置(local_hostname, master_server_addr, protocol, rdma_devices, …)都被作为整体字典 透传给 mooncake sdk 侧的setup_internal(ConfigDict)。这样 Mooncake 后续增删配置项时, LMCache 这边一行代码都不用改。
这个设计哲学非常重要:不替第三方做翻译,只做透传。这是开源协作里最难得的”边界感”——不感知具体底层,让对方保留演进自由。
落地效果:跑通了 mp mode + Mooncake Store (TCP) 的完整 store/lookup/load 链路。第一版还不快,但它通了。
5.2 第二步:Mooncake 社区的回礼 —— RDMA 预注册(#3018)
*[Feat] Add RDMA L1 memory preregistration support for MooncakeStore L2adapter* —— by Chizheng Fang, 2026-04-23
第一版接进来之后,Mooncake 社区的 fcczzz出手了。 他抓住了一个核心问题:Mooncake RDMA 模式下,LMCache 的 L1 内存必须预先注册给 RDMA 网卡,否则每次 I/O 都会触发 page pinning,性能损失巨大。
这一发 PR 做了:
- 在 C++
MooncakeConnector里加上了preregister_l1_memory路径。 - 让 LMCache 的 L1 共享内存指针在 adapter 创建时就被一次性注册到Mooncake 的 transfer engine。
- 同时清理掉了第一版里多余的
owned_real_client_复杂逻辑,简化为单client 实例。 - 把
preregister_l1_memory提升为MooncakeStoreL2AdapterConfig的独立开关,并补全测试。
这次 PR 的微妙之处在于:fcczzz 不只是”调用方报个 bug”,他直接读懂了 LMCache 的 L1 Manager、写出了改动 LMCache C++ 层和 Python 层的 patch, 还顺手优化了一版 native connector 的代码组织。这就是开源协作里最有营养的状态——不分彼此,大家都是社区的主人。
5.3 第三步:Batch 操作(#3172)
*[MP]: Add batch operations to Mooncake L2 adapter*—— by Chizheng Fang, 2026-05-07
RDMA 通了,下一步是榨性能。fcczzz 又来了一发:
- 在
MooncakeConnector里实现了 批量 store / lookup / delete,
把原来的循环 single-op 升级成一次性下发的 batch 调用。
- 修复了一个边界 case:Mooncake 的 “key exists” 错误应当被当作 miss 处理,
而不是抛出异常。
- 把 RDMA 集成测试隔离开来,避免和默认 TCP adapter 抢同一段 segment。
- 补齐了 batch_delete 测试,覆盖 mixed existing/missing 的场景。
为什么 batch 重要?因为 LMCache 一次 store 经常涉及成百上千个 KV chunk (每个 chunk 对应一个 token block),逐个走 RPC 的延迟是不可接受的。 batch 之后,吞吐量直接上一个台阶。
5.4 第四步:Per-Op Worker Pools(#3227)
*[MP]: Add per-operation dedicated worker pools to Mooncake L2 connector*—— by Chizheng Fang
这个改进解决的场景问题:
在共享 worker pool 下,一个 store 突发就能把 lookup 拖垮。
为什么?因为 lookup/retrieve/store/delete 这四种操作的延迟完全不同——lookup 极快但极敏感,store 慢但可以容忍。把它们塞进同一个线程池,store 突发期到来时,那些只想做一次”几十微秒”的 lookup 必须排队 在几十毫秒级的 store 后面,p99 直接爆炸。
#3227 的解法:
- 在
ConnectorBase里引入WorkerPoolConfig,把lookup / retrieve
/ store / delete 四条 lane 各自分配独立的 worker。
- 请求入队时按 op 类型路由到对应 lane;未配置的 lane fallback 到共享池。
- Python 配置里暴露
per_op_workers: dict[str, int],并做完整校验。 - 这个 lane 机制做在
ConnectorBase上,所有 native connector 都能受益
——其他后端只要在 pybind 里接住 WorkerPoolConfig 就行。
实测数据(Mooncake RDMA, 1024 keys, 1 MiB values, 4 stores/round):单位均是ms
| 配置 | Lookup avg | Lookup p99 | Load avg | Load p99 | Store avg | Store p99 |
| shared 4 workers | 0.947 | 16.832 | 3.399 | 41.165 | 5.292 | 51.390 |
| per-op (1/1/2) | 0.266 | 0.483 | 2.590 | 3.506 | 3.216 | 67.434 |
- Lookup p99 下降 ~35×(16.8 ms → 0.48 ms)
- Load p99 下降 ~12×(41.2 ms → 3.5 ms)
这不是”调参调出来的”性能,是架构性的红利——隔离不同 SLO 的负载, 本来就是高性能存储栈里最朴素也最有效的设计。
六、把这条时间线铺平来看

短短一个多月,从”能跑”到”跑得快”,再到”跑得稳”。每一步都不是某一方单独完成的——LMCache 出框架与 review,Mooncake 社区出底层与优化思路, 中间还有 chunxiaozheng 这样耐心的 reviewer 在每一次 PR 里把质量盯下来。
七、聊聊这次合作教给我们的事
7.1 好的”边界感”是开源合作的前提
LMCache 没有把 Mooncake 的配置一项一项翻译成自家的 dataclass, 而是选择透传 Dict[str, str]。这看起来”偷懒”,本质上是 给协作伙伴留够演进空间。开源合作里,边界划得越清楚,越省心。
7.2 插件化是社区可持续协作的基石
如果没有事先的 native connector 框架重构 + 动态加载机制, Mooncake 这次合作的代价会高得多——可能要往 LMCache 主仓里塞一堆 if/elif、修改一堆工厂函数、忍受一个又一个的 merge conflict。 Zero-Modification Extension 是开源协作的”减摩擦剂”, 谁先把它做好,谁就更容易被合作。
7.3 WinWin,是开源最朴素的逻辑
- 对 LMCache 来说:集成了一个生产级的 RDMA 分布式 L2 后端,
以及一连串可以受用于所有 native connector 的优化(per-op pools、batch、 L1 预注册思路)。
- 对 Mooncake 来说:打通了一个面向 vLLM 用户的导流入口,让它的 RDMA 能力真正被 LLM 推理用户用上。
- 对 用户 来说:一个开箱即用的最简实践 lmcache mp + mooncakestore 方案,简单且高性能。
开源的意义:把自己擅长的东西做好, 再把它免费送出去,最后自己得到的反而更多。
八、给读到这里的小伙伴们的一点点请求
如果你在做:
- 大模型推理服务
- 想搭建跨实例共享 KV Cache 的推理集群
- 对 RDMA / 分布式 KV 存储有性能要求
- 或者只是想看看一个 well-designed 的开源插件框架长什么样
强烈建议你试试 `LMCache MP Mode + Mooncake Store` 这套组合:
# 1. 编译时启用 Mooncake 扩展
BUILD_MOONCAKE=1 pip install -e . --verbose
# 2. 启动 LMCache 服务
lmcache server --l1-size-gb 100 --eviction-policy LRU \
--l2-adapter '{
"type": "mooncake_store",
"num_workers": 4,
"per_op_workers": {"lookup": 1, "retrieve": 1, "store": 2},
"preregister_l1_memory": true,
"local_hostname": "node01",
"metadata_server": "http://localhost:8080/metadata",
"master_server_addr": "localhost:50051",
"protocol": "rdma",
"rdma_devices": "mlx5_0",
"global_segment_size": "3221225472"
}'
具体的 benchmark 数据、配置说明、文档链接都在 #3227 和 docs/source/mp/l2_storage.rst 里。
也欢迎你给两个社区都点个 star、提个 issue、发个 PR—— 开源协作没有围墙,每一个 contributor 都让这条路更宽一点。
发表评论