相比于 CPU,GPU 在架构设计时将更多的晶体管用于数据处理,而不是数据缓存和流量控制,因此可以高度实现并行计算。具体可以参考 GPU 并行计算入门
由于深度学习是基于大量矩阵运算实现的,因此我们往往使用 GPU 训练深度学习网络。GPU 的计算能力和显存大小决定了计算速度和可运行的网络的大小,但除了 GPU 本身的性能外,网络训练/推理的性能还与我们使用的数据类型有关。在大模型时代,低精度和混合精度的使用非常常见。
float 和 double 类型的数据在内存中以二进制方式存储,由三部分组成:
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 时:
(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的计算方式
最早的 GPU 默认使用 FP32 类型进行运算,但随着模型越来越大,FP32 类型占内存/显存资源大且运算速度慢的问题逐渐暴露了出来。为了降低模型的大小使得在固定显存的 GPU 上可以运行更大(参数量更多)的模型,且提升模型的训练和推理速度,各种低精度的数据类型被提出。
深度学习中常用的数据类型如下:
数据类型 | bits | 符号位 S | 指数位 E | 尾数位 M | 数值范围 | 数值精度 | 设计原理 | 说明 |
---|---|---|---|---|---|---|---|---|
FP32 | 32 | 1 | 8 | 23 | − 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可以作为精度 baseline | |
FP16 | 16 | 1 | 5 | 10 | − 65504 -65504 −65504 ~ 65504 65504 65504 | 1 0 − 3 10^{-3} 10−3 | ||
TF32 | 19 | 1 | 8 | 10 | − 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−3 | TF32 的 E 与 FP32 相同,具有与 FP32 相同的数值范围;M 与 FP16 相同,具有与 FP16 相同的数值精度。 | TF32 (TensorFloat) 是 Nvidia 在 Ampere 架构的 GPU 上推出的用于 TensorCore 的数据格式,在 A100 上使用 TF32 的运算速度是在 V100 上使用 FP32 CUDA Core 运算速度的 8 倍。 |
BF16 | 16 | 1 | 8 | 7 | − 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−2 | BF16 以 16bits 存储,但其 E 与 FP32 一致,因此其数值范围与 FP32 一致;即其数值范围大于 FP32,但精度低于 FP16 | BF16 (bfloat16, brain floating point 16)是由Google Brain开发的,是一种最适合大模型训练的数据类型,但目前只适配于 Ampere 架构的 GPU(如A100)。 |
Int32 | 32 | 1 | 31 | − 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 1 | |||
Int16 | 16 | 1 | 15 | − 32768 -32768 −32768 ~ 32767 32767 32767 | 1 1 1 | |||
Int8 | 8 | 1 | 7 | − 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.001, min=-65504, max=65504, eps=0.000976562, smallest_normal=6.10352e-05, tiny=6.10352e-05, dtype=float16)
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)
BF16 类型的优势:
对于不同的任务和使用场景,最适用的数据类型也是不同的。
(1)不用任务使用不同的数据类型
(2)训练和推理的不同
本节参考:int8/fp16/bf16/tf32在AI芯片中什么作用?【AI芯片】AI计算体系06