专栏算法工具链【地平线J6工具链进阶教程】J6E/M工具链QAT精度调优

【地平线J6工具链进阶教程】J6E/M工具链QAT精度调优

芯链情报局2025-09-28
787
0

QAT调优流程:

流程总览:

针对J6E/M的硬件特性,以int8+int16的混合精度量化为主要调优配置,可尝试少量fp16算子如LayerNorm(fp16 LayerNorm在OE 3.5.0及以后版本支持)。

 

调优原则:

如上是一个标准的对称量化公式,产生误差的地方主要有:

  1. round 产生的舍入误差。例如:当采用 int8 量化,scale 为 0.0078 时,浮点数值 0.0157 对应的定点值为 round(0.0157 / 0.0078) = round(2.0128) = 2,浮点数值 0.0185 对应的定点值为 round(0.0185 / 0.0078) = round(2.3718) = 2,两者均产生了舍入误差,且由于舍入误差的存在,两者的定点值一致。

     

      对于舍入误差,可以使用更小的 scale,这样可以使得单个定点值对应的浮点值范围变小。由于直接减小 scale 会导致截断误差,所以常用的方法是使用更高的精度类型,比如:将 int8 换成 int16,由于定点值范围变大, scale 将减小。

 

  1. clamp 产生的截断误差。当 qmax * scale 无法覆盖需要量化的数值范围时,可能产生较大截断误差。例如:当采用 int8 量化,scale 为 0.0078 时,qmax * scale = 127 * 0.0078 = 0.9906,大于 0.9906 的值对应的定点值将被截断到 127。

     

      对于截断误差,可以使用更大的 scale。scale 一般是由量化工具使用统计方法得到,scale 偏小的原因是校准数据不够全,校准方法不对,导致 scale 统计的不合理。比如:某一输入的理论范围为 [-1, 1],但校准或 qat 过程中,没有观测到最大值为 1 或最小值为 -1 的样本或观测到此类样本的次数太少。应该增加此类数据或者根据数值范围,手动设置固定 scale。在截断误差不大的情况下,可以调整校准参数,通过不同的校准方法和超参缓解截断误差。

因此,QAT量化精度调优以减少上述两种误差为基本原则,下文将针对QAT每个阶段做调优介绍:

1. 浮点训练 & 评测

一般情况下,QAT精度调优以训练好的浮点模型作为初始条件,理想情况下,浮点模型的设计和迭代应充分考虑量化风险,主要包括2个方面:

  1. 在保持浮点精度的情况下,尽可能使用量化友好的结构和算子:特征提取部分尽可能使用CNN结构、尽量裁剪多余的attention和elementwise(add/mul/sub等)操作、激活函数首选ReLU、bbox回归和位置编码常用的sigmoid/inverse_sigmoid等的操作避免量化(从模型移除到前后处理)、使用可被QAT融合的结构例如Conv+普通BN2d+ReLU、模型尾部保持TAE算子(GEMM/Conv/Linear/Matmul)直接输出
  2. 调整浮点数据流和浮点训练策略,尽可能量化友好:输入数据做[-1, 1]的归一化、图像训练数据建议做bgr->yuv420->yuv444的处理感知部署时nv12输入数据的颜色损失、Conv/Linear后多增加BN和LN约束数据范围、Concat算子多输入前添加BN约束数据范围避免数值范围差异过大(Concat算子会综合多输入的数据范围重新统计scale,存在“大数吃小数”的风险)、INF/NAN等特殊类型的数据替换为例如100或-1、极大的数值(如float64的掩码值)根据物理意义做手动的截断、合理选择weight_decay避免weight数值分布不均或数值极小
此外还可以参考用户手册对应章节:【构建量化友好模型】

 

首先需要在浮点链路上完成训练和评测整体流程的打通,需要成功复现浮点模型的精度,一方面熟悉模型结构和评测流程,为接下来的量化调优做准备;另一方面避免浮点数据流的bug影响量化调优

 

此外有条件的情况下,建议对已有的浮点模型和权重做小lr的fine-tune,lr可以复用浮点最后一个epoch的配置,或者参考QAT建议选择浮点初始学习率的1/10~1/20,来提前排除模型过拟合相关风险

 

浮点模型更多算子/结构优化建议可以参考【地平线 J6工具链进阶教程】算子优化方案集锦

https://developer.horizon.auto/blog/13133)。

2. 模型改造

