量化与后训练量化
量化
\[ S=\frac{r_{max}-r_{min}}{q_{max}-q_{min}} \tag{1} \]
对于式\((1)\)中,\(r_{max}\),\(r_{min}\)表示原来的浮点实数的最大值和最小值,\(q_{max}\),\(q_{min}\)表示量化后定点整数的最大值和最小值,\(S\)是scale,表示实数和整数之间的比例关系。
例如:要将\([0.01, 0.85, 0.02, 0.96, 0.7 , 0.22, 0.86]\)量化为4bit无符号整数,则\(r_{max}=0.96,r_{min}=0.01,q_{min}=0,q_{max}=2^4-1=15,S=\frac{0.5-0.1}{15-0}=0.063\)
\[ Z=round(q_{max}-\frac{r_{max}}{S}) \tag{2} \]
在式\((2)\)中,\(round\)表示取整,接着上述的例子,则\(Z=round(15-\frac{0.96}{0.063})=round(-0.16)=0\)
\[ r=S(q-Z) \tag{3} \]
式\((3)\)表示反量化过程,\(r\)表示原来的浮点实数,\(q\)表示量化后的定点整数
\[ q=round(\frac{r}{S}+Z) \tag{4} \]
式\((4)\)是量化过程,以上述例子中的某个元素为例,如:
\(r=0.22\)则量化后\(q=round(\frac{0.22}{0.063}+0)=3\),再反量化则得出\(r'=0.063(3-0)=0.19\),可以看出是量化后再反量化回去的结果是存在精度损失的
此外,从式\((4)\)可以看出,将\(r=0\)代入,可以得出\(q=Z\),即定点整数的zero point就代表了浮点实数的0,二者转换不存在精度损失。这么做的目的是为了在 padding 时保证浮点数值的 0 和定点整数的 zero point 完全等价,保证定点和浮点之间的表征能够一致。
矩阵量化
假设\(mat_1、mat_2\)是浮点实数\(N \times N\)的矩阵,\(mat_3\)表示\(mat_1\times mat_2\),则分解后可以表示为:
\[ mat_3^{i,k}=\sum_{j=1}^N mat_1^{i,j} mat_2^{j,k} \tag{5} \]
假设\(S、Z\)表示矩阵的scale和zero point,则将\((3)\)代入到\((5)\),则可化为:
\[ S_3(q_3^{i,k}-Z_3)=\sum_{j=1}^NS_1(q_1^{i,j}-Z_1)S_2(q_2^{j,k}-Z_2) \tag{6} \]
右边提取\(S_1,S_2\)后,两边同除\(S_3\)后移项得:
\[ q_3^{i,k} = \frac{S_1S_2}{S_3}\sum_{j=1}^N(q_1^{i,j}-Z_1)(q_2^{j,k}-Z_2)+Z_3 \tag{7} \]
这次一来就可以仅通过定点整数运算计算出\(q_3^{i,k}\)了,但其中的\(\frac{S_1S_2}{S_3}\)例外,还需要使用技巧计算,假设\(M=\frac{S_1S_2}{S_3}\),由于M通常是\((0,1)\)之间的整数(大量实验统计的结果),因此可以表示为\(M=2^{-n}M_0\),其中\(M_0\)表示一个定点实数(定点实数不一定是整数),则就可以通过\(M_0\)的移位操作实现\(2^{-n}M_0\)了
CNN量化
假设卷积网络中输入为x,卷积层conv的输出为\(a_1\),激活层ReLU输出为\(a_2\),经过全连接层(fc层)后输出为y。
则我们的处理过程大致如下:
对于输入的x,统计样本的最大值和最小值,根据式\((1)、(2)\)计算出scale和zero point即:\(S_x\)和\(Z_x\)
假设conv、fc的权重参数分别为\(w_1、w_2\),scale和zero point分别为\(S_{w_1}、Z_{w_1}、S_{w_2}、Z_{w_2}\),统计\(a_1\)和\(a_2\)的最大最小值计算\(S_{a_1}、Z_{a_1}、S_{a_2}、Z_{a_2}\)
卷积和全连接的本质还是矩阵运算,因此可以将卷积运算表示为:
\[ a_1^{i,k}=\sum_{j=1}^Nx^{i,j}w_1^{j,k} + b \tag{8} \]
根据\((5)(6)(7)\)可得:
\[ q_{a_1}^{i,k} = M\sum_{j=1}^N(q_x^{i,j}-Z_x)(q_{w_1}^{j,k}-Z_{w_1})+ \frac{S_b}{S_a}(q_b-Z_b) +Z_{a_1} \tag{9} \]
由于前面的\(\sum_{j=1}^N(q_x^{i,j}-Z_x)(q_{w_1}^{j,k}-Z_{w_1})\)的结果通常会用int32的整数存储,因此bias通常也会选择量化到int32,这里可以直接用\(S_wS_x\)来代替\(S_b\),而\(Z_b\)则直接记为0,则公式\((9)\)可化为:
\[ q_{a_1}^{i,k} = M(\sum_{j=1}^Nq_x^{i,j}q_{w_1}^{j,k}-\sum_{j=1}^NZ_xq_{w_1}^{j,k}-\sum_{j=1}^N q_x^{i,j}Z_{w_1}+\sum_{j=1}^N Z_xZ_{w_1}+q_b)+Z_{a_1} \]
其中与x无关的项如\(q_{w_1}^{j,k}Z_x、Z_{w_1}Z_x、q_b\)都是推理前事先计算好的,这里进一步可以用函数表示上式为:
\[ q_{a_1} = M\cdot conv(q_x - Z_x) + Z_{a_1} \tag{10} \]
其中\(M=\frac{S_{w_1}S_x}{S_{a_1}}\)
上述中“直接用\(S_wS_x\)来代替\(S_b\)”的解释:
如果使用原来的\(S_b\)和\(Z_b\),将\(r_b\)映射到\(q_b\),使用\(S_wS_x\)和\(Z_b=0\)映射到得的\(q_b'\),则\(q_b\)和\(q_b'\)在数值上确实不一样,但性质上,\(S_b、Z_b\)和\(S_wS_x、0\)才能实现\(r_b\)与\(q_b\)之间的映射与反映射关系,只是值存在一定的差异,但是这个数值的不一样,大量的工程实践表明是在可接受范围内的,因此,这里是可以直接代替的
代码实现:
1
2
3
4
5
6
7
8def quantize_inference(self, x):
x = x - self.qi.zero_point
x = self.conv_module(x)
x = self.M * x
x.round_()
x = x + self.qo.zero_point
x.clamp_(0., 2.**self.num_bits-1.).round_()
return得到conv的输出后,不用反量化回去,直接继续后面的计算即可,下面开始进行ReLU运算
对于ReLU激活函数,量化后,计算的公式不再是\(y=\max (x, 0)\),而是\(y=\max(x,Z)\),即\(q_{a_2}=\max(q_{a_1},Z_{a_1})\) ,并且由于ReLU本身不会对数值进行任何运算,而只是相当于进行了简单的截断,因此对于量化的ReLU而言,scale和zero point前后一样并不影响结果。
另外对于relu、maxpooling这类激活函数而言,它们会沿用上一层输出的min、max,不需要额外统计,即\(a_1、a_2\)会共享相同的min、max
注:关于激活函数量化
得到relu的输出后,可以继续计算fc层,和式\((9)(10)\)一样,则
\[ q_y^{i,k}=M\sum_{j=1}^N(q_{a_2}^{i,j}-Z_{a_2})(q_{w_2}^{j,k}-Z_{w_2})+Z_y \tag{11} \]
\[ q_y=M\cdot Linear(q_{a_2}-Z_{a_2})+Z_y \]
然后通过\(y=S_y(q_y-Z_y)\)反量化回去,就可以得到结果了!
其中\(S_y、Z_y\)表示输出y对应的scale和zero point。
实际上,上述量化推理过程是在训练完成的全精度模型上进行的,所以可以事先统计出权重weight和各个中间特征输出的min和max,并以此计算出各个scale和zero point,然后把weight量化成int8或int16型的整数后,再进行上述量化推理过程!
总结
以上这种对训练后的模型进行量化与推理加速的过程,也被称为后训练量化,后训练量化相对比较简单,但有时候不能保证足够的精度,因此有时候也需要采用另一种方法量化感知训练