☰
大模型显存去哪了?训练推理显存账本与KV Cache容量规划
2026/10/19 5:39:24 网站建设 项目流程

显存去哪儿了?这个问题我问过的次数,可能比踩过 的 OOM 还多。装好模型、启动服务、把 batch 一调大,显存瞬间就没了;把 batch 调小,吞吐又上不去;上下文一拉长,整个服务肉眼可见地变慢。很多人第一反应是换一张更大的卡,但真懂的人会先掏出一张纸,把训练或推理的显存账本一笔一笔算清楚,再决定是该换卡、该量化,还是改参数。这次分享的核心就是这件事:大模型训练与推理的显存全景账本、FLOPs 白板手算,以及 KV Cache 容量规划。

适合谁看?一类是做私有化部署、想在小显存上跑更大模型的服务端开发者;另一类是刚接触大模型训练,经常被激活值、优化器状态这些隐蔽开销弄到预算直接超标的算法工程师。我会把训练侧和推理侧分开算账,KV Cache 公式和手算 FLOPs 的套路都过一遍,最后附一份排查速查表。

先给一个整体判断,这也是全篇最核心的一句话:训练显存的大头是优化器状态,推理显存的大头是 KV Cache。记住这句话,后面所有计算都围绕它展开。

1. 训练显存全景账本:为什么 7B 模型训练能吃上百 GB 显存

1.1 训练与推理的显存结构,从根上就不一样

很多人拿推理时的显存数字去估训练,结果总是差一个数量级。原因很简单:训练要反向传播,得存梯度、维护优化器状态;推理不要这些,但推理得一路缓存 K 和 V。两本账的结构完全不同,我习惯先看一张总账表,再往下拆。

开销项训练侧推理侧
模型权重bf16 权重 + fp32 主权重副本fp16/bf16 权重,或量化权重
梯度每参数一份无
优化器状态Adam 下每参数 12 字节无
KV Cache通常无随总 token 数线性增长
激活值 / 临时变量前向保存、反向释放prefill 阶段短暂存在

这张表能解释两个常见体感:训练时参数一涨,显存就飞了;推理时序列一长、并发一多,显存就没了。前者是固定开销在发力,后者是动态开销在膨胀。

1.2 Adam + 混合精度:每参数 16 字节的经典算法

训练账本第一大块是“每参数固定开销”。以现在最常用的 Adam + bf16 混合精度为例,一个参数在整个训练过程中要占多少字节?答案是 16 字节。这个数字值得贴在显示器边框上,它是几乎所有训练显存估算的起点。

拆开看更清楚。模型权重本身一份 bf16,2 字节;反向传播算出的梯度,暂存算 2 字节;接下来是重头,Adam 优化器要维护三个 fp32 张量:一份 fp32 主权重副本、一份一阶动量 m、一份二阶动量 v,每个 4 字节,合计 12 字节。2 + 2 + 12 = 16。

按这套算法,7B 模型训练最少也要 7×10^9×16 ≈ 112GB 显存,13B 大约 208GB,70B 直接破 1.1TB。这就是为什么大模型基本离不开多卡训练。如果你听到有人用 SGD 替代 Adam,每参数能降到 8 字节左右,代价是收敛曲线很可能变难看。理解了这条 16 字节规则,再去理解各种分布式显存切分方案就非常顺:无非就是把权重、梯度、优化器状态这三类东西分摊到各卡上,每张卡只持有一部分,用的时候再聚合,等于把 112GB 的整体开销拆到多张卡上。

1.3 激活值:训练里最容易漏算的“临时账”

固定开销算完还没结束。前向过程要保存大量中间张量给反向传播用,这就是激活值。它的可怕之处在于不随参数量简单线性涨,而是和 batch、序列长度、hidden size、层数搅在一起。举个例子:7B 模型,batch 8、序列长度 4096、hidden 4096、32 层,每个 token 每层要保存的中间张量常常有八九份,每份都是 batch×seq×hidden 的大小,全部叠起来轻松超过 60GB。注意,这还只是一个 batch。

遇到激活值爆显存,工业界标准解法是激活检查点(activation checkpointing):前向时只存少量边界张量,反向传播用到哪一块就重算哪一块。代价是多花大约三分之一的算力,换回激活值显存从“随层数线性叠加”变成接近“只留一份驻留量”,峰值能压下去一个数量级。所以训练时一 OOM,先别急着减 batch,先确认检查点到底开没开。这个次序我早期也搞反过,白花了不少时间。

2. 推理显存账本:KV Cache 才是动态大胃王

