构建 QConfig

Qconfig(Quantization Configuration)指的是量化配置,是深度学习模型量化过程中的关键参数集合,模型的量化方式由 qconfig 决定,在准备 calibration / qat 模型之前,需要先给模型设置 qconfig。 在前文的 PyTorch 模型量化基本流程 章节中,我们已经介绍了 QConfig 在整条链路中的位置。 接下来,本章将进一步结合新版 qconfig 模板的实际使用方式,为您介绍如何开始配置、如何选择模板,以及在典型场景下如何使用模板。

新版 qconfig

随着工具链在量化精度方向的不断优化,自工具链 OE3.5.0 版本开始,我们支持了新版 qconfig 模板。 新版 qconfig 将模板配置和回退逻辑统一到了同一套流程中,在适配平台能力、提升量化配置效率和支持典型高精度场景方面,都比旧版方式更适合当前使用。

与旧版相比,新版 qconfig 模板的变化主要体现在以下几个方面:

  • 模板和回退机制统一管理,整体流程更清晰。

  • Conv / Matmul / Linear 等 Gemm 类算子的单 / 双 int16 输入配置支持更友好。

  • 先配置 dtype,再根据要求决定是否 fuse,最终结果更符合预期。

  • 在 prepare 后自动保存 qconfig_dtypes.ptqconfig_dtypes.pt.pyqconfig_changelogs.txt,方便复用、检查和追溯。

注解

如果您当前仍在使用旧版 qconfig,建议您优先切换到新版模板方式进行使用。 如果您希望了解旧版 qconfig 的兼容与迁移方式,请参考兼容性说明-旧版 Qconfig 说明章节。

在新版模板用法中,您通常不再需要把重点放在手写 QConfig(...) 这类底层定义上,不再需要单独手写复杂的各项配置,而是可以优先关注 QconfigSetter + 模板 这条主线。

QconfigSetter 说明

QconfigSetter 是当前最推荐的 qconfig 配置方式。 它会结合模型计算图,按照模板顺序自动为模型设置 qconfig,并在 prepare 后输出对应的配置结果。

使用 QconfigSetter 依赖 prepare 过程的图模式,用法如下:

from horizon_plugin_pytorch.quantization import prepare, PrepareMethod, get_qconfig
from horizon_plugin_pytorch.quantization.qconfig_setter import *
from horizon_plugin_pytorch.quantization.observer_v2 import HistogramObserver

my_qconfig_setter = QconfigSetter(
    reference_qconfig=get_qconfig(observer=HistogramObserver),   # 用于提供 observer 的 qconfig
    templates=[                   # 配置的模板
        ...
    ],
    enable_optimize=True,         # 开启默认的各项优化
    save_dir=args.save_path,      # qconfig 相关配置结果和 changelog 的保存路径
)

float_model.eval()
qat_model = prepare(
    float_model,
    example_inputs=example_input,     # 图模式需要提供模型输入,用于获取计算图
    qconfig_setter=my_qconfig_setter,
    check_result_dir=args.save_path,
    method=PrepareMethod.JIT_STRIP,   # QconfigSetter 依赖计算图
)

使用时,我们建议您重点关注以下几个参数:

  • reference_qconfig,必要配置,用于统一指定 observer,observer 用于在后续 Calibration 或 QAT 过程中观测张量分布,为后续量化参数计算提供基础信息。

  • templates,必要配置,用于定义不同位置的 dtype、threshold 及其覆盖范围。当前可配置模板包括 ModuleNameTemplateConvDtypeTemplateMatmulDtypeTemplateSensitivityTemplateLoadFromFileTemplate,详细介绍可参考下方各模板详细介绍。

  • enable_optimize,用于决定是否启用默认优化模板 pass,通常建议保持为 True

  • save_dir,建议始终开启。prepare 执行后,qconfig 相关的配置结果和变更记录会保存到这里,便于您检查配置是否符合预期,也便于后续复用已有配置。相关产物的详细说明可参考 Prepare 说明 章节。

注意

custom_qconfig_mapping 虽然在接口层面可用,但它会覆盖模板结果,甚至可能导致板端出现 CPU 算子。因此在常规配置场景下,我们并不建议您把它作为主要的 dtype 调整入口。

