AMP自动混合精度计算:单精度 float和半精度 float16 混合
-
为什么需要半精度:
float16和float相比恰里,总结下来就是两个原因:内存占用更少,计算更快。
内存占用更少:这个是显然可见的,通用的模型 fp16 占用的内存只需原来的一半。memory-bandwidth 减半所带来的好处:
模型占用的内存更小,训练的时候可以用更大的batchsize。
模型训练时,通信量(特别是多卡,或者多机多卡)大幅减少,大幅减少等待时间,加快数据的流通。
-
计算更快:
目前的不少GPU都有针对 fp16 的计算进行优化。论文指出:在近期的GPU中,半精度的计算吞吐量可以是单精度的 2-8 倍;
-
如何使用
autocast 【一种上下文的模块,该模块框住的内容将自动采用混合精度】+ GradScaler【梯度scaler模块,会忽略nan值的更新】。
autocast上下文应该只包含网络的前向过程(包括loss的计算),而不要包含反向传播,因为BP的op会使用和前向op相同的类型。
from torch.cuda.amp import autocast as autocast # 创建model,默认是torch.FloatTensor model = Net().cuda() optimizer = optim.SGD(model.parameters(), ...) # 在训练最开始之前实例化一个GradScaler对象 scaler = GradScaler() for epoch in epochs: for input, target in data: optimizer.zero_grad() # 前向过程(model + loss)开启 autocast with autocast(): output = model(input) loss = loss_fn(output, target) # Scales loss. 为了梯度放大. scaler.scale(loss).backward() # scaler.step() 首先把梯度的值unscale回来. # 如果梯度的值不是 infs 或者 NaNs, 那么调用optimizer.step()来更新权重, # 否则,忽略step调用,从而保证权重不更新(不被破坏) scaler.step(optimizer) # 准备着,看是否要增大scaler scaler.update()
半精度带来的数据上下溢出问题
Loss在上下中,采用的仍然是半精度计算,在做Cross-Entropy-loss,会存在sum的操作,在参数设置为mean、sum都会进行sum的操作,容易出现nan值,标志是参数为none即对逐像素计算的loss不做其他操作,输出为一个矩阵,当一个矩阵的mean是正常的sum为inf可以直接判断sum操作已经溢出半精度能表示的范围了。
补充:
判断一个tensor A中是否存在nan,torch.isnan(A).any(),torch.isnan()
输出为一个与A同shape的bool矩阵,any为只要由一个true输出为true
判断一个tensor A中是否存在 inf,torch.isinf(A).any()
判断一个tensor A中元素是否满足条件b,torch.where(b, x, y)
→ Tensor,根据条件,返回从x,y中选择元素所组成的张量。如果满足条件,则返回x中元素。若不满足,返回y中元素。可以写成torch.where(x=0,True,False)