混合精度训练
MIXED PRECISION TRAINING
https://arxiv.org/pdf/1710.03740.pdf
论文概述
nvidia的Pascal和Volta系列显卡除了支持标准的单精度计算外,也支持了低精度的计算,比如最新的Tesla V100硬件支持了FP16的计算加速,P4和P40支持INT8的计算加速,而且低精度计算的峰值要远高于单精浮点的计算峰值。
为了加速训练过程以及减少显存开销,baidu Research和nvidia在这篇论文中合作提出了一种FP16和FP32混合精度训练的方法,并且在CNN分类和检测、语音识别和语言模型任务上进行了验证,实验过程中使用的GPU就是Tesla V100。
训练过程中每层的权重都存成FP32格式(Mater-Weights),每次训练时都会将FP32的权重降精度至FP16( a master copy),前向输出和后向梯度都使用FP16进行计算,更新时将FP16的梯度累加到FP32的Mater-Weight上。
混合精度的必要性
由于FP16所能表示的subnormal最小正数是\(2^{−24}\) ≈ \(5.96 × 10^{−8}\)(Half-precision floating-point format),也就是说在区间(\(-2^{-24}, 2^{-24}\))的数(或者说指数位小于-24的数)使用FP16表示时都会变成0。在一个普通话识别的模型训练中,有将近5%的权重梯度的指数位小于-24,如果更新时也用FP16计算,那么这些数在乘以学习率后都将变成0,从而对最终模型效果产生负面影响,使用混合精度训练的方式可以避免这种问题。
Loss scaling
混合精度训练可以解决权重更新量很小的问题,但无法解决梯度本身很小的问题。在一些网络中(比如SSD),梯度大部分都在FP16的表示范围之外,因此需要将梯度平移到FP16的表示范围内 。
平移实际上就是对梯度值乘以一个系数(等于\(2^{n}\),\(n\)为平移的位数),但另一种简单高效的方法是直接在前向时就将loss乘以scale,这样在后向传导时所有的梯度都会被乘以相同的scale。权重更新时需要将移位后的梯度除以scale后,再更新到权重上。
论文中提到他们在实验过程中使用的scale是8~32K,最终取得了与FP32一致的收敛结果。对于scale的选择,论文没有统一的方法,只是提到scale并没有下界,只要选择的scale不会在后向计算时导致溢出就行。
实验结果
图像分类
物体检测
语音识别
机器翻译
语言模型
MIXED PRECISION TRAINING OF CONVOLUTIONAL NEURAL NETWORKS USING INTEGER OPERATIONS
https://openreview.net/forum?id=H135uzZ0-
论文概述
半精度(16bit)分为半精度浮点(FP16)和半精度定点(INT16),FP16和INT16提供不同的精度和表示范围。INT16相比FP16的动态范围低,但精度更高,因此INT16相比FP16会带来更低的精度误差。
现在深度学习领域公认的数据类型是单精度浮点(float),半精和单精除了在直观感觉上的数据类型不同之外,在计算(algorithmic)和语义(semantic)上也会有很多的不同,比如说FP16的乘加操作得到的结果是FP32。因此在讨论半精度训练时,对于整个tensor的表达、乘加操作、低精度转换、缩放和规整方法和溢出处理都是需要同时考虑的。
intel的这篇论文主要受到之前flexpoint和混合精度训练的启发,从而提出了一种共享指数位的动态定点表达(dynamic fixed point representation)方法,使用INT16和float混合精度训练,在完全不进行任何调参的情况下,在多个CNN的模型上取得了当前所有低精度训练方法中最好的效果。
这篇论文主要涉及的技术点有:
- DFP:INT16的Tensor共享指数位,扩充INT16的动态表示范围。
- instruction:两个INT16进行乘法,结果存为INT32的指令。
- down-convert:基于最大值的低精度转换策略,使用nearest、stochastic和biased rounding三种不同的rounding方法。
- overflow management:将局部的INT32结果累加到FP32,防止累加时溢出。
DFP(Dynamic Fixed Point)
一个DFP tensor由一个定点的tensor和该tensor共享的指数组成,更通用的表示形式为DFP-P = \(<I, E_{s}>\),P表示定点tensor \(I\)的位宽,\(E_{s}\)表示共享指数位。标准单精使用的是8bit的指数位,在该论文中使用的DFP-16共享指数位也是8bit。
DFP-16和fp32的数据转换
共享指数位需要根据tensor中的绝对值最大的数和定点化的位宽来确定,计算公式如下:
\[E_{s} = E_{fmax} - (P - 2)\]
\(E_{s}\)表示DFP-P的共享指数,\(E_{fmax}\)表示原始fp32 tensor中绝对值最大的数对应的指数\(E_{fmax} = E(max_{\forall f \in F} |f|)\)
因此fp32的tensor与DFP的tensor有以下关系:
\[\forall i_{n} \in I, \ \ \ f_{n} = i_{n} \times 2^{E_{s}}, \ \ \ where f_{n} \in F\]
也就是说\(i_{n} = rounding(\frac{f_{n}}{2^{E_{s}}})\),这本质上与loss scaling思想是一样的,用平移的思想来解决动态范围不够的问题。
DFP-16 tensor的乘加运算规则
1、两个DFP-16 tensor相乘,结果存为DFP-32。
\[i_{ab} = i_{a} \times i_{b} , \ \ \ E_{s}^{ab} = E_{s}^{a} + E_{s}^{b}\]
2、两个DFP-16 tensor相加,结果存为DFP-32。
\[i_{ab} = \left\{\begin{aligned} i_{a} + (i_{b} >> (E_{s}^{a} - E_{s}^{b})) \ \ \ when E_{s}^{a} > E_{s}^{b} \\ i_{b}+(i_{a} >> (E_{s}^{b}-E_{s}^{a})) \ \ \ when E_{s}^{a} < E_{s}^{b} \end{aligned}\right.\]
\[E_{s}^{a+b} = max(E_{s}^{a}, E_{s}^{b})\]
3、两个DFP-32 tensor相加,结果保存为fp32。
DFP-32和DFP-16的数据转换
\[R_{s} = P - LZC(max_{\forall i_{ab} \in I^{32}}|i_{ab}|)\]
\[i_{ab}^{d} = i_{ab} >> R_{s} , \ \ \ E_{s}^{ab} += R_{s}\]
DFP混合精度训练
指令实现
intel的VNNI指令集中有一条DFP-16乘加的指令QVNNI16,这条指令的第一个操作数是DFP-16内存指针,第二个操作数是4个512位的向量寄存器(每个寄存器可以存储32个DFP-16),结果是一个512位的向量寄存器(该寄存器能存储16个DFP-32)。
上面的QVNNI16指令集实际上对mem输入做了两路并行展开,vinp2中一个寄存器支持同时对输入feature map的两个channel进行计算。在论文中,卷积层输入的格式为(N,C/16,H,W,16),权重的格式为(C/16,K/16,KH,KW,8c,16k,2c),C表示输入feature map的通道数,K表示输出通道数,KH和KW分别表示卷积核的height和width。
卷积计算过程伪代码:
每次对输入的ICBLK个通道进行计算,ICBLK个通道又会分成(ICBLK/16)组,每组计算16个通道,由于QVNNI指令每次只能对输入的8个通道进行计算,因此每组调用2次QVNNI16指令,计算结果vout会转换成FP32后与output累加。
实验结果
baseline和DFP-16的实验均在intel最新的Knights-Mill CPU上进行,DFP-16相比FP32训练加速1.8X。