☰
KV Cache量化指南:公式推导、显存节省与工程实践
2026/10/17 3:43:56 网站建设 项目流程

1. 为什么 KV Cache 成了长上下文推理的显存大头——先看懂显存去哪了

做 LLM 推理部署的工程师,应该都有过这种经历:模型权重明明只占十几个 GB,跑一个长上下文的对话任务,结果显存直接飙到满载,再开一个并发请求就 OOM。一开始我以为是权重的问题,后来一步步排查才发现,真正把显存吃干抹净的,是那个平时不怎么被提起的 KV Cache。

KV Cache 到底是怎么来的?Transformer 在自回归生成时,每个新 token 都需要对前面所有 token 做注意力计算。如果不缓存历史 token 的 Key 和 Value,每生成一个新 token 就要重算一遍全部前缀,那计算量就爆炸了。所以推理框架会把已经算好的 K、V 张量存下来,下次直接用。这个缓存就是 KV Cache。

这里有个关键点:KV Cache 不像模型权重那样是一个固定的静态显存开销,它是随着对话长度不断增长的。请求越长,缓存的 K、V 越多;并发请求越多,多个请求的 KV 叠加起来就更多。更关键的是,它还会随着模型层数、注意力头数成倍增长——这三项叠加在一起,增长曲线很快就变得非常吓人。

我算过一笔典型的账。一个常见的开源 7B 级模型,假设 32 层、隐藏维度 4096,用 FP16 精度跑推理,单请求上下文长度到 4096 个 token 时,KV Cache 的占用大约是 2GB。看着好像不算什么,但如果上下文翻一倍到 8192,这个数字变成 4GB;如果同时开 8 个并发请求,8 个请求各跑 8192 上下文,那就是 32GB。对比一下,模型权重 FP16 也就 14GB 左右,KV Cache 轻轻松松超过权重。

所以现在做推理优化的人,几乎都会盯上 KV Cache。它既是显存消耗的大头,又是动态增长、可控可压的部分。模型权重是死的,你没法砍;KV Cache 是活的,压缩它的性价比极高。这也是 KV Cache 量化的初衷——在尽量不影响效果的前提下,把这块动态显存成本压下来。

那 KV Cache 量化之后到底能省多少显存?这不是一个拍脑袋的百分比,而是能精确算出来的工程账。下面我从公式到实例,把账一笔一笔摆清楚。

2. 先记熟这套显存账本公式:KV Cache 的每一项成本都能算出来

2.1 逐个参数拆开算:2 × L × H × n 的由来

先给出 KV Cache 显存占用的完整计算公式:

KV_Cache_Size = 2 × L × H × tokens × batch_size × bytes_per_element

这里每个参数的来源都值得说一下,因为很多文章直接贴公式,但很少有人解释为什么是“2”。

  • 2:代表 K 和 V 两份张量。注意力计算中,Key 和 Value 是两个独立的矩阵,都需要缓存,所以自带一个系数 2。
  • L:模型层数。每一层都有独立的 K、V 映射,所以缓存要乘以层数。
  • H:单个 token 的隐藏维度大小,通常等于 num_heads × head_dim。注意,有些文章用“注意力头数”来算,那是不对的,必须换算成隐藏维度。因为每一层要为每个 token 缓存一组 K、V,每个 K/V 的维度就是隐藏维度。对于标准多头注意力结构,num_heads × head_dim 恰好等于 hidden_size。为了简化,下文直接用隐藏维度 h 来算。
  • tokens:当前已生成的序列长度。这里要区分两个阶段:prefill 阶段一次性处理整个输入,decode 阶段逐个生成 token。KV Cache 的占用是在生成过程中逐步涨上去的,计算总占用时用最终序列长度即可。
  • batch_size:并发请求数量。每个请求的 KV 都是独立缓存的,互不相干,所以直接乘。
  • bytes_per_element:KV 缓存里每个数值用多少字节表示。FP16 是 2 字节,FP32 是 4 字节,INT8 是 1 字节,INT4 是 0.5 字节。这一项就是量化发挥作用的地方。

把公式合并一下,就是:

KV_Cache_Size = 2 × L × h × n_tokens × batch_size × bit_width / 8

2.2 三档模型的显存基线账:7B / 13B / 70B 级

公式记熟了,拿三档常见模型做个基线账。注意这里的参数是简化配置,偏老一代架构,但足以说明量级差异。

模型规模层数 L隐藏维度 hFP16 下单 token 的 KV 缓存
7B 级3240962 × 32 × 4096 × 2B = 512 KB
13B 级4051202 × 40 × 5120 × 2B = 800 KB
70B 级8081922 × 80 × 8192 × 2B = 2.5 MB

