目录
该系列文章与qwe一同创作,喜欢的话不妨点个赞。
本文书接上回,替换层、conv与bn融合后,就要开启标定了。
标定这个步骤的结果,就是得到如下图onnx中qdq节点中的scale数值。
一、设置数据并行和评估模式
二、加速校准计算
这里主要找到所有 _input_quantizer
和 _weight_quantizer
,将 _input_quantizer
中的_calibrator._torch_hist
设置为 True
,之后在校准时会使用 pytorch 进行直方图计算,如果使用默认值 False 的话,就会使用 numpy 来计算直方图。这里不会对 _weight_quantizer
进行修改,是因为 _weight_quantizer._calibrator
属于 calib.MaxCalibrator
这个类。
三、校准模型
校准模型分为两个部分:
- 收集统计数据
- 计算绝对最大值
3.1 收集统计数据
- 收集的功能,封装在函数collect_stats中。
3.1.1 禁用量化模式,启用校准模式
将模型切换到评估模式后,遍历模型中所有的子模块去寻找是否属于TensorQuantizer
的子类。其实就是找量化层中的 _input_quantizer
和 _weight_quantizer
,如果找到 _input_quantizer
和 _weight_quantizer
,就将禁用量化模式(module.disable_quant),启用校准模式(module.enable_calib);
禁用量化模式就是将量化器中的 _if_quant
设置为 False。
启用校准模式就是将量化器中的 _if_calib
设置为 True。
这样在之后的 forward 中,就不会对数据进行量化操作,并且会收集数据用于校准。
3.1.2 收集统计数据
- 其实就是执行了模型的前向,因为
quantizer
中的collect
开关打开了,前向的过程已经不是输入数据,得到result这么简单了。而是输入数据,每个quantizer
都会收集流转到这里的数据的动态范围。
_input_quantizer
-
通常
_input_quantizer
创建时使用per-tensor加histogram的方式。即每个激活的量化器使用直方图的方式收集数据,最终计算出一个scale。 -
第一次使用模块中的
_input_quantizer
收集数据时,量化器的校准器中并没有存储统计数据,即_calib_hist
和_calib_bin_edges
为None
,在进行统计数据收集时,直接将统计结果赋值给_calib_hist
,然后为_calib_bin_edges
设置成一个长度为 2049 的张量,将0到输入数据x.max() 划分为 2049 个值。- 其含义为 2048 个 bin 的 2049 个边界,即直方图的边界。
-
这里的 self._num_bins 为 2048 是因为在初始化校准器时,默认设置的数值为 2048,是 tensorrt 默认设置的统计直方图时的 bin 的个数。
- 除了第一次收集数据时会从零创建直方图,后续收集数据会更新第一次创建的直方图。
- 更新直方图的最大值:
amax
- 更新直方图的边界的数值:
_calib_bin_edges
- 更新直方图的 bin 的个数:
_num_bins
- 111行判断当前数据的最大值与上一个统计的直方图的最大值哪个大。
- 113行在保证bins的
width
没有变化的情况下计算出新的bins的数量。114行计算出边界。主要是 116-117 行代码,需要再原始的直方图 bin 的数值上,将前一次与本次的直方图数值进行累加。 - 如果这个模块中的
_input_quantizer
收集过数据,会先判断当前数据的最大值是否超过直方图的最大范围,超过了的话就会维持之前每个 bin 的区间,去扩充 bin 的数量到可以包容到现在数据的最大值。
之后会将之前统计的结果与当前统计的结果相加保存到 _calib_hist
中。
_weight_quantizer
使用模块中的 _weight_quantizer
收集数据时,先根据指定 axis 找出局部最大值。
- 通常对于2d卷积的权重来说,因为形状通常是[O,I,KH,KW]的,并且采用
PER_CHANNEL
的方式,所以通常指定axis为0,即最终计算得到O个(输出通道数量个)scale。
这个是对于 torch.nn 中一些常用的 module 的 weight 的量化描述器的配置,这里主要会使用针对 Conv2D 和 ConvTranspose2D 的量化描述器,针对 SparseConv 的量化描述器是自定义的,主要区别就是 axis 不同,这里值为4,通常对于SparseConv的权重,这个维度也是输出的维度,在之后统计 amax 时会根据这个 axis 来统计最大值。
这里就会遍历出了指定 axis 之外的所有维度,找出每个维度的最大值,这样最后就可以将其余维度的最大值保存在指定 axis 中,最后的形状会变成 [1, 1, 1, 1, ori]。
训练好的模型,权重是固定的。
_cali