☰
大模型显存去哪了?训练推理账本、FLOPs与KV Cache容量规划全解析
2026/10/20 16:37:11 网站建设 项目流程

搞深度学习这些年,被问得最多的一句话是:"明明买的 80G 的卡,模型还没跑两步就 OOM,显存到底去哪了?" 到了大模型训练和推理阶段,这个问题更扎心:一个 7B 模型 BF16 权重才 14GB,为什么全参训练动不动就要上百 GB?为什么推理时上下文一拉长,KV Cache 转眼就吃掉几十 GB?要回答这些,不能靠感觉,得会算账。这篇就当第 27 讲,把"显存全景账本、FLOPs 白板手算、KV Cache 容量规划"三件事一次讲透:训练和推理的钱分别花在哪几项;矩阵乘法到 Transformer 的 FLOPs 怎么手推;KV Cache 的公式从哪来、不同模型吃多少、并发怎么规划。适合正在做微调、部署推理服务,或者总被 OOM 折磨的同学,看完你就能在白板上把显存需求算明白,而不是等任务跑挂了才后悔。

1. 显存全景账本:训练和推理的钱分别花在哪

1.1 训练账本:权重、梯度、优化器状态与中间激活

先说训练。很多人只盯着模型权重看,觉得 7B 模型 14GB 权重,一张 24G 卡总够了吧?实际完全不是这样。显卡在训练时要同时装下四类东西:

  • 模型权重:BF16/FP16 下每参数 2 字节,7B 就是 14GB;
  • 梯度:和权重同尺寸,也是每参数 2 字节,14GB;
  • 优化器状态:AdamW 要维护一阶动量 m 和二阶动量 v,各自是 FP32、每参数 4 字节,合起来 8 字节/参数,56GB;
  • FP32 主权重:混合精度训练里为了避免 BF16 精度不足导致更新失效,框架通常还会额外保留一份 FP32 主权重,每参数 4 字节,28GB。

把这几项加起来:2+2+8+4=16 字节/参数,7B 模型就是 112GB。这就是为什么 80G 单卡根本装不下 7B 全参微调,必须用 ZeRO/FSDP 把梯度、优化器状态分片到多卡。

这里有个经常被忽略的细节:如果不保留 FP32 主权重,每参数可以省 4 字节,总占用降到 84GB(2+2+8)。但代价是 Adam 更新收敛变差,特别是小学习率场景很容易踩精度坑。我建议你把 16 字节/参数当作训练内存估算的"标准默认值",只有明确知道自己在做什么时才往下降。另一个思路是 LoRA/QLoRA:冻结原权重,只训练低秩适配器,优化器状态只按 rank 维度的少量参数计算,成本能降一个数量级,这也是显存不够时最常用的出路。

除了这几大块,还有一笔容易被低估的开销:中间激活。反向传播要重算梯度,就必须把前向过程里每一层的输入、输出、dropout mask 留住。经验公式是:不做激活重计算(activation checkpointing)时,每层大约要 B×S×H×34 字节(BF16 场景),也就是 batch、序列长度、隐层维度三者相乘再乘一个常数。拿 B=4、S=4096、H=4096、L=32 举例,每层约 2.3GB,32 层就是 73GB。开了激活重计算后,这笔钱可以压到 1~2GB 级别,代价是前向要多算一遍,FLOPs 涨 30% 左右。等后面讲 FLOPs 时你会看到,这笔交易到底划不划算。

注意:上面的 34 是工程上的经验常数,不同框架、不同算子融合程度会有差异。你要做的是用它做数量级判断,真正落地时以框架 profiler 的数据为准。

1.2 推理账本:权重、KV Cache 与临时张量

推理和训练完全是另一套账。推理不需要梯度、不需要优化器状态,主要开销是三项:

  • 权重:照旧是每参数 2 字节(BF16)或量化后更低;
  • KV Cache:自回归生成时必须记住历史 token 的 Key 和 Value,后文会专章推导,先记住它是随上下文长度线性增长的"活钱";
  • 临时张量:attention softmax 的中间结果、gating 输出等,prefill 阶段比较占,decode 阶段通常很小。

