int8量化--调研

量化简介

https://arxiv.org/abs/1806.08342
深度学习中网络的加速主要有如下的几种方式:

  • 设计高效且小的网络,如MobileNet系列、shuffleNet系列、VoVNet等;
  • 从大的模型开始通过量化、剪裁、蒸馏等压缩技术实现网络的小型化;
  • 在inference阶段使用特殊的计算库实现计算的加速,比如MKL、TensorRT等;

1. 量化

在上面的方法中最简单的方法就是降低模型运算过程中的bits数量,降低到16bits、8bits甚至2bits。这样做具有如下的优点:

1)在众多的使用场合与模型中可以广泛使用,并不需要设计再设计特殊的网络结构,而且量化是从训练好的模型上进行的,并不需要重训练,且损失精度比较少。目前现有的大部分硬件计算资源都是支持这样的运算模式的,因而也不需要重新设计专门的硬件;
2)模型量化可以显著减少模型的尺寸大小,理论上可以减少模型的尺寸4倍,并且模型的性能损失很小;
3)使用更低bits数的模型可以减少在运算过程中的内存与缓存消耗
4)大多数的处理器对8bits的运算更快;
5)8bits的计算拥有更好的实现效率,因而进行对应的计算所消耗的能量更少;

  • 为什么量化有用?
    • 因为CNN对噪声不敏感。
    • 每个层的weights范围基本都是确定的,且波动不大,适合量化压缩;
  • 为什么不直接训练低精度的模型?
    • 因为你训练是需要反向传播和梯度下降的,int8就非常不好做

2. 对称和非对称个量化

2.1 对称量化(int8)

在这里插入图片描述
对于反向的过程描述为:

在这里插入图片描述

2.2 非对称量化(uint8)

在这里插入图片描述

3.训练量化与后量化

目前最简单的实现方案是英伟达的tensorRT方案,直接量化,无需retrain,实现简单;

其次就是谷歌的那套方案,稍显复杂需要retrain;

retrain的要求就是,你的权值、激活值(实测对最终精度的影响不是很大)都必须是分布比较均匀的,也就是方差不要太大。其次是能否控制每层的输出在一定的范围内,这对我们做int8量化时,溢出的处理很有帮助。

3.1 训练模拟量化(Quantize-Aware Training)

3.2TensorRT后训练量化(Post-Training Quantization)

公开,未开源

  • 只能用他的那一套工具(tensorRT)来进行量化、部署;当
  • 无法导出校准参数,移动端直接用;
  • 导不出中间参数的,无源码,安装包内的python接口调用的so文件;
  • ppt链接如下:
    • https://on-demand.gputechconf.com/gtc/2017/presentation/s7310-8-bit-inference-with-tensorrt.pdf

量化原理

  • 简单的max-max 映射: 针对均匀分布;只要数据分布的不是很均匀,那么精度损失是很大很明显的;
    在这里插入图片描述

  • 优化后:
    在这里插入图片描述

    • 为什么量化是可以保证原信息的?
      这个原因就好比高清图跟低分辨率图的区别,只要你的目标是大体识别出图中是啥这一信息,那么低分辨率的图也是允许的。

    • 为什么说最大值映射会精度损失严重?
      由于正负分布很不均匀,如果按照对称最大值映射(原意是为了尽可能多地保留原信息)的话,那么+max那边有一块区域就浪费了,也就是说scale到int8后,int8的动态范围就更小了,举个极限的例子就是量化后原本int8的动态范围只剩1bit了(就是正的样本没有,负的全部扎堆在一个很小的值附近),就是上面说到的满屏马赛克~这种情况下。。。那还表示个毛的原信息啊!

    • 为什么右边的饱和截取就ok呢?

      因为非饱和截取的问题是当数据分布极不均匀的时候,有很多动态范围是被浪费的,而饱和截取就是弥补这个问题的。

      当你数据分布很不均匀的时候,如图左边比右边多,那么我把原始信息在影射之前就截断一部分,然后构成对称且分布良好的截断信息,再把这个信息映射到int8上去,那么就不会有动态范围资源被浪费了,

      像上图这样,先找一个阀值T,然后低于最低阀值的就全部都饱和映射到-127上,如上图的左边的三个红色的点就是这么处理的。

    (很自然的思路~把无关的高频细节给去掉。网络图像压缩技术不就是这么整的么,PCA、傅立叶分解的思路都是这样!抓住事物的主要矛盾,忽略细节,从而提高整体性能!就像机器学习里的正则化优化不也是这样么,避免过于钻到细节里面从而产生过拟合啊!这么一想,其实,我们人生不也是这样么?什么事情都得抠死理,钻牛角尖么?!!有时候主动放弃一些东西首先你的人生肯定会轻松很多,其次说不定会收获到更稳定的人生幸福值(泛化性能)呢!)

  • 那么我们的问题就转换为如何寻找最优的阀值T使得精度的损失最小

