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

资讯详情

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

Atlas 300V AI推理加速卡部署YOLO实战:从ONNX转换到性能调优

Atlas 300V AI推理加速卡部署YOLO实战:从ONNX转换到性能调优 做模型部署这几年我最大的感受是很多人一提到AI推理加速脑子里就只剩下显卡两个字。直到有一次客户现场机房管理员指着一块不带风扇、没有视频输出口的PCIe卡问我这玩意是不是运算加速卡我才意识到Atlas这类设备在业界的认知度比想象中低得多。后来我把这块卡拿回来仔细研究才发现它不仅是运算加速卡而且在跑YOLO这类目标检测模型时有不少和GPU完全不同的门道。这篇就围绕Atlas 300V展开把硬件的真实身份、部署YOLO的完整链路、以及我踩过的坑一次说清楚。先说结论Atlas 300V是华为昇腾系列的AI推理加速卡它和GPU是两条完全不同的技术路线。你不能直接拿PyTorch的.pt权重往上一扔就跑得经历导出ONNX → ATC工具转换 → 用ACL接口写推理代码这套流程。听起来麻烦但当你搞清楚它为什么这么设计以及换了架构之后能换来什么你就不会觉得别扭了。1. Atlas 300V的真实身份一张不是显卡的运算加速卡1.1 先回答那个热搜问题它到底是不是运算加速卡答案是是但你要把它理解成专用加速卡而不是显卡。显卡这个词容易让人联想到画面渲染、显示器接口而Atlas 300V这类产品压根不关心显示它要干的事情只有一件高效地跑神经网络推理。从外观上看Atlas 300V Pro是一张标准的PCIe插卡体积不大被动散热无风扇靠服务器风道带走热量。我第一次看到它的时候第一反应也是这不会是某款老式显卡吧。直到我用npu-smi info命令查看设备状态看到下面的信息才对它有了直观认识------------------------------------------------------------------------------------------- | npu-smi 22.0.0 Driver Version: 22.0.0 | ---------------------------------------------------------------------------------------- | NPU Name | Model | HBM | Power | Temp | Health | | 0 Ascend 310P | 300V Pro | 24GB | 60W | 45C | OK | ----------------------------------------------------------------------------------------这块卡上的芯片是昇腾310P系列架构是华为自研的达芬奇架构里面的计算核心叫AI Core。它最大的特点不是跑分跑得多高而是把功耗压得很低一张卡整卡功耗只有60W左右24GB的LPDDR4X显存主要负责给权重和中间特征图做缓存。这里有个很容易混淆的点24GB这个数字在显卡厂商那里通常意味着能塞下大模型但在Atlas 300V上它更准确的定位是给推理过程中的中间计算提供足够的缓冲空间。你用这块卡去训练大模型是不现实的它就是为推理场景专门设计的。1.2 Atlas 300V、300I、300V Pro这几个型号怎么区分我在搜索Atlas 300V时发现很多人把300I、300V、300V Pro搞混。确实这几款卡长得都差不多都是PCIe形状也都是昇腾芯片但它们的定位有明显差异。Atlas 300I Pro对标的是偏视频图像处理的推理场景带硬件的视频编解码能力适合做视频流分析的预处理。常见的配置是8GB或16GB内存。Atlas 300V基础版AI推理卡核心侧重于通用AI推理型号通常配置8GB的内存。Atlas 300V Pro加强版AI推理卡24GB内存算力更高。这也是目前市面上比较抢手的型号适合在OCR、工业质检、智慧园区这类需要同时跑多个模型、处理较大输入分辨率的场景。拿我在用的Atlas 300V Pro来说它的AI算力标称是140 TOPS INT8也就是每秒可以完成140万亿次8位整数运算。这个数字不能直接和显卡的TFLOPS对比——NVIDIA的A100做FP16大概有312 TFLOPS但那是浮点算力两者用于量化的粒度和方式完全不同。实际使用中我们更关心的是跑一个YOLOv5s模型输入640x640一帧需要多少毫秒这个后面我会给出我自己实测的数据。1.3 达芬奇架构和NV的CUDA架构思路不同在哪想用好Atlas你必须理解达芬奇架构的底层逻辑。显卡上的CUDA核心是通用并行计算单元什么算子都能跑所以生态特别丰富什么深度学习框架都有现成的GPU支持。而昇腾的达芬奇架构更专一它的核心由Cube Unit矩阵计算单元、Vector Unit向量计算单元和Scalar Unit标量计算单元组成其中Cube Unit专门做矩阵乘加这对卷积神经网络有着天然的优势。打个比方CUDA相当于一个全能选手什么项目都能接但单项水平一般达芬奇架构更像是一支分工明确的手术团队做卷积这种特定任务时效率极高但你让它跑完全不相干的通用计算它反而不擅长。这也就解释了一件事为什么Atlas系列在跑CV模型时速度很快但跑一些结构特别复杂的NLP模型时会遇到算子不兼容的问题。不是它性能不行而是这些算子的计算模式不一定能映射到Cube、Vector、Scalar的架构分工上。所以你在做模型转换时常看到算子不支持的报错就是这个原因。2. 部署YOLO前的关键认知这东西不是装上就能跑的显卡2.1 为什么不能直接加载PyTorch权重很多第一次接触Atlas的朋友都会问我有一个训练好的YOLOv5权重能不能像GPU那样model.load_state_dict(torch.load(...))直接加载推理答案是不行而且根本原因不在于性能而在于指令集和计算图描述方式不同。PyTorch的权重文件里存的是张量的数值和结构关系它本身不依赖任何硬件。但当你真正执行模型时框架会把计算图摔给底层的运算库在GPU上是CUDA的cuDNN、TensorRT在Atlas上是CANNCompute Architecture for Neural Networks昇腾计算架构。CANN不认识PyTorch的TorchScript也不认识你直接写出来的Python算子它只认两种模型格式Caffe的caffemodel和昇腾的OM格式Offline Model离线模型。OM里面不仅包含了网络结构、权重信息还包含了算子在昇腾硬件上的排布策略、内存分配方案等已经提前规划好的指令就像一份已经排练好的节目单到了NPU上直接按顺序执行就行。所以部署路径就很清晰了权重 → 导出ONNX一种通用的神经网络交换格式 → 用CANN自带的ATC工具转成OM → 再写C或Python的ACL推理代码调用。我的建议是凡是接触Atlas系列设备的第一步就先把这条路径刻在脑子里后面遇到问题你会少走很多弯路。2.2 昇腾软件栈的分层结构在实操之前建议先把昇腾的软件栈结构梳理一遍不然你会被一长串名词搞晕CANN、Toolkit、Kernel、ACL、MindSpore、MindX这些都是什么关系我画一个最简单的分层理解最底层驱动Driver和固件Firmware。驱动负责让操作系统识别到NPU设备固件负责芯片的基础运行管理。驱动安装成功后npu-smi info才能正常显示设备。中间层CANN Toolkit。这是昇腾的计算架构相当于NVIDIA的CUDA Toolkit。CANN里面包含了一个至关重要的工具叫ATCAscend Tensor Compiler负责把ONNX、Caffe模型转换成OM离线模型。同时CANN还提供了统一的运行时接口也就是ACLAscend Computing Language——昇腾的计算接口语言类似CUDA Runtime API。你在写推理代码时基本上就是在调用ACL。上层推理引擎或框架。华为提供了MindSpore、MindX SDK、ModelBox等更高级的工具但底层都是基于ACL封装的。我个人的建议是如果你想搞清楚原理、灵活控制过程最好先用ACL的Python接口或C接口写一遍裸推理再上封装好的SDK这样出了问题时你不会两眼一抹黑。软件栈版本匹配是一个容易踩坑的点。CANN Toolkit的版本必须和驱动版本兼容如果你装的是CANN 6.x却配了个5.x的驱动跑npu-smi info能看到设备但ATC转换模型或者加载OM时会出现各种奇怪的报错。最稳妥的做法是去昇腾社区查一下版本配套表按照表格里的组合来装不要混搭。2.3 AIPP预处理这是Atlas提升整体性能的法宝在GPU上做推理预处理通常在CPU上做比如OpenCV读取图片、用NumPy做归一化、把BGR转成RGB然后再把数据拷贝到GPU显存。这会造成CPU和GPU之间的数据拷贝开销而且这些操作在CPU上是串行执行的一帧还好多路并发时CPU就很容易成为瓶颈。Atlas的ACL接口提供了一种叫做AIPPAscend Image Pre-Processing的硬件预处理模块。你可以在模型转换时把图像的裁剪、缩放、色域转换、归一化这些操作通通写进AIPP的配置文件里让NPU在数据进入模型之前自动完成这些操作。这样一来你只需要把原始图片的像素数据从CPU拷贝到NPU侧预处理可以直接在NPU内部完成减少了一次CPU到NPU的数据搬运。在多路视频流推理的场景下这个优化特别有感知。我后来才发现很多人部署YOLO性能跑不上去不是模型推理慢而是预处理和数据拷贝占了大量时间。用AIPP把这一步从CPU移到NPU之后端到端延迟能下降百分之二三十非常可观。3. 实操把YOLOv5的权重完整部署到Atlas 300V上3.1 第一大步导出ONNX模型我用YOLOv5 v6.0版本为基础来演示。先确认你已经有一个训练好的yolov5s.pt权重然后执行下面的命令导出ONNXcd yolov5 python export.py --weights yolov5s.pt --include onnx --opset 11这里有一个细节要注意YOLOv5官方导出ONNX时默认会把一些后处理算子一起导出比如torchvision.ops.nms。但是Atlas侧尤其是ATC工具对自定义的后处理算子支持不算友好所以更推荐的做法是只导出模型的主干和检测头把后处理放在NPU之外用CPU完成。你可以用YOLOv5源码里的detect.py配合--no-trace之类的参数来调整导出内容或者手动修改导出脚本把非极大值抑制NMS相关的代码从导出计算图中剥离。这样得到一个纯净的、只包含卷积、激活、输出原始预测值的ONNX模型。测试下来这个模型的输入是1x3x640x640的归一化图像输出是一个形状为1x25200x85的张量——其中25200 3个尺度 × 每个尺度20x20、40x40、80x80的网格点85则是4个坐标值 1个置信度 80个类别分数以COCO类别为例。3.2 第二大步用ATC工具转换为OM离线模型拿到ONNX后下一步就是写一个AIPP配置文件然后用ATC工具转换。以我常用的配置为例# aipp.cfg aipp_op { aipp_mode: static input_format: RGB888_U8 src_image_size_h: 640 src_image_size_w: 640 crop: false load_start_pos_h: 0 load_start_pos_w: 0 csc_switch: true rbuv_swap_switch: true 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 }这段配置的含义是输入原始图像是RGB888格式、每个像素是UINT8类型。csc_switch: true表示允许做色域转换rbuv_swap_switch: true表示交换R和B通道——因为你可能用OpenCV读图得到的是BGR而模型是按RGB训练的。min_chn_0到min_chn_2填的是1/255做归一化到0~1之间等效于PyTorch里除以255的操作。然后执行ATC转换命令atc --modelyolov5s.onnx \ --framework5 \ --outputyolov5s_om \ --soc_versionAscend310P3 \ --input_shapeimages:1,3,640,640 \ --insert_op_confaipp.cfg \ --output_typeFP32这个命令的参数逐个解释一下--framework55代表ONNX格式这是一个固定的枚举值。--soc_versionAscend310P3表示目标芯片型号必须和你的实际芯片一致。Atlas 300V Pro对应的昇腾310P系列芯片如果型号写错生成的OM能转换成功但加载到NPU上会报chip版本不匹配的错误。--insert_op_confaipp.cfg把刚才写的AIPP配置嵌入到OM模型里。--output_typeFP32设定模型输出是FP32精度。你也可以用FP16来加速但对于YOLO这种对数值敏感的目标检测任务我实测下来FP32的输出在后续NMS中更加稳定所以没有为了极致速度去压精度。转换完成后会生成一个yolov5s_om.om文件这就是能在昇腾NPU上直接加载的离线模型。你还可能得到一个包含算子耗时分析的atc.log文件可以顺手看一眼里面有各个算子在NPU上的预估执行时间可以初步判断哪个算子比较耗时。3.3 第三大步用ACL的Python接口写推理代码驱动装好了、模型转换好了接下来就是写推理代码。昇腾提供了C和Python两种ACL开发语言我这里用Python做示范逻辑相对容易理解。先初始化环境和加载模型import acl import numpy as np import cv2 ACL_MEMCPY_HOST_TO_DEVICE 4 ACL_MEMCPY_DEVICE_TO_HOST 3 ACL_MEM_MALLOC_NORMAL_ONLY 2 # 初始化 ret acl.init() assert ret 0, facl.init failed: {ret} # 指定设备 ret acl.rt.set_device(0) assert ret 0 # 创建上下文 context acl.rt.create_context(0) # 加载模型 model_path b./yolov5s_om.om model_id acl.mdl.load_from_file(model_path) assert model_id is not None然后获取模型描述申请输入输出内存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) output_size acl.mdl.get_output_size_by_index(model_desc, 0) input_data_buffer acl.rt.malloc(input_size, ACL_MEM_MALLOC_NORMAL_ONLY) output_data_buffer acl.rt.malloc(output_size, ACL_MEM_MALLOC_NORMAL_ONLY) input_dataset acl.mdl.create_dataset() output_dataset acl.mdl.create_dataset() input_data_buffer_item acl.create_data_buffer(input_data_buffer, input_size) acl.mdl.add_dataset_buffer(input_dataset, input_data_buffer_item) output_data_buffer_item acl.create_data_buffer(output_data_buffer, output_size) acl.mdl.add_dataset_buffer(output_dataset, output_data_buffer_item)接下来处理图片。这里的关键点是因为AIPP配置里已经做了归一化和通道转换我只需要把原图缩放并拷贝到NPU输入缓冲区不需要再做额外的预处理image cv2.imread(test.jpg) image cv2.resize(image, (640, 640)) # 注意因为AIPP里配置了 rbuv_swap_switch true这里可以传BGR原始数据 # 但实际开发中为了避免混淆我通常还是在这里转成RGB image_rgb cv2.cvtColor(image, cv2.COLOR_BGR2RGB) input_np np.ascontiguousarray(image_rgb, dtypenp.uint8) # 拷贝图像数据到NPU内存 ret acl.rt.memcpy(input_data_buffer, input_size, input_np.tobytes(), input_size, ACL_MEMCPY_HOST_TO_DEVICE) assert ret 0 # 执行推理 ret acl.mdl.execute(model_id, input_dataset, output_dataset) assert ret 0推理完成后读出输出数据# 从输出数据集中取第一个buffer output_buffer_item acl.mdl.get_dataset_buffer(output_dataset, 0) output_buffer_addr, output_buffer_size acl.get_data_buffer_addr(output_buffer_item) # 注意这里是关键必须通过bytes接口从内存复制出来 output_np np.frombuffer( acl.util.bytes_to_ptr(output_buffer_addr).get_bytes(output_buffer_size), dtypenp.float32 ).reshape(1, 25200, 85) # 释放内存 acl.rt.free(input_data_buffer) acl.rt.free(output_data_buffer) acl.mdl.unload(model_id) acl.rt.reset_device(0) acl.finalize()拿到1x25200x85的输出后后面的后处理就和GPU上跑YOLO完全一样了对每行做置信度筛选过滤掉低于阈值的候选框然后用NMS去重最后映射到原图坐标画框。把NMS留在NPU外部和AIPP留在NPU内部这两件事一结合整条链路的体验才会顺畅。3.4 一个小小的多路并发预告在实际项目中你几乎不可能只处理单张图片。像视频分析这类场景往往需要同时跑十几路甚至几十路视频流。Atlas 300V Pro的一个强项是在同一块卡上可以同时加载多个模型实例或者通过ACL的Stream机制做并发。Stream是ACL中类似CUDA Stream的概念是多路任务独立执行的载体。stream acl.rt.create_stream() # 在多线程中每路线程绑定不同的stream # acl.rt.execute 等操作都传入该stream做多路推理时我习惯把整个流程拆成多个线程每个线程各拥有独立的输入输出buffer和一个Stream在单卡上同时提交多个模型的推理请求。由于Atlas的调度器会合理分配AI Core资源这样能有效利用整卡算力。这个后面我会再展开讲性能优化这里先留个引子。4. 实测中的坑和排查路径这些报错信息替你试过错4.1 这个显卡在我机器上居然不亮驱动和固件的版本匹配我第一次拿到Atlas 300V时插进服务器系统日志里找不到任何新设备npu-smi info直接报Err: No npu devices found。我当时的第一反应是硬件坏了。后来问了一圈有经验的人才意识到问题多半出在软件栈版本上。Atlas这类设备对驱动、固件、CANN Toolkit的版本组合要求极其严格。你不仅要装驱动还要把固件刷成配套的版本两者缺一不可。完整的排查链路是这样的# 1. 先看系统能否识别到PCIe设备 lspci | grep -i processing如果结果里出现了类似Huawei Technologies Co. Ltd. Device 8020这样的条目说明硬件层面没问题操作系统能看到这张卡。如果没有则要先检查插槽物理接触、BIOS里是否禁用了PCIe设备。# 2. 再查驱动是否加载 lsmod | grep drv npu-smi infonpu-smi info是一个极其重要的诊断命令它不仅能显示卡的基本信息还能显示驱动版本和固件版本。如果这步能看到设备但显示版本有误或提示Firmware版本与驱动不匹配就需要去昇腾社区下载对应版本的固件安装包用官方提供的一键升级脚本刷新。# 3. 升级固件 ./upgrade-tool --device_index 0 --component firmware --upgrade_file ./firmware.bin这一步过后再重启服务器设备才能正常识别。这整个过程的经验是拿到设备后第一件事不是装CANN Toolkit而是先按配套表把驱动和固件对齐再往下走。我踩过的这个坑至少帮你省下半天折腾时间。4.2 ATC转换时的算子报错从E19999到问题定位的完整思路第二个让我头疼的问题出在ATC转换阶段。我用一个比较新版本的YOLOv8模型做转换时ATC报了个E19999: Inner Error下面跟着一大串错误码和输出节点的信息。E19999属于昇腾工具链的通用内部错误码本身没有明确含义但它的报错日志里通常会包含具体出错的那个算子的名字。我当时搜日志定位到问题出在一个叫Split的算子原因是模型里的某个Split操作在计算图优化时产生了非常规的输入输出形状ATC无法映射到NPU的Vector单元。解决思路有两个方向一是修改导出的ONNX在导出时用torch.onnx.export的dynamic_axes参数或者调整simplify的ONNX简化选项让计算图更规整。很多算子不兼容问题其实不是算子本身不支持而是计算图中有冗余的分支和多余的恒等操作导致ATC的图优化流程解析失败。二是用ATC提供的自定义算子注册机制针对不支持的算子做映射。这个门槛比较高一般用不到但如果真要解决可以参考昇腾社区自定义算子开发的文档用TBE或Ascend C语言写算子实现。最终我的经验是优先调整ONNX导出选项少动模型结构。对于YOLO系列建议把模型固定输入尺寸导出不要用动态shape因为ATC对动态shape的支持虽然存在但会明显降低NPU上的调度效率。固定成1,3,640,640后无论是转换成功率还是推理速度都会好很多。4.3 模型能加载但推理速度慢检查数据摆放和内存拷贝第三个问题不报错但比报错更让人难受——速度提不上去。明明模型转换成功了推理也能出结果但一帧耗时到了六七十毫秒跟GPU跑差不多的水平甚至更慢完全发挥不出NPU的优势。我排查后发现原因出在数据摆放和内存拷贝上。具体来说是每次推理前都用np.asarray或.tolist()处理数据或者在Host和Device之间反复拷贝了多次。有一种典型做法是把输入数据先放到一个numpy数组再转成Python list最后调ACL接口时又转换回字节流这中间多了两次无意义的复制。优化后的做法是在初始化时就申请好固定大小的输入输出buffer之后每一帧只把像素数据拷入不要反复malloc/free。用acl.rt.memcpy直接传入原始字节串不要在中间转成list再转回来。如果有多路视频流优先处理解码和前处理的流水线让NPU尽可能保持满负荷运行而不是等CPU处理完才提交。把内存申请挪到初始化阶段之后我的YOLOv5s单帧推理耗时从原来的60毫秒左右降到了20毫秒左右整卡算力有了一点眉目。后来再看版本配套表发现昇腾官方推荐的CANN版本配合驱动版本是有性能基准的不配套会造成部分算子走CPU回退性能自然就拉胯了。所以性能不达标的时候先检查版本配套表再检查数据拷贝路径这两个方向能解决绝大多数问题。5. 性能调优与多路并发落地把Atlas 300V真正推向生产环境5.1 用Stream机制和模型实例复数化提升卡利用率前面提到过ACL的Stream机制这其实是我用Atlas之后感觉最像正式做工程的地方。你可以把Stream理解为一条任务流水线每个Stream内部的任务按提交顺序执行不同Stream之间可以并行。在实际项目中如果数据量达到几十路视频流我的做法是这样创建多个Stream每个Stream绑定固定的一路或几路视频源。每路视频在初始化阶段完成模型加载和内存分配。推理请求通过acl.mdl.execute_async异步提交到Stream队列不等结果返回就继续处理下一帧。异步接口相当关键。acl.mdl.execute是同步的调用后必须等NPU执行完才能返回这在多路场景下会让CPU白白等待。acl.mdl.execute_async则不同你提交完请求后可以立刻去做画面缩放、数据传输入等准备工作等NPU算完了再回来取结果。我粗略测过在Atlas 300V Pro上用异步接口跑YOLOv5s、640x640输入、单帧单图推理端到端时延大约能做到20毫秒以内。如果采用批量推理batch4把4帧打包成一批输入整体的吞吐量还能再往上涨因为AI Core处理大矩阵比处理多个小矩阵效率更高。5.2 硬件解码和图像前处理管线一起搬进NPUAtlas 300V Pro上还有个经常被忽略的能力硬件视频解码。它支持H.264、H.265硬件解码这在视频分析场景是个大杀器。常规的部署架构是这样的视频流先由CPU用FFmpeg软解成YUV帧再做缩放和格式转换然后送进模型。CPU软解一路1080p的视频就要占用不少计算资源几十路下来CPU必然成为瓶颈。换到Atlas之后你可以把解码也交给NPU。CANN提供VDEC视频解码引擎的接口可以直接调用NPU上的硬件解码单元。我实验过用FFmpeg把视频流推给Atlas的VDEC做硬解然后直接把解码后的YUV帧作为输入送到AIPPAIPP支持YUV格式输入经过色域转换后进模型。这条链路的CPU占用率比纯CPU软解下降了非常明显真正做到了从拉流解码到推理画框的全链路硬件加速。有这个能力的存在Atlas 300V Pro在视频类项目中的价值就体现出来了——它不只是一块运算加速卡更像是一块集成了视频编解码和AI推理的复合加速卡。之前不少在GPU上还需要再买一块视频解码卡才能搞定的场景用一块Atlas就全解决了。5.3 模型精度和大模型的落地建议最后聊一下模型部署的精度问题。Atlas的推理链路对INT8量化支持得不错ATC工具也有量化校准的相关能力。但正如我之前提到的对于YOLO目标检测如果后处理中的NMS阈值非常敏感或者你检测的物体有小目标我不建议一味地降到INT8因为量化对细节纹理的损失确实存在。实际项目里我通常先用FP16跑通整个流程保证检测精度和GPU版本持平然后才去测试INT8是否能够满足业务指标。如果业务指标能接受再上INT8换更高吞吐如果不能接受就维持FP16。至于大模型Atlas 300V只有24GB内存跑上百亿参数的LLM是不现实的它更擅长的是YOLO、OCR、人脸识别、语义分割这类CV模型和中小规模的NLP模型。如果真有超大模型推理需求需要注意看昇腾的Atlas 800或Atlas 900系列训练服务器节点那里才有更大的显存和算力池。我一贯的观点是选型之前先想清楚自己的模型规模、延迟要求和吞吐目标再决定用哪一级的算力设备而不是拿到什么卡就往上面堆什么模型。Atlas 300V Pro对于中小型视觉模型、数十路视频分析这类场景性能和性价比都非常能打。
返回列表