写点什么

PTQ 精度 Debug 工具

  • 2024-12-02
    广东
  • 本文字数:7877 字

    阅读完需:约 26 分钟

PTQ 精度 Debug 工具

01 前言

使用 PTQ 后量化的模型量化方案,可以帮助用户非常简单便捷地完成从浮点模型到地平线混合异构模型的转换,模型转换工具会基于用户提供的校准样本对模型进行校准量化并保障模型高效地部署在地平线计算平台上。


但是在模型转换的过程中,不可避免地会因为浮点高精度到定点低精度的量化过程而引入精度损失,因此为了帮助用户准确快速地定位模型精度损失的主要原因,地平线工具链提供了一套 PTQ 精度 Debug 工具。


本文将基于 征程 5 工具链 1.1.62 版本的 OpenExplorer 开发包来详细介绍这些工具的使用方法和使用流程,以及对输出结果进行解读,来帮助用户快速上手。


对于 XJ3 工具链,PTQ 精度 Debug 工具的使用方法一致,但是暂不支持命令行配置和 runall 功能。


在本文开始之前,需要用户参考文章图片校准数据准备问题介绍与处理和模型精度验证及调优建议,掌握正确处理校准数据以及评测模型精度的方法,并且排除因输入数据预处理不当和欠佳的推理结果后处理等非模型自身造成的精度问题。

02 基础介绍

在 PTQ 模型后量化过程中,通常情况下造成精度损失的主要原因可能有以下几点:


  1. 敏感节点量化问题。模型中的一部分节点对量化比较敏感会引入较大误差;

  2. 节点量化误差累积问题。模型中各个节点的量化误差累积导致模型整体出现较大的校准误差,主要包含:权重量化导致的误差累积、激活量化导致的误差累积以及全量量化导致的误差累积。


针对上述情况,地平线工具链提供的精度 Debug 工具将协助用户自主定位模型量化过程中产生的精度问题,对校准模型进行节点粒度的量化误差分析并快速定位出现精度异常的节点。


PTQ 精度 Debug 工具提供的功能包括:


  • 获取节点量化敏感度;

  • 获取模型累积误差曲线;

  • 获取指定节点的数据分布;

  • 获取指定节点输入数据通道间数据分布箱线图等。

2.1 基本流程

使用精度 Debug 工具进行精度 Debug 的基本流程为:


1.校准模型和校准数据的准备。校准模型在模型转换过程中为常态化保存,无需用户额外操作,为模型转换的输出目录**model_output下的calibrated_model**;校准数据的保存需要用户在 Yaml 文件中的模型参数组中增加如下参数配置(配置后精度 Debug 功能同时开启,但需要用户重新编译一次模型,编译等级可以配置为 O0 以减少编译时间):


model_parameters:  debug_mode: "dump_calibration_data"
复制代码


2.通过**import horizon_nn.debug as dbg**导入 Debug 模块(使用命令行则无需导入),加载校准模型和校准数据;


3.通过精度 Debug 工具提供的 API 或者命令行,对精度损失明显的模型进行分析。(详细的流程以及分析见后文。)

2.2 校准数据

校准数据(calibration_data)在模型转换的校准阶段使用,模型通过对这些数据进行前向推理来获取每个被量化节点的量化参数,量化参数包括:缩放因子(Scale)和阈值(Threshold)。


值得注意的是,此处保存的校准数据是经过颜色空间转换以及预处理之后的 npy 格式的数据,该数据可以通过**np.load()直接送入校准模型进行推理,不同于在模型转换前数据准备阶段使用02_preprocess.sh**脚本生成的校准数据。


使用脚本生成的校准数据是 bgr 颜色空间的数据,在工具链内部会将数据从 bgr 转换到 yuv444/gray 等模型实际输入的格式。


按照上述精度 Debug 流程中提到的操作保存校准数据后,自动在**model_output目录下生成的calibration_data**文件夹结构如下所示:


