资讯动态

KV Cache命中率高还慢?带宽瓶颈与解耦架构解析

发布时间:2026/10/20 5:12:45 来源:尧图企业网站定制
上个月排查一个推理服务的性能问题现象特别典型Prefix Caching 和 KV Cache 命中率做到了 95% 以上TTFT首 token 延迟看着也不差可吞吐和尾延迟就是没法看。查到最后发现大家把缓存命中当成了万灵药可它解的是重复计算的毒压根没解搬运数据的毒。这篇文章就围绕这个事展开为什么 KV Cache 命中了大模型还是慢我会从单卡上的显存带宽讲起再聊 KV 放不进显存、溢出到内存之后的困境最后讲到以 KV Cache 为中心的分布式解耦架构Mooncake 这类方案。适合正在做推理优化、被性能瓶颈折磨的工程师也适合想系统理解大模型为什么慢、不想只看表面指标的读者。先给一个总判断后面所有分析都围绕它大模型推理的 decode逐 token 生成阶段本质上是带宽受限而不是算力受限。KV Cache 命中能省掉一部分计算但省不掉每个 token 都必须把权重和 KV 数据搬进计算单元这件事。带宽瓶颈不解决命中率再高也是隔着靴子挠痒。1. 缓存命中的真相KV Cache 省的是算不是搬1.1 一次解码要搬多少数据先快速回顾一下 KV Cache 是什么。Transformer 生成的时候分两段prefill 阶段处理整段提示词并行算出所有位置的 Key 和 Value并生成第一个 tokendecode 阶段每步只生成一个 token新的 Query 要和前面所有位置的 Key、Value 做注意力。如果每步都重新算一遍完整 attention代价随序列长度线性增长所以大家把前面算过的 Key、Value 存起来这就是 KV Cache。之后生成时只把新 token 的 Key、Value 追加进去不用从头算。从计算的角度KV Cache 命中的确省了一大笔它避免了历史 token 的重复 QK 点积和 softmax 加权求和。对于大量重复前缀的场景同样的系统提示词、相同的问题模板Prefix Caching 还能跳过 prefill 阶段的部分计算TTFT 会明显变好。但 decode 阶段的每一步GPU 要干的事远不止注意力计算。无论有没有缓存每个 token 都要做一次完整前向传播读一遍全部模型权重一大块数据再从 KV Cache 里读出历史位置的 K、V 参与注意力最后写回新 token 的 K、V。也就是说命中缓存并没有减少这一步要搬的权重也没有减少要读的历史 KV——它只是把这些数据从现算变成现取。给个直觉数字。一个 7B 参数、FP16 精度的模型光权重就是 14GB。假设显存带宽 2TB/sHBM 级别的高端卡batch size 为 1、序列不长、KV Cache 可忽略时每生成一个 token 至少要搬 14GB 权重理论上限就是 2e12 / 14e9约 140 token/s 出头。注意这还没算 KV、没算激活、没算任何软件开销物理墙已经贴脸了。1.2 命中率 95%为什么吞吐还是上不去现在可以回答标题的问题了。命中率高说明 prefill 阶段被跳过的计算多TTFT 好看但吞吐每秒生成多少 token和尾延迟主要由 decode 阶段决定而 decode 的瓶颈在带宽不在缓存命中率。我见过不少团队把 KV Cache 命中率当作服务健康度的核心指标。但实际上这个指标更像省了多少 prefill 算力跟每个 token 生成要多久没有直接关系。命中了缓存只是让你别重复造轮子可 decode 每走一步还是要从显存里把权重和 KV 扛一遍轮子该转还是得转。所以真正要盯的是后面几个物理量模型权重多大、KV Cache 多大、显存带宽多少、batch 多大。命中率再高权重和 KV 的搬运量一点没减QPS 自然上不去。把这个想通了再去看各种优化手段就能分清楚它们到底在解决哪一层的问题。2. 显存带宽这道墙解码速度的物理上限怎么算2.1 一张带宽对照表先摆一张表把常见数据通道的带宽拉出来对比。你会发现差距不是几倍是几十倍、上百倍。通道典型设备峰值带宽粗略说明HBM3新一代数据中心卡3TB/s 以上显存里最快的车道HBM2e常见数据中心卡约 2TB/s高端加速卡主力GDDR6X消费级显卡约 1TB/s游戏卡/消费卡的极限GDDR6中端显卡约 300-500GB/s入门卡也要注意DDR5 内存服务器主存约 50-80GB/s平时说的内存带宽PCIe 4.0 x16主板扩展开卡约 32GB/s 双向比内存还慢一个量级高速网络RoCE/InfiniBand集群互联约 50-200GB/s跨节点搬运的极限这个表的信息量很大。最直观的结论是显存里的 HBM 比 DDR 内存快了几十倍而内存又比 PCIe 和网络快一个量级。所以数据每次往下一层迁移带宽就要降一两个数量级。这就是为什么 KV Cache 一旦放不进显存性能立刻崩——不是算不过来是路太窄。表格里的数值是峰值真实应用一般只能吃到 60%-85%而且读写同时进行时还要再打折扣。做性能预算的时候千万别按峰值算账。2.2 带宽账本batching 和量化各自在搬什么明白了带宽是墙下一步就是做账。decode 一步的搬运量大致是# 每步需要搬运的字节数粗略估算 # 权重字节所有请求共用只需搬一次 # KV字节每个请求、每一步都要读完整历史随上下文线性增长 weights_bytes model_size * 2 # FP16 kv_per_token_bytes 2 * num_layers * num_kv_heads * head_dim * 2 step_bytes weights_bytes batch_size * kv_per_token_bytes * seq_len举个具体例子7B 模型 FP16 权重是 14GB假设 32 层、GQA 后 8 个 KV 头、head_dim 128那每个 token 的 KV 是 2 × 32 × 8 × 128 × 2 128KB。batch 为 1、上下文 4K 时每步要搬约 14GB 512MB 14.5GB在 2TB/s 带宽下单流上限大概 138 token/s。把 batch 从 1 提到 16权重还是只搬一次但能同时产出 16 个 token等效带宽利用率大幅提升。这就是为什么 continuous batching、动态批处理对吞吐影响那么大——它们本质上是在把权重这个固定成本摊到更多人头上。KV 这项则跟 batch 和上下文长度同时成正比。序列越长每步要读的历史 KV 越多。所以长上下文场景下KV 读取代价会逐渐追上甚至超过权重。这也是为什么同一个模型把上下文从 4K 拉到 32Ktoken 生成速度肉眼可见地往下掉。量化的作用也分两类。权重量化FP16 转 INT4减少的是权重字节KV Cache 量化FP16 转 FP8 甚至 INT4减少的是KV 字节。两者都能直接降低每步搬运量从而提升 token 上限。如果你观察到的瓶颈是带宽吃满量化往往比任何花哨的调度算法都管用。2.3 别把省算力的优化错当省带宽这里有组概念经常被混淆省算力和省带宽是两件事。FlashAttention 这类技巧主要优化计算效率与显存访问模式让 attention 不再把完整注意力矩阵写进显存再读出来它在长序列上确实同时缓解了显存容量和带宽压力。但如果你跳过了 attention 计算比如 KV Cache 命中省掉的只是算力恰恰没有省掉权重搬运——而权重搬运在短上下文、小 batch 的 decode 里往往是最大头。所以排查的时候先问一句我这一步到底被什么卡住了被算力卡住加并行度、优化计算图有用被带宽卡住减少搬运量才有用。用 GPU 占用率判断忙不忙很容易误判因为带宽受限时计算单元可能大部分时间在等待数据利用率却显示很高。内核等待数据时的占用和真正在算的占用完全是两码事。3. 显存放不下之后KV 从 HBM 到内存再到网络的链条3.1 KV Cache 有多胖一组粗略计算单卡放不下这个问题比大多数人想得来得早。KV Cache 的大小按这个公式算KV字节 2(K、V两份) × 层数 × KV头数 × 每个头的维度 × 序列长度 × batch × 每个元素字节数举一个真实量级的例子。假设模型 32 层、32 个查询头、用 GQA 后 KV 头是 8head_dim 是 128FP16 存储那么每个 token 的 KV 是 2 × 32 × 8 × 128 × 2 128KB。单条 4K 上下文的请求KV 占 512MBbatch 32 个并发请求直接到 16GB。对 7B 参数模型来说光 KV 就快赶上一个权重了。再往上拉上下文KV 总量线性增长但系数惊人。也就是说只要做长上下文或多路并发KV 很快就会把显存吃光。这时候服务还没有真正跑不动只是显存满了。于是各种方案出现了把不活跃的 KV 换到 CPU 内存、换到 SSD、或者把旧请求的 KV 丢一部分。这就像仓库满了之后往外面租临时仓但每次取货都要跑更远的路。3.2 溢出到内存命中命中不了带宽KV 溢出到 CPU 内存后会发生什么从上节带宽表能看到DDR5 带宽也就是几十 GB/s比 HBM 慢几十倍。可解码时每步都要读完整 KV如果 KV 在内存里每步都得走一趟 PCIe 或主机内存接口。一次两次还能忍持续生成就全卡在取数据上了。这里有个特别反直觉的地方KV Cache 命中率在这个阶段依然可能很好看。因为你确实复用了历史 KV没有重复计算但每次复用的数据要从慢速介质里取回来取回的时间早就把省下的算力抵消干净。我甚至遇到过某个开源服务把命中率调到 99%结果因为缓存存在 CPU 内存、命中后反而比不命中更慢的情况——不命中时至少还能并行地从显存权重算 attention命中后却要串行从内存搬几百 MB 过来。所以在任何一层做缓存都必须把缓存放在哪和取回带宽多少一起考虑。缓存的价值公式不是命中了多少而是命中后省下的时间减去取回数据花掉的时间。取回成本大于省下算力时这个缓存是负资产。3.3 跨机命中PD 分离之后的网络焦虑单卡放不下团队自然会走向分布式。这两年很流行的做法是 PD 分离prefill 节点专门处理长提示词、生成 KVdecode 节点只做逐 token 生成。好处是两类任务不再互相抢占资源prefill 可以大 batch 冲decode 可以专心吃带宽。但代价是prefill 算好的 KV要跨网络传给 decode 节点。于是新的瓶颈出现了——网络带宽。前面算过单条 4K 上下文的请求 KV 就有 512MBGQA 之后如果用普通万兆网络这个传输耗时是以秒计的比 decode 一个 token 的毫秒级慢了好几个数量级。就算走 InfiniBand512MB 在 200GB/s 的链路上也要两毫秒多和 decode 单步时间同量级。KV 一多、并发一高网络立刻成为瓶颈。更隐蔽的问题是远程 KV的持续性。decode 不是只读一次 KV 就完而是每生成一个 token 都要读一遍全部 KV。如果 KV 不在本地显存而在远端那就不是一次性传输的成本而是每步都要从网络拉数据——这基本等于把每步延迟乘以几十倍。所以 PD 分离方案里KV 的位置极其讲究decode 节点必须在开始生成前把 KV 挪到自己显存里一旦开始生成就得保证 KV 本地可用否则整个推理直接变成网络等待练习。4. Mooncake 的答案把 KV Cache 当调度中心而不是附属品4.1 它和普通 PD 分离的区别在哪聊到这就能看懂 Mooncake 这套公开方案的精髓了。它核心观点一句话既然 KV Cache 对推理性能和资源利用率影响这么大那就别把它当成 prefill 的副产品、decode 的消耗品干脆把它当成整个系统的调度中心。具体来说Mooncake 在架构上把集群分成 prefill 池、decode 池之外还单独设计了一层 KV Cache 池可以理解为分布式的 KV 存储/缓存层。所有请求的 KV 不再是谁算出来谁拿着而是由调度器统一决定存哪里、什么时候搬、搬给谁。调度器做决策时主要看的不是 GPU 算力而是 KV 的位置、网络传输成本、目标 decode 节点的显存余量。这套架构还叠加了前缀缓存的概念很多请求开头都一样系统提示词、few-shot 示例这部分 prefill 算一次KV 长期留着后面的请求直接命中。和普通 PD 分离相比它把KV 应该住在哪这个关键问题显式化、系统化了而不是靠各节点拿现有显存凑合。4.2 总搬运量没变为什么延迟反而降了第一次看到这个方案我也有个疑问KV 总量没变从 prefill 传到 decode 的总字节数没变甚至因为多了一层存储搬运次数可能更多凭什么更快想明白之后发现关键不在少搬而在什么时候搬和搬的时候算力在干嘛。第一它把传输和计算重叠起来。传输引擎可以在 GPU 还在算上一个请求的 token 时把下一个请求的 KV 提前搬到位这叫 prefetch / 流水线。网络传输是异步的算力和网络并不互相阻塞。普通方案里 KV 传输常常是同步等待而 Mooncake 做的是让传输尽量不在关键路径上。等于把上菜和做下一桌菜并行而不是客人坐着干等。第二分离之后decode 节点的工作更纯粹。decode 是带宽敏感型任务最怕被 prefill 的长计算插进来打断prefill 是计算敏感型任务最怕跟 decode 抢带宽。分开之后每个池都能把硬件特性压到极致单位时间产出的有效 token 反而变多。第三调度器尽量把 KV 放在离消费它的 decode 节点最近的地方避免每步从远端取。开始解码之前完成一次性搬迁解码过程中 KV 保持在本地显存。这直接解决了上一节说的远程 KV 每步拉取的灾难场景。所以结论是总搬运字节没变但搬运的时机、路径、和算力的配合方式变了总等待时间大幅下降吞吐和尾延迟自然好看。这件事给我的启发是带宽是物理定死的但何时搬、往哪搬、搬的时候谁闲着是调度能优化的。很多时候优化性能不是减少运动而是让运动不再阻塞关键路径。4.3 落到自己的部署里能借走的三件事不是谁都有条件搭这么一套集群但它的思路可以直接借到自己的服务里我总结成三条第一KV 的放置要学会看距离。凡是会被频繁读取的数据尽量留在离计算最近的存储层一次性传输可以走慢一点的通道但持续读取绝对不能走慢通道。第二把传输变成流水线。即使不做分布式单机内部也可以在计算当前 batch 的同时预取下一个 batch 的 KV 数据到显存或者用 CUDA stream 做计算与拷贝的重叠。别再让取数据和算数据串行排队。第三用调度而不是硬抗。显存吃紧时传统做法是拒请求、减少 batch、或者粗暴换出 KV更好的做法是预测访问模式把高频共享的前缀 KV 单独缓存把低频 KV 放到更便宜的层级。调度的核心是知道数据多久会被用一次再决定它住在哪。5. 别再只盯命中率这几个指标和排查方法更有用5.1 四张体检表指标如果只想盯几个指标我建议别再拿KV Cache 命中率当唯一主角组合起来看才靠谱。指标全称/含义主要暴露什么TTFTTime To First Token首 token 延迟prefill 性能、排队情况、前缀缓存是否命中TPOTTime Per Output Token每输出 token 时间decode 步长、带宽是否吃满ITLInter-Token Latencytoken 间隔解码步之间是否被抢占、停顿吞吐量tokens/s整体或每卡单位时间产出KV 命中率请求命中前缀缓存的比例省了多少 prefill 计算仅此而已补充一句TPOT 和 ITL 看着像实际不一样。TPOT 是稳态一步的平均时间ITL 则对波动敏感尾延迟、抢占、GC、网络抖动都会反映在 ITL 上。如果平均 TPOT 很好但 ITL 忽大忽小多半是调度或异步传输在捣乱。5.2 定位带宽瓶颈的实操步骤遇到命中率很高但吞吐不行的问题我一般按这个顺序排查先用剖析工具看内存/显存吞吐利用率。如果已经到 80% 以上基本可以确定是带宽瓶颈如果只有 30%说明是调度、模型结构、或等待类问题在拖后腿。算理论天花板。用显存带宽 ÷ 每步搬运字节数估算极限 token/s再和实测比。如果实测离理论值很近就别折腾调度算法了去减搬运字节。看是权重贡献大还是 KV 贡献大。小 batch、短上下文时权重是大头长上下文、大 batch 时 KV 是大头。知道大头在哪才知道该做权重量化还是 KV 量化。检查 KV Cache 的存储层级。用工具确认 KV 是否真的在 HBM 里有没有被换到 CPU 内存或走了远端。这一步经常最容易被忽略——很多服务显示命中但命中的是慢速层的缓存。再看网络。如果是分布式检查 prefill 到 decode 的 KV 传输耗时是否占了请求关键路径。用传输字节除以实际网络带宽估算耗时和 TPOT 对比立刻能看出通信是否被流水线隐藏。5.3 我踩过的坑和一点个人结论最后分享几个实际项目里踩出来的经验都是上面分析的具体化。第一个坑盲目调大 batch。带宽没吃满时加大 batch 能把权重成本摊薄吞吐确实涨但带宽一旦吃满加 batch 只会把 KV 搬运量顶上去TPOT 恶化尾延迟飙升。正确姿势是边加 batch 边盯 TPOT找到拐点。第二个坑只做权重量化不做 KV 量化。许多量化框架默认只量化权重KV 还是 FP16。短上下文可能感觉不到一上长上下文就露馅KV 读取占比上升带宽又被吃光。我之前遇到过上下文到 16K 之后生成速度掉一半排查到最后就是 KV 没量化。第三个坑迷信命中率。有个服务命中率 95%排查时发现它缓存的是整个请求的完整 KV而实际请求之间只有前缀相同、后缀完全不同完全命中率很高但有效复用少prefill 该算的照样算。后来改成 prefix cache 按 token 粒度缓存命中率跌到 70%TTFT 反而降了一半。我把这么多年做推理优化的体会浓缩成一句话大模型推理慢往往不是因为它算得慢而是因为它搬得慢。KV Cache 是搬数据方案的一部分但不是全部。与其不停提高命中率数字不如把数据放在哪、什么时候搬、哪个通道搬当成第一性问题来排查。后面如果大家有兴趣我再展开写写 KV 量化的踩坑过程和分布式 KV 传输的具体工程细节。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