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

资讯详情

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

Atlas 300V部署YOLOv5全流程:AI加速卡与GPU区别及昇腾CANN实践

Atlas 300V部署YOLOv5全流程:AI加速卡与GPU区别及昇腾CANN实践 没接触过华为Atlas之前我也以为AI加速卡就等于GPU拿到手应该装个CUDA就能跑。直到自己把YOLOv5部署到这上面才发现昇腾这套体系跟NVIDIA完全是两套思路。网上搜“atlas 300v 24g 是运算加速卡吗”的人不少我的回答是它是运算加速卡但它不是通用GPU卡不能直接跑CUDA代码模型格式、推理接口、开发习惯都得换一套。这篇文章把我在Atlas 300V上部署YOLO目标检测模型的全过程写清楚从硬件认知、环境搭建、模型转换到PyACL推理、多路视频流优化以及几个把我卡了好几天的报错都一次性说完。1. 先搞清楚Atlas 300V到底是什么1.1 运算加速卡和GPU不是一回事Atlas 300V外观就是一块标准半高半长的PCIe卡插到服务器里就能被识别。但它和市面上常见的GPU加速卡有几个关键区别。第一它不提供CUDA支持。你写好的PyTorch、TensorFlow代码不能直接搬到它上面跑所有模型需要先转换成昇腾的离线模型格式.om然后通过昇腾提供的CANN工具链和PyACL接口来加载和推理。这意味着整个开发链路要从“写Python CUDA”变成“训练模型 转换模型 写ACL推理代码”。第二它的算力集中在AI Core上。以我手里这块24G版本为例卡上集成的昇腾AI处理器里有一组专门为矩阵运算设计的AI Core同时还有负责图像编解码的DVPP模块。所以它特别适合视频流分析这类场景因为视频解码、缩放、模型推理可以在卡内流水线完成不需要频繁把数据搬回CPU。第三显存不是用来装通用计算的。24GB LPDDR4X看着不小但它的价值主要体现在两处一是能塞下比较大的模型和足够大的batch二是能缓存多路视频帧减少Host和设备端的数据搬运次数。这跟GPU显存的概念类似但不是一回事。注意如果你本意是想找一张通用计算卡跑CUDA生态的东西Atlas 300V大概率不适合。它更适合做“已经被训练好的模型”的推理部署尤其是视频结构化、目标检测、图像分类这类场景。1.2 为什么选Atlas 300V跑YOLO这次项目需求其实很简单把原来的GPU视频检测服务迁移到国产化算力平台上同时希望单卡能支撑的检测路数不要太拉胯。对比一圈之后选Atlas 300V 24G的主要原因是性价比和功耗。单卡功耗在70W上下却能支撑20路1080P视频解码。我测试跑YOLOv5s模型满负载状态下整卡功耗也没超过80W。相比一块动辄两三百瓦的GPU数据中心机房压力小很多。而且24G显存版本在跑多路视频流时buffer分配空间更充裕不用担心batch稍大一点就OOM。另一个原因是大batch推理友好。Atlas这一代推理卡比较吃batch单张图一张张喂进去效率不高。24G版本的好处是可以把4路、8路甚至16路视频帧拼成一个batch一起推理吞吐量能拉开明显差距。实测在batch8情况下YOLOv5s的帧率可以达到单batch模式的4到5倍。如果你只是图省事想用标准CUDA环境跑YOLO那不推荐折腾Atlas。但如果你有明确的国产化或者低功耗多路视频分析需求这卡的路子是对的。1.3 需要提前理解的两个关键模块部署前一定花点时间把昇腾的异构架构模型搞清楚否则后面写代码会一头雾水。一是AI Core的计算流程。模型在Atlas上执行时会先被ACL运行时加载到设备端然后按算子的依赖关系调度到AI Core上执行。整个过程中数据必须在Host内存和设备显存之间通过PCIe搬运。所以推理性能瓶颈往往不在算力而在数据搬运次数多少。二是DVPP模块的作用。Atlas卡上的视频解码、缩放、色域转换都是在DVPP完成的而不是CPU。比如视频流先通过DVPP解出YUV帧再在DVPP内部完成resize和格式转换最后直接转成模型要求的输入张量。这样CPU基本不参与图像预处理整条pipeline效率很高。实操心得刚开始写推理脚本时我习惯像GPU那样用OpenCV在CPU端做resize、BGR转RGB后来发现CPU占用率飙升NPU利用率却上不去。改用DVPP硬解码和硬件预处理之后同样的路数CPU占用从80%降到15%左右。2. 环境搭建驱动、固件、CANN一个都不能少2.1 版本选择和安装顺序Atlas的软件栈分为三层驱动、固件、CANN工具包。第一次安装最容易踩的坑就是这三者版本不匹配导致npu-smi info能看到卡但CANN初始化失败。我的安装顺序建议是安装NPU驱动也就是Ascend-hdk系列包里的驱动部分装完后用npu-smi info检查是否识别到设备。升级固件固件负责底层AI处理器的微码逻辑不烧录固件的话很多算子跑不动。安装CANN工具包这层才包含ATC模型转换工具、PyACL的Python接口等。具体版本选择上尽量安装同一批发布的配套版本比如CANN 7.0就配对应版本的驱动和固件。不要图新单独升级某一个组件否则很容易出现“驱动版本比CANN新导致SO库API不兼容”这种问题。安装完成后可以用以下命令快速验证环境是否正常npu-smi info正常情况下会列出所有Atlas设备显示芯片型号、显存大小、驱动版本号。如果这里看不到设备后面CANN再折腾也白搭。下一步在Python环境里测试ACL是否能初始化import acl print(acl.__version__)2.2 CANN工具链的逻辑CANN这层是整个昇腾开发的灵魂。它包含两个核心工具ATCAscend Tensor Compiler负责把TensorFlow、ONNX等格式的模型转换成昇腾的.om离线模型。AscendCLACL推理阶段调用的运行时接口。PyACL就是它的Python绑定负责模型加载、内存管理、推理执行等。理解这层的逻辑很关键。模型转换之后就不需要PyTorch环境了推理时只需要CANN运行时和PyACL。应用发布时只带.om文件和ACL的so库代码里不能有torch相关依赖这是跟GPU部署最大的不同点。2.3 为什么模型转换这一步很重要PyTorch训练出来的权重文件不能直接在Atlas上推理必须经过ATC转换。转换过程做的事情包括算子映射、图优化、算子融合、量化等。这一步直接决定模型在卡上跑得快不快。最初转换YOLOv5时我用的是PyTorch官方导出的ONNX文件没有做算子优化转换虽然成功了但推理速度只有预期的一半。后来加了onnxsim简化计算图再配合ATC的自带优化速度明显提升。这步不能省。3. YOLO模型转换实操从PyTorch权重到.om离线模型3.1 导出ONNX的几个关键参数YOLOv5的导出脚本自带export.py但我建议不要直接用默认参数这里有几个参数影响后续ATC转换的成功率。首先是opset版本。我选择的opset11这是和ATC工具兼容性最好的版本之一。如果某些新算子不支持可以试试opset13但尽量别超过17否则部分算子映射会出问题。其次是动态shape问题。ATC转换时尽量固定输入shape例如固定成1x3x640x640不要带动态维度。动态shape在Atlas上不是不能跑但会明显降低性能而且内存分配策略会变保守。导出命令参考python export.py --weights yolov5s.pt --include onnx --opset 11 --img 640 --batch 1导出后一定要先用onnxsim做一次计算图简化python -m onnxsim yolov5s.onnx yolov5s_sim.onnx这一步会把很多冗余算子合并掉ATC转换时的成功率和推理速度都会提升。3.2 ATC转换命令参数拆解ATC转换命令是整条链路的核心命令写错一个参数结果可能天差地别。以下是我最终使用的转换命令atc --modelyolov5s_sim.onnx \ --framework5 \ --outputyolov5s_aipp \ --soc_versionAscend310P3 \ --input_formatNCHW \ --input_shapeimages:1,3,640,640 \ --insert_op_confaipp.cfg \ --output_typeFP32 \ --loginfo逐项解释--framework55表示ONNX格式。--soc_versionAscend310P3表示目标芯片的型号。不同Atlas卡对应的soc_version不同可以用npu-smi info查到的芯片型号对照CANN文档确认。--input_shapeimages:1,3,640,640指定模型输入名字和形状。YOLOv5导出的输入名通常是images可以用onnx.load查看确认。--insert_op_confaipp.cfg插入AIPP预处理配置后面专门说。--output_typeFP32模型输出数据类型。这里要注意跟后处理代码对齐避免类型不匹配导致解析出错。转换完成后会生成一个.om文件同时日志里会显示算子的映射数量和耗时。如果某算子显示“not supported”就是转换失败需要回到导出环节解决。3.3 AIPP配置把图像预处理直接挪到卡里AIPP是Atlas上很有特色也很实用的功能它可以帮你在模型输入前自动完成色域转换、缩放、减均值等操作。也就是说Host端只需要把原始图像数据拷贝到设备端剩下的resize和通道转换都在卡内完成省去CPU参与。我的aipp.cfg配置示例aipp_op { aipp_mode: static input_format: RGB888_U8 src_image_size_w: 640 src_image_size_h: 640 crop: false resize: true resize_w: 640 resize_h: 640 padding: false mean_chn_0: 0 mean_chn_1: 0 mean_chn_2: 0 min_chn_0: 0.00392156862745098 min_chn_1: 0.00392156862745098 min_chn_2: 0.00392156862745098 }这里input_format设为RGB888_U8表示输入是RGB三通道U8格式min_chn就是1/255用来做归一化。注意如果你把AIPP的作用设为resize那么Host端传给模型的图像尺寸就不需要是640x640可以传入原始尺寸AIPP会负责缩放。这能省掉不少CPU开销。实操心得把resize挪到AIPP之后Host端图像预处理从约8毫秒降到了不到1毫秒。对于多路视频流这个优化直接决定了能不能跑满整卡。4. PyACL推理代码从初始化到输出解析4.1 初始化和设备管理用PyACL推理不需要torch也不需要ONNX runtime代码结构跟传统推理框架完全不一样。首先初始化ACL并指定设备import acl def init(): ret acl.init() assert ret 0, facl.init failed, ret{ret} ret acl.rt.set_device(0) assert ret 0, fset_device failed, ret{ret} context acl.rt.create_context(0) return context这里要注意如果程序里用了多线程每个线程最好创建独立的context不要跨线程共用否则很容易出现推理卡死或者报错。后面排障章节会专门讲这个问题。4.2 模型加载与内存分配加载.om模型后需要用acl.mdl.query_size查询模型需要的输入输出buffer大小再申请设备内存。model_path yolov5s_aipp.om model_id acl.mdl.load_from_file(model_path) # 获取输入输出尺寸 input_desc acl.mdl.create_desc() ret acl.mdl.get_input_desc(input_desc, model_id, 0) input_size acl.mdl.get_desc_size(input_desc) # 申请设备内存 device_input_ptr, ret acl.rt.malloc(input_size, 2)这里有个容易被忽略的细节acl.rt.malloc的第二个参数是内存对齐一般传2表示按2MB对齐这是昇腾对大型内存块的推荐方式。如果对齐方式不对后面D2D拷贝可能报错。4.3 执行推理并取回结果推理本身用acl.mdl.execute_async异步接口执行完后用acl.rt.synchronize等待完成。这里非常推荐异步方式因为同步接口会阻塞当前线程在视频流场景下会导致CPU空转。stream acl.rt.create_stream() ret acl.mdl.execute_async(model_id, [device_input_ptr], [device_output_ptr], stream) acl.rt.synchronize(stream) # 把结果从设备内存拷回主机 acl.rt.memcpy(host_output_ptr, output_size, device_output_ptr, output_size, ACL_MEMCPY_DEVICE_TO_HOST)拿到输出后YOLOv5的输出shape是[1, 25200, 85]需要按行解析检测框、置信度和类别概率。这里要跟模型输出格式对齐用reshape把原始buffer转成numpy数组。4.4 后处理NMS精简实现YOLO后处理主要就是筛选置信度、框坐标映射、NMS去重这三步。我直接用numpy写了一套精简版NMS没有引入opencv的高版本依赖方便在纯Python环境里跑import numpy as np def nms(pred, conf_thres0.25, iou_thres0.45): boxes pred[pred[:, 4] conf_thres] if len(boxes) 0: return [] # 转换为(x1, y1, x2, y2) xywh boxes[:, :4] x1 xywh[:, 0] - xywh[:, 2] / 2 y1 xywh[:, 1] - xywh[:, 3] / 2 x2 xywh[:, 0] xywh[:, 2] / 2 y2 xywh[:, 1] xywh[:, 3] / 2 scores boxes[:, 4] * boxes[:, 5:].max(axis1) # 按置信度降序 order scores.argsort()[::-1] keep [] while order.size 0: i order[0] keep.append(i) xx1 np.maximum(x1[i], x1[order[1:]]) yy1 np.maximum(y1[i], y1[order[1:]]) xx2 np.minimum(x2[i], x2[order[1:]]) yy2 np.minimum(y2[i], y2[order[1:]]) # 计算IoU area_intersect np.maximum(0, xx2 - xx1) * np.maximum(0, yy2 - yy1) area_i (x2[i] - x1[i]) * (y2[i] - y1[i]) area_order (x2[order[1:]] - x1[order[1:]]) * (y2[order[1:]] - y1[order[1:]]) iou area_intersect / (area_i area_order - area_intersect 1e-9) order order[1:][iou iou_thres] return keep注意推理结果里的坐标是相对于模型输入尺寸的。如果你的AIPP做了resize而原图是1920x1080那么后处理完需要把框坐标映射回原图否则画框就错位了。映射公式很简单x_orig x_model * orig_w / 640别漏掉。5. 多路视频流实战与性能优化5.1 单卡能撑多少路检测部署之前我给自己定的目标是单卡支持20路1080P视频流的YOLOv5s检测。刚开始直接用Python开20个线程每个线程单独跑推理结果发现帧率完全不够。后来把整个pipeline拆成解码、推理、后处理三段各自独立设计才把路数提上去。先说结论在Atlas 300V 24G上用YOLOv5s模型、batch8、DVPP硬解码的情况下单卡稳定跑16到20路1080P视频流是可行的。如果视频源是25fps检测帧率可以做到每路5到8fps这个指标对于特定场景的实时结构化分析是够用的。5.2 瓶颈不在NPU算力而在数据搬运多路视频流跑起来之后我第一个排查的就是为什么NPU利用率只有30%CPU反而满了。用npu-smi info观察后发现问题出在每路视频帧都是先拷贝到Host内存再在CPU上做BGR转换和resize最后才传到设备端。这个流程里CPU承担了大量图像预处理工作拖累了整个系统。优化方式有两个方向使用DVPP硬解码。Atlas卡支持直接把H264/H265码流解码成YUV帧这个过程不需要CPU参与。把预处理搬到AIPP。让模型输入前直接接收YUV帧或RGB帧由卡内完成缩放和通道转换。这两步做好之后CPU占用率大幅下降NPU利用率才能跑上去。5.3 batch策略和多线程设计实践下来Atlas卡非常吃batch。单张图推理延迟约8毫秒看起来还行但吞吐量不高。如果把8张图拼成一个batch推理单batch耗时约20毫秒平均每张只花2.5毫秒吞吐量提升了3倍多。具体设计时我采用了一个简单的异步队列主线程从视频流拉帧丢进一个待推理队列。推理线程攒够batch_size帧后一次性提交推理。后处理线程负责解析结果并画框。这样能保证整条管线尽量保持流水线状态不会出现推理等数据或者数据等推理的空闲时间。注意多线程访问PyACL时每个线程必须拥有独立的context。我的做法是在线程创建时调用acl.rt.create_context(0)线程结束时调用acl.rt.destroy_context释放避免ACL内部状态混乱。6. 部署中踩过的几个典型问题6.1 ATC转换时报E10001算子不支持第一次转换时日志里出现了很多E10001错误提示某个PyTorch内置算子无法映射到昇腾算子。排查后确定是ONNX导出时opset版本太高部分算子ATC不识别。解决办法很简单导出ONNX时指定--opset 11不要用默认或最新版本。另外如果模型里有自定义算子需要手动实现对应的TBE算子这个就比较复杂了。好在YOLOv5没有这种情况。6.2 推理返回错误码507018这个错误码在昇腾文档里对应“设备内存不足”。我当时查了半天发现不是显存真的不够而是前面几次推理的输出buffer没有释放Host和设备端内存泄漏越来越严重最终导致分配失败。解决办法是确认每次推理循环里# 用完的设备内存及时释放 acl.rt.free(device_input_ptr) acl.rt.free(device_output_ptr) acl.mdl.destroy_desc(input_desc) acl.mdl.destroy_desc(output_desc)把资源释放放到finally块里确保异常也能释放。这个错误码一旦出现重启进程是最快恢复方式所以代码里最好做异常兜底和自动重启机制。6.3 多线程推理卡死多线程推理时偶尔出现所有线程全部卡住程序不报错也不退出。一开始以为是死锁后来用gdb看线程栈才发现是ACL初始化时把所有线程共享了同一个context导致运行时状态冲突。昇腾文档里明确要求每个线程必须创建并使用独立的contextmodel_id可以是全局共享的但context必须独立。调整之后卡死问题再没出现。实操心得写多线程推理程序时建议把“初始化ACL 创建上下文 加载模型”放主线程然后在每个子线程里单独创建context。不要试图在每个子线程里重复加载模型这样会浪费大量设备内存。7. 这几个月用下来的几点体会Atlas 300V 24G不是一张能用惯性思维去驾驭的卡。它用一套完全不同于GPU的编程范式换来了低功耗下的高推理吞吐。刚开始确实不习惯但当你把模型转换、AIPP预处理、多线程context这几件事理顺之后整条推理链路跑得相当稳定。我自己的感受是如果目标是低功耗、高路数、结构化视频分析Atlas 300V这套方案值得认真投入如果只是想在已有CUDA生态里快速验证模型那还是老老实实用GPU吧没必要把时间花在迁移上。最后再分享一个小技巧拿到新版本CANN之后先别急着跑业务代码把官方自带的样例模型转换和推理跑通一遍确认环境没问题再切你自己的模型。这个顺序能帮你省下一整天排查环境问题的时间。每次回看这次部署最费时间的其实不是代码本身而是对昇腾这套“异构计算离线模型”思路的理解过程。希望这篇文章能让你少走点弯路。
返回列表