跳转至

Quantization

Abstract

  • Data Types
    • How is numeric data represented in modern computing systems?
  • Neural Network Quantization
    • K-Means-based Quantization
    • Linear Quantization
    • Binary and Ternary Quantization
  • Post-Training Quantization (PTQ)
    • How should we get the optimal linear quantization parameters (S, Z)?
  • Quantization-Aware Training (QAT)
    • How should we improve performance of quantized models?
  • automatic mixed-precision quantization

Numeric Data Types

  • Motivation: less bit-width => less energy
  • Integer: Unsigned, Signed(Sign-Magnitude, Two's Complement)
  • Fixed-Point Number
  • Floating-Point Number
    • IEEE FP32, FP64, FP16
    • Google's BFloat16: 8-bit exponent, 7-bit mantissa
    • Nvidia FP8(E4M3)
      • 没有 inf, \(S.1111.111_2\) 表示 NaN
      • 最大的规格化值为 \(S.1111.110_2=448\)
    • Nvidia FP8(E5M2)
      • 有 inf(\(S.11111.00_2\))和 NaN(\(S.11111.XX_2\)
      • 最大的规格化值为 \(S.11110.11_2=57344\)
      • 用于 backward
  • INT4 & FP4

Note

exp 位数越多,那么浮点数能表示的 dynamic range 动态范围就越大,这对于训练很重要(推理时需要更高的精度)。例如通常情况 BF16 比 FP16 更容易收敛。

  • Exponent Width => Range
  • Fraction Width => Precision

Neural Network Quantization

Quantization is the process of constraining an input from a continuous or otherwise large set of values to a discrete set.

K-Means-based Weight Quantization

  • 有一个 lookup table(codebook 调色板),而权重就是调色板里的索引。因此只用存浮点调色板和整数索引,不用完整地将浮点权重存下来。假设使用 N bits 位量化,那么至多可以有 \(2^N\) 个不同的权重,而调色板的值由 K-Means 算法得到。

    Example

    例如在下面的例子里,我们使用 2 bit 量化,即可以有 4 个不同的值。这里经过 K-Means 算法,我们得到了 4 个中心点,然后我们将权重量化为这 4 个中心点的索引。右侧即可得到的量化后的权重。

  • 量化后的权重也可以进行微调。对应地,梯度也要有相应的量化。

    Example

    这里权重与上面的例子相同,梯度也进行了基于 K-Means 的量化,最后得到的微调后的权重就是由两个量化结果计算得到的。

  • 量化可以和剪枝一起使用,让模型更小。

  • 经过量化之后,权重从连续变为离散的。
  • 实验证明,对于卷积层,我们可以将权重量化为 4 bit,而对于全连接层,我们可以将权重量化为 2 bit。

  • 哈夫曼编码:实际上不同的权重出现频率是不一样的,为此我们可以使用不同的位数来表示。对于经常出现的权重,我们应该使用更少的位数,这样计算的总位数更少。这就是哈夫曼编码的思想。但实际上在计算时我们需要先进行 decode 操作,这也会带来一定开销,而且实际中不易实现。

  • 需要注意的是这种量化方法我们只节省了存储空间,没有减少计算量。因为所有的计算和内存访问依然是浮点数。

Summary of Deep Compression

Linear Quantization

  • 我们可以使用仿射变换,将整数映射到实数。

    如上图所示,我们使用 \(r=S(q-Z)\) 将整数 \(q\) 映射到实数 \(r\)。其中 \(Z\) 是零点(整数),\(S\) 是缩放因子(浮点数)。

    • 零点的作用是为了让 \(r=0.0\) 这个数能被准确映射到一个整数 \(Z\) 上(因为我们发现在推理中零点经常出现)。
    • 确定缩放因子 \(S\):当我们量化的位数确定后,我们使用二进制补码表示法,那么 \(q_{max}\)\(q_{min}\) 就是 \(2^{N-1}-1\)\(-2^{N-1}\)。我们只需要找到 \(r_{max}\)\(r_{min}\) 即可求出 \(S\).

      Scale of Linear Quantization

    • 确定零点 \(Z\):确定 \(S\) 后,带入 \(r_{min}\)\(q_{min}\) 即可求出 \(Z\)

      Zero Point of Linear Quantization

  • 线性量化下的矩阵乘法:

    \[ \begin{aligned} \mathbf{Y} &= \mathbf{W}\mathbf{X} \\ S_\mathbf{Y}(q_\mathbf{Y} - Z_\mathbf{Y}) &= S_\mathbf{W}(q_\mathbf{W} - Z_\mathbf{W})\cdot S_\mathbf{X}(q_\mathbf{X} - Z_\mathbf{X}) \\ q_\mathbf{Y} & = \frac{S_\mathbf{W}S_\mathbf{X}}{S_\mathbf{Y}}(q_\mathbf{W} - Z_\mathbf{W})\cdot(q_\mathbf{X} - Z_\mathbf{X}) + Z_\mathbf{Y} \\ q_\mathbf{Y} &= \frac{S_\mathbf{W}S_\mathbf{X}}{S_\mathbf{Y}}(q_\mathbf{W}q_\mathbf{X} - q_\mathbf{W}Z_\mathbf{X} - q_\mathbf{X}Z_\mathbf{W} + Z_\mathbf{W}Z_\mathbf{X}) + Z_\mathbf{Y} \end{aligned} \]
    • \((q_\mathbf{W}q_\mathbf{X} - q_\mathbf{W}Z_\mathbf{X} - q_\mathbf{X}Z_\mathbf{W} + Z_\mathbf{W}Z_\mathbf{X})\) 会执行 N-bit 的整数乘法,以及 32-bit 的加减法。

      • 这里 \(-Z_\mathbf{X}q_\mathbf{W}+Z_\mathbf{W}Z_\mathbf{X}\) 可以 precompute 提前计算出来。(因为 \(Z\) 是可以提前确定的,而 \(q_W\) 也与 \(Y\) 无关是权重本身的值)
    • \(Z_\mathbf{Y}\) 会执行 32-bit 的加法。

    • 经验上,\(S_\mathbf{W}S_\mathbf{X}/S_\mathbf{Y}\) 通常会在区间 \((0,1)\) 之间,因此我们可以做这样的变形:

      \[ \frac{S_\mathbf{W}S_\mathbf{X}}{S_\mathbf{Y}} = 2^{-n}M_0, \quad M_0 \in [0.5, 1) \]

      这里 \(M_0\) 可以用定点数表示,而 \(2^{-n}\) 可以用移位操作表示。那么 \(S_\mathbf{W}S_\mathbf{X}/S_\mathbf{Y}\) 也被 rescale 为了 N-bit 的整数。

    • 通常权重的分布是对称的,因此我们可以让 \(Z_\mathbf{W}=0\),这样我们有:

      \[ q_\mathbf{Y} = \frac{S_\mathbf{W}S_\mathbf{X}}{S_\mathbf{Y}}(q_\mathbf{W}q_\mathbf{X} - q_\mathbf{W}Z_\mathbf{X} ) + Z_\mathbf{Y} \]
  • 线性量化下的全连接层:

    现在我们加上 bias,即计算 \(Y = W\cdot X + B\)。我们可以将 \(B\) 也量化为 \(q_B\),那么我们有:

    \[ q_\mathbf{Y} = \frac{S_\mathbf{W}S_\mathbf{X}}{S_\mathbf{Y}}(q_\mathbf{W}q_\mathbf{X} + q_{bias} ) + Z_\mathbf{Y} \]
    • 这里我们假设 \(Z_\mathbf{W} =0\), \(Z_\mathbf{b} = 0, S_\mathbf{b}=S_\mathbf{W}S_\mathbf{X}\)\(q_{bias}=q_\mathbf{b}-Z_\mathbf{X}q_\mathbf{W}\)
    • 括号内执行 N-bit 整数乘法和 32-bit 加法,其他操作均为 N-bit 整数加法。
    • 需要注意的是这里 \(q_\mathbf{b}, q_{bias}\) 均为 32-bit 的整数,防止溢出。
  • 线性量化下的卷积层:

Post-Training Quantization

How should we get the optimal linear quantization parameters (S, Z)?

Quantization Granularity

  • Per-Tensor Quantization

    • \(|r|_{max}=|\mathbf{W}|_{max}\),即取每个 tensor 的最大值(包括不同的 channel)
    • 使用单个缩放因子 \(S\) 作用整个 weight tensor.
    • 对于大模型效果较好;对于小模型精度会下降。
    • 原因在于不同的输出通道,其权重范围有较大不同(有 outlier weight)。

  • Per-Channel Quantization

    • 对于每个输出通道单独求最大值得到 \(|r|_{max}\),并作为该通道的 \(S_i\).

      Per-Channel vs. Per-Tensor

    • 代价是要存更多的信息(32-bit fp),输出通道较多时会消耗较多的存储空间。

  • Group Quantization

    • VS-Quant: Per-vector Scaled Quantization

      • 层次化的量化,将量化公式改为 \(r=\gamma \cdot S_q(q-Z)\).

        • \(\gamma\) 是一个浮点数,粗粒度的缩放因子。(精度无需达到 32-bit)
        • \(S_q\) 是一个整数,是每个向量的缩放因子。(精度可以更低)

      • 实现了精度和硬件效率的平衡:更昂贵的浮点缩放因子在更粗的粒度;更便宜的整数缩放因子在更细的粒度。 e.g. 我们使用 4-bit 量化,每 16 个元素使用一个 4-bit per-vector 缩放因子,那么有效位宽是 4+4/16=4.25 bit.

    • Multi-level scaling scheme(Shared Micro-exponent (MX) data type)

      • 思想类似,只是我们共用更多层次的缩放因子。这里除了我们的缩放因子的 mantiassa 均为 0 位仅有指数位,相当于我们让几个数据共享了指数位

      Example

      例如 MX9,实际上我们只用了 8-bit 进行量化,但是 L0 用了 1-bit,大小为 2;L1 用了 8-bit,大小为 16,因此有效位数为 9。

Dynamic Range Clipping

  • weight 是静态的,但是 activation 是动态的,我们需要确定他的范围,在部署模型前收集激活值的统计信息。
  • Exponential moving averages (EMA)
    • 在训练时,\(\hat{r}_{max,min}^{(t)}=\alpha \cdot r_{max,min}^{(t)} + (1-\alpha)\hat{r}_{max,min}^{(t-1)}\)
    • 这里是假设激活值满足高斯分布,因此我们可以使用均值和方差来估计范围。
  • minimize loss of information
    • 信息的损失是通过 KL 散度来衡量,用来找最适合 clip 的地方。\(D_{KL}(P||Q)=\sum\limits_{i=1}^N P(x_i)\log\frac{P(x_i)}{Q(x_i)}\)
    • 在训练后的模型上运行几个 calibration batches 来校准。

      Example

      量化之后,我们把超过最大值的激活值 clip 到最大值,因此可以看到在最大值的地方激活值的数量陡增。

Rounding

  • Rounding-to-nearest 不是最佳的,因为权重会相互影响。对于单个权重最好的 rounding 不一定是对于整个权重矩阵最好的。
  • 最佳的方法是,能够最好地重建原始的激活值的方法。
    • 我们从 \(\{\lceil w \rceil, \lfloor w \rfloor\}\) 中选择一个来更好地重建。
    • 基于学习的方法:假设 \(\tilde{w}=\lfloor \lfloor w\rfloor +\delta \rfloor, \delta\in[0,1]\),我们要优化下面的式子:

      \[ \begin{aligned} & \arg\min_{V}\|Wx-\tilde{W}x\|_F^2 + \lambda f_{reg}(V) \\ \rightarrow & \arg\min_{V}\|Wx-\lfloor\lfloor W\rfloor+h(V)\rfloor x\|_F^2 + \lambda f_{reg}(V) \end{aligned} \]
      • 这里 \(x\) 是输入,V 是相同形状的随机变量。
      • \(h\) 是一个函数,值域是 \((0,1)\),比如 ReLU。
      • \(f_{reg}(V)\) 是一个正则化项,鼓励 \(h(V)\) 是二元的,比如可以取做 \(f_{reg}(V)=\sum\limits_{i,j}1-|2h(V_{i,j})-1|^\beta\)

Quantization-Aware Training

  • How should we improve the performance of quantized models?
    • 在我们将模型量化后,可能需要微调来提供更好的精度。
  • 训练时,需要保存一份权重 \(W\) 全精度的拷贝;小幅度的梯度会以浮点格式被累积;当模型被训练好后,只有量化的权重会在推理中使用。

    这里输入送入层时,fp 的权重会经过量化操作与输入运算,随后在 Layer N 内部是浮点操作,输出通过激活量化操作,结果也是量化值。

    为什么需要保存权重的 full precision copy

    为了让微小变化能够对梯度产生影响。比如我们当前的梯度乘上学习率为 -0.1,当前的权重是 1,那么更新之后变为了 1.1,量化后依然是 1.0. 这样重复 5 次后,梯度应该变为 1.5,四舍五入为 2.0. 如果不保存拷贝那么这个微小的变化永远不会被捕捉。

  • Straight-Through Estimator (STE)

    • 问题在于,我们在 weight/activation quantization 时对激活值进行了量化操作,而在反向传播时这里的梯度是 0。这样的话,我们的梯度就无法传递到前面的层。(局部梯度为 0)
    • 我们使用 STE,即假设 \(\frac{\partial Q(W)}{\partial W}=1\),相当于用直线近似了本来的量化函数。这样的话我们只需要把上游梯度传到下游梯度即可。

  • Put them together:

Binary/Ternary Quantization

  • Can we push the quantization precision to 1 bit?
  • Binarization

    • Deterministic Binarization: 根据阈值直接将权重量化为 1 或 -1,类似于符号函数。

      \[ q=sign(r)=\left\{\begin{matrix}+1, & r\geq 0\\ -1, & r<0\end{matrix} \right. \]
    • Stochastic Binarization: 根据全局的统计信息,计算一个概率,然后根据这个概率来决定量化的值。更不容易实现,因为需要硬件生成随机数。

    • 直接使用二进制量化无法表示较大的权重,可能会有较大误差。为了保持精度,我们将量化后的权重 \(W^\mathbb{B}=sign(W)\) 乘上一个缩放因子 \(\alpha=\frac{1}{n}\|W\|_1\).

      Minimizing Quantization Error in Binarization

    • 如果权重和激活值都二值化,那么我们可以使用 XNOR-Net,即将卷积操作转化为 XNOR 和 popcount 操作。

      可以看到,二值化后的两个数字相乘可以用 XNOR 和 popcount 来实现。其中 popcount 是一个很高效的硬件操作。

    Binary Quantization

    input weight operations memory computation
    \(\mathbb{R}\) \(\mathbb{R}\) \(+,\times\) 1x 1x
    \(\mathbb{R}\) \(\mathbb{B}\) \(+,-\) ~32x less ~2x less
    \(\mathbb{B}\) \(\mathbb{B}\) xnor, popcont ~32x less ~58x less
  • Ternary Weight Networks (TWN)

    • 权重被量化为 -1, 0, 1 三个值。

      \[ q=\left\{\begin{matrix}+r_t, & r> \Delta \\ 0 , & |r|\leq \Delta \\ -r_t, & r<-\Delta\end{matrix} \right. \]
    • 这里的阈值我们启发式地选为 \(\Delta = 0.7\times \mathbb{E}(|r|)\), \(r_t=\mathbb{E}_{|r|>\Delta}(|r|)\).

      Example

    • Trained Ternary Quantization (TTQ)

      • 我们可以不使用固定的 \(r_t\),TTQ 引入了两个可学习的参数 \(w_p, w_n\) 来表示正/负比例因子。

        \[ q=\left\{\begin{matrix}w_p, & r> \Delta \\ 0 , & |r|\leq \Delta \\ -w_n, & r<-\Delta\end{matrix} \right. \]

Mixed-Precision Quantization

  • 我们可以对于每一层都使用相同的精度量化,也可以对于每一层使用不同的精度量化。因为不同层对量化的敏感度不同。
  • 问题在于有很大的设计空间,即如何确定具体每一层的精度。
  • Solution: Design Automation

评论