模型校准 Calibration
在量化过程中,一个重要的步骤就是确定量化参数。合理的初始量化参数,通常能够显著提升模型精度,并降低后续 QAT 训练的难度。
模型改造完成并完成浮点训练后,便可以进行 Calibration。Calibration 是在浮点模型中插入 Observer,使用少量训练数据,在模型 forward 过程中统计各处的数据分布,以确定更合适量化参数的过程。
-
对于部分模型,仅通过 Calibration 便可以使精度达到要求,不必继续进行耗时更高的 QAT。
-
即使 Calibration 后精度仍不满足要求,这一步也通常可以降低后续 QAT 的训练难度,缩短收敛时间,并提升最终训练精度。
多机多卡的适配
calibration 和 量化感知训练 的过程均支持多机多卡,任务启动方式和浮点模型完全相同,每个进程中在将模型使用 torch.nn.parallel.DistributedDataParallel 封装之前必须先进行 prepare。
Calibration 整体流程
完成 Prepare 后,Calibration 的目标是利用校准数据统计模型中各处张量分布,为后续量化计算提供更合适的初始量化参数。整体流程如下图所示:
在进入 Calibration 之前,您需要先完成 构建浮点模型、构建 QConfig 和 Prepare。 下面按实际使用顺序介绍整体 Calibration 的步骤。
-
先准备 Calibration 和 Validation 需要使用的数据。
实际使用中,通常会准备一份
calib_data_loader用于执行 Calibration,再准备一份eval_data_loader用于校准完成后的效果评估。数据选择建议可参考下文的 校准数据准备 小节的介绍。 -
先将模型状态设置为
eval,再把fake quantize状态切换为CALIBRATION,配置示例如下:fake quantize常用状态有三种,分别需要在QAT、Calibration、Validation前切换到对应状态。-
CALIBRATION。只观测各算子输入输出的统计量。 -
QAT。既观测统计量,也执行伪量化操作。 -
VALIDATION。只执行伪量化操作,不再继续更新统计量。
-
请先将模型切换到 eval 状态,再设置 FakeQuantState.CALIBRATION,不要颠倒顺序。
如果在设置为 FakeQuantState.CALIBRATION 之后再调用 model.eval(),通常无法正常进行校准。
-
执行 Calibration。将准备好的校准数据输入模型,Observer 会在模型执行 forward 的过程中观测相关统计量,后续量化参数如何计算则取决于 qconfig 中配置的 Observer 及其搜索策略。校准阶段通常只需要执行 forward,不需要 backward,示例如下:
如果您的 dataloader 直接返回模型输入,也可以直接将 batch 输入模型执行 forward。
-
校准完成后,将模型状态设置为
eval,并把fake quantize状态设置为VALIDATION,用于评估当前 Calibration 效果。如果您已经有现成的验证函数,可以直接使用
eval_data_loader对calib_model做评估,例如: -
验证 Calibration 效果。如果效果满足您的需求,则可以直接将模型转为定点模型,或者在此基础上继续进行后续的量化感知训练。 如果结果不满意,则可以参考 Calibration 效果不满足预期时建议 小节的介绍。
校准数据准备
前方的流程说明中我们介绍了 “Calibration 整体怎么做”,这里我们为您介绍下实际校准过程中,校准数据准备需要注意的事项。
Calibration 的结果和校准数据分布关系很大。实际使用时,建议您优先注意下面几点:
-
Calibration 数据通常不是越多越好。在达到一定规模后,继续增加数据的收益会明显下降。如果训练集较小,可以全部用于 Calibration。如果训练集较大,可以结合 Calibration 耗时选择合适的子集,建议至少进行 10 到 100 个 step 的校准。
-
校准数据要尽量覆盖模型的典型输入分布,各类部署场景都要包含,否则 Calibration 会出现偏差,甚至导致量化参数失真。比如应用场景包含白天、夜晚、城区、高速、弯道、直道等多种情况时,校准数据应尽量全部覆盖。
-
图像任务中,建议尽量使用 infer 阶段的前处理加训练数据进行校准。
-
在做图像增强时,可以使用水平翻转这类影响较小的增强,不建议使用马赛克这类会明显改变分布的增强。
校准算法与搜索策略
完成 Calibration 基本流程并准备好校准数据后,接下来,还需要关注“量化参数是如何算出来的”。 这部分主要由 qconfig 中配置的 Observer 和对应的 scale 搜索策略决定。
在前面的流程中,通过 qconfig 的指定,Observer 已经插入到了模型中。Calibration 阶段真正执行 forward 时,Observer 会统计张量分布,并据此计算量化所需的 scale 等参数。 不同 Observer 的统计方式和搜索策略不同,因此最终 Calibration 效果也可能存在差异。 因此,在 Calibration 阶段,我们更关注它们在校准阶段如何生效,以及不同策略会带来什么差别。
我们推荐您在 Calibration 中使用 HistogramObserver。它会统计校准集上的整体分布,并结合搜索策略自动选择更合适的 scale,与其他一些通常采用逐帧计算加滑动平均方式的 Observer 相比,这种方式在结果稳定性上往往更好。
从实际使用体验看,HistogramObserver 在结果稳定性和精度表现上通常也更适合作为常规首选。
当前常见的搜索方式如下:
在使用默认搜索方式完成 Calibration 后,如果精度仍不满足预期,可以通过 HistogramObserver.reset_scale 接口使用其他方式重新计算 scale,而无需重新跑一遍校准数据。
参考配置如下:
使用时建议注意如下两点:
-
常见切换方式是从默认
mse改为percentile,观察是否能减轻截断或饱和问题。 -
scale 会保存在
state_dict中,因此重算 scale 后请重新保存模型。
复用 Calibration 结果调整量化 dtype
在上一节对校准算法与搜索策略的介绍中,我们推荐使用 HistogramObserver 进行校准。与早期版本中部分 Observer 采用的「逐帧统计 + 滑动平均」策略不同,直方图校准会在整个校准集上统计全局分布,并据此计算 scale。
由于 scale 基于全局信息计算,完成一次 Calibration 后,您可以在不重新执行校准的前提下,按后续量化分析结果调整部分节点的量化 dtype,并根据校准时统计的信息重新计算对应的 scale。为此,我们提供了 load_calib_state_dict 接口,用于在 QAT 或 Validation 模型上加载 Calibration 产出的 checkpoint 时,会依据伪量化节点的实际 dtype 重新计算 scale。
支持的 dtype 切换范围
由于 float16 需要统计全局 min / max 信息,HistogramObserver 尚未覆盖,因此 load_calib_state_dict 仅支持下列 dtype 切换:
推荐使用流程
整体思路:先用一套相对宽松的 qconfig 完成 Calibration 并保存 state_dict,再按分析结果 prepare 目标模型,最后用 load_calib_state_dict 注入校准结果。
-
对浮点模型执行第一次
prepare并完成 Calibration。Calibration 阶段建议在QconfigSetter中关闭Conv + add的 fuse(此时 conv 输入 dtype 尚未最终确定,若后续将 conv 输入改为qint16,可能无法再 fuse)。Calibration 阶段不做 fuse,可兼容后续 fuse / 不 fuse 两种情况。 -
保存
calib_state_dict = calib_model.state_dict()。 -
在 Calibration 所用 qconfig 基础上,按敏感度分析等结果调整模板(例如将部分层从
qint8提升为qint16),对另一份浮点模型副本再次prepare,得到qat_model或用于评估的predict_model。 -
调用
load_calib_state_dict加载 Calibration 结果,再进入 Validation 或 QAT。
进入 量化感知训练 时,同样建议使用 load_calib_state_dict(qat_model, calib_state_dict) 替代直接 qat_model.load_state_dict(calib_model.state_dict()),以便在 QAT 的 qconfig 与 Calibration 阶段不完全一致时仍能正确恢复 scale。
不同平台上 Calibration 策略
不同平台的推荐起点并不完全相同,Calibration 阶段也建议按平台特点采用不同策略。
J6E/M/B
对于 J6E/M/B,建议优先通过全 int16 配置看精度上限,再回到 int8 + int16 的混合精度。
-
如果模型第一次接入 QAT,或当前精度问题比较复杂,建议先用全
int16模板验证精度上限。 -
如果全
int16下结果明显改善,再逐步回退到int8 + int16的混合精度,兼顾精度和性能。 -
如果全
int16下效果仍然不好,通常不建议直接进入 QAT,而应优先回看模型结构、校准数据和 fix scale 配置,必要时再结合敏感度分析继续定位。
J6H/P
对于 J6H/P,建议优先利用平台更强的浮点能力,从全局 fp16 + GEMM int8 / int16 的思路起步。
-
推荐起点通常是全局
fp16,同时将Conv / Matmul / Linear等 Gemm 类算子配置为int8。 -
如果基础配置结果不满足需求,再结合敏感度结果,将关键 Gemm 类算子提升到
int16。 -
对于 vector 类计算,fp16 通常更能降低量化调优难度。实际使用中,还需要额外关注 fp16 范围溢出、int16 / fp16 交错以及输出类型变化等问题。
更具体的平台差异可参考平台差异说明章节。
Calibration 结果验证建议
Calibration 结束后,建议您优先按下面的思路判断下一步:
与此同时,我们建议您:
-
关注精度与浮点基线的比值,而不是只看绝对指标。
-
关注
quantized.bc / hbm方向的验证结果,而不是只看calib model。 -
关注校准数据是否覆盖真实部署场景,而不是只覆盖单一输入分布。
-
关注结果是否稳定。如果多次验证结果波动较大,或者 bad case 集中在特定场景,通常也意味着当前校准数据覆盖性还不够。
Calibration 效果不满足预期时建议
当 Calibration 后精度不达预期时,建议您继续跳转到PyTorch 模型精度调优指导,并结合下面几类方法的详细使用方式、适用场景和结果判断进行定位:
-
分步量化。按模型阶段、子模块或局部片段逐步引入量化,观察误差从哪一段开始明显放大,用于先缩小问题范围。
-
直方图分析。观察关键层输入输出或激活分布,辅助判断是否存在截断、饱和、scale 异常、分布失真或长尾问题。
-
敏感度分析。根据量化敏感度结果识别对模型输出影响较大的算子,并为提高相关算子的量化精度提供依据。
这几类方法的定位有所不同。分步量化更适合先缩小问题范围,直方图分析更适合继续判断关键层的 scale 是否异常,敏感度分析更适合确认高精度应该加在哪些模块。
如果您在 Calibration 后发现 quantized.bc / hbm 明显劣化,也建议优先跳转到部署一致性分析说明继续排查。
