[TOC]

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

文章参考:https://github.com/Ewenwan/MVision/tree/master/CNN/Deep_Compression/quantization

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

概述

矩阵的量化

由于卷积网络中的卷积层和全连接层本质上都是一堆矩阵乘法,因此我们先看如何将浮点运算上的矩阵转换为定点运算。

假设$r_1、r_2$是浮点实数上的两个$N*N$的矩阵,$r_3$是$r_1、r_2$相乘后的矩阵:

$$
r_3^{i,k}=\sum_{j=1}^N r_1^{i,j}r_2^{j,k} \tag{1}
$$
假设 $S_1、Z_1$ 是$r_1$矩阵对应的$scale$ 和 zero point,$ S_2 、 Z_2 、 S_3 、 Z_3$ 同理,那么由 (1) 式可以推出:
$$
S_3(q_3^{i,k}-Z_3)=\sum_{j=1}^{N}S_1(q_{1}^{i,j}-Z_1)S_2(q_2^{j,k}-Z_2) \tag{2}
$$
整理一下可以得到:
$$
q_3^{i,k}=\frac{S_1 S_2}{S_3}\sum_{j=1}^N(q_1^{i,j}-Z_1)(q_2^{j,k}-Z_2)+Z_3 \tag{3}
$$
仔细观察 (3) 式可以发现,除了$ \frac{S_1 S_2}{S_3}$ ,其他都是定点整数运算。那如何把$ \frac{S_1 S_2}{S_3} $也变成定点运算呢?这里要用到一个 trick。假设$ M=\frac{S_1 S_2}{S_3}$,由于M通常都是 (0, 1) 之间的实数 (这是通过大量实验统计出来的),因此可以表示成$ M=2^{-n}M_0$,其中 $M_0$是一个定点实数。注意,定点数并不一定是整数,所谓定点,指的是小数点的位置是固定的,即小数位数是固定的。因此,如果存在 $M=2^{-n}M_0$,那我们就可以通过$M_0$的 bit 位移操作实现 $2^{-n}M_0$,这样整个过程就都在定点上计算了。

很多刚接触量化的同学对这一点比较疑惑,下面我就用一个简单的示例说明这一点。我们把$M=\frac{S_1 S_2}{S_3}$ 代入 (3) 式可以得到:
$$
q_3^{i,k}=M\sum_{j=1}^N(q_1^{i,j}-Z_1)(q_2^{j,k}-Z_2)+Z_3=MP+Z_3 \tag{4}
$$
这里面P是一个在定点域上计算好的整数。就是左边基于定点整数的求和公式计算出来的结果。

假设 $M$=7091,$P$=0.0072474273418460 ($M$可以通过$S$事先计算得到),那下面我们就是要找到一个$M_0$和 $n$,使得$MP=2^{-n}M_0P$成立。我们可以用一段代码来找到这两个数:

1
2
3
4
5
6
7
8
9
10
11
12
13
M = 0.0072474273418460
P = 7091

def multiply(n, M, P):
result = M * P
Mo = int(round(2 ** n * M)) # 这里不一定要四舍五入截断,因为python定点数不好表示才这样处理

approx_result = (Mo * P) >> n
print("n=%d, Mo=%d, approx=%f, error=%f"%\
(n, Mo, approx_result, result-approx_result))

for n in range(1, 16):
multiply(n, M, P)

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
n=1, Mo=0, approx=0.000000, error=51.391507
n=2, Mo=0, approx=0.000000, error=51.391507
n=3, Mo=0, approx=0.000000, error=51.391507
n=4, Mo=0, approx=0.000000, error=51.391507
n=5, Mo=0, approx=0.000000, error=51.391507
n=6, Mo=0, approx=0.000000, error=51.391507
n=7, Mo=1, approx=55.000000, error=-3.608493
n=8, Mo=2, approx=55.000000, error=-3.608493
n=9, Mo=4, approx=55.000000, error=-3.608493
n=10, Mo=7, approx=48.000000, error=3.391507
n=11, Mo=15, approx=51.000000, error=0.391507
n=12, Mo=30, approx=51.000000, error=0.391507
n=13, Mo=59, approx=51.000000, error=0.391507
n=14, Mo=119, approx=51.000000, error=0.391507
n=15, Mo=237, approx=51.000000, error=0.391507

卷积网络的量化

有了上面矩阵乘法的量化,我们就可以进一步尝试对卷积网络的量化。

假设一个这样的网络:

img

这个网络只有三个模块,现在需要把 conv、fc、relu 量化。

假设输入为$x$,我们可以事先统计样本的最大值和最小值,然后计算出$S_x$(scale) 和$Z_x$(zero point)。

同样地,假设 conv、fc 的参数为$w_1、w_2$,以及scale和 zero point 为$S_{w1}、Z_{w1} 、S_{w2} 、Z_{w2}$$。中间层的 feature map 为$a_1、a_2$ ,并且事先统计出它们的 scale 和 zero point 为$S_{a1} 、Z_{a1} 、S_{a2} 、Z_{a2}$。

卷积运算和全连接层的本质都是矩阵运算,因此我们可以把卷积运算表示成 (这里先忽略加 bias 的操作,这一步同样可以量化,不过中间有一些 trick,我们在之后的文章再仔细研究):
$$
a_1^{i,k}=\sum_{j=1}^N x^{i,j}w^{j,k} \tag{9}
$$
根据之前的转换,我们可以得到:

概括总结

这篇文章主要介绍了矩阵量化的原理,以及如何把矩阵量化运用到卷积网络中,实现全量化网络的计算。这中间忽略了很多细节,比如 relu 和 conv 的合并、激活函数的量化、量化训练的流程等。后面的文章会继续补充一些细节,并通过从零搭建一个 pytorch 的量化模型来帮助读者更好地理解中间的过程。

论文参考

image-20230319222508130