ARTICLE DETAIL

资讯详情

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

YOLOv5部署到Atlas 300i Pro:从ONNX到OM的完整实践指南

YOLOv5部署到Atlas 300i Pro:从ONNX到OM的完整实践指南 1. 项目概述与整体思路拆解1.1 为什么是Atlas 300i Pro而不是普通显卡先说结论Atlas 300i Pro是华为推出的一款边缘计算AI加速卡核心芯片是Ascend 310P系列主打推理场景也支持一定规模的训练。你如果之前只用过GPU第一次拿到这块卡可能会觉得整个开发流程都和你熟悉的不太一样——训练阶段还能用PyTorch跑但到了部署阶段整个工具链都是华为自研的CANN体系模型的存储格式也变成了OMOffline Model完全绕开CUDA和TensorRT。我最初接手这个项目时手头正好有一批YOLOv5的检测需求要部署到Atlas 300i Pro上跑实时视频流。当时查了一圈资料发现论坛上的帖子大多只讲某一段要么只讲ATC转换要么只讲Python调用pyACL很少有文章把“训练 → 导出ONNX → 转OM → SDK推理”这条完整链路串起来。于是我把整个流程从头到尾跑通了一遍也踩了不少坑这篇博文就是把我的完整实践记录整理出来。1.2 部署流程全景训练、转换、推理三阶段整个项目可以拆成三个阶段。第一阶段是YOLOv5模型训练这一部分实际上不用Atlashardware参与你有普通NVIDIA GPU就在普通GPU上训练甚至用CPU训练小数据集也能凑合但速度会非常感人。训练完成后导出ONNX格式。第二阶段是模型转换。ONNX要拿到Atlas 300i Pro上用ATC工具转成OM格式。这一步是整个流程里坑最多的尤其是算子映射、数据格式对齐NCHW还是NHWC、动态shape还是静态shape这几个问题稍不留神就转换失败或者推理结果全乱。第三阶段是SDK推理。华为提供pyACLPython ACL和C ACL两套接口我这次用的是pyACL。读取OM模型、创建context、申请Device内存、把图像预处理后送入模型、拿到输出做后处理这一套流程和CUDA的写法思路类似但API细节差异很大。这三个阶段看起来彼此独立但实操中会互相牵扯。比如你在训练侧如果不注意Opset版本导出ONNX时选错了算子集版本到ATC阶段就可能报算子不支持。再比如你如果在训练时用了某种数据增强部署时后处理也必须按照训练的逻辑做一致性对齐不然精度会莫名其妙掉下来。2. YOLOv5训练侧的关键准备与超参数选择2.1 数据集整理与标注格式校验不要一上来就训练先把数据弄规整。YOLOv5官方仓库要求的数据集结构是images和labels两个目录分别放训练集、验证集每张图片对应一个同名的txt标注文件。标注文件每一行是“class x_center y_center width height”注意这里的坐标是归一化到0到1之间的值不是像素坐标。很多新手踩坑都在这里用的标注工具是LabelImg导出格式选了VOC XML然后再手工转YOLO格式结果转出来的txt里坐标没有归一化或者类别索引从1开始而没有从0开始导致训练直接损失不收敛。我自己的习惯是标注完先用脚本检查一遍所有txt统计每一行的数值范围一旦出现大于1或者负数立即修复。这一步值得写个小脚本批量自检。检查内容有三项一是标注文件与图片文件是否能一一对应二是每行的五个数值是否都在合理范围内三是类别索引是否小于类别总数。实测下来这三项检查能过滤掉大多数标注质量问题避免无效训练。2.2 训练超参数与硬件环境的配合官方YOLOv5的train.py提供了很多超参数比如epochs、batch-size、img-size、optimizer、lr等。对于Atlas场景我建议使用一个中小规模的模型比如YOLOv5s或者YOLOv5m没必要一上来就用YOLOv5x。原因有两点一是310P的算力摆在那里大模型推理帧率会很难看二是部署后的实时性非常重要边缘卡更看重吞吐和延迟的平衡。batch-size的选择要看显存。训练时如果你用的是普通GPU比如RTX 3090或者A100batch-size可以开大一些提升训练稳定性。但如果你只有CPU或者显存很小batch-size往8以下调同时把图片尺寸从640降到416训练速度会快很多模型精度损失在可接受范围内。我这次训练时是用自己的GPU服务器完成的训练命令大概长这样python train.py --data data/custom.yaml --weights yolov5s.pt --img 640 --batch 32 --epochs 150 --device 0 --cache其中--cache很关键它会把图片提前缓存到内存里避免每个epoch都重新读磁盘训练时间能省不少。对于数据量大但磁盘读取慢的环境这个参数的收益非常明显。2.3 导出ONNX前的算子集与Opset版本检查训练完成后YOLOv5官方仓库自带export.py脚本可以直接导出ONNXpython export.py --weights runs/train/exp/weights/best.pt --include onnx --opset 11这里有一个非常重要的细节ONNX的Opset版本必须和ATC工具支持的版本对齐。华为CANN的ATC文档里会明确写支持哪些版本我这次使用的是CANN 5.1.RC2对ONNX的Opset支持到13左右但实际转换中发现Opset 11最稳。如果你用了自定义的算子模块或者训练时改过YOLOv5的网络结构比如加了注意力机制那导出ONNX之后就一定要用onnx.checker先校验一遍。另外还要注意YOLOv5官方版本的detect层输出包括三个尺度的预测特征图每个尺度输出shape为[batch, 3, grid_h, grid_w, 5num_classes]导出ONNX时这段逻辑会被整个保留。你如果不做任何裁剪直接转OM去推理后处理必须自己实现anchor解码、置信度过滤、NMS这几个步骤。很多人以为转成OM之后NMS也自动有了其实不会除非你使用YOLOv5的端到端版本在模型内部用NMSPlugin之类的算子把NMS包进去否则NMS始终在推理框架外面。所以在部署规划阶段就要想清楚后处理放在哪里放在Host端CPU上做就用Python或C写解码逻辑放在Device端做就要在模型转换时拼接一些额外算子。平台不同的方案各有优劣我建议先从Host端后处理做起逻辑简单调试方便等性能瓶颈出现了再考虑Device端融合。3. 模型转换从ONNX到OM的ATC历程3.1 ATC命令的典型用法与关键参数解析ONNX转换OM的核心工具是ATC位于CANN安装目录下的atc/bin里。使用前需要先source环境变量source /usr/local/Ascend/ascend-toolkit/set_env.sh接下来是转换命令。这是整个项目里最容易出问题的一环建议一个一个参数理解清楚再改atc --modelyolov5s.onnx \ --framework5 \ --outputyolov5s_bs1 \ --input_shapeimages:1,3,640,640 \ --enable_small_channel1 \ --insert_op_confaipp_yolov5.cfg \ --output_typeFP32 \ --input_formatNCHW \ --loginfo其中--framework5表示输入模型是ONNX--input_shape里的名字images必须和ONNX输入节点的名字完全一致大小写不匹配也会报错--input_formatNCHW一般要显式指定因为部分版本的ATC默认会把输入格式当NHWC处理一旦不对齐推理结果就是乱码。--insert_op_conf指定AIPP配置文件。AIPP是华为的AI预处理模块可以把图像缩放、减均值、除以标准差、通道顺序变换这些操作直接烧进模型里在Device端完成省去Host端手动预处理的开销。我强烈建议对于实时视频流场景启用AIPP虽然配置起来多写几行但省下来的预处理时间在帧率上体现得非常明显。3.2 AIPP配置文件的编写与通道顺序坑AIPP配置文件的格式是文本形式的典型内容如下aipp_op { aipp_mode: static input_format: RGB888_U8 mean_chn_0: 0 mean_chn_1: 0 mean_chn_2: 0 var_reci_chn_0: 0.003921569 var_reci_chn_1: 0.003921569 var_reci_chn_2: 0.003921569 crop: 0 horizontal_off: 0 vertical_off: 0 resize: 1 src_image_size_w: 640 src_image_size_h: 640 }mean_chn和var_reci_chn对应训练时的归一化参数。YOLOv5训练时用的是0到1归一化也就是像素值除以255等价于均值为0、方差倒数为1/255约0.003921569。如果你在训练时还用了ImageNet的均值和方差做标准化那这里要填对应的值千万别搞混。src_image_size_w和src_image_size_h是输入图像的原始尺寸resize: 1表示让AIPP把输入图像缩放成模型输入尺寸。这里要注意AIPP的resize是直接拉伸不是等比缩放加padding。如果你的输入图像宽高比和模型输入不一样直接resize会导致目标物体变形检测精度明显下降。更稳妥的做法是在Host端先把图像等比缩放并padding到640x640再喂给AIPP处理此时AIPP只做颜色空间转换和归一化不做resize。3.3 算子不支持与转换失败时的排查思路ATC转换失败是最消磨耐心的环节。常见的报错有两类一类是“Op type XXX is not supported”这个意思是某个算子没有对应的Ascend实现。解决办法是查看算子清单确认是否有替代算子或者回到ONNX层面把该算子在PyTorch里改成等价实现重新导出ONNX。另一类是“Incompatible shape”Require shape与实际shape不匹配这种大多是动态shape没处理好。要么把输入shape固定为一个静态值要么用--dynamic_batch_size或--dynamic_image_size参数把动态范围明确声明。我个人的经验是第一版转换尽量用静态shape也就是固定batch size为1、固定输入分辨率。动态shape虽然灵活但会显著增加ATC的工作量转换时间更长有些算子组合在动态shape下直接不支持。等静态shape的整条链路跑通了再根据实际业务需求引入动态batch。转换失败时有一个小技巧把--loginfo改成--logdebug能输出更多细节。同时去~/atc/log/目录下看plog日志重点查包含ERROR关键字的那几行。这类日志信息量很大但有点晦涩一开始可能看不太懂坚持几次就能掌握规律。4. pyACL推理SDK的完整实现4.1 ACL初始化与设备管理的正确姿势推理侧的代码我分为初始化、模型加载与推理、后处理三大块。ACL初始化是整个SDK的第一步类似CUDA里的cudaSetDevice但又多了一些概念。基本流程是import acl ret acl.init() ret acl.rt.set_device(0) context, ret acl.rt.create_context(0)这里面的逻辑是先用acl.init()初始化ACL全局状态然后指定物理设备再创建Context。Context是ACL中的关键概念类似CUDA的Context用于管理设备上的资源。如果你在多线程里调用ACL接口每个线程都要绑定自己的Context否则会报错这一点需要特别注意。模型加载遵循“从文件读二进制 → 申请Device内存 → 加载模型”的路径当下很多代码都这样写import acl model_path byolov5s_bs1.om model_id, ret acl.mdl.load_from_file(model_path) model_desc acl.mdl.create_desc() ret acl.mdl.get_desc(model_desc, model_id)model_id是模型加载成功后得到的ID后续推理都靠它定位模型。model_desc保存了模型的输入输出维度信息后面申请内存时需要读取它来确认input/output的buffer大小。4.2 数据从图片到Device内存的搬运细节推理时需要先把一张图片从numpy数组拷贝到Device端。很多人第一次接触ACL会忽略内存申请的内存类型导致数据拷贝失败。ACL中常见内存类型有ACL_MEM_MALLOC_HUGE_FIRST、ACL_MEM_MALLOC_NORMAL_ONLY等推理场景用acl.rt.malloc申请Device内存然后acl.rt.memcpy把Host数据拷进去input_size 640 * 640 * 3 * 4 # 假设输入是FP323通道640x640 input_data, ret acl.rt.malloc(input_size, acl.const.MEM_MALLOC_HUGE_FIRST) ret acl.rt.memcpy(input_data, input_size, img_contiguous.data_ptr(), input_size, acl.const.MEMCPY_HOST_TO_DEVICE)这里的img_contiguous需要是一个内存连续、dtype为float32、shape为(1,3,640,640)的numpy数组。YOLOv5训练时的预处理包括letterbox缩放、BGR转RGB、归一化这些操作如果在Host端做要确保最终数组的内存是连续的建议在拷贝前调用np.ascontiguousarray强制连续。如果开了AIPP那么Host端只需要把图像数据按RGB888_U8格式放好AIPP会在Device端处理归一化和通道变换。4.3 模型推理与输出后处理的血泪经验模型执行推理的接口是acl.mdl.execute同步执行模式下调用后会阻塞直到推理完成output_data, ret acl.rt.malloc(output_size, acl.const.MEM_MALLOC_HUGE_FIRST) ret acl.mdl.execute(model_id, [input_data], [output_data])拿到输出后需要把Device端数据拷回Host并reshape成模型的输出shape。YOLOv5的ONNX输出有三个Tensor对应的shape分别是[1,3,80,80,85]、[1,3,40,40,85]和[1,3,20,20,85]。这里的85是5 num_classes即cx, cy, w, h, objectness, class_scores。后处理代码要自己实现anchor解码和NMS。YOLOv5官方仓库的detect.py里已经有现成的non_max_suppression函数可以直接移到推理脚本里复用但要注意两点第一模型输出在Host端后坐标值是归一化到640x640的要映射回原始图像尺寸必须记录letterbox的padding信息第二NMS的IoU阈值和置信度阈值要和训练时对齐一般是IoU 0.45、置信度0.25如果对精度不满意可以适当调低置信度阈值。我在实现后处理时发现用纯Python循环处理三个尺度的anchor会比较慢尤其在高帧率视频流里会成为瓶颈。建议用numpy向量化计算把三个尺度的特征图先reshape成[batch, total_anchors, 85]然后用向量化方式一次性解码最后再对每个类别分别做NMS。实测下来这种方式的处理速度比纯Python循环快一个数量级。5. 性能分析与常见问题排查5.1 帧率上不去的瓶颈定位方法整条链路跑通后就该关心性能了。Atlas 300i Pro上的YOLOv5s推理单次模型推理时间大约在10到20毫秒之间具体取决于输入分辨率、芯片频率和CANN版本。但如果你发现端到端的处理帧率远低于预期瓶颈往往不在模型推理而在数据预处理和后处理。一个很好的定位思路是在代码的关键节点打点计时分别统计“图像读取时间”、“Host端预处理时间”、“H2D拷贝时间”、“模型推理时间”、“D2H拷贝时间”、“后处理时间”。我实测中发现一个非常典型的案例模型推理只用了12毫秒但后处理用了80毫秒原因就是Python循环解码anchor太慢。后来改成numpy批量解码后处理降到10毫秒以内整体帧率立刻翻倍。另外CANN提供了msprof工具做Profiling可以分析NPU上的算子耗时。用法是msprof --applicationpython infer.py --outputprofiling_dir跑一次后在输出目录里会生成详细的算子耗时报告。这里要提醒一点msprof本身会产生开销正式性能测试时不要开着Profiling跑否则数据会失真。一般是先开Profiling定位热点关掉以后再测真实性能。5.2 推理结果错乱、坐标偏移的排查步骤推理跑起来了但检测框位置不对这类问题通常出在预处理不一致上。排查步骤从输入数据开始先打印送入模型的输入数组和训练阶段预处理后的结果对比如果数值完全一致再检查输出解析方式是否和网络结构对应。很多时候坐标偏移是因为图像resize方式不一致比如训练用的letterbox加padding部署时图省事直接resize拉伸导致宽高比变化框的位置自然偏了。遇到这类问题建议做一个最小化验证取一张训练集图片用训练代码里的预处理生成输入喂给ONNX Runtime得到输出再把同样的输入喂给OM模型对比两份输出在数值上是否接近。如果ONNX Runtime和OM的输出一致说明模型转换没问题问题出在业务代码的预处理或后处理上。如果输出不一致再回头检查ATC转换参数和AIPP配置。5.3 内存泄漏与多路并发时的资源管理在视频流场景里推理服务通常要同时处理多路视频。ACL的Context是独立的每个线程绑定一个Context后可以并行执行推理。但要注意的是多线程访问同一个model_id是线程不安全的需要加锁或者每个线程单独加载一份模型虽然浪费内存但更稳定。内存释放也得特别注意acl.rt.free少了就泄漏久了服务会崩。ACL典型的资源释放顺序是释放输入输出内存 →acl.mdl.unload(model_id)卸载模型 → 销毁Context →acl.rt.reset_device(0)→acl.finalize()。每一层都不能漏。我自己一般会把初始化、释放封装成两个工具函数避免代码里到处散落acl.rt.free到释放时少调用了好几次。对于多路视频流我实测下来每路视频独占一个Python线程共用一个Context推理用acl.mdl.execute_async异步接口加回调处理整体资源开销可控且稳定。异步接口比同步接口难写但吞吐量明显更高适合多路场景。6. 部署实践中的避坑清单与评估建议6.1 从训练到部署最容易忽略的细节整个流程走完我有几个最想强调的细节写在这里也算给后来者提个醒。第一训练时YOLOv5的输入尺寸和OM模型的输入尺寸要保持一致。如果你训练时用的640x640部署转换时也应该用640x640不建议训练用640、部署转换用416这样模型的感受野和anchor尺度都会错位精度必然下降。第二导出的ONNX模型要保留原始的检测头除非你做了自定义裁剪否则不要随意删减输出层。部分封装好的模型文件会把输出处理成框坐标而不是anchor编码转换后你再按照标准YOLOv5方式解anchor就会得到乱七八糟的框。第三AIPP太好用但也要谨慎。一旦使用了AIPP的色域转换和归一化Host端输入的数据就必须是原始图像格式不能再自行做归一化否则相当于做了两遍归一化推理输出会彻底乱掉。这是现场最容易犯的错。第四Atlas 300i Pro实际功耗不低散热要做好。设备在机箱里长时间满载运行温度过高会导致降频推理速度越来越慢。如果发现连续运行几天后性能明显下降先检查散热。6.2 拓展方向从单模型推理到多模型流水线项目跑通单模型推理后可以试着一个进阶方向在同一个进程里加载多个模型比如同时跑YOLOv5检测和OCR识别形成流水线处理。ACL的acl.mdl.load_from_file支持多次调用加载多个模型ID每个模型独立执行。需要注意内存占用每个模型权重和输出的中间缓冲都会占Device内存内存不够时加载会失败这时需要评估模型大小或者用acl.rt.set_ip等接口对内存做更精细的管理。另一个拓展方向是模型本身的轻量化。如果觉得YOLOv5s在Atlas 300i Pro上的帧率还不满足业务需求考虑用YOLOv5n或者YOLOv5lite这种更小的变体虽然精度略低一些但很多场景完全够用。模型压缩和量化也是可行的优化路径Atlas工具链集成了量化能力可以在转换时把FP32模型量化成INT8推理速度可以提升一倍多但注意量化后精度需要重新验证。6.3 个人落地感受与资源投入建议坦白地说从零到一把这条链路跑通如果对CANN体系完全不熟一个人大概要投入一到两周。其中训练和部署的时间差不多各占一半但真正烧时间的不是训练本身而是转换和推理代码里的各种小问题。如果你也是第一次接触Atlas系列我的建议是不要一头扎进官方文档的海量信息里而是先把最小可行样例跑通。官方的sample仓里有yolov5的示例代码可以参考但最好带着理解去改不要直接拿来就用因为CANN版本更新很快旧版示例代码在新版上往往有兼容问题。如果是企业项目评估阶段建议在买硬件前先确认CANN工具链对本地环境版本的支持情况尤其要注意操作系统版本和CANN版本的对应关系版本不匹配安装过程会非常痛苦。我一个朋友在这上面踩了大坑因为操作系统版本过新CANN官方包不支持光搭环境就折腾了三天。提前在官方文档查清楚兼容性矩阵能省下大把时间。最后说句实话Atlas这套体系虽然上手门槛比CUDA高但一旦把工具链摸熟了它在边缘侧推理的性能和功耗比确实不错。尤其是CANN里很多自动优化能力比如算子融合、内存复用都在工具链层面帮你处理了这一点反而比裸写CUDA更省心。如果你有现成的YOLOv5权重想快速部署到边缘设备上这条路径值得投入时间去打通。
返回列表