构建浮点模型
这一阶段,我们主要做两件事:
-
把浮点模型改造成可进入量化流程的模型。
-
在进入
Prepare、Calibration和QAT等后续流程之前,先把明显的结构风险、输入域差异和量化友好性问题找出来。
如果这里处理得比较充分,后续的 Prepare、Calibration 和 QAT 等流程会顺很多。
模型改造
拿到浮点模型后,我们需要先做必要的改造,使模型能够进入后续量化流程。最基本的改造包括:
-
在模型输入前插入
QuantStub节点。 -
在模型输出后插入
DeQuantStub节点。 -
对于模型 forward 逻辑中存在次数可变的循环,例如时序模型逐帧计算,使用
horizon_plugin_pytorch.fx.jit_scheme.dynamic_block接口进行标记。相关说明可参考 Prepare说明。
最容易出问题的地方,是没有把 QuantStub 和 DeQuantStub 放在部署逻辑与非部署逻辑的边界上。
例如,计算辅助 loss 的 head 并不需要部署,就应当在这个 head 的输入处插入 DeQuantStub,这样这个 head 才不会进入量化范围。
另外,插入 QuantStub 和 DeQuantStub 时还需要注意下面几点:
-
仅关注需要量化的数据,也就是浮点类型数据;其他原生定点型或布尔型数据不需要插入 Stub。
-
QuantStub和DeQuantStub之间的计算,就是工具真正量化的范围。通常模型的辅助头和 loss 等并不需要部署,因此要确保这些逻辑不在量化范围。 -
如果模型中存在定点类型与浮点类型之间的混合计算,torch 可能会自动把结果 cast 回浮点,但对量化工具来说,这里仍然属于“量化与非量化的边界”,需要您显式处理这个边界。
改造模型时,还需要注意:
-
插入的
QuantStub和DeQuantStub节点必须注册为模型的子模块,否则工具无法正确处理它们的量化状态。 -
多个输入只有在数值分布相同时才适合共享
QuantStub,只要输入分布差异明显,我们都建议您拆开处理。 -
如果后续上板时输入数据来源为
"pyramid",请手动将对应QuantStub的scale固定为1/128。 -
您也可以使用
torch.quantization.QuantStub,但只有horizon_plugin_pytorch.quantization.QuantStub支持通过参数手动固定 scale。
下面给出一个简化的多输入多输出示例。每个 QuantStub 负责一个输入 Tensor 的量化。
改造后的模型可以无缝加载改造前模型的参数,因此如果已经有训练好的浮点模型,直接加载即可,否则请您先完成正常的浮点训练。
模型改造完成后,建议您先检查如下几项:
-
量化边界是否已经明确,
QuantStub / DeQuantStub的位置是否符合预期。 -
模型中是否已经包含明显不受支持的算子,避免问题拖到
Prepare、导出或编译阶段才暴露。 -
模型是否包含明显不利于部署的 CPU 算子。这里可结合 性能调优章节中的 CPU 算子排查方法 一并确认。
量化友好性检查
浮点模型变为定点模型时天然会引入一定精度误差。模型越量化友好,后续 Calibration 和 QAT 往往越容易收敛,最后的量化精度通常也越高。
一般来说,下面几种情况都可能让模型变得不够量化友好:
-
使用有精度风险的算子。例如
softmax、layernorm等,这类算子一般底层由查表或多个 op 拼接实现,更容易发生掉点问题。 -
一次 forward 中多次调用同一算子。同一算子多次调用时,对应的输出分布可能存在差异,但通常只会统计一组量化参数;当多次调用的输出分布差异过大时,量化误差就会变大。
-
add、cat等多输入算子的不同输入差异过大,也可能造成较大误差。 -
数据分布不合理。plugin 采用的是均匀对称量化,因此 0 均值的均匀分布通常更友好,应尽量避免长尾和离群点。同时,数值范围也需要与量化 bit 匹配;如果使用 int8 去量化分布在
[-1000, 1000]的均匀数据,精度通常是不够的。
例如,下面三个分布图从左到右,对量化的友好性依次递减。模型中大部分数值的分布通常应更接近中间这种形态。实际使用时,可以结合 debug 工具观察模型中的 weight 和 feature map 分布是否量化友好。

