Mooncake 的 KVCache 中心架构如何提升 LLM 服务吞吐?

Mooncake 拆分预填充与解码集群,围绕 KVCache 复用调度,在真实负载下让 Kimi 多处理 75% 的请求。

直接答案

Mooncake 提升 LLM 服务吞吐的方式是:把预填充和解码拆到不同集群,将 KVCache 池化到 GPU、CPU DRAM 和 SSD 上,再由一个以 KVCache 为中心的全局调度器在缓存复用与延迟 SLO 之间做权衡。在 10 个预填充实例加 10 个解码实例的真实负载回放中,它在满足相同 TTFT 和 TBT 目标的前提下,比 20 个 vLLM 实例多处理约 75% 的请求;在模拟长上下文测试中,吞吐提升幅度为 50% 至 525%。

1篇文献引用

本文由 WisPaper 驱动的搜索和论文分析生成。

KVCache 复用为何决定吞吐上限

Mooncake 是 Kimi 背后的服务系统,它的出发点是一个带多重约束的优化问题,而不是某个单一瓶颈:在满足延迟相关服务等级目标(SLO)的同时,最大化整体有效吞吐。论文点名的两个 SLO 是首 token 时延(TTFT)和 token 间时延(TBT),前者由预填充阶段决定,后者由解码阶段决定。

论文认为 KVCache 调度处在这个问题的中心,因为提升吞吐的两种常见做法都会与这些 SLO 冲突。复用 KVCache 能减少一个请求所需的计算量,但从远端取回这份缓存会拉长 TTFT。增大每个批次中的 token 数能提高模型浮点运算利用率(MFU),但批次变大会拉长 TBT。因此 Mooncake 的组织核心是决定缓存状态放在哪里、每个请求交给哪个实例,而不是把某个算子做得更快。

拆分预填充与解码以守住 TBT

该架构把集群拆成预填充池和解码池,两者通过 RDMA 传输跨节点 KVCache。两个池各有目标:预填充要在满足 TTFT SLO、最低 MFU 以及 KVCache 不超过 DRAM 的前提下最大化缓存复用;解码要在满足 TBT SLO 且 KVCache 不超过 VRAM 的前提下最大化吞吐。一个名为 Conductor 的全局调度器为每个请求挑选一对预填充实例和解码实例。

这种拆分之所以重要,是因为论文的模拟实验显示,长上下文请求会严重干扰 vLLM 的解码阶段,迫使 vLLM 逐个处理请求而无法批处理。Mooncake 在保持批处理的同时,用两阶段设计限制预填充对解码的影响,论文报告在该实验中它从未突破 TBT SLO。代价是协调问题:Conductor 依据当前负载预选解码节点,但本地调度器会在预填充之后复核 TBT SLO,一旦此时拒绝请求,已经完成的预填充工作就被浪费。

闲置 CPU、DRAM 与 SSD 成为缓存层

Mooncake 利用 GPU 集群中未被充分利用的 CPU、DRAM 和 SSD 资源,构建一个分离式 KVCache 池,使缓存块的生命周期可以超过单个实例的 VRAM。论文的请求轨迹分析说明了放置决策为何重要:超过 50% 的缓存块从未被使用,而某些块被访问数万次,作者认为必须复制这些热点块,以避免传输拥塞。

同一份轨迹采样自一小时的数据,以 23,608 条记录公开,字段包括 timestamp、input_length、output_length 和 hash_ids,它给出了 Mooncake 所针对的负载形态。平均输入长度为 8,590 个 token,平均输出长度为 182 个 token,输入输出比约为 720。在单一全局缓存池中,容量从 1,000 块提升到 50,000 块,命中率从 34% 升到 55%,在此样本中继续扩容收益很小;作者提醒该样本只是真实负载的一个子集,实际所需容量应按比例扩展。在这些访问模式下,LRU 表现最好。

分块流水并行加速长预填充

尽管分块预填充可以把预填充工作内联进解码批次,论文仍保留独立的预填充池,并给出两个理由:预填充节点处理长上下文需要不同的跨节点并行方式,而分离还创造了节省 VRAM 的机会。对长输入而言,把张量并行扩展到超过一个 8 卡 GPU 节点,需要每层执行两次基于 RDMA 的 all-reduce,这会降低预填充的 MFU;而序列并行仍需频繁跨节点通信,并与 KVCache 传输争抢网络资源。

