1. 从一次显存爆炸说起为什么70B模型一加载就吃掉144GB我第一次认真算KV Cache这笔账是因为帮朋友在一台双卡工作站上部署70B模型。当时他的机器是两张48GB显存的卡加起来96GB按他的理解“模型权重FP16也就140GB左右两张卡勉强够”结果模型权重还没加载完显存就爆了。后来把权重换成FP8权重占用降到70GB左右理论上96GB够用了可实际一跑长上下文推理显存又炸了。问题就出在KV Cache上。很多人对显存的认知停留在“模型多大就占多大显存”这个认知在短上下文、单轮对话场景下勉强成立但一旦进入长上下文、多轮对话、批量推理KV Cache就会变成显存占用的主角。70B模型在FP16精度下权重约140GB如果上下文长度拉到32Kbatch size开到8KV Cache轻松吃掉几十GB甚至上百GB。这就是为什么标题里说“70B模型需要144GB显存”——这个数字不是权重而是权重加KV Cache加中间激活值加框架开销的总和。这篇文章我想把KV Cache这件事从头到尾讲清楚。它是什么、为什么需要、怎么算、怎么省、怎么在低显存设备上跑起来。适合正在做模型部署、推理优化、显存调优的工程师也适合刚接触大模型推理、被显存问题折磨过的开发者。读完你至少能自己动手算出某个模型在某个配置下的KV Cache占用并且知道该从哪些方向去优化。2. KV Cache到底是什么用生活化类比拆开注意力机制的黑盒2.1 自回归生成的核心矛盾每一步都要看前面所有token大模型生成文本是自回归的也就是一个token一个token往外吐。生成第N个token的时候模型需要关注前面N-1个token的信息这个“关注”就是注意力机制。注意力机制的核心操作是当前token的Query向量去和前面所有token的Key向量做点积算出注意力权重再对前面所有token的Value向量做加权求和。问题来了如果每生成一个新token都把前面所有token的Key和Value重新算一遍计算量会随着序列长度平方级增长。生成第1个token算1次第2个算2次第1000个算1000次总计算量是12...N约等于N²/2。对于32K上下文这个数字是5亿次量级的注意力计算完全不可接受。KV Cache的思路非常直接前面token的Key和Value算过一次之后结果不会变那就缓存起来生成新token时直接复用只算当前token的Query、Key、Value。这样每步只需要做一次注意力计算总计算量从O(N²)降到O(N)。代价就是显存——你得把前面所有token的Key和Value存下来。2.2 一个类比KV Cache就像会议纪要你可以把自回归生成想象成一场持续进行的会议。每来一个新参会者新token他需要了解前面所有人说过的话前面token的信息。如果没有会议纪要每来一个人主持人都要把前面所有人的发言重新复述一遍会议永远开不完。KV Cache就是那份会议纪要每个人发言完记录员把他的核心观点Key和详细内容Value记下来新参会者直接翻纪要就行不用重新听一遍。这个类比还能解释KV Cache的几个特性。第一纪要越厚占用空间越大对应上下文越长KV Cache越大。第二纪要可以多人共享如果多个对话共享同一段前缀那这段前缀的KV Cache可以复用这就是Prefix Caching的原理。第三纪要可以压缩把冗长的发言提炼成摘要这就是KV Cache量化、KV Cache淘汰策略的思路来源。2.3 KV Cache的数学结构层数、头数、维度、精度四个变量要算KV Cache占用得先知道它的数据结构。Transformer每一层都有注意力模块每个注意力模块有多个注意力头。对于每个token每一层每个头都需要存一个Key向量和一个Value向量。所以KV Cache的总元素数量是总元素数 2 × 层数 × 头数 × 头维度 × 序列长度 × batch_size其中2是因为Key和Value各一份。头数乘以头维度通常等于模型的隐藏维度所以也可以写成总元素数 2 × 层数 × 隐藏维度 × 序列长度 × batch_size以70B模型为例典型配置是80层、隐藏维度8192、80个注意力头、头维度128。序列长度取32Kbatch size取1那么总元素数 2 × 80 × 8192 × 32768 × 1 ≈ 4.29 × 10^10FP16精度下每个元素2字节总占用约85.9GB。如果batch size开到8就是687GB这还没算权重。所以标题里说70B模型需要144GB显存其实是在某个特定配置下的估算比如序列长度没那么长、batch size比较小但加上权重和激活值之后的总和。2.4 为什么是144GB一个典型配置的拆解我们来还原一下144GB这个数字可能的来源。假设70B模型FP16权重约140GB这已经超过144GB了所以144GB肯定不是FP16权重加KV Cache。更合理的解释是权重用FP8或INT8量化到约70GBKV Cache用FP8量化序列长度32Kbatch size为1再加上中间激活值和框架开销。按FP8权重70GB算KV Cache FP8精度下每元素1字节上面算的4.29×10^10元素就是42.9GB。7042.9112.9GB再加上激活值、临时缓冲区、CUDA上下文、通信缓冲区等开销凑到144GB是合理的。如果KV Cache不量化用FP16那就是85.9GB加70GB权重已经156GB超过144GB了。所以144GB这个数字背后大概率是FP8权重加FP8 KV Cache加32K上下文加一些余量的配置。这个拆解说明一个关键点显存规划不能只看权重KV Cache在长上下文场景下可能和权重一样大甚至更大。下面我会把每一块显存占用都拆开算清楚。3. 显存账本权重、KV Cache、激活值、框架开销各占多少3.1 权重占用精度决定一切模型权重占用是最直观的参数量乘以每个参数的字节数。70B模型在不同精度下的权重占用如下精度每参数字节70B权重占用说明FP324280GB训练常用推理极少FP16/BF162140GB推理基准精度FP8170GB需要硬件支持精度损失可控INT8170GB量化方案成熟通用性好INT40.535GB精度损失明显需仔细校准FP16和BF16的区别在于指数位和尾数位的分配BF16动态范围更大但精度略低推理场景下两者显存占用一样。FP8和INT8都是1字节但FP8保留浮点结构对异常值更友好INT8是定点量化需要校准数据集确定缩放因子。实际选型时如果硬件支持FP8比如较新的数据中心卡优先FP8否则INT8是更通用的选择。3.2 KV Cache占用长上下文场景下的隐形大户KV Cache占用公式前面给过了这里用表格展示70B模型在不同序列长度和batch size下的KV Cache占用FP16精度80层隐藏维度8192序列长度batch1batch4batch8batch164K10.7GB42.9GB85.9GB171.8GB8K21.5GB85.9GB171.8GB343.6GB16K42.9GB171.8GB343.6GB687.2GB32K85.9GB343.6GB687.2GB1374.4GB这张表说明一个残酷的事实70B模型在FP16精度下32K上下文、batch size为8时光KV Cache就要687GB是权重的近5倍。这就是为什么长上下文推理对显存的要求远超权重本身。如果KV Cache量化到FP8这些数字全部减半32K、batch8时降到343.6GB依然很大。3.3 激活值和临时缓冲区容易被忽略的固定开销激活值是前向传播过程中产生的中间结果比如注意力分数矩阵、FFN中间层输出等。激活值占用和batch size、序列长度成正比但通常比KV Cache小一个量级。不过在某些实现里注意力分数矩阵是序列长度平方级的32K上下文下单个头的注意力矩阵就是32768×32768FP16下2GB80个头就是160GB这显然不可接受。所以实际实现都会用FlashAttention这类算法避免显式存储完整的注意力矩阵把激活值控制在可接受范围。临时缓冲区和框架开销包括CUDA上下文、cuBLAS工作空间、通信缓冲区多卡场景、内存碎片等。这部分通常在几GB到十几GB取决于框架和硬件。多卡推理时如果用了张量并行每张卡还需要额外的通信缓冲区这部分开销不能忽略。3.4 总账144GB是怎么凑出来的把上面几块加起来一个典型的70B模型FP8部署配置FP8权重70GBFP8 KV Cache32K上下文batch142.9GB激活值和临时缓冲区约15GB框架和CUDA开销约10GB余量约6GB合计约144GB。这个配置下单卡放不下需要两张80GB的卡做张量并行或者一张141GB的卡比如某些大显存型号勉强够。如果KV Cache不量化光KV Cache就85.9GB总账超过180GB两张80GB卡也不够。所以KV Cache量化不是可选项是长上下文70B推理的必选项。4. KV Cache量化实战FP8和INT8怎么选、怎么配、怎么避坑4.1 FP8和INT8的本质区别浮点还是定点FP8和INT8都是1字节但表示方式完全不同。FP8有几种格式常见的是E4M34位指数、3位尾数和E5M25位指数、2位尾数。E4M3动态范围小但精度高适合KV Cache这种数值分布相对集中的场景。INT8是定点表示把浮点数值线性映射到-128到127的整数区间需要一个缩放因子。FP8的优势在于对异常值更鲁棒。KV Cache里的Key和Value分布通常比较集中但偶尔会有一些绝对值很大的异常值INT8的线性映射会被这些异常值拉偏导致整体精度下降。FP8的浮点结构天然能处理这种动态范围不需要复杂的校准。INT8的优势在于硬件支持更广泛很多老卡不支持FP8但支持INT8而且INT8的量化工具链更成熟。4.2 量化粒度per-tensor、per-channel还是per-token量化粒度决定了缩放因子的数量。per-tensor是整个张量共用一个缩放因子最简单但精度最差。per-channel是每个通道一个缩放因子精度好一些但开销大。per-token是每个token一个缩放因子对KV Cache特别合适因为不同token的Key和Value分布差异可能很大。实际部署中KV Cache量化常用per-token或者per-head的粒度。per-token对每个token的Key和Value分别算缩放因子能更好地适应不同token的数值分布。per-head对每个注意力头单独算适合头之间分布差异大的模型。粒度越细精度越好但量化和解量化的开销也越大。需要在精度和速度之间做权衡。4.3 实操配置以主流推理框架为例不同推理框架对KV Cache量化的支持方式不同。以vLLM为例启动时可以通过参数指定KV Cache的精度python -m vllm.entrypoints.openai.api_server \ --model meta-llama/Llama-3-70B \ --tensor-parallel-size 2 \ --kv-cache-dtype fp8 \ --max-model-len 32768 \ --gpu-memory-utilization 0.95关键参数是--kv-cache-dtype可以设为fp8、int8或auto。--max-model-len决定最大序列长度直接影响KV Cache大小。--gpu-memory-utilization控制框架能用的显存比例设太高容易OOM设太低浪费显存。实测下来0.90到0.95比较稳留一点余量给临时缓冲区。如果用TensorRT-LLMKV Cache量化在构建引擎时配置需要指定kv_cache_quant_mode。如果用HuggingFace Transformers直接推理可以通过BitsAndBytesConfig配置但Transformers的KV Cache量化支持不如专用推理框架完善长上下文场景下性能差距明显。4.4 避坑经验量化后的精度验证不能省KV Cache量化会引入精度损失这个损失在短上下文下可能看不出来但长上下文下会累积。我踩过的坑是量化后跑短问题一切正常跑长文档摘要时开始出现重复、漏信息、逻辑断裂。后来做了对比测试才发现FP8 KV Cache在16K以上上下文时某些任务的输出质量下降明显。验证方法很简单准备一组测试用例覆盖短上下文1K以内、中上下文4K到8K、长上下文16K以上分别用FP16 KV Cache和量化KV Cache跑一遍对比输出。重点看长上下文下的信息召回率和逻辑一致性。如果量化后长上下文质量下降超过可接受范围要么换更细的量化粒度要么对部分层保持FP16。另一个坑是量化缩放因子的校准。INT8量化需要校准数据校准数据分布和实际推理数据分布不一致时量化误差会放大。建议用实际业务场景的样本做校准不要随便拿通用语料凑数。FP8虽然不需要校准但缩放因子的计算方式也会影响精度不同框架的实现有差异切换框架时要重新验证。5. 低显存跑70B从144GB压到24GB的几条路5.1 权重和KV Cache同时量化最直接的显存压缩把权重从FP16压到INT470B模型权重降到35GB。KV Cache从FP16压到INT832K上下文、batch1时降到21.5GB。两者相加56.5GB加上激活值和框架开销约15GB总共约71.5GB。这个配置下一张80GB的卡就能跑但INT4权重的精度损失需要评估某些任务上可能不可接受。如果权重用INT870GBKV Cache用INT821.5GB总账约106GB需要两张卡。如果权重用FP870GBKV Cache用FP821.5GB总账一样约106GB但FP8精度通常好于INT8。所以硬件支持FP8的话优先FP8权重加FP8 KV Cache。5.2 分层加载和卸载用时间换空间分层加载的思路是不是所有层都需要同时驻留显存。推理时按需加载当前层用完就卸载显存里只保留少量层。这样显存占用可以降到单层权重加KV Cache的量级但每层加载卸载都有开销推理速度会大幅下降。适合显存极度受限、对速度不敏感的场景。另一种卸载策略是把KV Cache放到CPU内存甚至SSD上显存只保留最近窗口的KV Cache。这就是滑动窗口注意力加KV Cache卸载的思路。CPU内存比显存便宜得多容量也大代价是每次访问卸载的KV Cache要走PCIe延迟增加。实测下来KV Cache卸载到CPU后推理速度可能降到原来的三分之一到五分之一但显存占用能降一个数量级。5.3 注意力变体MQA和GQA从结构上减少KV CacheKV Cache的大小和注意力头数成正比。多头注意力MHA每个头都有独立的Key和ValueKV Cache最大。多查询注意力MQA所有头共享一组Key和ValueKV Cache降到1/头数。分组查询注意力GQA折中几个头共享一组Key和ValueKV Cache降到1/分组数。70B模型很多采用GQA比如8个分组KV Cache直接降到MHA的1/8。前面算的32K、batch1、FP16下85.9GB用GQA后降到10.7GB。这是结构层面的优化不需要量化就能大幅降低KV Cache。选模型时如果关注显存效率优先选GQA或MQA的模型。5.4 分页注意力和前缀共享系统层面的显存复用分页注意力PagedAttention是vLLM的核心技术把KV Cache分成固定大小的块按需分配避免预分配整个序列长度的KV Cache造成的浪费。传统实现里即使实际只用了1K上下文也要按最大序列长度预分配KV Cache浪费严重。分页注意力按实际使用量分配显存利用率大幅提升。前缀共享Prefix Caching是另一个系统级优化。多个请求如果共享相同的前缀比如相同的系统提示词这段前缀的KV Cache可以复用不用每个请求都存一份。在批量推理场景下前缀共享能显著降低KV Cache总占用。实测下来系统提示词占比较高的场景前缀共享能省30%到50%的KV Cache显存。5.5 组合拳一个24GB显存跑70B的可行配置把上面几招组合起来24GB显存跑70B模型是可行的但需要接受一些限制。配置如下权重INT4量化35GB但通过分层加载显存只保留当前层约0.5GBKV CacheINT8量化加GQA32K上下文、batch1约2.7GB激活值和框架开销约5GB余量约15GB这个配置下推理速度会很慢因为权重分层加载的开销很大。如果换成权重常驻但用INT435GB权重加2.7GB KV Cache加5GB开销约42.7GB24GB显存还是放不下。所以24GB跑70B分层加载几乎是必须的速度换空间。如果对速度有要求建议降到13B或30B模型或者用更激进的量化加更短的上下文。6. 常见问题排查KV Cache相关的OOM和性能问题6.1 OOM排查速查表现象可能原因排查方法解决方向加载权重就OOM权重精度太高算权重占用对比显存换FP8/INT8/INT4短上下文正常长上下文OOMKV Cache超预期算KV Cache占用量化KV Cache、缩短上下文、减batchbatch增大后OOMKV Cache随batch线性增长算batch×单请求KV Cache减batch、启用量化、用分页注意力多卡推理OOM通信缓冲区或负载不均看每卡显存占用调张量并行度、调通信缓冲区运行一段时间后OOM内存碎片或缓存累积监控显存随时间变化重启服务、调分页注意力块大小6.2 显存碎片容易被忽视的OOM元凶显存碎片是指显存里有很多小的空闲块但没有足够大的连续块满足新分配请求。KV Cache的分配释放很频繁尤其是变长序列场景容易产生碎片。分页注意力通过固定大小的块分配能有效减少碎片。如果没有用分页注意力可以考虑预分配KV Cache池避免频繁分配释放。实测中遇到过一个案例服务跑了几小时后开始OOM但显存监控显示总占用并不高。后来发现是碎片问题空闲显存有十几GB但都是小块无法分配新的KV Cache。解决办法是启用分页注意力或者定期重启服务。分页注意力把KV Cache分成固定大小的块碎片问题基本消失。6.3 量化后精度下降的排查思路量化后精度下降先确认是权重量化还是KV Cache量化导致的。方法是固定一个变量权重保持FP16只量化KV Cache看精度变化然后KV Cache保持FP16只量化权重看精度变化。定位到问题来源后再调整量化粒度或换量化方案。如果KV Cache量化导致精度下降优先尝试per-token或per-head粒度比per-tensor精度好很多。如果还是不行考虑对部分层保持FP16比如只量化后半部分层前半部分保持FP16。实测下来KV Cache量化对浅层的影响比深层大因为浅层的Key和Value分布更分散。6.4 性能调优KV Cache相关的速度问题KV Cache量化后推理速度不一定变快因为量化和解量化有开销。如果硬件原生支持FP8FP8 KV Cache通常比FP16快因为显存带宽占用减半。如果硬件不支持FP8软件模拟FP8的开销可能抵消带宽节省速度反而变慢。INT8类似需要硬件支持INT8矩阵运算才有加速效果。另一个性能问题是KV Cache访问模式。KV Cache的访问是随机的因为注意力权重决定了对哪些位置的KV Cache访问多。如果KV Cache太大超出显存缓存容量访问延迟会增加。分页注意力通过块分配改善访问局部性对性能有帮助。实测下来分页注意力在长上下文场景下能提升20%到40%的吞吐。7. 我个人的显存调优心得显存调优这件事我的经验是先把账算清楚再动手调。很多人一上来就试各种量化方案试了半天不知道瓶颈在哪。正确的顺序是先算权重占用再算KV Cache占用再看激活值和框架开销找到最大头针对性地优化。70B模型长上下文场景下KV Cache往往是最大头那就优先量化KV Cache、用GQA模型、启用分页注意力和前缀共享。量化不是越激进越好。INT4权重加INT8 KV Cache确实能把显存压到很低但精度损失在复杂任务上可能不可接受。我的做法是先用FP8权重加FP8 KV Cache如果显存不够再降权重精度KV Cache精度尽量保持。因为KV Cache量化对长上下文质量的影响比权重量化更直接KV Cache精度降太多长上下文的信息保持能力会明显下降。最后分享一个实用技巧部署前用显存计算器把配置算一遍别凭感觉。网上有现成的KV Cache计算器输入模型参数、序列长度、batch size、精度直接出结果。算完再留20%的余量给临时缓冲区和碎片这样配置出来的服务稳定性最好。我见过太多因为没留余量跑一段时间后OOM的案例重启能解决但影响服务可用性。