LLM调度的核心KVCache

先说说Mooncake这个事儿。它是Moonshot AI给Kimi服务做的后端平台,核心思路很明确:以键值缓存(KVCache)为中心,搞了一套解耦架构。目的呢?就是在长上下文场景下把服务效能提上去,同时在高负载时还能稳住服务水平目标(SLOs)。简单讲,就是既要反赌,又要扛得住。
这个架构通过把预填充和解码阶段拆开,用KVCache当调度和优化的主轴,效果确实不错。实验数据摆在那里:相比基线方法,吞吐量最高提升了525%,能处理75%以上的实际请求,而且SLOs一点没崩。
Mooncake解决的问题
- 资源利用不均与服务效能
这里的痛点很明显——GPU集群里CPU、DRAM、SSD这些资源经常闲着,但预填充和解码阶段互相抢资源。Mooncake的KVCache中心调度器(Conductor)直接扛起平衡大旗:既追求最大整体有效吞吐量,又死磕延迟相关的SLOs。它通过解耦预填充集群和解码集群,把那些闲置的CPU、DRAM、SSD资源利用起来,实现KVCache的解耦缓存。这样一来,资源利用率上去了,整体吞吐量也水涨船高。
- 长上下文处理效率
长上下文请求是个硬骨头——输入太长,首个token时间(TTFT)容易炸。Mooncake的方案是分块流水线并行(CPP)。具体来说,把一个长请求的输入token切成小块,每块不超过1000个token,然后在多个节点上并行处理。这样TTFT能压下来不少。
- 高负载下的请求调度
负载一高就容易出幺蛾子:预填充阶段好不容易算完了,结果发现解码槽位不够,白忙一场。Mooncake干脆来了个基于预测的早期拒绝策略。提前预测未来负载和生成长度,如果解码那边要扛不住,就直接拒绝请求,避免资源浪费。说白了,就是让系统有选择地接活,而不是来者不拒。
Mooncake架构
整个架构包含几个关键组件:
KVCache中心调度器(Conductor)
预填充实例
解码实例
分布式KVCache池
每个请求来了,Conductor会调度它,选好预填充和解码实例,然后按下面几步走:
- KVCache重用:预填充节点先把可重用的前缀缓存加载到GPU内存里,启动请求。
- 增量预填充:预填充节点完成预填充,把新生成的增量KVCache存回CPU内存。
- KVCache传输:Messenger服务管这些缓存的传输,把每层生成的KVCache流式传输到目标解码节点的CPU内存。
- 解码:解码节点的CPU DRAM收齐所有KVCache后,请求就加入下一个解码批次,用连续批处理的方式跑。
分块流水线并行(CPP)
处理长上下文请求时,CPP机制是这么跑的:
- 输入分割:把请求的输入token切成小块,每块不超过预设阈值。
- 并行处理:不同块的token在不同节点上同时处理,实现并行化。
- 跨节点通信:只在流水线阶段边界处需要跨节点通信,而且通信和计算重叠,减少网络资源竞争。
- 层级预填充:KVCache的加载和存储操作在每一层的注意力计算前后异步执行,传输开销因此大幅降低。
KVCache中心调度算法
调度算法的核心步骤:
- 计算块键:把请求的输入token分成多个块,每个块算一个哈希键。
- 寻找最佳前缀匹配:比较请求的块键和每个预填充实例的缓存键,找出前缀匹配长度。
- 估计预填充执行时间:根据请求长度和前缀长度,估算预填充阶段要花多长时间。
- 选择实例:把请求分配给TTFT最短的实例,同时更新该实例的缓存和队列时间。
- 负载平衡:如果最佳远程前缀匹配长度没超过当前本地可重用前缀的阈值倍数,就触发KVCache的热斑点迁移。
过载场景下的调度
系统过载时,Mooncake的早期拒绝策略分三步:
- 系统负载评估:基于预填充和解码池的负载,决定要不要接这个请求。
- 早期拒绝:在预填充阶段之前,先评估解码实例的负载。如果负载太高,直接拒绝,避免资源白费。
- 基于预测的早期拒绝:预测解码实例未来一段时间内的负载,基于预测结果做决定,减少负载波动的影响。
Mooncake与VLLM对比
模拟长上下文场景,两个框架的表现差异很大。
长上下文请求会严重扰乱vLLM的解码阶段。为了应对,vLLM选择单独处理请求,而不是批量处理——这吞吐量自然就下来了。
Mooncake坚持批量处理,但它那两阶段分解设计巧妙地隔开了预填充对解码的影响,确保TBT SLO始终不受冲击。
对比下来,Mooncake的吞吐量比vLLM强出一大截——提升幅度在50%到525%之间,而且还能满足相同的TTFT和TBT SLO限制。