关于不同 observer 的差异、scale 搜索方式和 reset_scale 的用法,可参考 模型校准 Calibration 章节的相关介绍。

核心模板说明

当前支持您配置的模板包括:

模板是否必选主要作用常见用途
ModuleNameTemplate必要通过 module name 或 prefix 配置 dtype 和 threshold全局配置、局部覆盖、fix scale
ConvDtypeTemplate必要批量配置 Conv / Linear 类算子的输入和权重 dtype配置卷积和线性层的 int8 / int16 精度
MatmulDtypeTemplate必要批量配置 Matmul 类算子的输入 dtypeTransformer、注意力和 Gemm 精度配置
SensitivityTemplate可选根据敏感度分析结果提升关键算子的精度精度调优阶段批量提升 int16
LoadFromFileTemplate可选从已有 qconfig_dtypes.pt 中加载配置复用历史配置、兼容旧流程
注意

这些模板按配置顺序生效,前面模板的配置可被后面的模板覆盖,因此模板顺序非常重要。

ModuleNameTemplate

ModuleNameTemplate 用于通过 module name 指定 dtype 配置或量化阈值配置。 它支持全局、模型片段和算子级别的配置,也是很多 qconfig 配置的起点。

最常见的用法包括:

  • 配置全局输出 dtype。

  • 配置某个 prefix 下的一组模块使用不同精度。

  • 为特定算子补充 threshold,从而实现 fix scale。

ModuleNameTemplate 的详解主要包括以下几点:

  1. module name 可以为算子名或 prefix。当一个 ModuleNameTemplate 中不同的 module name 存在覆盖关系时,越长的名字优先级越高,例如:
ModuleNameTemplate(
    {
        "": qint8,  # 全局 qint8
        "head": qint16,  # 优先级高过全局配置,head 最终配置为 int16
        "head.conv0": torch.float16,  # 优先级高过 head 的配置,head.conv0 最终配置为输出 float16
    }
)
  1. 可以指定算子的 threshold。前提是算子中存在对应的伪量化节点,此时量化 scale 的计算方式为 scale=thresholdqdtype.minscale = \frac{threshold}{-qdtype.min},例如:
ModuleNameTemplate(
    {
        "quant": {"dtype": qint8, "threshold": 1.0},  # quant 的量化 scale 为 1.0/128
    }
)
  1. dtypethreshold 默认配置到算子的输出上,可通过指定 key 的方式配置 inputweight。在算子有多个输入时,可使用 None 做占位符,例如:
ModuleNameTemplate(
    {
        "conv0": {"input": qint8, "weight": qint16},
        "conv1": {"dtype": {"input": qint16}, "threshold": {"weight": 1.0}},
        "matmul0": {
            "dtype": {"input": [qint16, None]},
            "threshold": {"input": [1.0, None]},  # 将 matmul0 的第一个输入配置为 int16 且固定 scale 为 1.0/32768,第二个输入不做配置
        },
    }
)
  1. 模型中的某些位置很难依靠统计方式获得最佳量化 scale,例如物理量相关的输入或输出。 当算子的值域比较明确时,在新版 qconfig 模板中,您可以通过配置 dtype + threshold 来实现 fix scale,避免后续 Calibration 或 QAT 过程中基于统计或滑动平均得到的 scale 不能完整覆盖有效值域。
小技巧

部署时模型输入来源为 pyramid 或 resizer 的模型,需要输入节点量化精度配置为 int8 类型,另外这类输入一般是经过归一化的,数值范围在 [-1,1] 或者 [0,1],因此建议可以直接设置 fix scale。

在新版 qconfig 模板中,scale 的计算方式为:scale=thresholdqdtype.minscale = \frac{threshold}{-qdtype.min}, 其中 threshold 一般为该输入或输出绝对值的最大值,qdtype 为对应的量化类型。前面第 2 点和第 3 点中带 threshold 的配置,就是新版模板里 fix scale 的常见写法,例如:

my_qconfig_setter = QconfigSetter(
    reference_qconfig=get_qconfig(observer=HistogramObserver),
    templates=[
        ModuleNameTemplate({"": qint8}),
        ConvDtypeTemplate(input_dtype=qint8, weight_dtype=qint8),
        MatmulDtypeTemplate(input_dtypes=qint8),
        ModuleNameTemplate(
            {
                "backbone.quant": {"dtype": qint8, "threshold": 1.0},
                "backbone.conv1.conv1_2.conv": {
                    "dtype": {"weight": qint16},
                    "threshold": {"weight": 1.0},
                },
            }
        ),
    ],
    save_dir=args.save_path,
)

fix scale 是否生效,您可以在 prepare 后,通过生成的 model_check_result.txt 进行确认。

ConvDtypeTemplate

ConvDtypeTemplate 用于批量配置 Conv / Linear 类算子的输入和权重 dtype。

它最常见的作用包括:

  • 配置全局 Conv / Linearint8

  • 对特定 prefix 下的卷积算子做单 int16 或双 int16 输入配置。

  • 在混合精度场景下,针对部分敏感算子提高精度。

在实际使用中,ConvDtypeTemplate 一般需要和 ModuleNameTemplateMatmulDtypeTemplate 一起配置。 常见顺序是先用 ModuleNameTemplate 配全局输出 dtype,再用 ConvDtypeTemplateMatmulDtypeTemplate 完成基础 Gemm 配置,最后再追加局部 ConvDtypeTemplate 做覆盖。

配置示例如下:

my_qconfig_setter = QconfigSetter(
    reference_qconfig=get_qconfig(observer=HistogramObserver),
    templates=[
        ModuleNameTemplate({"": qint8}),
        ConvDtypeTemplate(
            input_dtype=qint8,
            weight_dtype=qint8,
        ),
        MatmulDtypeTemplate(
            input_dtypes=qint8,
        ),
        ConvDtypeTemplate(
            input_dtype=qint8,
            weight_dtype=qint16,
            prefix=["backbone.conv1.conv1_3.conv"],
        ),
    ],
)

MatmulDtypeTemplate

MatmulDtypeTemplate 用于配置 Matmul 类算子的输入 dtype。

它支持:

  • int8 输入。

  • int16 输入。

  • int16 输入。

  • 按 prefix 批量配置。

在 Transformer 或注意力相关结构中,这类模板通常会比较常用。

在实际使用中,MatmulDtypeTemplate 也通常需要和 ModuleNameTemplateConvDtypeTemplate 一起配置。 常见顺序同样是先完成基础模板配置,再在后面追加局部 MatmulDtypeTemplate 做覆盖。

配置示例如下:

my_qconfig_setter = QconfigSetter(
    reference_qconfig=get_qconfig(observer=HistogramObserver),
    templates=[
        ModuleNameTemplate({"": qint8}),
        ConvDtypeTemplate(
            input_dtype=qint8,
            weight_dtype=qint8,
        ),
        MatmulDtypeTemplate(
            input_dtypes=[qint8, qint8],
        ),
        MatmulDtypeTemplate(
            input_dtypes=[qint16, qint8],
            prefix=["encoder.encoder.0.layers.0.self_attn.matmul"],
        ),
    ],
)

SensitivityTemplate

SensitivityTemplate 用于根据敏感度分析结果,把排序靠前的敏感算子提升到更高精度。

它支持:

  • 激活敏感算子提升。
  • weight 敏感算子提升。
  • 同时处理激活和 weight 两类敏感算子。

更常见的用法是先完成一套基础 qconfig 配置,再通过敏感度模板将关键算子提升为 int16

SensitivityTemplate 通常放在基础模板之后使用。也就是说,先完成 ModuleNameTemplateConvDtypeTemplateMatmulDtypeTemplate 的基础配置,再在后面追加敏感度模板做高精度提升。

配置示例如下:

table1 = torch.load("output1_ATOL_sensitive_ops.pt")
table2 = torch.load("output2_ATOL_sensitive_ops.pt")

my_qconfig_setter = QconfigSetter(
    reference_qconfig=get_qconfig(observer=HistogramObserver),
    templates=[
        ModuleNameTemplate({"": qint8}),
        ConvDtypeTemplate(input_dtype=qint8, weight_dtype=qint8),
        MatmulDtypeTemplate(input_dtypes=qint8),
        SensitivityTemplate(
            sensitive_table=table1,
            topk_or_ratio=10,
            sensitive_type="weight",
        ),
        SensitivityTemplate(
            sensitive_table=table2,
            topk_or_ratio=0.5,
            sensitive_type="activation",
        ),
    ],
)