很多同学问:为什么训练把激活算一次反而不够用,推理反而要单独为 KV Cache 做容量规划?因为训练是一锤子买卖,激活是短暂的;推理的 KV Cache 却要持续占着,直到该序列被淘汰。预填充(prefill)阶段模型是算力瓶颈,1 个 token 的前向几乎要把所有权重读一遍;解码(decode)阶段则变成典型的内存带宽瓶颈——每生成一个 token 都要重新把全部权重从显存搬到计算单元,所以单用户生成速度基本等于"权重字节数 / 显存带宽"。这也是同样的模型,8 卡训起来飞快、单卡推理却很慢的根本原因。

推理阶段还有一笔很多人不算的账:CUDA 上下文和显存碎片。空卡启动一个进程通常要吃掉 0.5~1GB,长时间反复申请释放 KV block 后碎片会越来越严重。所以做容量规划时,我给每张卡留 2GB 左右的"系统冗余",不要算得刚刚好。

1.3 用 7B 模型算一笔完整账单

把上面两张账合起来,拿一个 7B 稠密模型做例子,你会看到训练和推理的显存需求完全是两码事:

项目训练(BF16 + Adam)推理(BF16 单路)
权重14GB14GB
梯度14GB无
Adam 动量 m/v56GB无
FP32 主权重28GB无
KV Cache(4K 上下文,GQA 8)训练无此项约 0.5GB
中间激活(B=4, S=4K)数十 GB 量级约 1~2GB
临时张量与系统开销少量约 1~2GB

训练这边合计轻松突破 100GB,推理单路才 17GB 左右。这个差距解释了所有工程实践里的怪现象:训练怕的是优化器状态和激活,推理怕的是权重读取带宽和 KV Cache 扩容。对症之后,优化方向完全不同:训练优先做分布式状态分片和激活重计算,推理优先做权重量化、GQA 改造和 KV Cache 管理。很多人拿着训练的思路去优化推理,或者反过来,结果自然是白忙一场。

2. FLOPs 白板手算:从矩阵乘法到 Transformer 全套

2.1 矩阵乘法的 FLOPs 到底怎么数

先立规矩。一次矩阵乘法 C[M,N] = A[M,K] × B[K,N],要做 M×N×K 次乘加,每次乘加算 1 次乘法和 1 次加法,所以业界统一口径是 2×M×N×K FLOPs。这个"2"是新手最容易漏的。一个线性层 y = xW,输入 x 是 [B,S,H],权重 W 是 [H,O],一次前向就是 2BSHO FLOPs。按 token 均摊,就是每个 token 2HO。

记住这个基本单位就够了,后面所有 Transformer 的 FLOPs 都是把它套进每一层逐项加出来的。我在白板上推的时候习惯一行一行写,而不是直接背公式,因为只有推导过一遍,你才知道哪个常数从哪来,模型结构一改也不会算错。

2.2 Transformer 单层前向的推导过程

假设模型隐层维度 H,标准多头注意力(每个 head 维度 d_k = H / n_heads)。对于单个 token,单层前向的开销如下:

  • QKV 投影:三个矩阵乘法,每个是 H×H,共 3×2H² = 6H²;
  • 注意力分数 QKᵀ:带因果掩码时,平均每个 query 要跟约 S/2 个 key 做点积,每个 head 开销约 S×d_k,所有 head 合计约 H×S;
  • 注意力加权求和 AV:与 QKᵀ 量级相当,也是约 H×S;
  • 输出投影:一个 H×H,2H²;
  • MLP(标准两层、中间 4H):第一层 H→4H 是 8H²,第二层 4H→H 也是 8H²,合计 16H²;若是 SwiGLU 这类三线性层结构,则要按 24H² 计。

逐项相加,标准 MLP 结构下单层前向约 24H² + 2HS + 若干可忽略项。细心的人会发现:24H² 恰好等于 2 × 12H²,而 12H² 正是这一层的参数量(QKV 3H² + 输出投影 H² + MLP 两层 8H²)。这说明一个很好用的结论:不看注意力那点序列相关项的话,每层前向 FLOPs ≈ 2 × 该层参数量,整个模型前向 1 个 token ≈ 2 × 全部参数量。

