构建浮点模型

这一阶段,我们主要做两件事:

  1. 把浮点模型改造成可进入量化流程的模型。

  2. 在进入 PrepareCalibrationQAT 等后续流程之前,先把明显的结构风险、输入域差异和量化友好性问题找出来。

如果这里处理得比较充分,后续的 PrepareCalibrationQAT 等流程会顺很多。

模型改造

拿到浮点模型后,我们需要先做必要的改造,使模型能够进入后续量化流程。最基本的改造包括:

  • 在模型输入前插入 QuantStub 节点。

  • 在模型输出后插入 DeQuantStub 节点。

  • 对于模型 forward 逻辑中存在次数可变的循环,例如时序模型逐帧计算,使用 horizon_plugin_pytorch.fx.jit_scheme.dynamic_block 接口进行标记。相关说明可参考 Prepare说明

注意

最容易出问题的地方,是没有把 QuantStubDeQuantStub 放在部署逻辑与非部署逻辑的边界上。

例如,计算辅助 loss 的 head 并不需要部署,就应当在这个 head 的输入处插入 DeQuantStub,这样这个 head 才不会进入量化范围。

另外,插入 QuantStubDeQuantStub 时还需要注意下面几点:

  • 仅关注需要量化的数据,也就是浮点类型数据;其他原生定点型或布尔型数据不需要插入 Stub。

  • QuantStubDeQuantStub 之间的计算,就是工具真正量化的范围。通常模型的辅助头和 loss 等并不需要部署,因此要确保这些逻辑不在量化范围。

  • 如果模型中存在定点类型与浮点类型之间的混合计算,torch 可能会自动把结果 cast 回浮点,但对量化工具来说,这里仍然属于“量化与非量化的边界”,需要您显式处理这个边界。

改造模型时,还需要注意:

  • 插入的 QuantStubDeQuantStub 节点必须注册为模型的子模块,否则工具无法正确处理它们的量化状态。

  • 多个输入只有在数值分布相同时才适合共享 QuantStub,只要输入分布差异明显,我们都建议您拆开处理。

  • 如果后续上板时输入数据来源为 "pyramid",请手动将对应 QuantStubscale 固定为 1/128

  • 您也可以使用 torch.quantization.QuantStub,但只有 horizon_plugin_pytorch.quantization.QuantStub 支持通过参数手动固定 scale。

注解

下面给出一个简化的多输入多输出示例。每个 QuantStub 负责一个输入 Tensor 的量化。

class Net(torch.nn.Module):
    def __init__(self):
        self.quantx = QuantStub()
        self.quanty = QuantStub()
        self.dequant = DeQuantStub()
        ...

    def forward(self, x, y):
        x = self.quantx(x)
        y = self.quanty(y)
        ...
        ret_0 = self.dequant(ret_0)
        ret_1 = self.dequant(ret_1)
        return ret_0, ret_1

改造后的模型可以无缝加载改造前模型的参数,因此如果已经有训练好的浮点模型,直接加载即可,否则请您先完成正常的浮点训练。

模型改造完成后,建议您先检查如下几项:

  • 量化边界是否已经明确,QuantStub / DeQuantStub 的位置是否符合预期。

  • 模型中是否已经包含明显不受支持的算子,避免问题拖到 Prepare、导出或编译阶段才暴露。

  • 模型是否包含明显不利于部署的 CPU 算子。这里可结合 性能调优章节中的 CPU 算子排查方法 一并确认。

量化友好性检查

浮点模型变为定点模型时天然会引入一定精度误差。模型越量化友好,后续 CalibrationQAT 往往越容易收敛,最后的量化精度通常也越高。

一般来说,下面几种情况都可能让模型变得不够量化友好:

  1. 使用有精度风险的算子。例如 softmaxlayernorm 等,这类算子一般底层由查表或多个 op 拼接实现,更容易发生掉点问题。

  2. 一次 forward 中多次调用同一算子。同一算子多次调用时,对应的输出分布可能存在差异,但通常只会统计一组量化参数;当多次调用的输出分布差异过大时,量化误差就会变大。

  3. addcat 等多输入算子的不同输入差异过大,也可能造成较大误差。

  4. 数据分布不合理。plugin 采用的是均匀对称量化,因此 0 均值的均匀分布通常更友好,应尽量避免长尾和离群点。同时,数值范围也需要与量化 bit 匹配;如果使用 int8 去量化分布在 [-1000, 1000] 的均匀数据,精度通常是不够的。

例如,下面三个分布图从左到右,对量化的友好性依次递减。模型中大部分数值的分布通常应更接近中间这种形态。实际使用时,可以结合 debug 工具观察模型中的 weight 和 feature map 分布是否量化友好。

data_distribution