LoadFromFileTemplate

LoadFromFileTemplate 用于从 qconfig_dtypes.pt 中加载之前保存的量化配置,适合复用已有配置结果。

注意

使用时需要特别注意以下几点:

  • qconfig_dtypes.pt 不保存 fix_scale 信息,因此如果原配置中包含 fix scale,加载后仍需要手工补配。

  • 使用 LoadFromFileTemplate 后,不支持继续修改 dtype。

  • 此时 enable_optimize 必须配置为 False,否则无法保证结果正确,部署时甚至可能出现 CPU 算子。

  • 如果您是在做旧版 qconfig 迁移,还需要额外关注兼容约束和一些参数对齐,请参考兼容性说明-旧版 Qconfig 说明章节。

配置示例如下:

my_qconfig_setter = QconfigSetter(
    reference_qconfig=get_qconfig(observer=HistogramObserver),
    templates=[
        LoadFromFileTemplate("qconfig_dtypes.pt"),
    ],
    enable_optimize=False,
    save_dir=args.save_path,
)

典型配置示例

全局 int8/int16

如果您希望先快速跑通一套基础配置,可以从全局 int8 开始:

QconfigSetter(
    reference_qconfig=get_qconfig(),
    templates=[
        ModuleNameTemplate({"": qint8}),
        ConvDtypeTemplate(input_dtype=qint8, weight_dtype=qint8),
        MatmulDtypeTemplate(input_dtypes=qint8),
    ],
)

这是很多模型常见的起点,尤其适合先完成一次 Calibration 验证。

配置全局 int16 的方式与全局 int8 类似,将上方示例中的 qint8 修改为 qint16 即可。

注意
  • 配置全局 int8/int16时必须要对 Conv 类算子的 weight 进行配置,否则 weight 会自动做 int16 计算,并可能出现不符合预期的 CPU 算子。

  • 配置全局 int8 后,model_check_result.txt中可能会显示模型中仍然存在 int16 计算的算子,这是工具为了提升量化精度做的自动化行为,比如 norm 这种进行拆分实现的算子,内部会采用 int16 较高精度的计算,然后再输出为 int8。

feature int16,weight int8

如果您希望先从更高的 feature 精度开始,也可以这样配置:

QconfigSetter(
    reference_qconfig=get_qconfig(),
    templates=[
        ModuleNameTemplate({"": qint16}),
        ConvDtypeTemplate(input_dtype=qint16, weight_dtype=qint8),
        MatmulDtypeTemplate(input_dtypes=qint16),
    ],
)

这里有两点经验需要特别注意:

  • 当您配置全局 feat int8 / int16 / fp16 时,必须同步对 Conv 类算子的 weight 做配置,否则 weight 可能会自动采用 int16 计算,并进一步导致不符合预期的 CPU 算子。

  • 当您配置全局 int8 后,model_check_result.txt 中仍可能看到部分算子以 int16 计算。这通常是工具为了提升量化精度做的自动化处理,例如某些拆分实现的算子会在内部采用更高精度计算,再输出为 int8

如果您还希望对某个 prefix 下的模块批量回切到 int8,也可以继续叠加 ModuleNameTemplateConvDtypeTemplateMatmulDtypeTemplate,例如:

my_qconfig_setter = QconfigSetter(
    reference_qconfig=get_qconfig(observer=HistogramObserver),
    templates=[
        ModuleNameTemplate({"": qint16}),
        ConvDtypeTemplate(input_dtype=qint16, weight_dtype=qint8),
        MatmulDtypeTemplate(input_dtypes=qint16),
        ModuleNameTemplate({"backbone": qint8}),
        MatmulDtypeTemplate(
            input_dtypes=[qint8, qint8],
            prefix=["backbone"],
        ),
        ConvDtypeTemplate(
            input_dtype=qint8,
            weight_dtype=qint8,
            prefix=["backbone"],
        ),
    ],
)

gemm 算子双 int8,其他算子 fp16

