> 文章列表 > MIT 6.S965 韩松课程 05

MIT 6.S965 韩松课程 05

MIT 6.S965 韩松课程 05

Lecture 05: Quantization (Part 1)

文章目录

    • Lecture 05: Quantization (Part 1)
      • 动机
      • 数字的数据类型
      • 量化
        • 基于 K-Means 的量化 [[Han et al., ICLR 2016]](https://arxiv.org/pdf/1510.00149v5.pdf)
        • 线性量化 [[Jacob et al. CVPR 2018]](https://arxiv.org/pdf/1712.05877.pdf)
      • 笔记二
        • 什么是量化?
        • 数字的数据类型
        • 基于 K-Means 的权重量化
        • 线性量化
        • 数学内容
      • 讲座内容
Title Quantization (Part 1)
Lecturer Song Han
Date 09/22/2022
Note Author Aaron Langham (alangham)
Description Review of data types in computer systems, introduction to quantization in neural networks, and introduction to three common quantization methods.

动机

  • 内存(以及内存中的数据移动)是昂贵的(就能量消耗而言)。 [Horowitz, M., IEEE ISSCC 2014]
  • 较低的 bit-width 操作更廉价。
  • 实际上,我们并不总是需要高精度位宽,因此可以通过使用较低的位宽来提高深度学习的效率。

数字的数据类型

我们如何在计算机系统中表示数字类的数据?

整数

  • 整数是最基本的数字数据类型,可以表示无符号或有符号的数据。
  • nnn 位无符号整数可以表示的范围为:[0,2n−1][0, 2^n - 1][0,2n1].
  • 有符号整数可以通过两种不同的方式表示其符号:
    • 原码表示法中的第一位表示符号,其表示范围为:[−2n−1−1,2n−1−1][-2^{n-1} - 1, 2^{n-1} - 1][2n11,2n11],然而对于 0 有两种表示方法(全 0 以及一个 1 后面全是 0),这是一种浪费;
    • 补码表示法的表示范围为:[−2n−1,2n−1−1][-2^{n-1}, 2^{n-1} - 1][2n1,2n11],不存在浪费,全 0 表示 0,而 1 后面跟着都是 0 则表示最大的负数 −2n−1-2^{n-1}2n1.

定点数

  • 与整数非常相似,但保留了一组 bit 作为分数部分。

浮点数

  • 与整数不同,此数据类型的值之间没有统一的间距。然而,它可以覆盖更大的范围(对于 NN 训练很重要,因为梯度具有大的动态范围)。
  • IEEE 754 指定的 32 位浮点数由 1 个符号位、8 个指数位和 23 个小数位组成。
  • 存储的十进制值表示为:(−1)sign×(1+fraction)×2exponent - 127(-1)^{\\text{sign}} \\times (1 + \\text{fraction}) \\times 2^{\\text{exponent - 127}}(1)sign×(1+fraction)×2exponent - 127
  • 还有许多浮点规范,例如:
    • IEEE FP32, IEEE FP16
    • Brain Float (BF16)
    • Nvidia TensorFloat (TF32)
    • AMD 24-bit Float (AMD FP24)

量化

我们将量化定义为将输入从连续或较大的值集范围约束为离散或较小的值集范围的过程。

基于 K-Means 的量化 [Han et al., ICLR 2016]

  • 选择聚类索引的位宽 BBB.
  • 在权重矩阵上运行 K-Means 聚类算法,寻找 2B2^B2B 个类
  • 将矩阵存储为 BBB-bit 的整数索引(大小与权重矩阵一致)以及长度为 BBB 的码本,表示为质心(浮点精度)
  • 为了微调训练的模型,将梯度元素分组到存储的质心中,对每个质心中的元素求和,乘以学习率,然后从存储的质心减去。
  • 由于权重以不同的频率出现,因此可以使用霍夫曼编码来对权重进行最佳编码,以实现快速访问。
  • 然而,所有的计算和内存访问仍然是浮点的。

线性量化 [Jacob et al. CVPR 2018]

  • 寻找两个参数(SSSZZZ) ,其创建整数(qqq)到实数(rrr)的仿射映射,使得:r=S(q−Z)r = S(q-Z)r=S(qZ)
  • ZZZ 为零点,被用来存储一个整数
  • SSS 是一个缩放因子,被用来存储一个浮点数
  • 对于矩阵,浮点计算 Y=WX\\mathbf{Y} = \\mathbf{W}\\mathbf{X}Y=WX 可以转为量化计算形式:
    qY=SWSXSY(qWqX−ZWqX−ZXqW+ZWZX)+ZY\\mathbf{q_Y} = \\frac{S_W S_X}{S_Y} (\\mathbf{q_W}\\mathbf{q_X} - \\mathbf{Z_W}\\mathbf{q_X} - \\mathbf{Z_X}\\mathbf{q_W} + \\mathbf{Z_W} \\mathbf{Z_X}) + \\mathbf{Z_Y}qY=SYSWSX(qWqXZWqXZXqW+ZWZX)+ZY
  • 我们可以近似 ZW\\mathbf{Z_W}ZW 为 0,由于训练后的权重往往为零均值,因此我们可以预先计算 ZXqW\\mathbf{Z_X}\\mathbf{q_W}ZXqW.
  • 量化可以是对称的(浪费一个量化槽位为代价),也可以是非对称的(需要更复杂的实现)。
  • 前面的矩阵乘法可以扩展为带有偏置项 (Y=WX+b\\mathbf{Y} = \\mathbf{W}\\mathbf{X} + \\mathbf{b}Y=WX+b) ,相应的量化计算形式为:
    SY(qY−ZY)=SWSX(qWqX−ZXqW+qb)S_Y (\\mathbf{q_Y} - \\mathbf{Z_Y}) = S_W S_X (\\mathbf{q_W}\\mathbf{q_X} - \\mathbf{Z_X}\\mathbf{q_W} + \\mathbf{q_b} )SY(qYZY)=SWSX(qWqXZXqW+qb)
  • 这允许全连接层和卷积层使用整数而不是浮点精度进行计算。

笔记二

Title Quantization (Part I)
Lecturer Song Han
Date 09/22/2022
Note Author Pranav Krishna (pkrishna)
Description The first half of an introduction to Quantization, which is a common model compression technique

什么是量化?

广义上说,量化是将连续信号离散化的过程;它在信号处理(以离散间隔采样)和图像压缩(减少每个像素可能的颜色空间)中很常见。这项技术与剪枝(来自上两节课)是正交的。

数字的数据类型

在机器学习中,量化涉及将每个权重的数据类型更改为限制性更强的数据类型(可以用更少的比特表示)。本节简要描述了机器学习模型中使用的常见数据类型:

整数

整数可以是有符号的,也可以是无符号的。如果它们是无符号的,那么它们只是 nnn 位的数字,在 [0,2n−1][0, 2^n-1][0,2n1] 的范围内。如果它们是有符号的,有两种方式可以表示它们:

  • 原码(Signed-Magnitude)表示法:表示范围为 [−2n−1−1,2n−1−1][-2^{n-1}-1, 2^{n-1}-1][2n11,2n11],第一位是符号位,0 代表正数,1 代表负数,剩下的 n−1n-1n1 位表示数字。它的一个缺点是 1000…1000\\dots10000000…0000\\dots0000 都表示数字 0

  • 补码(Two’s Complement)表示法:表示范围 [−2n,2n−1][-2^n, 2^{n-1}][2n,2n1],这里的想法是−x−1=∼x-x-1=\\sim xx1=∼x(按位取反)。请注意,与原码表示类型相比,负数“走另一条路”。它消除了前面方法的冗余。

定点数

表示小数,但行为与整数非常相似,因为它们只是整数偏移了 222 的幂。它们有固定数量的位来表示小数前后的数字。

浮点数

这是一种更常见的表示实数的方法。数据分为三部分——符号位(通常是第一部分)、指数位和尾数/有效位/分数。

数字值为 (−1)sign×(1+mantissa)+2exponent−exponent bias(-1)^{\\text{sign}} \\times (1 + \\text{mantissa}) + 2^{\\text{exponent} - \\text{exponent bias}}(1)sign×(1+mantissa)+2exponentexponent bias,其中尾数被读取为二进制小数,而指数被读取为整数。如果我们让 kkk 用于表示指数的位数,那么指数偏移被定义为 2k−1−12^{k-1}-12k11

以下是浮点约定的列表以及每个部分使用的位数:

约定 符号位 指数位 尾数
IEEE 754 1 8 23
IEEE Half-Precision 16-bit float 1 5 10
Brain Float (BF16) 1 8 7
NVIDIA TensorFloat 32 1 8 10
AMD 24-bit Float (AMD FP24) 1 7 16

系统评论:Brain Float 以牺牲表示的精度扩大表示范围,是专门为神经网络设置而设计的,因为精度不如范围重要。对于 NVIDIA,‘32’ 不是指总位数(即19),而是指使用的指数位数(8),与 IEEE 754 相同。19 位可能看起来是一个奇怪的数字,但 NVIDIA 使用专门的硬件来达到最佳效果。

基于 K-Means 的权重量化

动机: 正如 Brain Float 所暗示的那样,神经网络的性能实际上并不很大程度上取决于权重的精度。一般来说,像 2.09、2.12、1.92 和 1.87 这样的值都可以近似为 2。那么,为什么我们不把权重近似成这样呢?

方法: 将权重聚类到 nnn 个桶中(使用 K-Means 聚类算法),其中 nnn 通常被选择为 222 的幂。然后,使用已放置在桶中的所有权重的平均值来近似网络中出现的每个权重。将 bucket id 到值的映射存储在码本中,并为每个权重存储 bucket id。

使用:在推理过程中,你将使用它们根据码本表示的权重来替换 bucket id,并执行正常的浮点运算。如果你想微调模型,那么你只需要执行梯度下降,参数是码本表。通常,将桶中每个权重的梯度相加,得到整个桶的梯度。

另一件需要注意的重要事情是,如果你想剪枝和量化一个模型,一般的做法是先剪枝,再量化。

分析:就存储压缩比而言,设 NNN 为每个权重的原始位数,nnn 为用于聚类的存储桶数,WWW 为权重数。那么,压缩比大约为 Wlog⁡2n+NnWN\\frac{W\\log_2n + Nn}{WN}WNWlog2n+Nn;第二项用于码本存储。通常,随着模型尺寸的增大(即 W→∞W\\rightarrow\\inftyW),压缩比接近 log⁡2nN\\frac{\\log_2n}{N}Nlog2n

对于推理时间,它的速度可能会加快,这似乎违反直觉,因为码本方案为计算添加了另一层——使用 bucket id 获取值。然而,当考虑到实际的体系结构时,可以发现加速。一般来说,由于大小压缩,DRAM 访问和数据传输到高速缓存的成本会更低。请注意,该码本确实需要专门的架构来实现该方法的全部潜力——码本中的值通常存储在 on-chip SRAM 中,这需要一个时钟周期才能访问。

极限挑战

  • 超参数选择:此方法唯一要选择的超参数是码本条目的数量——通常,标准做法是使用 16 个;AlexNet 的实验表明,在精度显著下降之前,卷积层需要 4 位,而全连接层需要 2 位。奇怪的是,这些阈值与模型是否首先被剪枝无关。
  • SqueezeNet:一般来说,人们可能会问:为什么我们不从一开始就训练压缩模型?SqueezeNet 的创建者决定尝试一下。结合剪枝和量化,该模型实现了强大的 510 倍压缩率,同时保持了 AlexNet 的 Top-1 和 Top-5 精度值。
  • 霍夫曼编码:我们做了一个隐含的假设,即所有的桶都必须由相同数量的比特表示。但是,很明显,有些桶会比其他桶出现得更频繁。这就引出了一个问题——为什么我们不使用较低数量的比特来获得更频繁的权重?使用霍夫曼编码可以做到这一点,并且可以进一步提高压缩比。

线性量化

数学警告⚠️

量化的第二种主要类型是线性量化——你想要一个从整数到实数区间的仿射映射:r=S(q−z)r = S(q - z)r=S(qz),在这个公式中,SSS 是缩放因子,通常是一个浮点数,qqqrrr 的量化版本,因此是一个整数;ZZZ 是被选择为使得其映射到零的相同类型的整数。

寻找参数值(非对称量化)

rmin⁡,rmax⁡r_{\\min}, r_{\\max}rmin,rmax 为所有原始权重的最小值和最大值;设 qmin⁡,qmax⁡q_{\\min}, q_{\\max}qmin,qmax 是量化范围的最小值和最大值(对于 nnn 位,通常为 −2n,2n−1-2^n,2^n-12n2n1)。那么,我们大概想要 rmin⁡=S(qmin⁡−Z)r_{\\min} = S(q_{\\min} - Z)rmin=S(qminZ)rmax⁡=S(qmax⁡−Z)r_{\\max} = S(q_{\\max} - Z)rmax=S(qmaxZ)。从第一个中减去第二个,得到 rmax⁡−rmin⁡=S(qmax⁡−qmin⁡)r_{\\max} - r_{\\min} = S(q_{\\max} - q_{\\min})rmaxrmin=S(qmaxqmin),这就得到:S=rmax⁡−rmin⁡qmax⁡−qmin⁡S = \\frac{r_{\\max} - r_{\\min}}{q_{\\max} - q_{\\min}}S=qmaxqminrmaxrmin

对于零偏移,我们希望我们的量化方案能够精确地表示零。因此,从方程 rmin⁡=S(qmin⁡−Z)r_{\\min} = S(q_{\\min} - Z)rmin=S(qminZ) 中,我们也可以得到 Z=qmin⁡−rmin⁡SZ = q_{\\min} - \\frac{r_{\\min}}{S}Z=qminSrmin,我们将其更改为 Z=round(qmin⁡−rmin⁡S)Z = \\text{round}\\left(q_{\\min} - \\frac{r_{\\min}}{S}\\right)Z=round(qminSrmin)

对称量化

所涵盖的第一个方案是不对称量化,因其所能代表的正值和负值之间固有的不对称性而得名。可以使用的另一种方案是对称量化;在这种情况下,ZZZ 值固定为 000,而新的缩放因子变为 S=∣r∣max⁡−qmin⁡S = \\frac{\\lvert r \\rvert_{\\max}}{-q_{\\min}}S=qminrmax,使用与上一节类似的符号。

虽然实现更容易,并且处理零的逻辑更干净,但这会导致量化范围被有效地浪费(即,有一系列值可以由我们的方案表示,但不需要);在任何 ReLU 操作之后尤其如此,在 ReLU 操作中,我们知道值是非负的,这本质上会丢失一整个 bit 信息!一般来说,这意味着我们不使用这个方案来量化激活,但我们可以量化权重。

数学内容

现在,假设我们的模型中有一个线性层(没有偏差)Y=WXY=WXY=WX;对于我们的量化方案,这变成:
Y=WXSY(qY−ZY)=SW(qW−zW)⋅SX(qX−Zx)qY=SWSXSY(qW−zW)(qX−ZX)+ZYqY=SWSXSY(qWqX−zWqX−ZXqW+ZWZX)+ZY\\begin{align} Y &= WX \\\\ S_Y(q_Y - Z_Y) &= S_W(q_W - z_W) \\cdot S_X(q_X - Z_x) \\\\ q_Y &= \\frac{S_WS_X}{S_Y}(q_W - z_W)(q_X - Z_X) + Z_Y \\\\ q_Y &= \\frac{S_WS_X}{S_Y}(q_Wq_X - z_Wq_X - Z_Xq_W + Z_WZ_X) + Z_Y \\end{align} YSY(qYZY)qYqY=WX=SW(qWzW)SX(qXZx)=SYSWSX(qWzW)(qXZX)+ZY=SYSWSX(qWqXzWqXZXqW+ZWZX)+ZY
请注意,ZXqW+ZWZXZ_Xq_W+Z_WZ_XZXqW+ZWZX 可以预先计算,因为这不取决于特定的输入(ZXZ_XZX 仅取决于我们的量化方案,通过输入数据集的全局统计确定);并且,我们可以通过选择对称量化方案来设置 ZW=0Z_W=0ZW0

添加偏置项

如果我们添加偏置项,b=Sb(qb−Zb)b=S_b(q_b-Z_b)b=SbqbZb,我们还应该设置 Zb=0Z_b=0Zb=0 来匹配 WWW,为了使计算更简单,我们可以只设置缩放因子 SWSXS_WS_XSWSX(这在实践中很有效)。然后,我们有:
Y=WX+bSY(qY−ZY)=SW(qW−zW)⋅SX(qX−Zx)+Sb(qb−Zb)qY=SWSXSY(qW−zW)(qX−ZX)+ZY+SbSY(qb−Zb)qY=SWSXSY(qWqX−zWqX−ZXqW+ZWZX+qb−Zb)+ZYqY=SWSXSY(qWqX+qb−ZXqW)+ZYqY=SWSXSY(qWqX+qbias)+ZY\\begin{align} Y &= WX + b \\\\ S_Y(q_Y - Z_Y) &= S_W(q_W - z_W) \\cdot S_X(q_X - Z_x) + S_b(q_b - Z_b) \\\\ q_Y &= \\frac{S_WS_X}{S_Y}(q_W - z_W)(q_X - Z_X) + Z_Y + \\frac{S_b}{S_Y}(q_b - Z_b) \\\\ q_Y &= \\frac{S_WS_X}{S_Y}(q_Wq_X - z_Wq_X - Z_Xq_W + Z_WZ_X + q_b - Z_b) + Z_Y \\\\ q_Y &= \\frac{S_WS_X}{S_Y}(q_Wq_X + q_b - Z_Xq_W) + Z_Y \\\\ q_Y &= \\frac{S_WS_X}{S_Y}(q_Wq_X + q_{\\text{bias}}) + Z_Y \\end{align} YSY(qYZY)qYqYqYqY=WX+b=SW(qWzW)SX(qXZx)+Sb(qbZb)=SYSWSX(qWzW)(qXZX)+ZY+SYSb(qbZb)=SYSWSX(qWqXzWqXZXqW+ZWZX+qbZb)+ZY=SYSWSX(qWqX+qbZXqW)+ZY=SYSWSX(qWqX+qbias)+ZY
其中,我们定义 qbias=qb−ZXqWq_{\\text{bias}} = q_b - Z_Xq_Wqbias=qbZXqW,因为这可以通过与以前类似的参数预先计算。

卷积

事实证明,由于卷积本质上也是一个线性算子,因此其量化的推导与线性层的推导极其相似。通过类似的定义(即 ZW=Zb=0,Sb=SWSXZ_W=Z_b=0,S_b=S_WS_XZW=Zb=0Sb=SWSX),我们将得到 qY=SWSXSY(Conv(qW,qX)+qbias)+ZYq_Y = \\frac{S_WS_X}{S_Y}\\left(\\text{Conv}(q_W, q_X) + q_{\\text{bias}}\\right) + Z_YqY=SYSWSX(Conv(qW,qX)+qbias)+ZY,其中 qbias=qb−Conv(qW,ZX)q_{\\text{bias}} = q_b - \\text{Conv}(q_W, Z_X)qbias=qbConv(qW,ZX)


讲座内容


MIT 6.S965 韩松课程 05

在这节课上,我们将讨论量化,就像在上一节课中,我们讨论了可以帮助去除神经网络中参数量的剪枝技术。减少模型大小的一种方法是通过减少连接的数量,另一种方法则是减少权重表示的 bits。

右图是本次量化讲座的大纲,我们将首先回顾数据类型,我们将如何表示数字,学习网络量化的基本概念。然后我们将介绍三种广泛使用的计算方法:

  • 基于 K-Means 的量化
  • 线性量化
  • 二元或三元量化。

模型大小都在快速增长,我们希望拥有小的模型尺寸,并保持高精度,因为内存移动很昂贵,所以我们希望减少内存移动。(这些都是前面课程内容的回顾)

量化是将输入从一组连续或较大的值,约束为一组离散值的过程。例如右侧图中连续的正弦曲线连续信号可以通过一组离散的信号进行简化;再比如颜色空间,原始的图像像素可能是 RGB 256 的颜色空间范围,现在压缩到 16 种颜色空间下,相反,如果做超分辨率,则要扩大颜色空间范围。

首先让我们学习一些关于数字数据类型的基础知识,右侧的图展示的是二进制数的表示法,对于无符号数,nnn 位可以表示的范围是 [0,2n−1][0, 2^n-1][0,2n1],而对于有符号数,有两种表示法,分别是原码和补码表示法。

原码、反码、补码表示法:

原码:将最高位作为符号位(0 表示正,1 表示负),其它数字位代表数值本身

反码:如果是正数,则表示方法和原码一样;如果是负数,符号位不变,其余各位取反。

补码:如果是正数,则表示方法和原码一样;如果是负数,则将数字的反码加上 1

为什么需要反码?

举个例子,3+3 的原码计算 0011+0011=0110=6,计算正确,但 3+(-3)=0011+1011=1110=(-6),计算错误;

以反码表示法运算,3+(-3) = 0011+1100=1111,别慌,1111 也是结果的反码表示,将其转换为原码为 1000,即 -0,-0=0,也就是 0 有两种表示方法。

补码的好处

补码成功解决了数字 0 在计算机中非唯一编码的问题,也实现了减法变加法。

举个例子,钟表盘上,7点后退4格为3点,同理,前进8格也是3点,即 7-4 = (7+8) % 12 = 3

比如 7-3 的运算,用原码表示为 (+7)-(+3)=0111- 0011 = [0111 + (01000-0011)] % 01000 = [0111+0101] % 01000 = 0100 = 4

而如果使用补码:7-3 = (+7) + (-3) = 0111+1101=0100=4

关于补码为什么是反码再加 1,感兴趣的可以自己研究一下,这里给出我在大学时老师教的一种快速写补码的方法,正常的补码需要先写出反码再加 1,这容易犯错,而我们通常习惯于写原码,例如

-1234 的二进制原码表示为:110011010010,反码为 101100101101,正统补码计算为反码 +1, 即 101100101110

简便方法计算过程直接在原码基础上,从最右侧开始找到第一个 1 所在位置,该 1 和右侧所有的 0 保持不动,直接复制下来,然后左侧所有的数字取反即可,保留符号位不动。在该示例中,最右侧的 10 不动,其实原理上也是取反再加一的过程,只是不需要明确再写出反码了。

需要注意的是反码中的 0 有两种表示方法,而在补码中,全 0 表示的是数字 0,而 100…000 则表示最大的负数。

图中 49 的原码表示为 00110001,反码表示为 10110001,符号位变了,补码表示 11001111,我们如果忽略补码的符号位,剩下的 1001111 用原码进行解释,该值为 79,128-79=49,所以你懂了吗?补码实际上就是钟表中你说后退 4 步,它非说要前进 8 步。

左侧是定点数值的表示方法,依然采用补码表示法,需要理解的是,小数点右侧的数值分别表示 0.5, 0.25, 0.125 等,关于计算方法上有两种形式,一种就是朴素的累加方法,另一种是按照非小数形式计算整体的补码数值 49,然后乘上小数点位 2−42^{-4}24

右图表示的是 32 位的浮点类型表示法,IEEE 754 格式,其中 1 未代表符号位,接下来 8 位代表阶码,最后 23 位是尾数,其数值表示法如图所示,需要注意的是这里存在一个阶码偏移的问题。

采用这种方法的一个很直观的好处是表示的数值范围很大,由于阶码偏移的存在,阶码可以代表的指数区间范围为 [-126, 127],其中 -127 和 128 两个有特殊用途。

关于 IEEE 754 表示法,参见 https://zh.wikipedia.org/wiki/IEEE_754

在神经网络中,一些梯度值等波动范围比较大,采用这种表示法可以很好的表示,图中给出了一个具体数字的表示方法。

TF32: 英伟达 https://blogs.nvidia.com/blog/2020/05/14/tensorfloat-32-precision-format/

BF16: 由 Google Brain 开发,https://en.wikipedia.org/wiki/Bfloat16_floating-point_format 或 https://cloud.google.com/tpu/docs/bfloat16

关于各种数据格式表示法的更多内容:https://moocaholic.medium.com/fp64-fp32-fp16-bfloat16-tf32-and-other-members-of-the-zoo-a1ca7897d407

这里展示了不同的浮点数表示法,指数位的宽度决定能表示的数范围,尾数位的宽度决定能表示的数的精度。

  • FP32: 1 个符号位,8 个指数位,23 个尾数位,范围 ~1.18e-38 … ~3.40e38,具有 6-9 位有效小数位精度

    • 用法:
      • 神经网络计算的标准类型。神经网络中的权重、激活和其他值长期以来一直默认用 FP32 格式表示。
      • 对于许多科学计算(尤其是迭代计算),精度不够,导致错误累积。
    • 软件支持:
      • 在大多数 C/C++ 系统上表示的 float 类型;
      • 在 TensorFlow(如tf.float32)/ PyTorch(如 torch.float32torch.float)中受支持。
    • 硬件支持:
      • 通常在 x86 CPU 中受支持;
      • 通常在 NVIDIA/AMD GPU 中受支持。
  • FP16: 1 个符号位,5 个指数位,10 个尾数位,范围 ~5.96e−8 (6.10e−5) … 65504,4 位有效小数位精度。

    • 用法:

      • 深度学习有使用 FP16 而不是 FP32 的趋势,因为较低精度的计算似乎对神经网络来说并不重要。额外的精度没有任何意义,不仅速度较慢,而且会占用更多内存并降低通信速度。
      • 可用于训练,通常使用混合精度训练(TensorFlow / PyTorch)。
      • 可用于训练后量化以加快推理速度 ( TensorFlow Lite )。用于训练后量化的其他格式是整数 INT8(8 位整数)、INT4(4 位)甚至INT1(二进制值)。
    • 软件支持:

      • 目前不在 C/C++ 标准中(但有提案 short float),一些 C/C++ 系统支持 __fp16 类型。此外,可以与特殊库一起使用。
      • 在 TensorFlow(如 tf.float16)/ PyTorch(如 torch.float16torch.half)中受支持。
    • 硬件支持:

      • 在 x86 CPU 中不受支持(作为一种独特的类型)。

      • 在较旧的游戏 GPU 上支持不佳(FP32 的 1/64 性能,有关更多详细信息,请参阅有关 GPU 的帖子)。在现代 GPU 上得到很好的支持,例如 NVIDIA RTX 系列。

  • BF16: 1 个符号位,8 个指数位,7 个尾数位,BF16 格式是截断的 IEEE 754 FP32,允许与 IEEE 754 FP32 快速转换。在转换为 BF16 格式时,指数位被保留,而有效数字字段可以通过截断来减少。

    img

    范围 ~1.18e-38 … ~3.40e38,带 3 个有效小数位

    用法:

    • 似乎现在正在取代 FP16。与通常需要通过损失缩放等技术进行特殊处理的 FP16 不同,BF16 在训练和运行深度神经网络时几乎可以直接替代 FP32。

    软件支持:

    • 不在 C/C++ 标准中。可与特殊库一起使用。
    • 在 TensorFlow(如 tf.bfloat16)/ PyTorch(如 torch.bfloat16)中受支持。

    硬件支持:

    • CPU:现代 Intel Xeon x86(Cooper Lake 微架构)支持 AVX-512 BF16 扩展,ARMv8-A。
    • GPU:在 NVIDIA A100中支持(第一个支持),将在 未来的 AMD GPU 中支持。
    • ASIC:在 Google TPU v2/v3(不是 v1!)中受支持,在 Intel Nervana NNP 中受支持(现已取消)。
  • TF32:1 个符号位,8 个指数位,10 个尾数位,(好奇为啥不叫 TF19),范围 ~1.18e-38 … ~3.40e38,精度为 4 位有效小数位。TF32 使用与半精度 (FP16) 数学相同的 10 位尾数,显示出对 AI 工作负载的精度要求有足够的余量。并且 TF32 采用与 FP32 相同的 8 位指数,因此可以支持相同的数值范围。TF32 的优点是格式和 FP32 一样。使用 TF32 计算内积时,输入操作数的尾数从 23 位四舍五入为 10 位。舍入操作数精确相乘,并在普通 FP32 中累加。TF32 Tensor Cores 在 FP32 输入上运行并在 FP32 中产生结果,无需更改代码。非矩阵运算继续使用 FP32。这提供了一种在 DL 框架和 HPC 中加速 FP32 输入/输出数据的简单途径。

    用法:

    • TF32 的一大优势是编译器支持仅在最深层次上需要,即在 CUDA 编译器内部。其余代码只看到精度较低但动态范围相同的 FP32。利用 TF32 主要是调整库的调用者以指示 TF32 是否正常。TF32 作为一种可以快速插入以利用 Tensor Core 速度而无需太多工作的东西而存在。
    • FP16 和 BFLOAT16 等格式需要更多工作,因为它们涉及不同的位布局。值得努力使用这些格式,因为它们减少了内存带宽并因此允许更快的执行。(来源)

    作为比较,A100 的峰值性能是:

    • 没有张量核心的 FP32:19.5 TFLOPS
    • TF32 张量核心:156 TFLOPS(因此,使用 TF32 代替 FP32 可以轻松提高速度)。
    • FP16/BF16 张量核心:312 TFLOPS(因此,精心设计的 FP16/BF16 切换可以为你提供更多速度提升,但成本更高)。

    软件支持:

    • 不在 C/C++ 标准中。
    • 在 CUDA 11 中受支持。

    硬件支持:

    • GPU: NVIDIA A100 支持(第一个支持)。

这里分别给出一个有关 FP16 和 BF16 的数值计算方法,两者的指数偏移分别是 15 和 127。

量化误差是指输入值与其量化值之间的差,基于 K-Means 的量化方法,从存储上讲,量化前存储的是浮点类型权重,量化后是整数类型的权重和少量的浮点类型码表,而从计算角度上讲都是浮点型运算,即只是从存储角度节省了空间。

K-measns 量化示例:

>>> from sklearn.cluster import KMeans
>>> import numpy as np>>> X = np.array([[2.09], [-0.98], [1.48], [0.09], [0.05], [-0.14], [-1.08], [2.12], [-0.91], [1.92], [0], [-1.03], [1.87], [0], [1.53], [1.49]])
>>> kmeans = KMeans(n_clusters=4, random_state=0, n_init="auto").fit(X)
>>> kmeans.labels_
array([0, 1, 3, 2, 2, 2, 1, 0, 1, 0, 2, 1, 0, 2, 3, 3], dtype=int32)

上述聚类结果与图中结果一致,只是 label 做如下映射,[0, 1, 2, 3] 分别映射为图中的 [3, 0, 1, 2] 类别,图中 3 号 label 指的是浮点值 2.00

权重量化是说,我们不需要太高的精度,如 2.09, 2.12, 1.92, 1.87 等都可以简化为 2.0,无论是训练还是推理,精度有点不太重要的。例如图中绿色部分的几个权重都比较接近 -1,而粉色的接近于 1.5,这样我们就要制定合理的码表,这里仅用 4 个小的码表以及一个索引列表就可以近似表示原始权重。

从存储上讲,原来 16 个 FP32 格式的权重,占用空间 32bit x 16 = 64B, 而量化后的整数索引仅用 2 bit 即可表示,所以索引矩阵大小为 2bit x 16,再加上码表大小 4 x 32bit,整体缩小 3.2 倍小。假设参数量为 M>>2NM >> 2^NM>>2N,则整体约缩减 32/N 倍。

微调量化权重的过程如下:左图中上侧表示的是权重量化的过程,左下角是参数对应的梯度矩阵,我们将聚类模式也应用于梯度矩阵上,即相同位置的颜色与权重矩阵一致,而后根据聚类结果进行分组,即相同颜色的放在一起,进行求和,得到每个聚类小组内的梯度和,然后乘上学习率对码表进行梯度更新。

右图中显示了剪枝量化一起使用或单独剪枝或量化在不同压缩率下的精度。当单独工作时,如紫色和黄色线所示,当压缩到原始大小的 8% 以下时,剪枝网络的精度开始显著下降;当压缩到原始大小的 8% 以下时,量化网络的精度也开始显著下降。但是,如红线所示,当合并时,网络可以被压缩到原始大小的 3%,而不损失准确性。

模型在剪枝后,较小值的权重被减掉,剩下的权重分布大概成双峰状态,再经过微调后,曲线形状较为平滑,如上左图所示。量化后,值分布如右图所示。

再重新微调后,权重的值会稍微向左或向右移动一点(但是 PPT 中的图貌似一样哈,讲座视频 34:18-34:25 左右可以直观看到),它们将帮助恢复准确率。

右图中的两幅图显示了 CONV 层(左)、FC 层(右)的精度如何随着每个连接的比特数减少而降低。每个图都报告了 top1 和 top5 的精度。虚线仅应用量化,但不进行剪枝;实线同时进行量化和修剪。两者之间差别很小。这表明,剪枝+量化效果良好。

此外图中显示 CONV 层比 FC 层需要更多的精度位。对于 CONV 层,4 位以下精度显著下降,而 FC 层更稳健:直到 2 位,精度才显著下降。

霍夫曼码是通常用于无损数据压缩的最佳前缀码。它使用可变长度码字来编码源符号。该表是根据每个符号的出现概率得出的。更常见的符号用更少的比特表示。

霍夫曼树应该是大学数据结构中的基础知识,是根据每个元素出现的频率高低进行组织编码的。

例如有如下 A B C D 四个元素,其某种频率分别是 40, 10, 20, 30,构建过程,首先取两个最小元素 10 和 20,小在左,大在右,相加得到父节点 30,然后从队列中删掉 10 和 20,并将父节点 30 加入到队列,再从中去两个最小的,30 和 30,得到父节点 60,重复上述过程,再取 40 和 60 得到根节点 100,结束。那么构建的霍夫曼树结构如右图所示。

现在规定,左侧路径上都是 0,右侧路径上都是 1,则 A 的编码为 0,B 的编码为 100,C 编码为 101,D 编码为 11,这样越是频率高的,深度越浅,编码长度越短,反之,频率越低,深度越深,越需要较多的位数进行编码。利用高频短编码的优势进行数据压缩。另外这种编码可以避免前缀冲突,例如 101 是 C,则一定不存在 1 或 10 编码,即所有的父节点都是无效编码。

霍夫曼编码不需要训练,在所有微调完成后离线实现。

三级压缩 pipeline:剪枝、量化和霍夫曼编码。剪枝将权重的数量减少了 10 倍,而量化进一步提高了压缩率:在 27× 和 31× 之间。霍夫曼编码提供了更多的压缩:在 35× 和 49× 之间。压缩率已包含稀疏表示的元数据。压缩方案不会导致任何精度损失。

右图中,LeNet-300 与 LeNet-5 是基于 MNIST 数据集测试的。

这里显示了过去几年在 ImageNet 数据集上的几个流行神经网络的压缩效果,以 AlexNet 为例,原始模型大小为 240M,压缩后为 7M,top-5 的精度从 80.27% 到 80.30%,这是 2016 年的结果。

对于 VGG 来说,最初它非常笨重,550M,压缩后只有大约 11M,且精度仍然保持得很好;GoogleNet一开始就非常高效,同样对于 ResNet 来说,也只有几兆字节—这允许将模型放入 on-chip SRAM 缓存,而不是 off-chip DRAM 存储器,也同时很好的保持精度。

简单说,SRAM 小快贵,一般用作二级高速缓存,DRAM 慢一些,主要做内存。

有趣的是,无论它们一开始有多大,我都有一些压缩。因此,下一个问题基本上是,相比较与从大模型压缩到小模型而言,我们我们是否可以从一开始就发明和设计一个小型紧凑的模型呢?

几年前在与伯克利大学合作探索这个问题后,我们设计了 SqueezeNet。

我们的想法是,先使用 1×11 \\times 11×1 卷积可以将通道的数量压缩 4 倍,然后再使用 3×33 \\times 33×3 的卷积,下面有这两个分支去捕获一些信息,并进行维度扩展,最后将两个分支结果合并,在重复进入到下一层之前,需要先提高通道的数量。

左图右侧是模型网络的可视化结构图,可以看到,它非常深。

SqueezeNet 经过压缩后相较于原始 AlexNet 压缩了 510 倍,它非常小,但是精度却与 AlexNet 一致。原始训练出来的 SqueezeNet 有 4.8M,但是这样的小模型,实验表明仍可以进一步压缩,最后的模型约 0.5M,这非常令人兴奋。

在后面两节课中,我们将学习神经网络架构搜索技术(NAS),它可以自动生成或合成神经网络架构,这些架构可以满足一些,如模型大小或延迟或其他的一些约束条件等。例如你想要一个 3.2M 的模型,在以前你需要自己手动查找不同的通道数、不同的分辨率、不同的宽度或深度参数等,现在我们有自动化搜索工具可以做到这一点。

左图对基于 K-Means 空间权重量化方法的总结,在运行推理期间使用查找表(即,码本)来解码权重。也就是说,模型量化只是压缩了模型权重的存储空间,节省了存储成本,在计算过程中,仍会先对权重进行解码,以浮点数类型参与计算,计算图如图中右侧所示。

在前半部分,我们在空间中引入了 K-Means 来发挥作用,这将是你的实验室工具作业的一部分,它很容易实现,也可以量化到更低的位数,但这需要专门的硬件支持;

为了更简单,我们也有这种线性量化功能技术,让我们看看这是如何运作的。

因为这节课还有一点数学内容,所以希望能有所准备,我会慢慢地给你们讲完。

从同一个矩阵开始,我们还看这个 4×44 \\times 44×4 的矩阵,我们要做的是使用整数表示,我们仍然有一个码本,不过现在码本是线性的,用 01 表示 1、00 表示 0 等,这是数字的补码表示。

这样权重就可以用 2 位的有符号整数表示,然后我们引入零点,这是我们新引入的另一个概念,我们将很很快学会如何确定它。目前只需要有一个宏观视角,我们用这个量化后的权重,减去零点,再做缩放,现在就可以得到一个重构的权重矩阵,右下角是量化误差表。

整体看上去和 K-Means 思路差不多,只是这里的码表是线性的,(-2、-1、0、1 连续线性的)

我们有一个方程,我们想要从这个整数权重 qqq 线性投影到浮点 rrrSSS 是代表一个浮点数,是量化参数,ZZZ 是另一个量化参数,代表零点的意思,所谓的零点就是整数 ZZZ 的值经过映射后可以精确的表示浮点 rrr 中的零值。这在右图中,可以更形象的加以理解。

由于零在剪枝中的重要性,因此量化后需要确保有一个具体的整数值可以表示原始浮点中的零,右图清晰的展示了这种映射关系。右下角是一些不同 bit 宽度可以表示的范围,这里给出的是补码表示法,NNN 位二进制数补码能表示的十进制数的范围是 −2(N−1)-2^{(N-1)}2(N1)2(N−1)−12^{(N-1)}-12(N1)1

比较直观的是,我们在映射前后,应该保证 qmax⁡q_{\\max}qmax 映射到 rmax⁡r_{\\max}rmaxqmin⁡q_{\\min}qmin 映射到 rmin⁡r_{\\min}rmin,联立两个等式,我们可以求解出缩放系数 SSS

举例如下,在原始的权重矩阵中,rmax⁡r_{\\max}rmaxrmin⁡r_{\\min}rmin 分别是 2.12 和 -1.08,而量化位数为 2,其表示的范围为 -2~1,即 qmax⁡q_{\\max}qmaxqmin⁡q_{\\min}qmin 分别是 1、-2,则 SSS 计算后为 1.07,具体计算过程如图所示。

在求解得到 SSS 的值后,零点 ZZZ 的值自然也很容易求解得出,需要注意的是,ZZZ 是整数类型,这里的 round() 函数是四舍五入取整函数,例如 round(3.49)=3round(3.50)=4round(-3.49)=-3round(-3.50)=-4

作为示例,右图中的 ZZZ 值计算结果为,-0.9906,四舍五入后最终结果为 -1,即 qqq 中的 -1,可以精确的表示 rrr 中的零值。0=(−1−(−1))×1.070 = (-1-(-1)) \\times 1.070=(1(1))×1.07

我们现在考虑一个浮点类型的乘法运算 Y=WXY=WXY=WX,这里暂且忽略偏置项,我们利用上述的线性量化过程,可以将其转为整数运算:

WWW 是权重,是事先计算好的,XXX 是输入激活,是动态变化的,我们将其转为图中的形式,中间括号里的四项是 N-bit 的整数乘法,其结果间是 32-bit 的整数加减运算。蓝色框可以理解为新的缩放系数,最后一项也是一个 N-bit 的整数。

其中,黄色背底的两项是可以被事先计算出来的,因为它们不依赖于具体的输入,需要说明的是,这里的 ZXZ_XZX 只取决于我们的量化方案,它是通过输入数据集的全局统计确定的。

蓝色框中的缩放系数,根据经验,总是位于 (0,1) 区间内,因此可以正则化表示为 SWSXSY=2−nM0\\frac{S_W S_X}{S_Y}=2^{-n}M_0SYSWSX=2nM0,其中 M0∈[0.5,1)M_0 \\in [0.5, 1)M0[0.5,1),归一化乘子 M0M_0M0 现在很适合用定点乘子表示(例如,int16 或 int32,取决于硬件能力)。例如,如果使用 int32,则表示 M0M_0M0 的整数是最接近 231M02^{31}M_0231M0 的 int32 值。由于 M0>0.5M_0>0.5M00.5,该值总是至少为 2302^{30}230,因此将总是具有至少 30 位的相对精度。因此,M0M_0M0 的乘法运算可以实现为定点乘法运算。同时,乘 2−n2^{−n}2n 可以通过有效的比特移位来实现。

这一部分的详细解释参见论文 Quantization and Training of Neural Networks for Efficient Integer-Arithmetic-Only Inference 第 2.2 章节。

通常在模型训练中,权重值的分布形状大致有类似正态分布的形状,也就是 0 均值,这样的话我们可以直接令量化中的零点 ZW=0Z_W=0ZW=0

也就是左图所展示的具有对称性的量化,这样在量化前后,浮点 rrr 中的 0.0 与量化后 qqq 中的 0 是对应的。需要注意的是,N-bit 表示范围中的负数比正数多 1,因此并不是严格的对称。

因此在 N-bit 量化能够表示的 Full range 模式下,SSS 的计算公式可以很容易通过上式求得。右图中的公式计算其实依据的是 rmin⁡=S(qmin⁡−Z)r_{\\min} = S(q_{\\min} -Z)rmin=S(qminZ) 进行推导的(这个公式在视频 59:39 左右的 PPT 上有展示,在 PDF 格式的课件中看不到了)。这种模式在 Pytorch 的朴素量化方法以及 ONNX 中所使用。

同样,在 Restricted range 模式下,最左侧的 qmin⁡q_{\\min}qmin 将无效,此时量化后的区间是对称的,上述公式的推导则是依据 rmax⁡=S(qmax⁡−Z)r_{\\max}=S(q_{\\max}-Z)rmax=S(qmaxZ) 推导的(同样,该公式在视频 59:45 左右的 PPT 上有展示,在 PDF 格式的课件中看不到了)。这种量化模式被 TF、TensorRT 以及 Intel DNNL 中所使用。

针对上面提到的非对称以及对称线性量化,其各自的优缺点如下

对于非对称线性量化:

  • 量化范围得到充分利用
  • 实现更加复杂,零点需要硬件中的额外逻辑

对于对称线性量化:

  • 量化的范围将被浪费在有偏的浮点范围上。
    • 例如,ReLU 之后激活张量是非负的,因此对称量化将损失 1 个有效位。
  • 实现要简单得多。

现在重新看一下上面提到的矩阵乘法,当 ZW=0Z_W=0ZW=0 后,我们可以进一步简化为图中的形式。

前面我们把偏置项 bbb 去掉了,现在我们将其也考虑进来,同样令 ZW=0Z_W=0ZW=0,我们有如上的简化结果。

为了进一步简化,我们此处可以再次令 Zb=0Z_b=0Zb=0,同时,为了方便合并等式右侧两项,我们再令 Sb=SWSXS_b=S_WS_XSb=SWSX(这么做一是计算方便,二是实验结果确实不错),因此得到上图的简化结果。

中间图这里是做了一步总结,我们有 3 个假设,分别是 ZW=0,Zb=0,Sb=SWSXZ_W=0, Z_b=0, S_b=S_WS_XZW=0,Zb=0,Sb=SWSX,然后进行合并,其中两项与输出 XXX 无关,可以预计算,并将其合并为一项 qbiasq_{\\text{bias}}qbias,最终得到图中所示的形式。

因此,对于全连接层,其计算过程如右图所示,其整个过程都是整数运算。

而对于卷积层,形式上与全连接层一样,仅仅把矩阵乘法部分替换为卷积操作,如左图所示。右侧的计算图展示了整个卷积层量化计算的过程,其整个过程也都是整数运算。

右图显示了使用浮点与 int8 类型相比,延迟是多少与准确率是多少,在相同的精度下,int8 的延迟要短 20ms 左右,但整体上最终的准确率 int8 要比浮点类型低一些,不过这是 2018 年的技术成果,现在我们有更先进的技术可以进行 Post-Training Quantization (PTQ),并通过 Quantization-Aware Training (QAT) 进行微调,QAT 将在下一次讲座中介绍,可以弥补差距恢复精度。

这是本次讲座的总结,首先介绍了现代计算系统中的数值类型,包括整数、浮点数表示等;其次介绍了量化的基本概念,并重点介绍了基于 K-Means 和 线性的量化方法。右图是本次讲座中涉及到的参考文献。

成语解释