上一章主要借鉴的是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 等于零,减少了两次计算,如下图所示。
8位量化在采用对称的取值范围可以直接省去一个 int8 的 scale 计算。
遗留问题
这种全定点的推理,我认为对于模型的部署会有些问题:
- LayerNorm 的归一化、BatchNorm 如果放置到激活函数之后如何进行高效的定点计算。
- 对于一些新的 Layer 或者激活函数,比如 DeepFSMN、GLU 等适配全定点化是否引入比较大的误差。