S100 OpenExplorer

# #QConfig 详解

## #定义及原理

### #QConfig 的定义

qconfig(Quantization Configuration)指的是量化配置，是深度学习模型量化过程中的关键参数集合，模型的量化方式由 qconfig 决定，在准备 qat / calibration 模型之前，需要先给模型设置 qconfig。

注意

因历史原因，Plugin 中有不同 qconfig 的定义和用法，早期版本的 qconfig 将在不久的将来被废弃，我们只推荐您使用此文档中介绍的 qconfig 用法。

一个 qconfig 对象可以设置 input / weight / output 三个关键字，分别表示算子输入/权重/输出的量化配置，prepare 模型时会根据这些配置决定是否要在对应位置插入 FakeQuantize / FakeCast 节点，None 表示不插入任何节点。

`import torch from horizon_plugin_pytorch.quantization.qconfig import QConfig from horizon_plugin_pytorch.quantization.fake_quantize import FakeQuantize from horizon_plugin_pytorch.quantization.fake_cast import FakeCast from horizon_plugin_pytorch.quantization.observer_v2 import MinMaxObserver from horizon_plugin_pytorch.dtype import qint8 qconfig = QConfig( input=None, weight=FakeQuantize.with_args( observer=MinMaxObserver, dtype=qint8, qscheme=torch.per_channel_symmetric, ch_axis=0, ), output=FakeCast.with_args(dtype=torch.float16), # activation=xxx 早期用法，作用与 output 关键字一致，当前仍兼容，但建议您使用 output 关键字。 )`

### #FakeQuantize 的定义

FakeQuantize 是伪量化节点，会对输入进行量化反量化操作，插入伪量化可以在浮点模型的前向中模拟量化产生的误差。horizon_plugin_pytorch 支持 FakeQuantize / PACTFakeQuantize / _LearnableFakeQuantize 三种伪量化，我们只推荐您使用基于统计的 FakeQuantize，可以满足绝大部分需求。标准流程不对 PACTFakeQuantize 和 _LearnableFakeQuantize 两种方法做详细说明，如果一定有需求，请在阅读相关论文后再使用。

`# 基于统计的方法 from horizon_plugin_pytorch.quantization.fake_quantize import FakeQuantize # https://arxiv.org/pdf/1805.06085 from horizon_plugin_pytorch.quantization.pact_fake_quantize import PACTFakeQuantize # https://arxiv.org/pdf/1902.08153 from horizon_plugin_pytorch.quantization._learnable_fake_quantize import _LearnableFakeQuantize`

可以调用 FakeQuantize 的 with_args 方法得到构造器，并按上一节的代码示例用它构造 qconfig。with_args 的参数包括 FakeQuantize 和 observer 支持配置的参数，理论上可以配置所有 FakeQuantize 和 observer 类 init 方法声明中的参数，但为了屏蔽无关紧要的细节，我们只推荐您配置 observer 相关参数。

不同 observer 的参数不同，下面列出常用 observer 构造 FakeQuantize 的例子，其他 observer 的具体用法见校准章节。

`import torch from horizon_plugin_pytorch.quantization.qconfig import QConfig from horizon_plugin_pytorch.quantization.fake_quantize import FakeQuantize from horizon_plugin_pytorch.quantization.observer_v2 import MinMaxObserver, FixedScaleObserver, MSEObserver from horizon_plugin_pytorch.dtype import qint8 # MinMaxObserver 的 __init__ 方法包含很多参数，with_args 方法可以控制这些参数。 # 我们只推荐您设置 fq_constructor_1 示例中的几个参数。 # def __init__( # self, # averaging_constant: float = 0.01, # ch_axis: int = -1, # dtype: Union[torch.dtype, QuantDType] = qint8, # qscheme: torch.qscheme = torch.per_tensor_symmetric, # quant_min: int = None, # quant_max: int = None, # is_sync_quantize: bool = False, # factory_kwargs: Dict = None, # ) -> None: fq_constructor_1 = FakeQuantize.with_args( observer=MinMaxObserver, # 适用于 qat 阶段的 input / output / weight 和 calibration 阶段的 weight。 averaging_constant=0.01, # calibration 后进行 qat 时，可将 input / output 的 averaging_constant 置为 0 以固定 scale。 dtype=qint8, # 量化类型，考虑算子的支持情况进行设置。 qscheme=torch.per_channel_symmetric, # 只有 weight 支持 per channel 量化。 ch_axis=0, # per channel 量化时指定 channel。 ) # 同理，您也可以查看 FixedScaleObserver 和 MSEObserver 的 __init__ 方法了解有哪些可以设置的参数。 fq_constructor_2 = FakeQuantize.with_args( observer=FixedScaleObserver, # 固定 scale，无论何种情况都不会变。 dtype=qint8, # 量化类型，考虑算子的支持情况进行设置。 scale=INPUT_ABS_MAX / 128, # 设定的 scale 值，一般设为绝对值最大值除以量化类型最大值。 ) fq_constructor_3 = FakeQuantize.with_args( observer=MSEObserver, # 适用于 calibration 阶段的 input / output。 dtype=qint8, # 量化类型，考虑算子的支持情况进行设置。 ) qconfig = QConfig( weight=fq_constructor_x, ... )`

