目录
-
1. 精度调优推荐流程-
-
2. Calibration阶段的精度调优建议-
-
3. QAT量化训练阶段的精度调优建议-
-
4. 定点精度的调优建议-
-
5. 分析工具使用指南-
1. 精度调优推荐流程
QAT方案从浮点到部署模型包括五个步骤:浮点模型准备、数据校准、量化训练(可选)、定点转换、模型编译。每个阶段都有对应的精度验证及一致性对齐要求(特别是当板端推理结果与python端不一致时,地平线提供了一些工具可以帮助分析),具体操作方式建议参考QAT一致性对齐流程。在数据校准、量化训练或定点转换阶段出现精度损失时,建议可参考下图流程进行调优:
由于量化工具采用的是对称均匀量化,因此浮点模型搭建/训练阶段采用一些优化手段可以使得模型对量化更友好,具体可参考用户手册构建量化友好模型。后文主要分享一下calibration、QAT和定点转换三个阶段的精度问题分析和调优方式。
2. Calibration阶段的精度调优建议
推荐大家在量化训练之前先使用Calibration,一方面是因为Calibration时间较短,部分模型仅进行Calibration即可满足精度要求,可以免去费时的量化训练过程,另一方面通过Calibration初始化量化参数之后也可以加速模型的量化训练收敛。
calibration/float精度比<80%为经验值,仅供参考(部分模型calibration/float精度比<80%时也可以通过QAT训练达到预期精度)。
2.1 Calibration 精度损失 < 20%
使用默认qconfig配置进行Calibration,精度损失较小时,建议可以参考QAT方案Calibration使用说明 的第2.2和第4章进行超参、校准算法以及数据集的优化调试。若尝试调优后仍然存在小幅损失,则建议进行量化训练。
2.2 Calibration 精度损失 ≥ 20%
- 建议优先排查浮点模型精度是否正常(是否正常收敛、过拟合、加载权重是否正确等等);
- 当使用默认配置,Calibration损失较大或尝试了上一节的调参建议后仍存在大幅损失时,建议参考后文第五章使用分析工具来定位量化异常层。
3. QAT量化训练阶段的精度调优建议
-
量化参数初始化-
建议默认使用Calibration,可以使得QAT训练获得更好的精度、收敛得更快。-
Calibration精度损失较小时,建议参考QAT方案Calibration使用说明 第三章固定activation scale,即设置 activation averaging_constant=0.0。 -
Transform(数据增强)-
建议默认 QAT 时保持跟浮点一致,也可以适当减弱,比如分类的颜色转换可以去掉,RandomResizeCrop 的比例范围可以适当缩小等。 -
Optimizer-
默认情况 QAT 时保持跟浮点一致,也可以尝试 SGD,如果浮点训练采用的是 OneCycle 等会影响 LR 设置的优化器,建议不要与浮点保持一致,使用 SGD 替换。异常情况处理:
- 出现NAN:
- 检查浮点模型精度是否正常;
- 检查数据和 label 中有无 nan,inf;
- 调低学习率,或者使用 warmup 策略;
- 使用 torch.nn.utils.clip_grad_norm_ 进行梯度截断。
- loss异常
- 检查是否正确加载Calibration参数
- 出现NAN:
若按照以上建议调试仍然不能有效提升量化精度,建议参考后文第五章使用分析工具来定位量化异常层。
4. 定点精度的调优建议
- 首先建议参考QAT一致性对齐流程排查是否是前后处理引入的误差;
- 排查是否是加载ckpt之后又做了错误修改导致模型输出异常;
- 若为Calibration直接转定点,且Calibration精度损失很小,定点精度损失较大,请先确认Calibration阶段的操作是否有误(通常是由于伪量化节点状态设置不正确,导致Calibration阶段测试的是浮点精度,建议参考QAT方案Calibration使用说明 第六章第3个常见问题,正确使用set_fake_quantize设置模型状态);
- 若为Calibration直接转定点,定点精度损失不算太大,可继续进行qat,并尝试不同epoch的qat模型以找到最佳定点精度。
- 若以上策略均无明显收益,则建议参考后文第五章使用分析工具来定位量化异常层。
5. 分析工具使用指南
QAT方案提供了对应的profiler工具包horizon-plugin-profiler,工具链GPU docker中已预装好该工具包,若您为本地安装使用,可以从OE开发包的该路径下获取whl安装包:OE/ddk/package/host/ai_toolchain。该工具包完整的使用方式及各个接口的参数配置说明建议参考用户手册 分析工具使用指南,本文仅对几个常用的精度调优接口做介绍(fuse检查、共享op检查、量化配置检查、相似度对比、统计量、分步量化、单算子转换精度调试)。
**由于qat阶段会改变模型的weight,不建议将浮点模型/calibration模型与qat模型做比对;
请使用可以复现精度问题的真实数据作为分析接口的输入,否则可能会导致分析结果与实际不符(例如相似度出现0甚至是负值);
请在使用分析工具之前调用set_fake_quantize接口将calibration模型设置为VALIDATION的状态,避免因为伪量化节点未生效导致分析结果异常;
**
对于初次使用的用户,推荐使用集成接口来一次性完成相似度、统计量、共享 op 、fuse pattern和量化配置的检查,使用方式如下:
from horizon_plugin_profiler import check_unfused_operations
float_model = load_float_model(pretrain=True)
calib_model = prepare_qat_fx(copy.deepcopy(float_model), {"": default_calib_8bit_fake_quant_qconfig})
calib_model.eval()
# 设置FakeQuantState.CALIBRATION之后请不要再设置.eval(),避免出现scale不更新等异常情况
set_fake_quantize(calib_model, FakeQuantState.CALIBRATION)
calibrate(calib_model, calib_dataloader, device) # 从训练集中选适量数据进行模型推理
# 使用任何分析工具之前,都需要将模型状态设置为validation,以使得伪量化节点处于正常状态
set_fake_quantize(calib_model, FakeQuantState.VALIDATION)
# 双输入模型运行示例
model_profiler(float_model, calib_model, (data0, data1), mode="FvsQ", out_dir="float_calib")
calib_model2 = deepcopy(calib_model)
quantized_net = convert_fx(calib_model2)
model_profiler(calib_model, quantized_net, (data0, data1), mode="QvsQ", out_dir="calib_quantized")
对于debug工具生成的结果,建议依次关注如下几个方面:
5.1 共享op
5.1.1 可能导致的问题
- 共享op可能会导致模型部分结构 fuse 失败
- 共享op可能会导致多个op共用相同的量化参数,导致出现精度问题
5.1.2 分析方式
注:基于浮点模型做检查(calibration和qat模型由于已完成fuse,无法进行原始模型结构相关的检查)
方式1:调用集成接口model_profiler(mode=FvsQ),打开profiler.html,若存在共享op会有标红提示:
方式2:调用共享 op 检查接口get_module_called_count,观察终端打印结果中called times>1的 op
方式3:从浮点模型统计量或者是相似度分析(若共享op被fuse,可能观察不出来)的结果文件中观察,如下图所示,Module Name字段后带序号的(num)op即为共享op。
![]()
5.1.3 优化建议
单独定义每个op/block(除DeQuantStub节点之外),避免一次定义多次调用。
若存在共享conv对精度有增益等特殊设计场景,可通过复制两份op并加载同一份参数以避免出现异常fuse的情况。
5.2 未正确fuse
5.2.1 可能导致的问题
- 模型量化精度损失明显(如果每个算子都独立进行计算,则都会被单独计算量化参数)
- 模型部署延时变长(算子融合可以减少部署模型中的op数量,加快模型计算)
5.2.2 分析方式
注:基于浮点模型做检查(calibration和qat模型由于已完成fuse,无法进行原始模型结构相关的检查)
观察任意的相似度分析或者非原始浮点模型的统计量结果,如果发现存在有零散的bn、relu和floatfunctional.add则代表有可能存在未被正常fuse的现象。
![]()
再结合check_unfused_operations的检查结果,查看该算子的融合建议:
本文基于J5 OE1.1.68版本测试,集成接口model_profiler(mode=FvsQ)profiler.html中的fuse检查结果可能存在部分漏检,后续新版本将修复。
5.2.3 优化建议
参考用户手册中所列支持的算子融合范围,尽可能融合模型中可被融合的结构。-
如果使用的是fx模式,则在prepare过程中会自动完成算子融合,一般仅在异常op共享或wrap时会出现未fuse的情况,请按照前文建议取消共享并合理调整wrap范围以使得可被fuse的结构均被fuse。
5.3 数据分辨率不足
由于calibration和qat的默认qconfig均为int8配置,数据表示范围有限,因此使用默认配置可能会导致部分层出现明显的精度损失。
5.3.1 分析方式
5.3.1.1 模型输入
观察浮点模型统计量 profile_featuremap & get_raw_features结果中QuantStub节点的最大最小值,若不处于[-1,1]之间,则代表对应输入节点未做数据归一化,存在较大量化风险。
![]()
此外还有一些特殊场景输入节点有明确的物理含义(如gridsample的grid输入),其输入数值确实需要为一系列整型数:

