[TOC]

文章参考:https://blog.csdn.net/u010420283/article/details/114483073

文章参考:https://www.cvmart.net/community/detail/6015

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

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

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

文章参考:https://www.cnblogs.com/ywheunji/p/13348190.html

概述

随着深度学习的发展,神经网络被广泛应用于各种领域,模型性能的提高同时也引入了巨大的参数量和计算量。模型量化是一种将浮点计算转成低比特定点计算的技术,可以有效的降低模型计算强度、参数大小和内存消耗,但往往带来巨大的精度损失。尤其是在极低比特(<4bit)、二值网络(1bit)、甚至将梯度进行量化时,带来的精度挑战更大。

这篇文章比较详细,所以下面这个图是这篇文章的一个整体目录。当然啦,除了非常多的文字,这篇文章塞了59个公式,涉及到量化在推理和训练的内容。虽然可能看得很辛苦,但是也希望可以多多支持。打公式不容易,发现错误欢迎评论留言指正。

与FP32类型相比,FP16、INT8、INT4的低精度类型所占用空间更小,因此对应的存储空间和传输时间都可以大幅下降。以手机为例,为了提供更人性和智能的服务,现在越来越多的OS和APP集成了深度学习的功能,自然需要包含大量的模型及权重文件。以经典的AlexNet为例,原始权重文件的大小已经超过了200MB,而最近出现的新模型正在往结构更复杂、参数更多的方向发展。显然,低精度类型的空间受益还是很明显的。低比特的计算性能也更高,INT8相对比FP32的加速比可达到3倍甚至更高,功耗上也对应有所减少。

模型量化即以较低的推理精度损失将连续取值(或者大量可能的离散取值)的浮点型模型权重或流经模型的张量数据定点近似(通常为int8)为有限多个(或较少的)离散值的过程,它是以更少位数的数据类型用于近似表示32位有限范围浮点型数据的过程,而模型的输入输出依然是浮点型,从而达到减少模型尺寸大小、减少模型内存消耗及加快模型推理速度等目标。

首先量化会损失精度,这相当于给网络引入了噪声,但是神经网络一般对噪声是不太敏感的,只要控制好量化的程度,对高级任务精度影响可以做到很小。

其次,传统的卷积操作都是使用FP32浮点,浮点运算时需要很多时间周期来完成,但是如果我们将权重参数和激活在输入各个层之前量化到INT8,位数少了乘法操作少了,而且此时做的卷积操作都是整型的乘加运算,比浮点快很多,运算结束后再将结果乘上scale_factor变回FP32,这整个过程就比传统卷积方式快很多。

提前从体系结构的考量角度思考量化带来的另一个好处是节能和芯片面积,怎么理解呢?每个数使用了更少的位数,做运算时需要搬运的数据量少了,减少了访存开销(节能),同时所需的乘法器数目也减少(减少芯片面积)。

img

量化并不是什么新知识,我们在对图像做预处理时就用到了量化。回想一下,我们通常会将一张 uint8 类型、数值范围在 0255 的图片归一成 float32 类型、数值范围在 0.01.0 的张量,这个过程就是反量化。类似地,我们经常将网络输出的范围在 0.01.0 之间的张量调整成数值为 0255、uint8 类型的图片数据,这个过程就是量化

所以量化本质上只是对数值范围的重新调整,可以「粗略」理解为是一种线性映射。之所以加「粗略」二字,是因为有些论文会用非线性量化,但目前在工业界落地的还都是线性量化,所以本文只讨论线性量化的方案。

不过,可以明显看出,反量化一般没有信息损失,而量化一般都会有精度损失。这也非常好理解,float32 能保存的数值范围本身就比 uint8 多,因此必定有大量数值无法用 uint8 表示,只能四舍五入成 uint8 型的数值。量化模型和全精度模型的误差也来自四舍五入的 clip 操作。

模型量化优点

