手头那个 7B 模型同样一张 24GB 显卡训练时跑得好好的一到上线做推理并发一上来直接 OOM。相信很多人第一次遇到这个问题时都是一脸懵——代码没变模型没变怎么推理反而比训练更吃显存答案就藏在三个字母里KV Cache。它既是让大模型推理速度快起来的关键也是显存消耗的大头。这篇文章把这层窗户纸捅破KV Cache 到底是什么、为什么只需要缓存 K 和 V 而不是 Q、它跟上下文长度、lost in middle 现象、以及各家 API 厂商宣传的 prompt 缓存到底什么关系。最后给出一套实操层面的显存估算和调优方案。无论你是刚入门想搞懂原理还是已经在一线做部署、做长上下文优化这篇都能给你一些用得上的东西。1. 从一次推理请求看显存到底去哪了1.1 自注意力机制的计算过程要理解 KV Cache绕不开 Transformer 里最核心的自注意力机制。这个机制的本质就是让序列里的每个 token 都能“看到”它之前的所有 token通过计算两两之间的相关度来决定谁的贡献更大、谁的贡献更小。具体分三步每个 token 的隐状态向量分别乘以三个权重矩阵得到 Query查询向量、Key键向量、Value值向量用当前 token 的 Q 去和序列里所有 token 的 K 做点积得到注意力分数再经过 Softmax 归一化成权重拿这些权重去加权求和所有 token 的 V得到当前 token 的输出向量。这个过程在训练和推理中都要执行但两者最大的差别在于训练是并行计算整个序列推理是逐个生成 token。训练时模型拿到的是完整的一句话或一篇文章所有 token 的位置从一开始就是已知的。注意力计算可以在一个矩阵乘法里并行完成一次性算出所有位置之间的两两注意力分数效率很高。但推理不一样。大模型生成文本是自回归的先根据 prompt 预测第一个 token再把第一个 token 拼到 prompt 后面预测第二个 token然后继续拼、继续预测。每一步都只能生成一个 token前面的内容不会改。这就带来一个直观的问题——如果每一步都重新把整个序列从头到尾算一遍自注意力那生成第 100 个 token 时前 99 个 token 的 K 和 V 会被重复计算 99 次这种冗余计算量完全不可接受。1.2 训练和推理的差异决定了缓存策略打个生活化的比方。训练像是老师批改整套试卷所有答案一起看批改速度很快推理像是学生一道题一道题往下做每做一道新题都要把前面所有题目的题干和解答过程重新读一遍。如果这个学生不做笔记每做一步都把整本卷子从头看一次那做题速度会随着题目数量直线下降——这正是大模型推理没有 KV Cache 时的处境。而 KV Cache 的做法就是让模型在推理过程中边算边记每生成一个新的 token就把这个 token 对应的 K 向量和 V 向量存到缓存里。下一次要生成新 token 时只需要计算新 token 的 Q然后拿这个 Q 去和缓存里已有的所有 K、V 做注意力计算就行了不用重新计算历史 token 的 K 和 V。这一步优化带来的收益是数量级的。没有 KV Cache 时生成第 N 个 token 的计算量近似是 O(N²)有了缓存后变成 O(N)并且越到后面差距越大。一个 128K 上下文的场景两者计算量差出几千倍都不夸张。1.3 为什么不缓存 QQ 去哪了到这里细心的朋友会发现一个特别自然的疑问既然要缓存 K 和 V为什么把 Q 丢了为什么不是 QKV Cache 三件套一起存原因其实不复杂。在自回归推理中历史 token 的 Q 已经完成了使命——它们当时被用来计算注意力分数、加权求和得到输出然后这个输出进入了后续的 FFN、LayerNorm成为下一层网络的输入。之后新的 token 进来时历史 token 的 Q 不会再被任何计算引用。注意力计算只会用到当前 token 的 Q 来查询历史 token 的 K也就是说Q 是一次性的而 K 和 V 是要反复被查询的。更重要的是训练阶段之所以保留 Q是因为反向传播需要用到所有中间张量来算梯度推理阶段不做反向传播Q 用完就可以扔掉完全没有保留的必要。缓存一份 Q 不但白白浪费显存还会拖慢访存速度纯属负优化。所以业界约定俗成叫 KV Cache而不是 QKV Cache不是疏忽而是有充分理由的。2. KV Cache 的存储结构与显存账本2.1 我们到底在显存里存了什么KV Cache 不是在显存里随便找个地方塞几个矩阵它的结构跟模型架构严格对应。一个 7B 规模的 decoder-only 模型一般有 30 到 40 个 Transformer 层每一层都有一组独立的 K、V 缓存。具体看一个典型配置假设模型有 L 层每层有 H 个 KV 头每个头的维度是 D已经生成的 token 数是 S。那么这一层要缓存的 K 矩阵和 V 矩阵各自的形状都是 [S, H, D]或者按推理框架实际排布可能是 [H, D, S]逻辑上等价。L 层叠加起来KV Cache 的体量就是 L × 2 × S × H × D 个元素。注意这里的 H 不一定是模型全部的注意力头数。很多新模型用了 GQA分组查询注意力或 MQA多查询注意力让多个 Q 头共享同一组 K、V 头这会显著减少 KV Cache 的体量。比如 Llama 2 7B 是 32 个 Q 头、8 个 KV 头KV Cache 只相当于全 MHA 方案的 8/32也就是四分之一。这里顺便纠正一个常见误解KV Cache 跟模型权重一样也是按精度占空间的。FP16即 BF16下一个元素占 2 字节FP8 占 1 字节INT4 占 0.5 字节。权重可以量化KV Cache 同样可以量化后面实操部分会详细讲。2.2 显存计算公式和实际数字KV Cache 的显存占用可以写成一个非常透明的公式显存字节数 L × 2 × 精度字节数 × S × H_kv × D这个公式不绕我拿 Llama 2 7B 算一遍给你看。它的参数是 L32H_kv8D128精度按 2 字节算每个 token 单层单头的 K 和 V 合计2K、V 两个矩阵× 2 × 128 512 字节再乘 8 个 KV 头 4KB这是一层一个 token 的占用一层 4096 个 token 就是 4KB × 4096 16MB32 层加起来 512MB。也就是说一个 7B 模型在 4096 上下文下KV Cache 大约吃掉 512MB 显存。这还只是单条序列。跑在线服务时通常要同时处理多用户请求如果并发 8 路那就是 4GB。我们再把上下文拉长看一组直观数字。同样是这个 7B 模型如果上下文从 4K 拉到 32KKV Cache 直接涨 8 倍到 4GB拉到 128K就是 16GB。这时你会立刻明白为什么很多宣称支持超长上下文的模型本地部署之后实际能跑的长度远不如宣传——显存根本装不下那么大的缓存区。2.3 上下文长度、并发数和 KV Cache 的三角关系KV Cache 的显存成本和上下文长度是严格线性关系和并发路数也是严格线性关系。这意味着什么意味着** KV Cache 的总显存占用 单路 KV Cache × 并发路数**这两者同时增长时占用成乘法放大。我实际部署过一个 13B 模型权重部分用 INT8 量化后大概 14GB单卡 24GB 看起来挺富裕。但当我试图跑到 8192 上下文加 4 路并发时KV Cache 直接吃掉 8GB 左右加上权重、中间激活值、CUDA 上下文开销连模型带缓存就开始贴到 23GB 的警戒线了。后来把并发降到 2上下文保持 8192才稳定下来。这里需要特别提醒的是很多框架在初始化时就会给 KV Cache 预留显存。你现在用的推理框架可能有个gpu_memory_utilization之类的参数它控制的就是给权重之外预留多少空间其中很大一部分是预留给 KV Cache 的。如果框架不够聪明预留得太多模型根本加载不进来预留太少序列稍微长一点就开始 OOM。这个参数的调法实操部分展开。3. 容易被搞混的三个概念QKV、lost in middle、prompt 缓存3.1 为什么是 KV Cache不是 QKV Cache前面讲过 Q 不需要缓存的技术原因了这里再从一个更工程的角度补一刀。很多刚接触的人会把 Transformer 里的 QKV 三个矩阵当成某种“三个一组必须同时出现”的东西认为只缓存 KV 是一种不完整的方案。实际上自注意力计算里 Q 的角色是“发起查询的人”K 和 V 的角色是“被查询的数据库”。推理时每来一个 token只有 Q 是新的它需要跑到数据库里做一次近似检索数据库自然要保留而之前的查询请求记录没有保留价值。这个逻辑放到任何检索系统里都成立。再说训练层面那套完整保留 Q 的机制是服务于反向传播的属于训练基础设施的一部分跟 KV Cache 要解决的是完全不同的问题。推理框架如果还打着 QKV Cache 的招牌做缓存那是没搞懂这层区别要么是显存浪费要么是在玩概念。3.2 KV Cache 与 lost in middle 的关联lost in middle 是长上下文场景里一个非常著名的现象当给模型输入很长的上下文时它对开头和结尾的内容利用得很好但中间部分经常被“视而不见”就像一个人读长文只记住了开头和结尾。这个现象跟 KV Cache 确实有关系但要说得准确一点KV Cache 只是存储注意力计算所需 KV 的机制本身不直接导致模型“记不住中间”。真正的原因在注意力分数分布上。长上下文中模型每个新生成的 token 都要对全部历史位置做 Softmax而位置编码RoPE 等的引入会让注意力天然更偏向某些区域加上很多模型在训练时使用了滑动窗口或截断策略中间段落在预训练阶段就没被完整学习过。结果就是注意力权重在长序列里分布得并不均匀头部和尾部得分更高中间位置的 V 被平均之后贡献被稀释了。但 KV Cache 确实为这个问题的研究提供了观察窗口。因为有了 KV Cache我们可以在推理时直接检查每个位置的注意力分数分布看到底哪些 token 拿到了高权重哪些被忽略了。很多长上下文优化工作比如注意力重排、关键值增强、位置插值等都是基于 KV Cache 上观测到的注意力异常来做的。3.3 prompt 的 cache前缀缓存和 KV Cache 是什么关系市面上 API 厂商聊的“prompt 缓存”或者叫“上下文缓存”本质上是 KV Cache 的一种应用层玩法但很多人会把两者混为一谈。KV Cache 本身是自回归推理中每一步都会持续增长的缓存它随着生成 token 的增多不断变长。而前缀缓存Prefix Caching是这么个思路用户请求的 prompt 往往有大量重复前缀比如固定的 System Prompt、工具定义、长文档开头。这些前缀如果每次都重新计算一遍 KV既浪费算力又浪费时间。于是可以在服务端按内容哈希把前缀对应的 KV Cache 存起来下次来一个带相同前缀的新请求直接复用存好的 KV只对新出现的部分从头算。这个技术说白了就是用空间换时间的另一个维度——KV Cache 是从【时序】上复用历史计算结果前缀缓存是从【请求间】复用相同内容的计算结果。在实际工程里两者是叠加使用的前缀缓存负责命中重复前缀KV Cache 负责在生成过程中持续维护。像 SGLang 的 RadixAttention、vLLM 的自动前缀缓存还有各个云厂商 API 里的上下文缓存计费项本质都是这层东西。3.4 KV Cache、模型权重和中间激活值的显存分工部署大模型时显存里最少有三类东西在抢地盘模型权重、中间激活值包括临时张量、KV Cache。模型权重是最直观的模型文件多大加载进来基本就占多大。中间激活值是前向计算过程中产生的临时张量包括注意力分数、FFN 中间状态等它在推理时被反复创建和销毁不是常驻内存的大头。KV Cache 则是一个常驻的、随序列长度递增的结构。有人一说显存占用就把锅全甩给模型权重这是不准确的。很多场景下 KV Cache 和激活值加起来可能比权重更能决定能跑多大并发和上下文。比如 7B 模型 FP16 权重 14GB但 128K 上下文下光 KV Cache 就要 16GB权重也不如缓存吃得多。理解这个分工你才能在做显存预算时列清楚账而不是只盯着权重大小。4. 推理框架中的 KV Cache 配置实操4.1 主流推理框架中的 KV Cache 相关参数现在主流的推理框架从 vLLM、SGLang 到 Ollama、llama.cpp、TensorRT-LLM几乎都暴露了跟 KV Cache 相关的可调参数只是叫法五花八门。这里列几个最常见的方便你对照排查框架常见参数作用vLLM--max-model-len、--gpu-memory-utilization、--max-num-seqs前者限制总序列长度中间控制 KV Cache 显存占比后者限制并发序列数OllamaOLLAMA_CONTEXT_LENGTHnum_ctx、OLLAMA_NUM_PARALLEL分别控制上下文长度和并行请求数直接决定 KV Cache 大小llama.cpp--ctx-size、--parallel上下文长度和并行序列数TensorRT-LLM--max_seq_len、--max_batch_size、--kv_cache_free_gpu_mem_fraction序列长度、批量大小和 KV Cache 可用显存比例这里有一个通用的关系链KV Cache 大小 ≈ f(上下文长度) × 并发数所以你在任何框架里调这三个方向的参数本质都是在给 KV Cache 做预算。上下文长度设得越长能开的并发就越少并发开得越多单路上下文就必须缩短。两者都想拉满只能换更大的卡或者对 KV Cache 下手。4.2 显存估算实例7B、13B、70B 各跑多长才合适直接上实例。我用公开的模型配置按公式逐个算过一轮整理成一张表精度按 FP16、KV Cache 用 GQA 方案估算你看个量级即可模型层数KV 头数头维4K 单路 KV8K 单路 KV32K 单路 KVLlama 2 7B328128512MB1GB4GBLlama 2 13B408128640MB1.25GB5GBLlama 3 70B8081281.25GB2.5GB10GB参考一下权重占用量7B FP16 约 14GB13B 约 26GB70B 约 140GB。所以跑 70B 这种级别的模型时KV Cache 用满 32K 加上权重需要的显存几乎可以吃掉一整张 A100 80G 还多这也是为什么长上下文推理一般都要配张量并行或多机多卡。实际操作中我给一个经验值单卡能跑通的最长上下文按总显存减掉权重占用后再留至少 15% 的余量剩下的都分给 KV Cache 和激活值。比如一张 24GB 卡跑 7B INT8 量化权重约 7GB剩余约 17GB如果目标并发 4 路每路能分到约 3.5GB去掉激活和 CUDA 开销按表里 8K 单路 1GB 算8K 上下文跑 4 并发是稳的但 32K 单路 4GB 就超预算了只能降并发。4.3 减小 KV Cache 的四个实用策略如果预算不够就要动手压缩 KV Cache。我从“效果最明显”到“代价最小”排一个梯队第一梯队是换架构或头配置。GQA、MQA 这类设计直接把 KV 头数打个几折KV Cache 体量同比缩小。你选开源模型时优先看有没有 GQA这是最省事的办法。MHA 老架构在长上下文上天生吃亏再怎么优化缓存都是带着镣铐跳舞。第二梯队是KV Cache 量化。把缓存从 FP16 压到 FP8直接省一半压到 INT4 能省 75%。推理框架里一般叫kv_cache_dtype之类的参数。这个方案对模型质量会有轻微影响但在长上下文场景中很多工作表明 8bit KV Cache 的损失可以做到非常小属于性价比很高的优化。第三梯队是窗口化优化。有些框架支持 rolling KV Cache 或滑动窗口缓存只保留最近的 N 个 token 的 KV更早的直接丢。这对无限长文档的流式处理特别有用但代价是模型会“忘记”早期内容适合不需要完整访问历史的场景。第四梯队是PagedAttention 式的显存管理。这是工程层面止痛药它不减少 KV Cache 的总字节数而是把显存切块按需分配消除传统连续缓存造成的碎片化和浪费——你会发现有时候明明显存还够但系统说 OOM就是碎片太大导致的。vLLM 的核心卖点之一就在这里Ollama 等也借助类似机制提升并发。4.4 上线前的 KV Cache 巡检清单结合我自己的部署经验上线前我建议按这个顺序过一遍用上面公式估算当前配置下 KV Cache 的大小确认总显存有冗余查一下框架日志里有没有 KV Cache 占用、显存利用率的输出没有就手动打点设一个略低于显存上限的上下文长度而不是往框架允许的最大值上顶留出爆缓冲在目标并发数下做压测观测显存曲线是否平稳有没有随着对话轮次增长而失控如果用的是前缀缓存确认 prefix 命中的复用逻辑确实在生效比如重复请求同样 prompt 时显存不涨。这套检查做完基本能把部署环境里跟 KV Cache 相关的坑踩掉八成。5. 常见问题排查与避坑实录5.1 推理爆显存时先看什么最典型的场景就是模型加载完一推理就 OOM。这时候先别急着降模型精度第一步去查 KV Cache 的预算。做法很简单用nvidia-smi或参考框架日志里 BW 占用曲线看看显存是在几秒内瞬间拉满还是随着生成长度逐步爬升。如果是逐步爬升基本就是 KV Cache 随生成长度增长导致排查方向锁定上下文长度和并发。你可以临时把上下文长度减半如果显存显著下降且问题消失那就是缓存分配过大。如果上下文已经很短了还是 OOM再考虑权重量化或者换小模型。还有一个容易被忽略的坑有些框架默认初始化 KV Cache 时会按最大支持上下文一次性申请显存而不是按实际已经生成的 token 长度动态增长。这种情况下哪怕你只生成 100 个 token只要配置里写了 32K它也会把 32K 对应的缓存量先占上。所以有时候明明生成很短显存却已经是满配状态这就是原因。要解决就要把max_model_len或num_ctx严禁按实际需要设别图省事填一个很大的数。5.2 生成长度一长速度明显变慢这是一个非常普遍的体验短文本生成飞快长文本生成到后面越来越慢。不少人猜测这是模型“变笨”了其实是计算量在涨——每一轮新 token 的注意力计算都要扫一遍全部 KV Cache序列越长扫描越多。这种情况的排查不复杂但要注意区分两种“慢”一种是预填充处理 prompt慢那是 prompt 很长、初次计算 KV Cache 的代价另一种是解码逐 token 生成慢那是每一步注意力随 KV 增长线性变大的必然结果。前者可以用前缀缓存来规避后者只能用更短的上下文、更小的模型或者更高效的注意力实现如 FlashAttention 这类 IO 优化来改善。实测时你可以盯两个指标Time To First TokenTTFT和 Time Per Output TokenTPOT。TTFT 过高的基本是前缀没命中或者 prompt 过长TPOT 过高就要注意 KV Cache 的访存和计算开销是不是到瓶颈了。5.3 长对话出现内容错乱、答非所问这跟 KV Cache 的存储没什么直接关系更多是长上下文里的注意力分配问题也就是前面说的 lost in middle 等现象。排查思路分两步先确认是不是上下文长度超出了模型的有效范围。很多模型虽然支持长上下文但有效注意力区间远没那么长。你可以把 16K 上下文的对话砍到 8K 再测一次如果质量明显回升说明模型本身在这个长度上已经力不从心了。再确认有没有引入窗口化缓存。如果用了滑动窗口 KV Cache模型对窗口外信息的依赖能力会被切断早期对话内容会被新内容顶掉。这时候应该在产品层面引导用户把关键信息放在最近的几轮对话里或者对关键内容做摘要折叠而不是指望模型记住很长的历史。5.4 几个写在最后的坑与心得我在实际部署中踩过几个跟 KV Cache 相关的坑挑三个最值得说的多请求复用前缀缓存时要注意系统提示词哪怕改一个空格或换行哈希就会变导致命中率急转直下。解决方法是把 system prompt 在存缓存前做规范化处理文案层面保持严格一致。并发调大之后如果不限制单路最大 token 数某些用户的长文档请求会把整个显存吃光影响同卡其他用户。线上环境一定要同时设并发数和单路上下文上限两道闸缺一个都会出事。用了 KV Cache 量化后如果量化粒度过粗长上下文的连贯性会先于单句正确性出现劣化——先表现为跑题、前后矛盾而不是明显的语法错误。发现这种情况优先检查 KV Cache 量化位宽而不是怀疑模型本身。最后再分享一个小技巧想快速知道你的模型在每个上下文长度下大概需要多少 KV Cache不用翻文档直接在推理框架里把上下文长度分别设成 1K、2K、4K、8K、16K加载模型后观测显存差值差值除以对应序列长度的变化量就能算出单 token 的 KV Cache 成本。这个数字在你做容量规划时会非常好用——算清楚一条序列的增量成本并发和长度的博弈就有了明确的尺子。
阅读完成 · 觉得有帮助?