模型校准 Calibration

在量化过程中,一个重要的步骤就是确定量化参数。合理的初始量化参数,通常能够显著提升模型精度,并降低后续 QAT 训练的难度。

模型改造完成并完成浮点训练后,便可以进行 Calibration。Calibration 是在浮点模型中插入 Observer,使用少量训练数据,在模型 forward 过程中统计各处的数据分布,以确定更合适量化参数的过程。

  • 对于部分模型,仅通过 Calibration 便可以使精度达到要求,不必继续进行耗时更高的 QAT。

  • 即使 Calibration 后精度仍不满足要求,这一步也通常可以降低后续 QAT 的训练难度,缩短收敛时间,并提升最终训练精度。

注意

多机多卡的适配

calibration 和 量化感知训练 的过程均支持多机多卡,任务启动方式和浮点模型完全相同,每个进程中在将模型使用 torch.nn.parallel.DistributedDataParallel 封装之前必须先进行 prepare

Calibration 整体流程

完成 Prepare 后,Calibration 的目标是利用校准数据统计模型中各处张量分布,为后续量化计算提供更合适的初始量化参数。整体流程如下图所示:

calibration_v2_workflow
注解

使用时我们建议您先明确下面三点:

  • 如果 Calibration 后结果已经达标,您可以直接进入 定点模型转换 和后续编译流程。

  • 如果 Calibration 后结果不达标,可以选择进入 量化感知训练 或后续精度调优路径。

  • 无论 Calibration 结果看起来多好,最终都仍需要结合 quantized.bc / hbm 的结果判断是否满足部署要求,而不是只看 calib model

在进入 Calibration 之前,您需要先完成 构建浮点模型构建 QConfigPrepare。 下面按实际使用顺序介绍整体 Calibration 的步骤。

  1. 先准备 Calibration 和 Validation 需要使用的数据。

    实际使用中,通常会准备一份 calib_data_loader 用于执行 Calibration,再准备一份 eval_data_loader 用于校准完成后的效果评估。数据选择建议可参考下文的 校准数据准备 小节的介绍。

    # Calibration 时使用的 batch_size
    calib_batch_size = 256
    
    # Validation 时使用的 batch_size
    eval_batch_size = 256
    
    # Calibration 使用的数据量,配置为 inf 表示使用全部数据
    num_examples = float("inf")
    
    calib_data_loader, eval_data_loader = prepare_data_loaders(
        data_path,
        calib_batch_size,
        eval_batch_size,
    )
  2. 先将模型状态设置为 eval,再把 fake quantize 状态切换为 CALIBRATION,配置示例如下:

    from horizon_plugin_pytorch.quantization import FakeQuantState, set_fake_quantize
    
    calib_model.eval()
    set_fake_quantize(calib_model, FakeQuantState.CALIBRATION)

    fake quantize 常用状态有三种,分别需要在 QATCalibrationValidation 前切换到对应状态。

    • CALIBRATION。只观测各算子输入输出的统计量。

    • QAT。既观测统计量,也执行伪量化操作。

    • VALIDATION。只执行伪量化操作,不再继续更新统计量。

    class FakeQuantState(Enum):
        QAT = "qat"
        CALIBRATION = "calibration"
        VALIDATION = "validation"
注意

请先将模型切换到 eval 状态,再设置 FakeQuantState.CALIBRATION,不要颠倒顺序。 如果在设置为 FakeQuantState.CALIBRATION 之后再调用 model.eval(),通常无法正常进行校准。

  1. 执行 Calibration。将准备好的校准数据输入模型,Observer 会在模型执行 forward 的过程中观测相关统计量,后续量化参数如何计算则取决于 qconfig 中配置的 Observer 及其搜索策略。校准阶段通常只需要执行 forward,不需要 backward,示例如下:

    with torch.no_grad():
        for batch in calib_data_loader:
            image = batch[0].to(device)
            calib_model(image)

    如果您的 dataloader 直接返回模型输入,也可以直接将 batch 输入模型执行 forward。

  2. 校准完成后,将模型状态设置为 eval,并把 fake quantize 状态设置为 VALIDATION,用于评估当前 Calibration 效果。

    calib_model.eval()
    set_fake_quantize(calib_model, FakeQuantState.VALIDATION)

    如果您已经有现成的验证函数,可以直接使用 eval_data_loadercalib_model 做评估,例如:

    top1, top5 = evaluate(calib_model, eval_data_loader, device)
  3. 验证 Calibration 效果。如果效果满足您的需求,则可以直接将模型转为定点模型,或者在此基础上继续进行后续的量化感知训练。 如果结果不满意,则可以参考 Calibration 效果不满足预期时建议 小节的介绍。