所以 7B 模型生成 1 个 token,纯算力上约 14 GFLOPs;8K 上下文的 prefill,约 2×7B×8192 ≈ 1.15×10¹⁴ FLOPs,也就是 115 TFLOPs。这个量级配合高端加速卡的纸面 BF16 算力,prefill 应该是百毫秒级——但实际往往还要加上注意力、通信、内存搬运等开销。等看完 decode 阶段你就明白,算得快不代表生成快,瓶颈经常根本不在 FLOPs 上。

提示:QKᵀ 和 AV 两项里的 S 是序列长度,当上下文拉长到几万甚至百万 token 时,这部分的增长会让"FLOPs ≈ 2×参数量"的近似失效。长上下文场景记得把这两项单算,否则你会低估总算力需求。

2.3 训练为什么是 6 倍前向:反向传播的 FLOPs 拆解

反向传播凭什么比前向贵一倍?以一个线性层为例。前向 y = xW 只有一次矩阵乘。反向要算两个东西:对输入的梯度 dx = dy·Wᵀ,和对权重的梯度 dW = xᵀ·dy。这两次矩阵乘的规模都与前向相同,所以反向的 FLOPs 是前向的 2 倍。逐层累加,训练一个 token 的总 FLOPs 就是前向 2N + 反向 4N = 6N,也就是常说的"训练一个 token 约等于 6 倍参数量 FLOPs"。这是大模型训练一切算力规划的地基。

有了 6N,你可以很快算训练总计算量:6 × 7B × 500 亿 token ≈ 2.1×10²¹ FLOPs。假设你用 8 张 A100(BF16 纸面约 312 TFLOPS),实际训练 MFU 按 40% 估,总有效算力约 8×3.12×10¹⁴×0.4 ≈ 10¹⁵ FLOPs/s,那么训练时长约 2.1×10²¹ / 10¹⁵ ≈ 2.1×10⁶ 秒,约 24 天。这样你在规划数据量和卡数时,直接拿 6ND 一除就有数了,不用等集群排队跑一轮才知道行不行。

再补充一个工程常识:开了激活重计算后,实际 FLOPs 会从 6N 涨到约 7.2~8N;而使用张量并行、流水并行时,通信本身不算 FLOPs 但会拖慢有效吞吐。所以真实世界里 MFU 能到 50% 就算很健康了,白板手算的"理论下限"和实际耗时要留出 2~3 倍余量。

阶段每个 token 每层的 FLOPs说明
前向约 2×参数量线性层为主,注意力项随 S 增长
反向-输入梯度约 2×参数量dx = dy·Wᵀ
反向-权重梯度约 2×参数量dW = xᵀ·dy
训练合计6×参数量前向 + 反向

3. KV Cache 容量规划:公式推导与实例测算

3.1 KV Cache 大小公式与 GQA 改造

KV Cache 的来历要从注意力机制本身讲。自回归生成到第 t 步时,第 l 层的 query q_t 要和过去所有 token 的 key 做点积,再对这些 key 对应的 value 做加权求和。如果不想每步都重算历史 token 的 K、V,就必须把它们存在显存里,这就是 KV Cache。它的大小由四个维度决定:层数 L、batch 大小 B、已生成长度 S、以及每层 KV 的隐层宽度 H_kv,再乘 2(K 和 V 各一份)、乘上每元素字节数。写成白板公式就是:

KV Cache 字节数 = 2 × L × B × S × H_kv × 字节/元素

其中 H_kv 由注意力头结构决定:MHA(多头注意力)下 H_kv = H;GQA(分组查询注意力)下 H_kv = H × n_kv / n_q,也就是只有查询头分组共享少数几个 KV 头。之所以说 GQA 是 KV Cache 的"主刀医生",是因为它直接砍掉的是乘数项里最贵的那一项。MHA 到 MQA 是极端方案,所有查询头共享一组 KV,KV 占用缩到 1/n_q;GQA 是中间态,用 2~8 个 KV 头换来质量损失最小的压缩。