2.1 推理没有梯度,但也不只是权重

推理没有梯度,也没有优化器状态,账目清爽很多。固定部分主要是权重:fp16/bf16 下每参数 2 字节,7B 模型大约 14GB;如果做 INT4 量化,按 4bit 算每参数 0.5 字节,7B 大约 3.5GB,实际加上 scale 和 group 元信息,常见口径在 4.5GB 上下。另外运行库、执行环境还要占掉 1~2GB,这部分很多人直接漏算。

真正的重头是推理独有的动态开销:KV Cache。自回归解码时,每个已经生成出来的 token,都要把自己的 Key、Value 向量存下来,供后面所有 token 的注意力计算使用。这个缓存的规模与模型结构、上下文长度、batch 数直接相乘,而且它和权重不同,不是一次性买断的,是跟着生成步数持续累加的。所以服务开得越久、并发越高,这块越膨胀。

2.2 KV Cache 容量公式:从一次解码原理推出来

先回答一个基础问题:为什么要缓存 K 和 V?自回归生成时,每出一个新 token,都要和前面所有 token 计算 attention score。如果不缓存,等于每个新 token 都要把整个历史的 K、V 重新算一遍,复杂度直接平方级爆炸。缓存之后,每个新 token 只追加自己的 K、V,后续查询就是读一段连续的显存。这是一个典型用显存换算力的决策。

容量公式一张纸就能推出来。每一层,每个 token,要缓存 K 和 V 两组向量,每组长度是 KV 维度 kv_dim;再乘以精度字节数。整模型每 token 的 KV Cache 就是:

KV bytes/token = 2 × 层数 L × kv_dim × dtype_bytes

取一个常见的 7B 结构:32 层、hidden 4096、每头 128 维、32 个头。标准 MHA 下 kv_dim 就是 4096,fp16 时每 token 的 KV 是 2×32×4096×2 = 524288 字节,正好 512KB。你没看错,平均每个 token 要半 MB。跑一个 4096 长度、batch 4 的请求,KV Cache 就是 512KB × 4096 × 4 = 8GB。这个数算出来的时候,很多“灵异 OOM”的真相就浮出水面了。

2.3 GQA 与分页式 KV 管理:省和复用两条路

既然 KV Cache 决定推理密度,优化就往两个方向打。一个是从模型结构下手,用 GQA/MQA 减少 KV 头数量。还是上面那个结构,如果 KV 头从 32 个压缩到 8 组,kv_dim 从 4096 变成 1024,KV Cache 直接缩到四分之一,从每 token 512KB 降到 128KB。现在不少开源模型默认带 GQA,就是在用结构设计控制部署成本。

另一个方向是分配方式,业界现在普遍用分页式 KV 管理思路:不再为每个请求预留一整段连续显存,而是把 KV Cache 切成固定大小的页,按需分配。这样做能显著降低碎片浪费,也方便多请求共享同样的前缀。注意分页会有页对齐的开销,通常页大小在 16 或 32 token 量级,所以实际规划要在公式基础上留一点余量,不能把显存算到一滴不剩。

3. FLOPs 白板手算:一张纸推完训练算力和生成速度

3.1 手算基础:每个参数“两遍乘加”的账

FLOPs 手算没有想象中复杂,核心就一条:矩阵乘法里,每个权重参数被用一次,做一次乘法和一次加法,记 2 FLOPs。Transformer 前向每 token 大致等于 2 × 参数量 N,也就是 2N FLOPs。反向呢?反向要同时算权重梯度和输入梯度,工作量大约是前向的两倍,也就是 4N。所以训练每 token 的总计算量是 2N + 4N = 6N。

写成更常用的形式就是:训练总 FLOPs ≈ 6 × N × D,N 是参数量,D 是训练总 token 数。这个公式忽略了一些小项,比如 normalization 层、attention score 计算里偏小的部分,但对数量级判断非常够用。我在白板上推预算时用的就是这一条,误差通常不会超过百分之二三十,足够决策。

3.2 白板演算:7B 模型训 1 万亿 token 要多少卡日

来一个完整算例。N = 7×10^9,D = 10^12,训练总 FLOPs = 6 × 7×10^9 × 10^12 = 4.2×10^22。这个数字很大,但除以单卡日产能就变得可感知了。

取一个常见的数据中心加速卡规格:bf16 峰值约 312 TFLOPS,显存 80GB 级别。实际训练不可能全程跑在峰值,业界常看 MFU(算力利用率),一个相对健康的区间是 0.35 到 0.55。取 0.5,单卡日产能大概是 312×10^12 × 0.5 × 86400 ≈ 1.35×10^19 FLOPs。那么完成 4.2×10^22 的总量,需要多少卡日?

