一、混合精度训练(AMP):FP16/BF16原理、AMP的3种策略(O0/O1/O2/O3)、Loss Scaling技巧

各位同学好,我是你们的GPU工程师老K。今天咱们聊聊混合精度训练,也就是AMP。

说实话,我刚入行那会儿,训练大模型全靠FP32硬扛。显存不够?那就砍batch size呗。后来发现,这简直是暴殄天物。你想想看,一张A100 80G,跑个175B的模型,FP32连参数都塞不下。但换成FP16/BF16混合精度,嘿,不仅能塞下,还能跑得更快。

为什么会这样?因为FP16和BF16的每个数只占2个字节,而FP32占4个字节。显存占用直接砍半。再加上Tensor Core对低精度计算有硬件加速,训练速度能提升2-4倍。我在项目中遇到过,一个原本要跑3天的BERT-large,切到AMP后,1天半就跑完了。嗯,真香。

1.1 FP16和BF16的原理

先说说FP16。它也叫半精度浮点数,用1位符号位、5位指数位、10位尾数位。表示范围大约是±65504,精度大概3位有效数字。

BF16呢?它是Google搞出来的,1位符号位、8位指数位、7位尾数位。表示范围和FP32一样大(±3.4×10³⁸),但精度只有2位有效数字。

我个人的习惯是:如果模型对精度不敏感,比如CV领域的ResNet、ViT,用FP16就够了。但如果是NLP模型,尤其是训练过程中梯度分布很广的那种,我建议用BF16。为什么?因为BF16的动态范围大,不容易溢出。

类型 符号位 指数位 尾数位 表示范围 精度
FP32 1 8 23 ±3.4×10³⁸ ~7位
FP16 1 5 10 ±65504 ~3位
BF16 1 8 7 ±3.4×10³⁸ ~2位

核心要点:FP16精度高但范围窄,BF16范围宽但精度低。选哪个?看你的模型对范围和精度的需求。

1.2 AMP的4种策略:O0/O1/O2/O3

AMP(Automatic Mixed Precision)是NVIDIA官方推出的混合精度训练工具。它提供了4种策略,从O0到O3,精度逐渐降低,速度逐渐提升。

我刚开始用AMP时,直接上了O3,结果模型不收敛。后来才明白,这玩意儿得根据模型特性来选。

O0:纯FP32

说白了就是不用混合精度。所有计算和存储都用FP32。适合对精度要求极高的场景,或者模型本身很小,显存够用。

O1:保守混合精度

这是我最常用的策略。它会自动识别哪些操作可以用FP16加速,哪些必须保留FP32。比如卷积、矩阵乘法这些计算密集型操作,自动切到FP16。而BatchNorm、Softmax这些对精度敏感的操作,保留FP32。

我在项目中遇到过,O1策略下,模型精度和FP32几乎一样,但速度提升了1.5倍左右。

O2:激进混合精度

除了BatchNorm和少数特殊操作,其他全部用FP16。显存占用更低,速度更快。但风险也大,容易出现梯度下溢或上溢。

警告:O2策略下,一定要配合Loss Scaling使用。否则模型很容易炸掉。

O3:纯FP16

所有操作都用FP16。速度最快,但精度损失也最大。我建议只在推理阶段使用,或者对精度要求不高的场景。

策略 计算精度 存储精度 速度提升 风险等级
O0 FP32 FP32 1x 无
O1 混合 混合 1.5-2x 低
O2 FP16为主 FP16为主 2-3x 中
O3 FP16 FP16 3-4x 高

1.3 Loss Scaling技巧

Loss Scaling,说白了就是给损失函数乘一个大数,防止梯度下溢。

为什么会下溢?FP16能表示的最小正数是2⁻²⁴ ≈ 5.96×10⁻⁸。如果梯度比这个还小,就会变成0。梯度为0,参数就不更新了,模型就废了。

我个人的习惯是:初始缩放因子设为128.0,每N步检查一次梯度。如果梯度没有溢出,就把缩放因子翻倍。如果溢出了,就减半。这叫动态Loss Scaling。

# PyTorch AMP 示例
import torch
from torch.cuda.amp import autocast, GradScaler

scaler = GradScaler(init_scale=128.0)

for data, target in dataloader:
    optimizer.zero_grad()
    
    with autocast():
        output = model(data)
        loss = loss_fn(output, target)
    
    # Loss Scaling
    scaler.scale(loss).backward()
    scaler.step(optimizer)
    scaler.update()

小技巧:如果你发现模型训练过程中loss突然变成NaN,大概率是梯度溢出了。这时候可以尝试增大缩放因子,或者切到O1策略。

我曾经在一个NLP项目里,用O2策略训练GPT-2,结果跑了2万步后loss突然炸了。排查了半天,发现是某个层的梯度特别大,超过了FP16的表示范围。后来加了Loss Scaling,并把缩放因子从128调到512,问题就解决了。

1.4 知识体系总览

下面这张图,是我自己画的AMP知识体系。你看一眼,就能明白整个流程。

混合精度训练(AMP)知识体系 FP16 / BF16 原理 AMP 4种策略 Loss Scaling FP16 1+5+10位,范围±65504 精度~3位有效数字 适合CV模型 BF16 1+8+7位,范围±3.4×10³⁸ 精度~2位有效数字 适合NLP模型 O0 纯FP32 精度最高 O1 保守混合 推荐使用 O2 激进混合 需Loss Scaling O3 纯FP16 速度最快 Loss Scaling 初始缩放因子128 动态调整 核心目标:在保证精度的前提下,最大化训练速度和显存利用率

嗯,这张图把AMP的核心要素都串起来了。从数据类型选择,到策略配置,再到Loss Scaling,每一步都有讲究。

总结一下:

  • FP16精度高但范围窄,BF16范围宽但精度低
  • O1策略是安全牌,O2策略需要配合Loss Scaling
  • Loss Scaling的初始值设为128,动态调整
  • 如果模型不收敛,先检查是不是梯度溢出了

想要更多资料,联系可可