量化背景资料(chaos)#

模型量化本质上是一种压缩模型大小和减少运算量的技术,其基本原理是将模型中的浮点数参数和激活值转换成定点数或者低于浮点数的数据表示形式,从而大大减少存储空间和计算复杂度。数学上,模型量化可以理解为将高维空间中的浮点数数据表示映射到低维空间中的整数数据表示。

一般来说,模型量化可以分为 线性量化非线性量化 两种方式。

线性量化

将数据按照固定的间隔进行量化,如将数轴等分成 \(n\) 个小区间,将数据映射到对应的整数区间中。如果某个数据不恰好落在某个整数间隔的中点,就要接近距离它最近的整数。线性量化适用于数据分布比较均匀的情况。

非线性量化

根据数据的实际分布,将其映射到低维整数空间中。比如,如果数据主要集中在某个区域,就可以将该区域分成更多的小间隔,将数据映射到这些小区间内,以提高数据表达的精度。

总之,模型量化是通过牺牲精度来换取更高的运行效率和更小的模型尺寸。量化的难点在于,在牺牲精度的同时,如何最大程度地保留模型的特征和语义信息。因此,在进行模型量化时需要选择合适的量化策略和参数,以达到最优的压缩效果和准确度。

量化是什么?#

参考:quantization-and-resolution

备注

量化 是一种实现信号调制的方法,通过将输入值从无限长的连续值集合映射到更小的有限值集合。量化是有损压缩算法,它将给定的模拟信号表示为数字信号。换句话说,这些算法构成了模数转换器(analog-to-digital converter)的基础。处理量化算法的设备称为 量化器 (quantizer)。这些设备有助于对输入函数(即量化值)的误差进行四舍五入(近似)。

量化器#

量化器 是一种负责将采样的输入信号改变为具有某些预定电压水平的信号的装置。量化器执行的量化级别取决于编码器的比特值。

优点

  • 使用量化器,信号表示所需的比特数大大减少。

  • 量化器降低了比特率(bitrate),进而降低了带宽需求。

然而,量化器可能导致某些缺点,如在近似或四舍五入期间在原始信号中引入某些误差,称为 量化噪声

在数字电子学和电子仪器中,测量波形中两点之间的振幅距离称为 分辨率。分辨率主要与数模转换器有关。一般在测量信号的最大值与可解析部分之间。分辨率是指仪器在理论上检测变化的能力,用比特数表示。

线性量化#

假设深度学习模型中的权重 \(w \in \mathbb{R}\),希望将其压缩至 \(n\) 个位数(例如,8位)以便于在低端硬件上执行。可以使用均匀量化将其压缩为 \(q \in Q\),其中 \(Q = {q_0, q_1, \cdots, q_{2^n - 1}}\)\(2^n\) 个量化级别的集合。假定量化级别之间的间隔为 \(s\),则 \(s\) 可以由量化范围 \(r\) 的大小和量化级别数 \(2^n\) 得出:

\(s = r / (2^n - 1)\)

通过这种方式进行均匀量化,我们可以将 \(w\) 量化为最接近的量化级别 \(q\)

\(q = \operatorname{round}(w / s) * s\)

这里的 \(\operatorname{round}(x)\) 表示将 \(x\) 四舍五入到最接近的整数。当 \(w\) 落在两个量化级别之间时,四舍五入机制可以保证 \(q\) 是最接近的量化级别。

备注

量化水平

量化过程中使用的量化级别数。在深度学习中,一般使用的量化级别数为 \(2^k\),其中 \(k\) 通常是 8、16 或 32。量化水平(Quantization level)决定了权重或激活值被量化成多少个值,也就是量化级别的数量。在量化领域,较低的量化水平会导致精度损失,较高的量化水平需要更大的存储空间和计算能力,因此需要在实践中根据不同的应用和具体条件进行合适的调整。

当一个权重被量化为 \(2^k\) 个级别时,我们可以用下面的公式计算其量化误差,即该权重与原始浮点值之间的偏差值:

\[\operatorname{Error}_{q} = \frac{q_{max}-q_{min}}{2^{k}-1}\]

