ARTICLE DETAIL

资讯详情

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

昇腾Atlas 300V部署YOLOv5实战:从驱动到推理全流程

昇腾Atlas 300V部署YOLOv5实战:从驱动到推理全流程 做AI推理部署的人最近应该躲不开Atlas这个词。我上个月刚把一套YOLOv5检测服务从GPU环境切到Atlas 300V 24G上从驱动到推理代码折腾了三个工作日。先说结论Atlas 300V 24G确实是运算加速卡但它不是普通显卡没有显示输出专门为AI推理打造Atlas部署YOLO完全可行只是和CUDA的生态玩法不太一样。这篇文章是一份踩坑记录不是官方文档。你手上有Atlas 300V、300I这类昇腾推理卡或者正在评估要不要换国产推理硬件可以参考我的路径。Atlas 300V 24G这个名字我第一次接触时也有点迷惑它和游戏显卡一样插在PCIe槽里但名字里带“V”。实际上它是昇腾310P系列里的一张AI推理加速卡板载24GB存储支持FP16、INT8这类推理常用的低精度计算但没有显示接口所以不能接显示器。很多人问“Atlas 300V 24G是运算加速卡吗”答案非常明确是而且定位就是纯运算加速卡不是图形卡。我选它做YOLO部署主要是看中它单位功耗算力比较高、通过PCIe插槽供电、机架式服务器里可以插多张特别适合视频流目标检测这类需要长时间稳定跑推理的场景。下面我把整个“Atlas部署YOLO”的过程拆开讲从硬件识别、环境搭建、ONNX转OM、AscendCL推理到问题排查每一个环节都尽量给到可以直接复现的命令和代码。1. 项目背景与硬件认知1.1 为什么选择Atlas 300V 24G而不是普通GPU做目标检测服务第一反应肯定是NVIDIA GPU但实际项目里会遇到几个问题机柜功率预算有限GPU满载功耗高一个4U服务器塞四张卡就要考虑散热和供电采购周期和供货渠道也不一定跟得上。Atlas 300V 24G的优势就体现出来了它的设计目标不是游戏渲染也不是大模型训练而是数据中心里的视频分析、图像分类、目标检测这类推理任务。用一句话描述Atlas 300V 24G是给“已经训练好的模型”提供批量计算的加速卡。训练还是可以继续用GPU集群但线上推理服务可以放到昇腾卡上。这块卡24GB的内存对YOLO这类模型来说非常富裕YOLOv5s的权重文件只有十几MB跑起来也就占用几百MB到一两GB剩下的空间还能同时加载多路视频流、做批量推理。对比GTX/RTX系列Atlas 300V 24G没有CUDA核心也没有Tensor Core而是集成了昇腾AI Core。它的软件栈不叫CUDA叫CANNCompute Architecture for Neural Networks。这也是最需要提前适应的点不能直接把PyTorch模型丢上去跑需要经过ONNX转OM这一步。熟悉TensorRT的人可以把这个过程类比为“生成engine”只是工具链和算子库不同。1.2 昇腾部署YOLO的整体链路我的部署链路是PyTorch权重(.pt) - ONNX(.onnx) - OM(.om) - AscendCL推理为什么要多一道ONNX因为PyTorch模型上NPU执行昇腾没法直接识别C的模型结构需要一个中间表示。ONNX相当于通用翻译稿ATC编译器把ONNX翻译成昇腾NPU的“母语”OM。OM一旦生成就是静态图输入输出的shape基本固定换分辨率要重新转一遍。这里要强调一个观念OM不是模拟器也不是解释执行它是经过算子调度的离线模型加载后直接由NPU硬件调度执行。所以部署时不要想着“我先把模型跑起来再说”要先确认输入尺寸、batch大小、精度格式、后处理方式一次性转对否则后面会反复返工。YOLO的后处理NMS、阈值过滤、类别筛选我建议放在CPU上做。原因很简单NMS这类动态逻辑在NPU上实现麻烦而且YOLO输出的候选框数量不大CPU做NMS的耗时通常在1ms以内不会成为瓶颈。把后处理剥离出去模型转换过程会简单很多。2. 环境准备与软件栈搭建2.1 硬件安装与驱动固件检查Atlas 300V 24G的物理安装不复杂关机、断电、插到PCIe x16槽上。注意优先插在离CPU近的槽位减少跨NUMA节点的拷贝延迟。我这个服务器是双路x86卡插在第二颗CPU直连的PCIe槽上通过lspci能看到设备。开机后先确认系统识别到了NPU设备lspci | grep -i ascend正常会输出一行类似Accelerator: Huawei Technologies Co., Ltd. ...的信息。如果这里没有说明硬件未识别先别急着装软件检查卡是否插到位、PCIe供电是否正常。接下来安装NPU驱动和固件。CANN版本的编号和底层驱动版本有对应关系我用的组合是驱动版本22.0.4 CANN 6.3.RC2。不同版本命令差异不大但最好用官方兼容列表里的组合避免驱动和固件不匹配导致npu-smi报错。驱动安装包通常是.run文件执行chmod x Ascend-hdk-310P-npu-driver_22.0.4_linux-x86_64.run ./Ascend-hdk-310P-npu-driver_22.0.4_linux-x86_64.run --full安装完成后重启然后运行npu-smi info如果能看到卡的型号、驱动版本、固件版本、显存总量说明驱动层已经通了。这时候再确认一下FAQ里经常提到的“运算加速卡定义”它确实没有显示输出不装驱动时系统会把它的PCIe设备识别成未知设备装上昇腾驱动后才会识别为AI加速器。2.2 CANN Toolkit安装与环境变量配置CANN是昇腾的软件栈类比CUDA。它提供了ATC模型转换工具、AscendCL推理API、各种调优工具。安装的是Ascend-cann-toolkit与驱动版本要匹配。下载后执行chmod x Ascend-cann-toolkit_6.3.RC2_linux-x86_64.run ./Ascend-cann-toolkit_6.3.RC2_linux-x86_64.run --install默认安装在/usr/local/Ascend/ascend-toolkit下。安装完成需要source环境变量source /usr/local/Ascend/ascend-toolkit/set_env.sh建议把这行写进~/.bashrc否则每次新开终端都要手动source。环境变量里至少包含以下几类路径ASCEND_TOOLKIT_HOMECANN的根目录PATH把atc等工具加进来LD_LIBRARY_PATH运行时需要的so库PYTHONPATHpyACL的Python模块路径验证工具是否可用atc --version2.3 Python推理依赖准备我用的是Python 3.8虚拟环境。除了PyTorch、torchvision只用来导出ONNX和模拟验证还需要安装pip install opencv-python numpy onnx onnxsim注意pyACL模块不一定在Python默认搜索路径里如果import acl失败检查LD_LIBRARY_PATH和PYTHONPATH是否正确。可以在Python里试一试import acl print(acl.__version__)能打印版本号说明环境OK。这个acl模块就是推理代码要用的核心库后面所有NPU调用都通过它完成。3. 模型转换从YOLO权重到OM离线模型3.1 导出ONNX的关键细节我用的模型是YOLOv5s训练好的权重是.pt文件。先用官方export.py导出ONNXpython export.py --weights yolov5s.pt --include onnx --opset 11 --simplify --img 640这里有几个关键点--img 640固定输入尺寸为640x640不要用动态shape。ATC对动态shape支持有限固定尺寸转换成功率最高。--opset 11ONNX算子集版本不要太高。我实际用opset 13转换时报过Upsample算子不支持降到11就正常了。--simplify对ONNX做简化会去掉一些冗余节点减少后续ATC报错概率。在导出配置里关闭NMS不要让模型自带NMS后处理。ONNX里的NMS算子很难转CPU端做NMS性能并不差。导出后可以用onnx.checker验证import onnx model onnx.load(yolov5s.onnx) onnx.checker.check_model(model) print(onnx.helper.printable_graph(model.graph))用Netron打开ONNX看输入输出YOLOv5s通常是输入images: 1x3x640x640输出是1x25200x85。25200代表三个尺度特征图上的anchor数量总和85是xywh confidence 80类的维度。这个shape值后面写后处理代码要用最好记下来。3.2 ATC转换命令与参数选择ATC工具的作用是把ONNX翻译成OM。我的转换命令是source /usr/local/Ascend/ascend-toolkit/set_env.sh atc --modelyolov5s.onnx \ --framework5 \ --outputyolov5s_bs1 \ --input_shapeimages:1,3,640,640 \ --soc_versionAscend310P3 \ --logerror参数解释--framework55表示ONNX1是MindSpore2是Caffe别搞混。--input_shape一定要和ONNX导出时一致1表示batch size。如果要用batch4这里改成images:4,3,640,640同时ONNX导出的batch也要是4。--soc_version芯片型号。Atlas 300V 24G用的是昇腾310P系列我这边填的Ascend310P3实际以驱动和卡型号为准。不确定时用npu-smi info看型号或者在Atlas产品文档里查对应soc_version。--output_typeFP16如果需要混合精度推理可以加上。YOLOv5的权重是FP32转FP16后精度损失很小但推理吞吐提升明显。我先用FP32跑通再切FP16优化。转换成功后目录下会出现yolov5s_bs1.om文件。失败时常见报错E10001: Failed to parse the modelONNX模型解析失败检查opset版本、模型是否被损坏。E10008: Unsupported op某个算子不支持优先尝试--opset11再把ONNX简化一遍。E10018: The soc version is not supported--soc_version填错了查清楚卡的实际型号再填。3.3 转换后的模型验证OM文件生成后先不要急着写整个推理程序可以用官方工具msame做一次纯模型推理验证看输出是否合理。msame能以二进制文件或目录作为输入单卡测试很方便msame --model yolov5s_bs1.om \ --input ./input_bin \ --output ./outputinput_bin里放一张或多张预处理好的二进制图像shape要和模型输入一致。如果输出目录里有结果说明模型转换成功硬件能正常执行。如果手头没有msame也可以直接loading到pyACL里构造随机输入看是否报错。随机输入的任何输出都能证明“模型能跑”但不能证明“结果正确”只有用真实图片验证后处理逻辑。4. 基于AscendCL的推理代码实现4.1 初始化与设备上下文管理我用的是pyACL也就是Python版本的AscendCL。整个推理流程可以拆成初始化、设置设备、创建上下文、加载模型、申请内存、拷贝输入、执行推理、拷贝输出、后处理。先看初始化部分import acl # 每个进程只初始化一次 ret acl.init() if ret ! 0: raise RuntimeError(facl.init failed, ret{ret}) # 指定device id device_id 0 ret acl.rt.set_device(device_id) if ret ! 0: raise RuntimeError(fset_device failed, ret{ret}) # 创建context context, ret acl.rt.create_context(device_id)这段代码里最容易踩的坑是“上下文”。昇腾的上下文是线程绑定的不能在主线程创建后直接让子线程用。我做多线程推理时每个线程都重新初始化一遍自己的context避免串上下文导致内存访问异常。4.2 模型加载与内存管理加载OM模型并获取输入输出尺寸model_id, ret acl.mdl.load_from_file(yolov5s_bs1.om) 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) # 申请device侧内存 input_buffer, ret acl.rt.malloc(input_size, 2) # 2表示普通内存 output_buffer, ret acl.rt.malloc(output_size, 2)你一定发现了这里的input_buffer是一个整数地址不是Python对象。数据不能直接塞进去需要用acl.rt.memcpy从host内存拷到device内存。这个拷贝步骤非常重要漏掉它会导致推理结果全零。释放时要成对出现acl.rt.free(input_buffer) acl.rt.free(output_buffer) acl.mdl.unload(model_id) acl.rt.destroy_context(context) acl.rt.reset_device(device_id) acl.finalize()4.3 图像预处理与推理循环YOLOv5的预处理和官方代码保持一致letterbox缩放、BGR转RGB、归一化、NCHW排布。import cv2 import numpy as np def preprocess(img, input_size640): # letterbox保持宽高比并填充灰边 h, w img.shape[:2] ratio min(input_size / h, input_size / w) new_h, new_w int(round(h * ratio)), int(round(w * ratio)) resized cv2.resize(img, (new_w, new_h), interpolationcv2.INTER_LINEAR) canvas np.full((input_size, input_size, 3), 114, dtypenp.uint8) dx, dy (input_size - new_w) // 2, (input_size - new_h) // 2 canvas[dy:dynew_h, dx:dxnew_w] resized # BGR - RGB, HWC - CHW, 归一化 rgb cv2.cvtColor(canvas, cv2.COLOR_BGR2RGB).astype(np.float32) / 255.0 chw np.transpose(rgb, (2, 0, 1)) input_np np.ascontiguousarray(chw[np.newaxis, ...]).astype(np.float32) return input_np, ratio, dx, dy推理时把输入数据拷贝到device执行再拷贝回hostinput_np, ratio, dx, dy preprocess(image) ret acl.rt.memcpy( input_buffer, input_size, input_np.ctypes.data, input_np.nbytes, 1 # 1表示HOST_TO_DEVICE ) ret acl.mdl.execute(model_id, input_buffer, output_buffer) output_np np.zeros(output_size, dtypenp.uint8) ret acl.rt.memcpy( output_np.ctypes.data, output_size, output_buffer, output_size, 2 # 2表示DEVICE_TO_HOST )这里有个细节acl.mdl.execute是同步接口执行完就代表NPU算子已经跑完可以立刻读取输出。不用额外同步。4.4 后处理NMS与画框从输出buffer里按模型输出shape读取数据output_np output_np[:1 * 25200 * 85].reshape(1, 25200, 85)YOLOv5的输出排布是xywh confidence class_scores不是直接xyxy。我的后处理步骤选置信度大于0.5的框把xywh转成xyxy按类别做NMSIoU阈值0.45把框坐标映射回原图因为前面letterbox过。NMS可以写一个简化版def nms(boxes, scores, iou_thr0.45): order scores.argsort()[::-1] keep [] while order.size 0: i order[0] keep.append(i) if order.size 1: break xx1 np.maximum(boxes[i, 0], boxes[order[1:], 0]) yy1 np.maximum(boxes[i, 1], boxes[order[1:], 1]) xx2 np.minimum(boxes[i, 2], boxes[order[1:], 2]) yy2 np.minimum(boxes[i, 3], boxes[order[1:], 3]) w np.maximum(0.0, xx2 - xx1) h np.maximum(0.0, yy2 - yy1) inter w * h iou inter / (boxes[i, 2]-boxes[i, 0])*(boxes[i, 3]-boxes[i, 1]) ... # 实际写完整 ...后处理全部用numpy实现耗时可控。如果处理视频流建议把后处理放到独立线程避免阻塞下一帧推理。推理循环可以加一个耗时统计import time t0 time.time() for i in range(100): ret acl.mdl.execute(model_id, input_buffer, output_buffer) t1 time.time() print(favg: {(t1 - t0) / 100 * 1000:.2f} ms)这样能看到纯NPU推理开销和npu-smi info里的利用率对照着看。5. 常见问题与排查技巧实录5.1 板卡不识别npu-smi info无法使用现象npu-smi info执行报错比如No NPU device or no authority。排查顺序lspci | grep -i ascend看PCIe设备是否出现。确认驱动安装完成且已重启。确认当前用户是否有权限访问NPU设备文件通常需要root用户或加入HwHiAiUser用户组。检查内核模块是否加载lsmod | grep ascend如果PCIe设备出现了但驱动加载失败大概率是系统内核和驱动版本不兼容。换用官方兼容列表里的内核版本即可不要硬凑。5.2 ATC转换失败算子不支持遇到Unsupported op是最常见的。我遇到的场景是YOLOv5导出ONNX后包含了一些高版本算子比如Upsample、CumSum。处理办法降低opset--opset 11重新导出。用onnxsim再一次简化python -m onnxsim yolov5s.onnx yolov5s_sim.onnx把不需要的算子拆出去。比如后处理里的Sigmoid可以留在模型里但涉及动态shape的Resize尽量不要用。实在不行的算子可以用CPU算子替代。ATC本身也支持算子融合和替换但没必要为了一个算子纠结太久改一行导出配置可能比折腾算子节省时间。5.3 推理结果全零或完全乱框最典型的原因是输入数据没正确拷贝到device内存或者把device地址和host地址搞混了。建议在拷贝后把device侧数据再拷回来看看是否一致check_np np.zeros(input_np.nbytes, dtypenp.uint8) acl.rt.memcpy(check_np.ctypes.data, input_np.nbytes, input_buffer, input_np.nbytes, 2) print(np.array_equal(check_np, input_np.tobytes()))如果一致再检查归一化参数。YOLOv5训练时用的是0-1归一化如果你用了0-255或者少了BGR转RGB框的位置会乱但不会全零。全零往往是输入数据为零或者模型还没加载成功。5.4 内存申请失败或运行一段时间后崩了Atlas 300V 24G虽然有24GB显存但如果加载多个模型、多路视频流同时跑设备内存会被占满。acl.rt.malloc返回失败时先看npu-smi info里的HBM使用率再用acl.rt.mem_free及时释放不再使用的buffer。另外不要频繁load_from_file同一个模型一张卡能加载的模型数量有限。正确做法是启动时加载一次后续所有推理复用同一个model_id。我还遇到过一个问题在线程里使用主线程创建的context导致acl.mdl.execute偶发崩溃。排查了一整天最后改成每个线程独立初始化context才稳定。所以用多线程推理时一定要把“线程-Context-模型”的关系理清楚。6. 实操心得与后续优化6.1 第一次在Atlas上部署YOLO我建议的路径如果让我重新做一遍我会这样安排顺序先跑通Atlas官方CANN sample里的YOLOv3示例确认环境没问题再用自己的YOLOv5权重转ONNX、转OM最后才写后处理和业务代码。不要一上来就想着“一步到位把视频流并发日志全接好”那样出了问题根本不知道是硬件问题还是代码问题。还有一个小技巧先用随机输入把模型跑通再用真实图片做端到端对比。随机输入能排除图片预处理导致的干扰如果随机输入输出正常真实图片结果不对那问题集中在预处理和后处理如果随机输入都不正常问题在模型转换或内存管理。6.2 可以继续扩展的优化方向Atlas 300V 24G部署YOLO跑通只是第一步后续提升空间还很大Batch推理把多帧图片拼成一个batch提高NPU利用率。batch4或8时吞吐提升明显但要注意模型转换时的--input_shape要相应设置。异步推理acl.mdl.execute_async配合stream可以在CPU做后处理的同时让NPU执行下一帧流水线式推流。AIPP硬件预处理把resize、归一化、通道转换放到AIPP里减少CPU内存拷贝和计算但配置复杂一些建议先跑通再引入。模型量化YOLOv5转INT8后精度会掉一点但推理速度会提升不少。如果是检测小目标建议保留FP16别为了速度牺牲太多召回率。容器化部署昇腾提供了CANN镜像把环境打成镜像能解决换机器重新安装的麻烦。最后再分享一个我在实际部署中体会最深的事不要在转换模型这一步只追求“成功”而忽略“结果验证”。我第一次转好的OM在随机输入下跑得很正常但换上真实监控画面后小目标几乎全丢原因不是NPU问题而是YOLOv5导出ONNX时把输入预处理里的某些细节丢了。模型转换成功只是起点端到端的框和置信度都对才算真正部署完成。
返回列表