| 精度 | 位数 | 指数位数 | 尾数位数 | 范围 | 特点 |
|---|---|---|---|---|---|
| FP32 | 32 | 8 | 23 | ±1.18×10⁻³⁸ ~ ±3.4×10³⁸ | 标准单精度,训练默认使用,精度足够但显存大计算慢 |
| FP16 (半精度) | 16 | 5 | 10 | ±6.1×10⁻⁵ ~ ±6.5×10⁴ | 半精度,显存减半,计算更快,但容易溢出 |
| BF16 (脑浮点) | 16 | 8 | 7 | ±1.2×10⁻³⁸ ~ ±3.4×10³⁸ | 指数范围和FP32一样,不容易溢出,但精度略低 |
关键区别:
自动混合精度训练就是在训练过程中,根据操作类型,自动选择合适的精度:
这就是"混合"和"自动"的含义。PyTorch从1.6开始原生支持torch.cuda.amp。
优点:
问题:
混合精度训练中,通常在FP32空间保存一份权重备份:
FP16的最小正数值是 2^-14 ≈ 6.1e-5,很多更新参数的小梯度会小于这个值,直接变成0,梯度信息就丢失了(Underflow)。
解决方法:损失缩放(Loss Scaling)。
原理很简单:
scale(比如1024)scale倍,这样小梯度也能被FP16表示,不会变成0scale缩放到原来的大小# 伪代码
loss = criterion(output, target)
loss = loss * scale # 放大loss
scaled_loss.backward() # 梯度也被放大
scaler.unscale_(optimizer) # 梯度除以scale,恢复原来大小
optimizer.step() # 更新参数固定scale不一定合适:
AMP采用动态调整策略:
from torch.cuda.amp import autocast, GradScaler
# 初始化梯度缩放器
scaler = GradScaler()
for epoch in epochs:
for input, target in data:
optimizer.zero_grad()
# 前向过程开启autocast
with autocast():
output = model(input)
loss = criterion(output, target)
# 反向传播,scaler会放大loss
scaler.scale(loss).backward()
# 梯度unscale并更新,如果梯度没有Inf/NaN才更新
scaler.step(optimizer)
# 更新scaler的scale
scaler.update()from torch.cuda.amp import autocast, GradScaler
# ... 分布式初始化代码省略 ...
if args.use_amp:
scaler = GradScaler()
for epoch in range(1, args.epochs + 1):
train_sampler.set_epoch(epoch)
for step, batch_data in enumerate(train_loader):
self.model.train()
if args.use_amp:
with autocast():
logits, label = self.on_step(batch_data)
loss = self.criterion(logits, label)
scaler.scale(loss).backward()
scaler.step(self.optimizer)
scaler.update()
else:
logits, label = self.on_step(batch_data)
loss = self.criterion(logits, label)
loss.backward()
self.optimizer.step()RuntimeError: expected scalar type float but found c10::Half,需要手动在那个tensor上调.float()A: 不一定。只有模型较大,且GPU支持Tensor Core(Volta架构以上,即V100/2080Ti及之后),才能获得明显加速。小模型加速不明显,甚至可能因为类型转换开销变慢。
A:
A:
A: 推理也可以用,能减少显存占用,加快推理速度。只要训练时用了混合精度,推理直接用就可以,不需要特殊处理。
A: 大量实践表明,只要训练稳定,最终精度和全FP32训练几乎没有差别。梯度缩放和权重备份技术已经很好地解决了精度问题。
A: 混合精度训练就是同时使用FP16和FP32两种精度:大多数计算用FP16,参数更新用FP32。FP16每个元素只占2字节,FP32占4字节,所以显存占用大约减少一半,能训练更大的batch或者更大的模型。同时,NVIDIA GPU的Tensor Core对FP16有专门加速,训练速度也会提升。
A: FP16的表示范围比FP32小很多,太大的梯度会超过FP16表示范围变成Inf(溢出),太小的梯度会变成0(下溢)。解决方法是动态损失缩放:放大loss使得小梯度也能被表示,更新前再缩放回去,并且动态调整缩放因子。
A: BF16和FP16都是16位,但BF16保留了和FP32一样的8位指数,指数范围和FP32完全相同,所以不容易出现溢出/下溢,训练更加稳定。现代GPU(Ampere及以后)对BF16有原生硬件支持,速度和FP16一样快。
A:
A: autocast是上下文管理器,它会根据算子类型自动选择最合适的精度:对于矩阵乘、卷积这些适合低精度的算子,自动转为FP16/BF16;对于一些需要高精度的算子,保持FP32。用户不需要手动转换类型,框架自动处理,所以叫"自动"混合精度。