为什么相同参数量的模型,显存占用差异巨大?

引子:27B 模型的显存之谜

同样是一个 27B(270 亿参数)的大模型,在不同精度下部署时显存占用差异巨大:

张量类型 每参数字节 仅权重显存 含推理开销估算
FP16 / BF16 2 字节 ~54 GB ~60 GB
FP8 1 字节 ~27 GB ~30 GB
FP32 4 字节 ~108 GB ~120 GB

通俗类比

这和摄影中 RAW 与 JPG 的区别如出一辙:

  • RAW 格式 — 无损,精度拉满,但文件巨大,加载慢
  • JPG 格式 — 牺牲了肉眼几乎察觉不到的细节,换来体积骤减,加载飞快

大模型之所以能用低精度,核心前提是它天生的参数冗余性——在成千上亿的参数中,绝大多数参数的微小数值变化对最终输出的影响几乎微乎其微。这让我们能够安全地压缩参数精度。


计算机底层:数字怎么表示

任何数据的本质都是数字,而计算机用二进制来处理数字。一个浮点数在计算机中的表示分为三段:

1
2
3
4
5
┌──────────┬──────────────┬──────────────────┐
│ 符号位 │ 指数位 │ 尾数位 │
│ (Sign) │ (Exponent) │ (Mantissa) │
│ 1 bit │ 决定范围 │ 决定精度 │
└──────────┴──────────────┴──────────────────┘
  • 符号位 — 1 bit,标记正负
  • 指数位 — 决定数值能表示的大小范围(是 1000 还是 10000)。大模型最怕这里不够用导致数值溢出
  • 尾数位 — 决定精细程度,即小数点后能保留多少细节

关键洞察: 大模型对尾数精度的敏感度远低于指数位。这就是为什么 BF16(尾数只有 7 bit)能替代 FP32(尾数 23 bit)而性能几乎不降。


五种主流浮点型张量详解

FP32 — 传统单精度浮点数

1
结构:1 bit 符号位 + 8 bit 指数位 + 23 bit 尾数位 = 32 bit (4 字节)
维度 评价
精度 ⭐⭐⭐⭐⭐ 最高
显存占用 ⭐ 最大(7B 模型仅权重需 ~28 GB)
计算速度 ⭐ 最慢
动态范围 ⭐⭐⭐⭐⭐ 几乎拉满

典型用途: 梯度更新、优化器状态存储等关键环节,避免因精度损失导致梯度消失或爆炸。

一句话总结: 原画无损格式,什么都好,就是太占空间。


FP16 — 传统半精度浮点数

1
结构:1 bit 符号位 + 5 bit 指数位 + 10 bit 尾数位 = 16 bit (2 字节)
维度 评价
精度 ⭐⭐⭐ 中等
显存占用 ⭐⭐⭐⭐ 比 FP32 减半
计算速度 ⭐⭐⭐⭐ 快
动态范围 ⭐⭐ 最大仅 ±65,504

致命缺陷: 指数位只有 5 bit,数值范围太小。大模型的激活值和梯度很容易超出这个范围,直接溢出导致训练崩溃。需要靠损失缩放(Loss Scaling)来补救,麻烦且不稳定。

一句话总结: 简单粗暴砍一半体积,但训练容易溢出,现在很少用了。


BF16 — Brain Float 16(Google 制定)

1
结构:1 bit 符号位 + 8 bit 指数位 + 7 bit 尾数位 = 16 bit (2 字节)
维度 评价
精度 ⭐⭐⭐ 略低于 FP16(尾数少 3 bit)
显存占用 ⭐⭐⭐⭐ 同 FP16,2 字节
计算速度 ⭐⭐⭐⭐ 快
动态范围 ⭐⭐⭐⭐⭐ 与 FP32 同级

核心优势: 继承了 FP32 的 8 bit 指数位,动态范围与 FP32 一致。虽然尾数从 23 bit 降到 7 bit,但大模型对尾数精度并不敏感,这点损失几乎感觉不到。

一句话总结: 为大模型量身定做的半精度格式,现在绝大多数大模型训练的标准。


TF32 — Tensor Float 32(NVIDIA Ampere 架构引入)

1
结构:1 bit 符号位 + 8 bit 指数位 + 10 bit 尾数位 ≈ 19 bit 有效信息
维度 评价
精度 ⭐⭐⭐⭐ 介于 BF16 和 FP32 之间
显存占用 ⭐ 不省显存(存储格式与 FP32 相同,4 字节)
计算速度 ⭐⭐⭐⭐⭐ 在 Tensor Core 上加速
动态范围 ⭐⭐⭐⭐⭐ 与 FP32 同级

