本文主要是神经网络量化的基础知识,大部分内容是翻译,加粗部分关注一下。
研究问题及现有研究和本文结构
随着深度学习作为一种将智能注入电子设备的通用工具越来越受到大家的欢迎,小型化、低能耗、低延迟的神经网络解决方案必要性逐渐增加。如今神经网络可以再许多电子设备和服务中找到,从智能手机、智能眼镜和家用电器到无人机、机器人、自动驾驶。这些设备通常对神经网络的执行过程有着严格的时间限制或者在长期执行时对功耗有则严苛的要求。
减少神经网络计算时间和能耗的最有效方法之一就是量化。 在神经网络量化过程中,权重和激活通常被保存为低bit精度而不是训练时的16bit或者32bit。从32bit到8bit,存储消耗降低为原来的1/4,矩阵乘法的消耗则降低为原来的1/16。
神经网络已经被证明对量化有着比较好的鲁棒性,因此它可以被量化到比较低的位宽上,但是对网络的精度影响却不大。此外,神经网络量化通常还可以和一些常见的网络优化方法一起使用,例如神经网络结构搜索(NAS)、压缩(compression)、剪枝(pruning)等。在任何深度学习实例中,这都是一个提升模型效率的重要步骤。然而神经网络量化并不是没有代价的,更低的量化位宽可能给网络带来噪声,从而导致模型的精度下降。一部分网络模型对这部分噪声足够鲁棒,而有些网络模型则需要采用一些其它方案来规避这部分噪声,从而使得模型可以被量化。
在这篇文章中作者介绍了神经网络量化SOTA算法:
在本节中,作者介绍了神经网络量化的基本原理以及运行量化网络的定点加速器。这一节从硬件背景开始,然后介绍标准量化方案及其属性。 又讨论了与现代神经网络中常见的层相关的实际因素及其对定点加速器的影响。
在深入了解技术细节之前,首先探索量化的硬件背景以及它如何在设备上实现高效推理。
下图展现了一个矩阵-向量如何在神经网络加速器中相乘 y = w x + b y=wx+b y=wx+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}=w_{n,m} · x_m$
A n = b n + ∑ i = 1 4 C n , i , b n 为偏置bias。 A_{n}=b_{n}+\sum_{i=1}^{4} C_{n, i}, \quad b_{n} \text { 为偏置bias。 } An=bn+∑i=14Cn,i,bn 为偏置bias。
经过不断重复这样的计算步骤,就完成了矩阵之间的乘法。一旦所有输入元素计算完成,累加器中的值 A n A_n An就会被移回内存以用于下一个神经网络层的输入。
一般神经网络的训练都是用32位的浮点数表示权重和激活值。
如果我们要使用32位的浮点数执行推理,处理元件和累加器必须支持浮点逻辑,并且我们需要将 32 位数据从内存传输到处理单元。图1中的计算流程和数据传输消耗了神经网络推理过程中花费的大部分能量。
因此,可以通过使用较低位的定点或量化表示来实现。低位定点表示(比如 INT 8)不仅降低了大量的数据传输,也降低了图1中操作的内存和能量消耗,因为数字算术的成本通常与使用的位数成二次线性关系,并且定点加法比浮点加法更有效。
为了从浮点运算转移到高效的定点运算,我们需要一个将浮点向量转换成整数的方法。浮点向量x可以近似表示为标量乘以整数值向量:
基于上面的公式,量化权重和激活值,可以写出累加器的量化版本:
注意:对于权重w和激活值x近似时采取了不同的常量S_w和S_x。
这种做法比较灵活,且能够降低量化损失。在量化时,对于每个张量内的所有元素采用相同的近似常量,因此可以将 S w 和 S x S_w和S_x Sw和Sx拿到 ∑ \sum_{} ∑之外。现在忽略掉偏差量化,因为偏差通常存储在更高的位宽(32位)中,其比例因子取决于权重和激活的比例因子。
图2显示了当我们引入量化时,神经网络加速器是如何变化的。
在本文的例子中,使用INT8算法,但为了便于讨论,这可以是任何量化格式。累加器保持较高的位宽很重要,通常为32位宽。否则,由于在计算过程中累积了更多的乘积,我们可能会因溢出而遭受损失。
存储在32位累加器中的激活需要被写入存储器,然后才能被下一层使用。为了减少数据传输和下一层操作的复杂性,这些激活被量化回INT8。这需要一个重新量化步骤,如图2所示。
在这一节中,我们定义了将在本文中使用的量化方案。这种方案被称为均匀量化,它是最常用的量化方案,因为它允许定点运算的有效实现。
均匀仿射量化,也称为不对称量化,由三个量化参数定义: 比例因子s、零点z和位宽b。
一旦定义了三个量化参数,我们就可以继续进行量化操作。
通过反量化步骤可以定义量化范围为 ( q m i n , q m a x ) , q m i n = − s z , q m a x = s ( 2 b − 1 − z ) (q_{min},q_{max}),q_{min} = -sz,q_{max} = s(2^b-1-z) (qmin,qmax),qmin=−sz,qmax=s(2b−1−z)。
超出范围的任何x值都被剪裁到极限值,这样会带来剪裁错误(精度误差);如果要降低精度误差,可以扩大比例因子s,但是又会带来四舍五入的误差。
在后边会介绍如何选择量化参数,在裁剪误差和四舍五入误差之间做权衡。
对称量化是不对称情况的简化版本。 对称量化器将零点z 限制为 0。这减少了在不对称量化中的累加操作期间处理零点偏移的计算开销。 但是偏移量的缺乏限制了整数域和浮点域之间的映射。 因此,有符号或无符号整型网格的选择很重要:
无符号型的对称量化很适合单尾分布的数据,比如RELU的激活值。而有符号型的对称量化适合用于关于零大致对称的数据。
均匀量化的三种形式(非对称均匀量化,有符号型对称量化,无符号型对称量化)如图3所示:
2的幂次方量化是对称量化的一种特殊情况,其中比例因子限制为2的幂次方, s = 2 − k s = 2^{-k} s=2−k,这种选择可以提高硬件效率,因为s的比例对应于简单的位移位。然而,比例因子的受限表达会使舍入和削波误差之间的权衡变得复杂。
到目前为止,我们已经为每个张量定义了一组量化参数(量化器),一个用于权重,一个用于激活。这称为 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) 模拟通用浮点硬件的量化推理。
批量归一化是现代卷积网络的标准组件。批量归一化在缩放和添加偏移之前对线性层的输出进行归一化。(见公式)对于设备上的推理,这些操作在称为批量归一化折叠的步骤中折叠到上一个或下一个线性层中。 这完全从网络中删除了批量标准化操作,因为计算被吸收到相邻的线性层中。 除了减少额外缩放和偏移的计算开销之外,还可以防止额外的数据移动和层输出的量化。更正式地说,在推理过程中,批量归一化被定义为输出 x 的仿射映射:
其中,μ和σ是在批量统计上训练期间计算的指数移动平均的均值和方差,而γ和β是每通道学习的仿射超参数。。如果在线性层 y = BatchNorm(Wx) 之后应用批归一化,我们可以重写这些项,使批归一化操作与线性层本身融合。假设权重矩阵 W ∈ R n × m W∈R^{n×m} W∈Rn×m,对于k ={1,…,n} 我们对每个输出 y k y_k yk应用批处理归一化。
其中:
在第2.1节介绍的原始量子化加速器中,我们看到在计算矩阵乘法或卷积输出值之后,激活的重新量化发生了**。然而,在实际操作中,我们往往有一个非线性的直接跟随线性的操作。**将线性层的激活写入内存,然后将它们加载回计算核心以应用非线性是一种浪费。由于这个原因,许多硬件解决方案都附带一个硬件单元,在requantialization步骤之前应用非线性。如果是这种情况,我们只需要模拟非线性之后发生的requantialization。例如,requantialization块很容易对ReLU非线性进行建模,因为您可以将激活量化的最小可表示值设置为0。
其他更复杂的激活函数,如sigmoid或Swish需要更专门的支持。如果没有这种支持,我们需要在图中的非线性前后添加量化步骤。这对量化模型的准确性有很大的影响。虽然像Swish函数这样的更新激活可以提高浮点精度,但在量化之后这些功能可能会消失,或者在定点硬件上部署的效率可能会较低。
在神经网络中还有许多其他类型的层。如何建模这些模型很大程度上取决于具体的硬件实现。有时,模拟量化和目标性能之间的不匹配是由于层没有被适当量化。在这里,我们提供了一些关于如何模拟几个常用层的量化的指导:
Max Pooling: 不需要对激活值进行量化,因为输入和输出在同一个的量化网格上;
Average Pooling: 整数的平均数不一定是整数。因此,Average Pooling(平均池化)之后需要一个量化步骤。然而,我们对输入和输出使用相同的量化器,因此量化范围没有显著变化。
Element-wise addition: 尽管这一步骤很简单,但是很难能够正确地模拟。在相加的过程中,两个输入的加数的量化范围必须完全匹配(两数相加可能会超出量化范围)。如果不匹配,需要格外小心才能按预期进行其余的工作。对此,没有单一公认的解决方案,但添加requantialization(重新量化)步骤可以粗略地模拟添加的噪声。另一种方法是通过绑定输入的量化网格来优化网络。这将避免重新量化步骤,但可能需要进行微调。
Concatenation: 被连接的两个分支通常不共享相同的量化参数。这意味着它们的量化网格可能不会重叠,因此需要requantization (重新量化)步骤。与Element-wise addition一样,可以优化您的网络,以便为连接的分支共享量化参数。
在对多层神经网络进行量化时,我们面临着很大的量化选择空间,包括量化方案、粒度和比特宽度。在本节将探讨一些有助于减少搜索空间的实际考虑事项。
注意,在本白皮书中只考虑同构位宽。这意味着为权重或激活选择的位宽在所有层上保持不变。 同构位宽更普遍地由硬件支持,但最近的一些工作也探索了异构位宽或混合精度的实现(van Baalen et al., 2020; Dong et al., 2019; Uhlich et al.,2020)。
对于每个权重和激活量化,量化方案分为对称量化和非对称量化。
要了解为什么会出现这种情况,请考虑当权重不对称时会发生什么。下边分别是当前层量化后的权重和上一层的量化后的激活值。如下所示,它们要进行相乘得到当前层的计算结果。
如果两个操作都是对称的,第一项就是我们将拥有的(因为没有零点和偏置)。第三项和第四项仅取决于预先知道的比例、偏移量和重量值。因此,这两项可以预先计算并添加到层的偏置项中,几乎不需要任何成本。然而,第二项取决于输入数据 x。这意味着对于每批数据,我们需要在推理期间计算一个附加项。这可能会导致延迟和功率的显着开销,因为它相当于添加一个额外的通道。
出于这个原因,使用非对称激活量化和对称权重量化是一种常见的方法,可以避免额外的数据相关项。
在量化细粒度那一节,作者讨论了不同级别的量化粒度。权重和激活的 Per-tensor 量化已经成为标准有一段时间了,因为它受到所有定点加速器的支持。
然而,权重的 per-channel 量化可以提高准确性,特别是当权重的分布因通道而异时。 在加速器中可以通过应用单独的 per-channel 权重比例因子来实现 per-channel 权重量化 ,而无需重新缩放。激活的 per-channel 量化更难实现,因为我们无法将比例因子从求和中分解出来 ,因此需要为每个输入通道重新调整累加器。尽管权重的每通道量化越来越普遍,但并非所有商业硬件都支持它。因此,重要的是检查它是否可以在您的预期目标设备中使用。