尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

昇腾Atlas 300V部署YOLOv5实战:从ONNX转换到推理调优

昇腾Atlas 300V部署YOLOv5实战:从ONNX转换到推理调优 前阵子接手了一个工业视觉项目需求很直接在产线上跑 YOLOv5 做目标检测推理卡已经定好了是华为 Atlas 300V 24G。当时团队里有人质疑这卡到底行不行有人甚至以为它跟普通显卡差不多。结果整个部署过程从驱动到 C ANN从 ONNX 转换到推理调优前前后后折腾了两周踩坑踩到怀疑人生。这里说的 Atlas不是地图软件也不是数据库而是昇腾Ascend系列里的 AI 推理加速卡。Atlas 300V 24G 这个名字里的“24G”指的是 24GB 显存版本很多人第一次接触都会问一句“它是运算加速卡吗”——是但它跟 GPU 的运算方式完全是两码事。这篇就把我从零开始部署 YOLO 到 Atlas 300V 24G 的完整过程写出来包括硬件选型、软件栈安装、模型转换、推理代码、性能调优和排错速查。这套流程不只适配 YOLOv5YOLOv8、YOLOX 等检测类模型的落地思路也完全一致。1. Atlas 300V 24G 是什么卡为什么部署 YOLO 选它1.1 先回答“是运算加速卡吗”它是专攻推理的 NPU这个问题几乎每个刚接触昇腾的人都会问。Atlas 300V 24G 本质上是一块 PCIe 接口的 AI 加速卡它的计算核心不是 GPU 的 CUDA Core而是昇腾自研的达芬奇架构 AI Core。也就是说它是一块 NPUNeural-network Processing Unit专门为神经网络推理设计而不是通用并行计算卡。这带来一个很实际的区别拿它去跑 CUDA 程序是跑不起来的不能用搜到的一大堆 GPU 教程直接套但拿它去跑成熟框架导出的模型比如 PyTorch 模型的 ONNX 导出结果它反而有不错的能效比。24GB 这个显存容量在推理卡里算是大方了尤其对于 YOLOv5s、YOLOv8s 这类模型单张 640x640 输入做 FP16 推理只占几百 MB24GB 意味着可以同时塞下多个模型副本、开大 batch或者跑更大分辨率的输入而不用太担心显存不够。1.2 对比 GPU 和 Jetson为什么我最后选了它项目选型时我们其实对比过几种方案NVIDIA T4、Jetson Orin、还有 Atlas 300V 24G。T4 是经典的推理卡生态成熟资料好找但同价位下显存通常没有 24G 这么大而且如果项目对自主可控、国产硬件有要求T4 这条路基本走不通Jetson Orin 适合边缘小盒子功耗低但算力和多路并发能力相对有限我们产线要接多路摄像头它有点吃力。Atlas 300V 24G 的优势是三块显存大、板卡功耗相对可控、能效比在推理场景表现不错。它的短板也很明显——软件生态比 NVIDIA CUDA 生态差了不止一个量级文档分散版本兼容性有时候让人头大。简单说如果你只做推理部署且愿意为“国产化大显存”付出一点折腾成本它非常合适如果你想一套代码通吃训练推理那就别选它。2. 部署前的软硬件准备2.1 硬件安装与供电那些容易翻车的细节Atlas 300V 24G 是标准 PCIe 全高全长卡安装本身不难但有几个细节容易翻车。首先是电源这张卡虽然是推理卡但满载功耗并不低官方建议至少 300W 的电源余量服务器电源通常没问题千万别拿小功率台式机电源硬扛。其次卡上有辅助供电接口至少需要插一路插之前看清楚接口是 PCIe 8 Pin 还是别的规格插错直接烧卡。另一个容易忽略的是散热风道。这张卡是被动散热居多完全靠机箱风道带走热量装进塔式机箱时如果旁边全是硬盘位风道不畅就会触发高温降频。我这次用的是 4U 机架式服务器专门给卡位配了涡轮风扇温度控制在 70 度以内跑长时间高负载压力测试也没有降频。2.2 软件栈驱动、固件、CANN 一个都不能少硬件装好后真正的麻烦才开始。Atlas 300V 24G 的软件栈分三块驱动Driver、固件Firmware、CANN 工具包。CANN 是昇腾的异构计算架构类比起来相当于 CUDA cuDNN TensorRT 的集合体模型转换工具 ATC、推理运行时 ACL 都包含在 CANN 里。安装顺序一定要对先装驱动和固件再装 CANN。驱动和固件通常是一个 run 包里带两个部分或者分开两个 run 包。我这次用的环境是 Ubuntu 20.04.6内核版本官方支持范围有限先确认了内核版本在兼容列表里再动手否则后面各种莫名报错会让人崩溃。安装过程中需要 root 权限装上之后建议建一个专门的用户把设备文件权限处理好。命令大概是wget https://ascend-repo.obs.myhuaweicloud.com/.../Ascend-hdk-*.run chmod x Ascend-hdk-*.run ./Ascend-hdk-*.run --install这里需要注意不同型号的卡对应的驱动固件包名不一样一定要到官网“Atlas 300V 推理卡”的页面下对应版本而不是随便找一个大而全的“昇腾 HDK”包。下错版本的表现通常是驱动装上了但 n pu-smi 看不到卡或者设备健康状态报错。CANN 的安装更讲究版本不能只看数字大小。CANN 分为社区版和商业版社区版在官网能直接下商业版需要权限申请。我们这次用的是 CANN 8.0 社区版配套的 Python 版本建议 3.8 到 3.10太新或者太旧都可能出现 API 对不上的问题。安装 CANN 之前把依赖装好apt-get install -y python3-dev python3-pip gcc g make cmake ./Ascend-cann-toolkit_8.0.RC1_linux-x86_64.run --install安装完成后环境变量要手动 source 或者写进 ~/.bashrcsource /usr/local/Ascend/ascend-toolkit/set_env.sh这一步漏掉的话命令行下 atc 命令会找不到python 里 import acl 也会报模块不存在。2.3 验证环境npu-smi 命令怎么看环境装完千万不要急着跑模型先验证设备状态。npu-smi 是昇腾的设备管理工具类似 NVIDIA 的 nvidia-smi。输入npu-smi info如果能看到类似下面的输出说明驱动和固件正常-------------------------------------------------------------------------------------- | HBM-Health | OK | 100 | | Chip | 0 | 1000 MHz | | Hugepages-Total | 1 | 500 | --------------------------------------------------------------------------------------关键是看芯片状态是不是正常OK温度是不是过高Hugepages 是否被占用。如果 npu-smi 命令不存在说明驱动没装好或者环境变量没配上如果能看到卡但状态是 Fault多半是固件和驱动版本不匹配需要重新刷固件。3. YOLOv5 / YOLOv8 从 ONNX 到 OM 的完整流程3.1 模型导出的关键注意点Atlas 无法直接运行 PyTorch 的 .pt 文件也不能直接跑 ONNX它需要把 ONNX 模型通过 ATC 工具转换成昇腾的离线模型格式 OM。所以第一步是把训练好的 YOLOv5 或 YOLOv8 权重导出成 ONNX。导出时第一个坑是模型版本。YOLOv5 官方仓库里 export.py 会默认导出带 NMS 检测头的 ONNX但昇腾这边通常建议导出不带 NMS 的输出也就是只保留模型 backbone head 的原始输出把 NMS 放到后处理阶段用 Python 做。这样做的好处是模型结构更纯粹ATC 转换时算子兼容性问题会少很多坏处是后处理代码要自己写不过 YOLO 的 NMS 逻辑并不复杂稍后我会给出思路。导出命令大致是python export.py --weights best.pt --include onnx --opset 11这里有个非常影响后续精度的选项opset 版本。我试过 opset 17 导出的模型在 ATC 转换时某些算子不支持反而 opset 11 更稳妥。如果你习惯用 opset 12 以上转换时报“Unsupport op”的概率会明显上升所以建议固定 opset 11。导出的 ONNX 用 netron 打开看一眼确认输入名通常是 images输出名通常是三个尺度的特征图YOLOv5 一般叫 output0/YOLOv8 是 /model.24/Sigmoid_output_0 之类。把输入输出名记下来后面 ATC 配置要用。3.2 AIPP 配置与 ATC 模型转换ATC 是 Ascend Tensor Compiler 的缩写作用类似于 TensorRT 的 model parser把 ONNX 转成 OM 离线模型。转换之前需要准备一个关键文件AIPP 配置。AIPP 是昇腾的图像预处理单元可以在硬件层面完成缩放、减均值、除以标准差、色域转换等操作省去在 Python 里做前处理的开销。YOLOv5 部署常用的 AIPP 配置如下保存为 aipp.cfgaipp_op { aipp_mode: static input_format: RGB888_U8 src_image_size_w: 640 src_image_size_h: 640 crop: true load_start_pos_h: 0 load_start_pos_w: 0 crop_size_w: 640 crop_size_h: 640 csc_switch: true rbuv_swap_switch: false color_space: RGB }这段配置的核心作用是把输入 RGB 图像固定缩放或裁剪到 640x640并完成色域转换。注意这里的 640 要和模型训练时的输入尺寸一致如果你的模型是 1280x1280那这里也要改成 1280。ATC 转换命令示例atc --modelyolov5s.onnx \ --framework5 \ --outputyolov5s_bs1 \ --input_shapeimages:1,3,640,640 \ --insert_op_confaipp.cfg \ --soc_versionAscend310P3 \ --output_typeFP16这里有两个容易踩的坑。第一个是 --soc_version不同型号的卡对应不同的 SoC 版本Atlas 300V 24G 对应的版本通常可以在 npu-smi info 里看到或者查官方兼容列表用错会导致转换成功但模型无法加载。第二个是 --input_shapeONNX 导出时输入是动态的必须在这里固定成静态 batch 和分辨率否则生成的 OM 模型在部分场景下性能很差。转换完成后会在当前目录生成 yolov5s_bs1.om。拿到这个文件整个流程最难的 40% 就已经过去了。3.3 编写推理脚本pyACL 最简流程OM 模型准备好之后接下来用 Python 的 pyACL 接口来加载模型、做推理、读取结果。昇腾的 Python ACL 接口和 CUDA 的 pycuda 用起来逻辑相似但 API 名字完全不同。整个推理流程是初始化 ACL - 设置设备 - 加载模型 - 创建输入输出数据集 - 前处理图像 - 执行推理 - 后处理 NMS。一个最简代码框架如下import acl import numpy as np import cv2 # 初始化 ret acl.init() ret acl.rt.set_device(0) context, ret acl.rt.create_context(0) # 加载模型 model_path yolov5s_bs1.om model_id, ret acl.mdl.load_from_file(model_path) # 获取模型输入输出信息 desc acl.mdl.create_desc() ret acl.mdl.get_desc(desc, model_id) input_size acl.mdl.get_num_inputs(desc) input_shape acl.mdl.get_input_dims(desc, 0) output_size acl.mdl.get_num_outputs(desc) output_shape acl.mdl.get_output_dims(desc, 0) # 准备输入输出内存这里会用到 acl.rt.malloc ...实际代码比这复杂不少因为要手动管理内存缓冲区、用 numpy 把数据拷进设备内存、推理完成后再拷回。我建议封装一个简单的 YOLOInference 类把初始化、预测、释放封装起来避免在业务代码里到处写底层的 acl API。前处理部分虽然 AIPP 配置已经做了缩放但图片从 JPEG 解码到 RGB 数组这一步还是得自己来。先用 cv2 读取再用 cv2.resize 把长边缩放到 640短边填充到 640填充值用 114YOLO 训练时的默认填充值。注意这里填充操作要放到 AIPP 的 crop 之前完成或者说更稳妥的做法是在 AIPP 里关闭 crop直接把 resize padding 后的 640x640 图像送入 AIPP只让它做色域转换和归一化。3.4 后处理与 NMS 的实现思路YOLOv5 的原始输出三组特征图形状分别是 [1, 3, 80, 80, 85]、[1, 3, 40, 40, 85]、[1, 3, 20, 20, 85]其中 85 4 个框坐标 1 个目标置信度 80 个类别概率。后处理要做的是把这三个尺度的特征图摊平成候选框做阈值过滤再做 NMS。对于 YOLOv8输出结构有一点变化是 [1, 84, 8400] 这样的形式也就是把三个尺度合并成 8400 个候选框84 4 80。处理逻辑就变成直接从 8400 个框里筛。我在实现后处理时用了一个被验证很稳的小技巧把输出先转成 float32 再算 sigmoid不要直接在 float16 上做否则少量低置信度框的精度损失会影响最终 mAP。虽然理论上 FP16 推理已经很快了后处理转 float32 不会带来太大性能损耗因为后处理只发生在每一帧的最终输出上而不是神经网络计算的主循环里。NMS 这部分直接用 numpy 写一个非极大值抑制即可不需要引入 torch。批量处理时注意每个 batch 的框数量不同最好先用一个 dict 按类别收集候选框再分别做 NMS。实测下来单张图的后处理耗时在 2 到 5 毫秒左右对实时性影响可以接受。4. 性能调优实录第一版很慢后来靠这几个参数翻倍4.1 第一版跑出来只有不到 20 FPS问题出在哪我第一次把完整流程跑通时用 640x640 输入、batch1测出来的推理延迟在 50 毫秒左右也就是约 20 FPS离 30 FPS 的目标差了一截。当时第一个怀疑是模型优化不够在 ATC 里没有开启混合精度和算子融合。后来查了一遍发现真正的问题在上下文环节——日志级别默认是 INFO昇腾的 ACL 会往 stdout 打印大量 debug 级别的加速信息这些打印本身消耗了不小的 CPU 时间而推理线程被 CPU 抢占导致卡在数据拷贝和同步上。解决办法是初始化 ACL 时把日志级别调成 ERRORimport acl acl.init() acl.log.set_print_level(acl.ERROR) # 或者用环境变量 ASCEND_GLOBAL_LOG_LEVEL3此外我又用export ASCEND_SLOG_PRINT_TO_STDOUT0把系统日志输出关掉这一下子延迟就从 50 毫秒降到了 33 毫秒左右。第二个问题是数据从设备到 CPU 的拷贝用同步方式做的。Python 侧从 npu 读回输出数组时如果频繁调用 acl.rt.memcpy 并且没有合理等待多次同步等待会白白消耗很多时间。改成用 acl.rt.memcpy_async acl.rt.synchronize_stream 的异步方式后又提了 5 到 8 毫秒。4.2 静态 batch 和动态 shape 的取舍Atlas 300V 24G 这种推理卡对静态 shape 最友好。我第一次转换模型时图省事把输入保留成动态 shape也就是 --input_shapeimages:-1,3,-1,-1结果推理速度和稳定性都很差。后来改成固定分辨率、固定 batch1模型转换时能做更多的算子融合和内存复用性能提升非常明显。但固定 batch1 对多路并发是不利的。如果要用同一块卡同时处理 4 路摄像头建议直接转一个 batch4 的模型推理时把 4 帧图像打包成一个输入 tensor。这样做单路延迟几乎不增加整体吞吐量成倍提高。一个经验batch4 的模型显存占用约是 batch1 的 3 倍左右24GB 显存跑 YOLOv8s 的 batch4 毫无压力还能留出不少余量跑后处理。4.3 多路并发与流式处理方案项目最终采用了多路视频流并发方案。我的做法是开 4 个 Python 线程每个线程绑一个推理 context分别把自己的输入图像搬运到设备上然后用一个共享线程池统一执行 acl.mdl.execute模型推理。这里容易忽略的是pyACL 的 context 不能在线程间随意共享。每个线程必须创建自己的 context保证 acl.rt.set_device 在对应线程内调用否则会出现“ACL_ERROR_RT_CONTEXT_NULL”之类的报错。内存方面24GB 显存非常给力4 路 1080p 视频流、每路做 640x640 检测整体显存占用不到 6GB连一半都没用到。这时候甚至可以再堆几路或者把输入分辨率加到 1280x1280 来换取小目标检测精度的提升。4.4 精调AIPP 归一化是否做在硬件里很多人会忽略 AIPP 的归一化参数。YOLOv5 训练时通常用像素值除以 255 进行归一化也就是 [0,1] 区间。如果 AIPP 里配置了 mean 和 variance就要把对应的 mean 设为 0、var 设为 0.00392即 1/255。我一开始没有在 AIPP 里设置归一化导致推理结果全部偏离检测框大面积偏移或者置信度极低。后来把配置改成aipp_op { aipp_mode: static input_format: RGB888_U8 mean_chn_0: 0 mean_chn_1: 0 mean_chn_2: 0 var_chn_0: 0.00392157 var_chn_1: 0.00392157 var_chn_2: 0.00392157 }检测就恢复正常了。这个细节如果不在配置文件里写清楚排查起来特别浪费时间。5. 常见问题速查与排障思路5.1 硬卡检测问题装完驱动 n pu-smi 看不到卡这种情况最常见的三个原因驱动固件版本不匹配、卡没有正确插入 PCIe 插槽、电源供电不足。排查步骤我总结为lspci | grep -i process 看系统是否枚举到设备dmesg | tail -n 50 查内核日志看有没有异常中断或供电错误重新插拔卡并换一个 PCIe x16 插槽电源供电不足时dmesg 通常会报 voltage 或者 power 相关的错误这时候果断换个大功率电源或者用服务器专用供电线。5.2 ATC 转换时报算子不支持YOLOv8 导出的 ONNX 里有时会有一些昇腾不支持的自定义算子比如部分版本的 Focus、Silu、或者某个 PyTorch 版本导出的 Einsum。看到这类错误不用慌先查一下算子列表。我已经验证过YOLOv5s 的 ONNX 在 CANN 8.0 下是可以直接转换的YOLOv8s 的某些导出版本需要先把模型里的 SiLU 激活函数导出时转成其他等价形式或者用更高版本的 CANN 来支持。如果你确实遇到不支持的算子先升级 CANN 版本试试再不行就在模型导出阶段删掉检测头只保留 backbone把检测头的计算全部放到后处理里。另外ATC 转换报错信息有时候很含糊只给一个 operator id。这时候可以用 netron 打开 ONNX找到对应 id 附近的算子确认是不是 Upsample、Resize 这类算子这类算子对 opset 版本敏感。一个减少报错的经验ONNX 导出时固定 opset11不仅解兼容问题还能减少很多转换阶段的内存开销。5.3 推理结果异常全空检测或框严重偏移如果你模型转换成功、推理也能跑但检测结果完全不对先检查 AIPP 配置。最容易出问题的三个点是像素格式模型训练时用的是 RGB 还是 BGRAIPP 里要对应配好或者把 rbuv_swap_switch 设对归一化参数mean 和 var 如果不配对输出置信度会非常低输入尺寸模型训练时是 640x640你用 416x416 去推理检测框必然错位一个排查技巧是先用一张图分别做 CPUtorch推理和 Atlas 推理把两者在模型输出层面的结果对比如果差异很大那就是前处理或 AIPP 的问题如果输出接近但最终框不对那就是后处理的问题。5.4 显存或内存不足Atlas 300V 24G 虽然显存大但如果开太多动态 shape 模型副本或者多路并发时每路都加载一个独立模型同样会触发设备内存不足。我的建议是能合并 batch 就合并能复用模型就复用别为每一路单独加载一个 OM。另外Python 侧如果频繁申请释放设备内存会导致碎片化时间跑久了出现“Out of Memory”但实际显存没满的情况。解决办法是启动时一次性分配好 device memory 池或者循环复用固定的输入输出 buffer。以下是我这次部署过程中遇到的主要问题和对应解法整理成速查表问题现象可能原因解决动作n pu-smi 看不到设备驱动固件版本不匹配 / 供电不足 / PCIe 松动重新安装匹配版本的驱动检查 dmesg 和 lspci换插槽重插atc 命令找不到未 source set_env.shsource CANN 的环境变量脚本并写入 ~/.bashrcATC 转换报算子错误ONNX opset 过高 / 算子版本不兼容固定 opset 11尝试升级 CANN推理结果全空AIPP 归一化或像素格式配置错误检查 mean、var、RGB/BGR 配置推理速度只有 20 FPS日志级别过高 / 同步 memcpy / 动态 shape关闭 INFO 日志、用异步 memcpy、固定 shape运行一段时间报 OOM设备内存碎片化 / 动态分配过多一次性分配内存池、循环复用 buffer线程间共享 context 报错context 不能跨线程共享每个线程创建独立 context这些坑单看都不复杂但串联在一起就很折磨人。尤其是 AIPP 配置这种“看着没问题、一旦配置错完全没结果”的情况建议在实际部署前把 AIPP 相关的参数一个个验证一遍最好能最小化测通。最后再说一个我个人的习惯每次拿到一块新的推理卡我都不会直接上业务模型而是先做一个最简的“单张图推理 输出固定 tensor 形状”的冒烟测试。这个测试跑通之后再去接真实模型、真实业务逻辑。本身昇腾这套软件栈的反馈链条比较长如果一上来就把业务耦合进来出了问题根本不知道是模型的问题、转换的问题还是推理代码的问题。分阶段验证看起来多花了一点时间实际上在后面排查问题的时候至少省了一半的功夫。
返回列表