Mooncake:以 KVCache 为中心的 LLM 推理解耦架构详解

本文基于 Moonshot AI 发表的两篇论文:

  • Mooncake: A KVCache-centric Disaggregated Architecture for LLM Serving(arXiv 2407.00079)
  • MOONCAKE: Trading More Storage for Less Computation(FAST '25)

Mooncake 是 Kimi 大模型服务的底层推理平台,目前已在数千节点上稳定运行,每天处理超过 1000 亿 tokens。


1. 背景:LLM 推理的核心挑战

1.1 推理的两个阶段

基于 Transformer 的大语言模型(如 GPT、LLaMA)采用 decoder-only 架构,每次推理请求逻辑上分为两个截然不同的阶段:

Prefill 阶段(预填充):并行处理所有输入 token,生成第一个输出 token,同时计算并存储所有层的 Key/Value 中间结果,即 KVCache。由于注意力计算复杂度随序列长度二次增长(O(n2)O(n^2)),该阶段是计算密集型的。

Decode 阶段(解码):利用已有 KVCache 自回归地逐 token 生成输出,每次迭代仅处理 1 个 token。这使得它是内存密集型的,计算时间随 batch size 亚线性增长。

Prefill 与 Decode 的吞吐量和延迟对比

图 1:不同序列长度和 batch size 下,Prefill(左)和 Decode(右)的归一化吞吐量与延迟。Prefill 时间随输入长度超线性增长,Decode 时间随 batch 亚线性增长。

1.2 服务质量目标(SLO)

作为 MaaS(Model as a Service)提供商,Kimi 需要同时满足两类延迟约束:

  • TTFT(Time To First Token):请求到达至第一个 token 输出的时间,主要受 Prefill 阶段影响
  • TBT(Time Between Tokens):连续两个 token 之间的生成间隔,主要受 Decode 阶段影响

典型约束形如 TTFTP9030s\text{TTFT}_{P90} \leq 30\text{s}TBTP90100ms\text{TBT}_{P90} \leq 100\text{ms},即 90% 的请求需满足此限制。优化目标是在满足 SLO 的前提下最大化有效吞吐量(goodput)

1.3 传统架构的痛点

在传统的耦合式推理系统(如 vLLM)中:

  • Prefill 和 Decode 混跑在同一批次中,长上下文的 Prefill 会严重干扰 Decode 阶段的 TBT
  • KVCache 只能存在于单节点的 HBM(GPU 显存)或本地 DRAM 中,缓存容量有限,跨会话前缀复用率低
  • 无法充分发挥集群中大量闲置的 CPU、DRAM、SSD 和 RDMA 资源

2. 核心设计哲学:以 KVCache 为中心,用存储换计算

Mooncake 的核心洞察是:KVCache 的调度是 LLM 推理调度的核心

2.1 两大吞吐量提升路径

提升整体吞吐量通常有两条路径:

  1. 最大化 KVCache 复用:避免冗余计算,降低 TTFT
  2. 最大化每批次 token 数:提高 MFU(Model FLOPs Utilization),但会增大 TBT

这两条路径都与 SLO 存在潜在冲突。Mooncake 的关键在于精细调度,让二者协同而非对抗。

2.2 "用存储换计算"的数学依据

以 LLaMA3-70B 为例,若当前请求长度为 nn,其与已缓存 KVCache 的公共前缀长度为 pp,则:

  • 节省的计算量 l×(ap2d+bpd2)\approx l \times (ap^2d + bpd^2)ll 为层数,dd 为模型维度)
  • 需要传输的 KVCache 大小 =p×l×2dgqa×s= p \times l \times \frac{2d}{\text{gqa}} \times s

设 GPU 计算吞吐 GG,KVCache 加载带宽 BB(取 Bh2dB_{h2d}BnicB_{nic} 的较小值),则当:

BG>2dsgqa×(apd+bd2)\frac{B}{G} > \frac{2ds}{\text{gqa} \times (apd + bd^2)}

时,复用 KVCache 对 TTFT 是净收益的。对于 LLaMA3-70B(8×A800),当前缀长 8192 时,所需最小带宽仅约 6 GB/s。这意味着一块 100 Gbps 的 NIC 就足以支撑分布式 KVCache 复用——这是构建全局缓存池的理论基础。


