ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

昇腾NPU部署YOLOv8实战:从ONNX到om的ATC转换与推理避坑指南

昇腾NPU部署YOLOv8实战:从ONNX到om的ATC转换与推理避坑指南 简介本资源面向目标检测开发者与算法部署工程师提供 YOLOv8 在华为昇腾平台上的完整适配方案解决 PyTorch 模型向昇腾硬件迁移落地的实际问题。包内包含适配后的源代码与模型核心脚本 pt2om2.py 负责将 pt 权重转换为昇腾 OM 格式yolov8_om_infer.py 则给出 OM 模型推理示例覆盖数据加载、模型构建、推理计算与后处理等环节并针对昇腾 AI Core 架构在并行计算与张量加速方面做了适配优化。资源共 315 个文件以 92 个 py 脚本、41 个 yaml 配置、29 个 md 文档为主另含 pyc、onnx、pt 及少量 sh、yml 等压缩包约 153.25MB目录结构清晰便于按模块查阅。已有 948 人学习下载适合安防监控、自动驾驶、工业质检等场景下需要完成昇腾部署的读者参考可快速理解转换流程与推理调用方式。1. 昇腾上跑 YOLOv8为什么你的第一反应不该是改模型如果你手里有一块昇腾 310P 或 910B又刚好在 Ubuntu 20.04 上把 YOLOv8 的训练脚本跑通了第一反应大概率是「直接拿 PyTorch 的 .pt 去推理」。这个念头本身没错但真正落地时会发现昇腾的 CANN 工具链不认原生 PyTorch 权重你得先把模型转成 ONNX再走 ATC 转成 om最后用 ais_bench 或 pyACL 做板端推理。整条链路里模型结构本身几乎不用动真正花时间的是算子支持度、输入输出布局和前后处理对齐。YOLOv8 华为昇腾适配这件事核心不是「改网络」而是「把训练框架的产物翻译成昇腾能吃的格式并保证精度不掉、速度能看」。适合两类人一类是在信创环境里做目标检测落地的工程师手里有 Atlas 200I DK A2、Atlas 300I Pro 或者 310P 推理卡另一类是做毕业设计或项目验证想用昇腾跑 YOLOv8 但不想从零啃 CANN 文档。下面按「环境准备 → 模型导出 → ATC 转换 → 板端推理 → 避坑」的顺序拆一遍每一步都给可抄的命令和参数。2. 环境准备CANN、PyTorch 与 Ultralytics 的版本咬合2.1 为什么版本组合比显卡型号更致命昇腾适配的第一道坎不是代码是版本。CANN 版本决定了 ATC 支持哪些 ONNX 算子PyTorch 版本决定了 torch_npu 能不能装上Ultralytics 版本决定了导出的 ONNX 是 opset 几。常见做法是CANN 用 7.0 或 8.0PyTorch 用 2.1.0torch_npu 用对应配套版本Ultralytics 用 8.0.x 或 8.1.x。如果你在 x86 上做转换、在 ARM 板端做推理两边 CANN 版本尽量保持一致否则 om 模型可能加载失败。我一般会先确认三件事npu-smi info能不能看到设备python -c import torch; import torch_npu能不能正常导入atc --version有没有输出。这三个命令任何一个报错后面都不用往下走。# 检查昇腾设备状态 npu-smi info # 检查 PyTorch 与 torch_npu python3 -c import torch; import torch_npu; print(torch.__version__); print(torch_npu.__version__) # 检查 ATC 工具 atc --version逻辑说明npu-smi info看的是驱动和固件是否正常如果显示 no device 或者版本不匹配先处理驱动。torch_npu导入失败通常是 Python 版本或 CANN 环境变量没 source。atc --version没输出说明 CANN 的 toolkit 没装全只装了 runtime 是不够的。参数说明CANN 安装后需要source /usr/local/Ascend/ascend-toolkit/set_env.sh这个要写进~/.bashrc否则每次新开终端都要手动 source。torch_npu 的版本必须和 PyTorch 严格对应比如 PyTorch 2.1.0 对应 torch_npu 2.1.0.post8 这类差一个小版本都可能 import 失败。2.2 在 Ubuntu 20.04 上把 Ultralytics 跑起来Ubuntu 20.04 是昇腾文档里覆盖最全的系统版本Python 建议用 3.8 或 3.9。Ultralytics 直接用 pip 装就行但要注意它默认会拉最新版而最新版导出的 ONNX opset 可能超过 ATC 支持范围。我一般会锁版本# 创建虚拟环境 python3 -m venv yolov8_ascend source yolov8_ascend/bin/activate # 安装指定版本 Ultralytics pip install ultralytics8.0.196 onnx1.14.0 onnxsim0.4.35 # 验证 yolo checks逻辑说明ultralytics8.0.196这个版本导出的 ONNX 默认 opset 是 12ATC 对 opset 11 和 12 的支持最稳。onnxsim用来简化 ONNX 图去掉冗余算子能显著减少 ATC 转换时的算子不支持报错。参数说明yolo checks会输出环境信息重点看 Python 版本、PyTorch 版本和 CUDA 状态。在昇腾环境里 CUDA 显示不可用是正常的我们只用它做模型导出不做 GPU 训练。3. 从 .pt 到 .onnx导出时的三个关键参数3.1 导出命令与 opset 选择YOLOv8 的导出接口很直接但默认参数在昇腾场景下需要改。核心是opset、simplify和dynamic。dynamic 轴在 ATC 里支持有限建议导出固定 batch 和固定输入尺寸。from ultralytics import YOLO # 加载训练好的权重 model YOLO(yolov8n.pt) # 导出 ONNX固定输入 640x640batch1 model.export( formatonnx, opset12, simplifyTrue, dynamicFalse, imgsz640, batch1 )逻辑说明opset12是 ATC 支持较好的版本再高可能遇到 GridSample 或 NonMaxSuppression 算子不支持。simplifyTrue会调用 onnxsim 做图优化把一些可以合并的算子提前合并。dynamicFalse避免动态维度ATC 对动态 shape 的支持需要额外配置固定 shape 最省事。参数说明imgsz640要和后续 ATC 的--input_shape一致否则推理时预处理会错位。batch1是板端推理最常用的配置如果要做多 batch需要确认 ATC 的--input_shape写成--input_shapeimages:4,3,640,640。3.2 导出后检查 ONNX 图导出完不要直接扔给 ATC先用 onnx 工具看一眼输入输出名字和 shape。YOLOv8 导出的 ONNX 默认输入叫images输出叫output0shape 是[1, 84, 8400]。这个 84 是 4 个框坐标加 80 类分数8400 是三个检测头的 anchor 总数。import onnx model onnx.load(yolov8n.onnx) for inp in model.graph.input: print(input:, inp.name, [d.dim_value for d in inp.type.tensor_type.shape.dim]) for out in model.graph.output: print(output:, out.name, [d.dim_value for d in out.type.tensor_type.shape.dim])逻辑说明确认输入名是images输出名是output0后面 ATC 的--input_shape和推理代码里的输入输出名要跟这里完全一致。如果输出 shape 里出现 0 或 -1说明 dynamic 没关干净需要重新导出。参数说明dim_value为 0 表示动态维度固定 shape 下应该都是正数。如果输出是[1, 84, 8400]说明模型是 COCO 80 类如果是自定义数据集84 会变成4 类别数。4. ATC 转换把 ONNX 翻译成昇腾能吃的 om4.1 ATC 命令与参数拆解ATC 是昇腾的模型转换工具输入 ONNX输出 om。命令看起来长但关键参数就几个--model、--framework、--output、--input_shape、--soc_version、--input_format。atc --modelyolov8n.onnx \ --framework5 \ --outputyolov8n_ascend \ --input_shapeimages:1,3,640,640 \ --input_formatNCHW \ --soc_versionAscend310P3 \ --logerror \ --output_typeFP32逻辑说明--framework5表示输入是 ONNX这是固定值。--output是输出 om 的前缀实际生成yolov8n_ascend.om。--input_shape必须和 ONNX 导出的 shape 完全一致包括 batch。--soc_version要填你实际使用的芯片型号310P 填Ascend310P3910B 填Ascend910B填错会在推理时加载失败。参数说明--input_formatNCHW是 YOLOv8 的标准布局不要改成 NHWC除非你在导出时已经转了。--output_typeFP32保持浮点输出如果追求速度可以改FP16但精度可能掉一点建议先 FP32 验证精度再切 FP16。--logerror只打印错误调试阶段可以改--loginfo看详细过程。4.2 算子不支持时的排查路径ATC 转换最常见的报错是「算子不支持」。YOLOv8 里容易出问题的是NonMaxSuppression、GridSample和某些激活函数。如果报错信息里明确说了哪个算子先查 CANN 版本的算子支持列表。常见做法是把 NMS 从模型里拆出来ONNX 只导出到检测头输出NMS 在后处理里用 Python 或 C 实现。# 查看 ATC 转换日志中的算子信息 atc --modelyolov8n.onnx \ --framework5 \ --outputyolov8n_ascend \ --input_shapeimages:1,3,640,640 \ --soc_versionAscend310P3 \ --loginfo 21 | grep -i unsupported\|not support逻辑说明--loginfo会输出每个算子的映射情况grep 过滤出 unsupported 关键字。如果看到某个算子不支持优先考虑用 onnxsim 再简化一次或者用 ONNX 的 graph surgeon 把该算子替换成支持的等价实现。参数说明21把 stderr 重定向到 stdout因为 ATC 的日志有一部分走 stderr。grep -i忽略大小写避免漏掉Unsupported这种写法。5. 板端推理pyACL 加载 om 与前后处理对齐5.1 pyACL 推理骨架om 模型在板端用 pyACL 加载流程是初始化 ACL → 加载模型 → 创建输入输出 dataset → 执行推理 → 取输出 → 后处理。下面是一个最小可运行的推理脚本骨架。import acl import numpy as np import cv2 # 初始化 acl.init() device_id 0 acl.rt.set_device(device_id) context, _ acl.rt.create_context(device_id) # 加载 om 模型 model_path yolov8n_ascend.om model_id, _ acl.mdl.load_from_file(model_path) # 获取输入输出描述 input_desc acl.mdl.get_input_describe(model_id, 0) output_desc acl.mdl.get_output_describe(model_id, 0) print(input:, input_desc) print(output:, output_desc) # 准备输入数据 img cv2.imread(test.jpg) img cv2.resize(img, (640, 640)) img img[:, :, ::-1].transpose(2, 0, 1) # BGR-RGB, HWC-CHW img np.ascontiguousarray(img, dtypenp.float32) / 255.0 img np.expand_dims(img, axis0) # 创建 dataset 并执行 input_dataset acl.mdl.create_dataset() input_buffer acl.create_data_buffer(img.data, img.nbytes) acl.mdl.add_dataset_buffer(input_dataset, input_buffer) output_dataset acl.mdl.create_dataset() output_buffer acl.create_data_buffer(None, 84 * 8400 * 4) acl.mdl.add_dataset_buffer(output_dataset, output_buffer) acl.mdl.execute(model_id, input_dataset, output_dataset) # 取输出 output_data acl.get_data_buffer_addr(output_buffer) output_np np.frombuffer(output_data, dtypenp.float32).reshape(1, 84, 8400) print(output_np.shape)逻辑说明acl.mdl.execute是同步推理输入输出 dataset 的 buffer 大小要和模型描述一致。输入预处理必须和训练时一致RGB、归一化到 0-1、CHW 布局。输出[1, 84, 8400]需要做转置和 NMS 才能得到最终框。参数说明acl.create_data_buffer(img.data, img.nbytes)里的img.data是内存指针img.nbytes是字节数。输出 buffer 的大小按84 * 8400 * 4算4 是 float32 的字节数。如果模型输出是 FP16这里要改成 2。5.2 后处理从 84x8400 到检测框YOLOv8 的输出是[1, 84, 8400]84 的前 4 个是cx, cy, w, h后 80 个是类别分数。需要先转置成[8400, 84]再按置信度阈值过滤最后做 NMS。def postprocess(output, conf_thres0.25, iou_thres0.45): output output[0].T # [8400, 84] boxes output[:, :4] scores output[:, 4:] class_ids np.argmax(scores, axis1) confidences np.max(scores, axis1) mask confidences conf_thres boxes boxes[mask] confidences confidences[mask] class_ids class_ids[mask] # cxcywh - xyxy xyxy np.zeros_like(boxes) xyxy[:, 0] boxes[:, 0] - boxes[:, 2] / 2 xyxy[:, 1] boxes[:, 1] - boxes[:, 3] / 2 xyxy[:, 2] boxes[:, 0] boxes[:, 2] / 2 xyxy[:, 3] boxes[:, 1] boxes[:, 3] / 2 # NMS indices cv2.dnn.NMSBoxes(xyxy.tolist(), confidences.tolist(), conf_thres, iou_thres) return xyxy[indices], confidences[indices], class_ids[indices]逻辑说明output[0].T把[84, 8400]转成[8400, 84]每行是一个候选框。argmax取类别max取置信度。cxcywh转xyxy是标准操作。NMS 用 OpenCV 的NMSBoxes输入是 xyxy 格式的框列表。参数说明conf_thres0.25是置信度阈值低于这个值的框直接丢掉。iou_thres0.45是 NMS 的 IoU 阈值越高保留的框越多。这两个值要根据实际场景调检测小目标时 conf_thres 可以降到 0.1。6. 避坑与排查昇腾适配 YOLOv8 的五个血泪经验6.1 现象ATC 转换报「E19999: Inner Error」原因ONNX 图里有 ATC 不支持的算子或者 opset 版本过高。常见触发点是NonMaxSuppression和GridSample。解决先确认导出时opset12且simplifyTrue。如果还报错用onnxsim单独跑一次简化或者把 NMS 从模型里拆掉只导出到检测头。拆 NMS 的方法是在 Ultralytics 导出时加nmsFalse但要注意这样导出的输出 shape 会变。6.2 现象om 模型加载成功但推理结果全是乱码原因输入预处理和训练时不一致。YOLOv8 训练用的是 RGB、0-1 归一化、CHW 布局如果推理时用了 BGR 或者没归一化输出会完全错乱。解决检查cv2.imread后的通道顺序必须img[:, :, ::-1]转 RGB。归一化除以 255.0 不能漏。transpose 的顺序是(2, 0, 1)从 HWC 转 CHW。6.3 现象推理速度远低于预期原因--output_type用了 FP32或者--soc_version填错导致跑在低效模式。另外如果输入 shape 是动态的ATC 会插入额外的同步操作拖慢速度。解决确认--soc_version和实际芯片一致。精度验证通过后把--output_type改成 FP16。输入 shape 固定成1,3,640,640不要用动态 batch。6.4 现象torch_npu导入报「undefined symbol」原因PyTorch 版本和 torch_npu 版本不匹配或者 CANN 环境变量没 source。解决查昇腾官方的版本配套表严格按表装。每次新开终端先source /usr/local/Ascend/ascend-toolkit/set_env.sh。如果还报错用ldd看 torch_npu 的 so 文件依赖有没有缺失。6.5 现象板端推理时内存不足原因输入输出 dataset 的 buffer 没有及时释放或者模型本身太大。YOLOv8n 的 om 大概几十 MB但如果用 YOLOv8xom 会大很多。解决每次推理完调用acl.mdl.destroy_dataset和acl.destroy_data_buffer释放资源。如果模型太大考虑用 YOLOv8n 或 YOLOv8s或者做量化。7. 进阶技巧用 ais_bench 做精度比对与性能压测7.1 ais_bench 的定位ais_bench是昇腾提供的推理工具可以脱离业务代码直接跑 om 模型做精度比对和性能压测。它的价值在于当你怀疑是模型转换出了问题还是后处理写错了用 ais_bench 跑一遍纯推理看输出和 ONNX Runtime 的结果差多少。# 安装 ais_bench pip install ais_bench # 准备输入 bin 文件从 ONNX 推理时保存的输入 # 跑 ais_bench ais_bench --model yolov8n_ascend.om \ --input ./input.bin \ --output ./output.bin \ --output_dir ./result \ --batch_size 1 \ --device 0逻辑说明--input是预处理后的二进制输入shape 要和模型一致。--output是输出文件名。--output_dir是结果目录。跑完后可以用 numpy 读 output.bin和 ONNX Runtime 的输出做逐元素对比。参数说明--batch_size 1要和模型固定 batch 一致。--device 0指定设备 ID。如果要做性能压测加--loop 100跑 100 次取平均耗时。7.2 精度比对的具体做法我一般会准备同一张图的 ONNX 输入和 om 输入分别跑 ONNX Runtime 和 ais_bench然后算两个输出的余弦相似度。如果相似度低于 0.99说明转换过程有精度损失优先检查--output_type是不是 FP16改回 FP32 再试。import numpy as np onnx_out np.fromfile(onnx_output.bin, dtypenp.float32).reshape(1, 84, 8400) om_out np.fromfile(om_output.bin, dtypenp.float32).reshape(1, 84, 8400) # 余弦相似度 cosine np.dot(onnx_out.flatten(), om_out.flatten()) / ( np.linalg.norm(onnx_out.flatten()) * np.linalg.norm(om_out.flatten()) ) print(cosine similarity:, cosine) # 最大绝对误差 max_diff np.max(np.abs(onnx_out - om_out)) print(max abs diff:, max_diff)逻辑说明余弦相似度衡量两个向量的方向一致性越接近 1 越好。最大绝对误差看的是数值上的最大偏差FP32 转换下一般在 1e-3 量级FP16 会到 1e-2。参数说明reshape(1, 84, 8400)要和模型输出 shape 一致。如果自定义数据集类别数不是 8084 要改成4 类别数。7.3 一个我踩过的坑有一次转换完 om推理结果框的位置总是偏一点查了两天才发现是 ATC 转换时--input_format默认走了 NHWC而我的 ONNX 是 NCHW。虽然 ATC 没报错但内部做了隐式转置导致预处理后的数据布局对不上。从那以后我每次 ATC 都强制写--input_formatNCHW并且转换完先用 ais_bench 跑一遍纯推理确认输出和 ONNX Runtime 对齐了再写业务代码。这个习惯帮我省了很多返工时间希望帮到你。本文还有配套的精品资源点击获取
返回列表