Mooncake 改为实现分块流水并行(CPP):预填充集群中每 X 个节点组成一个流水线组,每个请求的输入被切成不超过 prefill_chunk 的块,不同块由不同节点同时处理。论文认为这种方式只需在流水线阶段边界做跨节点通信,且可与计算重叠,同时兼顾短上下文和长上下文,无需频繁动态重划分节点。它还把 KVCache 的加载和存储与逐层计算重叠起来,因为预填充是计算受限的。

75% 的增益来自 TBT 达标

真实负载的核心结果容易被误读,因为两个系统在满足两个 SLO 的方式上并不相同。Mooncake 使用 10 个预填充实例和 10 个解码实例,vLLM 使用 20 个实例,TTFT 上限设为 30 秒,TBT 上限设为每 token 0.1 秒。两个系统的 TTFT 分布几乎相同,接近 100% 的请求满足 TTFT SLO。差异出现在 TBT 上:Mooncake 约 100% 的请求达标,而 vLLM 只有 57%,且部分 vLLM 请求的 TBT 极高。

正是这一差异带来了论文报告的容量提升:Mooncake 在遵守 SLO 的前提下多处理了约 75% 的请求。这个数字是在该回放设置下、相同 SLO 目标下已服务请求数的比较,并不意味着 Mooncake 每个请求快 75%。模拟长上下文实验是另一组比较,基线配置不同,vLLM 为避免解码受干扰而逐个处理请求,Mooncake 在 16k、32k、64k 和 128k 提示长度上的优势为 50% 至 525%。

提前拒绝避免无效预填充

Mooncake 运行在严重过载的场景中,而不是假设所有请求都会被处理,因此准入控制也成为吞吐的一部分。基线策略在两个阶段开始前就依据负载拒绝请求,会把预填充本可处理的请求拒之门外,从而浪费资源。论文的提前拒绝策略更全面地考虑系统负载,而基于预测的提前拒绝则进一步预测预填充阶段之后的解码负载。

在一个使用 8 个预填充实例和 8 个解码实例、以 2 倍速度回放 23,000 条真实请求的过载实验中,基线拒绝了 4,183 个请求,提前拒绝拒绝了 3,771 个,基于预测的提前拒绝拒绝了 3,589 个。论文把改进归因于避免了不必要的预填充计算,以及平滑了两个池之间的负载波动。它的预测是系统级而非请求级的:假设每个请求的解码时间相同,估计批次数或 TBT 状态,因为在资源紧张时预测单个请求的输出长度被描述为成本高或不准。

吞吐结论的适用边界

证据支持的是一项具体主张:对于类似 Kimi 轨迹的长上下文、预填充偏重的负载,即输入 token 远多于输出 token 的场景,把预填充与解码分离并围绕 KVCache 调度,能在固定的 TTFT 和 TBT 目标内提高已服务请求数。525% 属于特定提示长度、且 vLLM 基线被迫逐个处理请求的模拟场景;75% 属于特定集群规模和 SLO 设置下的真实轨迹回放。两个数字都不会自动迁移到其他模型规模、其他 SLO 阈值或输入输出比不同的负载上。

论文还指出,其缓存容量结论来自一小时的样本,而请求级输出长度预测留待未来工作,这也是已部署的拒绝策略依赖较粗的系统级估计的原因。因此论文确立的实际优先级不是原始批次大小,而是缓存复用与 SLO 达标的配对:TBT 上的差距,即达标请求比例 100% 对 57%,才是多服务请求背后的机制,任何部署决策都应针对这一延迟目标来解读,而不是只看吞吐。

本文引用的文献

1

Mooncake: A KVCache-centric Disaggregated Architecture for LLM Serving

把预填充与解码集群分离,并围绕分离式 KVCache 池进行调度,使 Mooncake 在真实负载下满足 TTFT 和 TBT SLO 的同时,比 20 实例的 vLLM 基线多服务约 75% 的请求;模拟长上下文场景的提升为 50% 至 525%。