举两个落地例子。7B 级模型的典型参数:L=32、H=4096、n_q=32、d_k=128。MHA 下每个 token 每层要存 2×4096×2 字节 = 16KB,整模型每 token 就是 512KB;GQA 用 8 个 KV 头后降到 128KB。70B 级模型则更夸张:L=80、H=8192、n_q=64,MHA 下每 token 约 2.6MB,GQA-8 后仍有 320KB。你会发现 KV Cache 的增长几乎和模型规模、上下文长度是线性叠加的关系,这也是为什么长上下文系统几乎清一色上 GQA + KV 量化。

3.2 常见模型的容量测算对比

用同一个公式把常见配置的 KV 开销列出来(单序列、BF16、不开启 KV 量化):

模型规模层数/隐层KV 头结构每 token 开销4K 上下文32K 上下文128K 上下文
7B 级32 / 4096MHA512KB2GB16GB64GB
7B 级32 / 4096GQA-8128KB0.5GB4GB16GB
70B 级80 / 8192MHA2.6MB10.5GB86GB344GB
70B 级80 / 8192GQA-8320KB1.3GB10.7GB43GB

看到差距了吗?70B 模型如果还是 MHA,跑 128K 上下文时单条对话的 KV Cache 就是 344GB,比整机显存都大,这显然是不可服务的。而同样 70B 用 GQA-8,128K 上下文降到 43GB——仍然很重,但至少可以靠多卡张量并行硬扛。如果在 KV Cache 上再叠加 4-bit 量化,还能再砍一半,代价是精度略有下降。这就是为什么"长上下文"不是免费的:每多一万 token 上下文,70B 级模型就要多准备 3.2GB 显存(GQA-8),你得想清楚自己的业务到底需不需要这么长的上下文。不少团队在 32K 和 128K 之间反复横跳,最后发现真正高价值的用户请求平均都不到 8K,为此付出十几倍的 KV Cache 成本并不划算。

3.3 容量规划实战:并发、上下文与量化组合

容量规划的本质是一道算术题。以单卡 80GB 部署 7B BF16 推理为例:

  • 权重 14GB,系统冗余留 2GB,预留给 KV Cache 的空间约 64GB;
  • GQA-8、4K 上下文时,每条并发序列约 0.5GB,理论上能开 128 路并发;
  • 同样配置换成 MHA,每路变 2GB,并发直接掉到 32 路;
  • 若把上下文拉到 32K,GQA-8 下每路 4GB,并发只剩 16 路。

这个账一算就明白:并发数、上下文长度、KV 量化精度三者是跷跷板。部署时我习惯先定业务 SLA 里的最大上下文,再反推可接受并发,最后才决定要不要上 KV 量化。另外还要预留好 prefill 阶段的临时激活——特别是 batch 大、上下文长的时候,prefill 的中间张量可能瞬间吃掉几十 GB,虽然只持续几十毫秒,但在峰值统计里会造成严重的显存尖峰。建议用框架自带的 profiler 或显存监控把 prefill 峰值和 decode 稳态分开记录,不要只看平均占用,否则你规划的 128 路并发会被 prefill 的瞬时峰值直接击穿。

工程上还有一批 KV Cache 优化技巧可以叠加:滑动窗口只保留最近 N 个 token 的 KV;token 淘汰/压缩策略;KV Cache 换出到 CPU/内存做 offload;以及高性能推理框架普遍采用的页式 KV 分配,消息队列调度、显存碎片管理这些思路都被融合进去了。它们都是在上面公式基础上的进一步"抠内存",但再花哨的操作,也改变不了公式里最基础的几个乘数——规划时先把公式算对,再做优化,不然只会越优化越混乱。

4. 实操中的显存排查与常见问题速查

4.1 四步定位显存去哪了

看到 OOM 不要慌,按顺序排查,比瞎调参管用得多。第一步,分清是"分配"还是"保留"。nvidia-smi显示的是驱动层面的显存占用,包含 CUDA context、缓存块等,而框架自带的torch.cuda.memory_summary()才显示进程内已分配和未分配的缓存。很多所谓"显存泄漏"其实是框架缓存块没归还,不代表真的泄漏,重启进程或调低缓存上限就解决了。