因为模型本身存在一定冗余性,有些看起来不够量化友好的算子并不一定会显著拉低最终精度。因此,这里的判断最好和后续 CalibrationQAT 的训练难度以及最终量化精度一起综合看。

下方这些检查项可以帮助您在进入后续量化阶段前,先把常见的结构问题、输入问题和量化风险检查一遍。

是否使用了量化不友好的算子

softmaxlayernorm 等算子通常有更高的量化风险,exppowreciprocalsigmoidGeLUinverse_sigmoid 这类结构也需要重点关注。

  • 怎么判断:先直接从模型结构和 forward 路径看部署主干中是否包含这些算子。

  • 常见风险:这类算子要么值域不稳定,要么底层实现复杂,更容易在量化后放大误差。

  • 一般怎么处理:

    • 能不进入部署主干的,尽量不要进入部署主干。

    • 如果必须保留,建议在后续 CalibrationQAT 和一致性分析阶段重点关注这些位置的误差和敏感度表现。

是否存在共享模块

一次 forward 中多次调用同一算子,会导致不同调用位置共用同一组量化参数,容易放大量化误差。

  • 怎么判断:

    • 直接检查 forward 中是否复用了同一个模块实例。

    • Prepare 后,也可以结合 fx_graph.txt 和后续 debug 产物继续确认。

  • 常见风险:共享模块在 Prepare、校准和训练阶段都可能引起 scale 对齐、图结构理解和训练行为上的问题。

  • 一般怎么处理:如果不同调用位置的输出分布差异明显,优先考虑将共享模块拆开分别使用。

多输入分布差异是否过大

addcat 等多输入算子的不同输入差异过大,可能造成较大误差。

  • 怎么判断:

    • 先从结构上判断不同输入是否来自不同分支、不同特征层或不同数据域。

    • 后续也可以结合 debug 工具观察这些输入的分布和误差。

  • 常见风险:当不同输入数值范围差异较大时,量化参数往往难以同时兼顾,容易让某一路误差明显放大。

  • 一般怎么处理:如果差异明显,可优先考虑拆分量化路径,或者分别处理不同输入分支。

输入是否做了 0 对称归一化

plugin 采用的是均匀对称量化,因此 0 均值、关于 0 对称的输入分布通常更量化友好。

  • 怎么判断:

    • 先检查训练和推理前处理逻辑,确认输入是否围绕 0 做归一化。

    • 如果需要,也可以直接观察输入 Tensor 的统计分布。

  • 常见风险:输入分布若明显偏离 0 对称,通常会直接影响 CalibrationQAT 的精度表现。

  • 一般怎么处理:

    • 尽量让模型输入做关于 0 对称的归一化。

    • 如果模型结构允许,优先使用 BN 来帮助稳定数据分布。

    • 对具有明确物理含义、范围稳定的数据,也可优先考虑 fix scale。

训练与部署输入域是否一致

训练与部署输入域不一致时,建议在这一阶段就单独确认。

  • 怎么判断:

    • 检查训练输入与部署输入是否存在 rgb / bgr / yuv / nv12 等输入域差异。

    • 检查前处理是否已经吸收到模型中,或者是否计划在部署时插入前处理节点。

  • 常见风险:

    • 即使模型结构本身没有问题,训练域和部署域不一致,也会在 CalibrationQAT 和最终部署验证中带来额外掉点。

    • 特别是 nv12 输入会带来信息损失,如果训练阶段没有提前感知,后续定点模型和板端模型更容易出现超预期掉点。

  • 一般怎么处理:

    • nv12 / yuv 这类部署输入带来的损失,尽量前置感知,不要拖到编译或上板后再处理。

    • 如果必须通过插入前处理节点来吸收输入域差异,请在后续 模型校准 Calibration部署一致性分析说明 阶段重点关注这一部分误差来源。

数据分布是否量化友好

数据分布不合理,例如长尾、离群点过多,或者整体数值范围与量化 bit 不匹配,也会显著影响量化效果。

需要注意的是,QAT 本身也有一定的修正能力,因此“量化不友好”并不等于“不能量化”。很多时候,最终是否需要在这里做结构性调整,仍然要结合后续 CalibrationQAT 的难度以及最后达到的量化精度综合判断。

如果模型已经出现明显过拟合,常见表现是数值范围偏大、输入稍有扰动就会引起明显输出变化。这类情况通常也会放大量化误差。遇到这种情况时,可以优先从 weight decay、数据增强和训练策略上做调整,再继续观察 CalibrationQAT 的效果。

如何判断结果正常

完成整体浮点模型的构建后,如果下面几项判断基本成立,就可以继续进入下一阶段:

  • 模型的量化边界已经明确,QuantStub / DeQuantStub 插入位置符合预期。

  • 模型输入域与部署输入域的差异已经被识别,或者已经有明确的处理方案。

  • 没有明显的高风险结构、共享模块或异常输入分布被遗漏。