其中 \(q_{max}\)\(q_{min}\) 分别是量化级别的上下限。在深度学习中,我们通常使用固定的量化水平 (如8位、16位和32位) 进行神经网络中权重和激活值的量化,从而可以使得神经网络被适配到更多的硬件平台和应用场景中。一般来说,随着量化水平的增加,可以更精确地表示权重和激活值,但相应地需要更多的存储空间和计算资源。因此,在实际应用中,需要在量化精度和运行效率之间取得平衡,选择合适的量化水平来满足需求。

备注

峰值信噪比 (Peak Signal-to-Noise Ratio,PSNR) 是一种用于衡量图像或视频质量的指标,通常用于比较原始图像或视频与经过压缩或其他处理后的图像或视频之间的质量差异。其计算公式如下:

\[PSNR = 10\log_{10}\left(\frac{MAX_{I}^2}{MSE}\right)\]

其中 MAXI 是图像或视频像素的最大值,MSE 是均方误差。MSE 表示被检测图像或视频与参考图像或视频之间的差异度,计算方式为:

\[MSE = \frac{1}{MN} \sum_{i=1}^{M}\sum_{j=1}^{N}[I(i,j)-K(i,j)]^2\]

其中,I 和 K 分别是被检测图像或视频和参考图像或视频,M 和 N 分别是它们的高度和宽度。

PSNR 可以测量输入图像与参考图像之间的信噪比,也就是图像或视频质量的差异程度。通常情况下,PSNR 越高,表示图像或视频的质量越高,差异越小。由于它依赖于图像或视频像素的最大值,因此 PSNR 的值通常会在实际应用中取 \(8-10\) 位的灰度值作为 MAXI 的值。

PSNR 被广泛应用于视频编码、图像处理等领域,它可以帮助评估压缩算法、降噪算法等的性能。但是,PSNR 并不总是适用于衡量人类观感上的图像质量,因为人类对于图像质量的评价是主观的,对于不同的应用和场景,可能需要使用其他指标进行评估。

中平量化器#

中平量化器

(midtread)对权重值进行量化时,将零点作为量化级别的下限,然后根据每个级别之间的距离对权重值进行四舍五入,使其落在量化级别上。

具体来说,考虑将 \(w \in R\) 进行 midtread 量化,假设量化范围为 \([-r, r]\),量化级别数为 \(2^n\)。我们可以将零点作为最低级别 \(q_0=-r\),最高级别为 \(q_{2^n-1}=r\)。每个级别之间的间隔为 \(s = 2r / (2^n-1)\),则每个量化级别 \(q_i\) 的值为:

\[q_i = (-r + i \cdot s), \forall i \in [0, 2^n - 1]\]

对于一个给定的权重值 \(w\),我们可以首先对其做偏移,使其值转化为 \(w_c = w + r\),例如将 \([-r, r]\) 映射到 \([0, 2r]\)。接着,我们可以将 \(w_c\) 除以间隔 \(s\),并对结果进行四舍五入,使其落在量化级别上:

\[\widehat{w} = \operatorname{round}(w_c / s) \cdot s\]

最后,我们可以将其减去偏移量 \(r\) 并返回 \(\widehat{w}-r\),即为量化后的值。可以看出,对于 midtread 量化,每个量化级别的下限值都是 \(-r\),因此被称为 “midtread”。它适用于对称量化场景,特别是对于权重值范围不包含0点的神经网络来说,与传统的 min-max 量化方式相比,具有更高的压缩率和更好的动态范围覆盖。

中升量化器#

中升量化器

(midrise) 另一种常见的均匀量化方法,它也是按照量化级别之间的间隔对权重值进行四舍五入,但不同的是其将零点作为量化级别的中间值,即在量化级别的中央设置零点,并利用量化级别间隔的一半来构造量化级别。

具体来说,考虑将 \(w \in R\) 进行 midrise 量化,假设量化范围为 \([-r,r]\),量化级别数为 \(2^n\)。我们可以将零点设置为 \(q_0=0\),并将其他的 \(2^n-1\) 个量化级别对称地分布在 \([-r,0)\)\((0,r]\)。每个级别之间的间隔为 \(s = 2r / 2^n\)

每个量化级别的宽度为 \(\frac{r}{2^n-1}\),中心位置即为其值。对于 midrise 量化,对于一个给定的权重值 \(w\),我们可以将其除以间隔 \(s\),并对结果进行四舍五入,使其落在量化级别上:

\[\widehat{w} = \operatorname{round}(w / s) \cdot s\]