3. 整体架构

Mooncake 整体架构

图 2:Mooncake 整体架构。系统将 GPU 集群解耦为三类资源池:Prefill 节点池、Decode 节点池,以及由 CPU、DRAM、SSD、RDMA NIC 组成的分布式 KVCache 存储(Mooncake Store)。

Mooncake 采用以 KVCache 为中心的解耦架构,核心组件包括:

组件 职责
Conductor(全局调度器) 感知 KVCache 分布与工作负载,为每个请求选择最优的 Prefill/Decode 实例组合
Prefill 节点池 专门执行 Prefill 计算,支持跨节点流水线并行处理超长上下文
Decode 节点池 专门执行自回归解码,采用 continuous batching 最大化 batch 利用率
Mooncake Store 分布式 KVCache 存储系统,汇聚集群所有节点的 CPU DRAM/SSD,通过 RDMA 高速互联
Messenger 每个节点部署的 KVCache 传输服务,基于 GPUDirect RDMA 实现零拷贝高速传输

3.1 请求处理全流程

请求处理工作流

图 3:一个请求在 Mooncake 中的完整处理流程,涵盖 KVCache 复用、增量 Prefill、异步传输和 Decode 四个步骤。

一个请求到来时,经过如下步骤:

Step 1:KVCache 复用(KVCache Reuse)
Conductor 对输入 token 序列计算分层哈希,在全局 KVCache 索引中查找最长前缀匹配。选定 Prefill 实例后,将命中的 KVCache 块从远端 CPU DRAM 通过 RDMA 加载到该实例的 GPU HBM,直接跳过这部分的重复计算。

Step 2:增量 Prefill(Incremental Prefill)
仅对未命中前缀的 token 执行 Prefill 计算。若未命中 token 数超过阈值(prefill_chunk,通常 >1000 tokens),则将其拆分为多个 chunk,以流水线方式分发到多个 Prefill 节点并行处理(见 §5 的 CPP 机制)。

Step 3:KVCache 流式传输(KVCache Transfer)
Prefill 阶段逐层计算完成后,每层的新增 KVCache 立即异步流式传输到目标 Decode 节点的 CPU DRAM,与计算过程并行,消除等待时间。

Step 4:解码(Decoding)
KVCache 全部就绪后,请求加入 Decode 节点的 continuous batch 队列,开始自回归生成。Decode 节点由 Conductor 根据当前负载预先选定,以保证 TBT SLO。


4. Mooncake Store:分布式 KVCache 存储

Mooncake Store 是 Mooncake 的核心基础设施,它将 GPU 集群中大量闲置的 CPU DRAM、SSD 和高速 RDMA 网络整合为一个统一的全局 KVCache 缓存池。

4.1 KVCache 的分块与哈希管理

KVCache 分块与哈希

图 4:CPU 内存中的 KVCache 池。每个块附带由其内容哈希和前缀哈希共同计算出的 hash key,用于全局去重和匹配。

KVCache 以**分页块(paged blocks)**的形式存储,每块包含固定数量的 token(通常 16~512 tokens)。块的唯一标识 hash key 由以下方式计算:

1
block_hash[i] = Hash(tokens[i*B : (i+1)*B] || block_hash[i-1])

即当前块 token 内容与前缀块哈希拼接后取哈希,确保相同 token 前缀对应相同的 hash key,从而实现跨请求、跨会话的前缀去重复用。

缓存满时采用 LRU 淘汰策略(实验对比中 LRU 在该负载下性能最优),正在被访问的块不会被淘汰。热点块(被大量请求共享的系统 prompt 等)会通过调度策略主动复制到多个节点,降低访问延迟。

4.2 为何需要全局缓存而非本地缓存?

以 LLaMA3-70B 为例,单个 token 的 KVCache 大小约为 320 KB。即便为每个节点分配 1 TB DRAM 作为本地缓存,也只能存储约 300 万个 token。

实验分析表明:在多种真实负载下,本地 3M token 容量的缓存仅能达到理论最大命中率的不到 50%。而将 20+ 个节点的 DRAM 汇聚成全局缓存池(容量约 50M tokens),才能接近理论上限。

全局缓存 vs 本地缓存的实测收益:

  • 缓存命中率最高提升 136%
  • Prefill GPU 计算时间最高节省 48%

