昇腾Atlas 300V部署YOLOv5s:从模型转换到ACL推理与性能调优
一个半月前我接了个活儿把一个跑在GPU上的YOLOv5s检测服务迁移到一张Atlas 300V 24G推理卡上。当时团队里第一反应是——“atlas不是地图数据库吗”第二反应是——“300V 24G这玩意到底算不算运算加速卡”老实说我拿到卡之前也只在文档里见过它真正上手之后才发现这卡和常见的GPU推理卡在使用逻辑上差得不是一星半点。这篇就把我用Atlas 300V部署YOLO的完整过程、关键参数和踩过的坑写出来给想用昇腾推理卡跑目标检测、又没什么经验的人一个参考。内容覆盖选型对比、环境搭建、模型转换、ACL推理、性能调优这几个环节顺便把“它到底是不是运算加速卡”这个基础问题也一次说清楚。1. Atlas 300V 24G先把它“是不是运算加速卡”这件事说清楚1.1 板卡形态四颗昇腾310P组成一块卡Atlas 300V 24G这个型号官方叫法也常写作Atlas 300V Pro 24GB。它本质上是一张标准的PCIe半高半长推理卡插在服务器上就能用不需要专用机箱散热也就是被动散热加服务器风道。很多人第一次看到npu-smi info输出时会愣住因为一张卡显示出来不是1个设备而是4个NPU设备——没错这块板卡上有4颗昇腾310P AI处理器芯片每颗芯片独立工作。昇腾310P用的是华为达芬奇架构这个架构和GPU的CUDA核心设计思路很不一样。它里面分了几种计算单元AI Core负责矩阵和向量运算另外还有专门做标量运算、数据搬运的单元。对于YOLO这种卷积占比极高的网络来说矩阵算力基本决定了推理速度。这颗芯片定位就是边缘推理和智能视频分析场景低功耗、高能效不追求单芯片极限算力。板上4颗310P凑在一起整卡显存加起来才到24GB。这里要强调一下24GB是4颗芯片各6GB的显存加起来的总和不是24GB统一显存池。1.2 推理定位能跑什么、不能跑什么回到热词里那个问题“Atlas 300V 24G是运算加速卡吗”答案是是运算加速卡但要分清是哪种运算加速。它和NVIDIA的A100、4090这类训练卡/通用GPU完全是两个物种。Atlas 300V是纯粹的AI推理加速卡设计目标是高吞吐跑已经训练好的模型比如YOLO检测、ResNet分类、OCR识别、语音识别这类任务。官方标称的算力是INT8约140 TOPS这个级别FP16也有几十TFLOPS纸面数据非常漂亮。但它不能当图形卡用没有显示输出也不是用来训练模型的虽然理论上能跑训练实际没人这么干软件栈和显存带宽都不适合。它是一款专用推理加速设备类似Google的TPU、寒武纪的MLU而不是通用GPU。1.3 24GB显存的架构账不是统一显存池刚才说了24GB是4颗芯片各自6GB相加。这对部署策略有直接影响如果你要跑一个很大的模型比如YOLOv8x或者更大单颗芯片6GB显存放不下那就要么做模型切片要么把不同推理请求分发到不同芯片上而不是指望一个进程直接用满24GB。一开始我不太习惯这个逻辑后来想想也合理。推理场景追求的是“整体吞吐”4颗芯片相当于4个独立的推理引擎前端用负载均衡把请求撒出去反而比单卡单模型更容易打满吞吐。实际用的时候npu-smi info里能看到4个device编号通常是0到3每个device有自己的显存占用、温度、算力利用率。写推理程序时要么指定device id要么用多进程每个进程绑一个device。2. 拿Atlas跑YOLO的选型理由功耗、成本和生态的三笔账2.1 功耗账75W板卡和200W GPU的差距部署现场的机房条件决定了你不能光看算力。一个客户现场可能机柜供电有限、散热条件一般插一张350W的GPU和插一张最大功耗75W左右的Atlas 300V差别非常大。我在实测时观察过跑满4颗芯片做YOLOv5s INT8推理板卡整体功耗也就60多瓦满载不超过75W。而一张T4是70W一张A10是150WRTX 4090直接奔着450W去了。也就是说同样跑一个视频分析业务用Atlas 300V的话一个机箱里能塞进更多卡供电压力小很多散热要求也低。2.2 成本与部署形态国产化替代需求是目前很多项目选昇腾的核心原因这里不展开政策层面的东西单说硬件本身的性价比。Atlas 300V Pro 24G这种卡的市场定位和T4差不多但INT8推理吞吐在同价位段上并不吃亏甚至在部分模型上能打出更高的帧率。对于目标检测这种INT8友好型任务昇腾的达芬奇架构在INT8上的吞吐表现是相当不错的。部署形态也灵活。它是标准PCIe卡x86服务器能插鲲鹏/昇腾服务器也能插不需要专用供电线插上就能识别。我们当时用的是一台双路x86服务器插了两张Atlas 300V一共8个推理NPU设备跑8路视频流做实时YOLO检测CPU占用还很低。2.3 也别回避软件生态差距说句公道话昇腾的软件生态和CUDA比确实有差距但不是不能用而是用起来“思维方式不一样”。CUDA生态是“什么都有你随便挑”昇腾生态是“官方给了一条主路你沿着走就顺想走野路子就会难受”。主路就是模型先转成ONNX再用ATC工具转成.om格式最后用官方推理引擎或AscendCL接口调用。只要老老实实按这条链路走官方文档和案例基本能覆盖90%的问题。一旦想搞些骚操作比如自定义算子、复杂动态shape、训练推理混合调度那就要做好熬几个通宵的准备。这个生态现状决定了选型时的一个判断标准如果你的业务是成熟的CNN推理比如YOLO检测、分类、分割Atlas完全够用如果你要跑大模型、做训练、频繁改模型结构那还是老老实实用GPU。3. 从PyTorch权重到NPU可执行模型转换与部署完整链路3.1 底软准备驱动、固件、CANN版本对不上就全白搭昇腾的软件栈有三层驱动Driver、固件Firmware和CANNCompute Architecture for Neural Networks。驱动和固件管设备CANN是上层的计算库和工具链类似CUDA Toolkit。安装顺序不能乱先装驱动再烧固件最后装CANN。装完用npu-smi info检查能不能看到4个NPU设备。如果这一步就报错后面全都不用谈。我这边的环境是Ubuntu 20.04 x86_64CANN用的7.0.RC1那版配套的驱动和固件版本号在昇腾社区的“版本配套表”里能查到。切记不要自己随便混搭版本。比如CANN 7.0和老的驱动5.1搭配npu-smi info经常显示正常但ATC转换或者推理时会报一些莫名其妙的错误比如E19999内部错误或者aclrtMalloc failed排查一整天最后发现就是版本不匹配。安装完成后记得source一下CANN的环境变量source /usr/local/Ascend/ascend-toolkit/set_env.sh如果你用root安装默认装在/usr/local/Ascend/ascend-toolkit下。建议把source写进~/.bashrc不然每次开终端都要手敲。3.2 导出ONNX把网络的“形状”固定下来昇腾跑的是.om模型.om从哪儿来从ONNX来。我们用YOLOv5做例子首先要把PyTorch权重导出为ONNX。YOLOv5官方仓库自带导出脚本python export.py --weights yolov5s.pt --include onnx --opset 11这里最关键的参数是--opset。昇腾ATC对ONNX算子支持是按opset版本走的老版本比如opset 11支持最稳新版本虽然也在跟进但偶尔会碰到某个算子不支持的问题。我建议先按opset 11导出如果转换报错再考虑升级opset。导出时还有一个必须注意的地方输入shape要固定。YOLOv5导出时默认是动态shape--dynamic默认是False但有些代码仓库或者自己改过的模型会带上动态维度。ATC转换动态shape虽然也支持但配置麻烦、性能差不如直接固定model.model[-1].export True # 将Detect模块的export设为True导出时用固定shape导出之后用onnx.checker或者直接onnxruntime跑一遍确认ONNX本身没问题再往ATC走。这一步别看简单很多人忽略导致后面出问题分不清是导出问题还是转换问题。3.3 ATC转换一条命令背后的参数逻辑拿到ONNX之后用ATC工具转成.om。昇腾的ATC全称是Ascend Tensor Compiler它负责把ONNX/Caffe/TensorFlow模型转换成昇腾芯片能高效执行的离线模型。我用的核心命令如下atc --modelyolov5s.onnx \ --framework5 \ --outputyolov5s_bs1 \ --soc_versionAscend310P3 \ --input_shapeimages:1,3,640,640 \ --input_formatNCHW \ --loginfo逐项解释一下--framework55代表ONNX这个数字是固定的别改。--soc_versionAscend310P3这个要特别小心必须和板卡芯片型号对应。Atlas 300V Pro 24G用的是昇腾310P3芯片所以写Ascend310P3。如果你拿不准可以看npu-smi info里每个NPU设备的芯片型号或者查官方文档里的“处理器型号-Atlas系列推理卡”对照表。--input_shape要和你导出的ONNX输入节点名、shape完全一致。YOLOv5的输入节点名默认是imagesshape是1,3,640,640。如果你训练时用的是其他输入尺寸这里也要对应改。Input节点名可以用onnxruntime或者netron查看。--input_formatNCHWONNX导出的默认是NCHW一般不用改。转换成功后会生成yolov5s_bs1.om文件。如果报算子不支持多半是opset版本问题回去重新导出如果报shape不对就检查--input_shape和ONNX的输入是否一致。3.4 推理调用msame验证和ACL接口落地模型转好之后先用官方工具msame快速验证一下能不能跑通。msame --model yolov5s_bs1.om \ --input images.bin \ --output ./outputmsame会返回单次推理耗时和输出文件路径。输出文件是二进制raw格式需要用Python自己解析。这一步主要用来确认模型转换没问题以及看个大概性能真正落地还是要用AscendCLACL接口写推理服务。ACL是昇腾的C语言API也有Python封装。整体使用流程比CUDA简单很多acl.init()初始化acl.rt.set_device(0)绑定NPU设备acl.mdl.load_from_file()加载.om模型acl.mdl.create_desc()创建模型描述信息拿到输入输出buffer大小acl.rt.malloc()分配设备内存acl.mdl.execute()执行推理把输出拷贝回主机内存做后处理实际项目中我建议把推理部分做一个简单的C服务或者用Python的pyACL配合队列做并发。Python调ACL的动态库开销很小性能瓶颈主要在模型执行本身所以Python做推理服务完全可行开发效率高很多。我自己是把YOLO的后处理——解码、NMS、画框——写成了纯Python模块推理部分用pyACL整个服务跑在Flask上压测下来单路视频流完全没压力。3.5 NMS放哪跑几个可行方案的取舍这是昇腾部署YOLO最容易踩坑的地方。GPU上跑YOLONMS一般就放在TensorRT或者torchvision里顺手做了但ONNX导出的YOLO模型输出是解码前的原始预测——通常是1, 25200, 85这种形状包含每个anchor的坐标、置信度和类别概率。ATC转换不会帮你做NMS.om模型的输出依然是这些原始预测值。所以NMS必须自己在CPU上实现或者用昇腾提供的算子拼。我的建议是优先做CPU NMS。YOLOv5s的候选框也就25200个单帧NMS用向量化NumPy或者普通Python循环也就几毫秒相比NPU推理时间完全可接受。直接上一个轻量级NMS实现比如把所有框置信度排序按顺序抑制IoU大于阈值的框80个类别处理完整个流程还能稳定在2-3ms以内。如果你实在想在NPU上做NMS昇腾CANN里有一个NMS算子可以尝试但要自己写算子融合图调试成本高。还有一个思路是使用MindX SDK它自带后处理插件不过配置起来也不省心。4. 实测数据和调优记录什么样的吞吐值得开心4.1 基线实测先把每个环节的耗时拆开我拿YOLOv5s、输入640x640、转成INT8的.om模型做了一轮基线测试。先说明测试环境是双路x86服务器CANN 7.0.RC1单设备推理。单张图片单次推理NPU执行时间大约是2.5-4毫秒换算成单设备FPS大概250-400之间。注意这里说的是单颗310P芯片整卡4颗芯片如果同时跑理论吞吐能到1000FPS当然这是纯模型推理时间不含前后处理。这个成绩什么概念呢T4上用TensorRT跑YOLOv5s FP16单卡大概300-500 FPS算下来Atlas 300V的INT8性能并不差考虑到功耗差距性价比是真的能打。但基线数据只是参考实际业务不可能只算模型推理。整个链路是摄像头拉流 - 解码 - 缩放Resize - 归一化 - NPU推理 - 后处理NMS - 画框推流我测试时发现图像预处理反而成了瓶颈。用Python PIL或OpenCV做resize和归一化单帧要花5-8ms比NPU推理还慢。这是非常典型的现象。4.2 调优三板斧批量、AIPP、算力拆分第一板斧加大batch size。.om模型转出来是固定batch的。如果你转的是--input_shapeimages:1,3,640,640那一次只能推理一张。但如果场景允许比如做离线批量检测可以转成batch 4或batch 8然后在代码里攒够一个batch再送进去推理。我实测batch 4比batch 1的时延只增加了不到50%但吞吐翻了接近3倍。对于视频分析业务攒batch会牺牲一点点时延但对吞吐型场景非常值得。第二板斧用AIPP把预处理下沉到NPU。AIPP是昇腾的图像预处理模块可以在ATC转换时配置让NPU直接处理原始图像数据包括缩放、色域转换、归一化等省掉CPU上的OpenCV预处理。配置一个aipp.cfg里面可以指定aipp_op { aipp_mode: static input_format: RAW_RGB888_U8 src_image_size_w: 640 src_image_size_h: 640 csc_switch: true rbuv_swap_switch: true # BGR转RGB min_chn_0: 0 min_chn_1: 0 min_chn_2: 0 var_reci_chn_0: 0.003921569 var_reci_chn_1: 0.003921569 var_reci_chn_2: 0.003921569 }配置好之后ATC转换时加上--insert_op_confaipp.cfg这样输入给NPU的就直接是原始图片数据比如JPEG解码后的RGB buffer归一化在NPU内部完成。注意如果用了AIPP你写推理程序时的输入数据格式就变了要按AIPP配置喂数据。第三板斧把4颗芯片分开调度。整卡4颗芯片如果只用一个device其他三个就闲着。我们的做法是起4个推理进程每个进程绑定一个device id前端用一个队列做分发。这样整卡吞吐才是完整的。注意进程绑设备不是自己写个循环就行AscendCL在进程初始化时通过set_device绑定绑了之后这个进程的所有acl.mdl调用都会落到指定设备上。一个进程内部不要跨device否则内存拷贝开销巨大。4.3 调优后的结果与判断做了上面三板斧之后实际效果单设备YOLOv5s INT8batch 4纯推理约8-10ms/4帧平均单帧2-2.5ms加了AIPP之后CPU预处理时间从每帧5-8ms降到几乎为零仅剩JPEG解码整卡4设备并发跑4路1080p视频流做实时检测端到端每路稳定在25FPS以上CPU占用不到30%这个结果已经足够满足大多数安防、交通、工业质检场景的实时性要求。5. 部署中不得不提的几个坑每个都让项目晚一周5.1 版本匹配与驱动固件烧写昇腾的设备管理比较严格。新卡第一次上机如果没烧固件npu-smi info可能会显示“Device unhealthy”或者干脆看不到卡。需要先装驱动然后执行固件升级/usr/local/Ascend/driver/tools/upgrade-tool --device_index -1 --component firmware --upgrade --file xx.fw这个操作要小心烧固件过程千万不要掉电否则板卡可能变砖。我一开始以为装完驱动就万事大吉结果固件没烧设备一直报E40012折腾了一天才发现是固件版本太老。5.2 预处理不一致GPU上好好的NPU上检测全糊这是最隐蔽的坑。GPU上跑PyTorch时数据预处理是letterbox RGB /255。但ONNX导出的模型里并没有包含这些预处理逻辑如果你在NPU侧喂进去的是没有归一化的原始数据模型输出就会完全乱掉检测框置信度全部逼近0或者画出一些莫名其妙的框。我建议的做法是在喂给NPU之前严格按照训练时的预处理流程处理数据并且先用单张图片验证。官方也提供了一种方式就是用ATC的--insert_op_conf配AIPP来做预处理但AIPP的src_image_size_w/h和crop参数如果和训练设置不一致效果也会出错。所以每次改模型都要先跑一张验证图确认输出的检测结果和GPU上有可比性再做批量测试。5.3 动态shape的限制与新版本昇腾的.om模型虽然支持动态shape但限制很多。比如动态维度只能用于batchH和W的动态范围需要配置dynamic_dims而且会被限制在特定档位。大部分YOLO部署建议固定输入尺寸。如果你的业务需要多分辨率输入最稳妥的办法是转换多个不同分辨率的.om模型运行时根据输入情况选择对应模型。Atlas 300V Pro板载内存24G多放几个模型文件完全没问题。另外建议用较新的CANN版本比如6.3以上动态shape的支持会好很多ATC转换时报错的概率也低一些。5.4 npu-smi查不到设备或报错设备不识别是最常见的第一道坎通常由以下几个原因导致卡没插好或者供电不足重新插拔确认PCIe链路正常驱动没装对用npu-smi info前先确认lsmod | grep drv_pcie之类的驱动模块是否加载与GPU卡混插在某些服务器上存在PCIe资源冲突需要调整BIOS里的PCIe拆分模式如Above 4G Decoding开启服务器内核版本过新或过老昇腾驱动对内核版本敏感卸载重装前先确认内核版本在官方支持列表内如果npu-smi info能出来但显示HwChip错误多半是固件版本问题按前面的步骤重新烧固件。最后再分享一个我自己用着的经验昇腾的运行时日志默认在/var/log/ascend下面部署遇到问题别瞎猜先去翻日志。比如模型加载失败plog里会明确告诉你哪个算子不支持、哪块显存分配失败。排查效率能提高一大半。另外新卡上手先不要按着GPU的思维去调优把转换链路跑通、把预处理对齐、把后处理接上整个流程是通的再一步步看性能瓶颈在哪。我这次从拿到卡到全流程跑通用了大概三天之后基本就顺了现在这套服务已经稳定跑了一段时间没出过幺蛾子。