小模型在昇腾NPU上的推理部署:【om精度问题定位手段】
在将训练好的模型部署至昇腾NPU进行推理时,精度对齐是核心挑战。模型的精度损失往往不是单一原因造成,而是贯穿整个跨架构迁移与转换链路中多种因素共同作用的结果。这是一个典型的“查异排错”过程:由于AI训练通常基于GPU/CPU环境,而推理部署在专用的NPU硬件上,二者在硬件架构、软件栈、计算范式上存在系统性差异,任何环节的细微偏差都可能被链路放大,最终导致输出结果与预期不符。导致精度问题的根源错综复
作者:昇腾实战派
背景概述
在将训练好的模型部署至昇腾NPU进行推理时,精度对齐是核心挑战。模型的精度损失往往不是单一原因造成,而是贯穿整个跨架构迁移与转换链路中多种因素共同作用的结果。这是一个典型的“查异排错”过程:由于AI训练通常基于GPU/CPU环境,而推理部署在专用的NPU硬件上,二者在硬件架构、软件栈、计算范式上存在系统性差异,任何环节的细微偏差都可能被链路放大,最终导致输出结果与预期不符。
导致精度问题的根源错综复杂,
- 硬件计算差异:这是根本差异。昇腾NPU采用自有达芬奇架构,其指令集、计算单元(如Cube和Vector Core)与GPU的CUDA Core在数值计算方式(如累加顺序、舍入模式)和原生支持精度(如对FP16的优化)上存在底层差异,直接改变了计算的微观过程。
- 模型转换损失:从训练框架(PyTorch/TensorFlow)到中间格式(ONNX),再经由华为ATC工具编译为昇腾专属的离线模型(OM),构成了一个完整的转换链。每一次序列化/反序列化、算子映射或格式转换都可能引入微小的数值误差,误差在链路上累积。
- 编译优化影响:ATC编译器为优化性能,会实施算子融合、常数折叠、内存复用等图优化策略。这些优化可能改变计算图的执行顺序或中间结果的精度,虽然功能等效,但可能导致数值结果与原始模型产生微小偏差。
- 环境与配置差异:软件版本(驱动、固件、CANN版本、框架版本)、依赖库、甚至系统环境变量都可能影响数值计算的确定性,构成难以察觉的干扰因素。
因此,定位精度问题是一个系统性工程,需要沿着“原始模型 → ONNX → OM → NPU运行”的完整技术链路进行分层对比和全面排查。
本文将重点聚焦于“ONNX → OM”这一特定转换环节的精度问题排查与解决。 我们在此设定一个明确的前提:假设您的原始模型(如PyTorch、TensorFlow模型)已成功、无损地导出为ONNX模型,且在ONNX运行时(如ONNX Runtime)上验证通过,其精度与原始训练模型对齐。基于此,当OM模型在昇腾NPU上的推理结果与ONNX基准相比出现偏差时,即可将问题定界在ATC模型编译、图优化、或NPU算子实现等后续阶段,从而进行高效、精准的排查。
一、OM精度问题初步定位
1.1 ONNX模型推理与输出保存
def save_onnx_output(onnx_model_path, input_data, output_path="onnx_output.npy"):
"""
执行ONNX模型推理并保存原始输出张量
参数说明:
- onnx_model_path: ONNX模型文件路径(需确保环境已安装onnxruntime)
- input_data: 预处理后的输入数据(需与模型预期输入格式一致)
- output_path: 输出张量保存路径(支持.npy格式)
返回:
- 首输出层张量(适用于多输出模型的主输出提取)
"""
import onnxruntime as ort
import numpy as np
ort_session = ort.InferenceSession(onnx_model_path)
input_name = ort_session.get_inputs()[0].name
onnx_outputs = ort_session.run(None, {input_name: input_data})
np.save(output_path, onnx_outputs[0])
print(f"[ONNX] 推理完成,输出张量已保存至: {output_path}")
return onnx_outputs[0]
1.2 OM模型推理与输出保存
def save_om_output(om_model_path, device_id, input_data, output_path="om_output.npy"):
"""
执行昇腾OM模型推理并保存输出张量
参数说明:
- om_model_path: OM模型文件路径(需确保已通过ATC工具转换)
- device_id: 指定运行的NPU设备ID
- input_data: 与ONNX推理完全一致的输入数据(确保精度可比性)
- output_path: 输出张量保存路径
返回:
- 首输出层张量(与ONNX输出结构对齐)
"""
import numpy as np
from ais_bench.infer_interface import InferSession # 依赖CANN软件包
om_session = InferSession(device_id, om_model_path)
om_outputs = om_session.infer([input_data])
np.save(output_path, om_outputs[0])
print(f"[OM] 推理完成,输出张量已保存至: {output_path}")
return om_outputs[0]
1.3 输出精度对比分析
def compare_outputs(onnx_output, om_output):
"""
量化对比ONNX与OM输出的精度差异
参数说明:
- onnx_output: ONNX模型输出张量
- om_output: OM模型输出张量
- threshold: 误差容忍阈值(默认1e-6)
返回:
- 余弦相似度与最大绝对误差的字典结果
"""
import numpy as np
from scipy import spatial
onnx_flat = onnx_output.reshape(-1).astype(np.float32)
om_flat = om_output.reshape(-1).astype(np.float32)
# 计算余弦相似度(方向一致性度量)
cosine_sim = 1 - spatial.distance.cosine(onnx_flat, om_flat)
# 计算最大绝对误差(数值偏差极值)
max_abs_error = np.max(np.abs(onnx_output - om_output))
return {"cosine_similarity": cosine_sim, "max_absolute_error": max_abs_error}
1.4 完整流程
def precision_validation_pipeline(onnx_path, om_path, input_path, device_id=0):
# 数据预处理
input_data = preprocess(input_path) #待实现
onnx_out = save_onnx_output(onnx_path, input_data)
om_out = save_om_output(om_path, device_id, input_data)
# 步骤2:精度对比分析
metrics = compare_outputs(onnx_out, om_out)
print(metrics)
1.5 精度指标衡量说明
余弦相似度(Cosine Similarity)
- 物理意义:反映两个高维向量在方向上的对齐程度,忽略绝对数值大小
- 数值范围:[-1, 1],1表示完全同向,0表示正交,-1表示完全反向,>=0.995 精度优秀,可认为基本对齐
最大绝对误差(Max Absolute Error)
- 物理意义:找出所有输出元素中偏差最大的那个数值
- 数值范围:[0, +∞),找出所有输出元素中偏差的最大值,越接近0越好。
二、OM精度问题深度定位
2.1 原始OM精度问题的初步排查流程
当发现由ONNX直接转换得到的原始OM模型存在精度问题时,建议按照以下系统化流程进行排查。首先从ATC编译参数入手,这是最快速有效的起点。
**ATC精度相关关键参数排查清单 **
–fusion_switch_file:融合规则(包括图融合和UB融合)开关配置文件路径以及文件名,修改融合规则,可能引起精度及性能变化。
–precision_mode_v2:设置整个网络模型的精度模式,若精度异常时,可尝试整网设置为FP32计算,从而判断是否为计算精度引起的误差。
–op_precision_mode:指定算子内部处理时的精度模式
–modify_mixlist:在内置优化策略基础上进行调整,自行指定哪些算子允许降精度,哪些算子不允许降精度。
–optypelist_for_implmode:设置optype列表中算子的实现模式,算子实现模式包括high_precision、high_performance两种
–customize_dtypes:模型编译时自定义某个或某些算子的计算精度。
–keep_dtype:保持原始网络模型编译时个别算子的计算精度不变。
建议尝试设置--precision_mode_v2=origin,其他参数不设置。
2.2 常见精度问题
在定位精度问题时,可以根据现象判断是否为下述精度问题,定位思路和解决方案与算子精度问题略有不同。
2.2.1 数据溢出问题
现象: 若模型在FP16场景下出现精度问题,而在FP32场景下正常,则可能是数据溢出或特定算子在FP16下存在精度问题。
方案:
- 数据溢出导致的精度问题:
- 常量溢出:常见于Attention Mask等计算中,可通过修改网络图将常量的取值范围限制在FP16可表示范围内。
- 计算结果溢出:若中间计算结果超出FP16表示范围,可使用ATC参数
--keep_dtype或--customize_dtypes将相关算子指定为FP32精度执行,避免溢出。
- FP16下算子精度问题:
若确认是某些算子在FP16场景下存在精度缺陷,建议联系华为工程师确认;临时规避方案是使用--keep_dtype或--customize_dtypes参数,将这些算子强制指定为FP32精度运行。
2.2.2 内存踩踏问题
现象: 模型推理结果与预期结果差距较大,且数值无规律,可能是内存踩踏导致的精度问题。
判断: 可尝试在ATC命令中添加参数 --buffer_optimize=off_optimize 关闭内存复用,或设置 --disable_reuse_memory=1 关闭数据缓存优化。若关闭后推理结果恢复正常,则说明精度问题由内存踩踏引起。
方案: 若判定为内存踩踏问题,建议联系华为工程师进一步分析内存复用是否存在异常,并获取针对性修复建议。
三、算子级精度问题定位
算子精度问题的标准处理步骤如下:
- 数据对比:dump数据,对比ONNX与OM模型的算子输入输出,定位第一个出现精度问题的算子。
- 单算子验证:构造问题算子的单算子模型,使用相同输入验证输出精度。
- 结果分析:
- 若单算子精度正常,则可能是算子输入本身存在问题,或由累积误差导致精度下降,需前向二分定位关键算子。
- 若单算子精度异常,则基本确定该算子存在问题。
- 特殊处理:如果ONNX模型包含无法推理的自定义算子,可dump在线推理结果作为标杆进行对比,或修改模型图结构(如删除自定义算子)后重复以上步骤。
3.1 使用MSIT精度比对工具
推荐工具:msit debug compare(提供一键式全流程精度比对)
安装方法:
git clone https://gitee.com/ascend/msit.git
cd msit/msit
chmod +x install.sh
./install.sh --compare
使用命令:
msit debug compare -gm ${onnx_model_path} \
-om ${om_model_path} \
-i ${input_save_path} \
-o ${output_save_path}
关键参数说明:
-gm/--golden-model:标杆模型路径(支持TF/ONNX/Caffe/OM)。-om/--om-model:待检测的OM模型路径。-i/--input:输入数据路径(支持NPY/BIN格式)。-o/--output:结果输出路径。- 更多参数参考工具使用说明
3.2 精度比对结果分析
输出目录结构:精度对比结果保存在指定的输出路径下,文件夹名为时间戳
├-- dump_data
│ ├-- npu # npu dump 数据目录
│ │ ├-- {timestamp} # 模型所有npu dump的算子输出,dump为False情况下没有该目录
│ │ │ └-- 0 # Device 设备 ID 号
│ │ │ └-- {om_model_name} # 模型名称
│ │ │ └-- 1 # 模型 ID 号
│ │ │ ├-- 0 # 针对每个Task ID执行的次数维护一个序号,从0开始计数,该Task每dump一次数据,序号递增1
│ │ │ │ ├-- Add.8.5.1682067845380164
│ │ │ │ ├-- ...
│ │ │ │ └-- Transpose.4.1682148295048447
│ │ │ └-- 1
│ │ │ ├-- Add.11.4.1682148323212422
│ │ │ ├-- ...
│ │ │ └-- Transpose.4.1682148327390978
│ │ ├-- {time_stamp}
│ │ │ ├-- input_0_0.bin
│ │ │ └-- input_0_0.npy
│ │ └-- {time_stamp}_summary.json
│ └-- {onnx or tf or caffe} # 原模型 dump 数据存放路径,onnx / tf / caffe 分别对应 ONNX / Tensorflow / Caffe 模型
│ ├-- Add_100.0.1682148256368588.npy
│ ├-- ...
│ └-- Where_22.0.1682148253575249.npy
├-- input
│ └-- input_0.bin # 随机输入数据,若指定了输入数据,则该文件不存在
├-- model
│ ├-- {om_model_name}.json
│ └-- new_{om_model_name}.onnx # 把每个算子作为输出节点后新生成的 onnx 模型
├-- result_{timestamp}.csv # 比对结果文件
└-- tmp # 如果 -m 模型为 Tensorflow pb 文件, tfdbg 相关的临时目录
**分析对比结果:**
- 比对结果保存在result_{timestamp}.csv,每行为一个算子的输入或输出,包含在ONNX和OM的名称、shape、dtype、误差比对结果等信息。该工具提供多种误差精算结果,具体信息可查看比对结果文件各字段含义说明。
- dump数据保存在dump_data下,包含ONNX和OM的数据。OM的dump数据需要解析,包含输入和输出,ONNX的dump数据不需要解析,仅包含输出数据。
- 对比后工具会在屏幕上输出分析结果,输出需要重点关注的各误差对比算法中、首个差距不在阈值范围内的算子。
2023-04-19 13:54:10(1005)-[INFO]Operators may lead to inaccuracy:
| Monitor | Value | Index | OpType | NPUDump | GroundTruth |
|--------------------------:|-------:|------:|-------:|--------:|------------:|
| CosineSimilarity | 0.6722 | 214 | Mul | Mul_6 | Mul_6 |
| RelativeEuclideanDistance | 1 | 214 | Mul | Mul_6 | Mul_6 |
比如上面的结果中,表格第一行表示:第一次出现CosineSimilarity(余弦相识度)<0.99的算子,是Index为214、OpType为Mul、NPU侧算子名为Mul_6的算子,进行余弦相似度算法比对出来的结果,取值范围为[-1,1],比对的结果如果越接近1,表示两者的值越相近,越接近-1意味着两者的值越相反。
问题算子的主要特征:
ONNX 和OM的输入相似度很高,但输出相似度降低较多,可参考工具在屏幕上给出的分析结果,结合result_{timestamp}.csv中的对比结果来定位问题算子。
- 一般工具给出的算子的余弦相似度较低,该算子的输出有明显问题,精度问题一定发生在该算子或前面的算子*
打开对比结果,根据精度指标筛选可能的问题算子,精度指标一般为>0.999或0.99。某个算子精度下降明显、或者之后的算子精度未恢复到较高精度标准,为可疑算子。 - transdata算子只对数据格式和shape做处理,不做计算,一般不会引入精度问题,且由于数据格式不同,可能计算相似度时数据未对齐,若前后的算子精度正常,则忽略该算子。
- 排序类算子的输出顺序与输入相关,输入的细微误差可能会导致排序差别很大,精度指标不能直接判断是否有精度问题。如果后续计算的结果精度回复正常,可认为精度正常。如果后续计算结果未恢复正常,则需要读取排序算子的输入输出,在CPU侧重现排序结果,对比结果来验证是否存在精度问题。有些算子可能与CPU的排序方法不同,需要对比内容来验证精度,ex. TopK算子的输出按从大到小排序和按输入顺序排序会导致输出不同,但实际结果可能相同。
分析dump数据:
选取可疑算子,读取OM输入输出,并与ONNX的输入输出对比,OM的dump数据需要解析为NPY文件
#OM的dump数据需要解析为NPY文件
python3 /usr/local/Ascend/ascend-toolkit/latest/tools/operator_cmp/compare/msaccucmp.py convert \
-d /path/to/result/${timestamp}/dump_data/1/${model_name}/1/0/ \
-out /path/to/npy
参数说明:
- PY文件在CANN包的安装路径下,请根据实际安装路径修改。
- -d:待解析的文件路径,为包含dump数据的文件夹路径。
- -out:解析后文件的保存路径,文件夹必须为已存在的路径。
ONNX的dump数据仅包含算子输出,需要根据算子结构查找前序算子,获取前序算子的输出作为算子输入。ONNX模型可通过netron打开查看, 也可通过改图工具读取模型,使用get_prev_node方法查找算子结构。
3.3 验证单算子精度
验证单算子精度时,为保证算子输入完全相同,需要使用单算子模型进行推理。单算子模型可通过构造或者截取得到。如果OM中为融合算子,则ONNX中需截取/构造融合算子对应的多个小算子。推荐使用msit debug surgeon/auto_optimizer改图工具。
使用工具截取单算子
msit debug surgeon extract --input ${onnx_model} --output-file ${op_onnx_model} --start-node-names ${input_names} --end-node-names ${output_names}
参数说明:
- -in/–input:【必选】输入ONNX待优化模型,必须为.onnx文件
- -of/–output-file:【必选】切分后的子图ONNX模型名称,用户自定义,必须为.onnx文件
- -snn/–start-node-names:起始算子名称。可指定多个输入算子名称,节点之间使用","分隔,名称与ONNX中算子输入名称相同,不需包含权重名称
- -enn/–end-node-names:结束算子名称。可指定多个输出算子名称,节点之间使用","分隔,名称与ONNX中算子输出名称相同
使用改图工具构造单算子
from auto_optimizer import OnnxGraph
# 创建单算子ONNX
op_model = OnnxGraph('conv.onnx')
# 读取原ONNX模型
model = OnnxGraph.parse('resnet50.onnx')
# 找到问题算子
node = model['Conv_1']
# 复制算子至单算子模型
op_model.add_node(node.name, node.op_type, inputs=node.inputs, outputs=node.outputs, attrs=node.attrs)
init = [node.name for node in model.initializers]
for inp in node.inputs:
if inp in init:
op_model.add_initializer(inp, model[inp].value)
else:
op_model.add_inputs(inp, dtype='float32', shape=[]) # shape若为空,则转OM时需指定input_shape
for out in node.outputs:
op_model.add_output(out, dtype='float32', shape=[])
# 保存单算子模型
op_model.save('conv.onnx')
使用OM的算子输入作为ONNX的单算子模型输入,得到ONNX的单算子推理结果。
- 如果单算子的ONNX输出结果与OM输出结果不满足算子精度标准,则联系华为工程师,确认算子精度问题。
- 如果ONNX单算子的结果与OM的输出满足双千分之一的精度标准,都与ONNX整网中该算子输出结果差距较大,则说明是输入的数据误差在这个算子位置被放大了,需要继续前向定位。
3.4 累积误差问题定位
定位思路:在某些算子之后,后续计算精度正常也会得到错误结果,这些算子即为累积误差的问题算子。
定位方法:在模型输入到出现明显精度下降的区间中,使用二分法定位累积误差的问题算子。
- 将ONNX模型截断为两部分,第一个ONNX模型转为OM,并使用OM做第一步推理,OM的输出作为输入使用ONNX做第二步推理。因为ONNX模型为精度的标杆,可认为第二步推理的精度完全正确。
- 如果ONNX的最终输出有问题,则说明OM的输出已有累积误差问题,OM中包含问题算子,截断位置变为输入与当前截断位置的中间;如果ONNX输出结果正常,说明OM的输出正常,问题算子在ONNX中,截断位置变为当前截断位置与精度下降位置的中间。
- 重复步骤1、2直到定位出最小问题区间,确认单算子精度,如果单算子精度正常,则说明数据对算子精度比较敏感,可使用FP32或不使用fusion pass融合算子等方法提高算子精度,使用方法参考ONNX转OM。
推荐使用msit debug surgeon/auto_optimizer改图工具截取模型:
命令行截取
msit debug surgeon extract --input {onnx_model} --output-file {op_onnx_model} --start-node-names {input_names} --end-node-names {output_names}
参数说明
- -in/–input:【必选】输入ONNX待优化模型,必须为.onnx文件
- -of/–output-file:【必选】切分后的子图ONNX模型名称,用户自定义,必须为.onnx文件
- -snn/–start-node-names:起始算子名称。可指定多个输入算子名称,节点之间使用","分隔,名称与ONNX中算子输入名称相同,不需包含权重名称
- -enn/–end-node-names:结束算子名称。可指定多个输出算子名称,节点之间使用","分隔,名称与ONNX中算子输出名称相同
使用改图工具构造单算子
from auto_optimizer import OnnxGraph
nodes = ['Conv_10'] # 截断处的算子名,从该算子的输出位置截断
# 截取第一个ONNX
model = OnnxGraph.parse('resnet50.onnx')
for out in model.outputs:
model.remove(out.name)
for node in nodes:
for out in model[node].outputs:
model.add_output(out.name, dtype='float32', shape=[])
next_nodes = model.get_next_nodes(out.name)
for next_node in nodes:
model.remove(next_node.name, {})
model.remove_unused_nodes()
model.save('resnet50_1.onnx')
# 截取第二个ONNX
model = OnnxGraph.parse('resnet50.onnx')
for inp in model.inputs:
model.remove(inp.name)
for node in nodes:
for out in model[node].outputs:
model.add_input(out.name, dtype='float32', shape=[])
model.remove(node, {})
model.remove_unused_nodes()
model.save('resnet50_1.onnx')
四、性能优化引入精度问题
如果原始OM模型无精度问题,使用性能优化手段后出现精度下降问题,可参考对应的使用指导尝试修复精度问题:
- AIPP:设置是否与图像预处理一致,注意RGB顺序
- 替换子图、算子:确认改图前后模型是否等价,如果使用了融合算子,请确认融合算子的单算子精度是否符合要求。
- 量化:使用真实数据进行校准;量化配置中跳过部分精度影响较大的算子,AMCT中使用参数"skip_fusion_layers"、ModelSlim使用参数"disable_names"。
更多推荐



所有评论(0)