ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Atlas 300V 24G加速卡部署YOLOv5实战:从环境配置到推理调优全记录

Atlas 300V 24G加速卡部署YOLOv5实战:从环境配置到推理调优全记录 如果你搜Atlas 300V 24G 是运算加速卡吗大概率是卡已经到手了或者正在对比选型。我先给结论它确实是运算加速卡但运算加速这四个字得拆开看——它用NPU做神经网络推理加速不是GPU不是普通显卡不负责接显示器它的长项是把已经训练好的模型比如YOLO、ResNet这类CNN跑得又快又稳。前一阵我在一个视频结构化项目里用一张Atlas 300V 24G把YOLOv5推理任务完整落到了生产环境。从环境安装、模型转换、推理代码到调优前前后后折腾了一周多。这篇文章就把这次实战过程完整记录下来给后来人当个参照。1. Atlas 300V 24G到底是张什么卡先回答热搜里那个问题1.1 它是NPU不是GPU也不是训练卡很多人第一次接触Atlas时最容易产生的误解是把它当成另一种显卡。从外形看它确实像显卡PCIe接口、散热器、半高半长单槽设计插在服务器主板上就能用。但它的内部结构和定位完全不是一回事。Atlas 300V 24G用的是昇腾310P芯片这是一颗典型的AI推理SoC。所谓推理Inference是把训练好的模型权重加载到芯片上对新的输入数据做前向计算。它不负责训练模型即不做反向传播、梯度更新这类重活所以你不能指望拿它去训一个YOLO或者Transformer。训练这种任务需要的是大规模并行算力和高速显存带宽那是训练卡和GPU集群的领域。它也没有显示输出接口。你插上这张卡之后想往上面接个显示器看画面大概率是不行的。它被设计成协处理器角色CPU负责整个程序流程和复杂逻辑这张卡只负责把神经网络里的卷积、矩阵乘、激活函数这些算子快速算完。我个人的理解是它就是一台AI计算引擎输入是模型和预处理好的张量数据输出是每个类别的置信度、检测框坐标、语义分割结果这类推理结果。摸清了这一条后面写代码时思路就顺了。1.2 24G大显存对推理意味着什么Atlas 300V 24G的24G指的是板载的24GB LPDDR4X内存在推理卡里属于非常大的配置。推理卡和训练卡不一样的地方在于它的内存主要用于存放模型权重、中间特征图、输入输出缓冲而不是像训练那样频繁读写超大batch的数据。24G能存下什么级别的模型以YOLOv5系列为例参数量较大的YOLOv5l、YOLOv5x也能完整装入而且还有富余空间跑比较大的batch。对于现在工程里常用的YOLOv5s、YOLOv7-tiny、YOLOv8s这些模型24G内存绰绰有余甚至可以同时加载多个模型做模型级复用或者在一个模型上开较大的batch比如一次推理16张甚至32张640x640的图。内存大还有一个实际价值在模型转换时如果打开动态shape或者用了比较大的AIPP输入分辨率需要临时申请很大的工作缓冲区。显存小的卡在这种场景下很容易报OOM24G基本上能扛住绝大多数CV推理任务。1.3 什么场景我建议选Atlas而不是通用GPU这里我不做谁取代谁的结论只说我的选型逻辑。如果推理任务特别依赖CUDA生态里那些现成库比如TensorRT、DeepStream、各种带CUDA优化的第三方库GPU会更顺手社区资料多出了问题网上直接能搜到答案。但如果你遇到的是下面几种情况Atlas 300V反而更合适项目明确要求国产平台或者客户机房已经批量采购了昇腾设备不需要我引入新的GPU适配。推理卡多路并行部署Atlas半高半长单槽功耗低一个4U服务器能插很多张卡功率预算好做。场景是固定模型、固定输入尺寸、长期跑比如闸机通行检测、园区摄像头视频分析这类Atlas在固定图模式下性能发挥非常好。需要硬件视频解码昇腾的VPC模块支持H.264/H.265硬解可以把摄像头RTSP流直接送进卡里解码再送NPU推理整个pipeline很紧凑。如果你只是个人开发、跑跑实验手头已经有N卡那不建议专门换Atlas。但在工业项目里Atlas 300V 24G的优势在于为固定场景的批量推理做了大量简化这句话会在后面部署时体现得特别明显。2. 部署YOLO前硬件和环境准备应该做的确认清单2.1 物理安装和版本配套先看硬件。Atlas 300V 24G是PCIe 3.0 x16接口把它插进服务器空闲的x16槽位就行。有两个细节我曾经忽略过尽量插在有直连CPU的PCIe通道上不要插在PCH桥接出来的槽位否则多次拷贝会增加延迟。安装时注意服务器风道。这张卡是被动散热还是主动散热不同型号有差异尽量保证卡周围有足够的风流。装好之后先别急着装软件用lspci确认系统认到了设备lspci | grep -i ascend如果能看到一个类似Processing accelerators的设备硬件链路基本没问题。然后是软件配套。Atlas的驱动Driver、固件Firmware和CANN工具包是三个独立但必须配套的组件。驱动负责操作系统和硬件之间的通信固件是芯片内部的控制程序CANN是上层开发工具链。这三者的版本号必须匹配否则会出现驱动加载成功、但卡不工作或者npu-smi看不到卡的诡异现象。我自己实际用的组合是在Ubuntu 20.04系统上配套的驱动固件和CANN 6.3.RC3版本装好之后一切正常。安装包都自带安装脚本基本是解压后执行chmod x Ascend-hdk-*.run ./Ascend-hdk-*.run --install安装完成后记得source一下CANN的环境变量source /usr/local/Ascend/ascend-toolkit/set_env.sh建议把这行写进 /etc/profile 或者当前用户的 .bashrc否则每次开新终端都要手动source。2.2 环境验证用npu-smi和一个小demo确认链路是否通软件装完最怕的就是看起来装好了实际跑不起来。我的验证顺序是这样的npu-smi info这个命令会列出所有NPU设备包括芯片形态、温度、功耗、内存使用率、驱动版本、固件版本。如果能看到类似这样的输出------------------------------------------------------------------------------------------- | npu-smi 24.0.0 Version: 24.0.0 | ----------------------------------------------------------------------------------------- | NPU Name | Health | Power |卡就是正常被驱动接管了。第二步我会在CANN安装目录里跑一个自带的样例。以Python ACL接口为例验证环境最简单的方法是从命令行导入acl模块python3 -c import acl; print(acl.__version__)如果可以正常打印版本号说明CANN的Python接口已经可用。再往后我会用一个最基础的resnet50模型跑一遍官方样例确认整条软件链路通顺。这一步一定不要省。我遇到过一次驱动版本和固件版本不配套npu-smi能列出设备但一执行推理就报device open failed排查了一下午最后重新刷配套固件解决。2.3 第一个坑驱动与固件版本不配套导致查无此卡具体说一下这个坑因为太多人会遇到。某次我在一台新服务器上部署驱动装的是24.0.0版本固件装的是23.0.1老版本。装完以后lspci能看到设备但npu-smi info直接报[ERROR] Failed to get card info, please check if the card is normal.一开始我还以为是卡坏了换了槽位、重新插拔都没用。后来查CANN的版本配套表才发现驱动和固件版本差了一个大版本它们之间的通信协议不兼容。解决办法是把固件升到和驱动同一版本的配套固件重新执行安装脚本重启服务器npu-smi才恢复正常。这个教训后来我总结成了一条部署检查规则驱动、固件、CANN三者的版本号必须在官方配套表里能查到对应关系不要单独更新其中任何一个。尤其在离线环境下这三样东西最好一次性统一拷进内网避免混装。3. 把YOLOv5的ONNX模型转成Atlas认识的OM格式3.1 ONNX导出这些细节直接影响ATC转换Atlas不能直接加载PyTorch的.pt模型也不能直接加载ONNX原始模型它需要的是经过ATCAscend Tensor Compiler转换后的.om格式。整个转换逻辑是PyTorch训练出.pt导出为ONNX再通过ATC转换为OM最后在NPU上加载执行。我的项目用的是YOLOv5导出ONNX时要注意几个细节第一导出时的输入shape要固化。如果推理场景固定输入尺寸比如640x640导出时就把input shape写死成 (1, 3, 640, 640)。虽然ATC也支持动态shape但那会带来额外的转换复杂度而且动态shape模式下NPU性能通常会打折扣。第二导出ONNX时建议把检测头的输出节点弄清楚。YOLOv5的结构里包含三个不同尺度的输出头导出后可能输出三个张量也可能被合并为一个 (1, 25200, 85) 的大张量。我导出时习惯保持原始三个头输出这样转换后的OM输出结构稳定后面写后处理时逻辑更清晰。第三如果模型里有自定义算子ATC可能无法直接识别这需要先把算子替换成标准算子组合。YOLOv5比较规矩一般不会触发这个问题。导出命令我用的是YOLOv5官方仓库自带的export.pypython export.py --weights yolov5s.pt --include onnx --img 640 --batch 1导出成功后可以用onnxruntime或者netron查看一下模型输入输出结构确认输入名是images还是其他名称以及每个输出的shape。这个信息在ATC命令里会用到。3.2 ATC转换命令逐参数拆解在安装好CANN的机器上找到atc工具一般在 /usr/local/Ascend/ascend-toolkit/latest/bin/atc我的转换命令长这样atc --modelyolov5s.onnx \ --framework5 \ --outputyolov5s_bs1 \ --input_shapeimages:1,3,640,640 \ --soc_versionAscend310P3 \ --logerror这个命令里每个参数都有讲究--model指定输入的ONNX文件。--framework5表示输入模型格式是ONNX。ATC规定ONNX对应代码5TensorFlow对应3MindSpore对应1这个数字不能写错。--output指定输出OM文件的路径和名称。--input_shape用于固定输入张量的shape。这里的images必须和ONNX模型里输入节点的实际名称一致。我曾经因为输入名写错报了好几轮输入不匹配的错误。--soc_version是关键参数指定目标芯片的型号。Atlas 300V 24G对应的芯片是310P系列我的卡通过npu-smi查到的芯片形态是310P3所以填Ascend310P3。不同型号的Atlas卡对应的名称不同填错会导致转换后模型无法加载。稳妥的做法是查CANN文档里soc_version与产品型号对应表。--logerror表示只输出错误日志如果要排查转换细节可以改成debug。如果模型里有算子精度问题ATC会给出错误提示。最常见的错误是某个ONNX算子不支持解决办法是升级CANN版本或者在导出ONNX时避开这个算子。有些项目还会在ATC命令里加--output_type指定输出数据类型或者加--input_formatNCHW指定输入排布。YOLOv5导出默认是NCHW如果你的预处理用了NHWC就需要在这里保持一致。3.3 转换后的验证用om模型做一次纯推理OM模型转出来以后先别急着写完整推理代码。我先给一个最小验证逻辑用ACL接口加载OM模型创建一个和输入shape完全一致的全零或者随机数据作为输入执行一次同步推理看能不能正常得到输出。如果这一步能过说明OM模型本身没问题后面的问题大概率出在预处理或者后处理。有两点要提醒一是ATC转换默认会做算子融合和内存复用优化。转换日志里如果出现engine或者custom关键警告最好检查一下有没有损坏的节点。二是我会额外做一个精度冒烟测试取一张测试图片用Python做严格相同的预处理分别在ONNX RuntimeCPU和Atlas NPU上跑一遍推理对比输出tensor的误差。误差在1e-3量级以内算正常如果差距很大优先检查归一化参数和输入通道顺序。4. 推理代码怎么串起来预处理、执行、后处理的工程化写法4.1 输入预处理letterbox和归一化不能在这里浪费CPUYOLOv5原始输入是任意分辨率的图片但模型要求输入是640x640。直接resize会把图像压变形导致检测框漂移所以要先做letterbox把原图等比缩放然后填充到640x640。这一步听上去简单但工程上要在推理流程里抠性能有几个细节需要处理用OpenCV做letterbox时resize次数越少越好。对视频流来说每帧都重新申请Mat内存会带来额外开销建议提前分配好缓冲。归一化最好在拷贝到NPU设备内存之前完成或者直接使用AIPP在卡上做。输入张量的内存必须从Atlas的设备内存池申请不能直接把numpy数组的地址传给ACL接口。一般流程是先用acl.rt.malloc在设备侧申请input buffer然后用acl.rt.memcpy把host侧的已归一化数据拷过去。我第一版代码犯了个错误在Python里对每一帧都做从BGR到RGB、再转float32、再除以255的操作。看起来没什么但640x640x3的数据在Python循环里走一遍Numpy广播CPU占用立刻上涨推理卡在等数据。后面我把图像预处理统一集中到一个C扩展模块里做CPU开销降了一个量级。4.2 使用ACL接口加载OM并执行推理用Python实现时核心调用是CANN的pyACL。我贴一个简化但完整的加载和推理骨架import acl import numpy as np 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_input_size_by_index(desc, 0) output_size 0 for i in range(acl.mdl.get_num_outputs(desc)): output_size acl.mdl.get_output_size_by_index(desc, i) input_data, ret acl.rt.malloc(input_size, 2) output_data, ret acl.rt.malloc(output_size, 2)加载完成后每次推理的流程是把处理好的输入数据用acl.rt.memcpy从host拷到input_data指向的设备内存调用acl.mdl.execute(model_id, input_data, output_data)执行一次推理用acl.rt.memcpy把output_data拷回host侧numpy数组对numpy数组做后处理。这里有两个容易踩的坑输入内存拷贝大小必须和input_size一致。如果预处理后numpy数组的字节数大于input_size就会越界写程序可能崩溃或者返回脏数据。每次推理输出buffer里的数据不一定被清零如果模型有多个输出但你没读完下一次推理覆盖部分缓冲可能读到上一次的残留。稳妥做法是每次推理前把output_data对应的host侧缓冲清零或者严格按get_output_size来读取。4.3 后处理与NMS的并行写法YOLOv5的原始输出经过解码后包含大量候选框。后处理主要做三件事阈值过滤、类别筛选、NMS去重。我的工程实践里后处理没有放在NPU上做而是放在CPU侧用Numpy实现。原因很简单YOLOv5单帧640x640输入三个输出头加起来也就25200个候选CPU侧Numpy处理一帧通常在几毫秒内完成不值得为了省这点时间去做复杂的算子下沉。NMS的Python实现要特别注意性能。如果逐帧在循环里调用纯Python的NMS实现耗时可能达到20ms以上完全抵消掉NPU的加速效果。我是用提前过滤置信度低于0.5的框、限制每类最多保留的框数量这两个手段大幅降低NMS输入规模然后用Numpy向量化的方式实现IoU计算。多路视频流场景下我会把后处理丢进一个线程池推理线程只负责把帧送到NPU、取回输出检测框解析由多个worker并发处理。整条流水线这样设计下来CPU多核利用率合理NPU不会因为等后处理而空转。4.4 如何准确测量一帧的推理耗时性能统计不能直接在Python里用time.time()包一圈就完事那样会把Host和设备内存拷贝、驱动调度都算进去结果不准确。我测量时把一次完整推理拆成三段预处理耗时从输入图像到生成设备侧输入buffer计时范围是原始图像到acl.rt.memcpy之前推理耗时从调用acl.mdl.execute到返回这中间包含了Host到Device的同步等待后处理耗时从设备侧拷贝回host到输出最终检测框。用time.perf_counter()分别在三个位置打点连续跑100帧取P95值。只看推理耗时的话YOLOv5s FP16在Atlas 300V上的实际数字比很多人的预期要好看但工程上如果只看这一项往往会高估整条链路的吞吐。真正的性能瓶颈常常在预处理拷贝和后处理NMS上。5. 从25ms到9ms一次实际调优的完整记录5.1 第一版实现的性能基线我把第一版流程跑通后测出来的端到端P95耗时大概是25ms一帧。拆开看推理本身只占8ms左右剩下的大头在预处理和Host/Device的数据拷贝上。那一版的问题很明显Python层逐帧预处理letterbox和转float32/255的CPU开销约6ms每次推理后把整个输出buffer拷回hostYOLO三个输出加起来数据量不小拷贝占了4ms后处理虽然用Numpy但没有并行化NMS耗时5ms整个流程是严格的串行取帧-预处理-推理-后处理NPU至少有30%时间在空等。这个基线其实已经能跑但离部署到生产可接受还有距离。我开始逐段优化。5.2 三项核心优化及效果第一项优化是把预处理从Python挪到C。我用C写了一个共享库把resize、letterbox、BGR转RGB、归一化、HWC转NCHW全部一次性做完用Python的ctypes调用。这一步直接把预处理从6ms压到1.5ms。第二项优化是使用AIPPAI Preprocessing在NPU上完成图像归一化甚至resize。AIPP的配置需要在ATC转换时用aipp_config.cfg文件指定我在文件里配了输入图像格式、归一化均值和方差aipp_op { input_format : RGB888_U8 mean : 0.0 0.0 0.0 min_chn_0 : 0.0 var_reci_chn_0 : 0.003921569 }配置AIPP之后模型输入直接接收8位RGB图像归一化在NPU上完成host侧只需要做一次内存拷贝。这个优化再把预处理压缩到接近0而且减少了host内存占用。第三项优化是后处理线程池。我开了4个consumer线程每个线程从队列里取推理输出做解码和NMS主线程只管生产任务。这样推理耗时和后处理耗时重叠起来单帧端到端从25ms降到了14ms左右。再往后我把模型从FP16换成INT8量化版本推理耗时又降了一半。经过这三步端到端P95稳定在9ms上下。5.3 多路视频流接入时的batch策略单帧9ms看起来不错但如果一台Atlas 300V要同时处理多路摄像头视频流还需要考虑batch策略。Atlas在bs1模式下处理8路视频流等效吞吐大概是1/0.009约111fps看起来够但每路25fps的话8路就要200fps已经超了。这时候需要把多路视频流拼成一个batch一次推理4帧甚至8帧。bs4的转换命令只比bs1多改一个输入shapeatc --modelyolov5s.onnx --framework5 --outputyolov5s_bs4 \ --input_shapeimages:4,3,640,640 --soc_versionAscend310P3batch4时单次推理的处理能力提升但npu的空闲率反而降低。实际效果我测下来bs4比4次bs1的总体吞吐提升大约35%代价是单帧延迟会小幅上升因为要凑齐4帧才能发一次推理。对于视频流场景延迟稍微增加是可以接受的吞吐才是关键指标。6. 生产环境里的其他坑和我的最终建议6.1 容器部署时设备挂载现在大部分项目都在容器里跑。在Docker容器里用Atlas光装CANN是不够的必须把设备的几个节点挂载进去。我踩过的最典型报错是[ERROR] Device open failed, please check device is online但npu-smi在宿主机上看卡明明正常。原因就是容器启动时没有挂载设备文件。正确启动命令至少包含docker run -it \ --device/dev/davinci0 \ --device/dev/davinci_manager \ --device/dev/hisi_hdc \ -v /usr/local/Ascend:/usr/local/Ascend \ your_image如果用到多个设备需要一一列出。另外容器里的CANN版本和驱动版本也需要配套建议直接把宿主机的CANN目录挂进容器避免重复安装。6.2 输出shape和内存对齐问题YOLO部署到Atlas后最隐蔽的问题出在输出张量的shape上。ATC转换后OM模型的输出节点可能和ONNX不完全一致有些算子被融合后输出顺序会变。我刚转完模型时认为有三个输出头结果实际加载后莫名变成了一个合并输出后处理直接报形状不匹配。解决方案是不要凭经验假设输出个数而是用acl.mdl.get_num_outputs和acl.mdl.get_output_size_by_index把OM模型真实的输出信息打印出来再写后处理。这一步看起来多花几分钟但能省下后面几个小时。还有内存对齐问题。Atlas要求设备内存地址按一定字节对齐acl.rt.malloc分配的内存本身会满足对齐要求但如果你自己用malloc申请内存再传给ACL就可能出现性能骤降或者报错。所以一律使用ACL提供的内存管理接口不要自己分配。6.3 当前态最稳的工程组合经过这个项目我再搭Atlas 300V部署环境时基本固定用这套组合操作系统Ubuntu 20.04内核保持系统默认不自己乱改。驱动、固件、CANN三者严格按配套表安装CANN选6.3.RC3以上版本对YOLOv5系列的支持比较完善。模型转换用固定shape优先INT8量化量化前准备200到500张实际场景图片做校准集。推理代码用C封装成soPython只做业务编排和结果回调这样好处是避开了Python自身的GIL限制和内存碎片问题。预处理一定做到极致精简能挪到AIPP就在AIPP做不能就写C模块。我个人体会Atlas部署YOLO这件事难点从来不在跑通demo而在稳定地跑在生产环境。模型转换、内存管理、设备兼容这些环节都有不少隐含约束文档里不一定写全只有踩过坑才知道边界在哪。如果你正准备在一台新机器上部署我建议至少留出一天的余量专门用来处理版本配套和环境问题。最后再分享一个小技巧整个部署过程中我会把每一步验证命令都存成shell脚本按顺序编号。比如01_check_driver.sh、02_check_npu_smi.sh、03_run_sample.sh。一旦环境出问题重跑脚本就能快速定位是哪一层坏了。这比对着文档一条条敲命令效率高太多。
返回列表