"Quantization and Training of Neural Networks for Efficient Integer-Arithmetic-Only Inference"量化paper解读

概述

神经网络模型的量化是模型压缩的一种方式,在不更改模型结构的情况下,压缩模型的参数比特,减小模型大小,加速模型推理速度,在部署中常用的一种方式。
常见的模型一般都是32bits的浮点参数,我们会使用16F,int8, int4或者2bit来压缩,16F本质上来说并不是严格意义上的量化,只不过是利用更少的范围和更小的分辨率,在混合精度训练中经常用到,而且16F也是浮点数运算,提升不明显。所以8bit或者更少的bit量化才会很大程度上提升模型的效率,下面以8Bit为例来详细说一下量化原理。

我们以17年的这篇Paper为导入,是google出品,也是精品。这篇文章提出了模拟训练量化,也就是在训练过程中模拟量化过程,使得模型能够往适应量化的方向学习,学习到的参数也是极其适合量化的,正是这种差异性或者误差式学习,使得量化过程中可能出现的误差被不断地弥补,训练出的模型在实际量化时也比PTQ,即后处理量化更加准确。我们重点说一下。

首先,模型量化,究竟量化的是什么,目前的话,一般会量化权重和输出特征值,这两者是参与模型运算的操作对象,如卷积等。那是怎么做的呢?以Int8为例。

首先我们需要用int8来表示32F的参数,一般我们知道模型参数都是稀疏化的比较小的数,那么int8是-128-127之间的,也就是256的分辨率,那么从小数到整数肯定有个映射,或者一个线性比例关系,这个就是我们量化过程中比较重要的参数,scale。我们就是要得到这个scale,只有得到scale,我们才能把模型参数从小数量化到整数,才能把小数的浮点运算变成整数的定点运算。那怎么量化呢?

那就是这篇paper的关键了。我们如果直接训练好一个模型,再根据参数的最大最小值做一个放缩,好像这也能实现量化,也能达到小数到整数的映射,但是这样映射出的模型会掉点很严重,因为网络不适应这种缩放,这种缩放对于某些参数值存在数量级差异的层很不友好,会使得值很小的参数量化误差特别大,**这就是因为在用浮点数训练的时候,网络不知道你要量化,所以学出来的参数分布奇奇怪怪,有大有小,差异巨大,对你量化很不友好。**所以,问题来了,如何才能得到一批适合量化的模型参数呢,很显然,那么我们在训练的时候就要把这种意识传递给网络,就是告诉网络我们待会要进行量化了。那么具体怎么做呢?

既然想让网络懂得我要量化的意图,那么我们不妨在训练过程中模拟下量化的操作,本来参数的分布很广,信息挺丰富,学起来比较容易,那我一量化,肯定就会出现误差了,好了,那我们的目的达到了,------创造量化的误差让网络主动学习弥补这种误差。那这样网络就会朝着适合量化的方向去调整参数了,而不是像之前一样,到处去寻求最优解,这里我们相当于给网络加了一个约束,这个约束是参数可量化性,跟常用的正则化之类的约束有类似的地方。

模拟量化的意图和目的讲清楚了,那具体怎么模拟呢?我们来看看。

pt模型int8量化python int4量化_最大最小值

上图的公式是原文中给出的,我们要量化,首先要找scale,这个之前提到了,那么最直观的就是把某一层的参数最大最小值映射到0到255,所以s(a,b,n)就是这样得到的,这里a,b就是我们给定的最大最小范围,对于权重,我们直接取权重的最大最小值就行了,对于特征值,我们待会会讲到,会有别的处理方式。然后,找到scale之后,我们就可以利用scale把当前参数r映射到一个整数,这里肯定有一个取整操作,这就是误差,因为r并不一定是s的整数倍,那么这个误差就是我们创造出来想要网络学习的,在获得这个量化整数q之后,我们再反量化回去,即再乘上s,得到r’,经过这个过程r’和r就不一致,所以原来在r上学到的分布就不适合r’了,网络就该继续调整,以适合r’的分布了,当网络学好之后,我们会发现,r就会趋向于s的整数倍了,这就是我们说的可量化性。

那么特征又是怎么量化的呢?
特征量化和权重不太一样,因为权重一旦学习好就是定值,不随输入改变,但是输出特征值是一个随输入变化的,是一个动态范围,那么我们再选择量化区间时,就不能选择某一个输出特征的最大最小值,这样也不稳定,我们会利用统计方法得到训练数据的一个最大最小的均值吧,类似bn层的ruuning_mean,然后其他的量化反量化则是和权重一样的。

实际在Inference的时候,网络是怎样计算的呢,是否还是有浮点运算呢?

这个其实我也有疑惑,之前在看tensorrt的后处理量化时,有人说我们将某一次定点运算的结果去进行再次量化的时候,这时我们需要进行一个反量化,就是把前一次量化的scale乘上去,然后得到一个浮点结果再进行量化。现在看来这篇文章之后,作者给了一个优化方案,如下图。

pt模型int8量化python int4量化_最大最小值_02


这里是描述了一个简单的一次运算,可以理解成weight和feature在做卷积,然后我们在运算之前提前把两者量化,利用量化值q去运算,然后对于运算结果值,我们也需要量化,但是这里,把这个第二次的量化也融入这个运算过程,即我们提前利用S1,S2,S3计算出M,然后将M表示成定点的数,然后把浮点运算转换成定点运算,实现了无浮点运算。如下图所示。

pt模型int8量化python int4量化_卷积_03

模拟量化和反量化怎么插入网络呢?

如下图所示,我们在运算层之前进行权重量化,在激活层之后进行特征量化。

(这里看似特征量化在激活层之后,其实也可以看成是下一个卷积层的输入,所以,对于下一个卷积层来说,输入和权重是同时量化的,而激活函数层则没有进行量化,是浮点型运算)

pt模型int8量化python int4量化_卷积_04

结论

其实模拟训练量化就是在做一件事,让参数分布离散化,含有的信息更少了,熵减了,int8就是用1/256的分辨率去约束参数,让参数在连续的取值空间中聚集到更少的维度上去,所以我们对于一些比较重要的层,一般是输出层,我们可以选择不量化,使得这层的信息更多一些,能够保证模型的性能。比如我们可以在中间层使用2W2F(权重和特征都是2Bit量化),在输出层使用8W16F来量化,这样就能使得输出层含有更多信息,而这带来的只不过是输出层的多一些计算开销而已,一般可以忽略。