1. 从一次线上事故说起AI 越聊越慢的真相去年帮一个团队排查他们客服机器人的性能问题现象很典型单轮问答响应飞快200ms 内出首字但只要用户连续追问超过七八轮首字延迟就飙到 2 秒以上到第十五轮左右基本没法用了。他们一开始怀疑是模型推理框架的问题换了两个框架、加了 GPU、调了 batch size全都没用。最后把每一轮的耗时打点拉出来一看问题根本不在计算框架而在一个很多人听过但没真正搞明白的东西——KV Cache。这个场景其实非常普遍。你用一个对话类 AI 产品刚开始聊得挺顺聊到后面越来越卡尤其是那种需要它记住前面大段内容的场景比如让它读一份长文档再连续提问或者做多轮代码调试。很多人第一反应是模型太大了或者服务器不行但真正的原因往往藏在注意力机制的计算方式里。KV Cache 就是理解这件事的钥匙它既是大模型能流畅对话的功臣也是长上下文成本失控的元凶。这篇文章我打算把 KV Cache 从原理到实操彻底讲透。你会搞清楚三件事第一为什么没有 KV Cache 大模型几乎没法用第二KV Cache 到底吃掉了多少显存这个账怎么算第三长上下文真正的成本瓶颈在哪里以及工程上有哪些能落地的优化手段。内容会涉及注意力计算、显存估算公式、PagedAttention 这类工程方案也会给可以直接抄的参数计算过程。适合做推理部署、模型服务、AI 应用开发的工程师也适合想搞明白为什么我的 AI 越聊越慢的产品和技术负责人。基础不需要特别深但最好对 Transformer 的基本结构有个概念我会尽量用生活化的类比把关键点讲清楚。2. KV Cache 到底是什么一个缓存换速度的经典交易2.1 自回归生成一个字一个字往外蹦的本质要理解 KV Cache得先理解大模型是怎么说话的。现在主流的对话模型都是自回归生成也就是一次只预测下一个 token然后把预测出来的 token 接到输入后面再预测下一个如此循环。这就像你写句子写一个字看一眼前面所有字再决定下一个字写什么。问题就出在看一眼前面所有字这个动作上。在 Transformer 的注意力机制里每生成一个新 token模型都要拿当前这个 token 的 Query 向量去和前面所有 token 的 Key 向量做点积算出注意力权重再对前面所有 token 的 Value 向量做加权求和。注意这里前面所有 token是随着生成过程不断变长的。假设你在生成第 100 个 token那模型需要拿第 100 个 token 的 Q去和前面 99 个 token 的 K 做计算。生成第 101 个 token 时又要拿第 101 个 token 的 Q去和前面 100 个 token 的 K 做计算。你会发现前面那些 token 的 K 和 V 其实一直没变但每一轮都在被重新计算一遍。这就是巨大的浪费。2.2 没有缓存会怎样计算量的平方级爆炸我们来算一笔账。假设序列长度是 n注意力计算的核心是 Q 和 K 的矩阵乘法复杂度是 O(n²)。在自回归生成里如果每一轮都把前面所有 token 的 K、V 重新算一遍那么生成第 n 个 token 时累计的计算量大致是 1 2 3 ... n也就是 O(n²) 级别。序列越长浪费越夸张。举个具体的例子。生成 1000 个 token如果不做任何缓存注意力部分的计算量大致是缓存方案的 500 倍左右因为平均每轮要重算一半的前缀。这还只是注意力部分实际推理里还有前馈网络等其他开销但光是这一项就足以让长对话变得不可接受。所以 KV Cache 的核心思想特别朴素既然前面 token 的 K 和 V 算过一次就不会变那就把它们存下来下一轮直接拿来用不再重算。这就像你做菜切好的葱姜蒜放在案板上备用而不是每炒一道菜都重新切一遍。案板就是缓存切好的配料就是 K 和 V。这个类比虽然粗糙但抓住了本质用空间显存换时间计算。2.3 缓存带来的收益从平方到线性加上 KV Cache 之后每生成一个新 token只需要计算当前 token 的 Q、K、V然后拿新的 Q 去和缓存里所有的 K 做注意力再把新的 K、V 追加到缓存末尾。计算量从 O(n²) 降到了 O(n)每一轮的计算量基本恒定除了注意力那部分随长度线性增长。这个改变有多关键可以说没有 KV Cache就没有今天能流畅对话的大模型产品。你每次和 AI 聊天时感受到的秒回背后都是 KV Cache 在撑着。但天下没有免费的午餐省下来的计算时间是用显存换来的。而且随着对话变长这份显存开销会线性增长最终成为新的瓶颈。这就是为什么 AI 会越聊越慢——不是计算变慢了而是缓存越来越大显存压力越来越大能并行处理的请求越来越少排队时间越来越长。3. 显存账本KV Cache 到底吃掉多少资源3.1 一个可以直接套用的显存估算公式很多人对 KV Cache 的显存占用没有概念觉得不就是存点中间结果吗。实际上在大 batch、长序列的场景下KV Cache 的显存占用可以轻松超过模型权重本身。我们来看公式。单个 token 的 KV Cache 占用字节可以这样算每 token 占用 2 × 层数 × 注意力头数 × 头维度 × 数据类型字节数其中2 是因为要同时存 K 和 V层数、注意力头数、头维度由模型结构决定数据类型字节数FP16 是 2 字节FP8 是 1 字节INT8 也是 1 字节注意这里用的是注意力头数 × 头维度它等于隐藏维度在标准多头注意力里。有些模型用分组查询注意力GQAK 和 V 的头数比 Q 少这时候要按 K、V 的实际头数来算能省不少。整个序列的 KV Cache 占用就是总占用 每 token 占用 × 序列长度 × batch size3.2 拿一个真实规模的模型算一遍我们拿一个常见的 70 亿参数级别模型举例假设它有 32 层隐藏维度 4096用 32 个注意力头头维度 128数据类型 FP16。那么每 token 的 KV Cache 占用是2 × 32 × 32 × 128 × 2 字节 524288 字节 ≈ 0.5 MB也就是说每存一个 token 的 KV要花 0.5 MB 显存。这个数字单看不大但乘上序列长度和 batch 就吓人了。序列长度 2048batch size 10.5 MB × 2048 ≈ 1 GB序列长度 8192batch size 10.5 MB × 8192 ≈ 4 GB序列长度 8192batch size 164 GB × 16 64 GB一张 80GB 的卡光 KV Cache 就吃掉 64GB模型权重FP16 下 70 亿参数约 14GB加上激活值、框架开销基本就爆了。这就是为什么长上下文 大并发是显存杀手。你可能会说那用 GQA 呢如果 K、V 的头数从 32 降到 8每 token 占用直接降到 0.125 MB同样的场景显存占用变成 16GB压力小很多。这也是为什么现在新模型普遍用 GQA 而不是标准多头注意力——省 KV Cache 显存是核心动机之一。3.3 长上下文为什么贵注意力计算和缓存的双重压力很多人以为长上下文的成本就是能塞进去多少 token其实真正的成本分两块。第一块是注意力计算成本。虽然有了 KV Cache每轮计算量是线性的但注意力的核心矩阵乘法仍然是 O(n) 每轮累计到整个生成过程还是 O(n²)。序列翻倍注意力计算量翻四倍。这就是为什么 128K 上下文的模型处理长文档时首字延迟明显比短对话高。第二块是KV Cache 显存成本也就是上面算的那笔账。序列越长缓存越大能同时服务的请求越少吞吐量下降单位成本上升。而且长上下文场景下用户往往不会只发一轮多轮对话会让缓存持续累积直到触及显存上限。这两块成本叠加就是长上下文真正的成本在哪的答案不在模型权重而在随序列长度线性增长的 KV Cache 和随长度平方增长的注意力计算。理解了这一点后面的优化手段就都有方向了。4. 工程优化实战把 KV Cache 的成本压下来4.1 PagedAttention像操作系统管理内存一样管理缓存KV Cache 有一个很讨厌的特性它的长度是动态的每个请求的序列长度不一样而且生成过程中不断增长。如果按最大长度预分配显存浪费极其严重如果动态分配又容易产生内存碎片。这个问题和操作系统里的内存管理几乎一模一样。vLLM 提出的PagedAttention就是借鉴了操作系统的分页思想。它把 KV Cache 切成固定大小的块block每个块存若干个 token 的 KV。一个请求的缓存由多个不连续的块组成通过一张块表来索引。这样带来几个好处第一显存按需分配不用预留最大长度浪费大幅减少第二块可以在请求之间共享比如多个请求有相同的系统提示词前缀那部分 KV 可以共用一份第三内存碎片问题基本消除。实测下来PagedAttention 能把 KV Cache 的显存利用率从传统方案的 20% 到 40% 提升到 90% 以上吞吐量提升 2 到 4 倍。这个提升幅度在推理部署里是相当夸张的也是 vLLM 能火起来的关键原因之一。4.2 量化 KV CacheFP16 到 FP8 甚至 INT4既然 KV Cache 占显存那最直接的想法就是把它压小。KV Cache 里的数值本来就是激活值对精度没那么敏感量化空间很大。从 FP16 降到 FP8显存直接减半精度损失在大多数对话场景下几乎感知不到。再激进一点降到 INT4显存变成原来的四分之一但精度损失开始明显尤其是长上下文里误差会累积可能导致输出质量下降。我的经验是FP8 基本可以无脑上INT8 需要评测INT4 要谨慎最好只在对质量要求不高的场景用。量化 KV Cache 有个坑要注意K 和 V 对量化的敏感度不一样。有些实现只量化 V 不量化 K或者对 K 用更细的粒度。另外量化后的反量化操作会带来额外计算开销需要权衡。实测在长序列场景下量化省下的显存带来的并发提升通常能覆盖反量化的计算开销整体是划算的。4.3 前缀共享与缓存复用别重复算一样的东西很多 AI 应用有一个共同特征大量请求共享相同的前缀。比如客服机器人所有对话都以同一段系统提示词开头比如文档问答同一份文档被多个用户提问。这些相同前缀的 KV Cache 完全可以只算一次多个请求复用。这个优化在工程上叫前缀缓存prefix caching。实现方式是把前缀的 KV Cache 存起来新请求进来时先做前缀匹配命中就直接复用只计算新增部分。对于系统提示词很长的场景这个优化能省下大量首字延迟和计算量。注意前缀缓存对匹配的要求是完全一致的前缀差一个 token 都不能复用。所以系统提示词要尽量固定不要在里面塞动态内容比如当前时间、用户 ID否则缓存永远命中不了。4.4 滑动窗口与稀疏注意力不是所有历史都值得记住还有一个思路是从源头减少需要缓存的 token 数量。滑动窗口注意力只保留最近 N 个 token 的 KV更早的直接丢掉。这样缓存大小恒定不随对话变长而增长。代价是模型看不到太久远的信息适合那些只需要记住近期上下文的场景。稀疏注意力则更精细它不丢 token而是让每个 token 只关注一部分历史 token减少计算和缓存。这类方法在长上下文模型里越来越常见但实现复杂度高需要模型结构配合训练不是推理阶段能随便加的。选择哪种方案取决于你的场景。如果对话轮次多但每轮信息量小滑动窗口够用如果需要精确回忆长文档细节那就得老老实实保留完整缓存靠 PagedAttention 和量化来压成本。5. 常见问题与排查技巧实录5.1 首字延迟随对话变长而飙升怎么定位这是最典型的症状。排查思路分三步。第一步确认是不是 KV Cache 导致的把对话历史截断到固定长度看延迟是否稳定。如果截断后延迟正常基本就是缓存问题。第二步看显存占用曲线如果随对话轮次线性上升直到接近上限说明缓存没被有效管理。第三步检查推理框架是否开启了 PagedAttention 和前缀缓存很多默认配置是关的。如果确认是缓存问题优化顺序建议是先开 PagedAttention如果框架支持再开前缀缓存然后考虑 KV Cache 量化最后才是滑动窗口。这个顺序是按收益/风险比排的前面的改动风险低收益高。5.2 显存明明够为什么还是 OOM这种情况通常是显存碎片或者峰值估算错误。KV Cache 动态增长如果框架按最大长度预分配实际用量可能远小于预留量但预留的部分别人也用不了。PagedAttention 能解决大部分碎片问题。另一个原因是并发请求的缓存峰值叠加比如 16 个请求同时到达最长序列峰值显存是单请求的 16 倍如果按平均情况估算就会 OOM。解决办法是限制最大并发数或者用请求调度让长请求错峰。5.3 量化 KV Cache 后输出质量下降怎么办先确认下降是不是量化导致的关掉量化跑同样的输入对比输出。如果确实是量化问题几个调整方向把 K 的量化精度调高比如 K 用 FP8V 用 INT4缩小量化分组粒度让每组内的数值范围更集中对异常值做特殊处理因为注意力里偶尔会有数值特别大的激活值量化时容易被压扁。实测分组量化比全局量化效果好很多代价是元数据占用略增。5.4 常见问题速查表症状可能原因排查方向解决手段首字延迟随轮次上升KV Cache 累积截断历史对比延迟开 PagedAttention、前缀缓存显存够但 OOM碎片或峰值叠加看显存分配曲线限制并发、用分页管理长上下文吞吐骤降缓存占满显存算 KV Cache 占用量化、GQA、滑动窗口量化后质量下降精度损失累积关量化对比输出调高 K 精度、分组量化前缀缓存不命中前缀含动态内容检查提示词固定系统提示词5.5 几个踩过的坑第一个坑以为 batch size 越大吞吐越高。在长上下文场景下大 batch 会让 KV Cache 显存爆炸反而触发 OOM 或者频繁换页吞吐不升反降。正确做法是根据序列长度动态调 batch长序列用小 batch。第二个坑忽略 prefill 和 decode 的区别。prefill 阶段是处理输入计算密集decode 阶段是逐 token 生成显存带宽密集。KV Cache 主要影响 decode 阶段。优化时要分开看两个阶段的指标别混在一起调。第三个坑前缀缓存和动态提示词冲突。有些应用喜欢在系统提示词里插入当前时间戳结果每次前缀都不一样缓存永远不命中。把动态内容挪到用户消息里前缀就能稳定复用。6. 我个人的一些实操体会KV Cache 这个东西刚接触时觉得就是个存中间结果的小技巧真正做过推理部署之后才发现它是整个大模型服务成本结构的核心变量。你去看任何一个推理框架的优化重点绕来绕去都绕不开它。PagedAttention、量化、前缀缓存、GQA这些看起来独立的技术本质上都在回答同一个问题怎么用更少的显存撑住更长的上下文和更大的并发。我的建议是如果你在做 AI 应用哪怕不直接碰推理框架也一定要把 KV Cache 的显存账算清楚。因为你的产品设计——比如允许多长的对话历史、支持多大的文档、能扛多少并发——最终都受这个账本约束。很多产品经理拍脑袋定的支持 100 轮对话在工程上可能意味着显存成本翻十倍这个账不算清楚上线就是事故。最后分享一个我常用的估算习惯拿到一个新模型先算它每 token 的 KV Cache 占用然后乘以你预期的最大序列长度和并发数看看总显存够不够。这个数字如果超过显存的 60%就要提前规划优化方案了。这个习惯帮我避开了好几次上线前的性能翻车。