https://blog.csdn.net/ccbrid/article/details/103207676/

此博客介绍了apex的安装过程,且有如下问题:

发现loss整体变大,而且很不稳定。效果变差。而且这个错误提醒是什么意思呢?

Gradient overflow.  Skipping step, loss scaler 0 reducing loss scale to 0.00048828125

意思是:梯度溢出,issue上也有很多人提出了这个问题,貌似作者一直在收集这个问题出现的样例,尚未解决。

 

 

1 遇到梯度溢出问题

Gradient overflow.  Skipping step, loss scaler 0 reducing loss scale to 131072.0

 

2 后来:


我在另外一顶会文章上面放置了我新设计的model发现
1 结果loss=nan
2 而一直显示梯度爆炸
3 Nan or Inf found in input tensor

 

3 解决办法:
感谢此博主
https://blog.csdn.net/gzq0723/article/details/105885088

 

我看到一篇博文讲apex的amp的使用方法

解决办法:
经过验证可以通过以下几种方法,来防止出现梯度溢出的问题:

1、O2换成O1,再不行换成O0
2、把batchsize从32调整为16会显著解决这个问题,另外在换成O0的时候会出现内存不足的情况,减小batchsize也是有帮助的
3、减少学习率也是一种方法(没有亲自验证)
4、增加Relu会有效保存梯度,防止梯度消失(亲测有效,如下说明)

经过验证我发现SCRN网络在F3框架下用apex加速,
在BasicConv2d里面加入Relu, O1+32batchsize是可以运行的,偶尔报告梯度溢出,
但是去掉Relu, O1+32batchsize就会频繁报告梯度溢出

在F3框架下运行SCRN模型,BasicConv2d模块没有relu:
会有如下三种情况:
1 选择O2的时候会一直溢出,
2 选择O1之后好很多,但是还是有梯度消失的现象,(我会尝试这种训练后,告诉大家这个方法的训练结果能不能用)

3 选择O0的话我的2080Ti会内存不够,把batchsize从32变成16之后,不再出现梯度溢出但是O0貌似没有加速作用,温度很高,但是好在可以正常训练了

 

 

下面是详细介绍

使用此方法来解决一些舍入误差:混合精度训练+动态损失放大

1 混合精度训练(Mixed Precision)

混合精度训练的精髓在于“在内存中用FP16做储存和乘法从而加速计算,用FP32做累加避免舍入误差”。混合精度训练的策略有效地缓解了舍入误差的问题。

1 损失放大(Loss Scaling)

即使用了混合精度训练,还是会存在无法收敛的情况,原因是激活梯度的值太小,造成了下溢出(Underflow)。损失放大的思路是:

    反向传播前,将损失变化(dLoss)手动增大$2^k$倍,因此反向传播时得到的中间变量(激活函数梯度)则不会溢出;

    反向传播后,将权重梯度缩$2^k$倍,恢复正常值。很麻烦,但是新的API一句话解决

 Apex的新API:Automatic Mixed Precision (AMP)

曾经的Apex混合精度训练的api仍然需要手动half模型已经输入的数据,比较麻烦,现在新的api只需要三行代码即可无痛使用:

下面是opt_level四个选项:(这里是欧不是0)
    opt_level

    其中只有一个opt_level需要用户自行配置:

    O0:纯FP32训练,可以作为accuracy的baseline;

    O1:混合精度训练(推荐使用),根据黑白名单自动决定使用FP16(GEMM, 卷积)还是FP32(Softmax)进行计算。

    O2:“几乎FP16”混合精度训练,不存在黑白名单,除了Batch norm,几乎都是用FP16计算。之前是这个

    O3:纯FP16训练,很不稳定,但是可以作为speed的baseline;

选择O1之后好很多,但是还是有梯度消失的现象,
选择O2的时候会一直溢出,
选择O0的话我的2080Ti会内存不够,把batchsize从32变成16之后,不再出现梯度溢出但是O0貌似没有加速作用

下面是对loss scale不断减小的解释。因为梯度溢出,跳过了参数更小,缩小了放大倍数,来使其不溢出

动态损失放大(Dynamic Loss Scaling)