定位特殊: TF32 不是为了省显存,而是为了让原本用 FP32 训练的模型几乎不用改代码也能提速。

工作原理: 当 FP32 数据进入 Tensor Core 做矩阵乘法时,硬件会先按 TF32 规则对尾数精度进行截断/舍入,然后参与乘法运算,而累加仍保持 FP32 精度。

1
FP32 输入 → Tensor Core → 截断为 TF32 尾数 → 矩阵乘法 → FP32 累加输出

一句话总结: 表面上走 FP32 流程,底层偷偷提速,不省显存但解决 FP32 跑太慢的问题。


FP8 — 8 位浮点数(最激进压缩)

1
2
3
4
结构(两种变体):

E4M3:1 bit 符号 + 4 bit 指数 + 3 bit 尾数 = 8 bit (1 字节)
E5M2:1 bit 符号 + 5 bit 指数 + 2 bit 尾数 = 8 bit (1 字节)
维度 E4M3 E5M2
精度 ⭐⭐ 尾数更多,细节更好 ⭐ 尾数更少,精度更粗
显存占用 ⭐⭐⭐⭐⭐ 最小 ⭐⭐⭐⭐⭐ 最小
计算速度 ⭐⭐⭐⭐⭐ 最快 ⭐⭐⭐⭐⭐ 最快
动态范围 ⭐⭐ 数值范围小 ⭐⭐⭐ 数值范围大

工程实践中的经典搭配:

  • 前向传播 → E4M3(更擅长保留细节)
  • 反向梯度 → E5M2(更擅长避免溢出)

一句话总结: 在显存、精度、速度之间做了最激进的平衡。


五种张量类型对比

张量类型 总比特数 相对 FP32 显存占用 核心优势 核心劣势 核心使用场景
FP32 32bit 存储、32bit 计算 100% 精度高、范围大、最稳定 显存占用高、速度慢 训练梯度更新、优化器状态,高精度科学计算
TF32 计算 19bit / 存储 32bit 100% 基本不用改代码就能加速 FP32 训练 不省显存,本质是计算加速模式 FP32 工作流训练加速
BF16 16bit 存储/计算,指数位与 FP32 相同 50% 范围大、训练稳定、显存减半 尾数精度低于 FP16/FP32 大模型训练主流格式
FP16 16bit 存储/计算 50% 显存减半、吞吐高、生态成熟 指数范围小,容易溢出 旧方案混合精度训练、部分推理
FP8 8bit 存储/计算,常见 E4M3 / E5M2 25% 更省显存、吞吐更高 精度和范围更紧张,对硬件要求高 高端大模型训练、推理加速

取舍逻辑总结

五种张量类型真正解决的只有一个问题:你愿意拿多少精度换多少显存和速度?

类型 取舍逻辑 适用场景
FP32 精度至上,不计代价 梯度更新、优化器状态
TF32 不改代码的前提下提速 FP32 工作流加速
BF16 牺牲可忽略的精度,换取显存减半 大模型训练主流方案
FP16 简单粗暴砍体积 推理、早期混合精度
FP8 激进压缩,精度/范围需取舍 部署量化、极致显存优化

混合精度训练

在训练大模型时,通常会混用多种精度

1
2
3
前向传播:BF16 / FP8        ← 省显存
反向传播:FP32 / FP16 ← 保证梯度精度
权重存储:BF16 ← 节省存储

这就是为什么叫”混合精度”——有的环节用高精度算,有的用低精度算。


实用建议

部署阶段

  • 显存充足 → FP16 或 BF16 推理即可
  • 显存紧张 → 寻找 FP8 量化版本(显存减半)
  • 消费级显卡 → 优先 BF16(兼容性好,RTX 30/40 系均支持)或 FP8(需 Hopper 架构)

训练阶段

  • 标准方案 → BF16 混合精度训练(当前主流)
  • Ampere+ GPU(RTX 30 系 / A100)→ 开启 TF32 加速(无需改代码,框架默认开启)
  • Hopper+ GPU(H100)→ 考虑 FP8 训练(Transformer Engine,最新方案)

参考

  • IEEE 754 浮点数标准
  • NVIDIA Tensor Core 文档
  • Google Brain: BFloat16 论文
  • NVIDIA FP8 格式规范(OCP Microscaling Format)