https://zhuanlan.zhihu.com/p/132561405
模型量化是一种将浮点计算转成低比特定点计算的技术,可以有效的降低模型计算强度、参数大小和内存消耗,但往往带来巨大的精度损失。尤其是在极低比特(<4bit)、二值网络(1bit)、甚至将梯度进行量化时,带来的精度挑战更大。
Part 1 量化综述
什么是模型量化?为什么要进行模型量化?
模型量化是由模型、量化两个词组成。我们要准确理解模型量化,要看这两个词分别是什么意思。
在计算机视觉、深度学习的语境下,模型特指卷积神经网络,用于提取图像/视频视觉特征。
量化是指将信号的连续取值近似为有限多个离散值的过程。可理解成一种信息压缩的方法。在计算机系统上考虑这个概念,一般用“低比特”来表示。也有人称量化为“定点化”,但是严格来讲所表示的范围是缩小的。定点化特指scale为2的幂次的线性量化,是一种更加实用的量化方法。
卷积神经网络具有很好的精度,甚至在一些任务上比如人脸识别、图像分类,已经超越了人类精度。但其缺点也比较明显,具有较大的参数量,计算量,以及内存占用。而模型量化可以缓解现有卷积神经网络参数量大、计算量大、内存占用多等问题,具有为神经网络压缩参数、提升速度、降低内存占用等“潜在”优势。为什么“潜在”是加引号的呢?因为想同时达到这三个特性并不容易,在实际应用过程中存在诸多限制和前提条件。
另外,由于模型量化是一种近似算法方法,精度损失是一个严峻的问题,大部分的研究都在关注这一问题。
压缩参数
模型量化在最初的定义里是为了压缩模型参数,比如韩松在ICLR2016上获得best paper的论文,首次提出了参数量化方法。其使用k-mean聚类,让相近的数值聚类到同一个聚类中心,复用同一个数值,从而达到用更少的数值表示更多的数,这是量化操作的一种方案。反过来,从量化数变到原始数的过程,称之为反量化,反量化操作完之后,模型就可以按照原来的方式进行正常的计算。
我们认为绝大部分的模型量化算法都能压缩参数,因此压缩参数的实用性不存在问题。
提升速度
量化是否一定能加速计算?回答是否定的,许多量化算法都无法带来实质性加速。
引入一个概念:理论计算峰值。在高性能计算领域,这概念一般被定义为:单位时钟周期内能完成的计算个数 乘上 芯片频率。
什么样的量化方法可以带来潜在、可落地的速度提升呢?我们总结需要满足两个条件:
1、量化数值的计算在部署硬件上的峰值性能更高 。
2、量化算法引入的额外计算(overhead)少 。
要准确理解上述条件,需要有一定的高性能计算基础知识,限于篇幅就不展开讨论了。现直接给出如下结论:已知提速概率较大的量化方法主要有如下三类,
**1、二值化,**其可以用简单的位运算来同时计算大量的数。对比从nvdia gpu到x86平台,1bit计算分别有5到128倍的理论性能提升。且其只会引入一个额外的量化操作,该操作可以享受到SIMD(单指令多数据流)的加速收益。
**2、线性量化,**又可细分为非对称,对称和ristretto几种。在nvdia gpu,x86和arm平台上,均支持8bit的计算,效率提升从1倍到16倍不等,其中tensor core甚至支持4bit计算,这也是非常有潜力的方向。由于线性量化引入的额外量化/反量化计算都是标准的向量操作,也可以使用SIMD进行加速,带来的额外计算耗时不大。
**3、对数量化,**一个比较特殊的量化方法。可以想象一下,两个同底的幂指数进行相乘,那么等价于其指数相加,降低了计算强度。同时加法也被转变为索引计算。但没有看到有在三大平台上实现对数量化的加速库,可能其实现的加速效果不明显。只有一些专用芯片上使用了对数量化。
简单看一下二值化和线性量化两种方式,分别是右上角的图和右下角的图。
总结一下,要使用量化技术来提升模型运行速度,需要满足两个条件:
1、选择适合部署的量化方案。
2、在部署平台上使用经过深度优化的量化计算库(必要的时候,可能需要撸起袖子自己上)。
首先保证你实现的低比特计算效率超过原先浮点计算,否则为何要承担精度损失的风险而使用并不加速的量化模型呢。但低比特计算效率超过浮点计算其实并不容易,因为大家在浮点的计算库上已经做了非常多细致的优化比如winograd,间接卷积等等。
为了最大限度保证实用性,后面的论文所有工作都是基于二值化、线性量化两种前提来做的,并且绝大部分工作都报告最终实际的加速效果。这也是链接与编译团队做研究的一个风格。
降低内存
模型量化还有一个潜在的好处是降低运行时内存占用,这个特性无论是在移动端还是云端都是具有现实意义的。如果降低内存占用,可以得到如下好处:
1、降低访存量,存在提升速度的可能 。
2、在同样硬件环境下,同时处理更多视频或者视频路数 。
3、训练更大的模型。
我们分析下运行时内存都是被什么东西占用的,大家关心的参数weight只占很少一部分, 大部分内存占用来自激活值activation。如果你做低比特量化只关注卷积的话(很多论文其实也是只量化了卷积),那么是无法带来内存占用降低的。
如何才能用量化降低内存占用,只有一个方式: 将尽可能多的layer的激活值都进行量化 。在这个方向上之前商汤的一位实习生李润东也有一个工作,做了除了卷积之外更多层的量化。但是这样做会带来更多的精度损失,这可能也是大家需要关心的。
生产一个量化模型的有以下几种方法,借鉴了ICCV2019上一篇data-free量化论文的定义。
L1:直接将一个浮点参数直接转化成量化数,一般会带来很大的精度损失,但使用上非常简单。
L2:基于数据校准的方案,很多芯片都会提供这样的功能,比如tensorRT,高通,寒武纪等。它需要转模型的时候提供一些真实的计算数据。
L3:基于训练finetune的方案,有很多论文都是使用这种方法,它的好处是可以带来更大的精度提升,缺点是需要修改训练代码,实施周期比较长。
上图描述了一种实用的pipeline流程,一般会优先使用不进行finetune的offline方法,也就是离线方案。当离线方案精度损失过于严重,我们才会进行基于finetune的方法,来做进一步的抢救。
量化模型的落地
最后聊聊阻碍模型量化算法落地的几个问题,核心当然是精度问题。我们发现虽然学术界大家很早就开始做量化,但现在算法还无法大规模落地。主要存在几个Gap:
1、可落地的线性量化方案无法很好的刻画一些分布,比如高斯分布
2、比特数越低,精度损失就越大,实用性就越差
3、任务越难,精度损失越大,比如识别任务,就比分类任务要难非常多
4、小模型会比大模型更难量化
5、某些特定结构,如depthwise,对量化精度十分不友好
6、常见的对部署友好的方法比如merge BN,全量化,都会给精度带来更大的挑战
除了精度外,软硬件支持不好也是一个阻碍:不同的硬件支持的低比特指令是不一样的,同样训练得到的低比特模型,无法直接部署在所有硬件上。除了硬件之外,不同软件库实现的量化方案和细节也不一样,量化细节里包括量化位置、是否支持perchannel、是否混合精度等等。即使硬件支持了量化,但你会发现不是所有硬件可以在低比特上提供更好的速度提升, 造成这个状况的主要原因有多个,一方面是指令集峰值提升可能本身就并不多,而要引入较多的额外计算,另一方面也取决于软件工程师优化指令的水平,同时由于网络结构灵活多样,不一定能在不同网络结构上达到同样好的加速比,需要优化足够多的的corner case才可以解决。
相信大家对模型量化的概念和落地难点有了一个系统性的认识。我的部分就到这里结束了,下面是大家最期待的论文解读时间。
Part 2 论文解读
低比特量化:
Differentiable Soft Quantization: Bridging Full-Precision and Low-Bit Neural Networks
(ICCV 2019)
How to accelerate and compress neural networks with quantization
一种特别而有趣的技术是量化,它将网络内部的浮点数替换为整数。
有两种基本表示形式:整数和浮点数。
整数以基数2的数字系统表示。根据所使用的位数,整数可以占用几种不同的大小。最重要的是
- int8 or short (ranges from -128 to 127),
- uint8 (ranges from 0 to 255),
- int16 or long (ranges from -32768 to 32767),
- uint16 (ranges from 0 to 65535).
如果要表示实数,则必须放弃完美的精度。举个例子,数字1/3可以十进制形式写为0.33333…,具有无限多个数字,这些数字无法在内存中表示。为了解决这个问题,引入了浮点数。
本质上,浮点数是数字形式的科学记数法
最常以2为基数,但也可以是10。 (出于我们的目的,这无关紧要,但是我们假设它是2。)
与整数相似,有不同类型的浮点数。最常用的是
half或float16(1位符号,5位指数,10位有效数,所以总共16位),
单或float32(1位符号,8位指数,23位有效值,所以总共32位),
double或float64(1位符号,11位指数,52位有效值,所以总共64位)。
如果您尝试以科学格式将两个数字相加并相乘,您会发现浮点算术比整数算术要复杂得多。实际上,每次计算的速度很大程度上取决于实际的硬件。例如,台式机中的现代CPU的浮点运算速度与整数运算速度一样快。另一方面,GPU更针对单精度浮点计算进行了优化。 (因为这是计算机图形学中最普遍的类型。)
并不是完全精确,可以说使用int8通常比float32更快。但是,默认情况下将float32用于神经网络的训练和推理。 (如果您之前曾训练过网络并且未指定参数和输入的类型,则很可能是float32。)
那么,如何将网络从float32转换为int8?
Quantizing networks
这个想法原则上非常简单。 (实际上并没有那么多,我们将在后面看到。)假设您有一个输出在[-a,a)范围内的层,其中a是任何实数。
首先,我们将输出缩放到[-128,128),然后简单地舍入。也就是说,我们使用转换
To give a concrete example, let’s consider the calculation below.
The range of the values here is in (-1, 1), so if we quantize the matrix and the input, we get
在这里,我们看到结果不是int8。由于将两个8位整数相乘是一个16位整数,因此我们可以通过转换对结果进行反量化
to obtain the result
如您所见,这与我们最初的情况不完全相同。这是预料之中的,因为量化只是一个近似值,在此过程中我们会丢失信息。但是,有时这是可以接受的。稍后,我们将看到模型性能如何受到影响。
Using different types for quantization
我们已经看到,量化基本上是在操作方面进行的。从float32到int8不是唯一的选择,还有其他选择,例如从float32到float16。这些也可以组合。例如,您可以将矩阵乘法量化为int8,而将其激活为float16。
量化是一个近似值。通常,近似值越接近,可以预期的性能衰减就越小。如果将所有内容量化为float16,则会将内存减少一半,可能不会失去准确性,但不会真正提高速度。另一方面,使用int8进行量化可以得出更快的推断,但是性能可能会更差。在极端情况下,它甚至无法工作,并且可能需要量化感知培训。
Quantization in practice
在实践中,有两种主要的量化方法。
训练后:使用float32权重和输入来训练模型,然后量化权重。它的主要优点是易于应用。缺点是,这可能会导致准确性下降。
量化意识训练:在训练过程中量化权重。在这里,即使是梯度也针对量化的权重进行计算。当应用int8量化时,这是最好的结果,但是比其他方法要复杂得多。
实际上,性能很大程度上取决于硬件。量化为int8的网络将在专门用于整数计算的处理器上表现更好。
Dangers of quantization
尽管这些技术看起来很有前途,但是在应用它们时必须格外小心。神经网络是极其复杂的功能,即使它们是连续的,它们也可以快速变化。为了说明这一点,让我们回顾一下郝力等人的传奇论文《可视化神经网络的损失格局》。
下面是没有跳过连接的ResNet56模型的损耗情况的可视化。自变量代表模型的权重,而因变量是损失。
上面的图完美地说明了这一点。即使稍微改变权重,损失的差异也可能很大。
在量化时,这正是我们正在做的:通过牺牲压缩表示的精度来逼近参数。无法保证不会完全破坏模型。
因此,如果您要为安全至关重要且错误预测损失很大的任务构建深层网络,则必须格外小心。
现代深度学习框架中的量化
如果您想尝试这些技术,则不必从头开始。最完善的工具之一是TensorFlow Lite的模型优化工具包。它包含了尽可能缩小模型的方法。
You can find the documentation and an introductory article below.
https://www.tensorflow.org/lite/performance/model_optimization
https://medium.com/tensorflow/introducing-the-model-optimization-toolkit-for-tensorflow-254aca1ba0a3
PyTorch还支持多种量化工作流程。尽管目前已将其标记为实验性功能,但功能齐全。 (但是,请注意API会一直更改,直到它处于实验状态。)
https://pytorch.org/blog/introduction-to-quantization-on-pytorch/
pyroch中的量化
官方教程(英文):
https://pytorch.org/docs/stable/quantization.html
pytorch.org
官方教程(中文):
https://pytorch.apachecn.org/docs/1.4/88.html
pytorch.apachecn.org
目前很多高精度的深度学习模型所需内存、计算量和能耗巨大,并不适合部署在一些低成本的嵌入式设备中,为了解决这个矛盾,模型压缩技术应运而生,其主要是通过减少原始模型参数的数量或比特数来实现对内存和计算需求的降低,从而进一步降低能耗。目前性能最稳定的就是INT8的模型量化技术,相对于原始模型的FP32计算相比,INT8量化可将模型大小减少 4 倍,并将内存带宽要求减少 4 倍,对 INT8 计算的硬件支持通常快 2 到 4 倍。 值得注意的是量化主要是一种加速前向推理的技术,并且绝大部分的量化算子仅支持前向传递。
注:目前PyTorch的量化工具仅支持1.3及以上版本。
应用范围
数据类型:
- weight的8 bit量化 :data_type = qint8,数据范围为[-128, 127]
- activation的8 bit量化:data_type = quint8,数据范围为[0, 255]
bias一般是不进行量化操作的,仍然保持float32的数据类型,还有一个需要提前说明的,weight在浮点模型训练收敛之后一般就已经固定住了,所以根据原始数据就可以直接量化,然而activation会因为每次输入数据的不同,导致数据范围每次都是不同的,所以针对这个问题,在量化过程中专门会有一个校准过程,即提前准备一个小的校准数据集,在测试这个校准数据集的时候会记录每一次的activation的数据范围,然后根据记录值确定一个固定的范围。
量化方法
- 1.Post Training Dynamic Quantization:这是最简单的一种量化方法,Post Training指的是在浮点模型训练收敛之后进行量化操作,其中weight被提前量化,而activation在前向推理过程中被动态量化,即每次都要根据实际运算的浮点数据范围每一层计算一次scale和zero_point,然后进行量化;
- 2.Post Training Static Quantization:第一种不是很常见,一般说的Post Training Quantization指的其实是这种静态的方法,而且这种方法是最常用的,其中weight跟上述一样也是被提前量化好的,然后activation也会基于之前校准过程中记录下的固定的scale和zero_point进行量化,整个过程不存在量化参数(scale和zero_point)的再计算;
- 3.Quantization Aware Training:对于一些模型在浮点训练+量化过程中精度损失比较严重的情况,就需要进行量化感知训练,即在训练过程中模拟量化过程,数据虽然都是表示为float32,但实际的值的间隔却会受到量化参数的限制。
- 至于为什么不在一开始训练的时候就模拟量化操作是因为8bit精度不够容易导致模型无法收敛,甚至直接使用16bit进行from scrach的量化训练都极其容易导致无法收敛,不过目前已经有了一些tricks去缓解这个问题,但不在本文讨论之列。
量化流程
以最常用的Post Training (Static) Quantization为例:
**1.准备模型:**准备一个训练收敛了的浮点模型,用QuantStub和DeQuantstub模块指定需要进行量化的位置;
**2. 模块融合:**将一些相邻模块进行融合以提高计算效率,比如conv+relu或者conv+batch normalization+relu,最常提到的BN融合指的是conv+bn通过计算公式将bn的参数融入到weight中,并生成一个bias;
**3. 确定量化方案:**这一步需要指定量化的后端(qnnpack/fbgemm/None),量化的方法(per-layer/per-channel,对称/非对称),activation校准的策略(最大最小/移动平均/L2Norm(这个不太清楚,是类似TensorRT的校准方式吗???));
- activation校准:利用torch.quantization.prepare()
利用torch.quantization.prepare() 插入将在校准期间观察激活张量的模块,然后将校准数据集灌入模型,利用校准策略得到每层activation的scale和zero_point并存储;
**5. 模型转换:**使用 torch.quantization.convert()函数对整个模型进行量化的转换。 这其中包括:它量化权重,计算并存储要在每个激活张量中使用的scale和zero_point,替换关键运算符的量化实现;