则建议首先判断其数值范围是int8还是int16(上图明显超过了int8可表示范围),配置对应的qonfig,然后手动设置该输入节点的scale(scale=max/128.0或scale=max/32768.0)。
# gridsample算子grid输入的scale和qconfig配置示例
import horizon_plugin_pytorch.nn as hnn
data_shape = (3, 512, 960)
grid_size = (128, 128)
def get_grid_quant_scale(grid_shape, view_shape):
max_coord = max(*grid_shape, *view_shape)
coord_bit_num = math.ceil(math.log(max_coord + 1, 2))
coord_shift = 15 - coord_bit_num
coord_shift = max(min(coord_shift, 8), 0)
grid_quant_scale = 1.0 / (1 << coord_shift)
return grid_quant_scale
view_shape = [data_shape[1] / 16, data_shape[2] / 16]
grid_quant_scale = get_grid_quant_scale(grid_size, view_shape)
class ViewTransformer(nn.Module):
def __init__(self):
self.quant_stub = QuantStub(grid_quant_scale)
self.grid_sample = hnn.GridSample(
mode="bilinear",
padding_mode="zeros",
)
def forward(feats, points):
trans_feat = self.grid_sample(feats,self.quant_stub(points[i]),)
def set_qconfig(self) -> None:
from horizon_plugin_pytorch.quantization.qconfig import default_calib_16bit_fake_quant_qconfig
self.quant_stub.qconfig = default_calib_16bit_fake_quant_qconfig
5.3.1.2 模型输出
若模型输出层为conv/linear,为保障精度建议开启高精度输出(具体原理可查看QAT快速上手常见问题第一条),输出层是否正确开启高精度输出有如下几个判断途径,大家依据习惯择其一即可:
-
直接打印calibration/qat模型,若已开启了高精度输出,则对应conv/linear节点将不会有(activation_post_process): FakeQuantize字段(更详细的示例可以查看QAT FAQ第六条);
-
观察统计量结果文件最后一列,若开启了高精度则输出类型为float32;
-
观察qconfig检查结果文件,若开启了高精度则输出类型为float32;

