资讯动态

KV Cache:决定大模型推理速度与长上下文成本的核心缓存

发布时间:2026/10/22 20:19:03 来源:尧图企业网站定制
如果你用过大模型应用一定有过这种体验前几句问答快到飞起聊到二三十轮之后每个字都要等一秒钟有时候干脆卡住报错。如果你自己部署过开源模型做服务感受会更直接——上下文长度稍微拉高一点显存占用就像被偷走一样快速上涨批量稍大一点直接 OOM。大部分人会把问题归因于“模型太大”或者“服务器太差”但真正的原因往往只有一个KV Cache。KV Cache 是大模型推理性能的核心变量也是长上下文成本最容易被低估的部分。这篇文章我从头拆一遍它到底缓存了什么为什么对话越长推理越慢以及“长上下文真正的成本”到底花在了哪里。我尽量不写教科书直接按工程师视角讲清楚原理、公式和避坑经验适合正在做大模型推理服务、部署开源模型、或者被长上下文性能折磨过的人。1. KV Cache 到底缓存了什么1.1 自回归生成每生成一个字都要“重读”一遍上文先理清大模型生成文本的底层逻辑。目前主流大模型基本都基于 Transformer 解码器结构生成方式叫自回归给定一段 prompt先算出一个 token把它拼到输入末尾再拿去算下一个 token以此类推。整个过程像滚雪球每多生成一个 token输入序列就变长一格。这里的关键是每生成一个新 token模型都需要重新执行一次完整的前向计算。如果不做任何缓存这次前向会把输入序列里所有 token 的 Key 和 Value 重新算一遍——我说的是“所有”。你第一次生成时输入里有 10 个 token那要算 10 个位置的 K/V等到第 100 个 token 的时候输入里有 109 个 token还要把前面那 100 个位置的 K/V 全部重新算一遍。模型不会自己记得之前算过什么。做过性能分析的人都能一眼看出问题这是一笔随序列长度线性增长、但每次生成都要重复支付的账单。比如一个 7B 规模的模型有好几十层 Transformer 层每一层都要算 K/V。上下文长度是 1000 的时候每生成一个 token 等于重复计算 1000 个位置在几十层里的 K/V上下文长度是 10000 的时候重复计算量变成 10000 个位置。这显然不可持续。所以工程上必须做一件看起来理所当然的事把历史位置已经算好的 K/V 存下来下次直接用。这就是 KV Cache 最朴素的动机。1.2 KV Cache 的本质把历史计算的中间结果存下来要理解 KV Cache得先看注意力机制里三个向量的分工。Transformer 的注意力公式是 Attention softmax(Q·Kᵀ)·V。其中 K 是 Key代表“我是什么内容欢迎大家来查找”V 是 Value代表“我实际携带的信息是什么”Q 是 Query代表“我现在想找什么”。生成第 N 个 token 的时候当前这个位置的 Q 是全新的它要跟历史上所有位置的 K 做相似度匹配再按匹配权重去加权所有位置的 V。注意一个细节历史位置的 K/V 是在过去已经算好的它们只依赖于历史输入不依赖于当前这个新 token 的 Q。也就是说K/V 不会因为来了一个新的 Q 而改变。这意味着我们可以把历史上所有位置的 K/V 缓存起来。生成下一个 token 时不需要重新计算历史位置的 K/V只需要计算当前这个新 token 的 K/V拼到缓存末尾然后用当前的 Q 去跟完整的缓存做注意力计算。所以 KV Cache 不是某种调优选项它是 Transformer 解码器推理时绕不开的中间结果缓存。它在每一层都存在一份格式通常是 [batch_size, num_heads, seq_len, head_dim]。有些模型还分 K 缓存和 V 缓存所以实际占用要再乘 2。如果少了这一步推理服务的吞吐量会直接崩溃长上下文推理基本无法落地。1.3 一个例子算算 KV Cache 的体量光说“缓存”没有体感我直接给一个可复现的计算过程。假设一个常见的 7B 规模开源模型层数是 32 层采用了分组查询注意力GQAKV 头数设为 8每个头的维度是 128。注意力计算时通常用半精度FP16每个元素占 2 字节。单个 token 在单层里的 K/V 占用K 缓存8 个头 × 128 维度 1024 个元素V 缓存同上 1024 个元素两者合计 2048 个元素单层、单 token 占用2048 × 2 字节 4096 字节 4KB再乘以 32 层单 token 全模型 KV Cache 4KB × 32 128KB注意前面没有乘 batch。如果 batch_size 是 1 还好说但这只是 1 个 token 的占用。上下文长度 4096 时4096 × 128KB 524288KB 512MB如果 batch_size 是 8512MB × 8 4GB这还只是 7B 模型。如果模型层数更多、KV 头更多或者上下文拉到 32K、128KKV Cache 会膨胀到比模型权重本身还大。这就是为什么长上下文从来不是免费的它不增加“智力”但实打实地增加显存和带宽压力。2. 为什么 AI 越聊越慢瓶颈不在计算在搬运2.1 解码的两个阶段prefill 和 decode推理过程可以分成两个性质完全不同的阶段。第一个阶段叫 prefill预填充也就是用户把 prompt 发过来模型一次性处理整个 prompt同时生成第一个输出 token。这个阶段的特点是输入序列长、可以高度并行GPU 的计算单元能充分跑起来所以通常很快。第二个阶段叫 decode解码从第一个输出 token 之后开始每次只生成一个 token。生成完了拼回去下次再生成下一个。这个阶段的特点是每次前向只新增一个 token计算量本身很小但每一层都要基于完整序列长度做注意力计算。这两个阶段的关键差异在于prefill 阶段是“一次性扫描全部输入”decode 阶段是“每次都要扫描全部已有序列”。对 KV Cache 而言prefill 是在构建缓存decode 是在反复读取缓存。我见过很多人只在 prefill 上做优化结果长上下文场景还是很慢原因就是 decode 阶段被忽略。实际上 decode 占据了生成过程中绝大部分时间因为一次生成几百个 token就要执行几百次 decode 前向。2.2 decode 是访存密集型慢在带宽为什么 decode 阶段会越来越慢核心原因是访存带宽而不是计算量。继续用上面的例子。一个 7B GQA 模型单 token 的 KV Cache 是 128KB。当上下文长度是 10000 时全部 KV Cache 大约是 128KB × 10000 1.28GB。现在每生成一个 token理论上前向计算需要把这份 1.28GB 的 KV Cache 从显存里读一遍参与注意力计算。一个主流数据中心 GPU 的显存带宽大约是每秒 2TB 到 3TB 级别。只读 KV Cache 这一项1.28GB 就要花约 0.5 到 0.7 毫秒。上下文越长这个时间越长。到 30K 上下文时KV Cache 约 3.84GB每生成一个 token 光读 KV Cache 就要 1 到 2 毫秒。再加上模型权重读取、激活值计算、通信开销每个 token 延迟轻松超过 50 毫秒这对交互式应用来说已经慢得明显了。这也能解释为什么有时候模型计算量没变但速度掉得厉害。decode 阶段每生成一个 token计算量大概只增加一点点因为多一个位置参与计算但 KV Cache 的读取量随上下文长度线性增长。本质上这不是“算不过来”而是“搬不过来”。GPU 算力再强也得先把数据从显存搬到计算单元里。2.3 为什么每个 token 都要读全部 KV Cache有人会问能不能只读最近几个 token 的 KV Cache很遗憾注意力机制决定了不能这样偷懒。当前 token 的 Q 要和历史所有位置的 K 计算相似度再对所有位置的 V 做加权求和。换句话说新生成的一个 token 要“看”一眼所有历史 token历史全部位置的信息都要参与。这不是习惯问题是注意力公式决定的。Q 和每个历史 K 的相似度都可能影响当前生成的内容。如果你只保留最近的 K/V模型就会“失忆”丢失早期信息。FlashAttention 等技术能优化注意力计算的访存模式减少中间结果的反复读写但 decode 阶段至少要把 KV Cache 从头到尾读一遍才能算出注意力权重。所以 FlashAttention 能减少很多额外访存开销但无法消除 KV Cache 带来的线性读取成本。这也是长上下文场景下KV Cache 优化比单纯优化 attention 计算更重要的原因。3. 长上下文真正的成本显存、带宽与工程复杂度3.1 显存批量、序列长度与并发一起挤压容量聊到上下文成本很多人的第一反应是“上下文越长模型理解能力越强”。但工程上看上下文长度直接换算成显存占用。之前算的是单请求单 batch单条对话。真实服务的复杂之处在于多个用户、多个请求同时跑。假设部署一个 7B GQA 模型单 token KV Cache 128KB单条上下文 4096那么单请求 KV Cache 128KB × 4096 512MB。如果同时并发 16 个请求光 KV Cache 就是 8GB。这个量级已经跟模型权重FP16 约 14GB相当。如果并发 32 个KV Cache 冲到 16GB比权重还大。这种情况下显存分配策略稍有问题OOM 几乎是必然的。长上下文服务为什么贵因为单请求的 KV Cache 不只是由实际用到的长度决定还受系统为“最大可能长度”预留的显存影响。很多框架在初始化时会按 max_model_len 一次性分配 KV Cache 缓存池。也就是说你设置了 32K 上下文哪怕实际请求只有 1K token系统也会为潜在的 32K 预留显存其他请求能用的空间就变少了。这是一个非常隐蔽的成本长上下文是“提前支出”的不是“用多少付多少”。3.2 多轮对话的隐性成本KV Cache 只会增长不会释放多轮对话场景下KV Cache 的累积效应非常明显。每轮用户输入和模型输出都会产生新的 token这些 token 的 K/V 会不断追加到缓存里。用户可以怀疑自己记不住前面聊过什么但模型服务端必须把每一轮的内容都留着否则下一轮注意力计算就没有依据。于是你会看到这种曲线对话刚开始时显存占用非常低每轮增长很小对话进行到中段每轮新增的内容在累积单次请求的 KV Cache 越来越大生成延迟越来越高到了接近长度上限的时候服务端要么自动截断历史要么直接报错。用户端的体感就是“越聊越慢然后突然断了”。这里还有个容易被忽视的细节系统提示词和工具定义也会占用 KV Cache。很多应用在每次请求里都会拼上一大段 system prompt这段内容不会变但每次请求都要重新计算并缓存一遍。对话轮次越多这部分固定开销被摊薄得越少但如果你开了长上下文窗口这些“固定内容”同样会占掉大量缓存空间。3.3 对比计算成本与内存成本的权衡说到这里可以正式回答“长上下文真正的成本在哪”这个问题。它不在模型的参数量也不在推理的计算量而在 KV Cache 带来的显存占用和带宽消耗。先看计算成本。没加 KV Cache 时每生成一个 token 都要重新计算全部历史 token 的 K/V计算量随上下文长度线性增长而且增长的是重复计算。加了 KV Cache 后计算成本降下来了换来的代价是内存成本上升。而且 decode 阶段每生成一个 token都要把整个 KV Cache 读一遍这部分访问时间会直接变成延迟。所以长上下文问题本质上是一个“算力换内存”的权衡游戏。上下文越长KV Cache 越大读取越慢单个 token 延迟越高同时显存被缓存占掉能并发的请求变少整体吞吐量下降。真正的成本是每增加一个 token你都要在显存里多放一份向量并且在以后生成每个 token 时把它读一遍。理解这一点后续所有优化手段就有了明确的靶子。4. 优化 KV Cache 的实际手段4.1 模型结构层面GQA/MQA、头大小最根本的优化思路是让 KV Cache 本身变小。模型结构里有一个直接影响 KV Cache 大小的参数KV 头的数量。传统的多头注意力MHA里Query、Key、Value 的头数是完全一致的。假设模型有 32 个头那 KV Cache 就要存 32 份 K 和 32 份 V。分组查询注意力GQA的做法是让 Query 仍然保留 32 个头但 Key 和 Value 只保留少量几个头比如 8 个Query 分组共享这些 KV 头。这样 KV Cache 直接降到原来的四分之一而效果损失很小。MQA多查询注意力更激进KV 头只留 1 个KV Cache 更小但效果损失也更大。所以如果你在选模型或自己预训练模型尽量选 GQA 结构尤其是 KV 头数不超过 8 的。很多开源模型 7B 级别已经默认用 GQA这是它们能支持长上下文的关键原因之一。微调阶段也可以参考这一点不要为了省事直接用 MHA 大改模型KV Cache 会教你做人。4.2 KV Cache 量化INT8/FP8/4-bit如果不方便改结构那就对 KV Cache 本身下手。KV Cache 里的 Key 和 Value 本质上是一堆浮点向量默认是 FP16 甚至 FP32。对大多数场景来说把精度降低到 INT8 或 FP8对生成质量的影响都很小。量化之后每个元素从 2 字节变成 1 字节KV Cache 占用直接减半。4-bit 量化能进一步减到四分之一但需要处理缩放系数部署复杂度更高。实际操作中我一般从 INT8 开始跑一遍测试集看效果损失再决定要不要上 FP8 或 4-bit。量化 KV Cache 不仅能省显存还能减少带宽压力。因为 decode 阶段读取 KV Cache 的数据量也同比减少了延迟跟着下降。需要注意KV Cache 量化不是模型权重量化那种永久性的。有的框架支持在线量化比如 vLLM 里设置 kv_cache_dtype。但量化粒度很关键按 token 或按 channel 做缩放的效果差异不小。如果量化后出现明显的回答质量波动先检查是不是缩放因子算错了或某些层对精度异常敏感。4.3 PagedAttention 与显存管理模型结构和量化帮我们减少了 KV Cache 的体量但真正想让服务稳定跑起来还需要管理好显存的分配。传统做法是预分配一大块连续显存给 KV Cache但不同请求的长度差异很大连续分配的碎片化问题严重显存利用率很低。PagedAttention 的思路很有意思它把 KV Cache 切分成固定大小的块按需分配很像操作系统里的分页。每个请求的 KV Cache 不一定在显存里连续存放而是通过块表映射到物理位置。这样做的好处有三个一是显存利用率大幅提升小块按需分配不再为大长度预留全部空间二是碎片化减少回收和重新分配都更容易三是请求之间可以共享 KV Cache比如多条请求开头有相同的前缀时直接复用同一块缓存。如果你用过 vLLM 或类似框架会发现它支持配置 block_size、max_num_seqs、max_model_len、gpu_memory_utilization 等参数。这些参数不是随便填的。block_size 小内部碎片少但管理开销大block_size 大管理简单但容易浪费。一般短请求多的场景用小 block_size长上下文多的场景用大 block_size。gpu_memory_utilization 建议设置在 0.85 到 0.9 之间留出部分显存给模型权重和激活值否则可能出现“缓存池太大权重没地方放”的尴尬。4.4 Prefix Caching让相同开头直接复用还有一种优化叫 Prefix Caching专门针对“重复内容”问题。很多应用请求会带固定前缀比如系统提示词、角色设定、工具描述。这部分内容每次都是一样的理论上不需要重新计算和缓存。vLLM 的 Prefix Caching 建立在 PagedAttention 的分块机制上会把相同前缀的 KV 块识别出来让多个请求共享。实现上需要给每个 KV 块计算哈希哈希相同就复用。切换到不同请求时如果命中缓存prefill 阶段的计算量大幅减少首 token 延迟也随之下降。实际操作中Prefix Caching 对多用户场景效果非常明显。我有一个线上服务每天固定前缀占了完整请求的一半以上。开了 Prefix Caching 之后prefill 耗时降了 60% 以上而且显存占用显著下降。但要提醒的是前缀越长收益越明显如果前缀很短或每次请求前缀都不一样效果会很有限。4.5 产品层面的思路别把所有历史都塞给模型技术优化之外还有一个思维层面非常有效不是所有场景都需要完整历史。KV Cache 成本本质上由上下文长度决定而上下文长度很多时候是我们自己控制的。比如对话场景可以把历史对话每隔几轮做一次摘要用摘要替换原始内容。长文档问答可以先检索出最相关的片段只把片段拼进上下文而不是把整本资料都塞给模型。代码仓库场景先定位到相关文件或函数再精准拼接。这些手段没有改变模型结构但直接减少了 KV Cache 的体量效果立竿见影。这也是为什么很多长上下文模型宣传得天花乱坠真正部署时还是要加一层“上下文压缩”。模型能理解长上下文和“必须给它完整长上下文才能用”是两件完全不同的事。能用短上下文解决问题就不要为 KV Cache 买单。5. 常见问题与排查实录5.1 对话没超长度上限却突然 OOM这是我最常被问到的问题。先检查 KV Cache 分配策略。某些框架为了追求吞吐会按 max_model_len 和最大并发预算一次性分配大量 KV Cache 块。如果请求长度波动大短请求占了块长请求来了没块就会 OOM哪怕平均上下文远低于上限。排查步骤先看服务日志里每个请求的序列长度特别是峰值长度再统计当前并发数然后手动计算峰值 KV Cache 需求量单 token KV Cache × 平均长度 × 并发数。如果这个数接近显存容量说明需要限制并发、减少 max_model_len 或开启前缀共享。不要把 max_model_len 定得过高除非业务真的需要。5.2 显存占用不高但生成越来越慢有些场景显存还够但 token 生成延迟持续上涨。这通常是带宽瓶颈。KV Cache 总量不大但每次 decode 都要读取全部缓存。上下文从 2K 涨到 8K 时单 token 读取量翻了 4 倍延迟自然翻倍。这时候先算单位 token 的 KV Cache 大小再乘以平均上下文长度除以 GPU 带宽得到理论读取耗时。如果理论耗时接近实际单 token 延迟基本可以确认是带宽瓶颈。优化方向就是量化 KV Cache、减少 KV 头数或者用更短的上下文长度。5.3 换了 GQA 模型为什么还是慢GQA 能减少 KV Cache但不会自动消除所有性能问题。有的模型虽然 KV 头数少但层数非常深、隐层维度非常大模型权重的读取量仍会拖慢 decode。另外如果服务端没有正确配置块大小和调度策略GQA 的收益也可能被碎片化抵消。排查时不要只盯 KV Cache也要看模型权重读取和激活值计算。decode 是访存密集型任务权重和 KV Cache 都要从显存读入计算单元。层数越多、单层参数量越大读取权重的时间就越长。所以 GQA 不一定能单独解决延迟问题要结合量化、批处理和调度一起看。5.4 长上下文回答质量下降跟 KV Cache 有关吗如果已开 KV Cache 量化需要怀疑量化误差。尤其当模型输出出现逻辑混乱或细节丢失时先切回 FP16 对比测试。如果切回去仍有下降就不是量化问题而是上下文长度超过模型训练时的有效长度或者模型本身在长距离信息聚合上能力有限。我没少见过这种情况模型宣传 128K 上下文实际超过 8K 相关内容就开始“遗忘”。这其实跟 KV Cache 关系不大更多是模型训练数据和注意力机制本身的问题。工程上可以通过检索增强把关键内容放在离问题较近的位置降低跨长距离依赖的难度。5.5 我的排查顺序参考我自己的标准流程是先算后查。第一步算单 token KV Cache 的理论大小第二步算当前并发和长度下 KV Cache 总量第三步用性能分析工具看 decode kernel 耗时占比第四步确认瓶颈在权重读取还是 KV Cache 读取第五步针对性调参。大部分情况下调完 KV Cache 相关参数问题能缓解一大半。剩下那部分往往要回到产品设计层面去砍上下文长度。最后再分享一点体会做了这么久的大模型推理我越来越觉得 KV Cache 是理解长上下文真实的钥匙。很多宣传里的“长上下文能力”落地时都变成了显卡预算和延迟问题。每当你看到模型支持 32K 或 128K 上下文第一反应不应该是“它能记住多少”而应是“这份 KV Cache 要占多少显存、每生成一个 token 要读多久”。算完这笔账你对系统的性能就有了底。反过来很多所谓“越聊越慢”的线上问题其实不是模型变笨了而是 KV Cache 把显存和带宽吃干了。理解它优化它比盲目堆显卡重要得多。

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

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

免费获取报价 →
↑