我经常看到有人在这里算错,因为他们用“注意力头数”而不是“隐藏维度”参与计算。比如 7B 级模型如果有 32 个头,每个头 128 维,总维度仍然是 4096,不是 32。直接用隐藏维度 4096 就行。

接着算实际序列长度的显存。7B 级模型,FP16,batch = 1:

  • 上下文 2048:512 KB × 2048 = 1GB
  • 上下文 4096:512 KB × 4096 = 2GB
  • 上下文 8192:512 KB × 8192 = 4GB
  • 上下文 32768:512 KB × 32768 = 16GB

一个 batch 为 1 的请求,跑 32K 长上下文,KV Cache 就到了 16GB。此时模型权重 14GB,KV Cache 已经比权重还大。这还是在单并发下,线上服务基本不可能只跑一个请求。这就是为什么长上下文模型部署起来那么吃显存——大头全是 KV Cache。

70B 级的模型更夸张,单 token 缓存 2.5MB,意味着 8192 上下文就吃掉 20GB。这类模型如果不开任何缓存优化,做长上下文服务基本没法落地。

2.3 把总显存账摆平:权重、激活、KV Cache 和框架开销

单纯算 KV Cache 还不够,部署层面真正决定你能跑多大 batch、多长上下文的,是整张卡上的总显存分配。一份相对完整的显存账应该包含四块:

  1. 模型权重:FP16 下约等于参数量 × 2 字节。7B 级约 14GB,13B 级约 26GB,70B 级约 140GB(单卡根本放不下)。
  2. KV Cache:上面算过了,动态增长。
  3. 激活值(Activation):前向计算过程中的中间张量。prefill 阶段占用明显,decode 阶段很小。这部分也不好算,和输入长度、batch 都有关系,但不属于本文重点。
  4. 框架运行开销:CUDA context、显存碎片、比如推理框架预留的 buffer,一般占几个 GB。

一个常见的部署判断是:如果权重加框架开销占掉 20GB,剩 60GB 大部分都要留给 KV Cache。KV Cache 每请求越小,能塞进去的请求就越多。做量化,本质上是在 K、V 缓存这一项上做文章,把前面公式里的 bytes_per_element 从 2 降到 1 甚至 0.5,从而直接改变整张卡的容量规划。

3. 量化位宽与省显存账:INT8、INT4、FP8 的数学关系

3.1 KV Cache 量化为什么可行:注意力计算的宽容度

首先要打消一个顾虑:把 K、V 从 FP16 压到 INT8 甚至 INT4,注意力计算为什么还能保持质量?

原因在于注意力分数的计算本质是 softmax(QK^T)V,KV Cache 在推理时只参与一次矩阵乘法和缩放操作,它的数值分布相对集中,不像权重那样需要极高的数值精度。另外,量化误差是一个相对误差,当注意力分布比较尖锐时,个别 K、V 值的微小偏差不会显著改变 softmax 后的相对关系。实践中 FP16 和 INT8 在多数任务上精度差距很小,INT4 需要更精细的量化策略,但也能用。

当然,这不是说量化完全没有代价。它会影响解码质量,尤其是在一些对细节敏感的生成任务上。所以工程上从来不问“能不能量化”,而是问“量到多少位还能保住业务指标”。这是后话,先看显存账。

3.2 字节数对照表与省显存比例

KV 量化后,公式里唯一变化的是 bytes_per_element。下面是最常见的几个位宽对照:

精度每元素字节数KV Cache 相对 FP16 的压缩比显存节省比例
FP162 字节1.00%
FP81 字节0.550%
INT81 字节0.550%
INT40.5 字节0.2575%

所以最粗的账就是:INT8 量化后 KV Cache 显存直接砍半,INT4 量化后砍到只剩四分之一。这个比例不依赖具体模型和序列长度,是纯粹的位宽换算关系。

拿前面 7B 级模型、batch = 1 的例子套一下:

  • 上下文 8192,FP16:4GB
  • 上下文 8192,INT8:2GB
  • 上下文 8192,INT4:1GB

上下文 32768,FP16:16GB;INT8:8GB;INT4:4GB。也就是说,INT8 量化让 32K 长上下文的 KV Cache 占用从 16GB 降到 8GB,刚好腾出一张卡的余量。

3.3 scale/zero 那点额外开销:别让账本差了一截

量化不是把 FP16 数值往低位整数一扔就完事,还需要保存缩放因子(scale)和零点(zero point)。这部分也有显存占用,虽然小,但不能忽略,否则实际部署时你会发现自己算的数字和框架日志对不上。

per-tensor 量化只需要每层每个 K、V 张量存一个 scale,几乎可以忽略。per-channel 量化(每通道一个 scale)开销就相对大一些,但和 KV 本身动辄几 GB 相比,仍然只是零头。比如一个 7B 级模型,per-channel scale 大概增加几十 MB,相对 2GB 的 KV Cache 来说不到 3%。