-
可观察相似度结果中DeQuantStub节点是否含scale,若无scale则代表已开启了高精度输出,若有则代表未开启。

请注意,若对非输出层conv/linear配置了高精度输出,则会导致calibration和qat阶段报错AttributeError: ‘NoneType’ object has no attribute ‘numel’。
5.3.1.3 模型中间层
与模型输入的分析方式相同,通过观察相似度(qscale和数值范围是否匹配)和统计量(最大最小值是否超过int8数值范围)的结果,判断是否是为某个节点数值分辨率不足导致的量化损失。
5.3.2 优化建议
5.3.2.1 模型输入
模型输入一般有两种:原始数据(图像,雷达等)和模型的辅助输入(如 transformer 的位置编码);这些数据都需要量化之后才能作为量化网络的输入,由于量化工具采用对称均匀量化的方式,建议通过以下手段来改进:
- 数据预处理时针对输入数据做关于 0 对称的归一化;
- 查看量化配置是否合理,比如图像输入建议采用固定量化 scale=1/128.0(若归一化未处理至[-1,1],则设置scale=max/128.0,max为输入数据集绝对值的最大值);但固定 scale 不一定适合所有数据,需要具体分析;
- 如果数据分辨率要求比较高且无法调整,建议使用 int16 的量化。-
以图像输入为例,由于原始图像(不管是 RGB 还是 YUV)输入范围是 [0, 255],不适合对称量化,而做关于 0 对称的归一化之后,输入范围变为 [-1, 1],可以直接使用固定 scale=1/128.0 进行量化。
5.3.2.2 模型输出
模型输出很多时候有物理含义,可能要求比较高的分辨率,不适合 int8 量化,建议:
- 输出不量化。目前 conv2d 作为网络输出时,支持输出不量化(即配置输出节点的qconfig为
default_calib_8bit_weight_32bit_out_fake_quant_qconfig); - 如果 BPU 性能等原因需要量化输出,建议使用 int16 量化,或者通过调整输出物理含义的方式降低输出数据分辨率。
5.3.2.3 模型中间层
从实现角度看算子有两种:1. 单粒度算子,如 conv2d;2. 通过多个小算子实现的复杂算子,如 layernorm;这里主要关注算子整体的输出,忽略复杂算子内部的小算子输出。-
如果算子输出的数值范围较大,建议如下:
- 通过修改模型结构将数值限制在某个范围,可以根据不同算子采用不同的方案,比如 conv2d 后面加 BN、替换 relu 为 relu6 等;
- 使用 int16 量化;
- 如果遇到 conv-[bn]-[add]-relu 这样的 pattern,可以尝试在 QAT 阶段指定使用 relu6(不一定有效)。
如果存在某层的 weight 的数值范围较大,可以:
- 尝试调整 weight-decay;建议在 4e-5 附近做适当调整,不要过大或过小。weight decay 过小导致 weight 方差过大;过大则可能导致连锁反应,比如网络层输出的 weight 方差过大。
5.4 单算子损失异常
5.4.1 分析方式
这类问题的表现为单算子损失较大,量化后或转定点后相似度明显降低。例如下图中的cat节点就存在明显异常,观察其输入输出统计量,发现两个输入的数值范围不同:

