第1章:显存去哪儿了?——模型权重、KV Cache、中间激活值、优化器状态的内存占用拆解与估算公式

做推理部署的朋友,十有八九都被显存问题折磨过。

我记得刚入行那会儿,拿着一个13B的模型往A100上怼,心想“80G显存,总够了吧?”结果一跑起来,OOM了。我当时就懵了——模型权重才26G啊,剩下的54G去哪儿了?

后来我才明白,显存里住着的,远不止模型权重这一位“房客”。今天咱们就来拆一拆,显存到底被谁吃了,以及怎么提前算清楚。

1.1 显存里的四大“住户”

推理时,显存主要被四类数据占据:

  • 模型权重:就是模型参数本身,比如W、b这些矩阵。
  • KV Cache:自回归推理时,缓存的历史Key和Value。
  • 中间激活值:前向传播过程中,每一层的输出(比如LayerNorm后的结果)。
  • 优化器状态:训练时才有的东西,比如Adam里的momentum和variance。推理时没有。

嗯,这里要注意:推理和训练,显存占用结构完全不同。推理时没有优化器状态,但KV Cache是主角。训练时KV Cache可以忽略,但优化器状态和中间激活值才是大头。

1.2 模型权重:最直观的“房客”

模型权重的计算最简单。说白了就是:参数量 × 每个参数占的字节数。

举个例子:

  • 一个7B模型,用FP16(2字节)存储,权重占用 = 7 × 10⁹ × 2 = 14 GB
  • 如果换成INT4(0.5字节),占用 = 7 × 10⁹ × 0.5 = 3.5 GB

我个人习惯用这个公式快速估算:

权重显存(GB) = 参数量(B) × 字节数 × 1.05

多出来的5%是给一些额外开销留的,比如embedding层、bias等。我在项目中遇到过,有些模型实际加载后比理论值多出2-3%,所以加个5%的buffer比较稳妥。

精度 每参数字节 7B模型 13B模型 70B模型
FP32 4 28 GB 52 GB 280 GB
FP16 2 14 GB 26 GB 140 GB
INT8 1 7 GB 13 GB 70 GB
INT4 0.5 3.5 GB 6.5 GB 35 GB
我的经验: 如果显存紧张,优先考虑INT4量化。但要注意,有些模型量化后精度掉得厉害,尤其是数学推理类任务。我建议先跑几个benchmark再决定。

1.3 KV Cache:推理时的“隐形巨兽”

KV Cache是自回归推理特有的东西。你想想看,每次生成一个token,都要重新计算所有历史token的注意力吗?当然不是。我们把之前算好的Key和Value缓存起来,这就是KV Cache。

它的计算公式是:

KV Cache大小 = 2 × 层数 × 隐藏维度 × 序列长度 × 字节数

为什么是2?因为Key和Value各一份。

举个例子,一个7B模型(32层,隐藏维度4096),用FP16,生成2048个token:

KV Cache = 2 × 32 × 4096 × 2048 × 2 = 1.07 GB

如果生成8192个token呢?

KV Cache = 2 × 32 × 4096 × 8192 × 2 = 4.29 GB

看到了吗?序列长度翻4倍,KV Cache也翻4倍。这就是为什么长文本推理时显存会暴涨。

我曾经踩过的坑: 有一次部署一个对话模型,用户连续问了50轮,每轮都带历史上下文。结果序列长度飙到10000+,KV Cache直接吃了12G显存。后来我加了滑动窗口策略,只保留最近2048个token的缓存,问题才解决。

1.4 中间激活值:被忽略的“隐形住户”

中间激活值,就是每一层前向传播时产生的临时张量。比如LayerNorm的输出、FFN的中间结果等。

它的估算比前两个复杂一些。一般来说,中间激活值的大小约等于:

中间激活值 ≈ 层数 × 隐藏维度 × 序列长度 × 字节数 × 系数

这个系数取决于模型结构。对于标准的Transformer,系数大约在3-5之间。为什么?因为每层至少需要保存:

  • Attention的Q、K、V(3个张量)
  • Attention的输出(1个)
  • FFN的中间结果(2个,gate和up)
  • LayerNorm的输入输出(2个)