模型量化优点:

  1. 减小模型尺寸,模型存储主要是每个层的权值,量化后模型占用空间小,32bit可以缩减至bit,可减少75%的模型大小
  2. 减少存储空间,激活值用8bit后,减小了内存的访问带宽需求,在边缘侧存储空间不足时更具有意义。
  3. 易于在线升级,模型更小意味着更加容易传输
  4. 减少内存耗用,更小的模型大小意味着不需要更多的内存
  5. 加快推理速度,访问一次32位浮点型可以访问四次int8整型,单位时间内处理定点运算指令比浮点数运算指令多,运算更快。
  6. 减少设备功耗,内存耗用少了推理速度快了自然减少了设备功耗
  7. 支持微处理器,有些微处理器属于8位的,低功耗运行浮点运算速度慢,需要进行8bit量化

模型量化缺点

  • 模型量化增加了操作复杂度,在量化时需要做一些特殊的处理,否则精度损失更严重
  • 模型量化会损失一定的精度,虽然在微调后可以减少精度损失,但推理精度确实下降

为什么进行量化

量化方法

量化并不是什么新知识,我们在对图像做预处理时就用到了量化。回想一下,我们通常会将一张 uint8 类型、数值范围在 0255 的图片归一成 float32 类型、数值范围在 0.01.0 的张量,这个过程就是反量化。类似地,我们经常将网络输出的范围在 0.01.0 之间的张量调整成数值为 0255、uint8 类型的图片数据,这个过程就是量化。所以量化本质上只是对数值范围的重新调整,可以「粗略」理解为是一种线性映射。(之所以加「粗略」二字,是因为有些论文会用非线性量化,但目前在工业界落地的还都是线性量化,所以本文只讨论线性量化的方案)。

不过,可以明显看出,反量化一般没有信息损失,而量化一般都会有精度损失。这也非常好理解,float32 能保存的数值范围本身就比 uint8 多,因此必定有大量数值无法用 uint8 表示,只能四舍五入成 uint8 型的数值。量化模型和全精度模型的误差也来自四舍五入的 clip 操作。

一般而言,量化方案主要分为两种:在线量化(On Quantization)和离线量化(Off Quantization),在线量化指的是感知训练量化(Aware Quantization),离线量化指的是训练后量化(Post Quantization)。训练量化根据名字的意思很好理解,其实就是在网络模型训练阶段采用量化方案进行量化。训练后量化中的量化方案跟训练并不相关,主要是在模型离线工具(模型转换工具的时候)采用量化方案进行量化。

  1. 感知量化训练(Aware Quantization)

实际上无论是Tensorflow、MindSpore、Pytroch的量化感知训练是一种伪量化的过程,它是在可识别的某些操作内嵌入伪量化节点(fake quantization op),用以统计训练时流经该节点数据的最大最小值,便于在使用端测转换工具(推理转换工具)的时候,转换成端侧需要的格式时进行量化使用。

目的是减少精度损失,其参与模型训练的前向推理过程令模型获得量化损失的差值,但梯度更新需要在浮点下进行,因而其并不参与反向传播过程。

某些操作无法添加伪量化节点,这时候就需要人为的去统计某些操作的最大最小值,但如果统计不准那么将会带来较大的精度损失,因而需要较谨慎检查哪些操作无法添加伪量化节点。

值得注意的是,伪量化节点的意义在于统计流经数据的最大最小值,并参与前向传播,让损失函数的值增大,优化器感知到这个损失值得增加,并进行持续性地反向传播学习,进一步提高因为伪量化操作而引起的精度下降,从而提升精确度。

值得注意的是,训练时候的原理与在端测推理的时候,其工作原理并不一致。

2. 训练后动态量化(Post Dynamic Quantization)

训练后动态量化是针对已训练好的模型来说的,针对大部分已训练,未做任何量化处理的模型来说均可用此方法进行模型量化。