QAT校准和QAT训练都依赖对浮点模型代码做一定的改造,主要为在模型首尾插入量化(QuantStub)和反量化(DeQuantStub)标识算子,以及添加量化精度配置的代码。这一部分从上手到实践建议参考用户手册标准流程和配置:

  1. QAT快速入门

  2. QConfig详解

  3. 新版qconfig配置教程:https://developer.horizon.auto/blog/13112

  4. Prepare详解

2.1 常见问题

问题

建议

示例

多输入、多输出模型如何插入QuantStub和DeQuantStub

对每个输入或输出的tensor插入各自的QuantStub和DeQuantStub

 

模型中的标量、常量和bool类型的tensor应该怎么处理

 

在最新的版本中(OE 3.2.0以及之后)对于int8/int16/bool类型的标量或者常变量tensor,无需量化处理;对于浮点类型的常量tensor,建议统一手动量化;

 

对于模型分段部署的场景,应该如何插入QuantStub和DeQuantStub

根据部署要求,可以在模型分段处插入1对DeQuantStub+QuantStub来实现,同时控制分段处的scale一致

 

 

3. 模型检查

完成模型改造和量化配置后,调用Prepare接口时会对模型做算子支持和量化配置上的检查,这些检查一定程度上反映了模型量化存在的问题。对于不支持的算子将以报错的形式提醒用户,一般有两种情况:

  1. 未正确进行模型的量化改造。Prepare过程中QAT量化工具会对模型进行trace来获取完整的计算图,在这个过程中会完成算子替换等的优化,对于这些已替换的算子,输入输出类型如果是torch.tensor而非经过QuantStub转化后的qtensor,则会触发不支持算子的报错,表现为xxx is not implemented for QTensor;
  2. 确实存在不支持的算子。工具链已支持业界大量的常用算子,但对于部分非常见算子的不支持情况,需考虑进行算子替换并且作为算子需求向工具链团队导入。

 

