构建 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.pt、qconfig_dtypes.pt.py和qconfig_changelogs.txt,方便复用、检查和追溯。
如果您当前仍在使用旧版 qconfig,建议您优先切换到新版模板方式进行使用。 如果您希望了解旧版 qconfig 的兼容与迁移方式,请参考兼容性说明-旧版 Qconfig 说明章节。
在新版模板用法中,您通常不再需要把重点放在手写 QConfig(...) 这类底层定义上,不再需要单独手写复杂的各项配置,而是可以优先关注 QconfigSetter + 模板 这条主线。
QconfigSetter 说明
QconfigSetter 是当前最推荐的 qconfig 配置方式。 它会结合模型计算图,按照模板顺序自动为模型设置 qconfig,并在 prepare 后输出对应的配置结果。
使用 QconfigSetter 依赖 prepare 过程的图模式,用法如下:
使用时,我们建议您重点关注以下几个参数:
-
reference_qconfig,必要配置,用于统一指定 observer,observer 用于在后续 Calibration 或 QAT 过程中观测张量分布,为后续量化参数计算提供基础信息。 -
templates,必要配置,用于定义不同位置的 dtype、threshold 及其覆盖范围。当前可配置模板包括ModuleNameTemplate、ConvDtypeTemplate、MatmulDtypeTemplate、SensitivityTemplate和LoadFromFileTemplate,详细介绍可参考下方各模板详细介绍。 -
enable_optimize,用于决定是否启用默认优化模板 pass,通常建议保持为True。 -
save_dir,建议始终开启。prepare 执行后,qconfig 相关的配置结果和变更记录会保存到这里,便于您检查配置是否符合预期,也便于后续复用已有配置。相关产物的详细说明可参考 Prepare 说明 章节。
custom_qconfig_mapping 虽然在接口层面可用,但它会覆盖模板结果,甚至可能导致板端出现 CPU 算子。因此在常规配置场景下,我们并不建议您把它作为主要的 dtype 调整入口。
关于不同 observer 的差异、scale 搜索方式和 reset_scale 的用法,可参考 模型校准 Calibration 章节的相关介绍。
核心模板说明
当前支持您配置的模板包括:
这些模板按配置顺序生效,前面模板的配置可被后面的模板覆盖,因此模板顺序非常重要。
ModuleNameTemplate
ModuleNameTemplate 用于通过 module name 指定 dtype 配置或量化阈值配置。
它支持全局、模型片段和算子级别的配置,也是很多 qconfig 配置的起点。
最常见的用法包括:
-
配置全局输出 dtype。
-
配置某个 prefix 下的一组模块使用不同精度。
-
为特定算子补充
threshold,从而实现 fix scale。
ModuleNameTemplate 的详解主要包括以下几点:
- module name 可以为算子名或 prefix。当一个
ModuleNameTemplate中不同的 module name 存在覆盖关系时,越长的名字优先级越高,例如:
- 可以指定算子的
threshold。前提是算子中存在对应的伪量化节点,此时量化 scale 的计算方式为 ,例如:
dtype和threshold默认配置到算子的输出上,可通过指定 key 的方式配置input或weight。在算子有多个输入时,可使用None做占位符,例如:
- 模型中的某些位置很难依靠统计方式获得最佳量化 scale,例如物理量相关的输入或输出。
当算子的值域比较明确时,在新版 qconfig 模板中,您可以通过配置
dtype + threshold来实现 fix scale,避免后续 Calibration 或 QAT 过程中基于统计或滑动平均得到的 scale 不能完整覆盖有效值域。
部署时模型输入来源为 pyramid 或 resizer 的模型,需要输入节点量化精度配置为 int8 类型,另外这类输入一般是经过归一化的,数值范围在 [-1,1] 或者 [0,1],因此建议可以直接设置 fix scale。
在新版 qconfig 模板中,scale 的计算方式为:,
其中 threshold 一般为该输入或输出绝对值的最大值,qdtype 为对应的量化类型。前面第 2 点和第 3 点中带 threshold 的配置,就是新版模板里 fix scale 的常见写法,例如:
fix scale 是否生效,您可以在 prepare 后,通过生成的 model_check_result.txt 进行确认。
ConvDtypeTemplate
ConvDtypeTemplate 用于批量配置 Conv / Linear 类算子的输入和权重 dtype。
它最常见的作用包括:
-
配置全局
Conv / Linear为int8。 -
对特定 prefix 下的卷积算子做单
int16或双int16输入配置。 -
在混合精度场景下,针对部分敏感算子提高精度。
在实际使用中,ConvDtypeTemplate 一般需要和 ModuleNameTemplate、MatmulDtypeTemplate 一起配置。
常见顺序是先用 ModuleNameTemplate 配全局输出 dtype,再用 ConvDtypeTemplate 和 MatmulDtypeTemplate 完成基础 Gemm 配置,最后再追加局部 ConvDtypeTemplate 做覆盖。
配置示例如下:
MatmulDtypeTemplate
MatmulDtypeTemplate 用于配置 Matmul 类算子的输入 dtype。
它支持:
-
双
int8输入。 -
单
int16输入。 -
双
int16输入。 -
按 prefix 批量配置。
在 Transformer 或注意力相关结构中,这类模板通常会比较常用。
在实际使用中,MatmulDtypeTemplate 也通常需要和 ModuleNameTemplate、ConvDtypeTemplate 一起配置。
常见顺序同样是先完成基础模板配置,再在后面追加局部 MatmulDtypeTemplate 做覆盖。
配置示例如下:
SensitivityTemplate
SensitivityTemplate 用于根据敏感度分析结果,把排序靠前的敏感算子提升到更高精度。
它支持:
- 激活敏感算子提升。
- weight 敏感算子提升。
- 同时处理激活和 weight 两类敏感算子。
更常见的用法是先完成一套基础 qconfig 配置,再通过敏感度模板将关键算子提升为 int16。
SensitivityTemplate 通常放在基础模板之后使用。也就是说,先完成 ModuleNameTemplate、ConvDtypeTemplate 和 MatmulDtypeTemplate 的基础配置,再在后面追加敏感度模板做高精度提升。
配置示例如下:
LoadFromFileTemplate
LoadFromFileTemplate 用于从 qconfig_dtypes.pt 中加载之前保存的量化配置,适合复用已有配置结果。
使用时需要特别注意以下几点:
-
qconfig_dtypes.pt不保存fix_scale信息,因此如果原配置中包含 fix scale,加载后仍需要手工补配。 -
使用
LoadFromFileTemplate后,不支持继续修改 dtype。 -
此时
enable_optimize必须配置为False,否则无法保证结果正确,部署时甚至可能出现 CPU 算子。 -
如果您是在做旧版 qconfig 迁移,还需要额外关注兼容约束和一些参数对齐,请参考兼容性说明-旧版 Qconfig 说明章节。
配置示例如下:
典型配置示例
全局 int8/int16
如果您希望先快速跑通一套基础配置,可以从全局 int8 开始:
这是很多模型常见的起点,尤其适合先完成一次 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 精度开始,也可以这样配置:
这里有两点经验需要特别注意:
-
当您配置全局
feat int8 / int16 / fp16时,必须同步对Conv类算子的weight做配置,否则weight可能会自动采用int16计算,并进一步导致不符合预期的 CPU 算子。 -
当您配置全局
int8后,model_check_result.txt中仍可能看到部分算子以int16计算。这通常是工具为了提升量化精度做的自动化处理,例如某些拆分实现的算子会在内部采用更高精度计算,再输出为int8。
如果您还希望对某个 prefix 下的模块批量回切到 int8,也可以继续叠加 ModuleNameTemplate、ConvDtypeTemplate 和 MatmulDtypeTemplate,例如:
gemm 算子双 int8,其他算子 fp16
这类配置是 J6P/H 平台较常见的基础起点之一。
由于平台具备浮点能力,vector 类计算保留为 fp16 往往更容易先跑通流程,同时又可以将 Conv / Matmul 这类 Gemm 算子收敛到 int8 计算,是精度和性能之间较常见的起步配置。
参考配置如下:
gemm 算子双 int8,其他算子 fp16,并将高敏感度 gemm 配置为 int16
如果您已经有敏感度分析结果,也可以继续使用 SensitivityTemplate 做批量提升,将高敏感度 Gemm 算子提升为 int16,参考配置如下:
gemm 算子双 int8,其他算子 int16,并将高敏感度 gemm 配置为 int16
如果您希望进一步验证更高精度配置,也可以以“其他算子 int16,Gemm 算子双 int8”为基础,再将高敏感度 Gemm 批量提升为 int16,这种配置更适合用来验证更高精度上限,再根据部署性能要求逐步回退部分高精度 Gemm。
参考配置如下:
四个常用模板组合使用示例
一般来说,ModuleNameTemplate、ConvDtypeTemplate、MatmulDtypeTemplate 和 SensitivityTemplate 这 4 个模板是最常一起使用的一组组合。下面给出一个整合示例,便于您整体理解各模板的配合方式:
这个示例中,前 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:对于relu、relu6、concat、stack算子,应该在算子之后统计 scale,否则精度或性能存在损失。为此:-
将前面算子的 output dtype 配置为
float32。 -
Relu、concat、stack 算子在 export hbir 时,在 input 处插入伪量化,scale 复用 output scale。
-
-
FuseConvAddTemplate:处理硬件支持的conv + addfuse 场景,为此:-
将 conv 的 output dtype 配置为
float32。 -
将 add 对应的 input dtype 配置为
float32。
-
-
GridHighPrecisionTemplate:根据经验,grid sample 的 grid 计算过程用 qint8 精度不够,因此自动将相关算子配置为高精度。 -
InternalQuantsTemplate:模型分段部署场景下,会在分段点处插入 QuantStub,用于记录此处的 dtype 和 scale,此类 QuantStub 的 dtype 配置必须和输入保持一致。 -
OutputHighPrecisionTemplate:当 Gemm 类算子作为模型输出时,将其配置为高精度输出。 -
PropagateTemplate:对于拆分为子图实现的算子,存在经验性配置,如LayerNorm和Softmax内部小算子应使用高精度。 -
SimpleIntPassTemplate:性能优化,对于op0 -> op1 -> op2这类计算图中,同时满足以下条件时,会将 op1 输出类型修改为 int:-
op2需要 int 输入配置。 -
op0可以输出 int。 -
op1当前输出为float16,且属于以下类型:-
cat、stack。 -
mul_scalar。 -
无精度风险的查表算子,即在 fp16 上默认使用查表实现的算子。
-
-
-
SimplifyTemplate:删除多余的量化节点配置,将对应的 dtype 修改为None。
如果您发现最终 qconfig 结果与模板直觉不完全一致,很多时候就是这些默认优化模板起了作用。
常见使用误区
使用 LoadFromFileTemplate 后继续修改 dtype
这是不推荐的用法。
加载 qconfig_dtypes.pt 后,原则上不应再继续修改 dtype。如果仍继续修改,不仅结果可能不稳定,还可能破坏原本的优化结果。
只配了全局 dtype,没有显式配置 Conv / Matmul
这是另一个常见误区。
例如只通过 ModuleNameTemplate({"": qint8}) 做了全局配置,但没有同步配置 ConvDtypeTemplate 和 MatmulDtypeTemplate,最终结果就可能与您的预期不一致。
因此在大多数场景下,ModuleNameTemplate、ConvDtypeTemplate 和 MatmulDtypeTemplate 都应该同时考虑,而不是只配一个全局模板。
如果您在实际使用中遇到 dtype 回退、weight dtype 与预期不一致,或者最终结果与模板直觉不一致,建议参考常见故障章节的介绍,结合 model_check_result.txt 和 qconfig_changelogs.txt 继续定位。