校准数据准备

前方的流程说明中我们介绍了 “Calibration 整体怎么做”,这里我们为您介绍下实际校准过程中,校准数据准备需要注意的事项。

Calibration 的结果和校准数据分布关系很大。实际使用时,建议您优先注意下面几点:

  1. Calibration 数据通常不是越多越好。在达到一定规模后,继续增加数据的收益会明显下降。如果训练集较小,可以全部用于 Calibration。如果训练集较大,可以结合 Calibration 耗时选择合适的子集,建议至少进行 10 到 100 个 step 的校准。

  2. 校准数据要尽量覆盖模型的典型输入分布,各类部署场景都要包含,否则 Calibration 会出现偏差,甚至导致量化参数失真。比如应用场景包含白天、夜晚、城区、高速、弯道、直道等多种情况时,校准数据应尽量全部覆盖。

  3. 图像任务中,建议尽量使用 infer 阶段的前处理加训练数据进行校准。

  4. 在做图像增强时,可以使用水平翻转这类影响较小的增强,不建议使用马赛克这类会明显改变分布的增强。

校准算法与搜索策略

完成 Calibration 基本流程并准备好校准数据后,接下来,还需要关注“量化参数是如何算出来的”。 这部分主要由 qconfig 中配置的 Observer 和对应的 scale 搜索策略决定。

在前面的流程中,通过 qconfig 的指定,Observer 已经插入到了模型中。Calibration 阶段真正执行 forward 时,Observer 会统计张量分布,并据此计算量化所需的 scale 等参数。 不同 Observer 的统计方式和搜索策略不同,因此最终 Calibration 效果也可能存在差异。 因此,在 Calibration 阶段,我们更关注它们在校准阶段如何生效,以及不同策略会带来什么差别。

我们推荐您在 Calibration 中使用 HistogramObserver。它会统计校准集上的整体分布,并结合搜索策略自动选择更合适的 scale,与其他一些通常采用逐帧计算加滑动平均方式的 Observer 相比,这种方式在结果稳定性上往往更好。 从实际使用体验看,HistogramObserver 在结果稳定性和精度表现上通常也更适合作为常规首选。

当前常见的搜索方式如下:

搜索方式说明
mse默认方式。根据数据分布计算不同 scale 下伪量化前后的误差,选择损失更小的 scale。
percentile控制有效值在总体数据中的占比,percentile=1.0 时可理解为接近 minmax
otherswassersteinkljscdf_klcdf_js。一般不推荐作为常规起点。

在使用默认搜索方式完成 Calibration 后,如果精度仍不满足预期,可以通过 HistogramObserver.reset_scale 接口使用其他方式重新计算 scale,而无需重新跑一遍校准数据。 参考配置如下:

class HistogramObserver:
    @classmethod
    def reset_scale(
        cls,
        model,
        method,
        method_kwargs=None,
        prefix=None,
        dtype=None,
    ):
        """Reset scales in model by specified method.

        Args:
            model (torch.nn.Module): Model calibrated with HistogramObserver.
            method (str): Chosen from ('mse', 'percentile').
            method_kwargs (Dict, optional): For 'percentile' method, user can set `{'percentile': 0.0~1.0}` to control the proportion of numerical values included in the quantization interval. Defaults to None.
            prefix (Tuple[str], optional): Only reset scale in specified scopes. Defaults to None.
            dtype (_type_, optional): Only reset scale of specified quant dtype. Defaults to None.
        """
注意

使用时建议注意如下两点:

  • 常见切换方式是从默认 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 切换:

切换方向说明
qint8qint16在已校准的整型精度之间切换
float16float32浮点精度之间切换
Any → float32将节点统一为 float32
Any → None节点因 fuse 或配置修改被删除时使用

推荐使用流程

整体思路:先用一套相对宽松的 qconfig 完成 Calibration 并保存 state_dict,再按分析结果 prepare 目标模型,最后用 load_calib_state_dict 注入校准结果

  1. 对浮点模型执行第一次 prepare 并完成 Calibration。Calibration 阶段建议在 QconfigSetter 中关闭 Conv + add 的 fuse(此时 conv 输入 dtype 尚未最终确定,若后续将 conv 输入改为 qint16,可能无法再 fuse)。Calibration 阶段不做 fuse,可兼容后续 fuse / 不 fuse 两种情况。

  2. 保存 calib_state_dict = calib_model.state_dict()

  3. 在 Calibration 所用 qconfig 基础上,按敏感度分析等结果调整模板(例如将部分层从 qint8 提升为 qint16),对另一份浮点模型副本再次 prepare,得到 qat_model 或用于评估的 predict_model

  4. 调用 load_calib_state_dict 加载 Calibration 结果,再进入 Validation 或 QAT。