NVIDIA选择的是KL-divergence,其实就是相对熵,那为什么要选择相对熵呢?而不是其他的别的什么呢?因为相对熵表述的就是两个分布的差异程度,放到我们的情境里面来就是量化前后两个分布的差异程度,差异最小就是最好的了~因此问题转换为求相对熵的最小值!

KL散度就是来精确测量这种最优和次优之间的差异(由于选择了错误的编码导致的)。在这里F32就是原来的最优编码,int8就是次优的编码,我们用KL散度来描述这两种编码之间的差异;

int类的还比较好,因为最小的单位就是整型也就是1,0->1->2是直接跳跃的,因此我们计算整型数据集合的概率分布的时候直接统计求hist就好了,但是float呢?精度理论上是很小的呀,你总得定个边界吧?这样才好把数据归类啊,因此就类似于整型的我们分bin,我们分bin才能求bin的概率!那么分多少个bin呢?

NVIDAIA给的是2048个bin(maxnet代码里面给的是8000bins),比128bin要多,但是又不会多处太多从而迭代太多影响计算速度!

处理流程

NVIDIA的运算流程:
在这里插入图片描述

宏观处理流程如下,

  • 首先准备一个校准数据集,然后对每一层:
    • 收集激活值的直方图;
    • 基于不同的阀址产生不同的量化分布;
    • 然后计算每个分布与原分布的相对熵,然后选择熵最少的一个,也就是跟原分布最像的一个,此时阀值就选出来啦,对应的scale值也就出来了。

最关键:校准算法部分

calibration:基于实验的迭代搜索阀值。
校准是其核心部分,应用程序提供一个样本数据集(最好是验证集的子集),称为**“校准数据集”**,它用来做所谓的校准。

在校准数据集上运行FP32推理。收集激活的直方图,并生成一组具有不同阈值的8位表示法,并选择具有最少kl散度的表示;kl-散度是在参考分布(即FP32激活)和量化分布之间(即8位量化激活)之间。

INT8量化实现-校准算法

公式是:FP32 Tensor (T) = scale_factor(sf) * 8-bit Tensor(t),bias实验得知可去掉。
在这里插入图片描述

校准算法伪代码
首先看上图的原理,就是把大范围的一个值给缩小到一个小范围的值(注意是等比例的缩小)。

但是这里有个疑问啊,就是我收集的数据是正负都有的,那么这里的2048bins指的是正范围还是负范围呢?因为我看到后面都是量化的128bins里面去的,也就是说只管了int8(256)的一半!具体细节下篇文章中的代码实现部分详细分析;

这里看他的意思就是输入为[0, 2048] bins,然后想办法把这么大的分布给找到一个合理的阀值T然后把阀值内的bins映射到int8的128个bins里面来,最终而且信息熵损失是最少的。

怎么做的呢?

  • 首先不断地截断参考样本P,长度从128开始到2048,为什么从128开始呢?因为截断的长度为128的话,那么我们直接一一对应就好了,完全不用衰减因子了;
  • 将截断区外的值全部求和;
  • 截断区外的值加到截断样本P的最后一个值之上;(截断区之外的值为什么要加到截断区内最后一个值呢?我个人理解就是有两个原因,其一是求P的概率分布时,需要总的P总值,其二将截断区之外的加到截断P的最后,这样是尽可能地将截断后的信息给加进来。)
  • 求得样本P的概率分布;
  • 创建样本Q,其元素的值为截断样本P的int8量化值;
  • 将Q样本长度拓展到 i ,使得和原样本P具有相同长度;
  • 求得Q的概率分布;
  • 然后就求P、Q的KL散度值就好啦~
    上面就是一个循环,不断地构造P和Q,并计算相对熵,然后找到最小(截断长度为m)的相对熵,此时表示Q能极好地拟合P分布了。