加起来差不多8-10个张量,但有些可以复用或释放,所以实际系数在3-5左右。

举个例子,7B模型,序列长度2048,FP16:

中间激活值 ≈ 32 × 4096 × 2048 × 2 × 4 = 2.15 GB
我的建议: 中间激活值可以通过gradient checkpointing来减少,但推理时一般不需要。如果你做训练,这个值会大得多,因为要保存所有层的激活值用于反向传播。

1.5 优化器状态:训练时的“显存黑洞”

训练时,优化器状态才是真正的显存大户。以Adam优化器为例,每个参数需要保存:

  • 模型权重本身(1份)
  • 动量(momentum,1份)
  • 方差(variance,1份)
  • 梯度(gradient,1份)

一共4份。如果模型是FP16,那优化器状态就是4 × 2 = 8字节每参数。加上权重本身的2字节,总共10字节每参数。

所以一个7B模型,用Adam训练:

总显存 ≈ 7B × 10 = 70 GB

这就是为什么训练大模型需要那么多卡。我见过有人用单卡A100(80G)训练7B模型,结果连模型都加载不进去。

核心结论:
  • 推理时:权重 + KV Cache + 中间激活值
  • 训练时:权重 × 4(Adam) + 中间激活值(更大)
  • KV Cache随序列长度线性增长,是长文本推理的瓶颈
  • 优化器状态是训练时的显存黑洞,占权重的3倍

1.6 一张图看懂显存分配

下面这张图,是我自己画的一个显存分配示意图。你可以看到,不同场景下,各个“住户”的占比完全不同。

显存分配对比图(7B模型,FP16,序列长度2048) 推理场景 模型权重:14 GB(约81%) KV Cache:1.07 GB(约6%) 中间激活值:2.15 GB(约13%) 训练场景(Adam) 权重:14 GB 优化器状态:42 GB(约60%) 中间激活值:~10 GB 梯度:14 GB 注:训练场景总显存约80 GB,接近单卡A100上限 推理场景总显存约17.2 GB,单卡即可运行

1.7 实战估算:一个完整的例子

假设我们要部署一个13B模型,FP16精度,最大序列长度4096。我们来算算需要多少显存。

  1. 模型权重:13B × 2 = 26 GB
  2. KV Cache:2 × 40层 × 5120隐藏维度 × 4096序列长度 × 2 = 3.35 GB
  3. 中间激活值:40 × 5120 × 4096 × 2 × 4 = 6.71 GB
  4. 其他开销(CUDA context、框架缓存等):约2-3 GB

总计:26 + 3.35 + 6.71 + 2.5 ≈ 38.6 GB

嗯,这个数字意味着什么?一张A100(80G)完全够用,甚至还能跑个batch size 2。但如果换成H100(80G),因为H100的显存带宽更高,实际吞吐会更好。

我的经验: 实际部署时,我一般会预留20%的显存余量。因为有些框架(比如vLLM、TensorRT-LLM)会有额外的内存池开销。所以上面这个例子,我建议至少准备48 GB的显存。

1.8 避坑指南

最后,分享几个我踩过的坑:

  • 别信理论值:理论计算只是起点。实际加载模型时,框架会多分配一些内存。我见过一个13B模型,理论26G,实际加载后吃了31G。
  • KV Cache的batch维度:上面算的是单条序列。如果batch size是4,KV Cache要乘以4。很多人忘了这个。
  • 中间激活值可以优化:推理时,有些框架(如TensorRT-LLM)会复用中间张量的内存,实际占用比理论值小很多。
  • 量化不是免费的:INT4虽然省显存,但推理时可能需要额外的反量化操作,反而增加延迟。我建议先测一下再决定。

好了,显存去哪儿了,现在你应该心里有数了。记住一句话:推理看KV Cache,训练看优化器状态。这两个是显存的大头,其他都是小角色。


公众号:蓝海资料掘金营,微信deep3321