import copy

from horizon_plugin_pytorch.quantization import FakeQuantState, load_calib_state_dict, prepare, set_fake_quantize
from horizon_plugin_pytorch.quantization.qconfig_setter import (
    FuseConvAddTemplate,
    OutputHighPrecisionTemplate,
    QconfigSetter,
)

# 1. Calibration 模型:关闭 Conv+add fuse,并完成校准
calib_model = prepare(
    copy.deepcopy(model),
    example_inputs,
    qconfig_setter=QconfigSetter(
        ...,
        optimize_templates_kwargs={
            FuseConvAddTemplate: {
                "enabled": False,
            },
            OutputHighPrecisionTemplate: {
                "enabled": False,
            },
        },
    ),
)
# ... Calibration 过程省略

calib_state_dict = calib_model.state_dict()

# 2. 按量化分析结果调整 qconfig 后,重新 prepare 目标模型
qat_model = prepare(
    copy.deepcopy(model),
    example_inputs,
    qconfig_setter=...,  # 在 calib 配置基础上,根据量化分析结果新增 qint8->qint16
)

# 3. 加载 calib 结果,scale 会按当前模型的实际 dtype 重算
load_calib_state_dict(qat_model, calib_state_dict)

# 4. 评估或继续 QAT
qat_model.eval()
set_fake_quantize(qat_model, FakeQuantState.VALIDATION)
evaluate(qat_model, eval_data_loader, device)

进入 量化感知训练 时,同样建议使用 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 结束后,建议您优先按下面的思路判断下一步:

结果表现建议优先动作后续方向
精度很差,甚至无精度优先排查链路、模型结构、qconfig、Prepare 结果、fix scale 和数据覆盖情况。必要时先验证更高精度模板是否有效优先回看 构建浮点模型构建 QConfigPrepare 说明,再继续进入PyTorch 模型精度调优指导
与浮点有较大差距,例如 量化精度 / 浮点精度 <= 90%优先做敏感度分析,并结合更高精度模板验证精度上限。常见做法包括增加 int16 配置,或对有物理意义的数据手工指定固定量化阈值参考PyTorch 模型精度调优指导,继续做敏感度分析、模板调整、分步量化和直方图分析
距离浮点差距较小,例如 量化精度 / 浮点精度 > 90%可以直接尝试进入 QAT。如果已经达到 量化精度 / 浮点精度 >= 95%,建议优先尝试固定激活 scale 的 QAT若最终 quantized.bc / hbm 结果达标,可直接进入导出和编译。若还希望进一步提升,可继续做固定激活 scale 的 QAT 或局部高精度调优
注解

与此同时,我们建议您:

  1. 关注精度与浮点基线的比值,而不是只看绝对指标。

  2. 关注 quantized.bc / hbm 方向的验证结果,而不是只看 calib model

  3. 关注校准数据是否覆盖真实部署场景,而不是只覆盖单一输入分布。

  4. 关注结果是否稳定。如果多次验证结果波动较大,或者 bad case 集中在特定场景,通常也意味着当前校准数据覆盖性还不够。

Calibration 效果不满足预期时建议

当 Calibration 后精度不达预期时,建议您继续跳转到PyTorch 模型精度调优指导,并结合下面几类方法的详细使用方式、适用场景和结果判断进行定位:

  • 分步量化。按模型阶段、子模块或局部片段逐步引入量化,观察误差从哪一段开始明显放大,用于先缩小问题范围。

  • 直方图分析。观察关键层输入输出或激活分布,辅助判断是否存在截断、饱和、scale 异常、分布失真或长尾问题。

  • 敏感度分析。根据量化敏感度结果识别对模型输出影响较大的算子,并为提高相关算子的量化精度提供依据。

这几类方法的定位有所不同。分步量化更适合先缩小问题范围,直方图分析更适合继续判断关键层的 scale 是否异常,敏感度分析更适合确认高精度应该加在哪些模块。

如果您在 Calibration 后发现 quantized.bc / hbm 明显劣化,也建议优先跳转到部署一致性分析说明继续排查。