拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Atlas 300V 24G部署YOLOv5全流程:环境搭建、模型转换与性能调优

Atlas 300V 24G部署YOLOv5全流程:环境搭建、模型转换与性能调优 前两天看到有人在搜“atlas 300v 24g 是运算加速卡吗”紧接着还有一条是“atlas部署yolo”。这两个问题拼在一起基本就是一张昇腾推理卡从“这玩意到底能不能用”到“怎么把它跑起来”的全过程心态写照。我最近正好在Atlas 300V 24G这张卡上把YOLOv5检测模型整套流程从头到尾过了一遍包括环境安装、模型转换、推理执行、后处理、性能调优这些环节踩了不少坑也把关键步骤都摸清了。网上的资料偏零散要么只讲某个片段要么直接甩官方文档链接让读者自己啃我干脆把这段实践整理成一篇能直接照着操作的完整记录给准备在昇腾平台上做YOLO推理部署的人省点时间。这篇文章不会像官方手册那样把所有接口面面俱到地讲一遍而是沿着一条真实落地路径走从确认Atlas 300V 24G的硬件定位到搭建环境、转换模型、编写推理代码再到性能实测和常见报错排查。适合已经把YOLO在GPU或CPU上跑通过但第一次接触昇腾生态的开发者也适合正在选型推理硬件、想搞清楚这张卡到底能干什么的决策者。1. Atlas 300V 24G这张卡到底是什么定位1.1 先回答热词里的疑问它算不算“运算加速卡”答案是肯定的但它和很多人习惯用的GPU加速卡不是一回事。Atlas 300V 24G是昇腾310P系列芯片的推理加速卡24G是指板载内存容量。它的核心业务是AI模型推理也就是把训练好的模型加载进来对输入数据做前向计算输出检测、分类、分割这类结果。YOLO这类目标检测模型恰好就是它的典型应用场景之一。这里要分清一个概念推理加速卡和训练加速卡面对的是两种完全不同的需求。训练要跑反向传播要存梯度、优化器状态、中间激活值对算力和显存带宽的要求极其苛刻推理只需要前向计算算力要求相对低一些但对延迟、吞吐、单位功耗性能、长期稳定运行这些指标非常敏感。Atlas 300V 24G在INT8推理场景下能提供不错的算力24G内存也基本覆盖了主流视觉模型在batch处理下的内存需求用在视频分析、工业质检、边缘计算这类推理场景里是比较合适的选型。我理解“运算加速卡”这个疑问的根源在于很多人接触到的AI加速卡都是NVIDIA的GPU拿到一张昇腾卡之后发现驱动、开发库、模型格式全都不一样自然会怀疑这卡到底行不行。实际用下来Atlas 300V 24G的推理能力是够用的只是整个开发流程要从“GPU思维”切到“昇腾思维”。1.2 昇腾部署YOLO和GPU部署YOLO的本质区别在GPU上部署一个YOLO模型常规操作大概是这样PyTorch训练得到权重转成TorchScript或者ONNX然后用TensorRT做优化或者直接在PyTorch框架里调用模型做推理。整个链路都是围绕PyTorch和CUDA生态转的模型文件拿到手基本能直接跑中间要处理的主要是算子融合、精度校准、动态shape这些优化问题。昇腾平台完全不是这个思路。PyTorch模型不能直接在昇腾NPU上跑必须先把模型转换成昇腾的OM格式这个转换由CANNCompute Architecture for Neural Networks工具链中的ATCAscend Tensor Compiler完成。转换完成之后还要通过AscendCL昇腾计算语言的API来加载模型和执行推理也就是在代码里以C/C或Python的方式调用NPU。这意味着整个工程链路从模型格式、推理框架、内存管理方式都换了每一步都可能出现GPU时代根本没遇到过的问题。理解这个本质区别后后面所有的步骤就顺理成章了。你不再是“把模型扔进去跑”而是“把模型交给昇腾编译器重新编译再通过昇腾运行时调度”。这个心智模型的转换决定了你后面遇到报错时能不能快速定位问题。2. 部署前先理清驱动、固件、CANN三件套的版本搭配2.1 我第一次装环境的失败经历刚拿到设备时我犯了一个想当然的错误以为跟装GPU驱动一样装上驱动就能用。结果驱动装好之后运行npu-smi info能看见卡但是一跑CANN工具就报错转换模型时报ERROR(1000): Failed to load model搞了半天才发现是驱动、固件和CANN三者的版本不配套。这其实是昇腾环境最容易踩、也最让人挫败的坑。昇腾的环境不是由一个软件包构成的而是由三个关键组件组成驱动Driver负责NPU与操作系统之间的通信可以理解成让系统“认识”这张卡的基础软件。固件Firmware固化在NPU硬件上的运行程序负责NPU内部的任务调度、资源管理。CANN工具包Ascend Toolkit提供模型转换工具ATC、运行时库AscendCL、算子库等开发组件。三个组件的版本不是独立的官方有一份配套表明确规定了哪个CANN版本对应哪个驱动版本和固件版本。装的时候不能追求“每个都装最新”而要“整套都装配套”。我用的是CANN 7.0.RC1配套驱动是23.0.RC3系列固件也是同系列这套组合实测稳定。2.2 正确的安装顺序和环境变量配置安装顺序是固定的先装驱动再装固件最后装CANN。反过来的话CANN检测不到NPU设备后面全部白搭。具体步骤以x86服务器加昇腾310P卡为例# 1. 安装依赖包 sudo apt-get install -y gcc g make cmake zlib1g zlib1g-dev openssl libsqlite3-dev libssl-dev libffi-dev unzip pciutils net-tools libblas-dev gfortran libblas3 libopenblas-dev liblapack-dev # 2. 安装驱动--full表示完整安装--install-for-all表示所有用户可用 sudo ./Ascend-hdk-310P-npu-driver_23.0.rc3_linux-x86_64.run --full --install-for-all # 3. 安装固件 sudo ./Ascend-hdk-310P-npu-firmware_23.0.rc3_linux-x86_64.run --full # 4. 安装CANN工具包 sudo ./Ascend-cann-toolkit_7.0.RC1_linux-x86_64.run --installCANN装完后务必执行环境变量脚本否则命令行里的atc、npu-smi这些工具都找不到Python里import acl也会失败source /usr/local/Ascend/ascend-toolkit/set_env.sh这个环境变量脚本建议直接写进~/.bashrc或者/etc/profile因为昇腾工具链里的工具太多每个都依赖这个环境变量每次开终端都手动source一遍太容易遗漏。我是在服务器上给专门做推理的用户配了开机自动加载省心很多。2.3 用npu-smi确认设备状态环境装完后别急着做模型转换先确认设备正常。npu-smi info正常输出会列出每张NPU卡的状态、芯片类型、温度、内存使用率、算力利用率这些信息。如果能看到卡的型号和温度正常说明驱动和固件工作正常。如果命令报错或者找不到卡优先检查驱动是否加载成功以及当前用户是否有权限访问NPU设备。这里有一个权限问题值得注意默认情况下NPU设备节点归属于安装驱动时创建的HwHiAiUser用户组。如果你是用root装的环境然后用普通用户跑推理可能会遇到“Device open failed”或者“Permission denied”。解决办法是把当前用户加入HwHiAiUser组或者直接用root跑测试。我建议在项目初期用root验证功能等整个链路跑通后再做用户权限收敛这样可以避免把环境问题和权限问题混在一起排查。3. 从YOLO权重到OM模型ATC转换的完整链路3.1 PyTorch导出ONNX时的几个关键约束YOLOv5训练好的.pt权重不能直接丢给ATCATC不认PyTorch格式。中间必须要经过一个ONNX格式的桥梁。这个导出过程看似简单实际上有几个约束直接影响后面的转换能否成功。第一个是输入输出的shape要明确。YOLOv5在PyTorch中原生支持动态输入尺寸但如果直接导出ONNX并保持动态shapeATC转换时就会因为shape不确定而产生很多额外的配置和限制。在推理部署场景里除非业务必须支持任意输入尺寸比如文档扫描、复杂版面分析否则我强烈建议固定输入shape。以YOLOv5s为例最常用的是640x640导出命令直接指定import torch model torch.load(yolov5s.pt, map_locationcpu)[model].float() model.eval() dummy_input torch.randn(1, 3, 640, 640) torch.onnx.export( model, dummy_input, yolov5s.onnx, opset_version11, input_names[images], output_names[output0], dynamic_axesNone )第二次要注意的是opset_version。CANN对ONNX算子协议的支持是分版本的一般建议用opset 11太高的opset反而会在ATC转换时报不支持的算子版本。这是一个很反直觉的经验GPU生态里习惯用最新的opset以支持更多高级算子昇腾这边为了兼容性和稳定性反而推荐保守的opset版本。第三个是模型的输出。YOLOv5s在640x640输入下输出是[1, 25200, 85]的张量。这个数字的含义值得展开一下25200是三个检测尺度加起来的总预测框数——80x80特征图对应6400个位置40x40对应1600个位置20x20对应400个位置每个尺度有3个anchor所以(64001600400)×325200。每个预测框包含85个值4个边界框坐标中心点x、中心点y、宽度、高度、1个目标置信度、80个类别得分COCO数据集80类。理解这个shape的含义对后面写后处理代码至关重要。3.2 ATC转换参数逐项说明拿到ONNX文件后用ATC工具转换成OM模型。这个命令是整套流程的核心我贴一条我实际跑通的命令并逐项解释atc \ --modelyolov5s.onnx \ --framework5 \ --outputyolov5s_bs1 \ --input_shapeimages:1,3,640,640 \ --input_formatNCHW \ --soc_versionAscend310P3 \ --insert_op_confaipp.cfg \ --output_typeFP32逐个参数说明--model输入ONNX文件路径。--framework5固定表示输入是ONNX格式这是ATC的协议编号。--output输出OM文件的前缀生成完成后会得到yolov5s_bs1.om。--input_shape指定模型输入节点的shape格式是节点名:维度。这里images必须和导出ONNX时指定的input_names一致维度也和dummy input对齐。--input_formatNCHW输入数据的排布格式。PyTorch模型默认是NCHW如果你的数据管道用的是NHWC比如某些TensorFlow习惯这里必须对应修改。--soc_versionAscend310P3指定目标芯片类型。这个必须和你实际用的卡匹配填写错误会导致模型转换成功但加载到NPU上报错。--insert_op_confaipp.cfg插入AIPP预处理配置后面单独讲。--output_typeFP32指定网络输出数据类型YOLOv5的后处理在CPU端做保留FP32精度最省事。转换成功后终端会显示ATC run success同时生成.om文件。如果报错优先检查报错信息里的第一个算子名称那个算子就是CANN不支持的算子需要回到模型导出阶段做算子替换或升级CANN版本。3.3 AIPP配置让预处理也跑到NPU上在GPU推理流程里图像的预处理resize、归一化、通道变换通常在CPU上做用OpenCV或Pillow把图片处理好再拷到GPU显存里。昇腾的AIPPArtificial Intelligence Pre-Processing把这个逻辑搬进了NPU侧模型加载后第一次输入数据可以直接是原始图像字节预处理由NPU硬件完成。我用的aipp.cfg配置长这样aipp_op { aipp_mode: static input_format: RGB888_U8 src_image_size_w: 640 src_image_size_h: 640 csc_switch: false rbuv_swap_switch: false min_chn_0: 0 min_chn_1: 0 min_chn_2: 0 var_reci_chn_0: 0.003921568627451 var_reci_chn_1: 0.003921568627451 var_reci_chn_2: 0.003921568627451 }几个关键配置项的含义aipp_mode: static表示静态AIPP预处理参数在模型转换时固定运行时不可修改适合固定shape场景。input_format: RGB888_U8输入图像是RGB三通道的8位无符号整型。这里有个很常见的坑YOLOv5在PyTorch里训练时用的其实是RGB图像但OpenCV读出来是BGR如果不在AIPP里做通道顺序调整最后推理出来的检测结果会错得莫名其妙。我这个配置默认输入RGB所以代码里读图后不需要做cv2.cvtColor(img, cv2.COLOR_BGR2RGB)但如果你习惯按BGR喂数据就得把input_format改成BGR888_U8并打开rbuv_swap_switch。min_chn_0/1/2和var_reci_chn_0/1/2对应归一化的均值和方差。YOLOv5的归一化是像素值除以255所以最小值是0var_reci是1/255≈0.0039。这里配置好后host端就真的不需要做任何归一化操作了。AIPP开启后有个重要变化输入模型的数据变成了原始图像的BGR888或RGB888格式不再是预处理好后的NCHW浮点张量。在做数据拷贝时输入内存大小要按照图片宽高乘通道数来申请而不是按1×3×640×640×4的浮点大小申请。这个区别后面写代码时非常容易栽跟头。4. 用pyACL把OM模型跑起来4.1 推理主流程的骨架代码模型转换完成后真正的推理工程就要动手了。昇腾的官方推理接口是AscendCL简称ACL提供了C语言和Python两个版本。C语言性能最好但对内存管理要求高Python版本pyACL封装好一些适合快速开发和验证。我用的是pyACL整体推理主流程分为初始化→加载模型→准备输入输出内存→执行推理→获取结果→释放资源。先看初始化这段import acl import numpy as np import cv2 # 初始化ACL ret acl.init() acl.rt.set_device(0) # 使用0号设备 # 创建contextACL的context概念类似CUDA的context context, ret acl.rt.create_context(0) # 加载OM模型返回模型ID model_id, ret acl.mdl.load_from_file(yolov5s_bs1.om)如果acl.init()、set_device、create_context任一环节返回错误码非0就不可能继续往下走。acl.mdl.load_from_file是真正把OM模型加载到NPU上这一步如果有问题大概率是前面ATC转换时soc_version填错或者是驱动和运行时版本不配套。4.2 输入数据的H2D拷贝和输出数据的D2H拷贝模型加载完接下来要把图像数据送进NPU计算完成后把结果拿回来。这里要理解两个内存空间的概念hostCPU侧内存和deviceNPU侧内存。pyACL中有一组acl.util.numpy_to_ptr和acl.util.ptr_to_numpy工具专门用于在numpy数组和ACL内存指针之间转换比手动申请内存更简洁。先获取模型输入输出描述信息# 获取模型描述 model_desc acl.mdl.create_desc() ret acl.mdl.get_desc(model_desc, model_id) # 获取输入个数和输入数据大小 input_num acl.mdl.get_num_inputs(model_desc) input_data_size acl.mdl.get_input_size_by_index(model_desc, 0)这里get_input_size_by_index返回的大小非常关键。开了AIPP后这个大小对着的是原始图片数据字节数比如640x640x3而不是浮点tensor的大小。没开AIPP时对着的是1×3×640×640×4个字节。搞反的话要么内存申请不够要么数据没对齐推理结果一塌糊涂。准备图像和输入内存# 读取图像并resize到640x640注意此时不用归一化、不用转RGB img cv2.imread(test.jpg) img cv2.resize(img, (640, 640)) # 把numpy数组转换成device内存 img_ndarray np.ascontiguousarray(img) img_ptr acl.util.numpy_to_ptr(img_ndarray) # 申请device内存并拷贝 input_mem, ret acl.rt.malloc(input_data_size, 2 * 1024 * 1024) # 第二个参数是内存对齐 ret acl.rt.memcpy(input_mem, input_data_size, img_ptr, input_data_size, acl.ACL_MEMCPY_DEVICE_TO_DEVICE)注意这里用了ACL_MEMCPY_DEVICE_TO_DEVICE因为acl.util.numpy_to_ptr返回的指针实际上已经处于设备可访问的空间在pyACL中做了特殊封装所以直接D2D拷贝进去是可以的。如果你是自己用acl.rt.malloc_host申请内存再填充numpy数据那拷贝方向就要用ACL_MEMCPY_HOST_TO_DEVICE。执行推理# 申请输出内存 output_data_size acl.mdl.get_output_size_by_index(model_desc, 0) output_mem, ret acl.rt.malloc(output_data_size, 2 * 1024 * 1024) # 执行模型推理 ret acl.mdl.execute(model_id, [input_mem], [output_data_size], output_mem)acl.mdl.execute是同步执行的模型跑完才返回。执行成功后需要把NPU侧的输出数据拷回host侧才能用numpy解析# 申请host侧内存并拷贝回来 host_output np.zeros(output_data_size, dtypenp.uint8) host_output_ptr acl.util.numpy_to_ptr(host_output) ret acl.rt.memcpy(host_output_ptr, output_data_size, output_mem, output_data_size, acl.ACL_MEMCPY_DEVICE_TO_HOST) # 重新解释成浮点张量 output_np np.frombuffer(host_output.tobytes(), dtypenp.float32).reshape(1, 25200, 85)4.3 后处理NMS和坐标还原YOLOv5的模型输出是原始的预测张量要得到最终检测结果还要经过置信度筛选、NMS非极大值抑制、坐标格式转换这几步。这些操作在CPU端做就够了25200个框的后处理在CPU上耗时也就几毫秒放在NPU上反而麻烦。后处理核心逻辑def post_process(output_np, conf_threshold0.25, iou_threshold0.45, img_shape(640, 640)): # output_np shape: [1, 25200, 85] pred output_np[0] # (25200, 85) # 计算每个框的置信度objectness * class_score scores pred[:, 4] * pred[:, 5:].max(axis1) valid scores conf_threshold boxes pred[valid] scores scores[valid] class_ids pred[valid][:, 5:].argmax(axis1) # 转换坐标中心点宽高 - 左上角宽高 x_center, y_center, w, h boxes[:, 0], boxes[:, 1], boxes[:, 2], boxes[:, 3] x1 x_center - w / 2 y1 y_center - h / 2 x2 x_center w / 2 y2 y_center h / 2 # 按类别分别做NMS final_boxes [] for cls in np.unique(class_ids): cls_mask class_ids cls cls_boxes np.stack([x1[cls_mask], y1[cls_mask], x2[cls_mask], y2[cls_mask]], axis1) cls_scores scores[cls_mask] # 调用NMS函数保留的索引 keep nms(cls_boxes, cls_scores, iou_threshold) for idx in keep: final_boxes.append((cls_boxes[idx], cls_scores[idx], cls)) return final_boxesNMS函数可以用PyTorch自带的torchvision.ops.nms也可以自己实现一个简易版本def nms(boxes, scores, iou_threshold): x1, y1, x2, y2 boxes[:, 0], boxes[:, 1], boxes[:, 2], boxes[:, 3] areas (x2 - x1) * (y2 - y1) order scores.argsort()[::-1] keep [] while order.size 0: i order[0] keep.append(i) if order.size 1: break 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:]]) inter np.maximum(0.0, xx2 - xx1) * np.maximum(0.0, yy2 - yy1) iou inter / (areas[i] areas[order[1:]] - inter) inds np.where(iou iou_threshold)[0] order order[inds 1] return keep坐标还原这一步容易忽略。推理输入时我们把原图直接resize到了640x640所以输出坐标是基于640x640这个坐标系的。如果要画回原图或者计算真实位置必须按缩放比例还原scale_x original_width / 640 scale_y original_height / 640 final_x1 int(x1 * scale_x) final_y1 int(y1 * scale_y) final_x2 int(x2 * scale_x) final_y2 int(y2 * scale_y)如果你的预处理不是直接拉伸而是保持宽高比的letterbox方式那还原逻辑就更复杂一些需要把letterbox的padding信息也考虑进去。这里有个经验能直接用拉伸resize就尽量直接拉伸虽然会有轻微形变但YOLO系列模型对这个不敏感反而省去大量坐标换算的工作。5. 实测性能、调优方向以及我踩过的坑5.1 单卡推理延迟和吞吐数据整套流程跑通后我对模型做了性能实测。在Atlas 300V 24G上用YOLOv5s640x640输入FP32精度单张图片推理纯NPU推理时间稳定在6-9毫秒开了AIPP后图像resize和归一化都算在NPU侧如果加上host端读取图片、后处理NMS这些开销端到端单张图片处理时间大概在12-18毫秒。这意味着单卡单batch模式能支撑每秒55-80帧的处理能力完全满足常规视频流25帧/秒的实时分析需求。把batch从1调到4后效果更明显。batch4时4张图的总推理时间大约在15-20毫秒均摊到单张图上只有4-5毫秒整体吞吐翻了一倍以上。这说明Atlas 300V 24G这种推理卡更适合通过分批方式来压榨性能——单图延迟不是它的强项多图并发才是正解。需要说明的是这个数据是在我自己的服务器环境具体CPU型号对结果影响很大下测得的不同机器、不同驱动版本、不同CANN版本都会有浮动但量级是可信的。如果你的延时比我差很多优先排查两个方向模型是否转成了OM并且启用了AIPP以及推理代码里是否有额外的内存申请释放操作拖慢了主循环。5.2 三个最值得做的性能优化第一个优化是启用多batch推理。结合上文的实测数据batch4时吞吐提升很明显。实现方法也不复杂加载模型时用--input_shapeimages:4,3,640,640转换一个bs4的OM然后把多张图堆叠成一个numpy数组再送入NPU。前提是业务场景允许凑batch——比如视频分析场景下可以把同一路视频的多帧或者多路视频的帧攒到一起推理。第二个优化是减少host端的图像预处理开销。AIPP开启后resize和归一化都由NPU完成但图像读取和resize仍然占用了host CPU时间。实测中我用OpenCV的cv2.imreadcv2.resize处理一张1080P图片耗时大约3-5毫秒这个时间已经接近NPU推理时间了。如果追求极致性能可以改用imdecode直接解码到灰度或BGR、用更轻量的插值算法、或者维护一个图像缓存池。在批量视频流场景下这部分优化比扣NPU算子细节更见效。第三个优化是输出内存的复用。我第一次写的推理循环里每张图都acl.rt.malloc申请输出内存、处理完再释放结果性能很差。后来改成在循环外申请好内存每次推理后只做acl.rt.memcpy覆盖数据开销显著下降。内存申请释放是系统级调用本身就有不小的固定成本在每帧推理这种高频操作里这个代价是不能忽视的。5.3 常见报错速查表最后把我这次实践中遇到的典型报错整理成一张速查表。这几个错误基本覆盖了昇腾部署YOLO最常见的故障点报错信息根因解决办法ERROR(1000): Failed to load modelOM模型与当前设备soc类型不匹配或驱动/固件/CANN版本不配套用npu-smi info确认芯片类型重新执行ATC转换并核对soc_version检查三件套版本配套表acl.rt.memcpy failed, error code 507001内存越界通常是输入或输出内存申请大小不匹配检查get_input_size_by_index和get_output_size_by_index的返回值确认是否与数据实际大小一致ATC转换时报Unsupported opONNX模型中包含CANN不支持的算子降低ONNX opset版本或检查该算子是否来自不常用的PyTorch模块考虑替换实现推理输出全为0或结果明显错误AIPP通道顺序配置错误BGR/RGB颠倒确认input_format训练时输入图片通道顺序一致必要时打开rbuv_swap_switchrun acl.mdl.execute failed, error code 504输入数据和模型期望的shape不一致检查单帧输入是否严格等于1×3×640×640以及是否开了AIPP导致输入格式变成HWC这些报错在官方文档里都有排查指导但文档分散在多个页面翻起来非常费时。实践中遇到问题先按这张表对照一遍大部分问题能快速定位到根因省去了大量翻文档的时间。我实际用下来的体会是Atlas 300V 24G作为推理加速卡是称职的YOLO整套流程在昇腾生态里已经完全走通了性能也符合推理场景的预期。它和GPU的使用习惯差异很大但只要理解OM模型转换和AscendCL这两条主线上手并不困难。后续如果要做进一步优化可以考虑把模型量化到INT8——310P的INT8算力比FP32提升不少代价是少部分精度损失对工业场景的检测任务来说通常可接受。有精度调优需求的话可以先跑通AOE工具做算子调优再看量化带来的收益。
返回列表