Prepare运行成功后会在当前目录下自动保存模型检查文件model_check_result.txt和fx_graph.txt,建议参考下列解读顺序:
  1. 算子融合检查。算子融合作为QAT量化工具的标准优化手段,常见的融合组合为Conv+ReLU+BN和Conv+Add等,未融合的算子会在txt文件中给出,未按预期融合的算子可能是因为共享没有融合成功或者是QAT量化工具的融合逻辑变更(针对新版qconfig量化模板enable_optimize=True情况,见

      《【地平线 J6工具链入门教程】QAT新版qconfig量化模板使用教程》(https://developer.horizon.auto/blog/13112)),需要检查代码,确认未融合的情况是否符合预期:

      未融合的算子对模型性能会有一定影响,对于精度的影响需视量化敏感度具体分析,一般来说,Conv/Linear+ReLU+BN未融合建议手动修改融合

  2. 共享模块检查。一个 module 只有一组量化参数,多次使用将会共享同一组量化参数,多次数据分布差异较大时,会产生较大误差:

      called times > 1 的模块可能有很多个,全部改写成非共享是一劳永逸的。对于修改简单且精度影响大的共享算子如QuantStub,强烈建议取消共享;对于DeQuantStub算子,共享不会对模型精度产生影响,但是会影响Debug结果的分析,也建议取消共享,修改方式参考“2.1 模型改造-常见问题”

     

      例如下面的共享模块,量化表示的最大值为 128 * 0.0446799 ≈ 5.719,在第一次使用中,输出范围明显小于 [-5.719, 5.719],误差较小, 第二次使用中,输出范围超出 [-5.719, 5.719],数值被截断,产生了较大误差。两次数值范围的差异也造成了统计出的scale不准确,因此该共享模块必须修改

      上面共享算子的修改方式可以参考:

      对于不带权重的function类算子都可以参考上面的拆分方式,但是也存在部分共享算子或模块带有权重参数拆分起来比较复杂,是否需要拆分建议先根据量化敏感度进行分析。带有权重参数算子拆分时需要复制权重,拆分方式可以参考:

      此外,未调用的模块也会在文件中体现,called times为0,当Calibration/QAT/模型导出出现miss_key时,可以检查模型中是否有模块未被trace。
  3. 量化配置检查。txt文件中会给出模型量化精度的统计信息:

重点检查的信息有:

  1. 的input dtype应为torch.float32,对于qint8或者qint16的input dtype,一般是冗余的QuantStub算子可以改掉,不会对精度产生影响但可能会对部署模型性能有影响(算子数量)
  2. 如果没有配置浮点精度,模型中的算子不应出现torch.float32的输入精度,如上图的,需要检查是否漏插QuantStub未转定点,未转定点的算子在导出部署模型时会cpu计算从而影响模型性能。对于模型中的一些浮点常量tensor,工具已支持自动插入QuantStub转定点,建议获取最新版本
  3. 对于GEMM类算子(Conv/Matmul/Linear)作为模型输出时支持高精度输出(J6E/M支持int32输出),体现到这里则是的input dtype应为torch.float32,对于qint8或qint16输入的DeQuantStub需要检查是否符合高精度输出的条件,符合条件但未高精度输出的需修改。此外对于下面左图的结构,也建议优化为右图结构来保证高精度输出的优化
  1. qint8和qint16算子的占比,可以协助判断是否配置全int16生效

txt文件同时会给出逐层的量化配置信息:

重点检查的信息有:

  1. 每层算子的输入输出dtype、权重的dtype,是否符合量化配置;若和量化配置不符合,比如配置了int16,但是算子显示为int8,则需要关注下算子回退信息,例如Conv+Add的融合,Conv不支持int16输入则会导致前序算子的输出回退到int8。新的qconfig量化配置模板下算子回退过程需查看qconfig_changelogs.txt,详细参考:https://developer.horizon.auto/blog/13112
  2. 配置了fix scale的算子,是否正确显示FixedScaleObserver信息,scale值是否正确

  3. 逐层算子的observer是否正确:权重默认MinMaxObserver,QAT校准时激活默认MSEObserver,QAT训练时激活默认MinMaxObserver

  4. 若为QAT训练阶段且配置了固定校准的激活scale,查看averaging_constant,判断是否生效,生效为averaging_constant=0(即不更新scale),默认为0.01(更新scale)

对于fx_graph.txt,可以从中获取到模型中op/module的上下游调用关系,例如当存在算子called times为0未被调用的情况,可以通过Graph定位到上下文算子从而定位未被调用的原因(通常因为存在逻辑判断或循环次数变化);此外当出现导出的部署模型(bc模型)精度异常,也可以通过Graph信息来排查是否是导出计算图改变导致的。

重点关注的Graph信息:

  • opcode为算子调用类型
  • name为当前算子名称,需注意和model_check_result.txt中的module.submodule名称区别
  • target为算子输出
  • args为算子输入

4. QAT校准

4.1 int8+int16混合精度调优

QAT校准是QAT精度调优流程中的一个关键环节,校准不涉及繁重的训练,只需要从训练集中选取分布均衡的校准数据校准就可以快速获取模型逐层的量化阈值。此外因为校准不改变模型的原始权重,可以帮助用户快速地排查影响精度的QAT链路问题,以及定位模型存在的量化参数配置问题。建议用户预估当前模型调优难度来选取合适的校准策略:

  • 较易量化的模型:模型曾完成QAT并且当前仅迭代数据和权重、模型结构简单(例如以CNN结构为主的检测/分类模型)等。这一类模型可以选择全int8的量化精度进行校准,或者延用之前调优的混合精度(int8+int16)配置,具体的量化配置请参考“2. 模型改造”章节;
  • 较难量化的模型:模型第一次适配QAT、模型存在较大结构迭代、模型结构复杂(BEV多任务、Transformer结构、Sparse4D等)。这一类模型可以选择全int16的量化精度进行校准,具体的量化配置请参考“2. 模型改造”章节。

 

对于全int16配置下的模型量化精度不达标或者很差,建议运行QAT Debug工具来进行量化敏感度分析,Debug工具运行和产出物的分析方式请参考“4.2 Debug产出物解读”章节,请结合Debug结果做精准分析,一般来说全int16下精度不达标的可能原因有:
  1. QAT链路适配存在问题,模型在校准和评测下伪量化节点(fake_quant)和统计观测节点(observer)的状态异常、模型校准参数异常未更新等;

  2. 模型存在异常的数据,例如INF或者NAN,这些值很大程度上影响量化效果,需进行修改;

  3. 模型部分层/模块有很大的截断误差,一方面可能由于校准数据选择到了极端的数据,无法获取合理的量化阈值;另一方面一些带有物理意义的数据(例如距离、速度),仅通过统计无法获取合适的量化阈值,需要在充分考虑应用场景下手动指定固定的量化阈值。

全int16配置下的模型通常性能无法满足部署要求,除了只看精度上限的场景,在完成全int16调优后,模型最终都要回到全int8和混合精度调优上,以期望得到一个部署精度和部署性能平衡的量化模型。

 

对于全int8或者混合精度配置下的模型,针对不同的精度表现采用不同的调优策略:

  1. 精度表现很差甚至无精度,且未尝试全int16调优,优先尝试全int16调优,参考前文;

  2. 精度表现距离浮点差距较大(量化精度/浮点精度 ,经验值),建议运行QAT Debug工具来进行量化敏感度分析,结合Debug结果做精准分析,一般的优化手段有:增加int16的配置、对有物理意义的数据手动指定固定的量化阈值。此外需注意int16算子的比例,int16算子过多会影响部署性能,如果int16算子比例已超出部署预期,则可以考虑回退部分int16算子后尝试QAT训练
  3. 精度表现距离浮点差距较小(量化精度/浮点精度 > 90%,经验值),直接尝试QAT训练,在量化精度/浮点精度 >= 95%(经验值)的情况下,建议优先尝试固定校准激活scale的QAT训练(仅调整权重感知量化误差)

 

对于不同精度配置下的QAT校准,都有一些校准超参可以调整,需要用户结合具体模型去做调参优化,其中主要的参数有校准数据的batch size、校准的steps,详细的参数参考:

  1. 基础调优手段

  2. 高级调优手段

 

总结

int8+int16混合精度调优的重点应放在全 int16 调优,这里需要把使用问题,量化不友好模块等等各种千奇百怪的问题都解决,务必要把全int16的精度调优做扎实,看到模型的精度上限,然后根据模型部署的性能要求进行int8和int16混合精度的调优,达成部署精度和部署性能的平衡。

4.2 Debug产出物解读

Debug工具比对的对象是QAT校准模型和浮点模型,由于模型权重在QAT训练过程中已经发生了改变,不建议对比QAT训练后的模型和浮点模型。工具使用和运行请参考:【手册引用:https://docs.oe.horizon.auto/guide/plugin/user_guide/quant_analysis.html,整个过程是工具自动在传入的数据集中查找badcase或者用户手动设置badcase -> 运行badcase -> 逐层比较 -> 计算敏感度。

 

工具运行完成后会在指定目录下生成如下一些关键的文件,按照用到的文件重要性和使用频率分成:

P0-调优重点参考

P1-调优辅助分析

P2-用户不必关注,需要时可提供研发分析

建议分析流程为:

  1. 结合模型精度情况,找到和掉点精度相关的输出敏感度文件,举个例子,量化后模型3D检测框的朝向误差大,精度低,则找到rot/angle输出对应的敏感度文件pt和txt。如果相关输出较多,则需要综合分析

  2. 分析敏感度txt文件

    • 查看敏感度衡量指标,上面示例中为L1,其他较推荐的如ATOL、余弦相似度等,根据经验,ATOL在量化敏感的输出或指标上更能反映量化损失,例如预测轨迹输出或误差类指标。误差大,敏感度高的算子排序靠前,对应这些敏感算子激活量化精度为qint8,如果是已经配置了高精度,需要判断这里配置未生效的原因,可能是不支持的精度组合工具自动回退
    • 具体分析算子量化敏感原因,分析compare_per_layer_out.txt文件:
          对于model.view_transformation.transformer.layers.0.quant,scale 是 0.7764707,能表示的浮点范围为 0.776 * (-128) = -99.38 到 0.776 * 127 = 98.61。结合这里的物理含义,此 quant 的输入范围应该是 -100 到 1,所以会产生少量的截断误差。同时,此数值范围较大,对于 int8 来说,也会产生较大的舍入误差。所以需要改为 int16 量化并按输入范围设置固定 scale 为 100 / 32768。对于 model.obstacle_head.decoder.layers.0.cross_attn.quant_normalizer也一样是类似的问题。对于量化精度较低导致的截断误差/舍入误差问题,工具支持按敏感度比例批量配置高精度qint16
    • 对于舍入误差,可以修改为int16量化,对于截断误差,可以修改为int16,也可以手动指定固定的量化scale来cover数值范围。“对症下药”修改后,体现到对应算子的敏感度应降低(如没有降低,首先确认修改是否生效,其次确认修改是否正确),整体敏感度均降低后,体现到模型精度指标对应上升

 

总结

  1. Debug工具会提供逐层的误差衡量指标,但最终的调优目标仍是模型整体精度,而非单算子或单层误差指标,原因是部分算子对量化误差并不敏感(如sigmoid和softmax等)且模型整体具有噪声鲁棒性和泛化性;

  2. 优先查看敏感度(与精度掉点相关的输出),看完敏感度再看逐层比较。确定某一算子敏感后,先通过逐层比较或统计量确认造成的误差是舍入误差还是截断误差,然后再针对性地调整量化配置或修改模型结构。

4.2.1 Badcase调优

对于实车或回灌反馈的可视化badcase,利用Debug工具的调优流程为:

 

5. QAT训练

大部分模型仅通过QAT校准就可以获得较好的量化精度,对于部分较难调优的模型,以及还需要继续优化误差类指标的模型,通常校准设置的高精度比例导致延时超过部署上限,但精度仍无法达标,这种情况可以尝试QAT训练来获得满足预期性能-精度平衡的量化模型。

 

根据前文所述,在QAT校准量化精度/浮点精度 >= 95%(经验值)的情况下,充分利用校准阶段较好的激活量化参数,优先尝试固定校准激活scale的QAT训练(仅调整权重感知量化误差),设置方式具体参考“模型改造-QConfig详解”

 

参考浮点训练,QAT训练在大部分配置保持和浮点训练一致的基础上,也涉及到部分超参的调整来提升量化训练的精度,例如QAT的学习率、weight_decay、迭代次数等,详细的参数调整策略参考:

  1. 基础调优手段

  2. 高级调优手段

 

浮点和QAT训练中都涉及到对BN的状态控制,在浮点训练中可能会采用FreezeBN fine-tune的方式来提升模型精度,在多任务训练中也会采用FreezeBN的技巧。因此在QAT训练中,提供了FuseBN和WithBN两种训练方式:

  1. FuseBN即在Prepare后,QAT训练前将BN的weight和bias吸收到Conv的weight和bias中,在训练过程中不再单独更新,这一吸收过程是无损的。FuseBN也是QAT默认的训练方式。

  2. WithBN则是在QAT训练阶段保持Conv+BN不融合,带着BN进行训练,BN的参数单独更新,在训练结束后转成部署模型时再做融合。浮点训练阶段如果采用了FreezeBN的训练方式,QAT训练时需设置WithBN,设置方式如下:

 

通过观察QAT训练过程的Loss变化来初步判断QAT训练的量化效果,一般来说和浮点最后的Loss结果越接近越好,Loss过大可能难以收敛,Loss过小可能影响泛化性,对于异常的Loss建议的优化手段:

  1. 异常INF和NAN的Loss值,或者初始Loss极大且无收敛迹象,按如下顺序排查:

    • 去掉 prepare 模型的步骤,用 qat pipeline finetune 浮点模型,排除训练 pipeline 的问题,Loss如果仍异常,需要检查训练链路的配置如优化器optimizer和lr_updater等

    • 保持当前QAT训练配置,只关闭伪量化节点后观察训练的Loss现象,理论上和浮点无差异

    • lr 设置为 0,进行 qat 训练,排除参数调整不到位的问题。qat 训练的精度应该与 calibration 精度几乎一致

    • 此外还需要检查是否使用了特殊的数据增强策略(如旋转、马赛克等会改变真实的数据分布)

  1. 在排查完链路问题后出现初始Loss较大,有收敛迹象但收敛较慢,这种情况可以尝试调整学习率,延长QAT迭代次数,因为QAT训练本质上是对已收敛浮点模型的fine-tune,本身存在一定的随机性,用较大的学习率可以快速波动到一个理想精度(依赖一些中间权重的评测)

  2. 对于少数模型,QAT训练以及尝试了多次超参调整后精度仍无法达标,建议回归QAT校准阶段增加少量高精度算子(int16或者尝试对Layernorm层配置少量fp16高精度)、回归浮点结构检查是否还存在量化不友好的结构如使用了大量GeLU等(参考“浮点训练 & 评测”)

5.1 QAT训练效率

由于QAT训练过程需要感知模型量化所带来的损失,因此模型中会被插入必要的量化相关的节点:数据观测节点Observer和伪量化节点FakeQuant。数据观测节点会不断统计模型中数据的数值范围,伪量化节点会根据量化公式对数据做模拟量化和反量化,两者都会存在开销,此外就是QAT工具内部会对部分算子例如LN层做拆分算子的实现,因此相同配置下的QAT训练效率是会略低于浮点训练效率,具体还和模型参数规模、算子数量等有关。

 

对于用户可明显感知到的QAT训练效率降低,建议的优化手段有:

  1. 使用QAT工具提供的算子,这些算子优化了训练效率,例如MultiScaleDeformableAttention(参考手册:MultiScaleDeformableAttention
  2. 更新到最新的horizon-plugin-pytorch版本,新版本会有持续的bug fix和新特性优化,如模型中某些结构或者算子训练耗时增加明显,可以向工具链团队导入

6. 模型导出部署

完成QAT精度调优后得到的模型仍是PyTorch模型,需要使用简单易用的接口来一步步导出编译成部署模型:

PyTorch模型 -> export -> convert-> compile

export得到qat.bc;

convert得到quantized.bc;

compile得到hbm

由于导出生成物中计算差异的存在,对于每个生成物需简单验证其精度,可通过单张可视化或mini数据集,过程中如存在精度掉点,请参考后续一致性问题定位与解决的文章。

J6E/M调优案例集锦:

1. RT-DETR:

开源算法QAT调优流程示例,repo:https://github.com/lyuwenyu/RT-DETR

 

1.1 浮点复现

浮点公版精度:

浮点复现精度(一致):

 

1.2 浮点模型改造 & Prepare

QAT选择建议的JIT_STRIP模式,因此浮点改造只需插入QuantStub/DeQuantStub,其次就是明确模型量化部署范围,一个快速的方式是参考公版export_onnx脚本

 

对浮点模型进行QAT校准阶段的prepare

1.3 分析model_check_result

在默认全int16模板下分析model_check_result信息,检查模型不合理结构:

  1. 取消Bbox head的relu复用:src/zoo/rtdetr/rtdetr_decoder.py

无权重的简单算子复用,建议改掉;对于带权重的算子或整个module复用,视敏感度列表酌情改

  1. encoder输入的pos_embed需要手动量化:src/zoo/rtdetr/hybrid_encoder.py

model_check_result会提示存在mix precision input信息

  1. decoder输入的anchors需要手动量化:src/zoo/rtdetr/rtdetr_decoder.py

model_check_result会提示存在mix precision input信息

  1. 替换backbone自定义的FrozenBatchNorm2d为nn.BatchNorm2d(否则不会和conv融合且出现单独的mul和add):configs/rtdetr/include/rtdetr_r50vd.yml

BN没有融合,model_check_result会提示存在mix precision input信息

 

1.4 Calibration

开始Calibration前先导出部署模型,验证算子后端和模型性能,见”1.5 部署导出“。该模型仅通过Calibration就可以获得较好的量化精度

全int16

小成本bs 16,10 steps校准下模型精度为0:

  1. 首先排查链路问题,取消伪量化后精度和浮点一致,排除链路问题

  1. 适配Debug工具,分析敏感算子

    1. encoder输入的pos_embed需要手动量化:src/zoo/rtdetr/hybrid_encoder.py

      值域[-1, 1],给定fix_scale,统计量也可反映

    1. decoder输入的anchors需要手动量化(anchors前处理放在模型外):src/zoo/rtdetr/rtdetr_decoder.py

      避免anchors计算中的inf值,mask掉的无效锚点替换inf为100:

      同时指定quant_anchors和anchors_add为int16的fix_scale(需要通过插入identity解决Conv+Add结构回退int8):

    1. bbox输出的sigmoid相关操作放到后处理(linear层高精度输出),进行精度debug时带上:src/zoo/rtdetr/rtdetr_decoder.py
      src/zoo/rtdetr/rtdetr_postprocessor.py
    1. 将敏感度top2的model.backbone.conv1.conv1_1.conv和model.backbone.conv1.conv1_2.conv由激活int16改为权重int16,指标有微小提升,进一步可修改为权重激活双int16

全int16量化配置:

 

 

 

混合精度

基于全int16配置,仅将全int16模板改为全int8模板,精度为0,通过Debug工具继续分析敏感度算子,将新增的敏感算子配置int16,最简混合精度量化配置:

 

1.5 部署导出

提前导出验证发现模型存在cast、linear、mul、topk算子在cpu上,修改src/zoo/rtdetr/rtdetr_decoder.py:
  1. bool类型tensor转浮点并量化

  1. topk后的int64索引接gather,需手动cast

修改后算子统计如下(topk可以被bpu吸收、qdq可删除):

 

 

2. 静态感知模型:

2.1 车道线弯折和偏移

Calibration和QAT均出现车道线可视化弯折现象,浮点无此现象,且现有量化指标良好无法反映此现象

2.1.1 Badcase调优

Step1:定位Calibration阶段复现问题,出问题的可视化直接跑Debug工具,对应修改
  1. 使用精度上限双int16模板

  2. decoder中linear层改写wx+b实现权重int16(在OE 3.5.0以及之后版本已直接支持配置权重int16
  3. head中mul和sum使用fix_scale

解决弯折问题,出现新问题:车道线横向偏移

Step2:QAT训练解决车道线横向偏移过程中重新出现弯折问题,对QAT模型分段量化定位到辅助头量化引入较大量化误差,取消辅助头量化后同时解决车道线弯折和横向偏移问题
Step3:量化精度风险点解除,根据性能要求进行回退
  1. 量化精度上回退int16比例

  2. 结构上回退回原linear层

2.1.2 总结

步骤

说明

step1:使用w16a16模版

  1. 有效:继续step2

  2. 无效:

    1. 根据敏感度表,设置fix scale

    2. 替换低精度算子为高精度实现

step2:使用w8a16模版

  1. 有效:继续step3

  2. 无效:

    1. 可能部分算子需要设置w16,根据敏感度表检查设置

step3:进一步压缩耗时

  1. 使用w8a16模版,分模块回退int8,可以从backbone,neck开始进行回退,因为它们一般不敏感,同时结合敏感度表,对部分op手动设置激活int16

  2. 使用w8a8模版,根据敏感度表,按照topk的比例自动配置

  1. 精度debug工具,定位到敏感算子,高精度无法解决的问题,往往需要对敏感算子进行fix_scale配置或者改写调整

  2. 对于实际部署没有使用的结构,量化上需要谨慎(例如此案例的辅助头);算子选择上,选择BPU支持的算子以保证模型可导出部署

  3. 针对Badcase现象,设计增加评价指标

2.1.2 总结

步骤

说明

step1:使用w16a16模版

  1. 有效:继续step2

  2. 无效:

    1. 根据敏感度表,设置fix scale

    2. 替换低精度算子为高精度实现

step2:使用w8a16模版

  1. 有效:继续step3

  2. 无效:

    1. 可能部分算子需要设置w16,根据敏感度表检查设置

step3:进一步压缩耗时

  1. 使用w8a16模版,分模块回退int8,可以从backbone,neck开始进行回退,因为它们一般不敏感,同时结合敏感度表,对部分op手动设置激活int16

  2. 使用w8a8模版,根据敏感度表,按照topk的比例自动配置

  1. 精度debug工具,定位到敏感算子,高精度无法解决的问题,往往需要对敏感算子进行fix_scale配置或者改写调整

  2. 对于实际部署没有使用的结构,量化上需要谨慎(例如此案例的辅助头);算子选择上,选择BPU支持的算子以保证模型可导出部署

  3. 针对Badcase现象,设计增加评价指标

2.2 车道线朝向

2.2.1 量化问题 & 经验

  1. MapTR出现全int16 calib+qat下,map angle dist指标不达标

    1. Debug工具查看量化敏感度,weight和激活同时需要int16量化,在OE 3.5.0以及之后版本已支持
  2. MapTR出现qat map angle dist指标差,同时存在车道线抖动问题

    1. 优先排查特殊算子,例如存在浮点 CUDA MSDA算子和Horizon MSDA算子实现逻辑差异导致的计算数值误差,可通过Debug工具敏感度反映

    2. 对于实车的可视化问题,浮点模型就存在,根因是浮点指标收敛不够,被量化损失放大,需从浮点解决

2.3 案例总结

 

 

2.3.1 Float

  • 思路:

    • 确认是否有指标能体现“弯折”的程度/状态,如果没有,建议增加评价指标用来量化评估

2.3.2 Calibration

先确认calibration阶段是否存在“弯折”现象,如果存在,可以按照下述思路进行验证
  • 思路:

     

    Action

    依赖工具

    注意点

    #1

    检查calibration数据集,一般来说校准数据最好覆盖到全部的部署场景,如果校准数据单一,建议更新校准数据集

    校准集的更新:覆盖城区,高速,弯道,直道,白天,夜晚等等

     

    #2

    用精度Debug工具获取敏感度表,配合高精度的量化模版一同来做验证

    精度debug工具

    推理&评测&可视化脚本

    敏感算子是否有linear

    敏感算子是否有sigmoid

    是否有不支持的算子,如einsum

    #3

    依次做w16a16的模版验证(理论最高精度)-依次做w16a16的模版验证(理论最高精度)-依次做w16a16的模版验证(理论最高精度)-依次做w16a16的模版验证(理论最高精度)---> w8a16 - w8a16 - w8a16 - w8a16 ---> 逐步回退int8

    不同精度的qconfig模版设置

    如果w16a16效果无改善,可以做分步量化来进一步定位敏感模块的范围,针对性分析

  • 验证效果:

    • calibration阶段弯折改善

    有效解法

    现象

    1. 使用双int16模版

    2. 修改decoder中的linear layer为wx+b的形式

    3. 车道线head中的mul, sum使用fix scale = 7/32767

    1. 车道线可视化平滑

    2. 车道线与gt有一定横向偏差

    • 解法说明:

      • Linear layer在敏感度表前列且是int8量化,使用wx+b的形式之后,该算子的weight可以被int16量化

      • 在J6E/M中,linear算子没法直接把weight和input都设置为int16,如果需要weight和intput都是int16量化,就需要使用一种等价替代的方式,即broadcast mul & sum(具体方案可见:【地平线J6工具链进阶教程】算子优化方案集锦
      • 需要注意的是,如果mul的输出绝大多数数值都在0附近 -需要注意的是,如果mul的输出绝大多数数值都在0附近 -需要注意的是,如果mul的输出绝大多数数值都在0附近 -需要注意的是,如果mul的输出绝大多数数值都在0附近 ---> MSE校准受异常值影响较大 - MSE校准受异常值影响较大 - MSE校准受异常值影响较大 - MSE校准受异常值影响较大 ---> 输出scale非常大 - 输出scale非常大 - 输出scale非常大 - 输出scale非常大 ---> 0附近的大量小数值被舍入成0 - 0附近的大量小数值被舍入成0 - 0附近的大量小数值被舍入成0 - 0附近的大量小数值被舍入成0 ---> sum和发生巨大偏差,会使得mul后面跟着sigmoid或add+sigmoid时影响很大,因此对mul输出设置fixed scale = 7/32767(经验值),这是因为sigmoid并不需要太大的输入,而mul的输出分布需要小scale

2.3.3 QAT

在解决了calibration阶段的问题之后,理论上QAT训练不会再有问题,如果QAT阶段依然遇到了训练后弯折的问题,可以有两种考虑的做法

  • 思路:

     

    Aciton

    依赖工具

    注意点

    #1

    使用fix_scale的方式进行QAT训练

    plugin训练工具

     

    #2

    检查浮点模型,是否有对量化不友好的实现

    n/a

     

    #3

    如果QAT训练没有问题现象出现,且calibration使用了高精度的模版,那么逐步回退int8来提高效率,在精度 - 效率之间取得平衡

    plugin训练工具

    1. 正向分模块回退int8,从backbone,neck开始,逐步扩展到encoder,decoder

    2. 也可以配合敏感度表,按照敏感度算子的配比设置来进行设置,有条件建议2条路同时做

  • 验证效果:

    • 在qat训练后未出现弯折,之后继续调试减小耗时

    有效解法

    现象

    1. 使用双int16模版

    2. 修改decoder中的linear layer为wx+b的形式

    3. 车道线head中的mul, sum使用fix scale = 7/32767

    1. 车道线可视化平滑

    2. 横向偏差减小

    1. 使用全int16模版

    2. 修改decoder中的linear layer为wx+b的形式

    3. 车道线head中的mul, sum使用fix scale = 7/32767

    1. 车道线可视化平滑

    2. 横向偏差减小,指标接近浮点

    3. bc推理可视化无明显异常

    4. 板端耗时~88ms

    1. 回退部分wx+b 回退部分wx+b 回退部分wx+b 回退部分wx+b --> linear

    2. backbone + neck int8

     

    Qat 4w step

    • lane0 precision:90.82 recall:74.62

    • lane1 precision:98.32 recall:80.34

    • lane2 precision:97.71 recall:66.65

    • 平滑度:gt:0.097 qat:1.689

2.3.4 其他建议

  1. 训练超参里,可以做weight decay的微调,weight decay会影响参数的范围,更小的范围更有利于量化,建议从浮点阶段就微调weight decay

  2. 增加norm算子,也是一种限制参数分布范围的方式,如果某些op在敏感度表中表现为weight,activation都敏感,那么就需要设置双int16来做量化,但是受限于J6E/M的支持效率,这种量化会可能有效率的损失,因此如果能从模型结构上做改善会更好

调优技巧:

1. 分步量化:

下面这种方式仅适用于 Calib 阶段,QAT 阶段因为模型已经适应了量化误差,关闭伪量化精度无法保证

 

2. 部分层冻结下的QAT训练

模型 QAT 训练时,要求模型为 train() 状态,此时若部分层冻结,则需要对应修改状态,参考代码如下:

 

3. Calib/QAT过程NaN值定位

修改horizon_plugin_pytorch/quantization/fake_quantize_base.py中check_nan_scale="forward",出现NaN值会在calib/qat forward过程中报错,有助于定位到具体的算子。常见的可能出现NaN值的结构:
  1. Multi-head Attention的attn mask,需要手动做数值的clamp

 

算法工具链
技术深度解析社区征文官方教程征程6
评论0
0/1000