[TOC]

概述

文章参考:https://zhuanlan.zhihu.com/p/156835141

文章参考:https://aitechtogether.com/aiquestion/22771.html

文章参考:https://oldpan.me/archives/how-to-quan-1

markdown数学公式参考:https://geek-docs.com/markdown/markdown-tutorial/markdown-mathematical-formula.html

markdown数学公式参考:https://blog.csdn.net/weixin_42782150/article/details/104878759

markdown数学公式参考:https://zhuanlan.zhihu.com/p/59412540

介绍了矩阵量化的基本原理,并推广到卷积网络中。这一章开始,我会逐步深入到卷积网络的量化细节中,并用 pytorch 从零搭建一个量化模型,帮助读者实际感受量化的具体流程。

本章中,我们来具体学习最简单的量化方法——后训练量化「post training quantization」

卷积层量化

卷积网络最核心的要素是卷积,前文虽然有提及卷积运算的量化,但省略了很多细节,本文继续深入卷积层的量化。

这里我们继续沿用之前的公式,用 SZ表示 scale 和 zero point(offset), r表示浮点实数, q表示定点整数。

假设卷积的权重 weight 为 $w$,bias 为$b$,输入为$x$,输出的激活值为$a$。由于卷积本质上就是矩阵运算,因此可以表示成:

$$
a=\sum_i^N w_ix_i+b \tag {1}
$$

我们在之前学量化的公式的时候用 $r$表示浮点实数,$q$表示量化后的定点整数。大家不需要纠结到底是$+Z$还是$-Z$

浮点和整型之间的换算公式为:
$$
r=S(q-Z) \tag {2}
$$

$$
q=round(r/S +Z) \tag {3}
$$
由公式一和公式二得到量化的公式:
$$
S_a(q_a-Za)=\sum_i^NS_w(q_w-Z_w)S_x(q_x-Z_x)+S_b(q_b-Z_b)
$$
我们再将这个公式转换一下。将$q_a$量化之后的激活值提取出来。
$$
S_a(q_a-Za)=\frac{S_wS_x}{S_a}\sum_i^N(q_w-Z_w)(q_x-Z_x)+\frac{S_b}{S_a}(q_b-Z_b)+Z_a \tag {3}
$$
这里面非整数的部分就只有$\frac{S_wS_x}{S_a}$、$\frac{S_b}{S_a}$

因此接下来就是把这部分也变成定点运算。

对于 bias,由于$\sum_i^N(q_w-Z_w)(q_x-Z_x)$的结果通常会用int32的整数存储,因此bias通常也会量化到int32

这里我们可以直接用$S_wS_x$来代替$S_b$ 由于$S_w、S_x$都是对应 8 个 bit 的缩放比例,因此$S_wS_x$最多就放缩到 16 个 bit,用 32bit 来存放 bias 绰绰有余,而$Z_b$则直接记为 0。

因此,公式 (3) 再次调整为:
$$
S_a(q_a-Za)=(\frac{S_wS_x}{S_a}\sum_i^N(q_w-Z_w)(q_x-Z_x)+q_b)+Z_a \tag {4} \ = M(\sum_i^Nq_wq_x-\sum_i^Nq_wZ_x-\sum_i^Nq_xZ_w+\sum_i^NZ_wZ_x+q_b)+Z_a
$$

其中$M=\frac{S_wS_x}{S_a}$

根据上一篇文章的介绍,$M$可以通过一个定点小数加上 bit shift 来实现,因此公式 (4) 完全可以通过定点运算进行计算。

由于 $Z_w、q_w、Z_x、q_b$都是可以事先计算的,因此$\sum_i^Nq_wZ_x、\sum_i^NZ_wZ_x+q_b$也可以事先计算好,实际 inference 的时候,只需要计算$\sum_i^Nq_wq_x$和$\sum_i^Nq_xZ_w$即可。

卷积量化流程

了解完整个卷积层的量化,现在我们再来完整过一遍卷积网络的量化流程。

我们继续沿用前文的小网络:

img

其中,$x、y$示输入和输出,$a_1、a_2$是网络中间的 feature map,$q_x$表示$ x $量化后的定点数,$q_{a1}$ 等同理。

在后训练量化中,我们需要一些样本来统计$ x、a_1、a_2 以及 y 的数值范围「即 min, max$」,再根据量化的位数以及量化方法来计算 scale 和 zero point。

本文中,我们先采用最简单的量化方式,即统计 min、max 后,按照线性量化公式:
$$
S = \frac{r_{max}-r_{min}}{q_{max}-q_{min}} \tag{5}
$$

$$
Z = round(q_{max} - \frac{r_{max}}{S}) \tag{6}
$$

来计算 scale 和 zero point。

需要注意的是,除了第一个 conv 需要统计输入$x$的 min、max 外,其他层都只需要统计中间输出 feature 的 min、max 即可。另外,对于 relu、maxpooling 这类激活函数来说,它们会沿用上一层输出的 min、max,不需要额外统计,即上图中$ a_1、a_2 $会共享相同的 min、max 「为何这些激活函数可以共享 min max,以及哪些激活函数有这种性质,之后有时间可以细说」。

因此,在最简单的后训练量化算法中,我们会先按照正常的 forward 流程跑一些数据,在这个过程中,统计输入输出以及中间 feature map 的 min、max。等统计得差不多了,我们就可以根据 min、max 来计算 scale 和 zero point,然后根据公式 (4) 对一些数据项提前计算。

之后,在 inference 的时候,我们会先把输入$ x $量化成定点整数$ q_x$,然后按照公式 (4) 计算卷积的输出$ q_{a1}$,这个结果依然是整型的,然后继续计算 relu 的输出$ q_{a2}$。对于 fc 层来说,它本质上也是矩阵运算,因此也可以用公式 (4) 计算,然后得到$ q_y$。最后,根据 fc 层已经计算出来的 scale 和 zero point,推算回浮点实数 $y$。除了输入输出的量化和反量化操作,其他流程完全可以用定点运算来完成。

pytorch实现

有了上面的铺垫,现在开始用 pytorch 从零搭建量化模型。

下文的代码都可以在以下仓库上找到