| 精度 | 位数 | 指数位数 | 尾数位数 | 范围 | 特点 |
|---|---|---|---|---|---|
| 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一样,不容易溢出,但精度略低 |
关键区别:
自动混合精度训练就是在训练过程中,根据操作类型,自动选择合适的精度:
torch.cuda.amp。优点:
问题:
混合精度训练中,通常在FP32空间保存一份权重备份:
2^-14 ≈ 6.1e-5,很多更新参数的小梯度会小于这个值,直接变成0,梯度信息就丢失了(Underflow)。解决方法:损失缩放(Loss Scaling)。
原理很简单:
scale(比如1024)scale倍,这样小梯度也能被FP16表示,不会变成0scale缩放到原来的大小1# 伪代码
2loss = criterion(output, target)
3loss = loss * scale # 放大loss
4scaled_loss.backward() # 梯度也被放大
5scaler.unscale_(optimizer) # 梯度除以scale,恢复原来大小
6optimizer.step() # 更新参数固定scale不一定合适:
AMP采用动态调整策略:
1from torch.cuda.amp import autocast, GradScaler
2
3# 初始化梯度缩放器
4scaler = GradScaler()
5
6for epoch in epochs:
7 for input, target in data:
8 optimizer.zero_grad()
9
10 # 前向过程开启autocast
11 with autocast():
12 output = model(input)
13 loss = criterion(output, target)
14
15 # 反向传播,scaler会放大loss
16 scaler.scale(loss).backward()
17
18 # 梯度unscale并更新,如果梯度没有Inf/NaN才更新
19 scaler.step(optimizer)
20
21 # 更新scaler的scale
22 scaler.update()1from torch.cuda.amp import autocast, GradScaler
2
3# ... 分布式初始化代码省略 ...
4
5if args.use_amp:
6 scaler = GradScaler()
7
8for epoch in range(1, args.epochs + 1):
9 train_sampler.set_epoch(epoch)
10 for step, batch_data in enumerate(train_loader):
11 self.model.train()
12
13 if args.use_amp:
14 with autocast():
15 logits, label = self.on_step(batch_data)
16 loss = self.criterion(logits, label)
17 scaler.scale(loss).backward()
18 scaler.step(self.optimizer)
19 scaler.update()
20 else:
21 logits, label = self.on_step(batch_data)
22 loss = self.criterion(logits, label)
23 loss.backward()
24 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。用户不需要手动转换类型,框架自动处理,所以叫"自动"混合精度。