而阀值就等于(m + 0.5)*一个bin的长度;

4.权重量化和数据量化

4.1 权重量化

对于权重值的量化来说,权重值在进行推理加速时均已确定,因此不需要对权重进行校准。在算法中, dmin和 dmax可以直接使用权重值的最大值和最小值。只适用于对性能要求不高且要求模型大小比较高的场景下,这种方法一般不予以采纳。根据算法验证结果:

  • **卷积层:**每个卷积核采用一组独立的量化系数,量化后推理精度较高。因此,卷积层权重值的量化根据卷积核数量分组进行,计算得到的缩放比例与量化偏置的数量与卷积核数量相同。
  • **全连接层:**其权重值的量化通常使用一组缩放比例和量化偏置。

4.2 权重与激活量化

这里除了对权重参数进行per-channel的量化之外,还需要额外准备校准数据集,以提供对激活的量化参数的确定,这样的方法其量化的结果也是比较好的。在TensorRT中也是采用了这样的量化方式进行网络的量化技术,也达到了很不错的性能。

4.3 数据量化

数据量化是对每个要量化的层的输入数据进行统计,每个层计算出最优一组缩放比例与量化偏置。因为数据是推理计算的中间结果,其数据的范围与输入相关,需要使用一组参考(推理场景数据集)输入作为激励,得到每个层的输入数据用于确定量化 dmin 和 dmax 。实践中,常常通过对测试数据集进行采样,得到小批次数据集进行量化。由于数据的范围与输入相关,为了使确定的 [dmin~dmax] 在网络接收不同输入数据时有更好的鲁棒性,因此提出基于统计分布确定 [dmin~dmax] 的方案。统计量化后数据的统计分布与原始高精度数据的分布差异性,寻求最小化的差异性,即为最优 [dmin~dmax] 。据此计算offset和scale即可。

4.4 量化性能对比

只量化权重对性能的影响,其中包含量化粒度的对比(per-channel、per-layer):
在这里插入图片描述
权重与激活都量化对于性能的影响:

在这里插入图片描述
结论:

  1. Per-channel的权值量化与activation量化可以使得量化之后的模型与浮点型运算得到的结果相差尽量减小;

  2. activation的量化可以有效减少精度上的损失,下面的这几个操作会使得activation的范围较小:(a)在batchnorm之后没有添加scale;(b)ReLU6将激活之后的值限定在( 0 , 6 ) (0,6)(0,6)的范围内。

  3. 拥有更多参数的模型,例如ResNet与Inception-V3,其量化的鲁棒性是好于参数少的模型的;

  4. 当权重在层的粒度(非channel)上被量化时,会有很大的性能下降(这主要是由于batchnorm导致的数值波动),特别对于MobileNet网络;

  5. 大多数情况下由量化引入的性能下降是由于权值的量化过程;

量化方法汇总链接:

https://github.com/Ewenwan/MVision/tree/master/CNN/Deep_Compression/quantization

Pytorch支持int8量化,

PyTorch支持在具有AVX2支持或者更高版本的x86 CPU或者ARM CPU上运行量化运算符。
pytorch提供了三种量化模型的方法:

  • 训练后动态量化:最简单的量化形式,权重被提前量化,激活在推理过程中被动态量化
  • 训练后静态量化:最常用的量化形式,权重提前量化,并且基于观察校准过程中模型的行为来预先计算激活张量的比例因子和偏差。
  • 量化意识训练:在极少数情况下,训练后量化不能提供足够的准确性,可以使用torch.quantization.FakeQuantize函数通过模拟量化来进行训练。具体如何操作可以参考PyTorch的量化教程

如果想要在GPU上操作,可以先使用torch.nn.export函数将模型转换成onnx格式,然后就可以放到TensorRT框架上inference了。(TensorRT目前不能直接解析Pytorch的网络模型,需要转换成onnx)

ref:

https://zhuanlan.zhihu.com/p/266875176
https://blog.csdn.net/m_buddy/article/details/103640139
https://zhuanlan.zhihu.com/p/58182172

猜你喜欢

转载自blog.csdn.net/weixin_41521681/article/details/115104698