4.3 高性能传输引擎

Mooncake Store 的传输引擎基于 RDMA(Remote Direct Memory Access) 实现,目标是充分利用多 NIC 设备(每台 A800 节点配备 4×200 Gbps 或 8×400 Gbps NIC)。

Mooncake Store 传输引擎

图 5:Mooncake Store 传输引擎。通过拓扑感知的路径选择,将传输任务分配到最优 NIC,避免 UPI/PCIe 瓶颈,并将单次传输切分为 16 KB 的小片,多路并行传输。

拓扑感知路径选择(Topology-aware Path Selection)

现代服务器内部存在 NUMA 域、PCIe Switch 等拓扑层次,跨域传输会受到 UPI 带宽限制。传输引擎在启动时让每台服务器生成拓扑矩阵并广播到集群,为每种内存类型(CPU DRAM、GPU VRAM)维护"首选 NIC"和"备用 NIC"列表。正常情况下,数据传输优先使用与内存同 NUMA 域的 NIC,实现 RDMA 操作不跨 NUMA;故障时自动切换到备用路径。

每次传输被内部切分为 16 KB 的小片,不同片可以通过不同 NIC 并发传输,充分聚合所有 NIC 的带宽。

端点池(Endpoint Pooling)

连接按需建立(首次请求时才配对),并通过 SIEVE 算法管理端点池,限制活跃连接数防止开销过大。NIC 故障时自动切换到其他路径,连接恢复后重新加入池。

实测效果:在 40 GB 数据传输(对应 LLaMA3-70B 128k token 的 KVCache)场景下,传输引擎在 4×200 Gbps 网络下达到 87 GB/s,在 8×400 Gbps 网络下达到 190 GB/s,分别是 TCP 方案的 2.4× 和 4.6×


5. Prefill 节点池:处理超长上下文

5.1 为何坚持 Prefill/Decode 分离?

部分研究(如 Sarathi-Serve)认为 Chunked Prefill 可以避免 P/D 分离的复杂性——将 Prefill 分成小块插入 Decode batch,减少干扰。

Mooncake 在权衡后选择坚持分离架构,理由有三:

  1. SLO 不可兼顾:Chunked Prefill 内嵌到 Decode batch 时,难以同时最大化 Prefill MFU 和满足 TBT SLO——提高 Prefill 吞吐必然增大 Decode 延迟抖动
  2. 超长上下文需要跨节点并行:128k~1M token 的请求仅靠单节点 8 卡无法高效处理,需要多节点协作
  3. VRAM 利用机会:分离的 Prefill 节点 VRAM 在 KVCache 完成流出后可自由利用(如处理异步批处理任务)

5.2 分块流水线并行(Chunked Pipeline Parallelism,CPP)

对于超长上下文(如 128k tokens),Mooncake 提出 CPP 机制替代序列并行(SP):

工作原理

  • 将 Prefill 集群中的若干节点组成一个"流水线 Prefill 组"
  • 一个请求的输入 tokens 被切分成多个 chunk,每个 chunk 不超过 prefill_chunk 阈值
  • 不同 chunk 分配给流水线组内的不同节点并行处理,类似训练中的流水线并行

与序列并行(SP)的对比

维度 序列并行(SP) 分块流水线并行(CPP)
跨节点通信 每层至少一次全局通信(Ring/Striped Attention) 仅在流水线阶段边界通信,可与计算重叠
MFU 较低(频繁通信) 更高
动态扩缩容 复杂,需要全局通信组 简单,节点组大小可灵活调整
短请求处理 低效(SP 引入额外开销) 自然降级为单节点处理,无额外开销

CPP 的核心优势在于:跨节点通信仅发生在 chunk 边界,且可以与计算完全重叠,网络资源不与 KVCache 传输竞争。

5.3 逐层 Prefill(Layer-wise Prefill)

为进一步降低 VRAM 占用并减少传输等待,Mooncake 实现了逐层异步 KVCache 加载与存储

1
2
3
4
5
6
7
8
9
for layer in range(num_layers):
wait(async_load_kvcache[layer]) # 等待该层 KVCache 从 DRAM 加载完毕
launch(async_load_kvcache[layer+1]) # 触发下一层异步加载