若观察相似度和统计量结果均未获取到有效信息,此时则建议使用分步量化工具(排查QAT阶段的量化异常层)以及单算子转换精度调试工具(排查定点精度损失异常层)。
# 分步量化(即QAT阶段仍然保持部分op使用浮点计算)
# fx模式
qat_model = prepare_qat_fx(
model,
{"": default_qat_8bit_fake_quant_qconfig},
hybrid=True,
hybrid_dict={"module_name": ["conv1",]})
# eager模式
model.conv1.qconfig = None
qat_model = prepare_qat(model, hybrid=True)
# 单算子转换精度调试(即转定点后仍然保持部分op处于QAT/calibration的状态)
# 在prepare_qat/prepare_qat_fx之后进行配置,以下两种方式均可
set_preserve_qat_mode(calib_net, ("conv1"), ())
calib_net.conv1.preserve_qat_mode = True
5.4.2 优化建议
易出现量化误差损失较大的算子有以下几类:
- 多输入算子,如 cat,如果存在不同的输入数值范围差异过大时,可能出现“大数吃小数”的现象(cat输出会重新计算scale,因此会导致数值范围较小的输入信息被掩盖),最终导致精度异常。建议尝试以下方式改进:-
a. 需要通过各种手段限制输入范围,让多输入的数值范围相近;-
b. 使用 int16 量化; - 非线性激活算子(如sigmoid、sqrt、log、reciprocal等),底层都是通过查表实现的,由于查表项有限,当输出处于陡峭的区间时,可能导致分辨率不足。可尝试以下方式改进:-
a. 评估下是否可以少使用/不使用此算子或替换成其他不会导致值域增大的激活算子;-
b. 限制输入范围在较为平缓的区间;-
c. 使用 int16 量化;-
d. 如果 QAT 精度正常但 quantized 精度不足,建议寻求地平线技术支持。 - 复杂算子,比如 layernorm 和 softmax,一般是由多个小算子拼接而成,其中可能存在上面提到的非线性激活算子,也会导致精度问题。尝试以下方式改进:-
a. 评估下是否可以不使用此算子或替换成其他算子(精度风险较高的算子可查看用户手册 算子列表中的备注);-
b. 如果 QAT 精度正常但 quantized 精度不足,可尝试手动调整查表参数,如 layernorm 和 softmax 均支持手调的参数;