我的建议是:做显存预算时,KV Cache 理论值乘以 1.03 到 1.05 作为预留。这样既不会因为 scale 部分导致 OOM,也不会因为把比例算得太满而低估风险。

这里还要注意帧缓冲分配方式。有些服务框架是预分配显存池的,比如启动时直接分配一个固定大小的 KV 缓存池,量化后这个池子的上限可能不变,只是能容纳的请求数量变多。所以不能只看框架进程显存占用,要看实际的 cache 统计,这个后面专门说。

4. 省下来的显存去哪了:吞吐、并发与长上下文的连锁账

4.1 从“省显存”到“提吞吐”的关键链路

显存省下来只是手段,最终目的是换吞吐能力和更长的上下文服务能力。推理服务在显存足够的情况下至少还能继续加并发;显存不够就只能排队。KV Cache 占用每减少一半,理论上同一块卡能同时服务的请求数就能翻一倍,吞吐跟着线性上升。

举一个实际的容量规划运算。假设一张 80GB 的卡,部署 7B 级 FP16 模型,权重占 14GB,CUDA 上下文和框架预留算 6GB,剩下约 60GB 可动态分配。

设每个请求平均输出上下文 4096 token,KV Cache FP16 约 2GB:

  • FP16:60GB ÷ 2GB ≈ 30 并发
  • INT8:60GB ÷ 1GB ≈ 60 并发
  • INT4:60GB ÷ 0.5GB ≈ 120 并发

这只是理论上限,实际还要考虑计算资源(SM 占用、显存带宽)和调度策略。但量级差异已经很直观:KV Cache 量化之后,并发能力不是提升 10% 20%,而是成倍提升。

4.2 并发上限的估算方法:一个可以套用的模板

我建议每个做部署的团队都维护一张自家服务的“并发上限速算表”。方法不复杂:

  • 步骤 1:确认模型权重格式是 FP16 还是 INT8/INT4 权重,算出固定占用。
  • 步骤 2:确认框架预留显存(运行起来看空闲待机占用)。
  • 步骤 3:用上文公式算出单请求在目标上下文长度下的 KV Cache 占用。
  • 步骤 4:最大并发数 =(可用显存 - 权重 - 框架开销)÷ 单请求 KV Cache。

拿一个真实场景模拟一下:13B 级模型,40 层、隐藏 5120,权重 FP16 约 26GB,80GB 卡剩余约 48GB(扣除框架开销)。目标每请求 4096 上下文。

  • FP16:2 × 40 × 5120 × 4096 × 2B = 3.2GB,并发上限 48 ÷ 3.2 = 15
  • INT8:1.6GB,并发上限 48 ÷ 1.6 = 30
  • INT4:0.8GB,并发上限 48 ÷ 0.8 = 60

这个表非常有用,因为你可以拿着它跟老板说“量化后并发能力提升 X 倍”,而不是笼统地说“节省了显存”。

4.3 量化还白赚了一部分解码带宽

另一个经常被忽略的收益在显存带宽上。decode 阶段是 memory-bound 的,瓶颈在于从显存读取权重和 KV 的带宽。KV Cache 从 FP16 压到 INT8 或 INT4,意味着每次生成新 token 时,从显存读回的数据量也同比减少。这部分能带来 decode 阶段延迟的改善,尤其是在极长上下文场景下,每次读全量 KV 是一个不小的开销。

不过要泼一点冷水:带宽收益没有显存收益那么线性。因为现在很多推理框架在实现 KV 量化时,需要在读回后做反量化运算,反量化本身也消耗计算。如果 kernel 写得不好,量化省的带宽可能被反量化的指令开销抵消掉。所以实测延迟提升通常是有的,但往往低于显存节省的比例。

我自己的实测经验:7B 级模型、4096 上下文、batch 1 的场景下,INT8 KV 量化后 decode 单 token 延迟改善大概 5% 到 10%;INT4 能到 10% 到 20%。别期待翻倍,那是显存层面的收益,不是延迟层面的。

5. 工程实测中的坑:显存数字、量化误差与实现细节

5.1 坑一:用 nvidia-smi 的显存数字评估量化收益,误差很大

很多新手做量化评估喜欢看 nvidia-smi,然后得出结论“显存没怎么降”。这是最典型的误区。推理框架普遍采用显存池预分配和缓存复用机制,进程占用的显存总量在启动时就定得比较大,量化后进程占用的“已用显存”变化不明显。但真正反映 KV Cache 收益的是框架内部统计,比如日志里显示的 cache 使用量、cache total、每个请求的 cache 占用等。

正确做法是:量化前后分别看框架日志里的有效 KV 占用和并发上限。框架内部通常能精确到每个请求分配了多少 block,这比 nvidia-smi 的进程级别数字可靠得多。

