简介:从显存占用、精度策略、并行切分三个维度,拆解大模型训练与推理的本质差异,帮助读者理解为什么“能推理”不等于“能训练”,以及并行方案选型时的关键权衡与评估要点。
在评估大模型落地可行性时,很多人会先做一个简单测算:一个 7B 参数的模型,用 bf16 存储大约需要 14GB 显存,而一张 80GB 的加速卡看起来绰绰有余。于是得出结论:这张卡可以跑这个模型。
这个结论对推理场景基本成立,但对训练场景则完全不成立。本文要对比的,正是“大模型推理”与“大模型训练”这两个阶段在显存占用、精度策略和并行切分方式上的核心差异,并进一步对比不同并行切分方案之间的取舍。
理解这组差异,直接决定了三件事:需要多少卡、通信开销有多大、训练能不能稳定收敛。
推理:模型参数固定,前向计算产出结果。显存主要被权重和激活占用,不需要保存梯度,也不需要优化器状态。
训练:除了前向,还要反向传播计算梯度,并用优化器更新参数。显存要同时容纳权重、梯度、优化器状态以及大量中间激活。三者叠加,量级与推理完全不在一个层面。
换句话说,推理是“读模型”,训练是“读模型 + 写模型 + 记录修改历史”。多出来的部分,才是显存账的真正大头。
尽管差异巨大,推理与训练仍有共同的技术底座:
正因为底座相同,才容易让人误以为“推理能跑,训练也能跑”。差异恰恰出现在底座之上的额外开销。
推理阶段,显存大致由两部分组成:
7B 模型 bf16 推理,权重约 14GB,加上激活和 KV 缓存,单卡 80GB 通常可以容纳。
训练阶段采用混合精度 + Adam 类优化器时,每个参数要背负的状态远不止权重本身。以下是通用账目(不含激活):
| 项目 | 精度 | 每参数字节 |
|---|---|---|
| 权重 | bf16 | 2 |
| 梯度 | bf16 | 2 |
| 主权重 | fp32 | 4 |
| 优化器一阶动量 | fp32 | 4 |
| 优化器二阶动量 | fp32 | 4 |
| 合计 | — | 16 |
用一段通用示意代码可以直观看到这个账:
P = 7e9bytes_per_param = 2 + 2 + 4 + 4 + 4 # bf16权重 + bf16梯度 + fp32主权重 + 两个fp32动量total = P * bytes_per_param / 1e9print(f"每参数 {bytes_per_param} 字节,总计 {total:.0f} GB")# 每参数 16 字节,总计 112 GB
7B 模型光“状态”就要约 112GB,还没算激活。序列一长,激活往往比状态还大。所以“一张卡装不下”是训练的起点,而非例外。
既然 fp32 主权重占了 4 字节,看起来像浪费,能否全部用 bf16?答案是:不能。
原因是 bf16 的有效尾数位远少于 fp32。当参数更新量很小时,更新值在 bf16 下会被舍入到零,参数实际上不再变化。用一段通用示意代码可以复现这个现象:
import numpy as npdef to_bf16(x):b = np.asarray(x, dtype=np.float32).view(np.uint32) & np.uint32(0xFFFF0000)return b.view(np.float32)w = np.float32(1.0)for upd in (1e-2, 1e-3, 1e-4):print(f"更新量 {upd:g}: fp32 -> {np.float32(w+upd):.6f} "f"bf16 -> {to_bf16(to_bf16(w)+to_bf16(upd)):.6f}")w32 = np.float32(1.0); w16 = to_bf16(1.0)for _ in range(10000):w32 = np.float32(w32 + 1e-4)w16 = to_bf16(w16 + to_bf16(1e-4))print(f"累加 10000 次 1e-4: fp32 = {w32:.4f}, bf16 = {w16:.4f}")
典型输出会显示:更新量 1e-3 和 1e-4 时,bf16 结果几乎不变;累加一万次后,fp32 从 1.0 涨到约 2.0,而 bf16 仍停在 1.0。这就是必须保留 fp32 主权重的原因——它承载的是“小步慢走”的累积精度。
单卡放不下,就必须切。切分方式和通信代价,是训练与推理差异最大的工程环节。
| 切分方式 | 切什么 | 主要代价 | 典型适用 |
|---|---|---|---|
| 数据并行(含状态分片) | 优化器状态、梯度、权重 | 每步需 all-gather 权重、reduce-scatter 梯度 | 模型能放进单卡但状态放不下 |
| 张量并行 | 单层内的矩阵 | 每层前后都要通信,延迟敏感 | 单层过大,单卡放不下 |
| 流水并行 | 按层切分 | 存在流水气泡,需调度填充 | 层数多、跨节点带宽有限 |
| 序列并行 | 序列维度 | 注意力计算需跨卡通信 | 长序列场景 |
以状态分片为例,112GB 的状态切到不同卡数:
| 卡数 | 每卡状态显存 |
|---|---|
| 1 | 112.0 GB |
| 8 | 14.0 GB |
| 64 | 1.8 GB |
看起来 64 卡很美好,但代价是每一步都要把参数 all-gather 回来、梯度 reduce-scatter 出去。卡越多,单卡算得越少,通信占比越高。切法不对,加速卡大部分时间在等网络。
| 维度 | 推理 | 训练 |
|---|---|---|
| 显存构成 | 权重 + 激活 | 权重 + 梯度 + 优化器状态 + 激活 |
| 每参数开销(bf16+Adam) | 约 2 字节 | 约 16 字节 |
| 7B 模型基础显存 | 约 14GB | 约 112GB(不含激活) |
| 精度要求 | bf16 通常足够 | 需 fp32 主权重保精度 |
| 并行需求 | 单卡或少量卡即可 | 必须多卡切分 |
| 通信压力 | 较低 | 高,随卡数上升 |
| 主要瓶颈 | 显存带宽、KV 缓存 | 通信、激活、收敛稳定性 |
从推理方案转向训练方案,或从一种并行策略切换到另一种,需要提前评估:
大模型训练之所以难,不是难在“模型放不下”这一句话,而是难在三笔账同时成立:状态账要求多卡切分,精度账要求保留 fp32 主权重,通信账要求切分方式与网络拓扑匹配。推理与训练的最大差异,就在于训练多了梯度与优化器状态这两层开销,并因此被迫引入并行与通信。
选型时,先判断场景是推理还是训练,再按 16 字节/参数估算状态显存,最后根据单层大小和互联带宽选择切分维度。切法不对,卡再多也是在等网络;精度配错,步长再小也训不动。把这两点想清楚,大模型训练的显存与并行方案就不难落地。