在训练 7B、70B 甚至更大的模型时,如何让显存利用率翻倍、速度提升数倍?答案就在 FP16 与 BF16 的取舍之间。
一、 核心概念:数字的“重量”
计算机存储数字需要占据空间(位/bit)。精度越高,占用显存越多,计算越慢。
| 格式 | 全称 | 占用空间 | 特点 |
|---|---|---|---|
| FP32 | 单精度浮点数 | 32-bit | 沉重且慢。传统训练标准,极度精确但显存开销巨大。 |
| FP16 | 半精度浮点数 | 16-bit | 轻便但娇气。显存减半,速度极快,但容易产生数值溢出。 |
| BF16 | 大脑浮点数 | 16-bit | 现代标配。Google 专门为深度学习设计,牺牲精度换取极其强大的稳定性。 |
二、 FP16:速度虽快,但有“精度陷阱”
FP16 为了追求精确,牺牲了指数范围。这在大模型训练中会带来两大问题:
- 下溢出 (Underflow):当梯度太小时(如 ),FP16 无法表示,直接将其抹为 0。模型会因此停止学习。
- 上溢出 (Overflow):当激活值太大时,FP16 会直接变成
NaN(无穷大),导致训练崩盘。
🛡️ 对策:Loss Scaling (损失缩放)
为了解决 FP16 的尴尬,我们需要一套动态补偿机制:
- 逻辑:在算出 Loss 后,手动将其乘以一个很大的系数(如 65536),把微小的梯度“抬”到 FP16 能看见的范围。
- 清算:在最后更新参数前,再把梯度缩小回去。
- 现状:这种方法配置复杂,容易在缩放过程中导致数值波动。
三、 BF16:大模型时代的“工程奇迹”
BF16 (Brain Float 16) 是为了彻底解决 FP16 的痛点而生的。
- 设计精妙:它将小数点后的位数砍掉,却保留了和 FP32 几乎完全一致的指数范围。
- 物理优势:
- 不惧溢出:它能容纳和 FP32 一样大的数字,不再需要复杂的 Loss Scaling。
- 极度稳定:在大语言模型(LLM)这种梯度波动极大的任务中,它是目前业界的最优解。
- 硬件要求:需要 NVIDIA Ampere 架构(如 RTX 30/40系列, A100, H100)及以上显卡才支持。
[Image comparing BF16, FP16 and FP32 formats showing exponent and mantissa bits]
四、 混合 (Mixed) 的真相:影子参数 (Shadow Weights)
为什么叫“混合”?因为我们并不会完全放弃 32 位。
运作机制:
- 前向/反向传播:全部使用 16 位(FP16/BF16)。这确保了计算速度飞快,显存占用极低。
- 权重更新:优化器(AdamW)在后台偷偷维护一套 32 位 (FP32) 的主权重(也叫影子参数)。
- 逻辑:因为 16 位的步长太短,容易产生累积舍入误差。我们在 32 位下精准计算更新,再把结果同步回 16 位参数中。
五、 实战建议:我该选哪个?
在微调配置文件(如 TrainingArguments)中:
-
如果有 A100/3090/4090/H100:
- 核心选项:
bf16=True - 理由:最稳、最快,完全不用操心数值溢出。
- 核心选项:
-
如果是旧显卡 (V100/20系列/P系列):
- 核心选项:
fp16=True - 理由:不支持 BF16。此时务必注意观察 Loss 曲线,若出现
Loss=NaN,通常是梯度爆炸或缩放因子失效。
- 核心选项:
-
显存极度充裕:
- 核心选项:
fp32=True - 理由:虽然慢且重,但它是精度的“物理上限”。
- 核心选项:
💡 核心总结
混合精度训练是利用 16 位的速度 换取效率,利用 32 位的影子参数 保证质量。在现代大模型微调中,BF16 混合精度 是通向“高效且稳健训练”的入场券。