|-- calibration_data  #校准数据    |---- input1  #文件夹名为模型的输入节点并保存对应的输入数据          |-------- 0.npy          |-------- 1.npy          |-------- ...    |---- input2  #对于多输入模型将保存多个文件夹          |-------- 0.npy          |-------- 1.npy          |-------- ...    |---- ...
复制代码

2.3 校准模型

校准模型(calibrated_model.onnx)是将在校准阶段计算得到的每个被量化节点的量化参数保存在校准节点中,从而得到的模型。


模型转换过程中,模型转换工具将浮点模型进行结构优化后,通过校准数据计算得到每个节点对应的量化参数并将其保存在校准节点中,形成了校准模型。


因此校准模型是一种中间产物,主要特点是模型中包含校准节点,校准节点的节点类型为 HzCalibration。


校准节点主要分为两类:


  1. 激活(activation)校准节点。激活校准节点的输入是当前节点的上一个节点的输出,并基于当前激活校准节点中保存的量化参数对输入数据进行量化和反量化后输出;

  2. 权重(weight)校准节点。权重校准节点的输入是模型的原始浮点权重,并基于当前权重校准节点中保存的量化参数对输入的原始浮点权重进行量化和反量化后输出。


除了上述的校准节点,校准模型中的其他节点称为普通节点(node),普通节点的类型包括:Conv、Mul 和 Add 等。


下图直观地展示了校准节点和普通节点在校准模型中扮演的角色:


03 一键运行

为了方便用户快速获取 PTQ Debug 工具的结果,这里推荐使用一键运行功能,可以一键运行 PTQ Debug 工具中的全部功能。每个功能的详细解读可以参考第三部分功能详解。


根据经验,校准数据的数量对 PTQ Debug 的结果不会有太大影响,为了加速工具运行,建议用户使用一份校准数据即可,配置详见参数介绍。


此外 PTQ 精度 Debug 工具可以使用 GPU 进行计算加速,用户需要进入 GPU Docker 中先卸载预安装的 CPU 版本**horizon-nn,再安装对应版本的horizon-nn-gpu,安装所需的 wheel 文件在路径ddk/package/host/ai_toolchain**下,这样可以保证完整覆盖。


注意:因涉及 ONNXRuntime 版本问题,使用 GPU 加速计算仅作为尝试,如遇报错,建议回退 CPU 进行计算。

3.1 使用方法

1. API 使用

# 导入debug模块import horizon_nn.debug as dbg
dbg.runall(model_or_file='./calibrated_model.onnx', calibrated_data='./calibration_data',)
复制代码


2. 命令行使用

# 例如hmct-debugger runall calibrated_model.onnx calibration_data
复制代码


可通过**hmct-debugger runall -h/--help**查看相关参数。


3. 参数介绍

详细的 API 和命令行参数说明以及配置介绍,阅读原文后至社区搜索《 J5 算法工具链—PTQ Debug 工具使用》。


4. 执行顺序

PTQ Debug 工具各个功能的运行顺序如下:


  1. 分别获取权重校准节点和激活校准节点的量化敏感度;

  2. 根据 step1 的结果,分别取权重校准节点的 top5 和激活校准节点的 top5 绘制其数据分布;

  3. 针对 step2 获取的节点,分别绘制其通道间数据分布的箱线图;

  4. 绘制分别只量化权重和只量化激活的累积误差曲线。


注:当指定**node_type='node'**时,工具会获取 top5 节点,并分别找到每个节点对应的校准节点,并获取其校准节点的数据分布和箱线图。

3.2 Debug 分析

PTQ Debug 分析的流程如下所示:


  1. 首先根据累积误差曲线判断模型量化掉点是由激活量化导致的还是由权重量化导致的,确定是激活量化问题还是权重量化问题。如果激活和权重的单独量化误差不明显,可以判断是两者共同作用导致的。

  2. 对于激活量化问题,根据激活节点的量化敏感度排序确定敏感节点;对于权重量化问题,根据权重节点的量化敏感度排序确定敏感节点;对于激活和权重量化问题,计算普通节点的量化敏感度并排序,确定敏感节点。

  3. 获取量化敏感节点后,进行部分节点以较高精度量化或部分节点不量化测试。在 J5 工具链上,可以对敏感节点设置 Int16 量化,Int16 量化调优工具的使用参考社区文章 PTQ 精度调优手段—设置 Int16 量化,同时也可以让敏感节点**run_on_cpu。XJ3 工具链暂不支持用户手动设置节点以 Int16 精度量化,可以让敏感节点run_on_cpu**。

  4. 同时还可以根据敏感节点数据分布直方图以及通道间数据分布的箱线图来明确优化方向,例如激活敏感节点数据分布不均匀则可以通过优化模型结构来改善,权重敏感节点数据分布不均匀则建议使用 QAT 等。