第二步,区分峰值出现在哪个阶段:是前向 rollout、反向梯度回传、优化器更新,还是推理的 prefill/decode。可以在关键节点用torch.cuda.reset_peak_memory_stats()和torch.cuda.max_memory_allocated()打点,一次训练任务打完点,峰值位置一目了然。

第三步,逐项开关变量做对照测试:关闭激活重计算看激活占多少、把 batch 减半看线性比例、只跑一次反向看梯度那部分是否异常。第四步,查碎片。如果分配的峰值不高但总是 OOM,大概率是显存碎片化严重,尤其频繁申请释放 KV block 的推理服务常见此问题,页式 KV 分配类方案就是冲这个来的。

实操心得:遇到训练 OOM,第一反应不要查激活公式,而是先看打点日志里峰值出现在哪个函数。我见过太多人凭感觉把 batch 缩了一半,结果问题出在优化器状态的 56GB 上,怎么缩 batch 都没用,最后加一张卡才解决。

4.2 常见显存问题速查表

现象大概率原因快速解法
前向过程中 OOM,S 和 B 越大越严重中间激活爆炸开激活重计算、减小 batch、用梯度累积
反向或优化器更新时 OOM梯度/优化器状态超限ZeRO/FSDP 分片、换 LoRA、去掉 FP32 主权重
推理上下文越长越 OOMKV Cache 超限上 GQA 模型、KV 量化、降上下文上限、限并发
总占用不高但反复 OOM显存碎片重启进程、用页式 KV 分配、提前统一申请
换卡后显存占用变化异常环境变量与物理卡不对应检查容器内的 CUDA_VISIBLE_DEVICES 与nvidia-smi对应关系

这张表不是让你背的,而是排查时的思维索引。看到 OOM 先对号入座,再决定从哪个方向动手,能省下一整个下午的盲目调试。我自己的习惯是:所有显存问题先用一句话描述"峰值出现在哪个阶段 + 哪个配置项线性放大",能写清楚这句话,问题基本解决一半。

4.3 踩坑心得与显存规划建议

最后分享几条我自己踩出来的规律。第一,永远按峰值算账、按冗余部署。白板估算出来 17GB 的推理服务,我至少按 20GB 规划;训练估算 112GB,我不会在 2×80GB 上卡线跑,而是留出通信和临时 buffer 的空间。第二,训练内存用"16 字节/参数 + 激活项"打底,推理用"2 字节/参数 + KV 项"打底,这两句口诀覆盖了我 90% 的规划场景。第三,公式算完一定要用 profiler 回验一次,模型结构、框架实现细节会让常数有 10%~30% 的偏移,只有实测数据才能校准你的"手算模型"。

另外一个很容易踩的坑是序列打包(sequence packing)。为了提升训练效率把不同长度的样本拼到同一个 batch 里,激活的 S 会按最长长度算,显存可能瞬间翻倍。如果你发现训练吞吐没升多少、显存却先爆了,多半就是打包策略没有按实际长度分桶。推理侧同理,批量请求的 KV Cache 是按每条序列实际长度计费的,长短请求混在一起时要防止长上下文请求把整卡 quota 吃掉,最好按上下文长度分层限流。

我在实际项目里还有个习惯:任何新模型上线前,先跑一个最小化的热身请求,把 prefill 峰值、decode 稳态、KV 增长速率三个数据打出来存档,作为后续所有容量规划的基准。白板手算解决"方向对不对"的问题,实测数据解决"数量准不准"的问题,两者结合,显存管理就不再是玄学。

最后再分享一个小技巧:别总盯着大模型怎么"省显存",先问自己怎么"花得明白"。训练时把优化器状态、梯度、激活三大头分别打点统计,推理时把权重、KV、系统开销分开记账,每一笔都对应到公式的某个乘数上。这样你会突然发现,真正需要优化的不是某个玄学参数,而是最便宜最好改的那一两个乘数——对大多数人来说,先把 batch 和上下文长度管好,显存问题已经解决了八成。

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

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

立即咨询