开发指南
浮点模型的要求
symbolic_trace
和 PyTorch 的量化训练类似,horizon_plugin_pytorch 基于 fx 设计和开发,因此,要求浮点模型必须是可以正确的完成 symbolic_trace 的
仅支持部分算子
由于 BPU 只支持数量有限的算子,因此,horizon_plugin_pytorch 只支持算子列表中的算子和基于 BPU 限制而内部特殊定义的特殊算子。
构建量化友好模型
浮点模型变为定点模型的过程存在一定的精度误差,越是量化友好的浮点模型, qat 精度提升越容易,量化后的精度也越高。一般而言,有以下几种情况会导致模型变得量化不友好:
-
使用有精度风险的算子。例如: softmax , layernorm 等(详见 op 文档),这类算子一般底层由查表或多个 op 拼接实现,容易发生掉点问题。
-
一次 forward 中多次调用同一算子。同一算子多次调用,对应的输出分布存在差异,但只会统计一组量化参数,当多次调用的输出分布差异过大时,量化误差会变大。
-
add , cat 等多输入算子的不同输入差异过大,可能造成较大误差。
-
数据分布不合理。plugin 采用的是均匀对称量化,所以 0 均值的均匀分布最好,应尽量避免长尾和离群点。同时,数值范围需要与量化 bit 相匹配,如果使用int8量化分布为 [-1000, 1000] 均匀分布的数据,那么精度显然也是不够的。例如,下面三个分布图,从左到右对量化的友好性依次递减,模型中大部分数值的分布应当为中间这种分布。在实际使用中,可以用 debug 工具查看模型 weight 和 feature map 的分布是否量化友好。因为模型冗余性的存在,有些看起来分布非常量化不友好的 op 并不会显著降低模型的最终精度,需要结合实际的 qat 训练难度和最后达到的量化精度综合考虑。
那么如何使得模型更加量化友好呢?具体来说:
-
尽量少使用精度风险过大的算子,详见 op 文档。
-
保证多次调用的共享算子每次调用的输出分布差异不要太大,或者将共享算子拆开分别单独使用。
-
避免多输入算子不同输入的数值范围差异过大。
-
使用 int16 量化数值范围和误差都非常大的 op 。可通过 debug 工具找到这类 op 。
-
通过调大 weight decay ,增加数据增强等方式防止模型过拟合。过拟合模型容易出现较大数值,且对输入非常敏感,轻微的误差可能导致输出完全错误。
-
使用 BN 。
-
对模型输入做关于0对称的归一化。
需要注意的是, qat 自身具有一定的调 整能力,量化不友好并不代表不能量化,很多情况下,即使出现上面的不适合量化的现象,仍然可以量化得很好。因为上述建议也可能会导致浮点模型精度下降,所以应当在 qat 精度无法达标时再尝试上述建议,尤其是 1 - 5 条建议,最后应当是在浮点模型精度和量化模型精度中找一个平衡点。
qconfig 详解
什么是 qconfig
模型的量化方式由 qconfig 决定,在准备 qat / calibration 模型之前,需要先给模型设置 qconfig。我们不推荐您自定义 qconfig,尽量只使用预定义好的qconfig变量,因为自定义 qconfig 需要对具体的处理器限制认知清晰,详细了解训练工具的工作原理,定义出错可能导致模型无法正常收敛、模型无法编译等问题,浪费大量时间和人力。
目前,Plugin 中维护了两个版本的qconfig,早期版本的 qconfig 将在不久的将来被废弃,我们只推荐您使用此文档中介绍的 qconfig 用法。
如何获取 qconfig
- 使用封装好的 qconfig 变量。这些 qconfig 存放在
horizon_plugin_pytorch/quantization/qconfig.py
中,可以适用于绝大多数情况。包括:
from horizon_plugin_pytorch.quantization.qconfig import (
default_calib_8bit_fake_quant_qconfig,
default_qat_8bit_fake_quant_qconfig,
default_qat_8bit_fixed_act_fake_quant_qconfig,
default_calib_8bit_weight_16bit_act_fake_quant_qconfig,
default_qat_8bit_weight_16bit_act_fake_quant_qconfig,
default_qat_8bit_weight_16bit_fixed_act_fake_quant_qconfig,
default_qat_8bit_weight_32bit_out_fake_quant_qconfig, # 参考算子列表,支持高精度输出的算子可以设置此 qconfig 获得更高的精度
default_calib_8bit_weight_32bit_out_fake_quant_qconfig, # 参考算子列表,支持高精度输出的算子可以设置此 qconfig 获得更高的精度
)
- 使用
get_default_qconfig
接口。此接口较固定 qconfig 变量更灵活,我们推荐您对量化和硬件限制有清晰认知之后再使用。常用参数和解释如下:
from horizon_plugin_pytorch.quantization.qconfig import get_default_qconfig
qconfig = get_default_qconfig(
activation_fake_quant="fake_quant", # 支持 fake_quant, lsq, pact,常用 fake quant
weight_fake_quant="fake_quant", # 支持 fake_quant, lsq, pact,常用 fake quant
activation_observer="min_max", # 支持 min_max, fixed_scale, clip, percentile, clip_std, mse, kl
weight_observer="min_max", # 支持 min_max, fixed_scale, clip, percentile, clip_std, mse, kl
activation_qkwargs={
"dtype": qint16, # 由具体算子决定是否支持 int16
"is_sync_quantize": False, # 是否同步统计数据,默认关闭提升forward速度
"averaging_constant": 0.01 # 滑动平均系数,设置为0时,scale不更新
},
weight_qkwargs={ # 只支持 dtype = qint8, qscheme = torch.per_channel_symmetric, ch_axis = 0, 不建议做额外配置
"dtype": qint8,
"qscheme": torch.per_channel_symmetric,
"ch_axis": 0,
},
)
如何设置 qconfig
共有三种设置方法,我们推荐您使用前两种,最后一种设置方式将废弃。
- 直接设置 qconfig 属性。此方法优先级最高,其余方法不会覆盖直接设置的 qconfig。
model.qconfig = default_qat_8bit_fake_quant_qconfig
- qconfig 模板。在 prepare 接口上指定 qconfig setter 和 example_inputs,自动为模型设置 qconfig。
model = prepare_qat_fx(
model,
example_inputs=data,
qconfig_setter=default_qat_qconfig_setter,
)
- qconfig_dict。在 prepare_qat_fx 接口上指定 qconfig_dict。此用法将逐步废弃,如无兼容性需求,不推荐再使用,这里不展开介绍。
model = prepare_qat_fx(
model,
qconfig_dict={"": default_qat_qconfig_setter},
)
qconfig 模板
长期以来,配置 qconfig 出错的问题经常发生,因此我们开发了 qconfig 模板。qconfig 模板基于 subclass trace 方案感知模型的图结构,并按设定的规则自动设置 qconfig,是我们最推荐的设置 qconfig 方法。用法如下:
qat_model = prepare_qat_fx(
model,
example_inputs=example_input, # 用来感知图结构
qconfig_setter=( # qconfig 模板,支持传入多个模板,优先级从高到低。
sensitive_op_qat_8bit_weight_16bit_act_qconfig_setter(table, ratio=0.2),
default_calibration_qconfig_setter,
)
)
模板的优先级低于直接给模型设置 qconfig 属性,如果模型在 prepare 之前已经使用 model.qconfig = xxx 进行了配置,那么模板将不会生效。如果没有特殊需求,我们不推荐将两者混合使用,这很容易引发低级错误。绝大多数情况下,我们推荐您使用模板和 model.qconfig = xxx 两种设置方式中的一种即可满足需求。
模板可分为三类:
- 固定模板。固定模板中 calibration / qat / qat_fixed_act_scale 区别在于使用的 observer 类型和 scale 更新逻辑,分别用于校准,qat 训练,固定 activation scale qat 训练。default 模板( default_calibration_qconfig_setter / default_qat_qconfig_setter / default_qat_fixed_act_qconfig_setter )会做三件事:首先,将可以设置的高精度输出都设置上,对于不支持高精度的输出将给出提示;然后,从 grid sample 算子的 grid 输入向前搜索,直到出现第一个 gemm 类算子或者QuantStub,将中间的所有算子都设置为 int16。根据经验这里的 grid 一般表达范围较宽,int8 有较大可能不满足精度需求;最后,将其余算子设置为 int8。int16 模板( qat_8bit_weight_16bit_act_qconfig_setter / qat_8bit_weight_16bit_fixed_act_qconfig_setter / calibration_8bit_weight_16bit_act_qconfig_setter )会做两件事:首先,将可以设置的高精度输出都 设置上,对于不支持高精度的输出将给出提示;其次,将其余算子设置为 int16。
from horizon_plugin_pytorch.quantization.qconfig_template import (
default_calibration_qconfig_setter,
default_qat_qconfig_setter,
default_qat_fixed_act_qconfig_setter,
qat_8bit_weight_16bit_act_qconfig_setter,
qat_8bit_weight_16bit_fixed_act_qconfig_setter,
calibration_8bit_weight_16bit_act_qconfig_setter,
)
- 敏感度模板。敏感度模板有 sensitive_op_calibration_8bit_weight_16bit_act_qconfig_setter, sensitive_op_qat_8bit_weight_16bit_act_qconfig_setter, sensitive_op_qat_8bit_weight_16bit_fixed_act_qconfig_setter,三者的区别和固定模板中三者的区别一致,也是分别用于校准,qat 训练,固定 activation scale qat 训练。 敏感度模板的第一个输入是精度 debug 工具产生的敏感度结果,第二个参数可以指定 ratio 或 topk ,敏感度模板会将量化敏感度最高的 topk 个算子设置为 int16。搭配固定模板,可以轻松实现混合精度调优。
from horizon_plugin_pytorch.quantization.qconfig_template import (
default_calibration_qconfig_setter,
default_qat_qconfig_setter,
default_qat_fixed_act_qconfig_setter,
qat_8bit_weight_16bit_act_qconfig_setter,
qat_8bit_weight_16bit_fixed_act_qconfig_setter,
calibration_8bit_weight_16bit_act_qconfig_setter,
sensitive_op_qat_8bit_weight_16bit_act_qconfig_setter,
sensitive_op_qat_8bit_weight_16bit_fixed_act_qconfig_setter,
sensitive_op_calibration_8bit_weight_16bit_act_qconfig_setter,
)
table = torch.load("output_0-0_dataindex_1_sensitive_ops.pt")
qat_model = prepare_qat_fx(
model,
example_inputs=example_input,
qconfig_setter=(
sensitive_op_qat_8bit_weight_16bit_fixed_act_qconfig_setter(table, ratio=0.2),
default_calibration_qconfig_setter,
)
)
- 自定义模板。自定义模板只有 ModuleNameQconfigSetter,需要传入模块名和对应 qconfig 的字典,一般用于设置 fixed scale 等特殊需求,可以和固定模板,敏感度模板搭配使用。
from horizon_plugin_pytorch.quantization.qconfig_template import (
default_calibration_qconfig_setter,
default_qat_qconfig_setter,
default_qat_fixed_act_qconfig_setter,
qat_8bit_weight_16bit_act_qconfig_setter,
qat_8bit_weight_16bit_fixed_act_qconfig_setter,
calibration_8bit_weight_16bit_act_qconfig_setter,
sensitive_op_qat_8bit_weight_16bit_act_qconfig_setter,
sensitive_op_qat_8bit_weight_16bit_fixed_act_qconfig_setter,
sensitive_op_calibration_8bit_weight_16bit_act_qconfig_setter,
ModuleNameQconfigSetter,
)
table = torch.load("output_0-0_dataindex_1_sensitive_ops.pt")
module_name_to_qconfig = {
"op_1": default_qat_8bit_fake_quant_qconfig,
"op_2": get_default_qconfig(
activation_observer="fixed_scale",
activation_qkwargs={
"dtype": qint16,
"scale": OP2_MAX / QINT16_MAX,
},
)
}
qat_model = prepare_qat_fx(
model,
example_inputs=example_input,
qconfig_setter=(
ModuleNameQconfigSetter(module_name_to_qconfig),
sensitive_op_qat_8bit_weight_16bit_fixed_act_qconfig_setter(table, ratio=0.2),
default_calibration_qconfig_setter,
)
)
Calibration 指南
在量化中,一个重要的步骤是确定量化参数,合理的初始量化参数能够显著提升模型精度并加快模型的收敛速度。Calibration 就是在浮点模型中插入 Observer,使用少量训练数据,在模型 forward 过程中统计各处的数据分布,以确定合理的量化参数的过程。虽然不做 Calibration 也可以进行量化训练,但一般来说,它 对量化训练有益无害,所以推荐用户将此步骤作为必选项。
流程和示例
Calibration 与 QAT 的整体流程如下图所示:
下面分别介绍各个步骤:
-
构建并训练浮点模型。参考 horizon_plugin_pytorch 快速入门章节中的 获取浮点模型 小节内容。
-
在浮点模型上插入 Observer 节点。参考 horizon_plugin_pytorch 快速入门章节中的 Calibration 小节内容。使用
prepare_qat_fx
方法转化浮点模型前,需要为模型设置qconfig
。model.qconfig = horizon.quantization.get_default_qconfig()
get_default_qconfig
可以为weight
和activation
设置不同的observer
。目前,calibration 可选observer
有 "min_max"、 "percentile"、 "mse"、 "kl" 和 "mix"。如无特殊需求,weight_observer
推荐使用默认的 "min_max",activation_observer
推荐使用 "mse"。特殊用法和调试技巧见下面的常见算法介绍。fake_quant
参数对 Calibration 结果无影响,保留默认状态即可。def get_default_qconfig(
activation_fake_quant: Optional[str] = "fake_quant",
weight_fake_quant: Optional[str] = "fake_quant",
activation_observer: Optional[str] = "min_max",
weight_observer: Optional[str] = "min_max",
activation_qkwargs: Optional[Dict] = None,
weight_qkwargs: Optional[Dict] = None,
): -
设置
fake quantize
状态为CALIBRATION
。horizon.quantization.set_fake_quantize(model, horizon.quantization.FakeQuantState.CALIBRATION)
fake quantize
一共有三种状态,分别需要在QAT
、calibration
、validation
前将模型的fake quantize
设置为对应的状态。在 calibration 状态下,仅观测各算子输入输出的统计量。在 QAT 状态下,除观测统计量外还会进行伪量化操作。而在 validation 状态下,不会观测统计量,仅进行伪量化操作。class FakeQuantState(Enum):
QAT = "qat"
CALIBRATION = "calibration"
VALIDATION = "validation" -
calibration。把准备好的校准数据喂给模型,模型在 forward 过程中由 observer 观测相关统计量。
-
设置模型状态为 eval 并设置
fake quantize
状态为VALIDATION
。model.eval()
horizon.quantization.set_fake_quantize(model, horizon.quantization.FakeQuantState.VALIDATION) -
验证
calibration
效果。如果效果满意,则可以直接将模型转为定点或在此基础上进行量化训练,不 满意则调整calibration qconfig
中的参数继续 calibration。
常用算法介绍
有关每个算子的参数说明,请参考文末 API 文档。
算法 | 速度排名 | 精度排名 | 易用性排名 |
---|---|---|---|
min_max | 1 | 5 | 1 |
percentile | 2 | 4 | 4 |
mse | 4 | 1 | 2 |
kl | 5 | 2 | 3 |
mix | 3 | 2 | 1 |
常用的几种校准方法性能如上表所示,数字越小越好,速度表示相同数据校准耗时,精度表示该方法在大多数模型上的校准效果,易用性表示该方法的调参复杂度。
对于同一模型而言,不同方法不同参数的精度/速度会存在较大差别,最新的一些研究工作也表明,没有一种方法可以在所有模型上都取得最好的精度,需要针对地调整其参数。所以推荐用户对这几种校准方法都进行尝试。
-
min_max。此方法仅统计最大值最小值的滑动平均,用于快速确定 Batch size、average_constant 等通用参数,没有太多技巧。
-
percentile。此方法是所有方法中精度上限最高的,但也是调整起来最麻烦的,如果通过其他方法或本方法的默认参数就可以满足精度要求,那么不建议在调参上花太多时间。percentile 可调的参数一共有两个 bins、percentile。bins 越多,max 的候选项间隔越小,可供调整的粒度越细,但也意味着更高的计算耗时。建议先确定 percentile 再调整 bins,两者交替迭代缩小调参范围直至达到满意的效果。绝大部分情况下 bins 取 2048 提供的调整粒度完全足够,不需要单独调整这个参数。以下是一个模型的调参路径:
顺序 | percentile | bins | 精度 |
---|---|---|---|
1 | 99.99 | 2048 | 53.75 |
2 | 99.99 | 4096 | 54.38 |
3 | 99.995 | 4096 | 16.25 |
4 | 99.985 | 4096 | 32.67 |
5 | 99.9875 | 4096 | 57.06 |
6 | 99.9875 | 8192 | 62.84 |
7 | 99.98875 | 8192 | 57.62 |
8 | 99.988125 | 8192 | 63.15 |
在这个例子中,可以看到仔细调整后,精度提升了大约 10%。 模型中不同 op 的输入输出之间存在很大差异,一组全局的 percentile 参数可能很难满足所有 op 的需求,对精度要求较高时,可以先通过上面的方法找到较好的全局参数,再通过 debug 工具找到误差较大的几个 op,单独为这几个 op 设置 percentile 参数,设置方式参照 qconfig 设置。下面列举几种常见的容易导致误差较大的数据分布:
超长尾分布,percentile 的取值应当小一些,图中 99.9 是较好的取值。
值域过大,且分布并不集中在一处,这种情况无论是保留尾部还是忽略尾部都会带来较大的精度损失,应该在训练浮点模型时通过调整 weight decay 等参数避免这种情况的出现。
layernorm 的输出分布会呈现出若干集中度非常高的区域,此时 percentile 按照正常方法调整对于量化结果不会有任何影响,需要将 percentile 调整幅度增加。
-
mse。可调整的参数只有 stride,默认 stride 为 1,会逐步尝试最大值的 100 分位并选出量化反量化前后误差最小(L2 距离)的分位对应的值。此方法对大模型耗时较高,在合理范围内调大 stride 可以在保证精度的前提下减少耗时,stride 调整过大会影响精度。注意,调整此方法的参数只能优化耗时,并不能显著提升精度。
-
kl。可调的参数一共有两个 bin 和 update_interval。由于此方法耗时过长,不建议调整默认 bin,update_interval 默认为 1,调大可以减少耗时,但需要保证 update_interval 小于总的 calibration step,否则无法得到正常的量化参数。
-
mix。此方法为混合校准,对于每一个需要统计的地方,都会尝试 percentile 方法的不同参数,选出量化反量化前后误差最小(L2 距离)的方法。自动化程度较高,没有需要调整的参数。