发布时间:2025-12-09 16:05:09 浏览次数:7
背景 目前神经网络在许多前沿领域的应用取得了较大进展,但经常会带来很高的计算成本,对内存带宽和算力要求高。另外降低神经网络的功率和时延在现代网络集成到边缘设备时也极其关键,在这些场景中模型推理具有严格的功率和计算要求。神经网络量化是解决上述问题有效方法之一,但是模型量化技术的应用会给模型带来额外噪音,从而导致精度下降,因此工程师对模型量化过程的理解有益于提高部署模型的精度。
目录
1. 量化基础知识
1.1 量化理论
1.2 Uniform Affine Quantizer(非对称量化)
1.3 Uniform symmetric quantizer(对称量化)
2. 比例因子如何求得
2.1 饱和量化与不饱和量化
2.2 量化策略
3. 量化的粒度
4. 量化方法
4.1 训练后量化PTQ
4.2 量化感知训练QAT
5. 量化总结
量化实际上就是把高位宽表示的权值和激活值用更低位宽来表示。定点运算指令比浮点运算指令在单位时间内能处理更多数据,同时,量化后的模型可以减少存储空间。假设一次浮点运算为:
(1)
为了从浮点运算转移到高效的定点运算,我们需要一种将浮点向量转换为整数的方案。浮点向量x可以近似表示为标量乘以整数值向量:
(2)
其中,sx是浮点比例因子,xint是整数向量,例如INT8。我们将向量的量化版本表示为x'。通过量化权重和激活,我们可以写出公式(1)的量化版本:
(3)
请注意,对权重和激活分别使用单独的比例因子sx和sw。这提供了灵活性并减少了量化误差。由于每个比例因子应用于整个张量,因此该方案允许将比例因子从等式(3)中的累加中分离出来,并以定点格式执行MAC操作。现在有意忽略偏置量化,因为偏置通常存储在较高的比特宽度(32比特)中,其比例因子取决于权重和激活的比例因子。一般来说,偏置的比例因子为权重和激活的比例因子的乘积,这样做的好处是可以把sx和sw作为一个公因项提取出来,如下面的公式所示:
(4)
从以上的阐述不难看出,如何求比例因子是关键所在。
非对称量化由三个量化参数定义:比例因子s、零点z和比特宽度b。比例因子和零点用于将浮点值映射到整数网格,其size取决于比特宽度。比例因子通常表示为浮点数,并指定量化器的步长。零点是一个整数,确保实零(real zero)被量化而没有误差。这对于确保诸如零填充或ReLU之类的常见操作不会引起量化误差非常重要。
下图是一个uint8非对称量化的映射过程:
首先,量化前要计算比例因子,包括步长和零点两个量化参数。比例因子就是用来保证浮点区间内的变量都能无一缺漏的映射到要量化bit数的取值区间内。其中,步长和零点的计算公式如下:
(5)
这里要重点说下零点z,也叫做偏移,为何这里需要零点?实际上,浮点型的0会映射到零点,这个零点是一个整型数,用来确保0没有量化误差。具体就是,0有特殊意义,比如padding时,0值也是参与计算的,浮点型的0进行8bit量化后还是0就不对了,所以加上这个零点后,浮点型0就会被映射到0-255这个区间内的一个数,这样的量化就更精确。就相当于让映射后区间整体偏移,浮点最小值对应0。计算完量化因子,再从浮点区间任取一值的量化过程,
(6)
其中clamp用于将超出范围的值截断,因为xint有可能溢出。反量化时的公式为:
(7)
接下来我们模拟一层卷积定点运算过程,图1中表示了卷积层的定点量化计算过程。计算过程中 biases(b)、weight(w)、input(x) 和output(y) 全部为定点格式。计算公式如下:
y = Clamp(Round([(x-x_z)*s_x*(w-w_z)* s_w +(s_x*s_w)*(b-b_z)]/s_y))
红色部分是对输入进行反量化为浮点型,蓝色部分对权重进行反量化为浮点型,绿色部分对偏置反量化为浮点型,橙色部分对输出重量化为INT8类型,该式子还可以展开写出来,但是也可以看出来这一系列计算可能比浮点计算还要复杂。
图1 卷积层的定点量化计算过程
答:这里考虑的是ReLU激活,激活值都是大于0的数,如果截断的长度为128的话,那么我们直接一一对应就好了,即一个桶对应一直值,例如在第5个桶里的浮点数值就可以直接量化为5,完全不用衰减因子。如果激活值的范围是正负都有,那就从255开始。
3.为什么截断区外的值加到截断样本P的最后一个值上?
答:我的理解是截断区之外的值都会量化到截断点处,例如大于127的值都截断为127,知乎上的一个网友的回答是:第一是求P的概率分布时,需要P的总值,第二是尽可能地将截断后的信息加进来。(有点道理,但是不多)
4.第 7 行的quantize操作以及第 8 行的expand操作是什么意思?
答:上面也解释过了,这里再具体说下。首先第7行的quantize指的是将原始浮点值的直方图分布量化到128个桶里面,也就是将浮点数值量化到0-127,只不过这里量化的是直方图分布,例如将[ 1, 0, 2, 3, 5, 3, 1, 7]这个分布量化成两个桶,[ 1, 0, 2, 3, 5, 3, 1, 7] =>[1 + 0 + 2 + 3 , 5 + 3 + 1 + 7] = [6, 16],也就是说将前面四个桶量化成了6,后面四个桶量化成了16,如果这里是二值化,那么就是将前面6个浮点数映射成0,后面16个浮点数映射成1。那么expand操作又是什么呢?其实expand操作就相当于反量化了,将[6,6,6,6,16,16,16,16]反量化为[ 6/3, 0, 6/3, 6/3, 16/4, 16/4, 16/4, 16/4] = [ 2, 0, 2, 2, 4, 4, 4, 4],为什么6/3,16/4,因为在量化[ 1, 0, 2, 3, 5, 3, 1, 7]的时候是用加和量化的,反量化就是除以该部分的非零元素个数了。然后再分别得到两个集合的概率分布,计算这两个集合的KL距离。
总结一下:一般来说,对activation推荐使用KL散度量化。从上面的复杂介绍中我们可以看出: KL距离采样方法从理论上似乎很合理,但是也有几个缺点:1)动态范围的选取相对耗时。2)上述算法只是假设左侧动态范围不变的情况下对右边的边界进行选取,对于 ReLU这种数据分布的情况可能很合理,但是如果对于右侧数据明显存在长尾分布的情况可能并不友好。除了具有像ReLU等这种具有明显数据分布特征的情况,其他情况我们并不清楚从左边还是从右边来确定动态范围的边界。3)quantize/expand 方法也只是一定程度上模拟了量化的过程。
逐层量化(per-tensor):指定一个量化器(由尺度s和零点z定义)对张量进行量化。如果想让精度进一步提升,可以采用对张量的每个卷积核都使用对应的量化器。
逐通道量化(per-channel):对每个卷积核有不同的量化器。
对于激活输出我们不考虑逐通道量化,因为这会使卷积操作和矩阵乘操作中的内积计算变得复杂。“逐层量化”和“逐通道量化”都支持高效的点乘操作和卷积实现,因为这两种量化方式中量化参数在每个卷积核中都是固定的常量。
这一部分介绍实际场景中常用的两个量化方法。
字面意思,整个模型(浮点型的)训练完成后再单独把权值和激活值拿出来量化。过程中无需对原始模型进行任何训练,只对几个超参数调整就可完成量化过程。
4.1.1 只对权重量化
只量化权重和偏置,权重和偏置的比例因子一致。如果只是想为了方便传输和存储而减小模型大小,而不考虑在预测时浮点型计算的性能开销的话,这个量化很有用。
4.1.2 量化权重和激活值
激活值不像权值那样有固定数量,所以这时就得需要标定数据了,即计算激活值的动态范围,求得比例因子,然后再量化。一般使用1024张图片数据就足够估算出激活值的动态范围了。一般来说在PTQ中,权重使用MinMax量化,激活值使用滑动平均最大最小值或KL散度量化,偏置的比例因子为权重和激活值的比例因子的乘积。
4.1.3 总结
总结一下PTQ的大致过程:
训练时量化方法相比于训练后量化,能够得到更高的精度,即使是逐层量化甚至是4bit 量化也能取得很高的精度。因此量化过程也会相对复杂一点。训练时量化对于权重和激活输出采用模拟量化操作来衡量量化效果。对于反向传播,使用“直通估计器”去建模量化。注意,在前向和反向传播计算中,使用的都是模拟量化的权重和激活输出,也保留浮点型权重,并且在梯度更新的过程中更新它们。这样可以确保使用较小的梯度更新逐步更新权重,而不会造成梯度满溢。更新的权重被量化,然后用于后续的前向和反向传播计算。
模拟量化操作是在训练时就进行量化的,操作实际包括一个量化再紧跟一个逆量化。具体的,就是在前向传播的时候(forward)模拟了量化的这个过程,在forward时首先会把权值和激活值量化到8bit再反量化回有误差的32bit,整体训练还是浮点,反向传播(backward)的时候求得的梯度是模拟量化之后权值的梯度,用这个梯度去更新量化前的权值。这种做法将量化中的取整误差和截断误差带入到了总损失中,让模型在训练过程中去适应这种误差。对于SGD,更新方式如下:
(15)
由于模拟量化方程的导数几乎在各个位置均为0(连续的浮点数量化映射成了离散的点),故在反向传播求梯度时无法求得,解决这个问题的一种方法是使用直通估计器(straight-through estimator STE,Bengio et al. 2013)来近似梯度,它将舍入算子的梯度近似为 1。
(16)
利用这个近似值,可以从方程(15)中计算出量化操作的梯度。为了清楚起见,默认使用对称量化,即 z = 0,但同样的结果适用于非对称量化,因为零点是一个常数。我们使用 n 和 p 来定义整数量化范围,如 n = qmin/sw , p = qmax/sw。方程(15)的梯度与它的输入wfp被如下定义:
(17)
并不是所有的模型,都很适合量化。在实际的生产环境中,我们经常会遇到一些模型,量化之后精度不好,其根本原因是因为浮点阶段的模型并不适合量化。 这里说明一些常见的不适合量化的浮点情况。在实际执行的时候,可以通过 debug 工具去发现模型当中不适合量化的部分。受限于量化的方法和编译器的限制,目前量化的 op 实现,会有一些限制或者误差。这种算子的误差一般有两种体现,第一种是 QAT 的时候精度会有影响(情况很少),第二种是 QAT 转 Quantized 的时候精度会有影响。这里使用如下表格简单列出一些常见的情况。
| 代表OP | 不适合量化的原因 |
| 带有Cos,Exp,Pow,Sin,Sqrt,Sigmoid等数学计算的OP | 查表实现,误差较大 |
| 大尺寸的Conv,AvgPool等 | 大 kernel size 导致统计量不准确,建议使用常规 kernel,比如 2x2,3x3 |
| 操作范围较大的 Concat | 和大 kenerl size 的 conv 类似,统计量不准 |
这里需要说明的是,并不是用了这些 op,精度一定会有问题,还需要结合模型,具体的算法,使用的频率来看。如果大量使用,则需要考虑这些 op 对量化的影响。
QAT 训练还是会有一定的模型训练能力。因此不适合量化,并不代表不能量化。某些情况下,即使出现上面的不适合量化的现象,仍然可以量化的很好。 因此,搭建量化友好的浮点模型,是为了在量化精度出现明显问题的时候,辅助分析浮点模型存在的问题。
问题1:模型中间输出很大。
检查模型中间输出一般是对浮点模型的数据分布(min,max 等)进行分析,是否有比较明显的异常值(如数值很大,有几千几万)。这种情况,会导致量化完精度下降很多。一般来说建议对输入做关于0对称的归一化,如果某些层的输出很大,建议检查模型结构,在浮点训练阶段,数值较大的层后面加上BN、ReLU等normlization的操作,或者对模型中范围比较大的输出层使用int16量化。
问题2:模型权重范围很大。
对于模型中范围较大的weight使用int16量化,或者适当调整weight decay。此外建议对输入做关于0对称的归一化。
QAT经验总结
除下述表格中的超参之外,其它参数建议在 QAT 阶段和浮点阶段保持一致。
| 超参 | 推荐配置 | 高级配置(如果推荐配置无效请尝试) | 备注 |
| LR | 从 0.001 开始,搭配 StepLR 做 2 次 scale=0.1 的 lr decay | 1. 调整 lr 在 0.0001->0.001 之间,配合 1-2 的 lr decay; | |
| Epoch | 浮点 epoch 的 10% | 根据 loss 和 metric 的收敛情况,考虑是否需要适当延长 epoch。 | |
| Weight decay | 与浮点一致 | 建议在 4e-5 附近做适当调整。weight decay 过小导致 weight 方差过大,过大导致输出较大的任务输出层 weight 方差过大。 | |
| optimizer | 与浮点一致 | 有问题时,推荐尝试 SGD | |
| transforms(数据增强) | 与浮点一致 | QAT 阶段可以适当减弱,比如分类的颜色转换可以去掉,RandomResizeCrop 的比例范围可以适当缩小 | 数据增强减弱对浮点模型可能也会有收益 |
参考:
https://arxiv.org/abs/2106.08295
Int8量化-介绍(一) - 知乎
部署加速模型Int8量化_英伟达int8量化_CVer儿的博客-CSDN博客
【量化】——采用KL散度计算阈值_kl阈值_农夫山泉2号的博客-CSDN博客
深度学习模型量化(低精度推理)大总结_深度学习量化_爱上一只柠檬的pig_head的博客-CSDN博客
神经网络量化白皮书 - 简书
量化理解(Google量化白皮书《Quantizing deep convolutional networks for efficient inference: A whitepaper》)-阿里云开发者社区
Pytorch实现卷积神经网络训练量化(QAT) - 知乎
再读《神经网络量化白皮书》- 0x04 训练时量化(QAT) - 知乎
https://developer.horizon.ai/api/v1/fileData/horizon_j5_open_explorer_v1_1_33_cn_doc/plugin/source/tutorials/qat_experience.html