4.2×10^22 ÷ 1.35×10^19 ≈ 3111 卡日。

接下来就简单了:256 卡并行大约 12 天,512 卡大约 6 天,64 卡要拖到 48 天。这个推演能直接告诉你预算和排期。如果想砍时长,要么加卡,要么换更激进的算力利用率优化。手算的意义就是先拿到数量级,别被模型规模唬住,也别被纸面峰值骗了。

3.3 推理速度的物理红线:Decode 卡在带宽而不是算力

训练看算力,推理生成阶段却看带宽。先说 decode,也就是一个 token 一个 token 往外蹦的阶段,每 token 只需要前向 2N FLOPs。7B 模型也就是 14 GFLOPs,这点计算量对一张 312 TFLOPS 的卡来说,理论上每秒可以生成几千个 token,就算 MFU 只有 0.3 也有约 6600 token/s 的理论空间。

但实测远到不了。原因是 decode 每一步都要把全部权重从 HBM 里读一遍。7B fp16 权重 14GB,按 2TB/s 的显存带宽算,单是读权重这一步就至少 7ms,对应上限约 140 token/s。把权重量化到 INT4,4.5GB 读一遍只要 2.25ms,理论速度能提到 440 token/s 左右。所以量化对推理不仅是省显存,还直接解锁生成速度,这是很多人没想明白的一点。

更要命的是 KV Cache 也参与这个读取过程。随着上下文边长,每步要读的 KV 越来越多,所以长对话越聊越慢是物理规律,不是框架优化不到位。推理服务的速度预算,核心公式是“权重字节数 + 当前 KV 字节数”除以带宽,而不是看 FLOPs。这一点想通之后,你就不会再去纠结“为什么算力这么高却跑不满”了。

4. KV Cache 容量规划实操:24GB 卡能跑多大窗口

4.1 四步法:从显存数字反推 batch 与 max seq

现在到最实用的部分:给定一张卡,怎么规划推理服务。我习惯用四步法。

第一步,算权重占用。比如 7B 模型 INT4 权重约 4.5GB。第二步,扣掉框架、运行环境等预留,一般留 2GB。第三步,确定 KV Cache 的精度和 kv_dim,套公式算出每 token 占用。第四步,用剩余显存反推可容纳的总 token 数。

以 24GB 卡为例:24 - 4.5 - 2 = 17.5GB 可用于 KV Cache。假设 KV 是 INT8、GQA 8 组、32 层、head_dim 128,那么 kv_dim = 8 × 128 = 1024,每 token 占用 2 × 32 × 1024 × 1 = 65536 字节,也就是 64KB。17.5GB ÷ 64KB ≈ 26.7 万 token。如果并发请求是 16 路,平均每路能分到约 1.67 万 token;如果并发 32 路,每路约 8300 token。这个数直接决定 max sequence length 该设多少。

4.2 容量计算小脚本:把公式变成预算工具

拿公式手算没问题,但我习惯写一个几十行的小脚本,改参数快得多,也方便下次直接复用。

def kv_bytes_per_token(num_layers, kv_heads, head_dim, dtype_bytes): kv_dim = kv_heads * head_dim return 2 * num_layers * kv_dim * dtype_bytes def max_total_tokens(gpu_gb, weight_gb, reserve_gb, kv_per_token): usable = (gpu_gb - weight_gb - reserve_gb) * 1024 * 1024 * 1024 return int(usable / kv_per_token) # 参数区:24GB 卡,7B INT4 权重,GQA 8 组 L, kv_heads, head_dim = 32, 8, 128 for dtype in (1, 2): kv = kv_bytes_per_token(L, kv_heads, head_dim, dtype) print("dtype_bytes:", dtype, "per token KB:", kv / 1024) total = max_total_tokens(24, 4.5, 2.0, kv) print("max total tokens:", total) for batch in (8, 16, 32): print(" batch", batch, "-> avg seq", total // batch)

这段脚本输出很直观。同一张卡,dtype_bytes 从 1 变成 2,也就是 KV 从 INT8 换成 fp16,可容纳的 token 总量直接对半。批处理和最大上下文就是一对跷跷板:目标并发越高,单条上下文就得越短。

4.3 规划时容易忽略的两件事:连续批处理与页对齐

