首页
/
行业洞察
/
正文
INDUSTRY INSIGHT · 深度
KV Cache原理与显存优化:大模型推理加速的核心技术
📅 2026/10/25 10:33:03
✍️ 爱科研究院
👁 阅读 3,247
1. 为什么需要 KV Cache1.1 自回归生成里的隐形成本先说一个很多刚接触大模型的朋友都会困惑的点大模型推理的时候CPU和GPU到底在忙什么如果只是调 API你看到的是一个 prompt 丢进去几秒后一串 token 吐出来好像没什么特别的。但实际你每看到一个字生成完模型背后已经跑了完整的一次前向传播。大模型的生成方式是自回归也就是把上一次生成的 token 拼到输入序列后面再预测下一个 token。这个流程听起来很简单但问题来了序列是逐次变长的。从第 1 个 token 到第 N 个 token序列长度从 1 涨到 N。如果每次生成时都把整个序列从头算一遍注意力那成本的增长速度是平方级的。举个例子。你输入 1000 个字的 prompt让模型生成 1000 个字的回答。朴素的做法是生成第 1 个字时对 1000 个 token 做注意力计算生成第 2 个字时把 1000 个 token 和刚生成的 1 个 token 凑成 1001 个再整体算一遍注意力。到生成第 500 个字时你每往前推一步都要对 1500 个 token 重新做全量计算。这一路算下来大量计算花在了重复处理旧 token 上真正对新 token 的注意力计算反而被淹没。这个浪费有多夸张如果序列总长度是 L朴素实现每一步要算的注意力复杂度是 O(L)而整个过程要跑 L 步加起来是 O(L^2)。但每一步真正新增的信息只有一个 token理论上每一步只需要 O(1) 的计算量——前提是你把历史信息想办法留住了。这就是 KV Cache 出现的根本原因。1.2 关键洞察哪些计算可以复用要理解 KV Cache得先看注意力机制里每一部分具体在算什么。自注意力层的核心公式是Attention(Q, K, V) softmax(QK^T / √d) V这里 Q、K、V 分别来自输入序列和三个权重矩阵的乘法。对于一个长度为 n 的序列Q、K、V 都是 n×d 的矩阵先忽略多头拆分。我们在生成第 t 个 token 的时候Q、K、V 是什么状态Q 只跟当前 token 有关。每一步生成新的 tokenQ 就是新 token 通过 W_Q 映射得到的那一行。它只需算一次用完即弃不存在复不复用的问题。但 K 和 V 不一样。计算第 t 个 token 的输出时需要拿 Q_t 和之前所有位置的 K_1, K_2, ..., K_t 做点积再和所有位置的 V_1, ..., V_t 做加权求和。也就是说K、V 里那前 t-1 行在计算后续每一步时都会被反复用到。最关键的发现就在这里历史 token 对应的 K 和 V是只依赖历史 token 本身的。它们不会因为当前生成的是哪个 token 而变化。既然它们不会变那为什么不把第一轮算好的结果存下来后面直接取用非要每次从头再算一遍这就是 KV Cache 的本质思路用显存换算力。把已经算过的 K、V 矩阵缓存住生成每一步时只需要计算当前 token 的 K、V然后追加到缓存尾部再基于完整的缓存做注意力计算。整个过程的计算复杂度从 O(L^2) 降到了 O(L)代价是额外占用显存来存放这些缓存数据。打个生活化的比方。你做一道很长的数学证明题如果每推导一步都要把题目条件和前面所有推导过程重新抄一遍那做完一整道题的时间是没法看的。KV Cache 就是你的草稿纸前面已经推出来的中间结论写在草稿纸上后面直接用不用重推。2. KV Cache 到底存了什么2.1 K、V 矩阵的维度与生命周期先回答一个新手最容易搞混的问题KV Cache 里存的到底是啥是文本吗是 embedding 吗都不是。它存的是 K 和 V 矩阵而且是每一层 Transformer 层各自一份。现在的 LLM 基本都是多层 Transformer 结构比如一个 7B 参数的模型往往有 32 层。每一层都有自己的多头注意力也就有自己的 W_K、W_V 权重矩阵。输入 token 经过第 1 层算出的 K、V和第 20 层算出的 K、V 是完全不同的东西。所以 KV Cache 不是一个整体而是每一层都有一份各层互不通用。具体到单个层来说K、V 矩阵的维度通常是batch size当前处理的样本数num heads注意力头数seq_len缓存的 token 数量head_dim每个注意力头的维度也就是你有一个 (batch, num_heads, seq_len, head_dim) 的四维张量。K 和 V 各一份所以一共两个这样的张量。随着生成过程的推进每生成一个 token这个 token 在当前层的 K 和 V 就被算出来然后追加到缓存矩阵的 seq_len 那一维末尾。也就是说KV Cache 是动态增长的。如果你生成 1024 个 token那缓存里最终会有 1024 个位置如果你在服务端开了一个长对话缓存里存的就是整段对话历史上所有 token 的 K、V。这里有个细节Q 为什么不缓存上面解释过因为每一步的 Q 只和当前生成的 token 有关用完就没了。每一步只需要算当前 Q 对全部缓存的 K、V 的注意力完全不需要把历史 Q 留住。2.2 KV Cache 和模型架构参数的关系不同模型之间的 KV Cache 大小差异很大原因就在于架构参数不同。第一是层数。32 层的模型KV Cache 是 32 份80 层的模型80 份。层数越多缓存总量越大。第二是注意力头数。这里要区分一个概念Q 的头数和 K、V 的头数未必相等。传统多头注意力 MHA 里Q、K、V 头数完全一致每个 Q 头配一个 K 头和一个 V 头。但后来的模型里大量使用 GQA分组查询注意力和 MQA多查询注意力让多个 Q 头共享同一组 K、V 头这会在很大程度上减少 K、V 的存储量。第三是 head_dim也就是每个注意力头的向量维度。常见的是 128 或者 64。这个数值比较固定因为注意力分数计算要对 head_dim 开根号做缩放改它的空间不大。这三个参数乘起来再乘以 2K 和 V 两份再乘以序列长度就是 KV Cache 的体量。一个直观感受同样是 4096 上下文长度传统 MHA 架构模型的 KV Cache 动不动就上 GB而 GQA 架构的模型可能只要几百 MB。这个差异直接决定了你能不能把模型塞进一张 24GB 的卡里跑长文本。3. KV Cache 的显存占用到底有多离谱3.1 一个公式算出你的缓存有多少显存估算其实很简单就是算一下缓存矩阵里有多少个元素再乘以每个元素的字节数。通用公式KV Cache 大小 2 × num_layers × batch_size × seq_len × num_kv_heads × head_dim × dtype_size这个 2 就是 K、V 各一份。dtype_size 如果是 FP16/BF16就是 2 字节FP32 是 4 字节INT8 是 1 字节INT4 量化后平均 0.5 字节。拿一个经典 7B 模型来举例。假设它 32 层、32 个 Q 头这里先按 MHA也就是 KV 头也是 32、head_dim 128FP16 精度batch size 1上下文长度 4096单个 token 需要缓存一层里所有 KV 头的 KV 对32 头 × 128 维 × 2K 和 V× 2 字节 16384 字节。32 层全部加起来16384 × 32 524288 字节约 0.5 MB。注意这是每个 token 的开销。4096 个 token 就是 0.5 MB × 4096 2048 MB正好约 2GB。这个数字和模型权重放一起看就更清楚了。7B 模型 BF16 权重约 14GB在 24GB 显卡上已经占了大部分显存再加上 2GB 的 KV Cache 以及激活值、临时缓冲区跑 4096 上下文基本就是贴着上限了。如果上下文再拉到 8192KV Cache 直接翻倍到 4GB一张 4090 可能连模型加载都有风险。如果你用的是更大的模型比如 70B 级别层数更多KV Cache 的增速更夸张。这也是为什么很多服务端跑长文本时要专门做显存规划甚至多卡分布缓存。3.2 不同架构下的显存对照上面说的 7B MHA 模型只是一个参照实际工程中很少拿一个纯 MHA 的大模型直接上长上下文。现在主流模型纷纷转向 GQAKV Cache 能够显著瘦身。GQA 的意思是比如 32 个 Q 头分成 4 组每组共享 1 个 K 头和 1 个 V 头那么 KV 头数就从 32 降到了 4。KV Cache 直接缩到原来的 1/8。像一些 70B 级别模型使用 GQA 后KV Cache 的显存压力大幅下降部署门槛就低了很多。下面这张表可以看得更清楚假设都是 batch 1、FP16、seq_len 4096模型规模层数KV 头数head_dimKV Cache 大小7B MHA32321282048 MB7B GQA(4组)328128512 MB70B MHA806412810240 MB70B GQA(8组)8081281280 MB这个对比非常直观。同样 4096 长度GQA 架构的 70B 模型 KV Cache 只有 MHA 架构 70B 模型的 1/8。所以在现在的开源模型榜单上GQA 已经成了标配。另外要注意KV Cache 是动态增长的。显存规划一定要按最大上下文长度估算而不是按当前 prompt 长度。很多朋友部署后跑短文本没问题一拉长就 OOM原因就在这。4. 工程上怎么给它瘦身和调优4.1 模型侧的减负GQA/MQA 的取舍先说说为什么 GQA 能成立。注意力机制里多个 Q 头本身就是在不同子空间里做查询但 K、V 承载的信息其实有大量冗余。让一组 Q 头共享同一份 K、V在理论上会损失一定表达能力但实际效果远小于参数量的节省。尤其对于推理友好的部署场景省下的显存可以换来更大的 batch 或者更长的上下文综合收益很高。MQA 更进一步所有 Q 头共享一个 K 头和一个 V 头。这种极限压缩在模型效果上往往有可感知的下降所以现在主流模型基本都用 GQAMQA 只在早期一些模型里出现过。选型建议如果你要自己训练或者微调一个模型优先选择带 GQA 的底座模型。如果只是调用现成模型服务那不用管架构框架层面已经处理好了。但你要知道服务端为什么有时候限制上下文长度——很大程度上不是模型不聪明而是 KV Cache 装不下。4.2 系统侧的优化量化、PagedAttention 和前缀复用模型侧的 GQA 是从根源减少 KV Cache 总量工程侧的优化可以让缓存更高效地跑起来。第一个思路是 KV Cache 量化。既然缓存里存的是矩阵那就可以像量化权重一样把 KV 从 FP16 压到 INT8 甚至 INT4。INT8 量化 KV Cache 通常能让缓存体积减半而精度损失在绝大多数场景下可接受。INT4 更激进但需要谨慎验证效果衰减尤其是在长上下文 RAG 场景里信息密度低量化误差可能会被放大。第二个思路是 PagedAttention。这是 vLLM 等推理框架的核心优化。传统做法给每个请求预分配一块完整显存来存 KV Cache但生成的实际 token 数不确定导致内部碎片严重。PagedAttention 把缓存按固定大小的块来管理不连续的内存块可以用索引串联显存利用率大幅提升最重要的是它允许在同一批请求之间共享缓存。比如在 RAG 场景里多个请求共用同一段检索文档前缀这时候共用 KV Cache 能让吞吐翻倍。我当时把这个优化接入一个知识库问答服务之后在同样的显存下并发能力直接提了接近一倍。第三个思路是前缀复用。如果系统里经常有相同的 prompt 前缀比如 system prompt 很长、多轮对话里历史消息固定可以在服务端把这部分对应的 KV Cache 提前算好缓存起来。新请求进来只要前缀命中这部分 K、V 就不需要重算。这个优化在小 batch 高并发的场景里效果明显因为很多对话应用的前缀部分是固定的。4.3 上下文长度限制的真实原因很多人在用大模型时会问为什么模型有上下文窗口上限比如 4096、8192、128K。是不是模型权重里固定了序列长度不完全是。严格来说注意力层本身对长度没有天然限制你给它多长的序列它都能算。真正的限制来自两个地方一是训练时的位置编码分布。模型在预训练时见过的序列长度是有限的超过这个范围位置编码的外推能力再强也会有衰减。二是 KV Cache 显存。就算位置编码能外推到更长服务端也得有显存把那么长的 KV Cache 装下来。这两个限制叠加在一起就构成了实际的上下文上限。所以当你看到某个模型宣称支持 128K 上下文时除了看位置编码方案还要留意它对 KV Cache 做了什么优化。如果它没有量化和显存调度优化可能只是模型结构上能支持真部署起来单用户就要吃掉几十 GB 显存性价比很低。4.4 解码策略与缓存的生命周期管理这一节容易被忽略。beam search、温度采样、多轮对话这些看起来和 KV Cache 关系不大实际上影响很大。beam search 中每个 beam 有自己的生成轨迹也就是每个 beam 需要一套独立的 KV Cache。如果 beam size 为 4那么 KV Cache 就翻 4 倍。许多框架优化的目标之一就是最大化各个 beam 之间的 KV 共享尽量把相同前缀的部分合并计算。温度采样和 top-p 则不影响 KV Cache 的大小但会影响缓存的生命周期。因为采样引入了随机性你不能预判最终会生成哪些 token只能在每步生成后动态追加然后把不被采样的分支的缓存丢弃。如果要做多个候选结果重新排序那每个候选的 KV Cache 都要保留到最终选完才释放。多轮对话场景也要特别注意上一轮的 KV Cache 要不要带进下一轮理论上应该带因为对话历史是连续的复用上一轮的缓存可以省掉重新计算整个聊天记录的代价。但显存有限对话轮数多了之后缓存会持续累积。这时候要么做截断只保留最近 N 轮历史要么做摘要把旧对话压缩成摘要文本参与后续计算。我自己在做一个长对话应用时踩过这个坑开了缓存持续累积结果跑到第 20 轮对话时显存直接被打满。后来改成权重高的历史消息保留 KV Cache更早的对话压成摘要问题就解决了。这个思路在很多生产环境里都是标配。5. 常见问题与排查实录5.1 显存爆掉先查谁推理服务 OOM 的时候很多人的第一反应是模型太大了然后去换小模型。但我建议先查 KV Cache 的占用。用一个简单的公式估算模型权重占用的显存是固定的你真正要关注的是权重之外的部分。常见排查步骤如下先看模型权重多大参数量 × 精度字节数。再算激活值前向传播过程中每层产生的中间矩阵这部分往往被很多人忽略。最后算 KV Cache按上面给的公式代入实际上下文长度。对比显卡总显存看哪个占比最高。通常在短文本 batch 大的场景里占显存大头的是激活值在长文本 batch 小的场景里KV Cache 是大头。OOM 的时候如果发现 KV Cache 占比很高优先考虑调整上下文长度、做 KV Cache 量化或者开 PagedAttention而不是急着换模型。5.2 为什么生成阶段比预填充阶段快这是刚从业者最容易困惑的问题。同样是模型前向传播为什么输入 prompt 之后输出 token 的速度远高于处理 prompt 的速度答案是 KV Cache 起了关键作用。处理 prompt 的阶段通常叫预填充阶段。这时没有任何缓存模型需要一次性把整个 prompt 的 K、V 算出来并完成初始的注意力计算。因为此时没有历史缓存可用计算量就是跟 prompt 长度成比例的prompt 越长这个阶段越慢。而生成阶段每一轮只需要算新 token 的 Q、K、V再拿这个 Q 去查 KV Cache 里完整的 K、V 做注意力。单步的计算量不随历史长度线性增长所以看起来每吐一个 token 都很快。这也解释了为什么有的 API 服务在输入很长文档时响应慢而输出却很快。实际就是在预填充阶段要把整篇文档的 K、V 都算一遍、缓存下来。优化预填充阶段的关键在于并行度prompt 里的 token 之间没有依赖关系可以一次性并行处理所以很多框架专门优化了这个阶段的矩阵运算调度。5.3 一个容易被忽略的缓存正确性问题如果你是自己手写推理代码而不是用现成框架最容易踩的坑是缓存中混入了来自不同前缀的 K、V。举个例子。服务端的 KV Cache 按 token 序列位置存放。如果两个请求的前缀都命中了同一段 prompt而另一个请求的前缀只有一部分相同那它们的缓存尾部不能直接共享。实现时必须为每个请求维护独立的缓存索引否则一个请求的生成结果会静默地受到另一个请求历史的影响。这个问题在引入共享前缀优化后尤其容易发生。我之前遇到过一次先对 A 请求生成了一半然后 B 请求以相同的前缀进来复用了 A 的缓存但 A 生成出来的 token 并不适合作为 B 的上下文结果 B 的生成质量明显变差。后来在框架层面对缓存 key 做了严格管理把前缀扩展后重新计算和前缀命中后直接复用两类情况明确区分开才彻底解决。5.4 长上下文下 KV Cache 的精度衰减还有一个细节值得单独说KV Cache 量化之后长上下文的精度衰减会更明显。短文本下 INT8 量化的效果衰减可能感觉不出来但序列拉到 32K 甚至更长注意力分数经过多次 softmax 和加权后量化误差会一层层累积最终表现为生成质量下降、复读、甚至逻辑混乱。如果业务场景就是长文档问答、长代码分析这类对信息保真度要求高的任务我建议先做一轮对照组测试同样的 prompt分别用 FP16 和 INT8 的 KV Cache 跑人工对比关键事实的召回率。大多数情况下 INT8 是安全的但不要默认安全。至于 INT4除非你的框架对压缩后的 KV 有特殊补偿机制否则长文本场景要非常谨慎。6. 实操中的几点建议如果这篇文章能帮你建立起一个核心认知我希望是这个大模型推理速度优化的核心很大程度上就是围绕 KV Cache 做文章的。它决定了你能跑多长的上下文、能撑起多大的并发、甚至决定了某个模型适不适合在特定硬件上部署。几个可以直接落地的小建议。选型阶段优先考虑使用 GQA 架构的模型。在显存有限的情况下GQA 带来的收益比单纯增加参数量更实在。部署阶段不要等 OOM 了才去优化先按公式估算一下你的 KV Cache 峰值再决定要不要量化、要不要开 PagedAttention。上线阶段监控里一定要有 KV Cache 的实时占用指标这个指标比单纯的显存使用率更能反映长文本场景的风险。我最初从 7B MHA 模型切到 GQA 架构模型时最直观的感受是同样的 4090以前 4096 长度就贴着显存上限跑现在可以轻松跑到 8192而且多路并发的余量也出来了。后来再加上 KV Cache 量化和前缀复用吞吐又上了一个台阶。这些都不是什么玄学技巧就是把上面的原理一条条落到工程里。最后提一个容易被忽略的小点KV Cache 的复用和 release 时机也很重要。在多轮对话里如果用户新输入的内容立刻触发了历史缓存的截断比如超过最大长度你会明显感觉到首 token 变慢。有些框架支持在截断后重新构建前缀缓存但这需要额外的时间和显存交换。设计服务端策略时最好把多少轮后做摘要压缩作为可配置参数而不是拍脑袋写死。这个度需要根据你的显存预算和业务场景反复调我一般建议初始值设置为能覆盖用户 80% 对话历史的最大轮数不要盲目追求缓存全量覆盖因为那往往意味着你需要为极少数超长会话预留大量显存整体不划算。
📌 标签:
工业官网
设计趋势
AI 建站
SEO
获取完整报告 →
RELATED ARTICLES
推荐阅读
2026/10/25 10:28:02
网络建设与运维国赛脚本文件与答案视频导出实战指南
2026/10/25 10:28:02
Java Swing学生选课系统全流程:环境配置、数据库导入与避坑指南
2026/10/25 10:28:02
皮尔逊III型曲线拟合与设计频率插值:MATLAB实现与避坑指南
2026/10/25 14:43:34
基于LightGBM的糖尿病风险预警:从数据清洗到概率校准
2026/10/25 14:43:34
AI质检多架构部署:统一推理引擎与INT8量化避坑指南
2026/10/25 14:43:34
安装OpenClaw时,国内用户如何选AI模型API Key?TaoToken统一接入实践
2026/10/25 14:43:34
在双 H100 上用 vLLM 部署 DeepSeek‑V4‑Flash(1M 上下文)+ 编程 Agent 实战:TaoToken 统一 Key 接入
2026/10/25 14:43:34
微信小程序社区团购毕业设计实战:从白屏到真下单
2026/10/25 14:38:34
龙蜥离线部署 Oracle 11g:rpm 包拆解与静默安装避坑指南
2026/10/25 0:02:11
全国计算机二级C语言真题PDF高效刷题指南
2026/10/25 0:02:11
requests+BeautifulSoup:爬虫抓取h1标题的入门实战指南
2026/10/25 0:02:11
递推最小二乘参数辨识MATLAB编程:二阶系统与渐消记忆实现
2026/10/25 0:02:11
全国计算机二级C语言真题PDF高效刷题指南
2026/10/25 0:02:11
requests+BeautifulSoup:爬虫抓取h1标题的入门实战指南
2026/10/25 0:02:11
递推最小二乘参数辨识MATLAB编程:二阶系统与渐消记忆实现
2026/10/24 19:17:18
我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
2026/10/23 10:58:39
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026/10/25 12:42:56
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)