[FAST'25] Mooncake: Trading More Storage for Less Computation — A KVCache-centric Architecture for Serving LLM Chatbot 论文阅读
论文: Mooncake: Trading More Storage for Less Computation — A KVCache-centric Architecture for Serving LLM Chatbot (FAST’25)
作者: Ruoyu Qin, Zheming Li, Weiran He, Jialei Cui, Feng Ren, Mingxing Zhang, Yongwei Wu, Weimin Zheng, Xinran Xu (Moonshot AI, 清华大学)
开源: https://github.com/kvcache-ai/Mooncake
#0. 摘要
Mooncake 是 Moonshot AI 推出的 LLM 聊天机器人服务 Kimi 的底层服务平台. 该平台采用以 KVCache 为中心的分离式架构: 不仅将 Prefill 集群与 Decoding 集群分离, 还充分利用 GPU 集群中未被充分使用的 CPU, DRAM, SSD 和 NIC 资源, 构建出一个分离式的 KVCache 池. Mooncake 的核心是其以 KVCache 为中心的全局缓存, 以及一个在满足严格的延迟类服务级别目标 (SLO) 的前提下最大化吞吐量的调度器.
实验表明, Mooncake 在长上下文输入场景下表现尤为出色. 在真实 trace 的测试中, 与基线方法相比, 在满足 SLO 的前提下 Mooncake 将有效请求容量提升了 59%~498%. 目前 Mooncake 已运行在数千个节点上, 每日处理超过 1000 亿 token. 在实际部署中, 与此前的系统相比, Mooncake 的创新架构使 Kimi 在 NVIDIA A800 与 H800 集群上分别多处理 115% 和 107% 的请求.

图 1: Mooncake 在真实对话负载及不同 TBT SLO 下的有效请求容量. 该实验中 Mooncake 与三个基线系统均使用 16 个 8×A800 节点. 详见 §5.2.
#1. 引言
随着大语言模型 (LLM) 在各类场景中被迅速采用, LLM 服务的负载变得显著多样化. 这些负载在输入/输出长度, 到达分布上各不相同, 最重要的是, 它们对服务级别目标 (SLO) 的要求也不同. 作为模型即服务 (MaaS) 提供商, Kimi 的首要目标之一是求解一个带有多种复杂约束的优化问题: 优化目标是最大化整体有效吞吐量 (它直接影响收入), 约束则对应不同级别的 SLO. 这些 SLO 通常是与延迟相关的要求, 主要是首 token 时间 (Time To First Token, TTFT) 和 token 间时间 (Time Between Tokens, TBT).
为实现这一目标, 前提是充分利用 GPU 集群中的各类资源. 具体而言, 尽管 GPU 服务器目前以高度集成的节点形式提供 (如 DGX/HGX 超算), 仍有必要将它们解耦并重构为若干分离的资源池, 每个池针对不同但相互协作的目标进行优化. 例如, 许多研究 [7-9] 提出将 Prefill 服务器与 Decoding 服务器分离, 因为 LLM 推理的这两个阶段具有截然不同的计算特性.
沿着这一分离思路进一步推进, 我们聚合 GPU 集群中的 CPU, DRAM, SSD 和 RDMA 资源, 构建了一个分离式 KVCache, 称为 Mooncake Store. 这种新型架构利用了未被充分使用的资源, 实现高效的近 GPU 前缀缓存, 显著提升了全局缓存容量与节点间传输带宽. 由此得到的分布式 KVCache 系统体现了以更多存储换取更少计算 (trading more storage for less computation) 的原则. 如图 1 所示, 它显著提高了 Kimi 在许多重要真实场景中满足所需 SLO 的最大吞吐能力. 本文后续将首先对该策略给 LLM 服务带来的收益进行数学分析, 并用真实数据验证其效果 (§2.2); 然后详述实现这个 PB 级分离式缓存 (通过最高 8×400 Gbps 的 RDMA 网络互联) 的设计选择 (§3.2).
在此基础上, 我们还发现 KVCache 的调度是 LLM 服务的核心, 因此提出了相应的分离式架构. 图 2 展示了我们当前以 KVCache 为中心的分离式 LLM 服务架构, 命名为 Mooncake. 对每个请求, 全局调度器 (Conductor) 会选择一对 Prefill 与 Decoding 实例, 并按以下步骤调度请求:
- 将尽可能多的可复用 KVCache 传输到选定的 Prefill 实例;
- 以分块 (chunk) / 分层 (layer) 的方式完成 Prefill 阶段, 并持续将输出的 KVCache 流式传输到对应的 Decoding 实例;
- 在 Decoding 实例处加载 KVCache, 并将请求加入连续批处理 (continuous batching) 以生成输出.

图 2: Mooncake 架构.
虽然这一过程看似简单, 但由于诸多限制, 选择策略相当复杂. 在 Prefill 阶段, 主要目标是尽可能复用 KVCache 以避免重复计算. 然而分布式 KVCache 池在容量和访问延迟上都面临挑战. 因此 Conductor 负责以 KVCache 感知的方式调度请求, 并相应地执行换出, 复制等调度操作: 最热的块应复制到多个节点以避免读取拥塞, 最冷的块则应被换出以降低保留成本. 相比之下, Decoding 阶段有不同的优化目标与约束: 目标是在一个 Decoding batch 中聚合尽可能多的 token 以提升模型 FLOPs 利用率 (MFU), 但这不仅受 TBT SLO 限制, 还受 VRAM 能容纳的聚合 KVCache 总大小限制.
在 §4 中, 我们将详细介绍以 KVCache 为中心的请求调度算法, 它以 TTFT 和 TBT SLO 衡量用户体验, 同时平衡各实例的负载. 其中包括一种基于启发式的自动热点迁移方案, 无需精确预测未来 KVCache 使用量即可复制热点 KVCache 块. 实验结果表明, 我们以 KVCache 为中心的调度在真实场景下可显著降低 TTFT.
我们还将描述实现过程中的主要设计选择, 尤其是现有研究中未涉及的部分. 例如, 关于 P/D 分离, 目前学界对其在大规模实践中的可行性存在争论, 原因在于带宽需求以及与分块 Prefill (如 Sarathi-Serve [10]) 之间的权衡. 我们通过与 vLLM 对比证明: 借助高度优化的传输引擎, 通信挑战是可以应对的, 且在 SLO 限制严格的场景下 P/D 分离更优 (§5.2). 此外, 我们讨论了如何实现一个能无缝应对上下文长度动态分布的独立 Prefill 节点池. 我们采用 分块流水线并行 (Chunked Pipeline Parallelism, CPP) 机制, 将单个请求的处理扩展到多个节点, 这对降低长上下文输入的 TTFT 是必要的. 与传统基于序列并行 (SP) 的方案相比, CPP 降低了网络消耗, 并简化了对频繁弹性伸缩的依赖 (§3.3).
Mooncake 目前是 Kimi 的服务平台, 并已成功应对负载的指数级增长 (每日超过 1000 亿 token). 根据历史统计, 与此前的系统相比, Mooncake 的创新架构使 Kimi 在 A800 和 H800 集群上分别多处理 115% 和 107% 的请求.
为在保护专有信息的同时保证结果可复现, 我们还基于真实负载的回放 trace, 使用一个与 LLaMA3-70B 架构一致的 dummy 模型提供了详细的实验结果. 这些 trace 以及 Mooncake 的 KVCache 传输基础设施已开源.
在使用公开数据集和真实负载的端到端实验中, Mooncake 在长上下文场景下表现突出. 与基线方法相比, 在满足 SLO 的前提下, Mooncake 的有效请求容量最高提升 498%. 在 §5.3 中, 我们将 Mooncake Store 与本地缓存设计比较, 发现其全局缓存设计显著提升了缓存命中率: 命中率最高可达本地缓存的 2.36 倍, 使 Prefill 计算时间最多节省 48%. 据我们所知, Mooncake 是首个在大规模部署场景中, 展示使用分布式 KVCache 池在不同聊天会话与查询间共享 KVCache 能带来显著收益的系统. 我们还评估了 Mooncake 中支持高速 RDMA 传输的传输引擎, 其速度约为现有方案的 2.4 倍和 4.6 倍 (§5.4).
#2. 背景与问题定义
#2.1 LLM 服务的服务级别目标
现代大语言模型 (LLM) 基于 Transformer 架构, 使用注意力机制和多层感知机 (MLP) 处理输入. GPT [11] 和 LLaMA [12] 等主流 Transformer 模型采用 decoder-only 结构. 每个推理请求在逻辑上分为两个阶段: Prefill 阶段和 Decoding 阶段.
Prefill 阶段并行处理所有输入 token, 因此通常是计算密集型的. 该阶段生成第一个输出 token, 同时保存计算得到的 key 和 value 中间结果, 称为 KVCache. Decoding 阶段随后利用 KVCache 自回归地生成新 token. 受自回归生成的限制, 它每个 batch 一次只处理一个 token, 因此是访存受限的, 计算时间随 batch size 次线性增长. 因此 Decoding 阶段一个广泛使用的优化是连续批处理 (continuous batching) [13, 14]: 每次迭代前, 调度器检查状态, 将新到达的请求加入 batch, 同时移除已完成的请求.
由于 Prefill 和 Decoding 阶段特性不同, MaaS 提供商为它们设定了不同的指标来衡量相应的 SLO. 具体来说, Prefill 阶段主要关注请求到达到生成第一个 token 之间的延迟, 即首 token 时间 (TTFT). Decoding 阶段则关注同一请求连续两次 token 生成之间的延迟, 即 token 间时间 (TBT).
在实际部署中, 如果监控发现 SLO 未达成, 我们需要增加推理资源或拒绝部分新到达的请求. 然而由于目前 GPU 供应紧张, 对推理集群做弹性扩容通常不可行. 因此我们主动拒绝那些预计无法满足 SLO 的请求, 以减轻集群负载. 我们的主要目标是在满足 SLO 的前提下最大化整体吞吐量, 这一概念在其他研究 [8, 15] 中称为 goodput.
#2.2 以更多存储换取更少计算
为满足上述严格的 SLO, 一种常见方案是缓存此前生成的 KVCache, 并在发现前缀匹配时复用. 然而现有方法 [16-18] 通常将缓存限制在本地 HBM 和 DRAM 中, 认为全局调度所需的传输带宽过高. 但正如 §5.3 所述, 本地 DRAM 的容量仅能支撑理论缓存命中率的最多 50%, 因此设计全局缓存是必要的. 本节通过数学分析给出获得该策略收益所需的实际带宽, 解释为何分布式缓存是有利的, 尤其是对 LLaMA3-70B 这类较大的模型. 更多实验结果见 §5.4.2.
我们的分析基于表 1 的符号, 并代入 LLaMA3-70B 的具体参数.
| 符号 | 含义 | 取值 |
|---|---|---|
| 层数 | 80 | |
| 模型维度 | 8192 | |
| 公式 1 中的常数系数 | 4, 22 | |
| q head 数 / kv head 数 | 8 | |
| 张量元素大小 | 2 B (BFloat16) | |
| GPU 计算吞吐 | 8×312 TFLOPS | |
| Host 到 Device 带宽 | 128 GB/s | |
| NIC 带宽 | 800 Gbps | |
| 分别为 prompt 长度与匹配的前缀长度 |
表 1: 符号与参数. 模型与机器参数按 LLaMA3-70B 和 8×A800 设定.
当前主流 LLM 本质上都是自回归语言模型, 每个 token 的 KVCache 仅依赖于它自身及之前的 token. 因此, 对应相同输入前缀的 KVCache 可以在不影响输出精度的前提下复用. 若当前请求长度为 的 prompt 与此前缓存的 KVCache 共享长度为 的公共前缀, 则其 Prefill 过程可优化为:
给定输入长度, Prefill 阶段的 FLOPs 可计算为:
因此, 复用 KVCache 大约可将 Prefill 的计算开销降低. 但这需要把缓存的 KVCache 传输到 Prefill GPU 的 HBM 中, 其大小为. 设平均计算吞吐为, 平均 KVCache 加载速度为 ( 由 与 中的较小者决定), 则复用 KVCache 对 TTFT 有利的条件是:
在这种情况下, 复用 KVCache 不仅减少了 GPU 时间和成本, 还通过改善 TTFT 提升了用户体验. 带宽 相对计算吞吐 的要求, 在 (与模型规模成正比) 越大时越容易满足. 例如, 在 8×A800 GPU 的机器上运行 LLaMA3-70B 且前缀长度为 8192 时, 公式 2 给出所需的最小 为 6 GB/s; 对 8×H800 机器该要求则提高到 19 GB/s. 此外, 在实际场景中, 由于各传输阶段无法完美重叠, 实际所需带宽还会更高. 不过, 正如 §5.4.2 将展示的, 每台 NVIDIA A800 HGX 配备一个被充分利用的 100 Gbps NIC 已足以满足这些条件.
#3. Mooncake 的设计
#3.1 概览
如图 2 所示, Mooncake 采用分离式架构: 不仅将 Prefill 节点与 Decoding 节点分开, 还把 GPU 集群的 CPU, DRAM, SSD 和 RDMA 资源聚合起来实现分离式 KVCache. 为调度所有这些分离的组件, Mooncake 在中心实现了名为 Conductor 的全局调度器. Conductor 根据当前 KVCache 的分布和负载特征分发请求. Mooncake Store (§3.2) 则负责管理和传输这些 KVCache 块.

图 3: 推理实例的工作流程.
图 3 展示了一个请求的典型工作流程. 完成 tokenize 后, Conductor 选出一对 Prefill 节点和一个 Decoding 节点, 并开始一个包含四步的工作流:
- KVCache 复用 (KVCache Reuse): 被选中的 Prefill 节点 (组) 收到的请求包含原始输入, 可复用前缀缓存的 block key, 以及分配给该请求的完整缓存的 block key. 它根据前缀缓存的 block key, 将前缀缓存从远端 CPU 内存加载到 GPU 内存来启动请求. 若不存在前缀缓存, 则跳过此步. 这一选择需在三个目标间取得平衡: 尽可能多地复用 KVCache, 均衡不同 Prefill 节点的负载, 以及保证 TTFT SLO. 这就引出了以 KVCache 为中心的调度, 将在 §4 进一步讨论.
- 增量 Prefill (Incremental Prefill): Prefill 节点利用前缀缓存完成 Prefill 阶段, 并把新生成的增量 KVCache 存回 CPU 内存. 如果未缓存的输入 token 数超过某个阈值, 则把 Prefill 阶段切分成多个 chunk 并以流水线方式执行. 该阈值的选取是为了充分利用对应 GPU 的算力, 通常大于 1000 个 token. 为什么使用分块但仍保持分离的 Prefill 节点, 见 §3.3.
- KVCache 传输 (KVCache Transfer): 每个节点都部署了 Mooncake Store 来管理和传输这些缓存. 该步骤异步执行并与上面的增量 Prefill 重叠, 将每一层模型生成的 KVCache 流式传输到目标 Decoding 节点的 CPU 内存, 以减少等待时间.
- Decoding: 所有 KVCache 都到达 Decoding 节点的 CPU 内存后, 请求以连续批处理的方式加入下一个 batch. Decoding 节点由 Conductor 根据其当前负载预先选定, 以确保不违反 TBT SLO.
#3.2 Mooncake Store: KVCache 的缓存
Mooncake 的核心是其对 KVCache 分布式全局缓存的高效实现, 称为 Mooncake Store. 如 §2.2 所述, 复用缓存的 KVCache 不仅削减计算成本, 还能通过降低 TTFT 改善用户体验, 尤其是在聚合带宽被充分利用时. 然而要做到充分利用并不容易, 因为带宽最高可达 8×400 Gbps, 与 DRAM 带宽相当.
我们先在 §3.2.1 介绍 Mooncake Store 如何管理 KVCache, 包括存储方案和淘汰策略; §3.2.2 描述 Mooncake Store 基于对象的 API 和内存传输 API; §3.2.3 详述其传输引擎的设计 – 一个高性能, 零拷贝的 KVCache 传输系统, 旨在最大化每台机器使用多块 RDMA NIC 的收益, 并通过拓扑感知的路径选择和 endpoint 池化等技术提升执行效率与可靠性.
#3.2.1 KVCache 管理
在 Mooncake Store 中, 所有 KVCache 都以分页块 (paged block) 的形式存储在分布式缓存池中. 块大小 (即每个块包含的 token 数) 由模型大小和最优网络传输大小决定, 通常在 16 到 512 个 token 之间. 每个块附带一个 hash key, 它由块自身的 hash 及其前缀共同决定, 用于去重. 同一个 hash key 可在不同节点上有多个副本, 以缓解热点缓存的访问延迟, 副本数由 §4.2 描述的缓存负载均衡策略控制.
Mooncake Store 为缓存池中的每个缓存块分配空间, 并记录 block key 及其地址等元数据. 当缓存池已满时, Mooncake Store 使用 LRU (最近最少使用) 策略淘汰已有缓存块 (除非该块正被某个进行中的请求访问), 并用新块覆盖被淘汰块的空间.
#3.2.2 接口
在较高层, Mooncake Store 提供基于对象的 API, 例如 put(), get() 和 change_replica(). 它们以分离的方式缓存 KVCache: 把 KVCache 的小块组织为内存对象, 并使 Conductor 能调整每个 KVCache 块的副本数以获得更高的聚合带宽. 这些函数由一组同步的批量传输 API 支撑, 详见 Listing 1.
传输操作同时适用于 DRAM 和 GPU VRAM, 在最优时 (前提是指定的内存区域已预先注册) 会使用 GDR. 这些操作的完成情况可通过 getTransferStatus() API 异步监控, 它会报告传输是否仍在进行或已出错.
1 | // 在本地 DRAM/VRAM 上注册一段起始地址为 `vaddr`、长度为 `len` 的内存空间. |
#3.2.3 传输引擎
为高效实现上述 API, 传输引擎的设计围绕几个关键目标:
- 有效地将传输任务分发到多个 RDMA NIC 设备;
- 向 API 屏蔽 RDMA 连接管理的复杂性;
- 妥善处理暂时性的网络故障.
该传输引擎针对这些目标都做了精心的工程实现.

图 4: Mooncake Store 的传输引擎.
#3.2.3.1 网络环境
Mooncake 的收益依赖于高带宽网络互联. 目前我们使用标准 HGX 机器, 每块 A800 GPU 配一块 100/200 Gbps 的 NIC, 每块 H800 GPU 配一块 200/400 Gbps 的 NIC. 这一带宽与内存带宽相当, 而现有库 (NCCL 除外) 都无法充分利用这些容量. 至于 NCCL, 它无法很好地处理因节点/NIC 增减引起的动态拓扑变化, 且不支持 DRAM 到 DRAM 的路径. 相比之下, 我们的传输引擎在失败时会尝试寻找替代路径.
为应对拥塞, 网络使用云服务商调优过的 RoCEv2. 在调度器层面, 我们通过增加热点 KVCache 的副本数来缓解拥塞 (§4.2).
#3.2.3.2 拓扑感知的路径选择
现代推理服务器通常包含多个 CPU 插槽, DRAM, GPU 和 RDMA NIC 设备. 虽然技术上可以用任意 RDMA NIC 将数据从本地 DRAM 或 VRAM 传输到远端, 但这些传输可能受限于超级通路互连 (UPI) 或 PCIe Switch 的带宽. 为克服这些限制, Mooncake Store 实现了拓扑感知的路径选择算法.
在处理请求之前, 每台服务器会生成一个拓扑矩阵并在集群内广播. 该矩阵针对不同类型的内存 (类型在内存注册时指定), 把 NIC 分为 “preferred” (首选) 和 “secondary” (次选) 两个列表. 正常情况下, 传输会从首选列表中选取 NIC, 这样 RDMA 操作只发生在本地 NUMA 内, 或仅通过本地 PCIe Switch 做 GPUDirect RDMA. 出现故障时, 两个列表中的 NIC 都可使用. 整个过程包括: 根据内存地址确定合适的本地与目标 NIC, 建立连接, 执行数据传输.
例如图 4 所示, 要把本地节点 buffer 0 (分配在 cpu:0) 的数据传到目标节点的 buffer 1 (分配在 cpu:1), 引擎先用本地服务器的拓扑矩阵找出 cpu:0 的首选 NIC 并选一个 (如 mlx5_1) 作为本地 NIC. 类似地, 根据目标内存地址选出目标 NIC (如 mlx5_3). 这样即可建立从 mlx5_1@local 到 mlx5_3@target 的 RDMA 连接, 执行 RDMA 读写.
为进一步最大化带宽利用率, 单个请求的传输在内部被切分为多个 16 KB 粒度的切片 (slice). 每个切片可使用不同的路径, 使所有 RDMA NIC 协同工作.
#3.2.3.3 Endpoint 管理
Mooncake Store 用一对 endpoint 表示本地 RDMA NIC 与远端 RDMA NIC 之间的连接. 实际上每个 endpoint 包含一个或多个 RDMA queue pair 对象. Mooncake Store 中的连接按需建立, 在首个请求到来前 endpoint 保持未配对状态.
为防止大量 endpoint 拖慢请求处理, Mooncake Store 使用 endpoint 池化, 限制活跃连接的最大数量, 并使用 SIEVE [19] 算法管理 endpoint 的淘汰.
SIEVE 是 NSDI’24 论文 “SIEVE is Simpler than LRU: an Efficient Turn-Key Eviction Algorithm for Web Caches”(Zhang, Yang 等)提出的缓存淘汰算法。它的实现和 FIFO 一样简单,但命中率通常比 LRU 更高。
若某连接因链路错误而失败, 则会从两侧的 endpoint 池中移除, 并在下次传输尝试时重新建立.
#3.2.3.4 故障处理
在多 NIC 环境中, 一种常见故障场景是某块 NIC 暂时不可用, 而其他路径仍可连通两个节点. Mooncake Store 能有效应对此类暂时性故障: 若发现某连接不可用, 它会自动找出另一条可达路径, 并将请求重新提交给不同的 RDMA NIC 设备. 此外, Mooncake Store 还能检测其他 RDMA 资源 (包括 RDMA context 和 completion queue) 的问题, 并暂时避免使用这些资源, 直到问题 (如链路中断) 解决.
#3.3 Mooncake 的 Prefill 池
与不可侵犯的 Decoding 节点不同, 设计独立且有弹性的 Prefill 池的必要性和最佳实践仍存在争议. 例如, 尽管许多研究者 [7-9] 与我们一样倾向采用分离式架构, 但引入分块 Prefill (chunked prefill) [10] 之后, 这种分离是否仍有必要值得讨论.
不过经过仔细考虑, 我们决定保持 Mooncake 的分离式架构. 这一决定主要是因为在线服务通常有更严格的 SLO. 分块 Prefill 虽然减轻了对 Decoding 的干扰, 但要同时最大化 Prefill 阶段的 MFU 并满足 Decoding 阶段的 TBT SLO 仍然困难, 我们将在 §5.2 的端到端实验中展示这一点. 另一个重要原因是, 随着近期 LLM 的可用上下文长度从 8k 迅速增长到 128k 甚至 100 万 token [20], 我们认为 Prefill 节点需要不同的跨节点并行设置来处理长上下文. 对这类长上下文请求, 输入 token 数可能是输出 token 数的 10 到 100 倍, 因此优化 TTFT 至关重要. 由于长上下文 Prefill 中并行度充裕, 使用超过单个 8×GPU 节点来并行处理是可取的. 然而把张量并行 (TP) 扩展到多个节点, 每层需要两次开销高昂的基于 RDMA 的 all-reduce, 会显著降低 Prefill 节点的 MFU.
近来许多工作提出了序列并行 (SP) [21-27]. SP 把请求的输入序列切分到不同节点上以实现加速, 使得即使长请求也能满足 TTFT SLO. 但用于较短输入的请求时, SP 的 MFU 比仅使用单节点 TP 更低. 近期研究 [15] 提出弹性序列并行来动态扩缩 SP 组, 虽然可行, 但会给我们的架构增加复杂性. 此外, SP 仍需要频繁的跨节点通信, 既降低 MFU, 又与跨节点传输 KVCache 争用网络资源.
为此, Mooncake 利用 decoder-only Transformer 的自回归特性, 对长上下文 Prefill 实现了分块流水线并行 (Chunked Pipeline Parallelism, CPP). 我们将 Prefill 集群中每 X 个节点划为一个流水线化的 Prefill 节点组. 对每个请求, 其输入 token 被切成多个 chunk, 每个不超过 prefill_chunk. 同一请求的不同 chunk 可由不同节点同时处理, 从而并行化处理并降低 TTFT.
CPP 有两大好处:
- 与训练中的流水线并行类似, 它只需在每个流水线阶段的边界进行跨节点通信, 这很容易与计算重叠. 因此 MFU 更好, 与 KVCache 传输的网络资源争用也更少.
- 它天然适配短上下文和长上下文, 对短上下文 Prefill 没有显著开销, 也避免了频繁动态调整节点划分.
这种基于流水线的加速方法此前已在训练系统 [28] 中被探索过, 但据我们所知, 这是它在推理阶段的首次应用, 因为长上下文推理是近来才出现的.
#4. 调度
#4.1 Prefill 全局调度
以往的 LLM 服务研究通常采用负载均衡策略, 根据分配的请求数评估各实例的负载. 而在 Mooncake 中, Prefill 实例的选择还要考虑额外因素: 不仅是负载, 还包括前缀缓存命中长度以及可复用 KVCache 块的分布. 虽然倾向于把请求路由到前缀缓存更长的 Prefill 实例以降低计算成本, 但为保证系统整体均衡并满足 TTFT SLO, 把它们调度到其他节点可能更有益. 为解决这些复杂性, 我们提出了一种缓存感知的全局调度算法, 同时考虑前缀缓存带来的 Prefill 时间和本地排队时间.
算法 1 详述了我们以 KVCache 为中心的 Prefill 调度机制. 对每个新请求, 先将其 block key 逐一与每个 Prefill 实例的缓存 key 比较, 得到前缀匹配长度 (prefix_len). 有了这一匹配信息, Conductor 基于请求长度和 prefix_len (因实例而异), 使用由离线数据拟合的多项式回归模型估计对应的执行时间. 然后加上该请求在该实例上的预估等待时间, 得到该实例上的 TTFT. 最后 Conductor 将请求分配给 TTFT 最短的实例, 并相应更新该实例的缓存与队列时间. 如果 SLO 无法达成, Conductor 直接向上层返回 HTTP 429 Too Many Requests 状态码.
算法 1: 以 KVCache 为中心的调度算法
1 | 输入: Prefill 实例池 P, Decoding 实例池 D, 请求 R, 缓存块大小 B |
即: 当某实例的本地前缀长度相对全局最佳匹配长度差距足够大 (比值超过阈值) 时, 才值得从最佳实例拉取 KVCache (第 5-8 行, 第 20-21 行); 否则直接使用本地前缀, 不产生传输开销.
这个调度框架的骨架很直接, 但复杂性隐藏在各个组件的工程实现中. 例如, 为预测请求 Prefill 阶段的计算时间, 我们使用基于离线测试数据得到的预测模型, 它根据请求长度和前缀缓存命中长度估计 Prefill 时长. 得益于 Transformer 规则的计算模式, 只要离线数据足够, 该预测的误差界就很小. 请求的排队时间则通过累加所有排队请求的 Prefill 时间得到. 在实际实现中, TTFT 的计算是并行的, 其处理时间相对推理时间可忽略不计.
更困难的是预测传输时间, 因为它不仅取决于传输的数据量, 还取决于当前网络状态, 尤其是发送节点是否处于拥塞中. 这也要求对热点 KVCache 块做复制, 将在 §4.2 讨论.
#4.2 缓存负载均衡
在 Mooncake 中, 每个 Prefill 实例都有自己的一组本地前缀缓存, 这些缓存的使用频率差异显著. 例如, system prompt 几乎被每个请求访问, 而存储某个本地长文档内容的缓存可能只被一个用户使用. 如 §4.1 所述, Conductor 的作用对于在缓存匹配与实例负载间取得最优平衡至关重要. 因此从分布式缓存系统的角度看, 负载均衡同样重要: 具体来说, 需要制定如何备份缓存的策略, 使全局 Prefill 调度既能获得高缓存命中, 又能保持低负载.
针对这一 KVCache 调度问题, 一个朴素方案是收集每个块的全局使用情况, 用预测模型预测其未来使用量, 并据此做调度决策. 然而与 Prefill 时间的估计不同, 负载高度动态, 随时间变化剧烈. 尤其对用户量快速增长的 MaaS 提供商而言, 无法准确预测未来使用量. 因此我们提出一种基于启发式的自动热点迁移方案来增强缓存负载均衡.
如前所述, 由于实例负载高, 请求不一定总能被导向前缀缓存最长的 Prefill 实例. 此时, 若预估的额外 Prefill 时间短于传输时间, Conductor 会把缓存的位置和请求一起转发给另一个实例, 该实例主动从持有者处取回 KVCache 并存储在本地. 更重要的是, 如果最佳远端前缀匹配长度不大于 "当前本地可复用前缀长度 × 一个阈值"1, 我们倾向于直接计算输入 token. 这两种策略不仅缩短了请求的 Prefill 时间, 也促进了热点缓存的自动复制, 使其更广泛地分布到多个实例上.
1 该阈值目前为手动调节, 未来可通过算法自适应调整.
为验证策略的有效性, 我们开展了调度实验, 将随机调度, 负载均衡调度与我们的策略进行比较. 我们进一步比较了 §4.1 中描述的本地缓存感知调度, 与本节描述的考虑缓存负载均衡的全局缓存感知调度. 随机调度中, 每个请求任意选择一个 Prefill 实例; 负载均衡调度中, 选择负载最轻的实例. 具体地, 我们搭建了由 16 个 8×A800 节点组成的 Mooncake 集群, 并回放 §5.2.1 中的对话 trace 进行实验, 以 TTFT 评估各调度算法的性能. 图 5 的实验结果表明, 我们以 KVCache 为中心的调度算法优于随机调度和负载均衡调度. 通过引入缓存负载均衡, 全局缓存感知算法相比本地缓存感知算法又将平均 TTFT 降低了 14%.

图 5: Prefill 调度实验 (平均 TTFT: Global Cache Aware 3.07 s, Local Cache Aware 3.58 s, Load balancing 5.27 s, Random 19.65 s).
#5. 评估
如前所述, 根据 Kimi 的历史统计, 与此前基于 vLLM 的系统相比, Mooncake 使 Kimi 在 A800 和 H800 集群上分别多处理 115% 和 107% 的请求. 为进一步验证这一结果并保证可复现, 本节使用 dummy 的 LLaMA3-70B 模型对 Mooncake 做了一系列端到端实验和消融实验, 回答以下问题:
- Mooncake 在真实场景中是否优于现有 LLM 推理系统?
- 与传统前缀缓存方法相比, Mooncake Store 的设计是否显著提升了 Mooncake 的性能?
#5.1 实验设置
测试环境. 在复现实验中, 系统部署在一个高性能计算节点集群上. 集群中每个节点配有 8 块 NVIDIA-A800-SXM4-80GB GPU 和 4 块 200 Gbps RDMA NIC. Mooncake Store 中 KVCache 块大小设为 256. 部署 Mooncake 时, 每个节点根据启动参数作为 Prefill 实例或 Decoding 实例运行; 部署其他系统时, 每个节点承载单个实例.
指标. 我们测量每个请求的 TTFT 和 TBT, 其中 TBT 取最长的 10% token 到达间隔的平均值. 如 §2 所述, TTFT 阈值设为 30 s, TBT 阈值根据场景设为 100 ms, 200 ms 和 300 ms. TTFT 与 TBT 都低于各自阈值的请求视为有效请求, 有效请求占全部请求的比例称为有效请求容量. 为简洁起见, 后续实验中未提及 TTFT 的, 均默认满足 TTFT 阈值. 为更细致地比较缓存性能, 我们还测量了每个请求在 Prefill 阶段的 GPU 时间和缓存命中率.
基线. 我们使用最先进的开源 LLM 服务系统之一 vLLM [14] 作为实验基线. vLLM 具有连续批处理和 PagedAttention 技术, 显著提升了推理吞吐. 尽管有这些优势, 它将 Prefill 与 Decoding 阶段耦合的架构可能干扰 Decoding, 在长上下文场景中尤为明显. vLLM 近期的更新集成了前缀缓存和分块 Prefill 等功能, 以改善长上下文场景下的 TTFT 和 TBT 等指标, 我们的实验中也对这些功能做了比较. 实验使用 vLLM 最新发布版本 (v0.5.1), 由于当前实现的限制, 我们分别测试了该版本的前缀缓存与分块 Prefill 功能.
#5.2 端到端性能
端到端实验中, 我们评估 Mooncake 与基线系统在多种负载下的请求处理能力, 具体测量在规定 SLO 阈值内能保持的最大吞吐. 测试使用三类负载: 两个从 Kimi 采样的真实 trace (分别代表在线对话与工具&Agent 交互), 以及一个覆盖不同推理场景的合成负载. 我们先介绍这些负载的特点, 再讨论结果, 最后分析 Prefill 阶段的 GPU 计算时间, 进一步说明 Mooncake Store 在提升缓存利用率和降低计算成本方面的优势.
#5.2.1 负载
| 对话 (Conversation) | 工具&Agent (Tool&Agent) | 合成 (Synthetic) | |
|---|---|---|---|
| 平均输入长度 | 12035 | 8596 | 15325 |
| 平均输出长度 | 343 | 182 | 149 |
| 缓存比例 | 40% | 59% | 66% |
| 到达模式 | Timestamp | Timestamp | Poisson |
| 请求数 | 12031 | 23608 | 3993 |
表 2: 负载统计.
对话负载. 聊天机器人 [1, 5] 是 LLM 最普遍的应用之一, 因此对话请求是极具代表性的 LLM 推理负载. 如表 2 所示, 对话负载包含相当比例的长上下文请求 – 最长可达 128k token, 平均约 12k – 与当前长上下文数据集 [29, 30] 中的数据长度相当. 此外, 多轮对话带来了平均约 40% 的前缀缓存比例. 我们从在线推理集群采样了 1 小时的对话 trace, 每条记录包含输入与输出长度以及到达时间戳. 请求按这些时间戳分发, 并在模型输出达到预定长度时被提前终止.
工具&Agent 负载. 近期将 LLM 部署为工具或 Agent 来执行任务的研究 [31] 日益增多. 这类任务的特点是包含预先设计的, 通常很长且完全重复的 system prompt. 我们同样采样了 1 小时的工具&Agent trace. 如表 2 所示, 该负载前缀缓存比例很高, 输入和输出长度较短.
合成负载. 合成负载由公开数据集组合构建. 我们把真实 trace 中的请求分为三类: 短对话, 工具与 Agent 调用, 长文本摘要与问答. 对每一类, 我们选取以下数据集: ShareGPT [32], Leval [29] 和 LooGLE [30]. ShareGPT 包含输入较短的多轮对话. Leval 是评估模型长上下文性能的基准, 模拟工具与 Agent 交互中常见的长 system prompt 请求. LooGLE 面向长上下文问答与摘要任务, 输入长度最高 100k token, 包含多轮问答和单轮摘要, 很适合长文本摘要与问答场景. 总体上, 合成负载的平均输入长度最长; 虽然前缀缓存比例最高, 但其缓存命中相当分散, 因此需要较大的缓存容量.
预处理时, 每个对话轮次被映射成一个独立请求, 并包含此前交互的输入和输出. 对于同一长 prompt 对应多个问题的数据集, 每个问题连同其之前的 prompt 视作一个请求. 我们将处理后的数据集按 1:1:1 混合, 保持多轮对话请求的先后关系, 并随机打乱. 由于数据集未给出到达时间, 我们用 Poisson 过程以设定速率分发请求, 模拟真实情况.
#5.2.2 有效请求容量
为评估不同负载下能满足 SLO 的最大请求量, 我们测试了四种系统配置: Mooncake, vLLM, 开启前缀缓存的 vLLM, 开启分块 Prefill 的 vLLM, 每种均使用 16 个节点.
对话负载. 结果见图 1. 该负载输入长度多变, 输出较长, 由于 Prefill 阶段的长上下文, vLLM 的 TBT 波动显著. 分块 Prefill 虽然降低了对 Decoding 的干扰, 但在 Prefill 阶段提升 MFU 与满足 Decoding 阶段的 TBT 约束之间取得平衡仍然困难. 尽管满足了 TTFT SLO, 其有效请求容量仍不理想. 与 vLLM 相比, Mooncake 的有效请求容量显著提升.
工具&Agent 负载. 相反, 该负载前缀缓存比例高, 输出较短, 有利于 vLLM 系统: 较短的 Prefill 时间对输出影响极小. 然而如图 6 所示, vLLM 与开启分块 Prefill 的 vLLM 因 Prefill 处理时间较长, Decoding 受到更严重的干扰, 导致有效容量低于开启前缀缓存的 vLLM. Mooncake 使用全局缓存池大幅提升了缓存容量, 并通过节点间传输优化缓存利用, 在前缀缓存比例高的场景下表现出色. 因此在 200 ms 阈值下, 其有效容量比开启前缀缓存的 vLLM 高 42%.

图 6: Mooncake 在工具&Agent 负载下的有效请求容量实验.
合成负载. 该负载平均输入最长, 且缓存热点分散, 导致缓存容量较小时缓存利用率差. 如图 7 所示, Mooncake 处理的大多数请求 TBT 都保持在 100 ms 以内, 而 vLLM 处理的请求中约 20% 超过 300 ms. 开启前缀缓存和分块 Prefill 的系统性能与 vLLM 相近, 因为它们无法缓解长上下文对 Decoding 阶段的影响. 与 vLLM 相比, Mooncake 在 200 ms 阈值下有效请求容量提升了 40%.

图 7: Mooncake 在合成负载下的有效请求容量实验.
#5.2.3 Prefill GPU 时间
Prefill GPU 时间与请求的 TTFT 和服务成本正相关, 由请求的输入长度和缓存命中率决定. 我们分析了不同负载下 Prefill 阶段的平均 GPU 时间, 如图 8 所示.
对 Mooncake 而言, 对话负载的 Prefill GPU 时间最长, 因为其输入更长且前缀缓存比例更低. 合成负载前缀缓存比例最高但热点分散, 在 Mooncake 的全局缓存池中获得了最优的缓存命中率, 因此尽管平均输入最长, 其所需 Prefill GPU 时间却少于对话负载. 最后, 工具&Agent 负载的 Prefill GPU 时间最短, 因为它平均输入长度最短, 且前缀缓存比例相对较高.

图 8: 不同负载下每个请求在 Prefill 阶段的平均 GPU 时间.
跨系统比较, Mooncake 通过充分利用全局缓存做前缀缓存, 显著减少了 GPU 时间: 相比 vLLM, 对话, 工具&Agent 和合成负载分别减少 36%, 53% 和 64%. 开启前缀缓存的 vLLM 使用存储在 HBM 上的本地缓存, 缓存容量远低于 Mooncake, 其 Prefill GPU 时间在对话和工具&Agent 负载下分别是 Mooncake 的 1.43 倍和 1.40 倍. 而在缓存热点更分散的合成负载中, 开启前缀缓存的 vLLM 的 Prefill GPU 时间几乎与 vLLM 持平, 是 Mooncake 的 2.59 倍. 开启分块 Prefill 的 vLLM 为在 Decoding 阶段维持较低 TBT 而牺牲了一部分 Prefill 效率, 因此 Prefill GPU 时间最长, 在三种负载下分别是 Mooncake 的 1.90 倍, 2.68 倍和 3.33 倍.
#5.3 Mooncake Store
为回答问题 2, 我们考察 Mooncake Store 的全局缓存池对系统性能的影响. 分析表明, 虽然用本地 DRAM 构建 KVCache 内存比仅用 HBM 增加了缓存容量, 但把缓存限制在单个节点内仍会导致缓存利用率欠佳. 我们先定量分析缓存容量需求, 再通过实际负载实验展示其收益.
#5.3.1 缓存容量的定量分析
以 LLaMA3-70B 为例, 单个 token 所需的 KVCache 大小为 320 KB. 即使预留约 1 TB 的 DRAM 做本地缓存, 也只能存约 300 万个 token, 这被证明是不够的. 图 9 展示了各负载及其组合下的理论缓存命中率. 结果表明, 容量为 3M token 的本地缓存在大多数场景下达不到理论最大命中率的 50%. 我们还发现, 在这些负载中, 50M token 的缓存容量几乎就能达到理论最大命中率的 100%, 这需要至少池化 20 个节点的 DRAM. 结果凸显了全局缓存相比本地缓存可显著提升容量, 从而提高缓存命中率并减少 GPU 时间.

图 9: 不同缓存容量下前缀缓存命中率的定量分析. 只考虑请求序列, 不考虑 Prefill 计算时间或缓存中热点复制等因素. 3M token 容量处的虚线代表本地缓存容量, 交点标示了缓存命中率与理论最大命中率之比.
#5.3.2 实际负载实验
为评估全局缓存与本地缓存机制的效果, 我们关注两个指标: 缓存命中率和 Prefill 的平均 GPU 计算时间. 我们配置了一个 10 个 Prefill 节点的集群, 并将所有请求的输出限制为 1, 以隔离 Decoding 阶段的影响. 本地缓存配置中, 每个节点有 3M token 的容量, 但只能访问自己的缓存, 全局调度器被设定为将请求导向前缀匹配率更高的节点以最大化缓存利用率. 全局缓存配置中, 每个节点同样有 3M token 容量, 但可在所有节点间共享缓存, 并由主动的节点间缓存迁移支撑. 图 10 的实验数据表明, 在所有测试负载上, 全局缓存都获得了更高的缓存命中率和更短的平均 Prefill GPU 计算时间. 相比本地缓存, 全局缓存的缓存命中率最多提升 136%, Prefill 计算时间最多减少 48%.

图 10: 全局缓存与本地缓存下的缓存命中率及 Prefill 平均 GPU 计算时间.
#5.3.3 缓存副本
基于 §4.2 讨论的缓存负载均衡调度策略, Mooncake Store 中的缓存 key 可能在不同机器上有副本, 以降低热点缓存的访问延迟. 为进一步研究系统的动态行为, 我们统计了三种负载下各缓存 key 的副本数, 如图 11 所示.
可以观察到, 在对话和工具&Agent 负载中存在高度集中的热点缓存 (如排名前 100 的 key), 系统稳定后它们几乎在 Prefill 池的每个实例上都有副本. 相比之下, 合成负载共享的前缀缓存较少, 即使对排名前 10 的块, 副本数也更少且可能出现波动. 这说明 §4.2 的调度策略能有效地为热点缓存提供副本, 在前缀缓存高度集中的场景下尤其如此.

图 11: 各负载下缓存 key 的副本数. 我们每 30 秒持续监控并记录所有缓存块的 key 与副本数, 之后按所有采样的累计计数对缓存 key 排序. 图中展示了排名第 10, 100, 1000 和 10000 的缓存 key 副本数随时间的变化.
#5.4 KVCache 传输性能
#5.4.1 传输引擎
Mooncake 的传输引擎旨在促进节点间高效的缓存传输. 我们将它的延迟与两种常见方案比较: 使用 Gloo 后端的 torch.distributed, 以及基于 TCP 的传输. 所有方案均在并发度 64, 最小传输粒度 128 KB 下测试. 如图 12 所示, 传输引擎的延迟始终显著低于其他方法. 在传输 40 GB 数据 (对应 LLaMA3-70B 128k token 的缓存大小) 的场景中, 在 4×200 Gbps 和 8×400 Gbps 网络配置下, 传输引擎分别达到 87 GB/s 和 190 GB/s 的带宽, 约为 TCP 协议的 2.4 倍和 4.6 倍. 由于该传输引擎是一个解耦的基础工具, 可用于许多场景 (例如也用于 Moonshot AI 的 checkpoint 传输服务), 其代码后续也会开源.

图 12: 节点间缓存传输延迟.
#5.4.2 Mooncake 的带宽需求
Mooncake 的全局缓存池依赖高效的节点间缓存传输, 将缓存传输时间隐藏在 GPU 计算时间之内. 我们通过模拟 24 Gbps 到 400 Gbps 的各种带宽, 在 §5.2.1 的合成负载下测量传输时间和 TTFT, 评估网络带宽对系统性能的影响. 图 13a 显示请求的平均 TTFT 随带宽增加而下降. 当总通信带宽超过 100 Gbps 时, 平均 TTFT 保持在 2 s 以下, 显著低于重计算基线的 TTFT. 但当带宽低于 100 Gbps 时, 系统性能受到显著影响, 表现为 TTFT 急剧上升, 以及网络拥塞明显 (图 13b 中实际与理论传输时间出现显著偏离). 因此我们建议最低网络带宽为 100 Gbps, 以保证最优的系统性能.

图 13: 不同网络带宽下的合成负载实验. (a) 平均 TTFT. (b) 传输时间.
#5.4.3 端到端延迟分解
Mooncake 中单个推理请求的延迟可分解为五部分:
- 调度与排队时间;
- 逐层 Prefill 时间;
- 缓存传输时间;
- Decoding 节点把缓存从 DRAM 加载到 HBM 所需时间;
- Decoding 时间.
我们在前缀缓存比例为 0% 和 95% 的设置下实验分析了这五部分的占比, 如图 14 所示.
首先, 图中可见引入前缀缓存显著缩短了 Prefill 时间: 输入长度为 128k token 时, 前缀缓存使 Prefill 时间减少 92%. 其次, Mooncake 引入的开销对系统性能的影响极小: Schedule, Transfer 和 Load Cache 可与模型推理异步进行, 因此不影响 Mooncake 的吞吐. 而且这些开销带来的 TTFT 增加小于前缀缓存带来的减少. 即使计入这些开销, 输入长度为 128k token 时, Mooncake 中的前缀缓存仍可将 TTFT 降低 86%.

图 14: Mooncake 端到端延迟分解. 图中 Prefill 表示集成了缓存加载和存储的逐层 Prefill 时间, Decode 表示解码 128 个 token 的时间. (a) 前缀缓存比例 0%. (b) 前缀缓存比例 95%.
#5.5 P/D 比例
作为已部署的 P/D 分离系统, 本节探讨不同 P/D 比例对系统性能的影响. 我们将 P/D 比例定义为 Prefill 节点数与 Decoding 节点数之比. 使用 16 个节点但 P/D 比例不同的集群, 我们在 §5.2.1 的合成负载下测量平均 TTFT 和 TBT, 然后按 §5.2.2 介绍的方法计算有效请求容量, TTFT 和 TBT 阈值分别设为 10 秒和 100 毫秒. 增加 Prefill 节点会降低 TTFT 但提高 TBT, 反之亦然 (图 15b), 因此需要在 TTFT 与 TBT 之间取得平衡. 图 15a 表明, 当 P/D 比例约为 1:1 时 Mooncake 达到最高有效请求容量, 说明此时 Prefill 与 Decoding 集群的负载相对均衡.

图 15: P/D 比例对系统性能的影响. P 表示 Prefill 节点, D 表示 Decoding 节点. (a) 不同 P/D 比例下的有效请求容量. (b) 不同 P/D 比例下的延迟.
我们也注意到一些已有工作 [7, 9] 提出在 Prefill 和 Decoding 之间动态切换节点角色. 但在实际部署中, 我们发现在线流量的统计特性通常较为稳定. 因此我们选择固定 P/D 比例, 同时持续监控 Prefill 与 Decoding 集群的负载, 仅在负载出现显著波动时才切换节点角色.
#6. 相关工作
已有大量工作致力于通过调度, 内存管理和资源分离来提升 LLM 服务系统的效率. FasterTransformer [33], TensorRT-LLM [34] 和 DeepSpeed Inference [35] 等生产级系统旨在显著提升吞吐. Orca [13] 采用迭代级调度以支持不同阶段的并发处理, vLLM [14] 通过动态 KVCache 管理优化内存. FlexGen [36], Sarathi-Serve [10] 和 FastServe [37] 引入创新的调度与换出策略, 在有限硬件上有效分配负载, 它们的优化往往可以互补. 进一步的优化 [7-9] 催生了 Prefill 与 Decoding 阶段的分离, 也就是 Mooncake 的分离式架构. Mooncake 的设计建立在这些进展之上, 尤其借鉴了 vLLM 的开源社区, 我们对此深表感谢.
前缀缓存也被广泛采用, 以便跨多个请求复用 KVCache, 降低 LLM 推理系统的计算开销 [14, 34]. Prompt Cache [16] 在推理服务器上预计算并存储常用文本的 KVCache 以供复用, 显著降低了推理延迟. SGLang [17] 利用 RadixAttention, 在基数树结构中使用最近最少使用 (LRU) 缓存, 高效地实现了各种复用模式下的自动共享.
这些方法中, 与我们同期的工作 CachedAttention [38] 提出了一个分层 KV 缓存系统, 利用低成本的内存和存储介质容纳所有请求的 KVCache. Mooncake 的架构与 CachedAttention 有许多共同的设计选择. 然而在长上下文推理中, KVCache 会变得极其庞大, 需要高容量, 高效的数据传输以及以 KVCache 为中心的全局调度. 此外, Mooncake 不是一个独立的缓存服务, 它同时包含内存高效的缓存存储机制和缓存感知的调度策略, 进一步提升了前缀缓存的效率.
分布式 KVCache 池的收益取决于缓存命中率, 而在容量固定时, 每 token 的缓存越小, 命中率越高. 因此 KVCache 压缩 [39-41] 和对 KVCache 友好的注意力架构 [42, 43] 等正交技术可以进一步增强我们的方法.
#7. 结论
本文提出了 Mooncake, 一种以 KVCache 为中心的分离式架构, 用于高效服务 LLM, 尤其适用于长上下文场景. 我们讨论了在满足延迟类 SLO 要求的同时最大化整体有效吞吐这一目标下所涉及的必要性, 挑战与设计选择.