建议在开启量化的开关前后,各跑一组相同长度、相同并发数的请求,对比框架日志里的 cache 统计、排队时间和成功请求数。不要在进程占用数字上花过多精力。

5.2 坑二:INT4 不是万能钥匙,精度账要单独算

INT8 量化在多数业务场景下精度损失较小,但 INT4 就得谨慎了。K、V 的数值分布如果比较宽,4bit 的表示范围非常有限,量化步长很大,关键位置的精度损失会造成注意力分布偏移。

这类精度问题通常不在通用指标(如困惑度)上体现,而是在具体任务上体现。比如长文本摘要、翻译、代码生成对 KV 量化的敏感度就各不相同。我的建议是:从 INT8 开始,用你的真实业务数据集做指标对比,达标后再试 INT4。如果业务接受不了 INT4 的误差,那就停在 INT8;如果必须上 INT4,优先找支持更细粒度量化策略(如 per-channel、per-head 粒度)的实现,而不是直接开一个全局限 4bit 的开关。

另外注意,量化误差对长序列更敏感。上下文越长,误差累积风险越高。之前我在一个 16K 长文档摘要任务上试 INT4,发现 4K 内效果还行,到 10K 以上关键数字开始出错。后来改回 INT8 就稳了。所以评估时必须覆盖目标的最长上下文场景。

5.3 坑三:把 GQA/MQA 模型按 MHA 公式算,直接翻倍

新版模型越来越普遍地使用 GQA(分组查询注意力)或 MQA(多查询注意力)来减少 KV 头的数量。这对 KV Cache 的影响非常直接:KV 头数变少了,缓存的 K、V 张量也就小了。但公式里如果用“隐藏维度”直接算,就会把 GQA 模型的 KV Cache 高估很多,因为隐藏维度对应的是 Q 的头数 × 每头维度,而 KV 的维度只是分组后的头数 × 每头维度。

正确的做法是把公式里的 h 替换成 KV 部分的实际维度。比如一个模型 Q 是 32 组头,每组 128 维,KV 头数只有 4 组(GQA 分组 8),那么 KV 实际维度是 4 × 128 = 512,不是 4096。此时每 token KV Cache = 2 × L × 512 × 字节数,和原公式差了一个数量级。

这就是为什么有些模型从 MHA 改成 GQA 后,长上下文能力大幅提升——不是权重变了,而是 KV Cache 缩小到原来的八分之一。做显存账本前,先确认模型的注意力结构,这个信息在模型配置文件的 attention heads 参数附近就能看到。

5.4 一个可复现的实测流程建议

如果你现在准备在自己的服务上做 KV Cache 量化,建议按下面的流程走一遍:

  1. 先算理论账:确认模型层数、隐藏维度、注意力结构(是不是 GQA/MQA),以及目标上下文和并发数,套公式算出量化前后的 KV Cache 占用。
  2. 再查框架是否支持 KV 量化:看推理框架配置中是否有类似 kv_cache_dtype 或 quantization 的参数,确认是 FP8、INT8 还是 INT4。
  3. 对比显存与延迟指标:量化前后各跑同一批压测脚本,记录框架内部的 cache 统计、每请求平均延迟、成功率。
  4. 单独做一轮业务效果评估:选一批与线上一致的真实样本,跑生成结果对比关键指标(如摘要准确率、代码通过率等),量化收益和损失都要量化出来。

这套流程不复杂,但能避免很多返工。好多人上来直接改配置上 INT4,线上效果波动了又排查半天,最后才发现是 KV Cache 量化带来的误差。先小步验证再全量上,永远是做推理优化最稳的路径。

5.5 关于量化粒度与反量化开销的最后提醒

最后说一个实现层面的选择。同样是 KV 量化,per-token 和 per-channel 的粒度不同,显存收益微差,精度表现差异较大。per-token(每个 token 单独 scale)在长度变化时更鲁棒,per-channel(每个通道单独 scale)对分布稳定的通道更有效。选哪个取决于框架实现和你的实际任务。如果框架只提供全局量化,建议先用小 batch 测试精度底线,再做压测。

反量化开销也是这么一回事。有些框架在读取 KV 时做实时反量化,有些是存的时候就转成低精度低位宽,读取时直接用整数矩阵运算。后者性能更好,但并不是所有实现都支持。如果你的业务对延迟很敏感,建议看看框架日志或者 profiler 里是否有反量化 kernel 的耗时,很多延迟异常都能在这里找到答案。

这几种坑我都踩过一轮,最好的心态就是“先理论、后实测、再调参”。KV Cache 量化是个典型的投入产出比很高的优化手段,但前提是每一步账都算清楚。显存账只是第一笔,精度账和延迟账也要一起摆上桌。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询