昇腾 NPU 上手:ResNet-50 从 ONNX 到 OM 的完整踩坑记录

昇腾 NPU 上手:ResNet-50 从 ONNX 到 OM 的完整踩坑记录

环境:Ascend 910B2C + CANN 8.5.1

目标:把 resnet_50.onnx 转成 resnet_50.om,在 NPU 上跑起来,并和转换前的 ONNX 结果做对比。

最近在昇腾机器上跑了一次经典的 ResNet-50 转换流程。看起来只是「ONNX → OM → 推理」三步,实际踩了不少坑:Python 依赖、opset 版本、精度对比方式都不简单。本文把完整过程记下来,方便以后自己复用,也希望帮到同样入门的同学。


1. 最终产物

文件 说明
resnet_50.onnx ONNX Model Zoo 的 ResNet-50 v2(约 98MB)
resnet_50.om ATC 转换后的昇腾离线模型(约 50MB)
run_resnet50_om.py ACL Python 推理脚本

模型 I/O:

  • 输入datafloat32[1, 3, 224, 224](NCHW)
  • 输出[1, 1000](ImageNet 1000 类 logits)

2. 环境确认

先确认 NPU 和 CANN 是否正常:

bash 复制代码
npu-smi info
which atc
source /usr/local/Ascend/ascend-toolkit/set_env.sh

本次机器信息:

  • NPU:910B2C
  • CANN:8.5.1
  • 转换时 soc_versionAscend910B2C

3. 下载 ONNX 模型

ONNX Model Zoo 下载 ResNet-50 v2:

bash 复制代码
cd /home/compiler_projects

curl -L -o resnet_50.onnx \
  "https://media.githubusercontent.com/media/onnx/models/main/validated/vision/classification/resnet/model/resnet50-v2-7.onnx"

这个模型有两个小特点,后面转换会碰到:

  1. opset = 7(比较老)
  2. 权重被写在 graph.input (老式 ONNX 写法),真正的网络输入只有 data

4. ONNX → OM(ATC)

4.1 标准命令

bash 复制代码
source /usr/local/Ascend/ascend-toolkit/set_env.sh

atc --model=resnet_50.onnx \
    --framework=5 \
    --output=resnet_50 \
    --soc_version=Ascend910B2C \
    --input_format=NCHW \
    --input_shape="data:1,3,224,224" \
    --log=error

参数含义:

  • --framework=5:ONNX
  • --soc_version:必须和芯片匹配
  • --input_shape:静态 shape;这里把动态 batch 固定成 1

成功后会生成 resnet_50.om

4.2 坑 1:No module named 'numpy'

第一次跑 ATC 直接失败:

text 复制代码
Environment_Error_Import_Python_Module_Failed(EC0010):
ModuleNotFoundError: No module named 'numpy'

原因 :ATC 编译算子时会走 TBE(Python),它用的是 /usr/bin/python3,不是你日常装包的那个 Python。

即使 /usr/local/python3.11.xxx 里有 numpy,ATC 也看不到。

解决:给 ATC 实际使用的 Python 装依赖:

bash 复制代码
/usr/bin/python3 -m ensurepip --upgrade
/usr/bin/python3 -m pip install 'numpy<2' decorator attrs cloudpickle \
    ml-dtypes psutil scipy tornado sympy

装完 numpy 后,往往还会继续缺 decoratorscipy 等,建议一次性装齐。

4.3 坑 2:所有算子都报 No operator plugin

依赖装好后,又出现:

text 复制代码
Not_Supported_Operator(E13010):
No operator plugin is registered for Op: xxx, optype: ai.onnx::7::Conv

ConvRelu 都报不支持,看起来很吓人。查日志后发现:

  • ONNX 插件其实加载成功了
  • 但 CANN 8.5.1 注册的是 ai.onnx::8 及以上
  • 当前模型是 ai.onnx::7

所以不是「昇腾不支持卷积」,而是 opset 太旧,对不上注册表

4.4 处理方式:清洗模型 + 提升 opset

先把权重从 graph.input 里摘掉,只保留真正输入 data,并把 batch 固定为 1;再把 opset 提升到 11:

python 复制代码
import onnx

m = onnx.load("resnet_50.onnx")

# 1) 权重 initializer 不再作为 graph input
name_to_input = {i.name: i for i in m.graph.input}
for init in m.graph.initializer:
    if init.name in name_to_input:
        m.graph.input.remove(name_to_input[init.name])

# 2) 动态 batch -> 1
for i in m.graph.input:
    if i.name == "data":
        for d in i.type.tensor_type.shape.dim:
            d.ClearField("dim_param")
            if d.dim_value == 0:
                d.dim_value = 1

# 3) 提升 opset(对本模型直接改 version 即可)
for o in m.opset_import:
    if o.domain in ("", "ai.onnx"):
        o.version = 11

onnx.save(m, "resnet_50_op11.onnx")

然后再转:

bash 复制代码
atc --model=resnet_50_op11.onnx \
    --framework=5 \
    --output=resnet_50 \
    --soc_version=Ascend910B2C \
    --input_format=NCHW \
    --input_shape="data:1,3,224,224" \
    --log=error

