构建浮点模型
量化原理
模型量化的核心思想是:用低精度的数值(例如 INT8)来表示和计算原本用高精度数值(例如 FP32)训练的模型参数和激活值。
量化一个张量的基本步骤如下:
- 统计该张量的数值范围,从而确定与之对应的缩放系数(scale)。
- 使用缩放系数对张量进行缩放,使缩放后的数值范围落在低精度 dtype 可以表示的区间,然后 cast 到低精度的 dtype。 以 int8 量化为例,计算过程为:。
静态量化 VS 动态量化
静态量化需要在数据集上统计模型各处激活值的分布,为每处激活计算固定的缩放系数(此过程称为“校准”或“calibration”);动态量化不需要校准过程,而是在模型推理过程中实时统计当前张量的数值范围,实时计算缩放系数并完成到低精度的转换。
当前工具链仅支持静态量化,为此工具需要在模型中插入统计节点,详见 Prepare说明。
量化感知训练(Quantized Awareness Training)
为了弥补模型量化带来的精度损失,我们可以在模型 forward 过程中模拟低精度计算,在此条件下对模型进行 finetune 以提升模型在低精度计算下的精度指标,此过程称为量化感知训练。
模拟低精度计算的过程通过在模型中插入伪量化节点实现,详见 Prepare说明 和 FakeQuantize 的定义。
支持算子范围
基于 BPU 对算子的限制,我们仅支持 工具链算子支持约束列表 章节所列出的算子以及基于 BPU 限制而内部特殊定义的特殊算子。
构建浮点模型
在获取浮点模型后,我们需要对浮点模型进行必要的改造,以使其能够支持量化相关操作。模型改造必要的操作包括:
- 在模型输入前插入
QuantStub节点,在模型输出后插入DequantStub节点。 - 对于模型 forward 逻辑中存在的次数可变的循环(如时序模型中逐帧的计算),使用
horizon_plugin_pytorch.fx.jit_scheme.dynamic_block接口标记(此接口的具体作用在 Prepare说明 介绍)。
插入 QuantStub 和 DequantStub 时需要遵循以下原则:
- 仅关注需要量化的数据(即浮点类型的数据),其他原生的定点型或布尔型数据不需要插入 Stub。
QuantStub和DequantStub之间的计算,即为工具进行量化的范围。通常模型的辅助头和 loss 等并不需要部署,因此无需量化,需要确保这些逻辑不在量化范围。- 需要特别注意,模型中可能存在一些定点类型与浮点类型之间的计算,此时 torch 会自动将计算过程和输出 cast 为浮点。但是对于量化工具来说,此处也是“量化与非量化的边界”,您需要先将定点输入手动 cast 为浮点,插入
QuantStub,再进行与另一浮点数的计算。同理,量化的浮点值在 cast 为定点之前,需要先经过DequantStub。
改造模型时需要注意:
- 插入的
QuantStub和DequantStub节点必须注册为模型的子模块,否则将无法正确处理它们的量化状态。 - 多个输入仅在数值分布相同时可以共享
QuantStub,否则请为每个输入定义单独的QuantStub。 - 若需要将上板时输入的数据来源指定为
"pyramid",请手动设置对应QuantStub的scale参数为1/128。 - 您也可以使用
torch.quantization.QuantStub,但是仅有horizon_plugin_pytorch.quantization.QuantStub支持通过参数手动固定 scale。
在此提供一个简易的多输入多输出示例,注意每个 QuantStub 负责一个输入 Tensor 的量化。
改造后的模型可以无缝加载改造前模型的参数,因此若已有训练好的浮点模型,直接加载即可,否则需要正常进行浮点训练。
完成改造后,不要立即开始校准和量化感知训练,优先走通完整部署流程,确认模型是否包含 CPU 算子。
由于模型上板时的输入图像数据一般为 centered_yuv444 格式,因此模型训练时需要把图像转换成 centered_yuv444 格式(注意下面代码中对 rgb2centered_yuv 的使用)。
如果无法转换成 centered_yuv444 格式进行模型训练,请在模型部署时在输入上插入适当的颜色空间转换节点。(注意,该方法可能导致模型精度降低)
构建量化友好模型
浮点模型变为定点模型的过程存在一定的精度误差,越是量化友好的浮点模型,其量化感知训练越容易,量化后的精度也越高。一般而言,有以下几种情况会导致模型变得量化不友好:
-
使用有精度风险的算子。例如:softmax,layernorm 等,这类算子一般底层由查表或多个 op 拼接实现,容易发生掉点问题。
-
一次 forward 中多次调用同一算子。同一算子多次调用,对应的输出分布存在差异,但只会统计一组量化参数,当多次调用的输出分布差异过大时,量化误差会变大。
-
add,cat 等多输入算子的不同输入差异过大,可能造成较大误差。
-
数据分布不合理。plugin 采用的是均匀对称量化,所以 0 均值的均匀分布最好,应尽量避免长尾和离群点。同时,数值范围需要与量化 bit 相匹配,如果使用 int8 量化分布为 [-1000, 1000] 均匀分布的数据,那么精度显然也是不够的。 例如,下面三个分布图,从左到右对量化的友好性依次递减,模型中大部分数值的分布应当为中间这种分布。在实际使用中,可以用 debug 工具查看模型 weight 和 feature map 的分布是否量化友好。 因为模型冗余性的存在,有些看起来分布非常量化不友好的 op 并不会显著降低模型的最终精度,需要结合实际的 qat 训练难度和最后达到的量化精度综合考虑。

那么如何使得模型更加量化友好呢?具体来说:
-
尽量少使用值域不受限的非线性算子如 reciprocal、exp、pow 等,此类算子输出范围可能过大,导致量化精度低。
-
保证多次调用的共享算子每次调用的输出分布差异不要太大,或者将共享算子拆开分别单独使用。
-
避免多输入算子不同输入的数值范围差异过大。
-
使用 int16 量化数值范围和误差都非常大的 op。可通过 debug 工具找到这类 op。
-
通过调大 weight decay,增加数据增强等方式防止模型过拟合。过拟合模型容易出现较大数值,且对输入非常敏感,轻微的误差可能导致输出完全错误。
-
使用 BN。
-
对模型输入做关于 0 对称的归一化。
需要注意的是,qat 自身具有一定的调整能力,量化不友好并不代表不能量化,很多情况下,即使出现上面的不适合量化的现象,仍然可以量化得很好。因为上述建议也可能会导致浮点模型精度下降,所以应当在 qat 精度无法达标时再尝试上述建议,尤其是 1 - 5 条建议,最后应当是在浮点模型精度和量化模型精度中找一个平衡点。