AMP 默认使用动态损失放大,为了充分利用 FP16 的范围,缓解舍入误差,尽量使用最高的放大倍数()
如果产生了上溢出(Overflow),则跳过参数更新,缩小放大倍数使其不溢出,
在一定步数后(比如 2000 步)会再尝试使用大的 scale 来充分利用 FP16 的范围:

一些注意事项:

▲AMP中动态损失放大的策略

干货:踩过的那些坑

这一部分是整篇文章最干货的部分,是笔者在最近在 apex 使用中的踩过的所有的坑,由于 apex 报错并不明显,常常 debug 得让人很沮丧,但只要注意到以下的点,95% 的情况都可以畅通无阻了:

1. 判断你的 GPU 是否支持 FP16:支持的有拥有 Tensor Core 的 GPU(2080Ti、Titan、Tesla 等),不支持的(Pascal 系列)就不建议折腾了;

2. 常数的范围:为了保证计算不溢出,首先要保证人为设定的常数(包括调用的源码中的)不溢出,如各种 epsilon,INF 等;

3. Dimension 最好是 8 的倍数:NVIDIA 官方的文档的 2.2 条[2]表示,维度都是 8 的倍数的时候,性能最好;

4. 涉及到 sum 的操作要小心,很容易溢出,类似 Softmax 的操作建议用官方 API,并定义成 layer 写在模型初始化里;

5. 模型书写要规范:自定义的 Layer 写在模型初始化函数里,graph 计算写在 forward 里;

6. 某些不常用的函数,在使用前需要注册:amp.register_float_function(torch, 'sigmoid') ;严重怀疑是不是这个出问题的,放置了曦貌似没用

7. 某些函数(如 einsum)暂不支持 FP16 加速,建议不要用的太 heavy,XLNet 的实现改 FP16[4]困扰了我很久;

8. 需要操作模型参数的模块(类似 EMA),要使用 AMP 封装后的 model;

9. 需要操作梯度的模块必须在 optimizer 的 step 里,不然 AMP 不能判断 grad 是否为 Nan;

 

3 APEX加速使用说明

在F3框架折腾scrn之后我,突然发现可以在SCRN基础上直接加APEX加速,这不更直接,不用调整训练参数了

加入apex的方法也很简单:

只需三步:

1 from apex import amp
2 model, optimizer = amp.initialize(model, optimizer, opt_level="O1") # 这里是“欧一”,不是“零一”
3 with amp.scale_loss(loss, optimizer) as scaled_loss:
    scaled_loss.backward()

第一步 就是从apex导入amp这个包

第二步 用amp初始化模型model和优化器optimizer, 只要不报"梯度溢出"可以尝试O2或者O3

第三部 我把“optimizer.zero_grad()”从上面移动下来,放在一起 (不动也可以)
然后加入这两句话即可:
with amp.scale_loss(loss, optimizer) as scale_loss:
    scale_loss.backward()

  • 第四步 要注意把loss.backward()注释掉,这样才能把损失loss优化器optimizer送进amp进行优化,

不注释的话会报错如下:
RuntimeError: Trying to backward through the graph a second time, but the buffers have already been freed. Specify retain_graph=True when calling backward the first time.

 

 

 

4 apex 的github网站,以及一些网友的解释
https://github.com/NVIDIA/apex

1 前10行出现是正常的,在调整loss的scale

2 偶尔出现也是正常的,

3 但是频繁出现是有问题的,请按照我3解决办法中的方法进行操作即可

Getting gradient overflow warnings for the first few (5-10) iterations is normal, as the value used to scale the gradients calibrates. It's also normal to see the warning intermittently (every few hundred or thousand steps afterwards) as the scale occasionally adjusts.
A death spiral (dozens of iterations where the scale value decreases to very small values, eventually resulting by division by zero) is not normal, and indicates divergence. Please reopen if that's what you observe.
为前几次迭代(5-10次)获得梯度溢出警告是正常的,因为用于缩放梯度的值经过了校准。当量表偶尔调整时,间歇性地(每隔几百步或几千步)看到警告也是正常的。
一个死亡螺旋(数十次迭代,其中尺度值减少到非常小的值,最终由除以零得到)是不正常的,并且表明了差异。如果这是你所观察到的,请重新打开。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