最终,量化后的值即为 \(\widehat{w}\)。可以看出,对于 midrise 量化,零点处于量化级别中心,因此对于权重值范围包含0点的神经网络来说,midrise 量化比 midtread 量化更适用。midrise 量化的优点之一是它可以实现无损的量化,即当量化级别的数量足够大时,量化后的值可以与原始值精确匹配。然而,它需要分配额外的量化级别来处理值范围内的零点,因此通常需要更多的比特数来表示量化权重。

量化类型(chaos)#

模拟信号量化#

在量化过程中,量化器将给定模拟波形的幅值的最大值表示为离散形式的类似波形。这个过程也取决于编码器的比特级别。然后量化器将信号转换为量化信号,最好称为 信号调制。量化器所开发的量化信号的主要缺点是它所遵循的近似。例如,如果采样输出的值为 \(1.2\),那么量化值的输出为 \(1\)。因此,由于这个过程,大量的信息丢失,导致量化误差。

量化分辨率#

模数转换器(analog-to-digital converter)的分辨率(resolution)通常定义为输入模拟信号的微小变化,它使输出数字信号改变单元计数。分辨率是模数转换器的主要参数,它与传递函数有关,传递函数的特征是阶梯波形。波形的步长数等于波形的分辨率。然而,当信号的比特数较大时,传递函数显示出与输入信号相当大的偏差,并导致较大的量化噪声。

量化过程(chaos)#

定义缩放函数(scaling function) \(sc: \mathbb{R}^n \to [0, 1]^n\),将来自任意范围的值归一化到 \([0, 1]\)。给定这样的函数,量化函数的一般结构如下:

(1)#\[ Q(v) = sc^{-1}(\hat{Q}(sc(v))) \]

其中 \(sc^{-1}\)\(sc\) 的逆函数,\(\hat{Q}\) 是实际的量化函数(仅接受 \([0, 1]\) 的值)。

总是假设 \(v\) 是一个向量;当然,在实践中,权重向量可以是多维的,但可以将其重塑为一维向量,并在量化后恢复原始维度。

Quantized Neural Networks: Training Neural Networks with Low Precision Weights and Activations 中定义了各种规格的缩放函数,在本文中,将使用线性缩放

(2)#\[ sc(v) = \cfrac{v-\beta}{\alpha} \]

其中 \(\alpha=\max_i v_i - \min_i v_i\)\(\beta=\min_i v_i\)

这样,量化函数为

(3)#\[ Q(v) = \alpha \hat{Q}(\cfrac{v-\beta}{\alpha}) + \beta \]

举例说明:

import numpy as np

v = np.array([3.3, 4.6, 6.7, 7.9], dtype="float32")
beta = v.min()
alpha = v.max() - v.min()
alpha, beta
(4.6000004, 3.3)
def sc(v, alpha, beta):
    v = v - beta
    return v/alpha

借助缩放函数将值域归约到 \([0, 1]\) 空间:

vs = sc(v, alpha, beta)
vs
array([0.        , 0.28260866, 0.7391303 , 1.        ], dtype=float32)

注意

这个公式的问题是对整个向量使用了相同的缩放因子,它的维数可能很大。量级(Magnitude)不平衡可能导致精度的显著损失,其中缩放向量的大多数元素被推为零。

分桶

为了避免这种情况,我们将使用 bucketing,例如 [He et al., 2016] 分别对具有一定固定大小的连续值的桶应用缩放函数。这里的权衡是,为每个桶获得了更好的量化精度,但必须为每个桶存储两个浮点缩放因子。下面考虑量化点的均匀和非均匀放置。

均匀量化

固定参数 \(s \ge 1\),描述所使用的量化级别的数量。直观地说,均匀量化考虑 \(0\)\(1\) 之间的 \(s + 1\) 个等间隔点(包括这些端点)。确定性版本将分配每个(缩放的)向量坐标 \(v_i\) 到最近的量化点,而在随机版本中,执行概率 rounding,这样得到的值是 \(v_i\) 的无偏估计,方差最小。

形式上,定义具有 \(s + 1\) 能级(levels)的均匀量化函数为

(4)#\[ \hat{Q}(v, s)_i = \cfrac{\lfloor v_i s \rfloor}{s} + \cfrac{\xi_i}{s} \]

其中 \(\xi_i\) 是舍入(rounding)函数。