output = attention(q[layer], k[layer], v[layer]) # 执行注意力计算

launch(async_store_kvcache[layer]) # 异步将该层 KVCache 写回 DRAM

wait_all(async_store_kvcache)

这种方式使 KVCache 的传输与 GPU 计算完全并行,Prefill 实例的实际延迟约等于 max(KVCache 加载时间, 标准 Prefill 时间)

对于长上下文请求(KVCache 加载量大),逐层 Prefill 可显著降低 KVCache 存储延迟。此外,由于 KVCache 实时流出,Prefill 调度时可以忽略 VRAM 可用量(只需能容纳单请求即可),极大简化了调度器设计。


6. KVCache 感知调度

Conductor 是 Mooncake 的全局大脑,其调度算法以 KVCache 分布为核心,兼顾负载均衡和 SLO 满足。

6.1 缓存感知的 Prefill 调度

传统系统按请求数量做负载均衡,Mooncake 的 Prefill 调度综合考虑三个因素:

  1. 前缀命中长度:命中越长,计算量节省越多
  2. 队列等待时间:实例当前排队的预估时间
  3. KVCache 传输时间:若最优缓存在别的节点,需要额外传输

调度算法(Algorithm 1)对每个候选 Prefill 实例估算 TTFT:

TTFT(instance)=Ttransfer+Tqueue+Tprefill(n,p)\text{TTFT}(\text{instance}) = T_{\text{transfer}} + T_{\text{queue}} + T_{\text{prefill}}(n, p)

其中:

  • TtransferT_{\text{transfer}}:从最优缓存节点传输缺失 KVCache 的时间(本地命中时为 0)
  • TqueueT_{\text{queue}}:当前队列等待时间
  • Tprefill(n,p)T_{\text{prefill}}(n, p):根据离线拟合的多项式回归模型,由请求长度 nn 和前缀命中长 pp 估算的计算时间

最终选择预估 TTFT 最小的实例。若 TTFT 超过 SLO 上限,直接返回 HTTP 429 拒绝请求。

6.2 热点迁移与缓存负载均衡

真实负载中,不同 KVCache 块的访问频率差异极大——系统 prompt 可能被几乎所有请求访问,而某个长文档的 KVCache 可能只有一个用户使用。

Mooncake 采用启发式热点自动迁移策略(而非精确预测),核心思路是利用调度决策的副作用实现热点复制:

触发条件:当某个实例本地的最优前缀命中长度,与全局最优命中长度的比值超过阈值时,Conductor 判定该块为热点,并触发复制:

1
2
if best_global_prefix_len / local_prefix_len > kvcache_balancing_threshold:
TransferKVCache(best_instance → current_instance)

自然涌现的复制效果

  • 请求被调度到某实例时,若远端有更好的缓存,该实例会主动拉取并本地化
  • 如果不跨节点传输反而本地重算更快(即远端优势不足),则直接计算,避免不必要传输
  • 两种路径都天然地将热点 KVCache 复制到更多节点

实验表明,在对话(Conversation)和工具/Agent 负载下,排名前 100 的热点 KVCache 块(通常是系统 prompt)会在系统稳定后几乎复制到每个 Prefill 实例,彻底消除传输瓶颈。


7. 过载场景下的调度

与多数 LLM 服务研究假设资源充足不同,Kimi 在高峰期持续面临严重过载。Mooncake 在解耦架构下面临传统系统没有的独特挑战。

7.1 过载的定义与度量

Mooncake 使用 SLO 满足率而非简单的请求数量来度量负载:

  • Prefill 负载:当前队列中的请求预计 TTFT 是否超限
  • Decode 负载:当前 batch 中预计 TBT 是否超限

7.2 早拒绝(Early Rejection)

在解耦架构中,Prefill 完成后请求才进入 Decode 队列。若此时 Decode 已满载,该请求会被拒绝——但 Prefill 的计算资源已经白白浪费

早拒绝策略:在请求到达时,同时评估当前 Prefill 和 Decode 的负载,以二者的较大值决定是否接受请求:

1
2
if prefill_load > threshold or decode_load > threshold:
reject(request)

这将 Decode 的负载判断提前到 Prefill 开始之前,避免浪费。

7.3 早拒绝引发的负载振荡问题