其工作比较简单,在端测转换工具的时候,对网络模型的权重进行统计其每一层卷积的layer或者channel的最大值和最小值,然后通过量化公式对数据进行byte转换。这样得到的权重参数比以前小1/4。推理的时候,在内存初始化的时候对网络模型中的权重进行反量化操作变成float进行正常的推理。

3. 训练后校正量化(Post Calibration Quantization)

训练后静态量化,同时也称为校正量化或者数据集量化。其原理是对于Mindspore在端测低比特推理的时候(Inference),需要生成一个校准表来量化模型。这个量化校准表的生成需要输入有代表性的数据集,对于分类任务建议输入五百张到一千张有代表性的图片,最好每个类都要包括(即数据无偏)。

量化分类

按照量化映射方法又可以分为对称量化和非对称量化

一般而言,无论per channel还是per layer量化方案,对于weight权重的量化使用对称量化,对于activate激活的量化使用非对称量化。

其原因是对于权重而言,其数据的分布为对称的如或,因此采用对称量化可以进一步有效降低计算量,使得数据分布更加符合其真实的分布;activate激活在网络中通常使用ReLU和ReLU6等作为网络模型的激活函数,其数据分布集中在或者,如果采用对称方案会加剧数据的离散程度,另外一个原理是会造成数据的浪费,如[-128,0]之间并不存储任何数据,只有[0,127]有数据分布。

006C3FgEgy1gyb2rr7w9gj30k006aweq

下面以int8作为量化的标准,介绍一下两种量化算法的主要细节。

映射方法

对称量化symmetry(int8 -> -128-127)

img

1557203-20200720210258413-1058842107

从上图我们可以看出。量化之后的数据范围是[-128,127]。

对称的量化算法原始浮点精度数据与量化后INT8数据的转换如下:
$$
float=scale×int
$$
其中,scale默认是float32浮点数,为了能够表示正数和负数,int采用signed int8的数值类型。通过将float32原始高精度数据转换到signed int8数据的操作如下,其中round为取整函数,量化算法需要确定的数值即为常数scale:

$$
int = round*(1/scale)
$$
所谓的对称量化,就是将原浮点数的范围由[$X_{min}$, $X_{max}$]扩充为[$-X_{max}$,$ X_{max}$],这里假定 |$X_{max}$|>|$X_{min}$|,这个假定很好理解,为了对称之后能够囊括进所有的数据。这样原有浮点数还是一个对称的数据范围。同时量化之后,我们可以使用有符号整数int8的数据范围[-128,127]来对称表示原有的float数据范围。

对于fp32的值若均匀分布在0左右,映射后的值也会均匀分布,若fp32的值分布不均匀,映射后不能充分利用映射空间。

为了简单理解,咱们举个例子:

假设待量化的数据范围是[-34.0f,127.0f]。那么我们取待量化的数据范围是[-127.0f,127f]。那么计算出的scale的值就是0.1。

非对称量化asymmetric(uint8 -> 0-255)

下面我们介绍一下非对称量化。

img

1557203-20200720211942785-1161421924

非对称的量化算法与对称的量化算法的主要区别在于数据转换的方式不同,如下,同样需要确定 $$ scale $$与offset这两个常数:
$$
r = S(q-Z) \tag{1}
$$
确定后通过原始float32高精度数据计算得到uint8数据的转换即为如下公式所示:
$$
q = round(\frac{r}{S}+Z) \tag{2}
$$
其中,scale是float32浮点数,uint为unsigned INT8定点数,$$offset$$是int32定点数。其表示的数据范围为$$[scale×offset,scale×(255+offset) ]$$。若待量化数据的取值范围为 $$[x_{min},x_{max}]$$ ,则scale的计算公式如下:
$$
scale=(x_{max}−x_{min})/(Q_{max}−Q_{min})
$$
offset的计算方式如下:

$$
offset(zero;point)=Q_{min}−round(x_{min}/scale)
$$
对于权值和数据的量化,都采用上述公式的方案进行量化,$$ x_{min} $$ 和 $$ x_{max} $$ 为待量化参数的最小值和最大值, $$ Q_{max} $$和 $$ Q_{min} $$ 为:
$$
Q_{max} =2^n,Q_{min}=0
$$

通过收缩因子$scale$和零点zero point(用于偏移),将float32张量 的 min/max 映射分别映射到 8-bit 数据的 min/max ,有对应的映射公式

加入零点的原因,0有特殊意义如padding,若不加零点z,量化后会映射到0,加入零点z后,浮点0量化后映射到0-255中间的一个数字。

可以无误差地量化浮点数中的数据 0,从而减少补零操作(比如卷积中的padding zero)在量化中产生额外的误差。

缺点是只有正数,有的处理方式是把0点左移到-128,范围控制在[-128,128]中,但还是非对称量化。

对称与非对称的优缺点:

非对称可以处理好float32数据分布不均匀的情况。

若对称算法产生的量化后数据很多都是在【0,127】内,左边的范围利用很少,减弱了量化数据的表示能力,影响模型精度。

量化的粒度

一般分为通道级量化和张量级量化,(不具体介绍概念)

常见推理推理引擎量化方案

QNN是非对称量化

paddle是对称量化

量化的粒度

一般分为通道级量化和张量级量化,(不具体介绍概念)

通常张量的每一个通道代表一类特征,因此不同通道之间可能有数据分布相差较大情况,此时适合使用通道级量化

一般而言卷积中建议对权值使用非对称通道级的量化,激活采用张量级量化

TensorRT后量化方法:

不饱和的线性量化就是量化后的数据扎堆在某一个范围(导致本不该相同的变为相同的),存在一些范围利用少的情况,会导致精度损失较大。

TensorRT量化方法:

  激活值饱和量化(右图),选择合适的阈值T

  权值非饱和量化(左图)

img

TensorRT另一个主要的优化是在层间融合或张量融合

模型推理的时候,每一次的操作都是由gpu启动不同的cuda核心来完成的,大量的时间花在cuda核心启动和读写操作上,造成了内存带宽的瓶颈和GPU资源浪费。TENSORRT通过层间融合,横向融合把卷积偏置激活合并成一个结构,并且只占用一个cuda核心,纵向融合把结构相同,权值不同的层合并成一个更宽的层,也是占用一个cuda核心,因此整个模型结构更小更快。

量化公式

由浮点到定点的量化公式。比如进行int8的量化,数据范围是[-128,127],最大值最小值分别是Xmax, Xmin,X_q表示量化后的数据,X_f表示浮点数据。

1
2
3
4
5
6
7
X_q=X_f/scale +zero
# 缩放比例就是. 就是最大值减去最小值相减除以255
scale=(Xmax-Xmin)/(127-(-128))=(Xmax-Xmin)/255

zero=0-round(Xmin/scale)
或者
zero=255-round(Xmax/scale)

量化初始化

在一般的端测推理的时候,由于网络模型的输入并没有伪量化节点记录输入tensor的最大值和最小值,因为无法求得量化参数scale和offset,这个时候推理框架许可要根据输入数据的标准差deviation和均值mean进行计算,得到对应的量化参数。

其中标准差Standard Deviation的计算公式为下:

其对应的scale为:

image-20220720190005482

Offset的计算为:

image-20220720190023703

值得注意的是,由于在其他端测推理框架(如tflite)需要手工输入标准差deviation和均值mean,容易导致对数据分析、数据输入的错误和框架的割裂,因此感知量化训练的时候会对模型的输入和输出加上fakeQuant伪量化节点,记录数据min和max,从而求得Mindspore端测用到input tensor的scale和offset。

1.1.2 感知量化训练

2. BN折叠

初步实施方案中构图比较复杂的主要集中在如何拆分BN层,本节将会对拆分的原理进行细化,落实确定到每一个算子上面,包括每个算子的具体计算公式,控制原理。