文章目录

0. 前言1. 数据的存储方式2. 不同数据类型介绍2.1 深度学习中常用的数据类型2.2 BF16 类型的优势2.3 不同数据类型的使用场景

3. 不同数据类型之间的转换

0. 前言

相比于 CPU,GPU 在架构设计时将更多的晶体管用于数据处理,而不是数据缓存和流量控制,因此可以高度实现并行计算。具体可以参考 GPU 并行计算入门

由于深度学习是基于大量矩阵运算实现的,因此我们往往使用 GPU 训练深度学习网络。GPU 的计算能力和显存大小决定了计算速度和可运行的网络的大小,但除了 GPU 本身的性能外,网络训练/推理的性能还与我们使用的数据类型有关。在大模型时代,低精度和混合精度的使用非常常见。

1. 数据的存储方式

float 和 double 类型的数据在内存中以二进制方式存储,由三部分组成:

符号位 S(Sign): 0 代表正数,1 代表负数指数位 E(Exponent): 用于存储科学计数法中的指数部分,决定了数据的范围尾数位 M(Mantissa): 用于存储尾数(小数)部分,决定了数据的精度

int 类型只包括符号位和指数位,没有尾数位。

如 float 9.125 在计算机中分别按照整数和尾数的二进制进行存储,9 的二进制为 1001,0.125 的二进制为 0.001;所以 9.125 表示为 1001.001,其二进制的科学计数法表示为

1.001001

×

2

3

1.001001 \times 2^3

1.001001×23

在计算机中,任何一个数都可以表示为

1.

x

x

x

×

2

n

1.xxx \times 2^n

1.xxx×2n 的形式,其中

n

n

n 是指数位,

x

x

x

xxx

xxx 是尾数位。

指数位决定了该数据类型的数值动态范围:指数位越多,可表示的数值范围越大。 尾数位决定了该数据类型的数值精度:尾数位越多,可表示的数值精度越高。

下面是 FP16 和 FP32 (float) 的存储示例图:         

        

以 FP16为例,其指数位 E 为 5 bits,由于 00000 和 11111 是特殊数据,所以 E 的范围为 00001~11110,即 1~30;尾数位 M 为 10 bits,范围为 0~1023

(1)计算 FP16 可以表示的数据范围

FP16 可以表示的数据大小为:

(

1

)

S

2

E

15

(

1

+

M

2

1024

)

(-1)^S*2^{E-15}*(1+\frac{M}{2^{1024}})

(−1)S∗2E−15∗(1+21024M​)

因此 FP16 可以表示的最大的正数为:

0

 

11110

 

1111111111

=

(

1

)

0

2

30

15

(

1

+

1023

1024

)

=

65504

0 \ 11110 \ 1111111111=(-1)^0*2^{30-15}*(1+\frac{1023}{1024})=65504

0 11110 1111111111=(−1)0∗230−15∗(1+10241023​)=65504

可以表示的最小的负数为:

1

 

11110

 

1111111111

=

(

1

)

1

2

30

15

(

1

+

1023

1024

)

=

65504

1 \ 11110 \ 1111111111=(-1)^1*2^{30-15}*(1+\frac{1023}{1024})=-65504

1 11110 1111111111=(−1)1∗230−15∗(1+10241023​)=−65504

所以 FP16 可以表示的数据范围为

[

65504

,

65504

]

[-65504,65504]

[−65504,65504]

与 FP16 相比,FP16 可以表示的数据范围为

[

3.4

×

1

0

38

,

3.4

×

1

0

38

]

[-3.4\times10^{38},3.4\times10^{38}]

[−3.4×1038,3.4×1038]

(2)特殊情况分析

在指数位 E 为 00000 或 11111 时:

E

=

00000

E=00000

E=00000时,FP16 可以表示的数据大小为:

(

1

)

S

2

1

15

(

0

+

M

2

1024

)

(-1)^S*2^{1-15}*(0+\frac{M}{2^{1024}})

(−1)S∗21−15∗(0+21024M​)

E

=

11111

E=11111

E=11111时,若 M 全为 0,表示 ± inf;若 M 不全为 0,表示 NAN

(3)分析 FP16 的数值精度

由于尾数位的限制,任何数据类型表示的数都是有精度的,即 FP16 并不是可以表示

[

65504

,

65504

]

[-65504,65504]

[−65504,65504] 内的任意数字。

