大语言模型(LLM)量化基础知识(一)
请大家关注我的知乎博客:- 派神 - - 知乎
随着大型语言模型 (LLM) 的参数数量的增长,与其支持硬件(加速器内存)增长速度之间的差距越来越大,如下图所示:
上图显示,从 2017 年到 2022 年,语言模型的大小显著增加:
- 2017 年: Transformer 模型(0.05B 参数)
- 2018 年: GPT(0.11B 参数)、BERT(0.34B 参数)
- 2020 年: GPT-2(1.5B 参数)、MegatronLM(8.3B 参数)
- 2021 年: GPT-3(175B 参数)、T-NLG(17B 参数)
- 2022 年: MT-NLG(530B 参数)
加速器(一般指GPU)是专门的硬件,用于加速机器学习训练。它们的内存容量对于训练大型模型至关重要。图中显示加速器内存的增长速度远低于模型大小的增长速度:
- 2017 年: TPUv2(16GB)
- 2018 年: V100(32GB)
- 2020 年: TPUv3(32GB)
- 2021 年: A100(40GB)
- 2022 年: A100(80GB)
- 在 2023 年至 2024 年期间,最大且最常用的模型的参数量大约在 700 亿左右。
上图显示了模型大小和加速器内存之间不断增长的差距。“差距”表示模型大小的增长速度远远超过了硬件内存容量的增长速度。这种差距对 LLM 的训练和部署提出了重大挑战。2022 年,模型规模达到了顶峰,但到了 2023 年,人们发现中等规模的模型(~70B 参数)在实际应用中更加实用和高效。然而加速器内存的增长速度没有跟上模型大小的增长速度。 这突出了对更高效的训练技术、模型压缩方法和专用硬件的需求,以克服这一瓶颈。
大型模型的规模和复杂性会限制其访问性和实用性,因此需要找到解决方案,将一个大型、复杂的模型转换为一个更小、更高效的模型,同时保留其核心功能,从而提高可访问性和实用性。 这使得在资源有限的环境中部署和使用模型成为可能。
模型压缩的方法
为了将一个大型、复杂的模型转换为一个更小、更高效的模型,我们需要使用一些模型压缩的方法,下面我们来介绍几种模型压缩的方法: 1.剪枝(Pruning),2.知识蒸馏(Knowledge Distillation),3.量化(Quantization),这里我们会深入讲解 第三种 量化(Quantization)方法,对于剪枝(Pruning)和知识蒸馏(Knowledge Distillation)我们只做简单介绍。
1.剪枝(Pruning)
剪枝(Pruning)的核心思想是移除对模型性能提升不大的连接或神经元,从而减小模型的大小和复杂度:
2.知识蒸馏(Knowledge Distillation)。
知识蒸馏(Knowledge Distillation)的核心思想是使用一个已经训练好的大型模型(教师模型)来指导一个较小型模型(学生模型)的训练,从而将大型模型的知识“蒸馏”到小型模型中。
知识蒸馏指的是使用一个训练好的大型模型(教师模型)来指导小型模型(学生模型)的训练,从而使小型模型获得与大型模型相近的性能,实现模型压缩的目的。
3. 量化(Quantization)
在讲解量化方法之前,我们先回归一下神经网络:
上图是神经网络的一个隐藏层,展示了线性层 followed by 激活函数的计算过程,其中:
- w₁, b₁, w₂, b₂, w₃, b₃: 这些是神经网络的权重(Weights,蓝色w) 和偏差(Biases,紫色b)。每个神经元都有一组对应的权重和偏差。
- x: 橙色箭头和 x 指的是输入向量。
- a₁, a₂, a₃: 这些是每个神经元的输出,也就是激活值(Activations)。
- 公式 aᵢ = g(wᵢ ⋅ x + bᵢ): 这展示了线性层 followed by 激活函数的计算过程。其中:
- wᵢ ⋅ x 表示权重向量和输入向量的点积。
- + bᵢ 表示加上偏差。
- g() 表示激活函数。
- 公式 g(z) = 1 / (1 + e⁻ᶻ): 这定义了激活函数 g(z) 为 sigmoid 函数。sigmoid 函数将线性层的输出映射到 0 到 1 之间,引入非线性。
在神经网络中,我们可以量化权重和激活值,其中:
- 权重 (w̄):神经网络参数
- 激活值 (a):在神经网络层中传播的值
- 例如,在线性层中,a = g(w̄ ⋅ x + b);a 是激活值;w̄ 和 b 是权重。这里用一个线性层的例子进一步解释了激活值、权重和偏差的关系。
有了神经网络的基础知识后,下面我们来真正进入主题,我们来讨论一下模型压缩中的一种重要技术:量化(Quantization)。 它通过降低模型参数的精度来减小模型的大小和所需的存储空间,如下图所示:
量化的核心思想是以较低的精度存储模型的参数,在上图中:
- 左侧表格 (FP32): 展示了使用单精度浮点数 (FP32) 存储的模型参数。每个值占用 4 个字节。
- FP32 to INT8: 表示将参数从 FP32 转换为 8 位整数 (INT8) 的量化过程。
- 右侧表格 (INT8): 展示了量化后的模型参数,使用 INT8 存储。每个值占用 1 个字节
这里如果以FP32 格式存储,每个值需要 4 字节存储空间,9 个值共需 36 字节。而以INT8 格式存储,每个值只需要 1 字节存储空间,同样的 9 个值只需 9 字节。
这里展示量化如何通过降低数值精度(从 FP32 到 INT8)来显著减少模型的存储空间(从 36 字节到 9 字节)。 虽然精度有所损失,但在很多情况下,这种损失对模型性能的影响是可接受的,并且可以换取更小的模型尺寸和更快的推理速度。
然而量化是会有代价的,那就是它会产生量化误差error,如下图所示:
这里我们将FP32 转换为 INT8 导致了精度损失。 每个误差值是原始 FP32 值和量化后 INT8 值之间的差。例如,第一个值 13.5 被量化成 13,误差为 0.5。最先进的(state-of-the-art)量化方法背后的全部挑战是避免性能下降的前提下尽可能的降低这个误差。
下面我们来介绍一些机器学习中不同的数据表示方式,这里会重点介绍整数和浮点数类型的区别,如下图所示:
这里我们将FP32 转换为 INT8 导致了精度损失。 每个误差值是原始 FP32 值和量化后 INT8 值之间的差。例如,第一个值 13.5 被量化成 13,误差为 0.5。最先进的(state-of-the-art)量化方法背后的全部挑战是避免性能下降的前提下尽可能的降低这个误差。
整数 (Integer, int8):
- Integer (int8): 表示 8 位整数类型。
- 绿色方块序列:用 8 个绿色方块直观地表示了 int8 类型,每个方块代表一个比特。其中包含的数字 “1 0 0 0 1 0 0 1” 是一个 int8 数值的二进制表示示例。
浮点数 (Floating Point, FP32, FP16, BF16):
- 三种常见的浮点数类型:单精度浮点数 (FP32)、半精度浮点数 (FP16) 和 Brain Floating Point (BF16)
- 彩色条形图:用不同颜色的条形图展示了 32 位浮点数 (FP32) 的组成部分及各自占用的位数:
- 符号位 (Sign):1 位,用于表示正负。
- 指数位 (Exponent):8 位,用于表示数值的范围(大小)。
- 尾数位 (Fraction):23 位,用于表示数值的精度。
- 总共 32 位。
上面我们介绍了整数 (int8) 和浮点数 (FP32, FP16, BF16)的组成部分。 并重点突出了浮点数的三个组成部分(符号、指数、尾数)及其各自的作用,并以 FP32 为例展示了这些部分是如何构成一个 32 位浮点数的。 这有助于理解不同数据类型在精度和存储空间上的权衡,以及它们在机器学习中的应用。 虽然图片中没有明确指出 FP16 和 BF16 的具体组成,但它们与 FP32 的相似性,只是精度和范围有所不同。
下面我们来介绍一种最简单的量化方法:线性量化 (Linear Quantization)
线性量化的原理是将一个较大范围的浮点数线性映射到一个较小范围的整数。如下图所示:
在上图中:
- 蓝色线段 (FP32): 代表使用单精度浮点数 (FP32) 表示的数值范围,从 -234.1 到 251.51。
- 红色线段 (INT8): 代表使用 8 位整数 (INT8) 表示的数值范围,从 -128 到 127。
- 虚线: 连接 FP32 和 INT8 的值,展示了线性映射的关系。例如,FP32 中的 -234.1 映射到 INT8 中的 -128,FP32 中的 251.51 映射到 INT8 中的 127。
这里我们推荐使用 Hugging Face 的 Quanto 工具将线性量化应用于实际模型。
上图清晰地展示了线性量化的过程:将浮点数范围映射到整数范围。 它还指出了可以使用 Quanto 工具进行实际操作,并提示了量化技术在大语言模型中的重要应用。 通过上图我们可以更容易理解量化是如何工作的,以及为什么它可以用于模型压缩。
4.整数的表示
整数 (Integer)的两种表示方法:无符号整数 (Unsigned Integer)和有符号整数 (Signed Integer),下面我们以 8 位整数为例进行了说明:
无符号整数 (Unsigned Integer):
- n 位无符号整数范围:这里给出了 n 位无符号整数的取值范围,从 0 到 2 的 n 次方减 1。
- 范围:以 8 位无符号整数 (torch.uint8) 为例,说明其取值范围是 0 到 255。
- 绿色方块序列:用 8 个绿色方块直观地表示了 8 位无符号整数,每个方块代表一个字节(byte)。 数值 “1 0 0 0 1 0 0 1” 是一个示例。
- 计算过程:下方展示了如何将二进制 “1 0 0 0 1 0 0 1” 转换为十进制 137 的过程,每个byte乘以 2 的相应幂次再求和。
有符号整数 (Signed Integer):
- 表示方法:有符号整数使用补码 (Two's Complement) 表示法。
- n 位有符号整数的取值范围:这里给出了 n 位有符号整数的取值范围,从 -2 的 (n-1) 次方到 2 的 (n-1) 次方减 1。
- 8 位有符号整数 (torch.int8) 取整范围:这里以 8 位有符号整数 (torch.int8) 为例,说明其取值范围是 -128 到 127。
- 红绿方块序列:用一个红色方块和 7 个绿色方块表示 8 位有符号整数,红色方块代表符号位(1 表示负数)。 数值 “1 0 0 0 1 0 0 1” 是一个示例。
- 计算过程:下方展示了如何将补码表示的二进制 “1 0 0 0 1 0 0 1” 转换为十进制 -119 的过程。最高位(符号位)的权重为负,其余位计算方式与无符号整数相同。
这里我们看到无符号整数只能表示非负数(0和正数),而有符号整数可以表达负数,0和整数,并且有符号数采用的是补码 (Two's Complement) 表示法,在补码中,最高位代表负的 2 的幂次方,其余位则按照标准的二进制转十进制方法计算。
在上图中将二进制 "1 0 0 0 1 0 0 1" 转换为十进制的方法如下:
- 识别符号位: 最左边的byte (1) 是符号位。 1 表示这是一个负数。
- 剩余字节(byte)位计算: 除了符号位之外的其余byte按照正常的二进制到十进制转换方法计算: 0 * 2⁶ + 0 * 2⁵ + 0 * 2⁴ + 1 * 2³ + 0 * 2² + 0 * 2¹ + 1 * 2⁰ = 0 + 0 + 0 + 8 + 0 + 0 + 1 = 9
- 符号位权重: 在 8 位补码中,符号位的权重是 -2⁷ (-128)。
- 最终计算: 将符号位的权重和剩余byte计算结果相加: -128 + 9 = -119
因此,二进制 "1 0 0 0 1 0 0 1" 的 8 位补码表示对应的十进制数是 -119。
或许有读者不熟悉补码 (Two's Complement),下面我们就来介绍一下补码:
补码 (Two's Complement) 是一种用于表示有符号整数的二进制编码方式。它之所以被广泛使用,是因为它可以将加法和减法运算统一起来,简化了计算机硬件的设计。
补码的作用:
- 表示正负数: 补码用最高位(最左边的位)作为符号位,0 表示正数,1 表示负数。
- 简化运算: 使用补码,加法和减法可以用相同的电路实现,无需额外的减法电路。 这是补码最主要的优势。
- 唯一表示零: 与原码和反码不同,补码中零只有一个表示形式,避免了歧义。
补码的计算方法:
对于一个 n 位的整数:
- 正数: 正数的补码与其二进制表示相同。
- 负数: 负数的补码计算方法如下:
- 取其绝对值的二进制表示。
- 将所有byte位取反(0 变 1,1 变 0)。
- 将结果加 1。
举例说明 (8 位):
-
+5
- 二进制:00000101
- 补码:00000101 (与原码相同)
-
-5
-
绝对值的二进制:00000101
取反:11111010
加 1:11111011 (这是 -5 的补码)
-
补码的运算:
- 加法: 直接将两个补码相加,忽略最高位的进位。
- 减法: 将被减数与减数的补码相加,等同于加上减数的相反数。
补码的用途:
- 计算机硬件: 几乎所有现代计算机都使用补码来表示和运算整数。 这是因为补码简化了 ALU(算术逻辑单元)的设计,可以只用加法器实现加减法运算。
- 编程语言: 许多编程语言的底层也使用补码来表示整数。
- 数字信号处理: 在 DSP 中,补码也用于表示和处理数字信号。
补码是一种高效的二进制表示法,它简化了计算机的运算,并且能够清晰地表示正负数和零。 它的广泛应用使得计算机能够更快速、高效地进行整数运算。
下面我们介绍一下在PyTorch 中不同整数数据类型的表示方法,如下图所示:
上门图片中的表格展示了在 PyTorch 中如何使用不同的整数类型。它列出了每种类型的正式名称 (torch.dtype) 和更常用的别名(如果存在),方便开发者在代码中使用。 例如,你可以使用torch.int32或torch.int来表示 32 位有符号整数。 了解这些数据类型及其别名对于编写高效的 PyTorch 代码至关重要,尤其是在处理需要特定整数类型的数据或进行模型量化时。
下面我们来分别介绍一下在PyTorch 中 8 位无符号整数 (torch.uint8) 和有符号整数 (torch.int8) 的表示范围:
在上图中:
-
Unsigned integer: [0, 2ⁿ - 1]: 表明是无符号整数,并给出了 n 位无符号整数的通用取值范围公式,从 0 到 2 的 n 次方减 1。
-
torch.iinfo(torch.uint8): 展示了如何在 PyTorch 中使用 torch.iinfo() 函数获取 torch.uint8 类型的信息。 注意这里标注了 "two 'i's" 并配上了眼睛 👀 表情符号,强调了函数名中有两个 "i",提醒用户不要拼写错误,这是一个很贴心的细节。
-
iinfo(min=0, max=255, dtype=uint8): 这是 torch.iinfo() 函数的输出结果,显示了 torch.uint8 的最小值 (0)、最大值 (255) 和数据类型 (uint8)。
上图展示了 PyTorch 中torch.uint8类型的取值范围,并演示了如何使用torch.iinfo()函数获取数据类型的信息。 强调了 "two 'i's" 的细节,有助于用户避免常见的拼写错误。 这对于理解 PyTorch 中整数类型的表示和使用至关重要,尤其是在图像处理等需要处理像素值(通常在 0 到 255 之间)的情况下。
下面我们来介绍一下在PyTorch 中 8 位有符号整数 (torch.int8) 的表示范围,以及补码表示法:
- [-2ⁿ⁻¹, 2ⁿ⁻¹ - 1]: 这里给出 n 位有符号整数(补码表示)的通用取值范围公式。
- "torch.int8": 表示这是一个关于 torch.int8 类型的示例。
- "torch.iinfo(torch.int8)": 展示了如何在 PyTorch 中使用 torch.iinfo() 函数获取 torch.int8 类型的信息。
- "iinfo(min=-128, max=127, dtype=int8)": torch.iinfo() 函数的输出结果,显示了 torch.int8 的最小值 (-128)、最大值 (127) 和数据类型 (int8)。
这里我们演示了,在PyTorch 中 torch.int8 类型的取值范围,并强调了使用补码表示法。 还演示了如何使用torch.iinfo()函数获取数据类型的信息。 这对于理解 PyTorch 中整数类型的表示和使用至关重要,特别是对于模型量化等需要处理低精度数值的情况。
下面我们来看看如何在 PyTorch 中使用torch.iinfo()函数获取不同有符号整型数据类型的信息,如下图所示:
图中展示了三个使用 torch.iinfo() 函数的例子,分别对应三种不同的整型数据类型:
-
torch.iinfo(torch.int64): 调用 torch.iinfo() 函数,传入 torch.int64 数据类型,这将返回一个 torch.iinfo 对象,其中包含了 torch.int64 类型的各种属性信息,例如最大值、最小值等。
-
torch.iinfo(torch.int32): 调用 torch.iinfo() 函数,传入 torch.int32 数据类型。
-
torch.iinfo(torch.int16): 调用 torch.iinfo() 函数,传入 torch.int16 数据类型。
上图展示了如何使用torch.iinfo()函数获取 PyTorch 中不同整型数据类型的属性信息。 虽然图中没有显示torch.iinfo()函数的输出结果,但它告诉我们如何使用该函数来查询int64、int32和int16的信息,例如它们的取值范围、精度等。 通过这些信息,开发者可以更好地理解和使用 PyTorch 中的整型数据类型。
5.浮点数的表示
下面我们来介绍一下浮点数的三个组成部分及其在不同浮点数格式中的作用,如下图所示:
浮点数的三个组成部分包括:
- Sign(符号): 正数或负数(始终为 1 位)。 用于表示数值的正负。
- Exponent(指数): 影响可表示的数值范围。 指数部分决定了数值的大小范围,类似于科学计数法中的指数。
- Fraction(尾数): 影响数值的精度。 尾数部分决定了数值的精度,类似于科学计数法中的有效数字。
FP32、BF16、FP16 和 FP8 都是浮点数格式,它们对指数和尾数部分使用了特定数量的位。 这意味着不同格式的浮点数在数值范围和精度上有所不同,这取决于它们分配给指数和尾数的位数。 符号位始终是 1 位。
下面我们来介绍一下 FP32(单精度浮点数)的格式以及它如何表示不同范围的数值。
上图中展示了FP32 的三个组成部分以及它们各自占用的位数:
- Sign(符号位): 1 位,红色部分,用于表示正负(0 为正,1 为负)。
- Exponent(指数位): 8 位,绿色部分,用于表示数值的范围(大小)。
- Fraction(尾数位): 23 位,蓝色部分,用于表示数值的精度。
- Total: 总共 32 位,对应“单精度”。
上图数轴和下方的公式解释了 FP32 如何表示不同范围的数值:
- 0: 零是一个特殊值,通常表示为所有位都为 0。
- Subnormal values(非规格化值): 指数位全为 0 (E = 0) 的情况。 这部分数值非常接近于 0,用于表示非常小的数。 其范围大约是 1.4 x 10⁻⁴⁵ 到 1.2 x 10⁻³⁸。 公式为 (-1)ˢ * F * 2⁻¹²⁶,其中:
- S 是符号位。
- F 是尾数位表示的小数部分。
- Normal values(规格化值): 指数位不全为 0 且不全为 1 (E ≠ 0) 的情况。 这是最常用的范围,可以表示大多数的浮点数。 其范围大约是 1.2 x 10⁻³⁸ 到 3.4 x 10³⁸。 公式为 (-1)ˢ * (1 + F) * 2ᴱ⁻¹²⁷,其中:
- S 是符号位。
- F 是尾数位表示的小数部分。
- E 是指数位表示的无符号整数。
数轴上的分段:
数轴上的方括号和数值表示了不同范围的边界:
- 2⁻¹⁴⁹ : 非规格化值的最小值(接近于 0)。
- (1 - 2⁻²³)2⁻¹²⁶: 非规格化值的最大值。
- 2⁻¹²⁶: 规格化值的最小值。
- (1 + 1 - 2⁻²³)2¹²⁷: 规格化值的最大值(接近于 FP32 能表示的最大值)。
上图清晰地展示了 FP32 浮点数的格式、不同组成部分的作用,以及如何表示不同范围的数值,包括非常接近于零的非规格化值和更大范围的规格化值。 它还提供了计算 FP32 数值的公式,有助于更深入地理解浮点数的表示方式。
接下来我们再来介绍一下BF16(Brain Floating Point)的格式以及它如何表示不同范围的数值。
上图中展示了BF16 的三个组成部分以及它们各自占用的位数:
- Sign(符号位): 1 位 (红色),表示数值的正负(0 为正,1 为负)。
- Exponent(指数位): 8 位 (绿色),表示数值的范围(大小)。 与 FP32 相同。
- Fraction(尾数位): 7 位 (蓝色),表示数值的精度。 比 FP32 的 23 位少。
- Total: 总共 16 位,因此被称为“半精度”。
数轴和数值范围:
数轴和下方的公式解释了 BF16 如何表示不同范围的数值:
- 0: 零是一个特殊值,通常表示为所有位都为 0。
- Subnormal values(非规格化值): 指数位全为 0 (E = 0)。用于表示非常接近 0 的小数。范围大约是 9.2 x 10⁻⁴¹ 到 1.2 x 10⁻³⁸。 公式:(-1)ˢ * F * 2⁻¹²⁶,其中:
- S 为符号位。
- F 为尾数位表示的小数部分 (0 ≤ F < 1)。 注意,BF16 的尾数位只有 7 位,精度比 FP32 低。
- Normal values(规格化值): 指数位不全为 0 且不全为 1 (E ≠ 0)。这是最常用的范围。范围大约是 1.2 x 10⁻³⁸ 到 3.4 x 10³⁸。 公式:(-1)ˢ * (1 + F) * 2ᴱ⁻¹²⁷,其中:
- S 为符号位。
- F 为尾数位表示的小数部分 (0 ≤ F < 1)。
- E 为指数位表示的无符号整数。
数轴上的分段和边界值:
数轴上的方括号和数值表示了不同范围的边界:
- 2⁻¹³³: 非规格化值的最小正值。
- (1 - 2⁻⁷)2⁻¹²⁶: 非规格化值的最大值,也是最小规格化值。
- 2⁻¹²⁶: 规格化值的最小正值。
- (1 + 1 - 2⁻⁷)2¹²⁷: 规格化值的最大值。
与FP32的比较:
- BF16 的指数位宽度与 FP32 相同,因此它们表示的数值范围大致相同。
- BF16 的尾数位宽度比 FP32 小得多,因此 BF16 的精度比 FP32 低。
- BF16 在深度学习训练中被广泛使用,因为它在数值范围和精度之间取得了平衡,可以减少内存使用和计算量,同时保持合理的模型精度。
上图清晰地解释了 BF16 浮点数的格式、数值范围、精度以及如何表示不同类型的数值。 通过与 FP32 的比较,可以看出 BF16 牺牲了一定的精度来换取更小的存储空间和更快的计算速度。
接下来,我们来介绍一些FP16(半精度浮点数)的格式、数值范围以及如何表示不同类型的数值(包括非规格化值和规格化值):
上图中列出了 FP16 的三个组成部分及它们占用的位数:
- Sign(符号位): 1 位 (红色),表示数值的正负(0 为正,1 为负)。
- Exponent(指数位): 5 位 (绿色),表示数值的范围(大小)。
- Fraction(尾数位): 10 位 (蓝色),表示数值的精度。
- Total: 总共 16 位。
数轴和数值范围:
数轴和下方的公式解释了 FP16 如何表示不同范围的数值:
- 0: 零是一个特殊值,通常表示为所有位都为 0。
- Subnormal values(非规格化值): 指数位全为 0 (E = 0)。用于表示非常接近 0 的小数。范围大约是 6.0 x 10⁻⁸ 到 6.1 x 10⁻⁵。 公式:(-1)ˢ * F * 2⁻¹⁴,其中:
- S 为符号位。
- F 为尾数位表示的小数部分 (0 ≤ F < 1)。
- Normal values(规格化值): 指数位不全为 0 且不全为 1 (E ≠ 0)。这是最常用的范围。范围大约是 6.1 x 10⁻⁵ 到 6.5 x 10⁴。 公式:(-1)ˢ * (1 + F) * 2ᴱ⁻¹⁵,其中:
- S 为符号位。
- F 为尾数位表示的小数部分 (0 ≤ F < 1)。
- E 为指数位表示的无符号整数。
数轴上的分段和边界值:
数轴上的方括号和数值表示了不同范围的边界:
- 2⁻²⁴: 非规格化数的最小正值。
- (1 - 2⁻¹⁰)2⁻¹⁴: 非规格化数的最大值,也是最小规格化值。
- 2⁻¹⁴: 规格化数的最小正值。
- (1 + 1 - 2⁻¹⁰)2¹⁵: 规格化数的最大值。
与FP32的比较:
- FP16 的指数位和尾数位都比 FP32 少,因此 FP16 的数值范围和精度都比 FP32 低。
- FP16 在深度学习训练中被广泛使用,因为它比 FP32 占用更少的内存和带宽,可以加快训练速度。 但是,使用 FP16 需要注意数值范围和精度 limitations,避免出现下溢或上溢等问题。
总而言之,上图清晰地解释了 FP16 浮点数的格式、不同组成部分的作用,以及如何表示不同范围的数值。 通过与 FP32 的比较,可以看出 FP16 牺牲了数值范围和精度来换取更小的存储空间和更快的计算速度。
三种浮点数数据类型的比较(FP32、FP16 和 BF16)
这里主要比较FP32、FP16 和 BF16的精度和最大值,如下图所示:
上图的表格中有三列:
- Data Type(数据类型): 列出了三种浮点数类型:FP32(单精度)、FP16(半精度)和 BF16(Brain Floating Point)。
- Precision(精度): 比较了三种数据类型的精度。
- FP32:精度最高 (best)。
- FP16:精度次之 (better)。
- BF16:精度较低,但仍然不错 (good)。
- maximum(最大值): 比较了三种数据类型可以表示的最大值。
- FP32:最大值约为 10³⁸。
- FP16:最大值约为 10⁴。
- BF16:最大值约为 10³⁸。
上图清晰地展示了 FP32、FP16 和 BF16 之间在精度和数值范围上的差异。FP32 提供最高的精度和最大的数值范围,但需要更多的存储空间。FP16 降低了精度和数值范围,以节省存储空间。BF16 则在数值范围和精度之间取得了平衡,在保持与 FP32 相似数值范围的同时,降低了精度以减少存储空间和计算成本,使其成为深度学习训练中的常用选择。这也说明 BF16 在某些应用场景下的优势。