04 功能详解

接下来将针对 PTQ 精度 Debug 工具提供的功能和对应的 API 或命令行使用方法,逐一进行详解。

4.1 获取累积误差曲线

通过只量化浮点模型中的某一个节点,并依次计算该模型中每个节点与浮点模型中节点输出的误差,获得累积误差曲线。

API 使用方法:

# 导入debug模块import horizon_nn.debug as dbg
dbg.plot_acc_error( save_dir='./', calibrated_data='./calibration_data/', model_or_file='./calibrated_model.onnx', quantize_node=['weight', 'activation'], metric='cosine-similarity', average_mode=False)
复制代码

命令行使用方法:

# 例如hmct-debugger plot-acc-error calibrated_model.onnx calibration_data -q ['weight', 'activation']
复制代码


可通过**hmct-debugger plot-acc-error -h/--help**查看相关参数。

参数介绍:

详细的 API 和命令行参数说明以及配置介绍,阅读原文后到社区搜索《 J5 算法工具链—PTQ Debug 工具使用》。

配置方式与 Debug 结果分析:

  • 指定单节点量化/不量化:

  • 如下配置意为分别只量化**Conv_2Conv_90并保持其他节点不量化,计算累积误差曲线;或分别解除量化Conv_2Conv_90**并保持其他节点量化,计算累积误差曲线。

  • API 配置方式为:

  • quantize_node=['Conv_2', 'Conv_90']/non_quantize_node=['Conv_2', 'Conv_90']

  • 命令行配置方式为:

  • -q ['Conv_2', 'Conv_90']/-nq ['Conv_2', 'Conv_90']

  • 指定多个节点量化/不量化:如下配置意为分别只量化**Conv_2以及只量化Conv_2Conv_90并保持其他节点不量化,计算累积误差曲线;或分别解除量化Conv_2以及解除量化Conv_2Conv_90**并保持其他节点量化,计算累积误差曲线。

  • API 配置方式为:

  • quantize_node=[['Conv_2'], ['Conv_2', 'Conv_90']]/non_quantize_node=[['Conv_2'], ['Conv_2', 'Conv_90']]

  • 命令行配置方式为:

  • -q [['Conv_2'], ['Conv_2', 'Conv_90']]/-nq [['Conv_2'], ['Conv_2', 'Conv_90']]

  • 按照量化敏感度排序选择节点量化/不量化:


注:**non_quantize_node**设置不量化的节点等价于让其运行在 CPU 上。


测试部分量化精度时,可以按照量化敏感度排序进行多组量化策略的精度对比,此时可以参考以下用法:


# 导入debug模块import horizon_nn.debug as dbg
# 首先使用量化敏感度排序函数获取模型中节点的量化敏感度排序,或者可以直接加载保存的量化敏感度node_message = dbg.get_sensitivity_of_nodes( model_or_file='./calibrated_model.onnx', metrics='cosine-similarity', calibrated_data='./calibration_data/', output_node=None, node_type='node', verbose=False, interested_nodes=None)
# node_message为字典类型,其key值为节点名称nodes = list(node_message.keys())
# 通过nodes来指定不量化节点,可以方便使用dbg.plot_acc_error( save_dir='./', calibrated_data='./calibration_data/', model_or_file='./calibrated_model.onnx', non_quantize_node=[nodes[:1],nodes[:2]], #分别解除量化敏感度排序靠前的节点 metric='cosine-similarity', average_mode=True)
复制代码

可视化结果:

针对部分量化精度分析的累积误差曲线图,在分析时,用户应当更关注模型输出位置(曲线尾部)的量化相似度,例如下图,qmodel_1 模型的精度要好于 qmodel_0:



  • 指定激活节点/权重节点分别量化:


如下配置意为分别只量化权重节点和激活节点并保持其他节点不量化,计算累积误差曲线。


API 配置方式为:**quantize_node=['weight', 'activation']**


命令行配置方式为:**-q ['weight', 'activation']**


可视化结果:通过下图可以分析出激活节点量化引入了大量的量化累积误差,而权重节点量化对模型精度无负面影响。



4.2 获取节点量化敏感度

API 使用方法:

# 导入debug模块import horizon_nn.debug as dbg# 导入log日志模块import logging
# 若verbose=True时,需要先设置log level为INFOlogging.getLogger().setLevel(logging.INFO)# 获取节点量化敏感度node_message = dbg.get_sensitivity_of_nodes( model_or_file='./calibrated_model.onnx', metrics=['cosine-similarity', 'mse'], calibrated_data='./calibration_data/', output_node=None, node_type='node', data_num=None, verbose=True, interested_nodes=None)
复制代码

命令行使用方法:

# 例如hmct-debugger get-sensitivity-of-nodes calibrated_model.onnx calibration_data -m ['cosine-similarity','mse']
复制代码


可通过**hmct-debugger get-sensitivity-of-nodes -h/--help**查看相关参数

参数介绍:

详细的 API 和命令行参数说明以及配置介绍,见 J5 算法工具链—PTQ Debug 工具使用。

Debug 结果分析:

首先您通过**node_type设置需要计算敏感度的节点类型,然后工具获取校准模型中所有符合node_type的节点,并获取这些节点的量化敏感度。当verbose**设置为 True 时,工具会将节点量化敏感度进行排序后打印在终端,排序越靠前,说明该节点量化引入的量化误差越大。


同时对于不同的**node_type,工具会显示不同的节点量化敏感度信息,设置verbose=True**,结果如下:


# node_type='node'=================node sensitivity=================node                  cosine-similarity  mse---------------------------------------------------Conv_60               0.77795            68.02103...
# node_type='weight'# weight:权重校准节点名# node:权重校准节点对应的普通节点名,即权重校准节点的输出为其输入====================================node sensitivity====================================weight node cosine-similarity mse-----------------------------------------------------------------------------------------471_HzCalibration Conv_2 0.99978 0.07519...
# node_type='activation'# activation:激活校准节点名# node:激活校准节点后的普通节点,即激活校准节点的输出为其输入# threshold:校准阈值,若有多个阈值则取最大值# bit:量化比特===================================node sensitivity===================================activation node threshold bit cosine-similarity mse---------------------------------------------------------------------------------------406_HzCalibration Conv_60 0.91501 8 0.77851 67.82422...
复制代码


此外,调用 API 时,API 还将以字典格式(Key 为节点名称,Value 为节点的量化敏感度信息)返回节点量化敏感度信息,以供用户后续使用,返回格式如下:


node_message:{'Conv_60': {'cosine-similarity': 0.77795, 'mse': 68.02103}, 'Conv_48': {'cosine-similarity': 0.78428, 'mse': 64.36318}, ...}
复制代码

量化敏感度保存与加载:

由于模型的量化敏感度在 PTQ 精度 Debug 过程中扮演着重要的角色,累积误差曲线的计算分析中会用到,因此建议用户及时保存模型量化敏感度,直接加载以节省时间,可以参考如下代码进行量化敏感度的保存与加载:


注意:命令行暂时无法手动保存量化敏感度。


# node_message保存为text文件filename = open('sensitivity_of_nodes.txt', 'w')for node, sensitivity in node_message.items():    filename.write(str(node) + ':' + str(sensitivity))    filename.write('\n')filename.close()# 加载保存的text文件node_message = {}filename = open('sensitivity_of_nodes.txt')for line in filename:    line_split = line.split(':', 1) #按照第一个出现的':'进行分割    node_message[line_split[0]] = line_split[1] #{str: str,...}filename.close()
# node_message保存为json文件(推荐)import jsonsave_json = json.dumps(node_message, sort_keys=False, indent=4, separators=(',', ': '))filename = open('sensitivity_of_nodes.json', 'w')filename.write(save_json)filename.close()# 加载保存的json文件filename = open('sensitivity_of_nodes.json', 'r')node_message = json.load(filename)filename.close()
复制代码