FP16 可以表示的最小的正数为:

0

 

00001

 

0000000000

=

(

1

)

0

2

1

15

(

1

+

0

1024

)

=

6.10

×

1

0

5

0 \ 00001 \ 0000000000 =(-1)^0*2^{1-15}*(1+\frac{0}{1024})=6.10\times10^{-5}

0 00001 0000000000=(−1)0∗21−15∗(1+10240​)=6.10×10−5

FP16 的数值精度为 0.001,即两个不同 FP16 数值的最小间隔为 0.001,这是由于十进制和二进制之间的数值转换决定的,具体分析可见 LLM大模型之精度问题(FP16,FP32,BF16)详解与实践

用 PyTorch 验证一下:

import torch

print(torch.finfo(torch.float16))

# finfo(resolution=0.001, min=-65504, max=65504, eps=0.000976562, smallest_normal=6.10352e-05, tiny=6.10352e-05, dtype=float16)

本节参考:

float在内存中的存储

LLM大模型之精度问题(FP16,FP32,BF16)详解与实践

彻底搞懂float16与float32的计算方式

2. 不同数据类型介绍

最早的 GPU 默认使用 FP32 类型进行运算,但随着模型越来越大,FP32 类型占内存/显存资源大且运算速度慢的问题逐渐暴露了出来。为了降低模型的大小使得在固定显存的 GPU 上可以运行更大(参数量更多)的模型,且提升模型的训练和推理速度,各种低精度的数据类型被提出。

2.1 深度学习中常用的数据类型

深度学习中常用的数据类型如下:

数据类型bits符号位 S指数位 E尾数位 M数值范围数值精度设计原理说明FP32321823

3.4

×

1

0

38

-3.4\times10^{38}

−3.4×1038 ~

3.4

×

1

0

38

3.4\times10^{38}

3.4×1038

1

0

6

10^{-6}

10−6大部分CPU/GPU/深度学习框架中默认使用FP32,FP32可以作为精度 baselineFP16161510

65504

-65504

−65504 ~

65504

65504

65504

1

0

3

10^{-3}

10−3TF32191810

3.4

×

1

0

38

-3.4\times10^{38}

−3.4×1038 ~

3.4

×

1

0

38

3.4\times10^{38}

3.4×1038

1

0

3

10^{-3}

10−3TF32 的 E 与 FP32 相同,具有与 FP32 相同的数值范围;M 与 FP16 相同,具有与 FP16 相同的数值精度。TF32 (TensorFloat) 是 Nvidia 在 Ampere 架构的 GPU 上推出的用于 TensorCore 的数据格式,在 A100 上使用 TF32 的运算速度是在 V100 上使用 FP32 CUDA Core 运算速度的 8 倍。BF1616187

3.39

×

1

0

38

-3.39\times10^{38}

−3.39×1038 ~

3.39

×

1

0

38

3.39\times10^{38}

3.39×1038

1

0

2

10^{-2}

10−2BF16 以 16bits 存储,但其 E 与 FP32 一致,因此其数值范围与 FP32 一致;即其数值范围远大于 FP16,但精度略低于 FP16BF16 (bfloat16, brain floating point 16)是由Google Brain开发的,是一种最适合大模型训练的数据类型,但目前只适配于 Ampere 架构的 GPU(如A100)。Int3232131

2.15

×

1

0

9

-2.15\times10^{9}

−2.15×109 ~

2.15

×

1

0

9

2.15\times10^{9}

2.15×109

1

1

1Int1616115

32768

-32768

−32768 ~

32767

32767

32767

1

1

1Int8817

128

-128

−128 ~

127

127

127

1

1

1

PyTorch验证:

import torch

print(torch.finfo(torch.float32))

# finfo(resolution=1e-06, min=-3.40282e+38, max=3.40282e+38, eps=1.19209e-07, smallest_normal=1.17549e-38, tiny=1.17549e-38, dtype=float32)

print(torch.finfo(torch.float16))

# finfo(resolution=0.001, min=-65504, max=65504, eps=0.000976562, smallest_normal=6.10352e-05, tiny=6.10352e-05, dtype=float16)

print(torch.finfo(torch.bfloat16))

# finfo(resolution=0.01, min=-3.38953e+38, max=3.38953e+38, eps=0.0078125, smallest_normal=1.17549e-38, tiny=1.17549e-38, dtype=bfloat16)

print(torch.iinfo(torch.int32))