因为模型本身存在一定冗余性,有些看起来不够量化友好的算子并不一定会显著拉低最终精度。因此,这里的判断最好和后续 Calibration、QAT 的训练难度以及最终量化精度一起综合看。
下方这些检查项可以帮助您在进入后续量化阶段前,先把常见的结构问题、输入问题和量化风险检查一遍。
是否使用了量化不友好的算子
softmax、layernorm 等算子通常有更高的量化风险,exp、pow、reciprocal、sigmoid、GeLU、inverse_sigmoid 这类结构也需要重点关注。
-
怎么判断:先直接从模型结构和 forward 路径看部署主干中是否包含这些算子。
-
常见风险:这类算子要么值域不稳定,要么底层实现复杂,更容易在量化后放大误差。
-
一般怎么处理:
-
能不进入部署主干的,尽量不要进入部署主干。
-
如果必须保留,建议在后续
Calibration、QAT和一致性分析阶段重点关注这些位置的误差和敏感度表现。
-
是否存在共享模块
一次 forward 中多次调用同一算子,会导致不同调用位置共用同一组量化参数,容易放大量化误差。
-
怎么判断:
-
直接检查 forward 中是否复用了同一个模块实例。
-
在
Prepare后,也可以结合fx_graph.txt和后续 debug 产物继续确认。
-
-
常见风险:共享模块在
Prepare、校准和训练阶段都可能引起 scale 对齐、图结构理解和训练行为上的问题。 -
一般怎么处理:如果不同调用位置的输出分布差异明显,优先考虑将共享模块拆开分别使用。
多输入分布差异是否过大
add、cat 等多输入算子的不同输入差异过大,可能造成较大误差。
-
怎么判断:
-
先从结构上判断不同输入是否来自不同分支、不同特征层或不同数据域。
-
后续也可以结合 debug 工具观察这些输入的分布和误差。
-
-
常见风险:当不同输入数值范围差异较大时,量化参数往往难以同时兼顾,容易让某一路误差明显放大。
-
一般怎么处理:如果差异明显,可优先考虑拆分量化路径,或者分别处理不同输入分支。
输入是否做了 0 对称归一化
plugin 采用的是均匀对称量化,因此 0 均值、关于 0 对称的输入分布通常更量化友好。
-
怎么判断:
-
先检查训练和推理前处理逻辑,确认输入是否围绕 0 做归一化。
-
如果需要,也可以直接观察输入 Tensor 的统计分布。
-
-
常见风险:输入分布若明显偏离 0 对称,通常会直接影响
Calibration和QAT的精度表现。 -
一般怎么处理:
-
尽量让模型输入做关于 0 对称的归一化。
-
如果模型结构允许,优先使用 BN 来帮助稳定数据分布。
-
对具有明确物理含义、范围稳定的数据,也可优先考虑 fix scale。
-
训练与部署输入域是否一致
训练与部署输入域不一致时,建议在这一阶段就单独确认。
-
怎么判断:
-
检查训练输入与部署输入是否存在
rgb / bgr / yuv / nv12等输入域差异。 -
检查前处理是否已经吸收到模型中,或者是否计划在部署时插入前处理节点。
-
-
常见风险:
-
即使模型结构本身没有问题,训练域和部署域不一致,也会在
Calibration、QAT和最终部署验证中带来额外掉点。 -
特别是
nv12输入会带来信息损失,如果训练阶段没有提前感知,后续定点模型和板端模型更容易出现超预期掉点。
-
-
一般怎么处理:
-
对
nv12 / yuv这类部署输入带来的损失,尽量前置感知,不要拖到编译或上板后再处理。 -
如果必须通过插入前处理节点来吸收输入域差异,请在后续 模型校准 Calibration 和 部署一致性分析说明 阶段重点关注这一部分误差来源。
-
数据分布是否量化友好
数据分布不合理,例如长尾、离群点过多,或者整体数值范围与量化 bit 不匹配,也会显著影响量化效果。
需要注意的是,QAT 本身也有一定的修正能力,因此“量化不友好”并不等于“不能量化”。很多时候,最终是否需要在这里做结构性调整,仍然要结合后续 Calibration、QAT 的难度以及最后达到的量化精度综合判断。
如果模型已经出现明显过拟合,常见表现是数值范围偏大、输入稍有扰动就会引起明显输出变化。这类情况通常也会放大量化误差。遇到这种情况时,可以优先从 weight decay、数据增强和训练策略上做调整,再继续观察 Calibration 和 QAT 的效果。
如何判断结果正常
完成整体浮点模型的构建后,如果下面几项判断基本成立,就可以继续进入下一阶段:
-
模型的量化边界已经明确,
QuantStub / DeQuantStub插入位置符合预期。 -
模型输入域与部署输入域的差异已经被识别,或者已经有明确的处理方案。
-
没有明显的高风险结构、共享模块或异常输入分布被遗漏。
