数据类型概述
数据类型概述
int、int16、int32、int64、float、float16、float32、float64
在做模型量化的时候,经常遇到这几个类精度表示,做下记录:
bits:位数
bytes:字节 1bytes = 8 bits
单精度用小数用23位存储,加上默认的小数点前的1为1,2^(23+1) = 16777216.
因为10^7<16777216<10^8,所以说单精度浮点数的有效位数是7位。
双精度的小数位数是52位存储,2^(52+1) = 9007199254740992.
因为10^16<9007199254740992<10^17,所以双精度的有效位数是16位。
float16/32/64对神经网络计算的影响
神经网络的计算,或者说深度学习的计算,全都是浮点数。浮点数的类型分16/32/64(128位的不再考虑范围内,numpy和python最大只到float64),选择哪一种浮点数类型,对神经网络的计算有不同的影响。
(1)目前业界深度学习的标准是BF16,一种16位的浮点数,据说Google的TPU已经支持,未来Intel的芯片也会支持;
(2)我们在一般计算上的,通过numpy得到的16位浮点数,是FP16,不是BF16;FP16是IEEE754-2008的标准;这两个标准,在能够表示的数值的范围上有区别;
(3)对于内存的影响:float64占用的内存是float32的两倍,是float16的4倍;比如对于CIFAR10数据集,如果采用float64来表示,需要60000*32*32*3*8/1024**3=1.4G,光把数据集调入内存就需要1.4G;如果采用float32,只需要0.7G,如果采用float16,只需要0.35G左右;占用内存的多少,会对系统运行效率有严重影响;(因此数据集文件都是采用uint8来存在数据,保持文件最小)
(4)采用numpy的float16,即FP16,来计算深度学习,容易出现runtime warning;因为精度不够,更容易在计算过程中,产生极限值;(也许有办法解决这个问题,比如进一步缩小初始化的weights和bias)
(5)如果采用numba来进行计算加速,要求所有计算的数据类型要保持一致;如果不加速,numpy在计算过程中,会自动upcast到数据精度更大的类型上去,这个问题要通过仔细写代码来规避;
(6)如果做gradient check,即通过比较网络计算的梯度和数学定义的方式计算出来的梯度来判断代码是否正确,float32的精度可能都不够,需要比较到小数点后6-8位;
(7)在没有numba加速的情况下,如果是64位的CPU,float64的计算速度最快;因为对于float32和float16,CPU都需要多条指令来进行数据的转移,没有严格测试过,有可能float16是最慢的;
(8)基于以上事实,我自己的teapot库,决定采用float32!并保持在整个计算过程中,全部参与计算的数据,都是float32,不会upcast的情况;并可通过一个全局可变常量,来控制浮点数类型的选择,默认就是float32;
(9)也可以考虑数据集数据在内存中使用float16,而神经网络的weights和bias使用float32,这样在计算过程中,float16和float32并存,即减少了内存占用,也保持了一定的精度;不过这样做,就无法采用numba加速;
深度学习与bfloat16(BF16)
Deep learning has spurred interest in novel floating point formats. Algorithms often don’t need as much precision as standard IEEE-754 doubles or even single precision floats. Lower precision makes it possible to hold more numbers in memory, reducing the time spent swapping numbers in and out of memory. Also, low-precision circuits are far less complex. Together these can benefits can give significant speedup.
深度学习促使了人们对新的浮点数格式的兴趣。通常(深度学习)算法并不需要64位,甚至32位的浮点数精度。更低的精度可以使在内存中存放更多数据成为可能,并且减少在内存中移动进出数据的时间。低精度浮点数的电路也会更加简单。这些好处结合在一起,带来了明显了计算速度的提升。
BF16 (bfloat16) is becoming a de facto standard for deep learning. It is supported by several deep learning accelerators (such as Google’s TPU), and will be supported in Intel processors two generations from now.
bfloat16,BF16格式的浮点数已经成为深度学习事实上的标准。已有一些深度学习“加速器”支持了这种格式,比如Google的TPU。Intel的处理与在未来也可能支持。
The BF16 format is sort of a cross between FP16 and FP32, the 16- and 32-bit formats defined in the IEEE 754-2008 standard, also known as half precision and single precision.
BF16浮点数在格式,介于FP16和FP32之间。(FP16和FP32是 IEEE 754-2008定义的16位和32位的浮点数格式。)
BF16的指数位比FP16多,跟FP32一样,不过小数位比较少。这样设计说明了设计者希望在16bits的空间中,通过降低精度(比FP16的精度还低)的方式,来获得更大的数值空间(Dynamic Range)。
IEEE 754 浮点数标准
IEEE 754标准的主要起草者是加州大学伯克利分校数学系教授William Kahan,他帮Intel公司设计了8087浮点数处理器(FPU),并以此为基础形成了IEEE 754标准,Kahan教授也因此获得了1987年的图灵奖。目前,几乎所有计算机都采用IEEE 754标准表示浮点数。
在IEEE 754中,定义了两种浮点数格式:32位单精度和64位双精度。
IEEE 754浮点数格式
(单精度和双精度都有一个固定的1bit符号位,因此,浮点数不存在unsigned这个概念,都是有符号的)
IEEE 754 的规定:
32位单精度格式中包含1bit符号位s,8bit阶码e,23bit尾数f;
64位双精度格式中包含1bit符号位s,11bit阶码e和52bit尾数f;
基数隐含为2;(基数越大,浮点数能够表示的范围越大,但是精度越低,数变得更稀疏,选择2作为隐含的基数,就是为了精度,并通过双精度来提高表示范围)
尾数f用原码表示,第一位总是1,因而可在尾数中省略第一位的1,称为隐藏位,这样使得单精度格式的23bit尾数实际表示了24bit有效数字,双精度格式的52bit的尾数实际上表示了53bit有效数字。IEEE 754规定隐藏位1的位置在小数点之前。
阶码用移码表示,但偏置常数并不是通常n位移码所用的2^(n-1),而是2^(n-1)-1;因此,单精度浮点数偏置常数为127,双精度浮点数偏置常数为1023。因为尾数f中有一位在小数点之前的1的隐藏位中,所以,如果尾数换成用等值的纯小数表示的话,阶码就需要加1,相当于偏置常数为128和1024。这样做带来两个好处:
(1)尾数可表示的位数多了一位,因而使浮点数的精度更高;
(2)阶码的可表示范围更大,因而使浮点数范围更大。例如:对于单精度浮点数格式,其阶码为8位,当偏置常数用128时,最大机器码11111111对应的值是255-128=127,当偏置常数用127时,其对应的值为255-127=128。显然,偏置常数采用127时,阶码的范围更大。
虽然零有+0和-0,一般情况下,两者是等效的。
浮点数的表示范围和非规划数:
从上面的知识可以看出,浮点数也是由范围的,双精度的范围比单精度呀高。
在计算机表示整数的概念里面,有一个溢出的概念,overflow。浮点数同样有益处的概念,不过浮点数的溢出稍微复杂一点,浮点数一共有4个溢出区域:正上溢,正下溢,负下溢,负上溢。
在IEEE 754标准中,非规格化数(小数点前固定是0)就是用来填补正下溢和负下溢这两个接近0的空间。
当计算结果非常小的时候,已经小于规格化数(小数点前固定是1)能够表示的最小值,这时的计算采用“逐级下溢”的方式:
当运算结果的阶码太小,比最小能表示的阶码还小,即小于-126或小于-1022时,尾数右移,价码加1,如此循环直到尾数为0或阶码达到可表示的最小值(尾数不能为全0,实际看到的效果是,阶码是0,f不等于0,但是f一直在右移,左边0 的个数越来越多)。
浮点数的密度问题:
对于n为二进制编码,所能表示的不同的数据最多有2^n个,因此,浮点数虽然表示范围扩大了,但与定点数相比,并没有能够表示更多的数。实际上,只是这些数在数轴上朝正负两个方向在更大的范围内散开,在数轴上的分布变稀疏了。定点数分布是等距且紧密的,而浮点数分布是不等距且稀疏的,越远离原点越稀疏。
下面这段文字,很好的解释了浮点数的密度问题,以及非规格化数带来的范围变化:
浮点数的密度问题
双精度浮点数扩展:
IEEE 754规定,双精度扩展格式必须至少具有64位有效数字,并总共占用至少79位,但没有规定其具体的格式,处理器厂家可以选择符合该规定的格式。
例如:Intel x86 FPU采用80位双精度扩展格式,包含4个字段:1个符号位,15位阶码(偏置常量为16383),一位显示首位有效位(explicit leading significant bit)和63位尾数。Intel采用的这种扩展浮点数格式与IEEE 754规定的格式有一个重要的区别,它没有隐藏位,有效位数共64位。Intel 安腾FPU采用82位扩展精度。
又如:SPARC和Power PC处理器中采用128位扩展双精度浮点数格式,包含一位符号位,15位阶码(偏置常量为16383),和112位尾数,采用隐藏位,所以有效位数为113位。
浮点数在C语言中的表示:
float表示单精度;
double表示双精度;
long double表示扩展双精度(长度和格式随编译器和处理器类型的不同而有所不同)。