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 时,有两种方法供您选择:
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 依赖计算图
)
模板说明
您可配置的模板如下:
-
ModuleNameTemplate(必要,需要覆盖到全部量化算子):通过 module name 指定 dtype 配置或量化阈值。
-
ConvDtypeTemplate(必要):指定 Conv 类算子的 input 和 weight dtype。
-
MatmulDtypeTemplate(必要):指定 Matmul 算子的 input dtype。
-
SensitivityTemplate(可选): 根据敏感度将 top-n 的算子配置为高精度。
-
LoadFromFileTemplate:加载 qconfig.pt 文件,用于复现之前的量化配置。此时 enable_optimize 必须为 False,否则配置结果的正确性无法保证,部署时可能存在 cpu 算子。
这些模板按配置顺序生效,前面模板的配置可被后面的模板覆盖。
ModuleNameTemplate 详解
-
module name 可以为算子名或 prefix。当一个 ModuleNameTemplate 中不同的 module name 存在覆盖关系时,越长的名字优先级越高,例如:
ModuleNameTemplate(
{
"": qint8, # 全局 qint8
"head": qint16, # 优先级高过全局配置,head 最终配置为 int16
"head.conv0": torch.float16, # 优先级高过 head 的配置,head.conv 最终配置为输出 float16
}
)
-
可以指定算子的 threshold(前提是算子中存在对应的伪量化节点),此时量化 scale 的计算方式为 scale = threshold / -qdtype.min,例如:
ModuleNameTemplate(
{
"quant": {"dtype": qint8, "threshold": 1.0}, # quant 的量化 scale 为 1.0/128
}
)
-
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,第二个输入不做配置
}
)
场景示例
-
全 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
],
)
-
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
],
)
-
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
],
)
-
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 还集成了一系列优化和合法化模板,在本章节进行说明。
-
CanonicalizeTemplate: 按算子类型对 dtype 配置进行合法化,当前默认规则有:
-
EqualizeInOutScaleTemplate:对于 relu,concat,stack 算子,应该在算子之后统计 scale,否则精度或性能存在损失。为此:
-
FuseConvAddTemplate:硬件支持 conv + add 的 fuse,为此:
-
GridHighPrecisionTemplate:根据经验,grid sample 的 grid 计算过程用 qint8 精度不够,因此自动将相关算子配置为高精度。
-
InternalQuantsTemplate:模型分段部署场景下,会在分段点处插入 QuantStub,用于记录此处的 dtype 和 scale,此类 QuantStub 的 dtype 配置必须和输入保持一致。
-
OutputHighPrecisionTemplate:当 Gemm 类算子作为模型输出时,将其配置为高精度输出。
-
PropagateTemplate:对于拆分为子图实现的算子,存在经验性配置,如 LayerNorm 和 Softmax 内部小算子应该使用高精度。
-
SimpleIntPassTemplate:性能优化,对于 op0->op1->op2 此类计算图,若以下条件同时成立,则将 op1 输出类型修改为 int:
-
SimplifyTemplate:删除多余的量化节点配置(将对应的 dtype 修改为 None)。