对于确定版本,定义 \(k_i = sv_i − \lfloor v_i s \rfloor\),并且设置

(5)#\[\begin{split} \xi_i = \begin{cases} 1, & \text{if } k_i \gt 0.5 \\ 0, & 其他 \end{cases} \end{split}\]

对于随机版本设置 \(\xi_i \sim \operatorname*{Bernoulli}(k_i)\)

备注

\(k_i\) 是原始点 \(v_i\) 和最近的量化点的归一化距离。

假设需要量化到 4 比特位,一共有 16(\(s=2^4\))个数,使用一致(均匀)量化函数 (4) 将量化数据逆变换回原空间:

def Qz(v, s):
    k = s * v - np.floor(v * s)
    o = k > 0.5
    return o.astype("int")

def Q(v, s):
    x = np.floor(v * s) + Qz(v, s)
    return x/s
# 原空间的值为 3.3, 4.6, 6.7, 7.9
qv = Q(v, s=2**4)
qv

小技巧

量化的本质其实就是将 \(v\) 通过缩放函数映射到 \([0, 1]\) 上后,通过一致量化函数得到各个值最接近的量化点,并用之代替原来的值。然后再用逆缩放函数映射回原空间,这样处理之后,原空间就相当于用量化点重构了,即原空间所有点都是量化点经过逆缩放变换得到。

非均匀量化

非均匀量化以一组 \(s\) 个量化点作为输入 \(\{p_1, \cdots, ps\}\),并将每个元素 \(v_i\) 量化到这些点中最接近的值。

随机量化相当于添加高斯噪声#

显然,随机均匀量化是其输入的无偏估计 \(\operatorname*{E}[Q(v)] = v\)。有

\[ Q(v)^T x = v^T x + \xi \]

其中 \(v\) 表示权重向量,\(x\) 表示输入,\(\xi\) 是渐近正态分布的随机变量。

这意味着量化权重相当于在每一层(激活函数之前)的输出中增加一个渐近正态分布的零均值误差项。误差项的方差取决于 \(s\)。可以将量化与倡导在神经网络的中间激活中添加噪声作为正则化的工作联系起来。

微分量化#

利用非均匀量化点的 placement,引入微分量化作为提高量化神经网络精度的通用方法。实验上,发现在这种情况下,随机量化和确定性量化之间的差别很小,因此将关注更简单的确定性量化函数。

\(p = (p_1, \cdots, ps)\) 表示量化点的向量,\(Q(v, p)\) 表示量化函数。理想情况下,希望找到一组量化点 \(p\) 将模型量化时的精度损失降到最低。关键的观测结果是,为了找到这个集合 \(p\),可以使用随机梯度下降,因为可以计算 \(Q\) 关于 \(p\) 的梯度。

量化神经网络的主要问题是,决定哪个 \(p_i\) 应该取代给定的权值是离散的,因此梯度为几乎处处为零:\(\operatorname{d} \cfrac{Q(v, p)}{v} = 0\)。这意味着不能通过量化函数反向传播梯度。为了解决这个问题,通常使用直通估计器([Bengio et al., 2013][Hubara et al., 2016])的变体。另一方面,模型作为所选 \(p_i\) 的函数是连续的,可以被微分;\(Q(v, p)_i\) 关于 \(p_j\) 的梯度几乎在任何地方都是确定的,它很简单

(6)#\[\begin{split} \cfrac {\operatorname{d}{Q(v, p)_i}}{\operatorname{d} p_j} = \begin{cases} \alpha_i & \text{如果} v_i \text{被量化为} p_j \\ 0, & 其他 \end{cases} \end{split}\]

这里,假设使用分桶方案,\(\alpha_i\) 表示第 \(i\) 个元素的缩放因子。若不使用分桶方案,则 \(\alpha_i = \alpha\)。否则,它将根据权重 \(v_i\) 属于哪个桶而改变。

因此,可以使用训练原始模型时使用的相同损失函数,通过式 (6) 和通常的反向传播算法,计算其相对于量化点 \(p\) 的梯度。可以用标准 SGD 算法最小化关于 \(p\) 的损失函数。

反量化#

通常将一张 uint8 类型、数值范围在 \([0, 255]\) 的图片归一成 float32 类型、数值范围在 \([0, 1]\) 的张量,这个过程就是 反量化

