ARTICLE DETAIL

资讯详情

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

Atlas 300V 24G推理加速卡部署YOLOv5:从模型转换到性能调优实战

Atlas 300V 24G推理加速卡部署YOLOv5:从模型转换到性能调优实战 1. 先搞清楚Atlas 300V 24G到底是不是运算加速卡最近不少做视觉算法落地的朋友在问Atlas特别是Atlas 300V 24G这张卡。问题集中在两个这玩意儿到底算不算运算加速卡以及它能不能拿来部署YOLO。我今天就把这块卡从头到尾聊透——包括硬件定位、推理原理、模型转换流程以及我在实际项目中踩过的坑。先说结论Atlas 300V 24G准确的型号是Atlas 300V Pro确实是运算加速卡而且是专门为AI推理设计的加速卡。它不是训练卡也不是简单的“显卡”而是华为昇腾产品线里面向边缘推理和数据中心推理场景的PCIe加速卡。我接触这张卡大概有大半年时间了最早是在一个视频结构化项目里用到它。当时团队里有人一听说“华为的卡”第一反应是没有CUDA、生态不行结果真上手做了两个星期的适配后发现事情并不是想象中那样。尤其是把YOLOv5从PyTorch搬到Atlas上跑推理整个过程比预想的顺畅前提是你得理解它的工作机制别拿GPU那套思路硬套。1.1 一张表格看明白300V系列的定位Atlas 300V系列目前市面上常见的有几个版本参数差异挺大别买错了型号芯片内存INT8算力功耗主要场景Atlas 300V昇腾310P8GB/16GB约70/100 TOPS50W~72W轻量推理、边缘盒子Atlas 300V Pro昇腾310P24GB约140 TOPS75W视频分析、多路推理、大模型轻量部署这里要特别说明一下Atlas 300V 24G标称的“24G”是LPDDR4X内存不是像GPU那样的GDDR6显存更不是HBM。它的内存带宽大约是204GB/s这个数字和NVIDIA的RTX 4090超过1TB/s相比差了一个量级但它本来就是推理卡对带宽的依赖没那么极端——因为推理时权重和中间激活值在片上缓冲和内存之间搬移的模式和训练时差别很大。简单说它就是为“低功耗、高吞吐、多路并发”设计的。很多人第一次看到“140 TOPS算力”会觉得很强实际上这个数字是INT8精度的峰值算力。做推理部署时INT8是主流选择而训练场景几乎不会有人用INT8。所以你拿它跟GPU比算力时要看精度口径INT8 TOPS和FP16 TFLOPS完全是两码事。1.2 为什么推理要用独立加速卡而不直接用CPU这可能是很多刚接触部署优化的同学最容易忽略的问题。一台普通服务器哪怕是双路至强跑单路YOLOv5m的视频流推理CPU占用率轻松到60%以上一秒钟能处理的帧数也就十几帧。瓶颈主要出在CPU的SIMD指令集对卷积这类算子支持太弱加上内存访问模式不适配。而Atlas 300V这种推理卡内部有专门的AI Core。每个AI Core里包含Cube单元负责矩阵运算、Vector单元负责向量运算和Scalar单元负责标量控制算力密度比CPU高两个数量级还不止功耗却低得多。75W的卡能压住几十路720P的视频流做YOLO检测这个效率CPU完全做不到。另外一个关键点是异步流水。GPU在推理时也存在CPU和GPU之间的数据拷贝开销但Atlas这套架构在推理场景做了专门优化——数据通过Device侧内存管理机制配合CANNCompute Architecture for Neural Networks里的异步接口能把预处理、推理、后处理重叠起来。这个特点在后面部署YOLO时会直接体现出来。2. 在Atlas上跑YOLO的前置认知CANN软件栈与模型转换原理理解了硬件之后下一步就是软件。NVIDIA有CUDA生态昇腾这边对应的叫CANN全称Compute Architecture for Neural Networks。这是一个比CUDA更“重”的软件栈因为它不仅仅提供类似CUDA的底层编程接口还内置了图编译、算子调度、算子融合、内存规划等一整条工具链。CANN的架构大致分几层底层是驱动和运行时往上是ACLAscend Computing Language编程框架对标的是CUDA Runtime API再往上是各种上层框架的适配层PyTorch、TensorFlow、MindSpore等。我们做YOLO部署直接打交道的主要是ATC工具模型转换和ACL API推理调用。2.1 ONNX到OM的转换本质上是一次“静态编译”用过TensorRT的朋友都知道你要先用trtexec把模型转成engine文件再在运行时反序列化加载。Atlas的思路类似但更彻底——ATCAscend Tensor Compiler会把ONNX模型解析成计算图然后在离线状态下完成算子的格式推导、布局转换、算子融合、内存复用规划最终生成一个OMOffline Model文件。为什么要做这一步而不是像PyTorch那样直接动态执行因为推理场景中模型的网络结构是固定的输入shape大多数时候也是固定的。既然都是固定那就可以把大量运行时开销提前到离线阶段完成。比如算子融合把ConvBNReLU融合成一个算子减少kernel launch次数这个优化在GPU上有类似做法TensorRT也这么干但在Atlas上做得更彻底。这里有一个新手最容易犯的错误把PyTorch模型直接拿来做推理。Atlas的runtime不认.pt文件不认.pth文件也不认ONNX它只认OM。所以整个部署链路就是PyTorch权重 → 导出ONNX → ATC转OM → ACL加载推理。2.2 三种路线怎么选在Atlas上跑YOLO我实践下来大致有三条路线路线A手动走ACL推理。YOLOv5导出ONNXATC转OM然后写Python或C代码调用ACL API做预处理、推理、后处理。优点是完全可控最适合生产环境定制缺点是代码量大需要理解ACL的接口习惯。路线B用MindSpore框架适配。把YOLOv5的权重搬到MindSpore上然后直接用MindSpore的推理接口。优点是代码简洁缺点是对PyTorch模型的兼容转换工作量大版本匹配问题多不太推荐。路线C用昇腾官方的ModelZoo或开源仓库的现成样例。官方仓库里有一些YOLO系列的推理示例改改路径就能跑。适合验证环境、快速跑通Demo但真要接业务还得改很多东西。我的建议是想快速看到效果先走C想正式落地老老实实走A把ACL这套接口吃透。3. 完整实操YOLOv5s从PyTorch到Atlas 300V 24G接下来是重头戏我用一套完整的步骤演示怎么把一个YOLOv5s模型部署到Atlas 300V 24G上并跑出一个可用的推理程序。环境假设服务器已经安装好昇腾驱动和CANN Toolkit操作系统是Ubuntu 20.04Python 3.8。3.1 环境准备驱动、CANN Toolkit、nnrt先确认你的卡被系统识别到了。在终端执行npu-smi info能看到类似下面的信息就说明驱动正常---------------------------------------------------------------------------- | npu-smi 24.1.rc1 Version: 24.1.rc1 | --------------------------------------------------------------------------- | NPU Name | Health | Power | HBM | Temp | ... | | 0 Atlas 300V Pro | OK | 25W | 24G | 40C | ... | ---------------------------------------------------------------------------安装CANN Toolkit时需要注意版本对模型的算子兼容性。比如某些YOLOv5版本用到的Focus层、SiLU激活函数在不同CANN版本里支持程度不一样。我这边用的是CANN 7.0整体兼容性较好。安装时建议选“完整安装”而不是“最小安装”因为最小安装缺少ATC工具后面转换模型还得补麻烦。另外跑Python推理时依赖一个叫aclruntime的Python包部分版本叫topi或te。CANN完整安装后会配套好不需要单独pip建议直接使用CANN自带的Python环境。这里有个坑如果你服务器上已经装了很多Python包直接用系统Python可能importacl失败因为CANN的Python库路径没有加到PYTHONPATH里。source /usr/local/Ascend/ascend-toolkit/set_env.sh python -c import acl; print(ok)能打印ok就说明Python侧环境通了。3.2 导出带正确输出格式的ONNX文件YOLOv5官方仓库自带导出脚本但直接用会有几个问题。YOLOv5默认导出的ONNX是带着完整后处理NMS等的如果你在PyTorch里推理时用的是官方detect层的代码那ONNX导出后也会包含这部分逻辑。但Atlas运行时在模型内部跑NMS有时候会触发算子不支持的问题所以建议导出“裸模型”——只保留BackboneNeckHead的输出张量后处理放到ACL推理完成之后自己做。我的做法是修改YOLOv5的export.py或者自己写一段导出脚本。核心代码如下import torch from models.experimental import attempt_load # 加载权重 model attempt_load(yolov5s.pt, map_locationcpu) model.eval() # 关键把输出头和原始检测层解耦 # 通过replace操作让模型只输出3个尺度的原始特征 # 假设输入尺寸640x640类别数80 dummy_input torch.zeros(1, 3, 640, 640) # 自己构造一个只输出特征图的wrapper class RawOutputModel(torch.nn.Module): def __init__(self, model): super().__init__() self.model model def forward(self, x): # 调用model.model(x)得到的是feature maps # 不经过detect层的NMS y self.model.model(x) return y wrapper RawOutputModel(model) torch.onnx.export( wrapper, dummy_input, yolov5s_raw.onnx, opset_version11, input_names[images], output_names[output0, output1, output2], dynamic_axes{images: {0: batch}} )导出后用onnxsim简化一下计算图再检查一下输入输出的shapepython -m onnxsim yolov5s_raw.onnx yolov5s_sim.onnx python -c import onnx; monnx.load(yolov5s_sim.onnx); print([i.name for i in m.graph.input]); print([o.name for o in m.graph.output])这里需要额外提醒输出节点有三个分别对应640x640输入下的80x80、40x40、20x20三个尺度的特征图。不同YOLO版本的特征图shape不太一样导出前最好打印确认不然后面写后处理时很容易对不上。3.3 ATC转换让ONNX变成OM的关键一步这一步是整个链路中最容易出问题的地方。ATC转换的本质是把ONNX的算子逐一手动或自动映射到昇腾支持的算子格式同时完成布局转换和算子融合。命令如下atc \ --modelyolov5s_sim.onnx \ --framework5 \ --outputyolov5s_640 \ --input_formatNCHW \ --input_shapeimages:1,3,640,640 \ --soc_versionAscend310P3 \ --loginfo参数说明--framework5表示输入模型是ONNX。1是Caffe2是MindSpore3是TensorFlow5是ONNX别记反了。--output输出OM文件的路径前缀。--input_shape固定输入shape。这里固定成1张图如果要做动态batch可以写成images:-1,3,640,640但我建议固定shape动态shape在Atlas上会损失部分优化机会性能可能下降20%以上。--soc_version这个最容易踩坑。Atlas 300V Pro对应的Soc版本是Ascend310P3不同CANN版本和固件版本可能会显示成Ascend310P或Ascend310P1。如果你不确定可以先执行npu-smi info看芯片型号或者在ATC转换时用--soc_versionAscend310P3试报错的话再查日志。日志提示success后当前目录下会生成yolov5s_640.om。然后用omg或者atc自带的模型查看工具确认一下模型信息omg --modelyolov5s_640.om --output_typeFP32 --save_original_model1这一步不是必须的但能帮你快速验证OM文件没有损坏。3.4 写一个最小可跑的ACL推理程序接下来是写代码。我先给一个Python版本的完整骨架适合快速验证。逻辑分四步初始化设备、加载模型、准备输入输出、执行推理。import acl import numpy as np import cv2 # ACL初始化 ret acl.init() # 指定设备多卡时用device_id控制 ret acl.rt.set_device(0) # 加载om模型 model_path b./yolov5s_640.om model_id acl.mdl.load_from_file(model_path) # 获取模型描述信息 model_desc acl.mdl.create_desc() ret acl.mdl.get_desc(model_desc, model_id) # 获取输入输出尺寸 input_size acl.mdl.get_input_size_by_index(model_desc, 0) output0_size acl.mdl.get_output_size_by_index(model_desc, 0) output1_size acl.mdl.get_output_size_by_index(model_desc, 1) output2_size acl.mdl.get_output_size_by_index(model_desc, 2) # 申请device侧内存 input_ptr acl.rt.malloc(input_size, 2) output0_ptr acl.rt.malloc(output0_size, 2) output1_ptr acl.rt.malloc(output1_size, 2) output2_ptr acl.rt.malloc(output2_size, 2) # 准备输入数据 image cv2.imread(test.jpg) # 缩放、归一化、HWC转到CHW需要注意letterbox操作 img preprocess(image) # 输出为1x3x640x640的float32 ndarray input_data np.ascontiguousarray(img) # 拷贝数据到device acl.rt.memcpy(input_ptr, input_size, input_data.ctypes.data, input_size, 1) # 构造推理输入输出描述省略部分ACL api细节 # 关键使用acl.mdl.execute执行推理 ret acl.mdl.execute(model_id, [input_ptr], [output0_ptr, output1_ptr, output2_ptr]) # 取回结果 output0 np.zeros(output0_size // 4, dtypenp.float32) acl.rt.memcpy(output0.ctypes.data, output0_size, output0_ptr, output0_size, 1) # output1、output2同理 # 后处理把三个尺度的输出拼起来做NMS boxes postprocess(output0, output1, output2, conf_thres0.4, iou_thres0.5) # 释放资源 acl.rt.free(input_ptr) acl.rt.free(output0_ptr) acl.rt.free(output1_ptr) acl.rt.free(output2_ptr) acl.mdl.unload(model_id) acl.rt.reset_device(0) acl.finalize()这段代码省去了一些ACL的具体参数填写比如acl.mdl.execute需要传入的完整输入输出指针列表、模型的动态batch信息、stream的定义等。实际写的时候建议直接把官方样例resnet50_sample.py拿来改样例里的框架逻辑是完全通用的把截图输入和输出处理换成YOLO的就行。有一个非常重要的点YOLOv5的输入要做letterbox处理而不是直接cv2.resize。所谓letterbox就是保持原始宽高比缩放到640x640缺失部分用灰色填充通常是114,114,114。直接拉伸会导致目标变形检测精度下降明显。很多Atlas上的YOLO部署案例精度下降问题就出在预处理这里。预处理函数可以这样实现def letterbox(img, new_shape(640, 640), color(114, 114, 114)): shape img.shape[:2] r min(new_shape[0] / shape[0], new_shape[1] / shape[1]) new_unpad (int(round(shape[1] * r)), int(round(shape[0] * r))) dw (new_shape[1] - new_unpad[0]) / 2 dh (new_shape[0] - new_unpad[1]) / 2 img cv2.resize(img, new_unpad, interpolationcv2.INTER_LINEAR) top, bottom int(round(dh - 0.1)), int(round(dh 0.1)) left, right int(round(dw - 0.1)), int(round(dw 0.1)) img cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, valuecolor) return img后处理部分注意YOLOv5输出的格式是[batch, anchors, 5num_classes]其中5对应(cx, cy, w, h, objectness)。三个尺度分别对应80x80、40x40、20x20的特征图每个网格点上默认有3个anchor。后处理就是把所有anchor的输出合并筛选置信度做NMS。这块逻辑不复杂但写错shape会非常头疼。我建议用torch的后处理代码转成numpy版本保持逻辑一致。3.5 性能测试到底能跑多少帧模型跑通后我实际测了Atlas 300V 24G上YOLOv5s的推理性能。测试条件单路1080P视频流输入640x640INT8量化完成后单batch推理时间大约在4~6ms算下来单卡跑20~25路视频流每路25fps问题不大。但我第一次跑的时候发现单batch模式下GPU利用率只有30%左右——因为大部分时间花在数据拷贝上。后来把batch size调到4整体吞吐量提升了一倍还多。Atlas 300V这种推理卡非常依赖batch size它芯片内的矩阵单元最擅长处理大批量矩阵计算单张图喂进去反而会浪费计算资源。如果你的业务是单张图片实时请求可以在服务层面做请求聚合把同时到达的多张图拼成一个batch再推理延迟增加一点点吞吐量翻倍这个优化思路在Atlas上比在GPU上更明显。4. 调优与踩坑不亲身体验很难发现的细节4.1 不使用DVPP的话预处理就是最大瓶颈我第一次部署时发现一个反直觉的现象模型本身推理只要4ms但整条链路跑下来单张图要20ms以上多出来的十几毫秒全花在读图、resize、归一化、HWC转CHW上。这些操作是CPU在处理完全没利用上Atlas的硬件能力。Atlas芯片内部有个模块叫DVPPDigital Vision Pre-Processing专门做图像解码、缩放、格式转换比如JPEG解码、YUV420SP转RGB等。利用DVPP做预处理CPU占用率几乎为零速度还快得多。DVPP的坑在于它的输入输出格式限制。比如缩放功能要求输入是YUV格式JPEG解码输出也通常是YUV而YOLO需要的输入是RGB的CHW float32。所以链路通常走JPEG → DVPP解码成YUV → DVPP缩放 → 自己写kernel转RGB → 归一化。这套链路需要调用CANN的acl.dvpp相关API代码量不少但跑通了收益极大。4.2 INT8量化该不该做怎么做Atlas 300V的标称算力140 TOPS是INT8数据如果跑FP16精度算力会损失不少。所以想要性能最大化最好把模型量化到INT8。CANN提供了一套叫做AMCTAscend Model Compression Toolkit的量化工具支持对ONNX模型做离线量化。量化流程是准备校准数据集跑一遍模型收集每层激活值的分布然后生成量化后的模型。我的实际经验YOLOv5s在COCO类别的检测任务上从FP16切到INT8mAP下降不到1个百分点但推理速度提升约1.5倍。如果你的业务对精度要求不是变态级INT8一定是首选。但量化时校准数据集要选得有代表性别只拿几张纯色图片糊弄否则某个层激活分布估算不准量化后特定场景会出现大面积漏检。4.3 常见报错和排查速查表我把这半年多来遇到的高频问题整理成一个表格方便以后排查现象可能原因解决办法ATC转换时报E40000算子不支持ONNX里包含Atlas暂不支持的算子比如部分GridSample、部分版本的SiLU升级CANN版本或者用onnxsim消除冗余算子再不行就要用算子替换手写Equivalent推理时报aclErrorruntime model execute failed模型输入shape和实际数据不匹配或者输入数据不是按NCHW排列打印模型desc的实际输入shape对比确认预处理后ndarray是C_CONTIGUOUS加载OM文件时内存不足卡上同时加载了多个大模型24G显存被耗尽用npu-smi info查看NPU内存占用统计每个模型占用必要时排队加载推理结果全为0或者形状错乱输入图像的letterbox参数和后处理坐标还原没做后处理时坐标要除以缩放比例r并减去padding偏移量多线程推理崩溃ACL的context不是线程安全的多线程推理需要每个线程创建独立context参考官方多线程样例每个线程初始化自己的acl context和streamDVPP输出图像花屏YUV格式宽度没有按16对齐或者缩放参数不合规DVPP对图像尺寸对齐有硬性要求宽高要2或16的倍数先对齐再送DVPP4.4 多线程并发推理的注意点在实际服务里不可能一张图一个进程去推理并发是必须的。Atlas的并发和GPU不太一样GPU上可以定义多个CUDA Stream并发执行kernel而Atlas上更推荐的方式是多线程多context。每个线程创建自己的acl.rt.context和acl.rt.stream线程内部按单线程同步方式调用acl.mdl.execute。但要注意同一个模型ID可以在多个线程里并发调用吗答案是可以但建议加锁控制同一时刻发起的推理请求数量。Atlas 300V的NPU只有一组计算资源线程开多了并不会并行执行反而引入线程切换开销。我测试下来的经验是开2~4个线程做请求分发和结果回收比开几十个线程要稳得多吞吐量不降反升。另外ACL默认是同步推理接口即acl.mdl.execute执行完才返回。如果需要异步要配合acl.mdl.execute_async和acl.rt.synchronize_stream使用真正的异步路径是用C开发时才会用到Python端先掌握同步模式就够应付大部分场景。5. 部署之后的扩展从单卡到多卡、从Demo到服务如果只是跑通Demo上面那些内容足够了。但真到生产环境还有几个问题值得提前想清楚。多卡场景一台服务器插两张Atlas 300VPCIe通道要对插到不同CPU的Root Port上否则NUMA访问远端内存会影响性能。分配设备时用acl.rt.set_device(device_id)做绑定同时注意两张卡之间不能直接通信不像NVLink那样需要走CPU内存中转。服务化封装推理程序最好封装成独立的推理服务用gRPC或者HTTP对外提供接口。我在项目中是把ACL推理代码包成一个Python进程接收上游消息队列发来的图片处理完再回传结果。好处是业务代码和推理代码解耦模型更新不影响主链路。动态batch如果你做在线检测服务请求流量不均匀固定batch会有浪费。CANN支持动态batch转换时输入shape写成images:-1,3,640,640推理时通过acl.mdl.set_dynamic_batch_size指定具体batch数。但动态batch会牺牲一部分算子融合优化换来的灵活性是否值得取决于你的业务流量模型。还有一件事要提醒Atlas的驱动、固件、CANN版本三者之间有严格配套关系。升级驱动后CANN不匹配模型可能加载失败降级CANN后固件不匹配NPU初始化直接报错。之前我图省事直接apt upgrade把驱动升了结果所有OM模型全挂折腾了一天。建议固定版本号别频繁升级除非有明确的功能需求或者已知漏洞修复。最后再分享一个调试小技巧用ATCL转换报错时日志里其实已经把失败算子打印出来了但信息往往被一堆Warning刷掉。教大家一个排查姿势atc --modelyolov5s_sim.onnx --framework5 --outputyolov5s_640 \ --soc_versionAscend310P3 --logdebug转换完成后去/root/atc_*/debug目录下看plog日志搜索FAILED、not support、Unsupport这些关键词直接能定位到是哪个算子出的问题。如果日志太长就用grep -i error\|failed过滤。这个办法比你在网上搜索“ATC报错E40000怎么办”要高效得多。另外建议保留一张不使用NMS的onnx备份。Atlas上如果某个后处理算子不支持你可以把NMS逻辑从模型里摘出去放到CPU上跑。YOLO的NMS在检测目标数量不是特别大的时候CPU上跑也就一两毫秒对整体性能影响完全可控但模型兼容性和调试效率会好很多。Atlas 300V 24G这个卡说实话一开始我也有点偏见——毕竟项目时间紧、交付压力大谁都不想换一个不熟悉的硬件平台。但实际把YOLO模型从PyTorch搬迁上去之后我发现这套工具链的成熟度比预期高很多。它的价值在于24GB内存能装下不少中等规模的模型75W功耗放在边缘机房完全不心疼140 TOPS INT8算力在视频分析场景下能顶得住几十路摄像头。它的劣势也很明显如果你的推理任务里有大量动态shape、大量定制算子那适配成本会比较高。总之评估的时候别只看算力数字要从模型结构、业务并发模式、团队技术栈几个维度综合判断。
返回列表