-
计算机中的浮点数表示,按照IEEE754可以分为三种,分别是半精度浮点数、单精度浮点数和双精度浮点数。三种格式的浮点数因占用的存储位数不同,能够表示的数据精度也不同。
-
Signed bit用于控制浮点数的正负,0表示正数,1表示负数;
-
Exponent部分用于控制浮点数的大小,以2为底进行指数运算;
-
Significand部分用于控制浮点数的精度,存储浮点数的有效数字。
-
默认深度学习模型训练过程中都是使用fp32。
-
使用fp16能带来什么好处:
-
减少显存占用:现在模型越来越大,当你使用Bert这一类的预训练模型时,往往显存就被模型及模型计算占去大半,当想要使用更大的Batch Size的时候会显得捉襟见肘。由于FP16的内存占用只有FP32的一半,自然地就可以帮助训练过程节省一半的显存空间。
-
加快训练和推断的计算:与普通的空间时间Trade-off的加速方法不同,FP16除了能节约内存,还能同时节省模型的训练时间。在大部分的测试中,基于FP16的加速方法能够给模型训练带来多一倍的加速体验。
-
张量核心的普及:硬件的发展同样也推动着模型计算的加速,随着Nvidia张量核心(Tensor Core)的普及,16bit计算也一步步走向成熟,低精度计算也是未来深度学习的一个重要趋势。
-
x = F P 16 ( ( − 1 ) S i g n e d ∗ 2 E x p o n e n t − 15 ∗ ( 1 + S i g n i F i c a n d 2 10 ) ) x=FP16((-1)^{Signed}*2^{Exponent-15}*(1+\frac{SigniFicand}{2^{10}})) x=FP16((−1)Signed∗2Exponent−15∗(1+210SigniFicand))
-
x = F P 32 ( ( − 1 ) S ∗ 2 E − 127 ∗ 1. S F ) x=FP32((-1)^S*2^{E-127}*1.SF) x=FP32((−1)S∗2E−127∗1.SF)
-
x = F P 64 ( ( − 1 ) S ∗ 2 E − 1023 ∗ 1. S F ) x=FP64((-1)^S*2^{E-1023}*1.SF) x=FP64((−1)S∗2E−1023∗1.SF)
-
FP16最大值为
0 11110 1111111111
,其计算方式为- ( − 1 ) 0 ∗ 2 30 − 15 ∗ 1.1111111111 = 1 ∗ 2 15 ∗ ( 1 + 2 − 1 + 2 − 2 + . . . + 2 − 10 ) = 65504 (-1)^0*2^{30-15}*1.1111111111\\ =1*2^{15}*(1+2^{-1}+2^{-2}+...+2^{-10})\\ =65504 (−1)0∗230−15∗1.1111111111=1∗215∗(1+2−1+2−2+...+2−10)=65504
-
如果 Exponent 位全部为0:
- 如果 Significand位 全部为0,则表示数字 0
-
如果 Exponent 位全部位1:
-
如果 fraction 位 全部为0,则表示 ±inf
-
如果 fraction 位 不为0,则表示 NAN
-
-
-
使用fp16能带来什么问题:
-
溢出错误
-
半精度浮点数有两个字节存储。由于FP16的动态范围比FP32的动态范围要狭窄很多,因此在计算过程中很容易出现上溢出(Overflow )和下溢出(Underflow)的错误,溢出之后就会出现“Nan”的问题。在深度学习中,由于激活函数的的梯度往往要比权重梯度小,更易出现下溢出的情况。
-
表示范围
-
运算结果大于最大正数时称为正上溢,小于绝对值最大负数时称为负上溢,正上溢和负上溢统称上溢。数据一旦产生上溢,计算机必须中断运算操作,进行溢出处理。
-
当运算结果在0至最小正数之间时称为正下溢,在0至绝对值最小负数之间时称为负下溢,正下溢和负下溢统称下溢。 数据下溢时,浮点数值趋于零,计算机仅将其当作机器零处理。
-
-
-
舍入误差
-
Rounding Error指示是当网络模型的反向梯度很小,一般FP32能够表示,但是转换到FP16会小于当前区间内的最小间隔,会导致数据溢出。如0.00006666666在FP32中能正常表示,转换到FP16后会表示成为0.000067,不满足FP16最小间隔的数会强制舍入。
-
解决方案
-
输入FP16的数据,部分运算继续使用FP16计算,得到FP16结果
-
将部分运算转成 FP32类型进行计算,得到 FP32中间结果
-
输出时将所有的FP32数据转换为FP16
-
-
-
-
混合精度训练,指代的是单精度 float和半精度 float16 混合训练。为了想让深度学习训练可以使用FP16的好处,又要避免精度溢出和舍入误差。于是可以通过FP16和FP32的混合精度训练(Mixed-Precision),混合精度训练过程中可以引入权重备份(Weight Backup)、损失放大(Loss Scaling)、精度累加(Precision Accumulated)三种相关的技术。
-
权重备份(Weight Backup)
-
权重备份主要用于解决舍入误差的问题。其主要思路是把神经网络训练过程中产生的激活activations、梯度 gradients、中间变量等数据,在训练中都利用FP16来存储,同时复制一份FP32的权重参数weights,用于训练时候的更新。
-
在计算过程中所产生的权重weights,激活activations,梯度gradients等均使用 FP16 来进行存储和计算,其中权重使用FP32额外进行备份。
-
深度模型中,lr * gradent的参数值可能会非常小,利用FP16来进行相加的话,则很可能会出现舍入误差问题,导致更新无效。因此通过将权重weights拷贝成FP32格式,并且确保整个更新过程是在 fp32 格式下进行的。即:
- w e i g h t 32 = w e i g h t 32 + l r ∗ g r a i d e n t 16 weight_{32}=weight_{32}+lr * graident_{16} weight32=weight32+lr∗graident16
-
权重用FP32格式备份一次,那岂不是使得内存占用反而更高了呢?是的,额外拷贝一份weight的确增加了训练时候内存的占用。 但是实际上,在训练过程中内存中分为动态内存和静态内容,其中动态内存是静态内存的3-4倍,主要是中间变量值和激活activations的值。而这里备份的权重增加的主要是静态内存。只要动态内存的值基本都是使用FP16来进行存储,则最终模型与整网使用FP32进行训练相比起来, 内存占用也基本能够减半。
-
-
损失缩放(Loss Scaling)
-
如果仅仅使用FP32训练,模型收敛得比较好,但是如果用了混合精度训练,会存在网络模型无法收敛的情况。原因是梯度的值太小,使用FP16表示会造成了数据下溢出(Underflow)的问题,导致模型不收敛。于是需要引入损失缩放(Loss Scaling)技术。
-
为了解决梯度过小数据下溢的问题,对前向计算出来的Loss值进行放大操作,也就是把FP32的参数乘以某一个因子系数后,把可能溢出的小数位数据往前移,平移到FP16能表示的数据范围内。根据链式求导法则,放大Loss后会作用在反向传播的每一层梯度,这样比在每一层梯度上进行放大更加高效。
-
-
精度累加(Precision Accumulated)
- 在混合精度的模型训练过程中,使用FP16进行矩阵乘法运算,利用FP32来进行矩阵乘法中间的累加(accumulated),然后再将FP32的值转化为FP16进行存储。简单而言,就是利用FP16进行矩阵相乘,利用FP32来进行加法计算弥补丢失的精度。这样可以有效减少计算过程中的舍入误差,尽量减缓精度损失的问题。
再将FP32的值转化为FP16进行存储**。简单而言,就是利用FP16进行矩阵相乘,利用FP32来进行加法计算弥补丢失的精度。这样可以有效减少计算过程中的舍入误差,尽量减缓精度损失的问题。