模型压缩-量化算法概述
前言
浮点数和定点数
浮点数(floating point number
)和定点数(fixed point number
)都是计算机中表示实数(即带有小数部分的数)的方式。在数值计算中,小数在内存中是以浮点数格式表示和参与运算。浮点数和定点数中的“点”指的是小数点。
1,浮点数表示法是用科学计数法表示实数,即:
其中, 表示尾数, 表示基数(通常为 2), 表示指数。浮点数的表示法将尾数和指数分别存储在计算机内存中,由于指数可为负数或小数,可以表示非常大或非常小的实数,同时也具有一定的精度。浮点数一般使用 IEEE754
标准表示,如单精度浮点数使用 32 位-FP32
(1 位符号位,8 位指数位,23 位尾数位)来表示实数。
2,定点数表示法是用一个固定的小数位数来表示实数,即用一定数量的二进制数表示小数部分。定点数没有指数部分,小数部分的位数是固定的,所以它的范围和精度比较有限。定点数一般使用有符号或无符号整数表示,比如常见的 INT8
。
INT8 是 8 位定点数,其取值范围为 ,共 个不同的取值。
IEEE754 标准
我们知道,浮点数是一种对于数学中实数的近似值数值表现法,其由符号、尾数、基和指数组成,如 , 。
在数学中,实数(real number)是有理数和无理数的总称,直观理解就是小数。
在 IEEE 754
标准中, 浮点数是用二进制表示的, 形式如下:
其由三部分组成:
- 符号,sign,其值用 表示。
- 指数,exponent,其值用 表示,对于单精度浮点数,指数占
8
位 - 尾数,fraction,其值用 表示。
IEEE 754 标准中的单精度和双精度格式如下图所示:
单精度浮点数值的分类:
1,规格化的(一般情况):
指数位既不全为 0(数值 0),也不全位 1(单精度数值 255,双精度数值 2047)。这种情况下,指数为被定义以偏置形式表示的有符号整数,即指数位数值是 。计算过程省略,但是由此产生的指数的取值范围,对于单精度是 ,而对于双精度是 。
对于单精度的规格化数,其数值范围为 ,经过换算十进制数范围为 。
2^{-126} = 1.17*10^{-38}
2 * 2^{127} = 3.4*10^{38}
2,非规格化的
CSAPP 中文书上翻译为阶码域为全 0 时,所表示的数时非规格化形式,即指数为全为 0(数值 0)。
3,特殊值。
和非规格化相反,阶码域为全 1 。
一,模型量化概述
所谓量化,其实可以等同于低精度(Low precision)概念,常规模型精度一般使用 FP32(32 位浮点数,单精度)存储模型权重参数,低精度则表示使用 INT8
、FP16
等权重数值格式。
模型量化(Model Quantization
,也叫网络量化)过程分为两部分:将模型的单精度参数(一般 FP32
-32
位浮点参数)转化为低精度参数(一般 INT8
-8
位定点参数),以及模型推理过程中的浮点运算转化为定点运算,这个需要推理框架支持。
模型量化技术可以降低模型的存储空间、内存占用和计算资源需求,从而提高模型的推理速度,也是为了更好的适配移动端/端侧 NPU
加速器。简单总结就是,模型变小了,速度变快了,支持的场景更多了。
最后,现在工业界主流的思路就是模型训练使用高精度-FP32 参数模型,模型推理使用低精度-INT8 参数模型。
1.1,模型计算量和参数量统计
之所以需要做模型量化,是因为训练好的原始 FP32
模型实在太大了,在端侧/移动端都很好直接部署,即使运行起来速度和功耗也很大。经典 backbone 的参数量和计算量统计结果如下:
来源 thop,也叫
PyTorch-OpCounter
工具统计结果。
Pytorch
框架中常用的参数量和计算量统计分析工具有,torchsummary
和 thop
。以 thop
为例,其基础用法如下所示:
import torch
from torchvision.models import resnet50
from thop import profile
model = resnet50()
input = torch.randn(1, 3, 224, 224)
macs, params = profile(model, inputs=(input, ))
print("flops: %.2f\nparameters: %.2f" % (macs, params))
运行结果如下所示:
[INFO] Register count_convNd() for <class 'torch.nn.modules.conv.Conv2d'>.
[INFO] Register count_normalization() for <class 'torch.nn.modules.batchnorm.BatchNorm2d'>.
[INFO] Register zero_ops() for <class 'torch.nn.modules.activation.ReLU'>.
[INFO] Register zero_ops() for <class 'torch.nn.modules.pooling.MaxPool2d'>.
[INFO] Register zero_ops() for <class 'torch.nn.modules.container.Sequential'>.
[INFO] Register count_adap_avgpool() for <class 'torch.nn.modules.pooling.AdaptiveAvgPool2d'>.
[INFO] Register count_linear() for <class 'torch.nn.modules.linear.Linear'>.
flops: 4133742592.00
parameters: 25557032.00
二,模型量化的方案
在实践中将浮点模型转为量化模型的方法有以下三种方法:
-
data free
:不使用校准集,传统的方法直接将浮点参数转化成定点数,使用上非常简单,但是一般会带来很大的精度损失,但是高通最新的论文DFQ
不使用校准集也得到了很高的精度。 -
calibration
:基于校准集方案,通过输入少量真实数据进行统计分析,从而确定浮点值域(min/max)。很多芯片厂商都提供这样的功能,如tensorRT
、高通、海思、地平线、寒武纪。TensorRT 校准时,一般是用训练集的子集。这里使用校准集,是因为第一个 conv 需要统计输入 的 min、max 外,其他层都只需要统计中间输出 feature 的 min、max 即可。 -
finetune
:基于训练finetune
的方案,将量化误差在训练时仿真建模,调整权重使其更适合量化。好处是能带来更大的精度提升,缺点是要修改模型训练代码,开发周期较长。
TensorFlow
框架按照量化阶段的不同,其模型量化功能分为以下两种:
- Post-training quantization
PTQ
(训练后量化、离线量化); - Quantization-aware training
QAT
(训练时量化,伪量化,在线量化)。
PTQ
Post Training Quantization
是训练后量化,也叫做离线量化。
-
根据量化零点 是否为
0
,训练后量化分为对称量化和非对称量化。根 -
据数据通道顺序
NHWC
(TensorFlow) 这一维度区分,训练后量化又分为逐层量化和逐通道量化。目前nvidia
的TensorRT
框架中使用了逐层量化的方法,每个网络层采用同一个阈值来进行量化。逐通道量化就是对每一层的每个通道都有各自的阈值,对精度可以有一个很好的提升。
2.1,量化算术的分类
目前已知的加快推理速度概率较大的量化方法主要有:
- 二值化,其可以用简单的位运算来同时计算大量的数。对比从 nvdia gpu 到 x86 平台,1bit 计算分别有 5 到128倍的理论性能提升。且其只会引入一个额外的量化操作,该操作可以享受到 SIMD(单指令多数据流)的加速收益。
-
线性量化(最常见),又可细分为非对称,对称和
ristretto
几种。在nvdia gpu
,x86
、arm
和 部分AI
芯片平台上,均支持8bit
的计算,效率提升从1
倍到16
倍不等,其中tensor core
甚至支持4bit
计算,这也是非常有潜力的方向。线性量化引入的额外量化/反量化计算都是标准的向量操作,因此也可以使用SIMD
进行加速,带来的额外计算耗时不大。 -
对数量化,一种比较特殊的量化方法。两个同底的幂指数进行相乘,那么等价于其指数相加,降低了计算强度。同时加法也被转变为索引计算。目前
nvdia gpu
,x86
、arm
三大平台上没有实现对数量化的加速库,但是目前已知海思351X
系列芯片上使用了对数量化。
与非线性量化不同,线性量化采用均匀分布的聚类中心,原始浮点数据和量化后的定点数据存在一个简单的线性变换关系,因为卷积、全连接等网络层本身只是简单的线性计算,因此线性量化中可以直接用量化后的数据进行直接计算。
三,量化算法
3.1,浮点转定点算术(量化)
32-bit
浮点数和 8-bit
定点数的表示范围如下表所示:
数据类型 | 最小值 | 最大值 |
---|---|---|
FP32 |
-1.17e38 | 3.4e38 |
int8 |
-128 | 127 |
uint8 |
0 | 255 |
原始神经网络模型的推理由浮点运算构成。FP32
和 INT8
的值域是 和 ,而取值数量大约分别为 和 。FP32
取值范围非常广,因此,将网络从 FP32
转换为 INT8
并不像数据类型转换截断那样简单。
根据偏移量 是否为 0,可以将浮点数的线性量化分为两类-对称量化和非对称量化。
当浮点值域落在 之间,权重浮点数据的量化运算可使用下式的方法将 FP32
映射到 INT8
,这是对称量化。其中 表示 FP32
权重, 表示量化的 INT8 权重, 是缩放因子(映射因子、量化尺度(范围)/ float32
的缩放因子)。
对称量化的浮点值和 8
位定点值的映射关系如下图,从图中可以看出,对称量化就是将一个 tensor
中的 内的 FP32
值分别映射到 8 bit
数据的 [-128, 127]
的范围内,中间值按照线性关系进行映射,称这种映射关系是对称量化。可以看出,对称量化的浮点值和量化值范围都是相对于零对称的。
因为对称量化的缩放方法可能会将 FP32 零映射到 INT8 零,但我们不希望这种情况出现,于是出现了数字信号处理中的均一量化,即非对称量化。数学表达式如下所示,其中 表示量化零点(量化偏移)。
大多数情况下量化是选用无符号整数,即 INT8
的值域就为 ,这种情况,显然要用非对称量化。非对称量化的浮点值和 8
位定点值的映射关系如下图:
总的来说,权重量化浮点值可以分为两个步骤:
- 通过在权重张量(Tensor)中找到 和 值从而确定 和。
- 将权重张量的每个值从 FP32 转换为 INT8 。