量化简介#

参考:Model Compress via Distillation and Quantization

量化是什么?#

参考:quantization-and-resolution

备注

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

量化器#

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

优点

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

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

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

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

量化类型#

均匀量化 (uniform quantization)又称 线性量化。在这种类型的量化中,量化器将两个量化级别之间的 step 保持在同一级别。根据原点存在的位置,将均匀量化分为 midtread 量化(这种均匀量化的特征是一个阶梯图。中心点位于 graph 的 tread 的中点)和 midrise 量化(这种均匀量化的特征也是一个阶梯图,但这种图的中点出现在图的上升部分)。

非均匀量化 (non-uniform quantization):量化器产生的 step 不是恒定的,而是有一定程度的变化。在这种情况下,量化信号与离散信号表现出非线性。

模拟信号量化#

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

量化分辨率#

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

量化过程#

定义缩放函数(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")
b = v.min() # \alpha
a = v.max() - b # \beta
a, b
(4.6000004, 3.3)
def sc(v, a, b):
    v = v - b
    return v/a

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

vs = sc(v, a, b)
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
array([3.3125, 4.625 , 6.6875, 7.875 ])

小技巧

量化的本质其实就是将 \(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)。