### #FakeCast 的定义

FakeCast 是伪转换节点，会将输入转换为 float32 类型，如果数据类型是 float16，那么还会在中间模拟转 float16 产生的截断误差，此节点主要用于标志需要浮点计算的算子。

使用 FakeCast 构造 qconfig 的方法与 FakeQuantize 类似，但只有 dtype 一个参数。

`import torch from horizon_plugin_pytorch.quantization.qconfig import QConfig from horizon_plugin_pytorch.quantization.fake_cast import FakeCast qconfig = QConfig( input=FakeCast.with_args(dtype=torch.float16), # 考虑算子的支持情况进行设置。 ... )`

### #构造 QConfig

在构造 Qconfig 时，有两种方法供您选择：

- 按照上文介绍的定义方法，直接构造 QConfig 对象。这种方法比较灵活，可以配置任何可配置的参数，但同时要求也比较高，需要您对 QConfig 有一定的理解。

- 使用 `get_qconfig` 接口构造 Qconfig。此接口较直接构造 QConfig 对象的方法更简单易用，但不够灵活，高级用法和需求无法使用此接口实现。

`import torch from horizon_plugin_pytorch.quantization import get_qconfig from horizon_plugin_pytorch.quantization.observer_v2 import MinMaxObserver from horizon_plugin_pytorch.quantization.qconfig import QConfig from horizon_plugin_pytorch.quantization.fake_quantize import FakeQuantize from horizon_plugin_pytorch.dtype import qint8 # qconfig_1 / qconfig_2 / qconfig_3 / qconfig_4 等价。 qconfig_1 = QConfig( weight=FakeQuantize.with_args( observer=MinMaxObserver, averaging_constant=0.01, dtype=qint8, qscheme=torch.per_channel_symmetric, ch_axis=0, ), output=FakeQuantize.with_args( observer=MinMaxObserver, averaging_constant=0, dtype=qint8, qscheme=torch.per_tensor_symmetric, ch_axis=-1, ), ) qconfig_2 = QConfig( weight=FakeQuantize.with_args( observer=MinMaxObserver, qscheme=torch.per_channel_symmetric, ch_axis=0, ), output=FakeQuantize.with_args( observer=MinMaxObserver, averaging_constant=0, ), ) qconfig_3 = get_qconfig( observer=MinMaxObserver, # 输入输出 observer 类型，只支持 horizon_plugin_pytorch.quantization.observer_v2 中的 MinMaxObserver 和 MSEObserver，默认值为 MinMaxObserver。 in_dtype=None, # 输入数据类型，考虑算子的支持情况进行设置。None 表示 QConfig 的 input 关键字为 None，默认值为 None。 weight_dtype=qint8, # 权重数据类型，考虑算子的支持情况进行设置。None 表示 QConfig 的 weight 关键字为 None，默认值为 qint8。 out_dtype=qint8, # 输出数据类型，考虑算子的支持情况进行设置。None 表示 QConfig 的 output 关键字为 None，默认值为 qint8。 fix_scale=True, # 是否固定输入输出 scale。 ) qconfig_4 = get_qconfig(fix_scale=True)`

## #通过 QconfigSetter 进行 qconfig 配置

QconfigSetter 根据模型的计算图，按设定的规则自动设置 qconfig，是我们最推荐的设置 qconfig 方法。使用 QconfigSetter 依赖 prepare 过程的图模式，用法如下：

`from horizon_plugin_pytorch.quantization import prepare, PrepareMethod, get_qconfig from horizon_plugin_pytorch.quantization.qconfig_setter import * qat_model = prepare( model, example_inputs=example_inputs, # 图模式需要提供模型输入，用于获取计算图 qconfig_setter=QconfigSetter( reference_qconfig=get_qconfig(), # 用于提供 observer 的 qconfig templates=[<Templates>], # 用户配置的模板 enable_optimize=True, # 开启默认的各项优化 save_dir="./qconfig_setting", # qconfig 配置结果(qconfig.pt 文件)和 changelog 的保存路径 ), method=PrepareMethod.JIT_STRIP, # QconfigSetter 依赖计算图 )`

