昇腾 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:
- 输入:
data,float32,[1, 3, 224, 224](NCHW) - 输出:
[1, 1000](ImageNet 1000 类 logits)
2. 环境确认
先确认 NPU 和 CANN 是否正常:
npu-smi info
which atc
source /usr/local/Ascend/ascend-toolkit/set_env.sh
本次机器信息:
- NPU:
910B2C - CANN:
8.5.1 - 转换时
soc_version:Ascend910B2C
3. 下载 ONNX 模型
从 ONNX Model Zoo 下载 ResNet-50 v2:
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"
这个模型有两个小特点,后面转换会碰到:
- opset = 7(比较老)
- 权重被写在
graph.input里(老式 ONNX 写法),真正的网络输入只有data
4. ONNX → OM(ATC)
4.1 标准命令
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 直接失败:
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 装依赖:
/usr/bin/python3 -m ensurepip --upgrade
/usr/bin/python3 -m pip install 'numpy<2' decorator attrs cloudpickle \
ml-dtypes psutil scipy tornado sympy
装完 numpy 后,往往还会继续缺 decorator、scipy 等,建议一次性装齐。
4.3 坑 2:所有算子都报 No operator plugin
依赖装好后,又出现:
Not_Supported_Operator(E13010):
No operator plugin is registered for Op: xxx, optype: ai.onnx::7::Conv
连 Conv、Relu 都报不支持,看起来很吓人。查日志后发现:
- ONNX 插件其实加载成功了
- 但 CANN 8.5.1 注册的是
ai.onnx::8及以上 - 当前模型是
ai.onnx::7
所以不是「昇腾不支持卷积」,而是 opset 太旧,对不上注册表。
4.4 处理方式:清洗模型 + 提升 opset
先把权重从 graph.input 里摘掉,只保留真正输入 data,并把 batch 固定为 1;再把 opset 提升到 11:
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")
然后再转:
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
这次输出:
ATC run success, welcome to the next use.
5. 怎么跑 resnet_50.om
昇腾侧用 ACL(Ascend Computing Language)加载 OM。核心流程:
acl.init()/set_deviceacl.mdl.load_from_file("resnet_50.om")- Host → Device 拷贝输入
acl.mdl.execute(...)- Device → Host 取输出
- 释放资源
仓库里放了一个可直接跑的脚本 run_resnet50_om.py:
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
冒烟结果示例:
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. 一张流程图总结
resnet50-v2-7.onnx (opset7)
│
▼
清洗 input / 固定 shape / 提升 opset→11
│
▼
ATC 转换
(soc=Ascend910B2C)
│
▼
resnet_50.om
│
├──────────────► ACL 推理(NPU)
│
onnxruntime (CPU) ──────► 同输入对比精度
8. 经验清单
- 先
source set_env.sh,否则atc/ ACL 环境变量容易缺。 - ATC 用的 Python ≠ 你终端默认 Python,缺包要装到
/usr/bin/python3对应环境。 - 老 ONNX(opset 7)在新 CANN 上可能全员报不支持,先升到 11+。
- 转换前最好确认:
- 真正输入名(这里是
data) - 静态 shape
- 权重不要混在
graph.input里
- 真正输入名(这里是
- 精度验收别只看单个 logit,cosine + topk 更有参考价值。
9. 参考命令速查
# 环境
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 版本和报错贴出来一起对一下。
鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。
更多推荐


所有评论(0)