ARTICLE DETAIL

资讯详情

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

Atlas 300V 24G部署YOLO:模型转换、推理优化与高能效实践

Atlas 300V 24G部署YOLO:模型转换、推理优化与高能效实践 1. 先说清楚Atlas 300V 24G到底算不算“运算加速卡”1.1 一张容易让人误判的卡最近做AI推理项目手里分到一张Atlas 300V 24G的卡。拿到手第一反应是——这块卡怎么这么轻、这么小半高半长的PCIe卡单槽位没有外接供电装在服务器里跟一张普通网卡差不多。也正因为外形太“朴素”很多第一次接触的人都会问同一个问题Atlas 300V 24G是运算加速卡吗答案是是而且是一张定位非常明确的专用推理加速卡。它用的是昇腾310P系列芯片整卡24GB显存功耗控制在几十瓦这个量级靠PCIe插槽供电就能跑。它不能像NVIDIA的CUDA那样做通用计算也不适合直接拿来训练大模型但在目标检测、图像分类、OCR、视频结构化这类推理场景里它的能效比非常能打。说白了这张卡就是冲着“把模型跑起来、跑得快、跑得省电”这三个目标去的。很多做算法的人第一次接触昇腾生态都会有个认知落差模型训练还是用GPU顺手到了部署阶段才意识到推理侧对功耗、体积、成本的敏感度远高于训练侧。一张300V 24G插在x86服务器上整机功耗增量很小却能把YOLO这类模型的推理时延压到很低的水平。这就是它存在的意义。1.2 跟GPU比差在哪、好在哪既然聊到运算加速卡就绕不开CUDA对比。这里说点大实话Atlas 300V 24G和NVIDIA的推理卡比如T4、A10不是同一个赛道上的产品强行对比参数意义不大但有几个关键差异值得关注。先说差距。最直接的差距在软件生态上PyTorch、TensorFlow对CUDA的支持是原生的而昇腾这边要么用MindSpore、要么用ONNX模型转换到CANN框架中间多了一道适配工序。如果你只会在PyTorch里调模型从来没有碰过模型转换那上手昇腾卡的第一周会有点痛苦。再说优势。24GB显存这个指标在同级别推理卡里非常亮眼。很多边缘侧的推理卡只有8GB、16GB显存跑大分辨率模型或者多batch推理时捉襟见肘。300V 24G意味着你可以把YOLOv5这种模型的分辨率拉到1280甚至更高也可以一次往卡里塞8张、16张图做批量推理显存不再是瓶颈。再加上它的功耗只有几十瓦一台2U服务器插4张卡总功耗比一张高功耗GPU还低散热压力小很多。所以我的建议是如果你做的是纯GPU通用计算、训练、或者需要CUDA生态里那些复杂的第三方库老老实实用GPU。但如果你就是想把训练好的检测模型部署到机房跑视频流、跑图片批处理要求低功耗、低延迟、高吞吐那Atlas 300V 24G完全值得认真评估。2. 跑YOLO前的准备环境搭建与模型转换链路2.1 驱动、固件和CANN的版本匹配昇腾卡的软件栈和NVIDIA有很大区别。GPU装个驱动、装个CUDA、配好PyTorch就能跑昇腾这边则需要一套相对完整的环境驱动、固件、CANN工具包三者版本必须对齐。版本不匹配是新手最容易踩的坑。我见过不少人在论坛里提问“为什么npu-smi能看到卡但是跑模型报错”最后排查下来都是驱动和固件版本不一致导致的。这块卡的管理工具是npu-smi装好驱动之后可以先跑一下npu-smi info确认设备状态。CANN是昇腾的计算平台类似CUDA的角色但比CUDA更偏上层。它提供了模型转换工具ATC、推理运行时ACL、各种加速库。安装CANN的时候要注意版本对应关系官方文档里有一张很详细的兼容性列表驱动版本、固件版本、CANN版本、甚至操作系统版本都有约束。我的经验是不要追求最新版本找一套经过验证的组合稳定用下去。生产环境最怕的不是功能少而是环境升级之后模型转换链路出问题。安装完成后环境变量也要配好source /usr/local/Ascend/ascend-toolkit/set_env.sh这一步容易忽略不source的话atc命令和Python的acl库都找不到。建议写进~/.bashrc里省得每次手动执行。2.2 从PyTorch权重到OM模型一次完整的ATC转换环境准备好之后最核心的步骤就是把YOLO模型从PyTorch权重转成昇腾的OM格式。OM是昇腾的模型格式类似TensorRT的engine文件转换完之后推理时不需要再依赖PyTorch。以YOLOv5为例完整链路是pt权重导出为ONNX再用ATC工具转成OM。第一步在PyTorch环境下完成import torch model torch.load(yolov5s.pt, map_locationcpu)[model].float() model.eval() dummy_input torch.zeros(1, 3, 640, 640) torch.onnx.export( model, dummy_input, yolov5s.onnx, opset_version11, input_names[images], output_names[output] )这里有一个细节导出ONNX时opset版本不要太新很多转OM的算子兼容问题都是opset版本过高引起的。opset 11是个比较稳妥的选择算子支持和兼容性都经过大量验证。ONNX转OM这一步在昇腾环境里执行核心命令是atcatc --modelyolov5s.onnx --framework5 --outputyolov5s \ --input_shapeimages:1,3,640,640 \ --soc_versionAscend310P3 \ --insert_op_confaipp.cfg参数说明--framework5表示输入是ONNX模型--output指定输出的OM模型路径--input_shape定义输入张量的shape这里batch为13通道640x640--soc_version指定芯片型号要先用npu-smi info确认具体是310P的哪个版本--insert_op_conf插入AIPP配置文件这个后面单独说转换成功的标志是终端输出类似“ATC run success”的信息同时目录下多出一个.om文件。如果中途报错多半是算子不支持或者某个opset版本问题这时候优先查CANN的算子支持列表确认YOLOv5里的算子是否都被覆盖。常见的需要关注的算子类型有上采样、拼接、激活函数这些在昇腾上都有优化过的实现但前提是模型转换时能正确映射。2.3 AIPP配置把预处理挪进模型里熟悉推理部署的人都知道图像输入模型之前要做resize、归一化这些操作。在GPU上这套流程通常在PyTorch或者TensorRT的前后处理代码里完成但昇腾提供了一种更优雅的方式AIPPAI Preprocessing。AIPP可以配置色域转换、缩放、裁剪、均值方差归一化操作让这些计算在模型内部完成而不是在CPU上跑。好处很直接推理一张图时CPU端省掉前处理计算整体吞吐能提升一截。下面是一个针对YOLOv5的AIPP配置示例aipp_op { aipp_mode: static input_format: RGB888_U8 src_image_size_w: 640 src_image_size_h: 640 crop: false 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 }这个配置做了两件事一是声明输入图像是RGB格式的8位无符号整数二是把每个像素值乘以0.003921569也就是除以255完成归一化。注意YOLOv5原始训练时归一化方式就是除以255不需要额外减均值所以mean全为0。用了AIPP之后CPU端只需要把图像数据整理成RGB排列的数组传进模型其他都交给卡处理。这里有个容易翻车的点AIPP配置里的input_format必须和模型转换时定义的输入格式一致。如果模型输入是NCHW排列而AIPP配置成RGB888_U8的NHWC数据推理结果就会乱掉。我在第一次配置时就在这个细节上栽过跟头输出检测框全部偏移排查了半天才发现是通道顺序的问题。3. 推理代码怎么组织pyACL调用全流程3.1 设备初始化和模型加载OM模型转换完成接下来就是写推理代码。昇腾推理有两套主流方式一是用MindSpore Lite的Python API二是直接用pyACL。我建议直接用pyACL虽然接口偏底层一点但可控性最强出问题的时候也更容易定位。pyACL的调用流程和CUDA runtime API有相似之处先初始化、再申请资源、加载模型、执行推理。基础框架如下import acl # 初始化 ret acl.init() assert ret 0 # 设置设备、创建上下文和流 ret acl.rt.set_device(0) assert ret 0 context, ret acl.rt.create_context(0) assert ret 0 stream, ret acl.rt.create_stream() assert ret 0 # 加载OM模型 model_id, ret acl.mdl.load_from_file(yolov5s.om) assert ret 0 model_desc acl.mdl.create_desc() ret acl.mdl.get_desc(model_desc, model_id)这套初始化代码几乎每个推理程序都会用到可以直接复用到其他模型上。需要留意的是acl.rt.set_device的参数是设备ID多卡服务器上每张卡对应一个ID用npu-smi info查看卡号和ID的对应关系。模型加载之后推荐把model_desc里的输入输出维度信息打印出来确认它和ATC转换时定义的shape一致。这一步别省因为不同版本的YOLO模型输出维度差异很大比如YOLOv5的输出通常是(1, 25200, 85)如果后处理代码写的是固定维度模型版本一换就容易出问题。3.2 输入输出的内存管理最容易翻车的地方推理代码的骨架写完之后真正见功夫的是内存管理。pyACL要求数据在设备侧也就是NPU的显存里所以要把CPU上的图像数据拷贝过去。这一步涉及到device内存的申请和释放是出错率最高的区域。先看一段标准的输入处理import numpy as np from PIL import Image # 申请device内存 input_size 1 * 3 * 640 * 640 # 根据模型输入动态计算 input_data, ret acl.rt.malloc(input_size, 2) # 2表示内存对齐为2MB assert ret 0 # 图像转成RGB数组 img Image.open(test.jpg).convert(RGB).resize((640, 640)) img_array np.array(img).astype(np.uint8) # 把数组写入device内存 ret acl.rt.memcpy(input_data, input_size, img_array.tobytes(), img_array.nbytes, acl.rt.MEMCPY_HOST_TO_DEVICE) assert ret 0这段代码里最容易忽视的是图像数据的内存排列。PIL读出来的数组是HWC排列也就是(640, 640, 3)而YOLOv5 ONNX模型的输入是NCHW排列即(1, 3, 640, 640)。如果你在ATC转换时没有做特殊处理CPU端需要先把HWC转成CHWimg_array np.ascontiguousarray(img_array.transpose(2, 0, 1))有些教程会让你用np.transpose之后直接传给memcpy但transpose返回的数组可能需要重新确保内存连续加上ascontiguousarray更稳妥。如果忘了这一步数据拷贝虽然不会报错但推理结果会完全错乱。推理结束后的输出读回和输入类似用acl.mdl.get_output_data_size拿到输出大小申请device内存执行推理再把结果拷回CPU。整个过程的内存都要记得释放acl.rt.free(input_data) acl.rt.free(output_data) acl.mdl.unload(model_id) acl.rt.destroy_stream(stream) acl.rt.destroy_context(context) acl.rt.reset_device(0) acl.finalize()内存泄漏在推理服务里是个隐形炸弹。长时间运行的服务如果每张图泄漏一点内存几十万张图之后系统就会因为内存不足崩溃。所以建议在压测之前先跑个几千张图的循环观察内存曲线是否平稳。3.3 推理与YOLO后处理的衔接模型执行推理的代码很简洁# 异步执行推理 ret acl.mdl.execute_async(model_id, [input_data], [output_data], stream) assert ret 0 ret acl.rt.synchronize_stream(stream) assert ret 0这个调用的输入和输出参数都是指针列表即使模型只有一个输入一个输出也要包成list。同步流的操作必须有否则异步执行还没完成就去读输出拿到的就是未初始化的内存。拿到输出之后剩下就是YOLO的后处理。以YOLOv5为例输出数据排列大致是(1, 25200, 85)含义是每个候选框对应85个值4个坐标、1个置信度、80个分类概率。在CPU端做解析output_array np.frombuffer(output_bytes, dtypenp.float32).reshape(1, 25200, 85) boxes output_array[0] # 置信度过滤 conf_mask boxes[:, 4] 0.5 candidate boxes[conf_mask] # 提取坐标和类别 xywh candidate[:, :4] obj_conf candidate[:, 4:5] cls_conf candidate[:, 5:] cls_ids cls_conf.argmax(axis1) cls_scores cls_conf.max(axis1)后处理看起来不复杂但它在整个推理链路中占用的耗时比例往往被低估。我在实测中发现当NPU推理本身只要几毫秒时如果后处理用纯Python逐框解码CPU就成了新的瓶颈。优化思路有两个一是尽量用numpy的向量化操作替代for循环比如用numpy计算所有候选框的面积、坐标偏移把NMS也向量化。二是如果单帧后处理还是不够快可以考虑用Cython或者直接把后处理下沉到C实现Python只做胶水层。4. 实测效果与调优记录4.1 一张24G卡能跑出什么水平在模型转换和推理代码都跑通之后我做了几轮实测。测试环境是一台双路服务器插一张Atlas 300V 24G模型是YOLOv5s输入分辨率640x640batch为1。先说结果单张640x640图片的端到端时延在几毫秒到十几毫秒之间具体数值和驱动版本、CANN版本、CPU后处理代码都有关系。但抛开具体数字不谈有两个趋势非常明显一是芯片利用率很稳定长时间跑满负荷时功耗也压得住二是时延非常平稳不像GPU那样偶尔出现明显抖动。这个特性对视频流检测场景尤其重要时延抖动意味着丢帧或者积压。另一个让我意外的是24GB显存的冗余度。跑单路YOLOv5时显存占用通常只有几个GB大量显存是空闲的。一开始我觉得有点浪费后来把batch调大、分辨率调高之后才意识到这块卡的设计目标不是跑单路小模型而是同时扛住多路视频流或者高分辨率输入。24G显存的价值在这种场景下才真正发挥出来。4.2 把卡吃满的三个方向多batch、异步流和DVPP如果只是单路推理跑通那这张卡的大部分算力其实被浪费了。想要把24G显存和整卡算力吃满我实际测试下来有三个方向最有效。第一个方向是多batch推理。YOLOv5的ONNX模型在ATC转换时可以指定batch大于1比如一次输入8张图。这样模型在NPU上的计算效率更高因为算子执行时数据复用更好单位时间处理的图片数量能接近线性增长。改法很直接转换时把--input_shape从1,3,640,640改成8,3,640,640推理代码里把8张图拼成一个numpy数组一次性拷贝到device。需要注意的是batch增大后输出维度也会等比例变化后处理代码要做相应适配。第二个方向是使用异步流。pyACL提供了acl.rt.create_stream创建多个流的能力。用流水线模式一个流负责当前一帧的推理另一个流负责下一帧的数据拷贝两者交替执行等待时间就能隐藏起来。多stream的代码比单流复杂一些但收益明显。我在实测中把推理和数据搬运拆到两个流上端到端时延比单流模式又降了一大截。第三个方向是DVPP硬件解码。如果输入源是视频流比如RTSP流或者本地视频文件解码工作量非常大。昇腾卡上的DVPP模块支持硬件JPEG解码和视频解码可以把CPU从解码任务中解放出来。代码上比直接调用OpenCV复杂但CPU占用率会明显下降整体吞吐提升显著。如果能接受代码复杂度这块值得投入时间。5. 常见问题排查实录5.1 模型转换报错的几种典型情况ATC转换是出问题最多的环节我把遇到过的典型错误整理成一个速查表错误类型典型提示原因解决方案算子不支持Unsupported op: XXX模型里有CANN不支持的算子查算子支持列表换opset版本重新导出ONNX或者用MindSpore重写相关结构版本不匹配soc version mismatch--soc_version填错用npu-smi info确认芯片具体型号输入shape错误input shape mismatch--input_shape和模型实际输入不一致用netron查看ONNX模型的输入节点信息内存不足out of memory模型太大或分辨率太高降低输入分辨率或检查命令行里的临时目录空间我遇到最多的是第一个算子不支持。YOLOv5的很多结构在ONNX导出时会拆成多个基础算子其中某个算子如果CANN不支持整个转换就失败。这时候有两个思路一是降低ONNX的opset版本很多新算子只是语法糖低版本opset会拆解成基础算子恰好这些算子CANN反而支持二是查看报错信息中具体是哪个op不识别回到PyTorch里对这个结构做调整比如把某些自定义模块改成更标准的实现。5.2 推理异常的定位思路模型转换成功、代码跑通不代表推理结果就是对的。我调试YOLO输出时遇到过几个典型的异常输出全为零、检测框乱飞、置信度值异常大。输出全为零的第一反应是看输入数据有没有正确传到device侧。先在CPU端打印一遍输入数组的均值、方差确认图上有内容然后检查memcpy的方向是不是写反了HOST_TO_DEVICE这个枚举值是否写成了DEVICE_TO_HOST。检测框乱飞则优先查两个东西一个是AIPP的配置确认input_format和模型输入格式一致尤其是RGB和BGR的顺序第二个是后处理解码时坐标的计算公式YOLOv5的坐标是相对于输入尺寸的如果映射回原图时没有按缩放比例还原框的位置就会偏移甚至跑到画面外。置信度值异常大或者全部都是NaN通常是归一化方式重复了。比如AIPP里已经除以255后处理代码里又除了一遍数据就会变得非常小反过来如果AIPP没配归一化而后处理按归一化后的数据解码置信度就会非常大。这类问题通过打印原始输出数组能快速定位。5.3 性能不达标的排查顺序最后聊聊性能问题。如果你跑出来的吞吐和时延远低于预期我的排查顺序是固定的。第一看CPU占用率。如果CPU占用接近100%而NPU利用率不高说明瓶颈在前后处理优先优化图像解码和后处理代码比如用numpy向量化、或者用DVPP替换OpenCV解码。第二看NPU利用率。用npu-smi info持续观察卡的状态如果利用率低于50%说明模型太小或者batch太小计算单元没被填满优先增大batch。第三看数据拷贝耗时。如果模型推理本身只有几毫秒但每次推理都花大量时间在内存拷贝上就要考虑用异步流或者内存复用不要每次推理都重新申请、拷贝、释放。第四看是否开了多进程。昇腾卡支持多进程并发如果有多个CPU核心空闲可以尝试用多进程跑多个推理进程。但注意每个进程都要自己初始化ACL、加载模型、管理内存进程之间的资源要合理分配。这套排查顺序帮我解决过不少性能问题。如果你也遇到类似情况强烈建议按这个顺序走一遍大部分问题都能定位到具体环节。6. 最后说点实在的从拿到板卡到完整跑通YOLOv5这个过程中我最大的感受是昇腾生态的学习曲线确实比CUDA陡但一旦把环境、转换、推理这条链路理顺它给你的回报也非常直接——低功耗、大显存、稳定的推理时延这些恰好是生产环境最看重的指标。如果你现在正准备在Atlas 300V 24G上部署YOLO我的建议是先别急着写业务代码花半天时间把环境搭对、把模型转换链路走通、把官方的样例程序跑起来再开始改自己的模型。磨刀不误砍柴工这半天时间能省下后面好几个通宵。另外再分享一个小技巧调试阶段不要直接在服务器上改代码先在本地把ONNX模型导出、用onnxruntime验证输出维度再到昇腾环境上转OM、验证推理结果。这样出了问题至少能快速定位是模型的问题还是卡的问题不用来回折腾环境。
返回列表