### #模板说明

您可配置的模板如下：

1. `ModuleNameTemplate`（必要，需要覆盖到全部量化算子）：通过 module name 指定 dtype 配置或量化阈值。

2. `ConvDtypeTemplate`（必要）：指定 Conv 类算子的 input 和 weight dtype。

3. `MatmulDtypeTemplate`（必要）：指定 Matmul 算子的 input dtype。

4. `SensitivityTemplate`（可选）： 根据敏感度将 top-n 的算子配置为高精度。

5. `LoadFromFileTemplate`：加载 qconfig.pt 文件，用于复现之前的量化配置。此时 enable_optimize 必须为 False，否则配置结果的正确性无法保证，部署时可能存在 cpu 算子。

这些模板按配置顺序生效，前面模板的配置可被后面的模板覆盖。

`ModuleNameTemplate` 详解

1. module name 可以为算子名或 prefix。当一个 `ModuleNameTemplate` 中不同的 module name 存在覆盖关系时，越长的名字优先级越高，例如：

   `ModuleNameTemplate( { "": qint8, # 全局 qint8 "head": qint16, # 优先级高过全局配置，head 最终配置为 int16 "head.conv0": torch.float16, # 优先级高过 head 的配置，head.conv 最终配置为输出 float16 } )`

2. 可以指定算子的 threshold（前提是算子中存在对应的伪量化节点），此时量化 scale 的计算方式为 `scale = threshold / -qdtype.min`，例如：

   `ModuleNameTemplate( { "quant": {"dtype": qint8, "threshold": 1.0}, # quant 的量化 scale 为 1.0/128 } )`

3. dtype 和 threshold 默认配置到算子的输出上，可通过指定 key 的方式配置 input 或 weight，在算子有多个输入时，可使用 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. 全 int8：

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

2. feature int16, weight int8：

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

3. gemm 算子双 int8，其他算子 fp16：

   `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 ], )`

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

   `QconfigSetter( reference_qconfig=get_qconfig(), templates=[ ModuleNameTemplate({"": qint16}), # 全部算子配置为 int16 输出 ConvDtypeTemplate(input_dtype=qint8, weight_dtype=qint8), # conv 的 input 和 weight 配置为 int8 MatmulDtypeTemplate(input_dtypes=qint8), # matmul 两个输入均配置为 int8 SensitivityTemplate( # 若敏感度较高的 feat 或 weight 配置为 int8，则将其修改为 int16 sensitive_table=..., topk_or_ratio=..., ), ], )`

### #默认优化 pass 说明

除您可配置的模板外，`QconfigSetter` 还集成了一系列优化和合法化模板，在本章节进行说明。

1. `CanonicalizeTemplate`： 按算子类型对 dtype 配置进行合法化，当前默认规则有：

   - Gemm 类算子不支持 float 输入（含 weight）。

   - 插值类算子：在不同 march 下有不同的限制。

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

   - 其他算子的通用规则：算子的 input dtype 和 output dtype 不能同时存在 qint 和 float。

2. `EqualizeInOutScaleTemplate`：对于 relu，concat，stack 算子，应该在算子之后统计 scale，否则精度或性能存在损失。为此：

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

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

3. `FuseConvAddTemplate`：硬件支持 conv + add 的 fuse，为此：

   - 将 conv 的 output dtype 配置为 float32。

   - 将 add 对应的 input dtype 配置为 float32。

4. `GridHighPrecisionTemplate`：根据经验，grid sample 的 grid 计算过程用 qint8 精度不够，因此自动将相关算子配置为高精度。

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

6. `OutputHighPrecisionTemplate`：当 Gemm 类算子作为模型输出时，将其配置为高精度输出。

7. `PropagateTemplate`：对于拆分为子图实现的算子，存在经验性配置，如 `LayerNorm` 和 `Softmax` 内部小算子应该使用高精度。

8. `SimpleIntPassTemplate`：性能优化，对于 op0->op1->op2 此类计算图，若以下条件同时成立，则将 op1 输出类型修改为 int:

   - op2 需要 int 输入。

   - op0 可以输出 int。

   - op1 当前输出为 float16，且属于以下类型：

     - cat，stack。

     - mul_scalar。

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

9. `SimplifyTemplate`：删除多余的量化节点配置（将对应的 dtype 修改为 None）。

页面目录