# iinfo(min=-2.14748e+09, max=2.14748e+09, dtype=int32)

print(torch.iinfo(torch.int16))

# iinfo(min=-32768, max=32767, dtype=int16)

print(torch.iinfo(torch.int8))

# iinfo(min=-128, max=127, dtype=int8)

2.2 BF16 类型的优势

BF16 类型的优势:

BF16 的设计思想是在不改变内存占用的情况下,用

1

/

10

1/10

1/10 倍的精度换取了

1

0

34

10^{34}

1034 倍的值域。BF16 只有 2bytes 的内存,但其数值范围与 4bytes 的 FP32 相同。 在深度学习领域,数值范围的作用远高于数值精度;即数据类型的指数位的作用大于尾数位的作用。采用梯度下降法的网络权重的更新方式为:

w

n

e

w

=

w

o

l

d

l

r

g

r

a

d

w_{new}=w_{old}-lr \cdot grad

wnew​=wold​−lr⋅grad 由于梯度

g

r

a

d

grad

grad 和学习率

l

r

lr

lr 通常较小,往往会出现很小的数值,因此必须使用能够表达较大范围的数据类型。使用 FP16时往往会出现 underflow(下溢)的情况,即小于

6.55

×

1

0

4

-6.55\times10^{4}

−6.55×104 的数值被截断为 0 ,导致梯度无法更新。所以 BF16 具有比 FP16 更优异的性能。BF16 与 FP32 的转换很容易。 使用混合精度计算时,需要频繁得对 BF16/FP32 和 FP32 进行转换。BF16 基本上可以看作成一个“截断”版的 FP32, 两者之间的转换是非常直接,其实现电路也会非常简单。相比于 FP16,BF16的使用能有效的降低电路的面积。

2.3 不同数据类型的使用场景

对于不同的任务和使用场景,最适用的数据类型也是不同的。

(1)不用任务使用不同的数据类型

例1:相比于目标检测,分类任务对于数据类型就没有那么敏感了,可能使用 FP16 和 Int8 获得的精度并没有差多少,但使用 Int8 能够显著提升训练和推理性能。例2:由于图像往往是 Int8 格式,因此在 CV 的推理任务中以 Int8 为主;但在 NLP任务中应以 FP16 为主。

(2)训练和推理的不同

FP32 往往只是作为精度基线 (baseline),比如要求使用 FP16 获得的精度达到 FP32 baseline 的 99% 以上。但通常不会使用 FP32 进行训练,尤其对于大模型来说。训练往往使用 FP16, BF16 和 TF32,以降低内存占用,缩写训练时间,降低训练资源(较少耗电)。推理 CV 任务以 Int8 为主,NLP任务以 FP16 为主,大模型可以使用 Int8/FP16 混合推理。

本节参考:int8/fp16/bf16/tf32在AI芯片中什么作用?【AI芯片】AI计算体系06

3. 不同数据类型之间的转换

NVIDIA 的 float(FP32) 与 FP16 的转换方法为:

FP32 -> FP16:

typedef unsigned short half;

half nvFloat2Half(float m)

{

unsigned long m2 = *(unsigned long*)(&m);

// 强制把float转为unsigned long

// 截取后23位尾数,右移13位,剩余10位;符号位直接右移16位;

// 指数位麻烦一些,截取指数的8位先右移13位(左边多出3位不管了)

// 之前是0~255表示-127~128, 调整之后变成0~31表示-15~16

// 因此要减去127-15=112(在左移10位的位置).

unsigned short t = ((m2 & 0x007fffff) >> 13) | ((m2 & 0x80000000) >> 16)

| (((m2 & 0x7f800000) >> 13) - (112 << 10));

if(m2 & 0x1000)

t++; // 四舍五入(尾数被截掉部分的最高位为1, 则尾数剩余部分+1)

half h = *(half*)(&t); // 强制转为half

return h ;

}

FP16 –> FP32:

float nvHalf2Float(half n)

{

unsigned short frac = (n & 0x3ff) | 0x400;

int exp = ((n & 0x7c00) >> 10) - 25;

float m;

if(frac == 0 && exp == 0x1f)

m = INFINITY;

else if (frac || exp)

m = frac * pow(2, exp);

else

m = 0;

return (n & 0x8000) ? -m : m;

}

本节参考:fp16和fp32神经网络混合精度训练

相关阅读

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。