4.3 获取节点数据分布

指定节点,分别获取该节点在浮点模型和校准模型中的输出,得到输出数据分布。另外,将两个输出结果做差,获取两个输出之间的误差分布。

API 使用方法:

# 导入debug模块import horizon_nn.debug as dbg
dbg.plot_distribution( save_dir='./', model_or_file='./calibrated_model.onnx', calibrated_data='./calibration_data', nodes_list=['317_HzCalibration', #激活节点 '471_HzCalibration', #权重节点 'Conv_2']) #普通节点
复制代码

命令行使用方法:

hmct-debugger plot-distribution calibrated_model.onnx calibration_data -n ['317_HzCalibration','471_HzCalibration','Conv_2']
复制代码


可通过**hmct-debugger plot-distribution -h/--help**查看相关参数。

参数介绍:

详细的 API 和命令行参数说明以及配置介绍,阅读原文后至社区搜索《 J5 算法工具链—PTQ Debug 工具使用》。

Debug 结果分析:

可视化结果:



注:蓝色三角表示数据绝对值的最大值;红色虚线表示最大的校准阈值。


数据分布结果的分析标准为是否满足对量化友好的正态分布,只要分布中有一个很明显的单峰就认为满足正态分布,不需要严格满足正态分布公式,例如可视化结果中的第一张图,输入数据分布相对比较集中,对于激活节点而言输入数据分布是友好的;同理第二张图校准前后权重节点的数据分布也是友好的。


第三张图是普通节点校准前后的输出数据分布以及量化误差分布,也符合正态分布,当节点输出不符合正态分布时,用户可以尝试在模型中增加 BatchNorm 层并重新训练,然后再进行 PTQ 量化。

4.4 获取节点通道间数据分布

绘制指定校准节点输入数据通道间数据分布的箱线图。

API 使用方法:

# 导入debug模块import horizon_nn.debug as dbg
dbg.get_channelwise_data_distribution( save_dir='./', model_or_file='./calibrated_model.onnx', calibrated_data='./calibration_data', nodes_list=['317_HzCalibration'], axis=None)
复制代码

命令行使用方法:

hmct-debugger get-channelwise-data-distribution calibrated_model.onnx calibration_data -n ['317_HzCalibration']
复制代码


可通过**hmct-debugger get-channelwise-data-distribution -h/--help**查看相关参数。

参数介绍:

详细的 API 和命令行参数说明以及配置介绍,见J5算法工具链—PTQ Debug工具使用

Debug 结果分析:

可视化结果:



注:横坐标表示节点输入数据的通道数;纵坐标表示每个 channel 的数据分布范围,其中红色实线表示该 channel 数据的中位数,蓝色虚线表示均值。


通过箱线图可以直观地了解当前数据每个通道之间的数据分布情况。通过观察箱线图纵坐标确认数据分布范围,当某一个通道有异常值时(即数值极大或极小,例如上图中第 21 通道),认为当前节点采用 per-tensor 量化会有较大的量化风险,需要尝试使用 per-channel 量化去减少量化误差。箱线图的阅读可以参考下图:


05 实例参考

我们还提供了三篇社区文章,来展示如何在具体模型上使用 PTQ Debug 工具进行分析:


  1. 【PTQ 精度 debug 示例】mnasnet_1.0_96 精度问题分析

  2. 【PTQ 精度 debug 示例】repvgg_b2_deploy 精度问题分析

  3. 【PTQ 精度 debug 示例】MobileVit_s 精度问题分析


用户头像

还未添加个人签名 2021-03-11 加入

还未添加个人简介

评论

发布
暂无评论
PTQ 精度 Debug 工具_自动驾驶_地平线开发者_InfoQ写作社区