全定点 8-Bit 量化加速神经网络推理
八位整型量化感知训练及推理方法

上一章主要借鉴的是2016年的文章On the efficient representation and execution of deep acoustic models,然而,在2017年谷歌更近一步扩展到了全定点的计算, 参考 Quantization and Training of Neural Networks for Efficient Integer-Arithmetic-Only Inference,原理和之前分享的内容也基本上一致。

基本原理

还是应用了最大最小化原理,将 32 位的 RQM 换成了 8 位的 zero_point,具体的量化反量化操作如下代码:

def ChooseQuantizationParamsV2(xmin, xmax, num_bits=8):
    qmin = -(int)(1 << (num_bits-1))
    qmax = (int)((1 << (num_bits-1))-1)

    scale = (xmax - xmin) / (qmax - qmin)
    initial_zero_point = qmin - xmin / scale
    nudged_zero_point = initial_zero_point
    if initial_zero_point < qmin:
        nudged_zero_point = qmin
    elif initial_zero_point > qmax:
        nudged_zero_point = qmax
    else:
        nudged_zero_point = (int)(initial_zero_point)

    return scale, nudged_zero_point


def QuantizeV2(x, scale, nudged_zero_point, num_bits=8):
    qmin = -(int)(1 << (num_bits-1))
    qmax = (int)((1 << (num_bits-1))-1)
    transformed_val = x/scale+nudged_zero_point
    clamped_val = np.clip(transformed_val, qmin, qmax)
    quantized = np.round(clamped_val)
    return quantized.astype(np.int8)


def DequantizeV2(x, scale, nudged_zero_point):
    return (x.astype(np.float32) - (float)(nudged_zero_point)) * scale

代码分析

目前,既要保证识别效果,同时还要使用 8 bit 量化模型,一种比较完备的做法就是将推理阶段的量化操作迁移到训练阶段,如 Tensorflow 说明文档一章介绍 Fixed Point Quantization。采用 fake 的量化后的浮点来作为 input 和 weight 的替换,同时浮点范围采用了平滑最大最小值的方法,具体可以查看 TensorFlow 的官方代码 MovingAvgQuantize,训练中平滑得到了全局的输入和权重的最大最小值(类似 batchnorm 的统计方式),代码段如下:

    if symmetric:
      if narrow_range:
        min_max_ratio = -1
      else:
        # In two's complement notation, the negative range is slightly larger
        # than the positive range.
        min_max_ratio = -((1 << num_bits) - 2) / (1 << num_bits)

      # TFLite requires that 0.0 is always in the [min; max] range. Because
      # batch_min <= batch_max, it follows that range_min <= 0 <= range_max.
      range_min = math_ops.minimum(batch_min, batch_max / min_max_ratio)
      range_max = math_ops.maximum(batch_max, batch_min * min_max_ratio)
    else:
      # TFLite requires that 0.0 is always in the [min; max] range.
      range_min = math_ops.minimum(batch_min, 0.0)
      range_max = math_ops.maximum(batch_max, 0.0)

    assign_min = moving_averages.assign_moving_average(
        min_var, range_min, ema_decay, name='AssignMinEma')
    assign_max = moving_averages.assign_moving_average(
        max_var, range_max, ema_decay, name='AssignMaxEma')

    return _FakeQuantWithMinMaxVars(
        inputs,
        assign_min,
        assign_max,
        per_channel=per_channel,
        num_bits=num_bits,
        narrow_range=narrow_range)

关于 int8t 的推理过程,还有一个官方博客把推理过程也写出来了 TensorFlow Lite 8-bit quantization specification,细节之处让人不得不佩服谷歌的匠心,除了因式分解后的提前计算,还有将权重的最大最小值强制设计成对称,这样 zero_point 等于零,减少了两次计算,如下图所示。

img

8位量化在采用对称的取值范围可以直接省去一个 int8 的 scale 计算。

遗留问题

这种全定点的推理,我认为对于模型的部署会有些问题:

  1. LayerNorm 的归一化、BatchNorm 如果放置到激活函数之后如何进行高效的定点计算。
  2. 对于一些新的 Layer 或者激活函数,比如 DeepFSMN、GLU 等适配全定点化是否引入比较大的误差。
01/02/2021