这次输出:

text 复制代码
ATC run success, welcome to the next use.

5. 怎么跑 resnet_50.om

昇腾侧用 ACL(Ascend Computing Language)加载 OM。核心流程:

  1. acl.init() / set_device
  2. acl.mdl.load_from_file("resnet_50.om")
  3. Host → Device 拷贝输入
  4. acl.mdl.execute(...)
  5. Device → Host 取输出
  6. 释放资源

仓库里放了一个可直接跑的脚本 run_resnet50_om.py

bash 复制代码
source /usr/local/Ascend/ascend-toolkit/set_env.sh
cd /home/compiler_projects

# 随机输入冒烟
python3 run_resnet50_om.py

# 真实图片(需 Pillow,会做 ImageNet 预处理)
python3 run_resnet50_om.py --image cat.jpg

冒烟结果示例:

text 复制代码
model=/home/compiler_projects/resnet_50.om
input dims=[1, 3, 224, 224], bytes=602112
output dims=[1, 1000], bytes=4000
top-5:
  #1: class=490, score=...
  ...

6. 和转换前的 ONNX 对比

转换完最关心的还是:结果有没有漂?

方法很简单:同一份输入,分别用

  • onnxruntime(CPU)跑 ONNX
  • ACL 跑 OM

然后比:

  • max_abs_diff / mean_abs_diff
  • 相对 L2
  • cosine 相似度
  • top1 / top5

对比结果(固定随机种子 seed=0)

指标 ONNX vs OM
cosine 相似度 0.999998
相对 L2 0.00175
max abs diff ≈ 0.0097
mean abs diff ≈ 0.0024
top1 一致
top5 5/5 完全一致

top5 类别完全相同:[490, 904, 556, 794, 599],分数也非常接近。

这类微小数值差通常来自昇腾图编译后的 FP16 混合精度计算,对分类任务基本无感。只要 topk 稳定、cosine 接近 1,就可以认为转换是成功的。


7. 一张流程图总结

text 复制代码
resnet50-v2-7.onnx (opset7)
        │
        ▼
 清洗 input / 固定 shape / 提升 opset→11
        │
        ▼
      ATC 转换
   (soc=Ascend910B2C)
        │
        ▼
   resnet_50.om
        │
        ├──────────────► ACL 推理(NPU)
        │
onnxruntime (CPU) ──────► 同输入对比精度

8. 经验清单

  1. source set_env.sh ,否则 atc / ACL 环境变量容易缺。
  2. ATC 用的 Python ≠ 你终端默认 Python ,缺包要装到 /usr/bin/python3 对应环境。
  3. 老 ONNX(opset 7)在新 CANN 上可能全员报不支持,先升到 11+。
  4. 转换前最好确认:
    • 真正输入名(这里是 data
    • 静态 shape
    • 权重不要混在 graph.input
  5. 精度验收别只看单个 logit,cosine + topk 更有参考价值。

9. 参考命令速查

bash 复制代码
# 环境
source /usr/local/Ascend/ascend-toolkit/set_env.sh

# 转换
atc --model=resnet_50_op11.onnx \
    --framework=5 \
    --output=resnet_50 \
    --soc_version=Ascend910B2C \
    --input_format=NCHW \
    --input_shape="data:1,3,224,224" \
    --log=error

# 推理
python3 run_resnet50_om.py
python3 run_resnet50_om.py --image your.jpg

结语

这次把 ResNet-50 在昇腾上跑通后,最大的感受是:

ATC 本身不难,难的是环境对齐和模型版本兼容。

只要把 Python 依赖、soc_version、ONNX opset 这三件事处理好,经典 CNN 转 OM 还是很顺的;再补一个同输入精度对比,心里就踏实了。

如果你也在做昇腾模型迁移,欢迎把你的 soc_version、CANN 版本和报错贴出来一起对一下。

相关推荐
小溪学编程15 小时前
Java BufferedReader 详解:从基础用法到性能优化
java·python·性能优化
泡海椒1 天前
告别反射低效:JQuick-Java ASM动态调用链性能优化实战
java·python·性能优化
泡海椒1 天前
JQuick-java (JQuick-ASM)性能优化原理:字节码生成与缓存机制深度解析
java·缓存·性能优化
鸡蛋卷啊卷1 天前
性能优化的要求-Android性能优化之道2
性能优化
天空之城--1 天前
Android行业一周动态:编码趋势与行业资讯汇总
android·性能优化·架构·kotlin·android jetpack
ai小陈2 天前
PyTorch Profiler性能分析实战:定位GPU训练中的慢算子
人工智能·深度学习·机器学习·ai·性能优化·gpu算力
闲坐含香咀翠2 天前
从 132MB 到 25MB:列式存储怎么把 CPU 缓存命中率提上去的
前端·数据结构·性能优化
闲坐含香咀翠2 天前
把 AntV S2 列头计算从 O(n²) 降到 O(n):一次开源组件的性能改造
前端·性能优化
闲坐含香咀翠2 天前
Worker 常驻 + 零拷贝:postMessage 的结构化克隆算法与 Transferable 的真实代价
前端·性能优化