这类配置是 J6P/H 平台较常见的基础起点之一。 由于平台具备浮点能力,vector 类计算保留为 fp16 往往更容易先跑通流程,同时又可以将 Conv / Matmul 这类 Gemm 算子收敛到 int8 计算,是精度和性能之间较常见的起步配置。 参考配置如下:

QconfigSetter(
    reference_qconfig=get_qconfig(),
    templates=[
        ModuleNameTemplate({"": torch.float16}),  # 全部算子配置为 fp16 输出
        ConvDtypeTemplate(input_dtype=qint8, weight_dtype=qint8),  # conv 的 input 和 weight 配置为 int8
        MatmulDtypeTemplate(input_dtypes=qint8),  # matmul 两个输入均配置为 int8
    ],
)

gemm 算子双 int8,其他算子 fp16,并将高敏感度 gemm 配置为 int16

如果您已经有敏感度分析结果,也可以继续使用 SensitivityTemplate 做批量提升,将高敏感度 Gemm 算子提升为 int16,参考配置如下:

table1 = torch.load("output1_ATOL_sensitive_ops.pt")
table2 = torch.load("output2_ATOL_sensitive_ops.pt")

my_qconfig_setter = QconfigSetter(
    reference_qconfig=get_qconfig(observer=HistogramObserver),
    templates=[
        ModuleNameTemplate({"": torch.float16}),
        ConvDtypeTemplate(input_dtype=qint8, weight_dtype=qint8),
        MatmulDtypeTemplate(input_dtypes=[qint8, qint8]),
        SensitivityTemplate(
            sensitive_table=table1,
            topk_or_ratio=10,
        ),
        SensitivityTemplate(
            sensitive_table=table2,
            topk_or_ratio=0.1,
        ),
    ],
)

gemm 算子双 int8,其他算子 int16,并将高敏感度 gemm 配置为 int16

如果您希望进一步验证更高精度配置,也可以以“其他算子 int16,Gemm 算子双 int8”为基础,再将高敏感度 Gemm 批量提升为 int16,这种配置更适合用来验证更高精度上限,再根据部署性能要求逐步回退部分高精度 Gemm。 参考配置如下:

table1 = torch.load("output1_ATOL_sensitive_ops.pt")
table2 = torch.load("output2_ATOL_sensitive_ops.pt")

my_qconfig_setter = QconfigSetter(
    reference_qconfig=get_qconfig(observer=HistogramObserver),
    templates=[
        ModuleNameTemplate({"": qint16}),
        ConvDtypeTemplate(input_dtype=qint8, weight_dtype=qint8),
        MatmulDtypeTemplate(input_dtypes=[qint8, qint8]),
        SensitivityTemplate(
            sensitive_table=table1,
            topk_or_ratio=10,
        ),
        SensitivityTemplate(
            sensitive_table=table2,
            topk_or_ratio=0.1,
        ),
    ],
)

四个常用模板组合使用示例

一般来说,ModuleNameTemplateConvDtypeTemplateMatmulDtypeTemplateSensitivityTemplate 这 4 个模板是最常一起使用的一组组合。下面给出一个整合示例,便于您整体理解各模板的配合方式:

from horizon_plugin_pytorch.quantization.qconfig_setter import *
import torch

table1 = torch.load("output1_ATOL_sensitive_ops.pt")
table2 = torch.load("output2_ATOL_sensitive_ops.pt")

my_qconfig_setter = QconfigSetter(
    reference_qconfig=get_qconfig(observer=HistogramObserver),
    templates=[
        ModuleNameTemplate({"": qint8}),
        ConvDtypeTemplate(input_dtype=qint8, weight_dtype=qint8),
        MatmulDtypeTemplate(input_dtypes=qint8),
        ModuleNameTemplate(
            {"quant": {"dtype": qint8, "threshold": 1.0}}
        ),
        MatmulDtypeTemplate(
            input_dtypes=[qint16, qint8],
            prefix=["head"],
        ),
        ConvDtypeTemplate(
            input_dtype=qint8,
            weight_dtype=qint16,
            prefix=["backbone"],
        ),
        SensitivityTemplate(
            sensitive_table=table1,
            topk_or_ratio=10,
            sensitive_type="weight",
        ),
        SensitivityTemplate(
            sensitive_table=table2,
            topk_or_ratio=0.5,
            sensitive_type="activation",
        ),
    ],
)