通常将网络输出的范围在 \([0, 1]\) 之间的张量调整成数值为 \([0, 255]\)、uint8 类型的图片数据,这个过程就是 量化

graph LR; A1[int8空间]; A2[uint8空间]; B[float32空间]; A1 --反量化--> B; A2 --反量化--> B; B --量化--> A1; B --量化--> A2;

这里 float32空间 特指 \([0, 1]\) 实数空间。

小技巧

可以前往 谷歌量化白皮书 了解更多量化知识。

量化常用术语#

常规精度一般使用 FP32(32位浮点,单精度)存储模型权重;低精度(Low precision)则表示 FP16(半精度浮点),INT8(8 位的定点整数)等数值格式。不过目前低精度往往指代 INT8。混合精度(Mixed precision)在模型中使用 FP32 和 FP16 。FP16 减少了一半的内存大小,但有些参数或操作符必须采用 FP32 格式才能保持准确度。

量化一般指 INT8。不过,根据存储权重元素所需的位数,还可以包括:

  • 二值神经网络([Courbariaux et al., 2016]):在运行时权重和激活只取两种值(例如 +1,-1)的神经网络,以及在训练时计算参数的梯度。

  • 三元权重网络([Li et al., 2016]):权重约束为 +1,0 和 -1 的神经网络。

  • XNOR 网络([Rastegari et al., 2016]):过滤器和卷积层的输入是二进制的。XNOR 网络主要使用二进制运算来近似卷积。

[Han et al., 2015] 更关注如何压缩整个模型而非存储一个元素的位数。作者将剪枝、量化和编码等技术结合起来,在不显著影响准确性的前提下,将存储需求减少 35x(AlexNet)至 49x(VGG-19)。该论文还表明量化卷积层需要 8 位以避免显着的精度损失,而全连接只需要 4 位。

[Cheng et al., 2017] 关于模型压缩的调查列出了许多工作,并将它们分类为参数剪枝和共享,低秩分解和稀疏性,传递/紧凑卷积滤波器和知识蒸馏等。

工业界最终选择了 INT8 量化:FP32 在推理(inference)期间被 INT8 取代,而训练(training)仍然是 FP32。

通常,可以根据 FP32 和 INT8 的变换机制对解决方案进行分类。一些框架简单地引入了 QuantizeDequantize 层,当从卷积或全链接层送入或取出时,它将 FP32 转换为 INT8 或相反。在这种情况下,模型本身和输入/输出采用 FP32 格式。深度学习框架加载模型,重写网络以插入QuantizeDequantize 层,并将权重转换为 INT8 格式。

量化的简单分类#

将 float 32 (全精度)数据类型映射到 Int 数据类型:

量化映射方法

按照每个间隔是相等的还是不相等的,划分为 均匀量化(uniform quantization,或 线性量化)和 非均匀量化 (non-uniform quantization,或 非线性量化)。

量化的对称性

按照映射到整数的数值范围划分为 对称量化 (有正负数)和 非对称量化 (全是正数)。非对称量化有 zero-point(主要作用是用于避免 padding 出现误差)。

量化级

二值网络(1-bit)、三值网络(2-bit)、3-bit、4-bit、5-bit、6-bit、7-bit、8-bit。

量化误差来源#

  1. 从 float-32 到 Int 数据类型的 round 运算误差;

  2. 激活函数的截断;

  3. 数值溢出误差。

量化中可能出现的问题#

  1. weight 和 activation 的数据分布呈现出类拉普拉斯分布或者类高斯分布,数据分布是钟型分布,大部分数据集中在中间,两头的数据比较少。采用非均匀量化提高来量化的分辨率。

  2. 动态值域问题(dynamic range):每一层的数值范围不一定都相同,activation在不同层的数值范围会不一样。采用截断的方式提高来量化的分辨率。

  3. round 误差。采用随机舍入 (Stochastic rounding,简写 SR)([Croci et al., 2022])方法。

  4. 量化感知训练,量化运算的导数为 \(0\),在 backwards 的时候,梯度在后向传播中传不到后面。

    • STE(Straight-Through Estimator),直通估算器,即将量化运算的梯度设置为 \(1\),那么梯度就可以传递下去。

    • 设计光滑可导且导数不为 \(0\) 的量化,比如 Lq-Net 和 DSQ(Differentiable Soft Quantization)。