第一个容易踩的坑是连续批处理。在线服务很少用固定 batch,而是边生成边补新请求,所以 KV Cache 的真实占用等于“当前正在处理的所有请求的已有 token 数之和”。如果你把 max_num_seqs 设得很大,但每个请求都很短,占不满理论容量;反过来,如果请求都长,计算出来的平均长度就不可信。因此规划时既要看总和,也要给突发峰值留 20% 余量。

第二个坑是分页式 KV 管理的页对齐。页大小固定后,最后一个未写满的页仍然占整页空间。假如页大小是 32 token,你每个请求实际只用到 10 token,这一页的另外 22 token 空间也不会给别人用。也就是说,理论公式是最理想状态,实际容量要打个折扣。把 max sequence length 设置成页大小的整数倍附近,能减少这种浪费。

4.4 吞吐优先还是延迟优先:KV 量化是最后一道杠杆

同样显存预算下,可调的旋钮就那几个:权重精度、KV 精度、并发数、上下文长度。我总结过一个选择顺序:先确认延迟上限,再倒推 batch 和上下文,最后用 KV 量化补差。权重量化优先,因为它同时省显存、降带宽、提速度;KV 量化次之,精度损失通常可控,但对某些敏感任务需要实测验证;最后才考虑砍并发,因为砍并发直接掉吞吐。

配置每 token KV 占用17.5GB 可容纳总 tokens16 路并发时平均 seq
MHA + fp16512KB约 3.3 万约 2091
MHA + int8256KB约 6.7 万约 4172
GQA + fp16128KB约 13.3 万约 8346
GQA + int864KB约 26.7 万约 16689

这张表一眼可以看出 GQA 和 KV 量化叠加的威力:同样是 24GB 卡,同样是差不多的模型,容量能差七八倍。所以遇到显存瓶颈,别急着喊换卡,先把结构优化的钱省下来。

5. 显存与性能问题排查:从现象到账本

5.1 三张高频场景对照表

实战里遇到的问题,追根溯源都能归到某一类账上。

现象最可能的账本原因第一动作
训练一开就 OOM优化器状态占大头开激活检查点,再考虑减 batch
训练小 batch 正常,大 batch 炸激活值随 batch 线性暴涨检查 checkpointing,砍 batch
推理小并发正常,并发一高就炸KV Cache 总量超预算降 max_num_seqs,或 KV 量化
上下文越长服务越慢每步读取的 KV 越来越多KV 量化,限制最大长度
服务启动就占满显存,但吞吐低框架预分配与碎片调整缓存上限,必要时重启后端

训练侧的炸点多半在优化器状态和激活值;推理侧的炸点几乎都是 KV 和并发设置。定位的第一步不是改代码,而是拿公式估算一遍,大概率还没动手就知道问题在哪了。

5.2 经验:看任务管理器数字之前,先看账本

有一个很常见的误区:看到显存用量接近 100%,就以为模型真的吃满了。实际上很多推理框架启动时会预分配整块 KV 缓存,任务管理器显示的是预留量,不是实时使用量。预留高不代表真的用满了,但 OOM 发生时才代表真的不够。遇到这种情况,先查缓存的真实使用率,再看是不是有请求数量过多。

另一个经验是永远留余量。分页对齐、临时张量、运行时波动,都会让实际容量低于理论值。我自己的习惯是按理论值的 80% 做容量设定,剩下 20% 给尖峰和内存碎片。手算的价值不是算出一个精确到小数点的东西,而是让你在启动服务之前就知道哪一项会先爆。

5.3 优化措施的代价清单

最后把常见优化手段和它们付出的代价列一下,没有免费午餐。

方案省下什么要付什么代价
激活检查点激活值显存约 30% 额外算力
分布式显存切分权重 / 梯度 / 优化器状态通信开销
权重 INT4权重占用与带宽精度损失,需校准
KV INT8KV Cache 容量潜在精度影响
GQA 结构KV Cache 容量表达能力折中
分页式 KV 管理内存碎片少量页对齐浪费
参数 offload显存用量显著掉速

每次调优前问自己一句:我现在动的是哪本账?省下的显存是从固定开销里省,还是从动态开销里省?代价是什么?所有“玄学 OOM”和“诡异掉速”,最后都能落到这张表里。

我个人做容量规划的习惯是:任何模型上线前,先把权重、KV、预留这三位算一遍,写进注释里,再决定 batch 和 seq 上限。踩过几次坑之后你会发现,显存其实从来没有凭空消失,只是你当时没算清它去了哪。这次分享就把这套手算方法完整交给你,下次遇到 24GB 卡跑不动大上下文,别急着怀疑模型有问题,拿出公式先算算 KV 那本账。

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

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

立即咨询