2021 Google模型量化白皮书
最近开始学习模型量化的知识,找到了一篇入门版的论文A White Paper on Neural Network Quantization.
在学习的过程中做一些记录加深理解,防止遗忘(记性不好让人头大,担心自己老年痴呆),并添加一些自己的理解。如果可以帮到大家,是我的荣幸。
摘要
当前神经网络在许多应用中都取得了进展,但它也总是带来很高的计算消耗。如果我们想要将神经网络部署在具有严格的功率和计算要求的边缘设备上,降低神经网络推理时的功率和延迟是关键。模型量化是达到这些要求的最有效的方法之一,但是模型量化过程中引入的噪声也会带来准确率的下降。
在这篇论文中,作者介绍了当前效果最好的模型量化算法,它们在保持低位权重和激活值的同时尽量消除量化噪声对模型性能影响。文章首先从硬件背景介绍模型量化,之后主要讨论两类主流的量化算法:训练后量化 (PTQ) 和量化感知训练 (QAT)。PTQ不需要预训练和标注的数据,因此,它是一种轻量级的按键式的量化方法。在大多数情况下,PTQ可以达到8比特量化,同时精度接近浮点型。QAT需要微调和标注数据,但是可以完成更低位的量化,取得更有竞争力的结果。对于这两种方案,作者基于已有的文献和扩展实验提供了测试好的pipelines,这些pipelines对于常见的深度学习模型和任务达到了SOTA的性能。
个人理解:为了追求性能,神经网络模型的体量不断增大,这给在边缘设备上部署模型造成了困难。因此需要一些技术使得模型轻量化,加快模型的推理速度和功率消耗。模型量化就是一种非常有效的模型轻量化算法。它主要通过对模型内的权重和激活值参数进行类型转化(比如浮点型转低位整型,或者混合精度等)来加快模型推理速度。但是,这种粗略的量化方式会引入噪声使得模型精度有所下降。主流的模型量化算法主要分为PTQ和QAT两种。PTQ是在模型参数训练好之后进行精度转换,这种算法可以解决大多数问题。QAT是在训练过程中进行量化,步骤要繁琐一些,需要微调和标注数据,但是其精度要好于PTQ。
引言
随着深度学习越来越作为一种通用方案用来给电子设备赋予智能属性,体量小、低延迟并且性能好的神经网络方案成为一种发展趋势。如今,在很多电子产品和服务中都可以看到神经网络的身影,比如智能手机、智能眼镜、智能家居、机器人、自动驾驶等。这些设备通常要求神经网络在执行中遵守严格的时间限制,并且为了长续航性能需要降低推理的功耗。
模型量化,是降低计算时间和能量消耗的最佳方法之一。模型训练的时候,权重和激活张量通常以16或32位精度的数据类型存储,而模型量化将权重和激活的张量存储在低精度的张量中。当把权重和激活张量从32位转为8位后,存储张量的内存空间缩小4倍,同时矩阵乘法的计算消耗降低14倍。神经网络已被证实,模型量化到较为低位的带宽之后,精度所受到的影响在可接受的范围之内。除此之外,模型量化还经常和其他模型优化的方法一同使用,比如网络架构搜索,模型压缩,模型剪枝等。所以模型量化是深度学习实际落地应用的一个核心步骤。但它也有缺点:低位宽量化会引入噪声到模型中,导致精度下降。尽管一些网络对噪声具有抗干扰性,但是其他网络还需要额外的工作去最大化量化的好处。
在这篇论文中,作者介绍了SOTA的模型量化算法。他们首先介绍量化,并讨论了硬件背景和实际应用条件。然后分别讨论两种主流量化算法:PTQ和QAT。PTQ处理的是已经训练好的网络,使用少量数据或者不需要使用数据进行量化,它需要较少的超参数进行调整,并且不用端到端的训练。这使得PTQ不需要太多工程量和计算消耗,并且可以看作一种push-button方法(按钮启动方法,我理解的是在模型训练的时候不需要考虑,模型训练好之后如果需要PTQ就设置添加PTQ,如果不需要就不用添加,类似于想用的时候只需要按一下即可)。QAT依赖于在训练pipeline中使用模拟量化重新训练神经网络。虽然这需要在训练和潜在的超参数调整方面付出更多努力,但与低位量化的 PTQ 相比,它通常会进一步缩小与全精度精度的差距。对于这两种方案,作者引入了基于现有文献和广泛实验的标准pipelines,从而为常见的计算机视觉和自然语言处理模型带来了最先进的性能。 我们还提出了一个调试工作流程来识别和解决量化新模型时的常见问题。
模型量化的理论基础
在本节中,作者介绍了神经网络量化的基本原理以及运行量化网络的定点加速器。这一节从硬件背景开始,然后介绍标准量化方案及其属性。 稍后又讨论了与现代神经网络中常见的层相关的实际考虑因素及其对定点加速器的影响。
硬件背景
在深入了解技术细节之前,首先探索量化的硬件背景以及它如何在设备上实现高效推理。图1展示了一个矩阵-向量如何在神经网络加速器(硬件模块)中相乘 y = ω x + b y = {\omega}x + b y=ωx+b 的机制图。这是大型矩阵之间相乘和卷积操作的基础模块。这样的硬件模块旨在通过并行计算提升神经网络的推理速度。图1中神经网络加速器的两个基本元素:处理元件 C n , m C_{n,m} Cn,m和累加器 A n A_n An。
其中 C n , m C_{n,m} Cn,m执行的是乘法操作( C n , m C_{n,m} Cn,m里边, ω n , m 和 x m 相 乘 {\omega_{n,m}}和x_m相乘 ωn,m和xm相乘),
A n = b n + ∑ i = 1 4 C n , i , b n 为 偏 置 b i a s A_n=b_n + {\sum}_{i=1}^{4}C_{n,i}, b_n为偏置bias An=bn+∑i=14Cn,i, bn为偏置bias 。
经过不断重复这样的计算步骤,就完成了矩阵之间的乘法。一旦所有输入元素计算完成,累加器中的值 A n A_n An就会被移回内存以用于下一个神经网络层的输入。
一般神经网络的训练都是用32位的浮点数表示权重和激活值。如果我们要使用32位的浮点数执行推理,处理元件
C
n
,
m
C_{n,m}
Cn,m和累加器
A
n
A_n
An必须支持浮点逻辑,并且我们需要将 32 位数据从内存传输到处理单元
C
n
,
m
C_{n,m}
Cn,m。 图1中的计算流程和数据传输消耗了神经网络推理过程中花费的大部分能量。 因此,可以通过使用较低位的定点或量化表示来实现。低位定点表示(比如 INT 8)不仅降低了大量的数据传输,也降低了图1中操作的内存和能量消耗。因为数字算术的成本通常与使用的位数成二次线性关系,并且定点加法比浮点加法更有效。
图1. 神经网络加速器硬件中的矩阵-向量相乘运算的机制图
为了从浮点型运算转为更高效的定点运算,我们需要一个将浮点型向量转换为整型向量的机制:一个浮点型向量
X
X
X能够被粗略地表示成一个常量诚意一个整型向量。
基于上面公式中表示的转换机制,累加器
A
n
A_n
An的计算过程可近似为:
这里值得注意的一点是,对于权重w和激活值x近似时采取了不同的常量
S
w
S_w
Sw和
S
x
S_x
Sx。这种做法较为灵活,且能够降低量化损失。在量化的时候,我们之所以可以把
S
w
S_w
Sw和
S
x
S_x
Sx拿到
∑
\sum
∑之外,是因为对于每个张量里的所有元素采用相同的近似常量。 我们现在有意忽略偏差量化,因为偏差通常以更高的位宽(32 位)存储,其比例因子取决于权重和激活的比例因子。
图2中展示了当引入量化后,神经网络加速器发生了什么改变。在这里以 INT 8类型进行举例。对于累加器,保持一个更高的位宽是重要的,通常在32位宽。否则,随着在计算过程中相加的元素变多,我们可能会因溢出的风险而遭受损失。
储存在32位累加器中的激活值在用作下一层的输入之前,需要将它们写入内存。为了降低读写内存时数据传输量和下一层的计算量,这些激活值需要被重新量化回 INT8类型(这时就需要一个requantization步骤,看图2)。
图2. 用于量化推理的神经网络加速器中的矩阵乘法逻辑示意图。
均匀仿射量化
在本节中,作者定义了他们将在论文中使用的量化方案。 这种方案称为均匀量化,它是最常用的量化方案,因为它允许有效地实现定点算术。
均匀仿射量化,也称为非对称量化,由三个量化参数定义:比例因子 s、零点 z 和位宽 b。
- 比例因子和零点用于将浮点值映射到整数网格,其大小取决于位宽。
- 比例因子 s 通常表示为浮点数,并指定量化器的步长;
- 零点 z 是一个整数,可确保对实零进行无误差量化。这对于确保零填充或 ReLU 等常见操作不会引起量化误差很重要。
看到上边对三个量化参数的描述可能会有些不太明白,我们慢慢往下看。
一旦三个量化参数定义好之后,便可以执行量化操作了。
首先按照下边的公式将一个原始的权重或激活值向量x映射到无符号的整型网格中{0, … ,
2
b
−
1
2^b-1
2b−1},其中b是位宽。
表示四舍五入操作,clamp(x;a,c)函数定义如下:
相反地,为了从量化后的数据中恢复出真实的数据x,我们需要反量化操作:
将量化步骤和反量化步骤结合在一起,得到通用的量化函数
q
(
.
)
q(.)
q(.):
通过反量化步骤,我们也可以定义量化范围(
q
m
i
n
,
q
m
a
x
q_{min}, q_{max}
qmin,qmax),其中
q
m
i
n
=
−
s
z
q_{min}=-sz
qmin=−sz,
q
m
a
x
=
s
(
2
b
−
1
−
z
)
q_{max}=s(2^b-1-z)
qmax=s(2b−1−z)。超出此范围的任何 x 值都将被剪裁到其极限,从而导致剪裁错误(精度上的误差)。如果想要降低裁剪误差,可以通过增大比例因子 s 扩大量化范围。然而,增大比例因子的同时会增大四舍五入的误差,因为四舍五入的误差在范围
[
−
1
2
s
,
1
2
s
]
[-\frac{1}{2}s, \frac{1}{2}s]
[−21s,21s]。在后边会介绍如何选择量化参数,在裁剪误差和四舍五入误差之间做trade-off。
对称均匀量化
对称量化是不对称情况的简化版本。 对称量化器将零点限制为 0。这减少了在不对称量化中的累加操作期间处理零点偏移的计算开销。 但是偏移量的缺乏限制了整数域和浮点域之间的映射。 因此,有符号或无符号整数网格的选择很重要:
无符号型的对称量化很适合单尾分布的数据,比如RELU的激活值。而有符号型的对称量化适合用于关于零大致对称的数据。
均匀量化的三种形式(非对称均匀量化,有符号型对称量化,无符号型对称量化)如图3所示。
图3. 位宽为 8 的不同均匀量化网格的直观解释。s 是比例因子,z 是零点。 浮点网格为黑色,整数量化网格为蓝色。
2的幂次方量化
2的幂次方量化是对称量化的一种特殊情况,比例因子 s 被限制为2的幂次方,即 s = 2 − k s=2^{-k} s=2−k。这种方式在硬件上可以表现的很高效,因为这样缩放相当于简单的位移操作。然而,这种比例因子的有限表达能力会使舍入误差和裁剪误差之间的权衡变得复杂。
量化粒度
到目前为止,我们已经为每个张量定义了一组量化参数(量化器),一个用于权重,一个用于激活。这称为 per-tensor 量化。我们还可以为张量的各个维度(例如,权重张量的输出通道)定义单独的量化器,从而增加量化粒度。在神经网络量化中, per-tensor 量化是最常见的粒度选择,因为它的硬件实现更简单:图2中所有累加器都使用相同的比例因子 S w , S x S_w, S_x Sw,Sx。但是,我们可以使用更精细的粒度来进一步提高性能。例如,对于权重张量,我们可以为每个输出通道指定不同的量化器。这称为per-channel量化。
还有一些其他的工作不止于为每个输出通道指定不同的量化器,而是为每组权重或激活应用单独的量化器。但是,增加组的粒度通常会以一些额外开销为代价来提高准确性。开销与处理具有不同比例因子的值之和的累加器相关。大多数现有的定点加速器目前不支持这种逻辑,因此,我们不会在这项工作中考虑它们。然而,随着该领域研究的增长,预计未来会有更多对这些方法的硬件支持。
量化模拟
为了测试神经网络在量化设备上的运行情况,我们经常在用于训练神经网络的相同通用硬件上模拟量化行为。这就叫做 quantization simulation (量化模拟)。 我们旨在使用浮点型的硬件估计定点运算。相比较于在真正的量化硬件或者使用量化过的核跑实验,模拟量化更好实现。这允许用户高效地测试不同的量化策略,并且对于QAT还可以使能GPU加速。在这一节,作者首先解释了量化模拟步骤的基本原理,然后讨论能够帮助减少模拟量化核在真实设备上运行之间的差异的技术。
之前,我们了解了在定点运算型的设备上,矩阵-向量相乘是怎么计算的。在图4a中,作者将这种运算过程推广到卷积层,而且还包括一个激活函数以使其更真实。在设备上推理期间,硬件的所有输入(偏差、权重和输入激活)都是定点格式。 然而,当我们使用常见的深度学习框架和通用硬件模拟量化时,这些量是浮点数。 这就是我们在计算图中引入量化模块以诱导量化效果的原因。
图 4b 显示了如何在深度学习框架中对相同的卷积层进行建模。在权重和卷积之间添加量化块以模拟权重量化,并在激活函数之后添加以模拟激活量化。 偏差通常不会被量化,因为它以更高的精度存储。在前边的小节中,作者更详细地讨论了何时将量化模块置于非线性之后是合适的。 量化模块实现前边列出的的量化函数,每个量化模块由一组量化参数(比例因子、零点、位宽)定义。量化模块的输入和输出都是浮点格式,但输出位于量化网格上。
图4. 卷积层量化前向传递的示意图:a) 计算实际设备上量化推理的图。 b) 模拟通用浮点硬件的量化推理。
Batch normalization folding
批量归一化是现代卷积网络的标准组件。批量归一化在缩放和添加偏移之前对线性层的输出进行归一化。对于设备上的推理,这些操作在称为批量归一化折叠的步骤中折叠到上一个或下一个线性层中。 这完全从网络中删除了批量标准化操作,因为计算被吸收到相邻的线性层中。 除了减少额外缩放和偏移的计算开销之外,这还可以防止额外的数据移动和层输出的量化。更正式地说,在推理过程中,批量归一化被定义为输出 x 的仿射图:
B
a
t
c
h
N
o
r
m
(
x
)
=
γ
(
x
−
μ
σ
2
+
ϵ
)
+
β
BatchNorm(x)={\gamma}({\frac{x-{\mu}}{\sqrt{{\sigma}^2}+{\epsilon}}})+{\beta}
BatchNorm(x)=γ(σ2+ϵx−μ)+β
其中
μ
{\mu}
μ和
σ
{\sigma}
σ为均值和方差,是训练过程中以batch为统计单位计算结果的指数移动平均数。
γ
{\gamma}
γ和
β
{\beta}
β是从每个通道中学习到的仿射超参数。如果像上边说的,在线性层 y= BatchNorm(Wx) 之后立即应用批量归一化,我们可以重写这些公式,使批量归一化操作与线性层本身融合。假定一个权重矩阵
W
ϵ
R
n
×
m
W{\epsilon}~R^{n×m}
Wϵ Rn×m,对输出
Y
Y
Y的每一个通道
y
k
y_k
yk(k={1, 2, …, n})应用批量归一化。
其中,
通过上述变换,就将批量归一化层和线性层融合在了一起。
Activation function fusing
在图2中介绍的简单量化加速器中,我们看到激活的反量化发生在计算矩阵乘法或卷积输出值之后。然而,实际上我们经常在线性运算之后直接出现非线性(图4中的情况)。因为将线性层的激活写入内存,然后将它们重新加载到计算核心应用非线性运算会很浪费资源。出于这个原因,许多硬件解决方案都带有一个硬件单元,在重新量化步骤之前应用非线性。 如果是这种情况,我们只需要模拟在非线性之后发生的重新量化。 例如 ReLU 非线性很容易由重新量化块建模,因为您可以设置它的最小可表示值激活量化为 0。
其他更复杂的激活函数,例如 sigmoid 或 Swish,需要更专门的支持。如果这种支持不可用,我们需要在图中的非线性前后添加一个量化步骤。这会对量化模型的准确性产生很大影响 尽管像 Swish 函数这样的新激活可以提高浮点精度,但这些激活在量化后可能会消失,或者在定点硬件上部署的效率可能较低。
其他网络层及其量化
在神经网络中还有很多其它经常用到的层。它们如何被建模主要取决于具体的硬件实施。有时,模拟量化和目标性能之间的不匹配归结为未正确量化的层。 在这里,我们提供了一些关于如何模拟几个常用层的量化的指导,帮助这些层被正确量化,并使得模拟量化能够匹配目标性能。
- Max Pooling:不需要对激活值进行量化,因为输入和输出在相同的量化网格上;
- Average Pooling:几个整数的平均数不一定是整数。出于这个原因,Average Pooling之后需要一个量化步骤。然而,因为量化范围没有显著变化,可以对输入和输出采用相同的量化模块;
- Element-wise addition:尽管这一步骤很简单,但是很难能够正确地模拟。在相加的过程中,两个输入的加数的量化范围必须完全匹配。如果不匹配,需要格外小心才能按预期进行其余的工作。我理解的是因为两个数相加可能会超出量化范围。 对此,没有单一公认的解决方案,但添加重新量化步骤可以粗略地模拟添加的噪声。另一种方法是通过绑定输入的量化网格来优化网络。这将阻止重新量化步骤,但可能需要微调。
- Concatenation:被连接的两个分支通常不共享相同的量化参数。这意味着它们的量化网格可能不会重叠,因此需要重新量化步骤。与Element-wise addition一样,可以优化您的网络,以便为连接的分支共享量化参数。
Practical considerations
在对多层神经网络进行量化时,我们面临着大量的量化选择空间,包括量化方案、粒度和位宽。 在本节中,作者探讨了一些有助于减少搜索空间的实际考虑因素。
请注意,在本白皮书中,作者仅考虑同质位宽。这意味着为权重或激活选择的位宽在所有层中保持不变。因为硬件更普遍地支持同构位宽,但最近的一些工作也探索了异构位宽或混合精度的实现。
Symmetric vs. asymmetric quantization
对于每个权重和激活量化,我们必须选择一个量化方案。一方面,非对称量化更具表现力,因为有一个额外的偏移参数,但另一方面可能存在计算开销。要了解为什么会出现这种情况,请考虑当权重不对称时会发生什么。
下边分别是当前层量化后的权重和上一层的量化后的激活值。如下所示,它们要进行相乘得到当前层的计算结果。
如果两个操作都是对称格式,第一项就是我们将拥有的(因为没有零点和偏置)。第三项和第四项仅取决于预先知道的比例、偏移量和重量值。因此,这两项可以预先计算并添加到层的偏置项中,几乎不需要任何成本 然而,第二项取决于输入数据 x。这意味着对于每批数据,我们需要在推理期间计算一个附加项。这可能会导致延迟和功率的显着开销,因为它相当于添加一个额外的通道。出于这个原因,使用非对称激活量化和对称权重量化是一种常见的方法,可以避免额外的数据相关项。
Per-tensor and per-channel quantization
在量化细粒度那一节,作者讨论了不同级别的量化粒度。权重和激活的 Per-tensor 量化已经成为标准有一段时间了,因为它受到所有定点加速器的支持。然而,权重的 per-channel 量化可以提高准确性,特别是当权重的分布因通道而异时。在加速器中可以通过应用单独的 per-channel 权重比例因子来实现 per-channel 权重量化,而无需重新缩放。激活的 per-channel 量化更难实现,因为我们无法将比例因子从求和中分解出来,因此需要为每个输入通道重新调整累加器。尽管权重的每通道量化越来越普遍,但并非所有商业硬件都支持它。因此,重要的是检查它是否可以在您的预期目标设备中使用。