作者:昇腾实战派

背景概述

在将训练好的模型部署至昇腾NPU进行推理时,精度对齐是核心挑战。模型的精度损失往往不是单一原因造成,而是贯穿整个跨架构迁移与转换链路中多种因素共同作用的结果。这是一个典型的“查异排错”过程:由于AI训练通常基于GPU/CPU环境,而推理部署在专用的NPU硬件上,二者在硬件架构、软件栈、计算范式上存在系统性差异,任何环节的细微偏差都可能被链路放大,最终导致输出结果与预期不符。

导致精度问题的根源错综复杂,

  1. 硬件计算差异​:这是根本差异。昇腾NPU采用自有达芬奇架构,其指令集、计算单元(如Cube和Vector Core)与GPU的CUDA Core在数值计算方式(如累加顺序、舍入模式)和原生支持精度(如对FP16的优化)上存在底层差异,直接改变了计算的微观过程。
  2. 模型转换损失​:从训练框架(PyTorch/TensorFlow)到中间格式(ONNX),再经由华为ATC工具编译为昇腾专属的离线模型(OM),构成了一个完整的转换链。每一次序列化/反序列化、算子映射或格式转换都可能引入微小的数值误差,误差在链路上累积。
  3. 编译优化影响​:ATC编译器为优化性能,会实施算子融合、常数折叠、内存复用等图优化策略。这些优化可能改变计算图的执行顺序或中间结果的精度,虽然功能等效,但可能导致数值结果与原始模型产生微小偏差。
  4. 环境与配置差异​:软件版本(驱动、固件、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下存在精度问题。

方案:

  1. 数据溢出导致的精度问题:
    • 常量溢出​:常见于Attention Mask等计算中,可通过修改网络图将常量的取值范围限制在FP16可表示范围内。
    • 计算结果溢出​:若中间计算结果超出FP16表示范围,可使用ATC参数 --keep_dtype--customize_dtypes 将相关算子指定为FP32精度执行,避免溢出。
  2. FP16下算子精度问题:
    若确认是某些算子在FP16场景下存在精度缺陷,建议联系华为工程师确认;临时规避方案是使用 --keep_dtype--customize_dtypes 参数,将这些算子强制指定为FP32精度运行。

2.2.2 内存踩踏问题

现象: 模型推理结果与预期结果差距较大,且数值无规律,可能是内存踩踏导致的精度问题。

判断: 可尝试在ATC命令中添加参数 --buffer_optimize=off_optimize 关闭内存复用,或设置 --disable_reuse_memory=1 关闭数据缓存优化。若关闭后推理结果恢复正常,则说明精度问题由内存踩踏引起。

方案: 若判定为内存踩踏问题,建议联系华为工程师进一步分析内存复用是否存在异常,并获取针对性修复建议。

三、算子级精度问题定位

算子精度问题的标准处理步骤如下:

  1. 数据对比​:dump数据,对比ONNX与OM模型的算子输入输出,定位第一个出现精度问题的算子。
  2. 单算子验证​:构造问题算子的单算子模型,使用相同输入验证输出精度。
  3. 结果分析​:
    • 若单算子精度正常,则可能是算子输入本身存在问题,或由累积误差导致精度下降,需前向二分定位关键算子。
    • 若单算子精度异常,则基本确定该算子存在问题。
  4. 特殊处理​:如果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 累积误差问题定位

​定位思路:​在某些算子之后,后续计算精度正常也会得到错误结果,这些算子即为累积误差的问题算子。
​定位方法:​在模型输入到出现明显精度下降的区间中,使用二分法定位累积误差的问题算子。

  1. 将ONNX模型截断为两部分,第一个ONNX模型转为OM,并使用OM做第一步推理,OM的输出作为输入使用ONNX做第二步推理。因为ONNX模型为精度的标杆,可认为第二步推理的精度完全正确。
  2. 如果ONNX的最终输出有问题,则说明OM的输出已有累积误差问题,OM中包含问题算子,截断位置变为输入与当前截断位置的中间;如果ONNX输出结果正常,说明OM的输出正常,问题算子在ONNX中,截断位置变为当前截断位置与精度下降位置的中间。
  3. 重复步骤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"。
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