然而,早拒绝策略引入了新问题——Prefill 和 Decode 实例之间的负载产生反相振荡

负载振荡示意

图 6:早拒绝引入的 Prefill/Decode 负载振荡。两个实例的负载周期性地交替达到峰值和低谷,严重浪费资源。

振荡的根本原因是Prefill 到 Decode 之间存在时间差

  • 阶段 1:Prefill 和 Decode 负载都低 → Conductor 大量接受请求 → Prefill 满载
  • 阶段 2:Prefill 处理的请求涌入 Decode → Decode 满载 → Conductor 开始拒绝 → Prefill 空转
  • 阶段 3:Decode 消化完积压,负载下降 → Conductor 再次大量接受 → Prefill 再次满载
  • 如此循环往复,形成严重的防相位振荡

7.4 基于预测的早拒绝(Prediction-based Early Rejection)

基于预测的早拒绝

图 7:基于预测的早拒绝(右图)相比朴素早拒绝(左图),显著消除了负载振荡,使 Prefill 和 Decode 负载趋于平稳。

解决方案是预测未来的 Decode 负载,而非基于当前时刻的快照做决策。

Mooncake 目前采用系统级预测(而非请求级预测):

  1. 假设每个请求的 Decode 阶段耗时均为固定值 tdt_d
  2. 对于当前时刻 tt,将 Prefill 实例在未来 tdt_d 内预计完成的请求加入"模拟 Decode 队列"
  3. 将"模拟 Decode 队列"中已超过 tdt_d 的请求移除
  4. 计算模拟队列的平均 TBT/SLO 比值,作为未来 Decode 负载预测

基于这个预测值决定是否接受新请求,从源头切断振荡的正反馈环。


8. 架构设计的关键权衡总结

设计选择 问题背景 Mooncake 的方案 关键 Trade-off
P/D 分离 Chunked Prefill 是否可替代 P/D 分离? 保持分离,但短请求可 inline 架构复杂性 vs. SLO 可预测性
全局 KVCache 池 本地缓存能否满足需求? 全集群 DRAM/SSD 汇聚为全局池 传输带宽 vs. 计算节省
CPP vs. SP 多节点如何处理超长 Prefill? Chunked Pipeline Parallelism MFU vs. 动态弹性
逐层传输 如何降低 Prefill 的 VRAM 占用? KVCache 按层异步流出 实现复杂度 vs. 调度简化
热点复制 如何避免热点 KVCache 传输拥塞? 启发式调度副作用自然触发复制 内存占用 vs. 访问延迟
基于预测的早拒绝 P/D 解耦导致负载振荡如何解决? 预测未来 Decode 负载 预测精度 vs. 振荡抑制

9. 总结

Mooncake 提出了一套以 KVCache 为调度核心的 LLM 推理解耦架构,其核心贡献可以归纳为:

  1. "用存储换计算"的系统性践行:通过整合集群闲置的 CPU DRAM/SSD 构建全局分布式 KVCache 池,利用 RDMA 高速网络实现跨节点前缀缓存复用,突破了单机缓存容量瓶颈

  2. 精细的 P/D 解耦:将 Prefill 和 Decode 阶段分配给不同资源池,消除相互干扰,使两个阶段都能独立优化,同时通过 layer-wise 异步传输最大化管线并行

  3. CPP 机制:以低通信开销的流水线并行替代序列并行,在自然适配长短上下文的同时保持高 MFU

  4. KVCache 感知的全局调度:Conductor 综合考虑缓存命中、传输时间、队列深度,并通过调度副作用自动触发热点复制,实现无需精确预测的缓存负载均衡

  5. 过载场景的预测性早拒绝:通过预测未来 Decode 负载而非响应当前快照,从根本上消除解耦架构中的负载振荡问题

这套架构使 Kimi 在 A800 集群上相比原有 vLLM 方案能处理 115% 更多请求,每日处理超过 1000 亿 tokens,在以长上下文为核心场景的 LLM 服务领域树立了工程实践的新标杆。


参考文献:

  • Ruoyu Qin et al. “Mooncake: A KVCache-centric Disaggregated Architecture for LLM Serving.” arXiv:2407.00079v4, 2024.
  • Ruoyu Qin et al. “MOONCAKE: Trading More Storage for Less Computation.” FAST '25, 2025.