这个示例中,前 3 个模板用于完成基础配置,后面再分别叠加 fix scale、局部高精度和敏感度提升配置。实际使用时,您可以根据模型结构和调优目标删减或替换其中的模板。

默认优化模板 pass 说明

除您显式配置的模板外,QconfigSetter 还集成了一系列默认优化模板,用于合法化和优化 qconfig 结果,在配置 enable_optimize 为 True时,默认优化模板生效, 这些模板不一定需要您逐个手工配置,但理解它们的作用很重要,可以帮助您理解默认优化模板会做哪些优化。

主要包括:

  • CanonicalizeTemplate:按算子类型对 dtype 配置进行合法化,当前默认规则有:

    • Gemm 类算子不支持 float 输入,含 weight。

    • 插值类算子默认仍优先使用 int8,当前 nash-e/m/p/h 平台上的插值类算子虽支持了 fp16 和 int16,但性能较差,所以模板默认仍使用了 int8。若插值算子需要使用其他 dtype,请使用 ModuleNameTemplate(..., freeze=True) 将它的上一个算子的输出配置为需要的 dtype。

    • DPP、RPP 等特殊算子仅支持 int8。

    • 其他算子的通用规则是,算子的 input dtype 和 output dtype 不同时包含 qint 和 float。

  • EqualizeInOutScaleTemplate:对于 relurelu6concatstack 算子,应该在算子之后统计 scale,否则精度或性能存在损失。为此:

    • 将前面算子的 output dtype 配置为 float32

    • Relu、concat、stack 算子在 export hbir 时,在 input 处插入伪量化,scale 复用 output scale。

  • FuseConvAddTemplate:处理硬件支持的 conv + add fuse 场景,为此:

    • 将 conv 的 output dtype 配置为 float32

    • 将 add 对应的 input dtype 配置为 float32

  • GridHighPrecisionTemplate:根据经验,grid sample 的 grid 计算过程用 qint8 精度不够,因此自动将相关算子配置为高精度。

  • InternalQuantsTemplate:模型分段部署场景下,会在分段点处插入 QuantStub,用于记录此处的 dtype 和 scale,此类 QuantStub 的 dtype 配置必须和输入保持一致。

  • OutputHighPrecisionTemplate:当 Gemm 类算子作为模型输出时,将其配置为高精度输出。

  • PropagateTemplate:对于拆分为子图实现的算子,存在经验性配置,如 LayerNormSoftmax 内部小算子应使用高精度。

  • SimpleIntPassTemplate:性能优化,对于 op0 -> op1 -> op2 这类计算图中,同时满足以下条件时,会将 op1 输出类型修改为 int:

    • op2 需要 int 输入配置。

    • op0 可以输出 int。

    • op1 当前输出为 float16,且属于以下类型:

      • catstack

      • mul_scalar

      • 无精度风险的查表算子,即在 fp16 上默认使用查表实现的算子。

  • SimplifyTemplate:删除多余的量化节点配置,将对应的 dtype 修改为 None

如果您发现最终 qconfig 结果与模板直觉不完全一致,很多时候就是这些默认优化模板起了作用。

常见使用误区

使用 LoadFromFileTemplate 后继续修改 dtype

这是不推荐的用法。

加载 qconfig_dtypes.pt 后,原则上不应再继续修改 dtype。如果仍继续修改,不仅结果可能不稳定,还可能破坏原本的优化结果。

只配了全局 dtype,没有显式配置 Conv / Matmul

这是另一个常见误区。

例如只通过 ModuleNameTemplate({"": qint8}) 做了全局配置,但没有同步配置 ConvDtypeTemplateMatmulDtypeTemplate,最终结果就可能与您的预期不一致。

因此在大多数场景下,ModuleNameTemplateConvDtypeTemplateMatmulDtypeTemplate 都应该同时考虑,而不是只配一个全局模板。

如果您在实际使用中遇到 dtype 回退、weight dtype 与预期不一致,或者最终结果与模板直觉不一致,建议参考常见故障章节的介绍,结合 model_check_result.txtqconfig_changelogs.txt 继续定位。