第一次拿到 Atlas 300V 24G 这张卡的时候,我第一反应也是先打开搜索框,输入“atlas 300v 24g 是运算加速卡吗”。这个问题看起来简单,但如果你沿着这个关键词翻下去,会发现网上说法五花八门,有人说它是“加速卡”,有人说它是“推理卡”,还有人直接拿它跟 N 卡对比显存。今天这篇东西,我就把这个问题彻底讲透,再把大家搜得最多的“atlas 部署 YOLO”这条实操链路完整走一遍。
这篇文章适合谁看?两类人。一类是刚接触昇腾生态、手里正好有一块 300V 24G 推理卡,想搞清楚它到底能干嘛、不能干嘛的开发者;另一类是已经跑通了基础环境,正卡在模型转换、推理代码或者性能调优上,想找一些能直接抄作业的排错经验的人。我把从硬件认知、环境安装、模型转换、推理代码,到性能优化和常见报错的完整过程都整理在下面了,内容偏工程向,没有什么玄学,基本都能落地。
1. 先搞明白:Atlas 300V 24G 到底是一张什么卡
1.1 它究竟是“运算加速卡”还是“推理加速卡”
先说结论:Atlas 300V 24G 是一张 AI 推理加速卡,不是通用运算加速卡。
这两个概念在工程上差别非常大。通用运算加速卡,典型代表是 NVIDIA 的 A100、RTX 4090 这类 GPU,它们不仅能做神经网络训练和推理,还能跑 CUDA 通用计算、分子模拟、渲染、科学计算等乱七八糟的负载。而 Atlas 300V 24G 走的是另一条路线:它里面的核心是昇腾 NPU,专门为神经网络算子的执行做了深度定制,跑 CNN、Transformer 这类模型的推理任务效率很高,但你不能拿它像 GPU 那样去跑任意并行计算程序,算子生态也不支持你随便写个 CUDA 内核然后编译上去跑。
我习惯用一个类比来解释这件事:GPU 是一辆公交车,站点多、路线广,什么活都能拉一点;NPU 推理卡更像是专线班车,从 A 点到 B 点开得飞快,但你非让它去送快递、跑货运,那就不是它该干的活了。所以那张卡的定位从设计第一天起就很明确:目标检测、图像分类、语义分割、OCR、视频结构化这一类高频推理场景,才是它的主场。
1.2 硬件接口、内存与算力认知
Atlas 300V 24G 这个名字里已经透露了最关键信息——24G,指的是板载 24GB 的 HBM 设备内存。HBM 这种内存的特点就是带宽高,对神经网络推理这种需要频繁读写中间特征图的任务非常友好。
接口形态上,它是一张标准 PCIe 卡,通常插在服务器的 PCIe x16 插槽上,被动散热,需要服务器机箱内有风道给它吹风。也就是说,你往自己桌面上那台普通台式机里塞这张卡,大概率没法直接用——散热和供电都是问题。我见过有人把 300V 硬塞进无风道工控机里,跑了一个小时温度直接顶到红线,推理速度垮掉一半,所以环境准备阶段就要先确认散热条件。
关于算力,这类卡对标的是 INT8 推理场景。24G 版本在 INT8 精度下的总算力可以到百 TOPS 级别,但我不建议你只看这个数字,因为实际吞吐和模型结构、分辨率、batch size 强相关。同一张卡,跑 YOLOv5s 和跑一个 100M 参数的大模型,效果完全是两个世界。更靠谱的做法是拿自己的模型做一次基准测试,后面我会给出我实际测过的参考数据。
1.3 适用业务画像
从我接触过的项目来看,300V 24G 最常见的落地场景有这么几类:
- 视频结构化:多路摄像头视频流实时分析,比如行人、车辆、行为识别,24G 大显存可以同时跑多个模型实例。
- 工业视觉检测:产线上做缺陷检测、OCR 识别,推理时延要求高,模型通常不大,单张卡可以扛很高的并发。
- 智慧园区/安防:人脸比对、入侵检测、烟火识别这一类,常配合 EasyDarwin、MediaX 这类流媒体框架一起用。
- 多路边缘盒子场景:一个大盒子里面插多张 300V,做分布式推理,单路成本比 GPU 更有优势。
不适合的场景也要说清楚:训练大模型、跑科学计算、做渲染、跑数据库加速,这些都不适合用它。总有人问“24G 显存能不能训练 ChatGLM”,答案是能加载,但速度会让你怀疑人生,而且很多训练算子在 NPU 上根本没有实现或者效率极低,别硬来。
2. 部署前硬性准备:驱动、固件与 CANN 工具链
2.1 拿到卡之后的第一件事
Step 1 不是急着写代码,而是确认板卡是否被系统正确识别。插上卡、开机、进 Linux,执行:
npu-smi info如果输出里能看到类似“Atlas 300V”的板卡信息,且温度、电压、HBM 使用率都正常,说明硬件层面 OK。npu-smi 这个工具是随驱动一起安装的,这也是为什么我们要先装驱动。
安装顺序上有讲究,我踩过一次坑之后养成了固定习惯:先装固件、再装驱动,最后装 CANN toolkit。固件是芯片底层的微码,驱动是操作系统和固件之间的桥梁,CANN 是上层运行时和开发工具包。三者版本必须匹配,一旦不匹配,最典型的症状就是 npu-smi 能看见卡,但运行时一加载模型就报错,而且报错信息往往不是直接告诉你“版本不匹配”,而是抛一些莫名其妙的错误码,排查起来非常费时间。
实际安装命令大概是这样的(具体版本号建议直接去官方昇腾社区查配套表,不要照抄博客里的历史版本):
# 以 root 权限执行,先装固件,再装驱动 ./Ascend-hdk-310p-npu-firmware_<版本号>_linux-<arch>.run --full ./Ascend-hdk-310p-npu-driver_<版本号>_linux-<arch>.run --full # 再装 CANN 工具包 ./Ascend-cann-toolkit_<版本号>_linux-<arch>.run --install装完之后记得重启一次机器,让驱动真正加载生效。不重启也行,有时也能跑,但稳妥起见我每次都会重启,毕竟业务环境里省这五分钟不值得。
2.2 环境变量与版本匹配
CANN 装好后,需要 source 环境变量,路径一般在/usr/local/Ascend/ascend-toolkit/set_env.sh:
source /usr/local/Ascend/ascend-toolkit/set_env.sh这个脚本会帮你设置ASCEND_HOME_PATH、LD_LIBRARY_PATH、PATH等关键变量。我建议把它写进/etc/profile或者你自己的 shell 配置里,否则每次新开会话都要手动 source 一遍,容易漏。
版本匹配这块,我要再啰嗦一句:昇腾整个体系里,驱动、固件、CANN、甚至是板卡的硬件版本,互相之间都有严格的配套关系。官方的“版本配套表”就是一个 Excel 表格,里面有每一对能正常工作组合的版本号。我自己的做法是先把表格下载下来,确认好一个组合,然后所有机器都用同一套版本,避免“这台机器能跑、那台机器报错”的环境漂移问题。
还有一个实用工具是ascend_install.info和npu-smi info输出里的固件版本号,可以用它反向核对当前环境。出问题时,第一件事就是要版本信息,别上来就翻日志。
3. YOLO 上卡:从 PyTorch 权重到 OM 离线模型的完整链路
3.1 模型转换总览
你从 GitHub 上下载的 YOLOv5 是 PyTorch 权重,.pt文件。NPU 不认这个格式,它认的是离线模型文件.om。所以整条链路是这样的:
PyTorch 权重 → ONNX 文件 → ATC 工具 → OM 文件 → AscendCL 加载推理
很多人第一次接触昇腾,就是在这里蒙掉的:为什么不能直接加载.pt?一方面是因为 NPU 的算子实现是私有的,PyTorch 导出的计算图里有大量自定义算子,必须经过编译映射到 NPU 的算子库上;另一方面,离线模型.om是经过后端编译和格式优化的,加载后可以跳过图解析阶段,启动推理更快,也方便做前后处理的融合。
所以,模型转换这一节是整个部署流程里最核心、也最容易出问题的环节,值得多花一点篇幅。
3.2 PyTorch 导出 ONNX 的正确姿势
我以 YOLOv5 为例。YOLOv5 官方仓库其实自带导出脚本:
python export.py --weights yolov5s.pt --include onnx --opset 13 --dynamic这里有两个关键点。
第一个是--dynamic。不用 dynamic 的话,导出的 ONNX 输入 shape 是固定的[1,3,640,640],一旦你想在推理时换成 batch 4,就得重新导出模型。开了 dynamic_axes 之后,batch 维度变成动态的,可以在 ATC 转换时指定具体 batch。不过要注意,动态维度不一定是越灵活越好,它会让转换后的 OM 在运行时做更多 shape 推导,性能有一定损失。我的习惯是:如果生产环境业务并发是固定的,就导出固定 batch 的模型,性能最稳;如果业务流量波动大,再用动态 batch 配合运行时多 stream 处理。
第二个点是 opset 版本。YOLOv5 官方脚本默认的 opset 不一定匹配你本地的 PyTorch 版本。AT C 转换时,对 ONNX 算子集的支持有限,opset 太新会导致 ATC 识别不了某个算子。建议统一用 opset 13,兼容性比较好。
导出完成后,先别急着转 OM,用 Netron 打开 ONNX 文件看一眼输入输出的名字和 shape。YOLOv5 的输出是三个检测头,分别输出[1,3,80,80,85]、[1,3,40,40,85]、[1,3,20,20,85]这样的 tensor(类别数不同会变)。记住输入节点名字,比如images,后面 ATC 要用。
3.3 ATC 工具转换 OM:参数详解
ATC 是昇腾官方的模型转换工具,装好 CANN 后就有。核心命令长这样:
atc --model=yolov5s.onnx \ --framework=5 \ --output=yolov5s_310p \ --soc_version=Ascend310P3 \ --input_shape="images:1,3,640,640" \ --precision_mode=allow_fp32_to_fp16 \ --insert_op_conf=aipp.cfg \ --output_type=FP32一个个参数说:
--framework=5,表示输入模型是 ONNX。--soc_version,这个值很关键。它写成Ascend310P3还是别的,取决于你的芯片具体型号。怎么确认?装好驱动后可以用npu-smi info查看芯片名,再对照 CANN 文档里支持的 soc_version 列表。我见过有人卡在这里,填了一个差不多的版本,ATC 也不报错,但生成的 OM 加载到板卡上就是起不来,最后发现是 soc 不匹配。解决方式是用官方工具get_soc_version或者直接查文档,别猜。--input_shape,指定输入动态轴的 shape。这里的images要和 ONNX 里的输入名完全一致。1,3,640,640对应 batch、通道、高、宽。--precision_mode=allow_fp32_to_fp16,意思是允许把模型里的 FP32 算子转成 FP16 计算,以换取更快的推理速度和更低的内存占用。一般来说 YOLO 这种模型整体转 FP16 精度损失很小,可以放心开。--insert_op_conf,是插入 AIPP 预处理配置文件。AIPP 是昇腾的硬件预处理单元,可以把图像缩放、减均值、除方差、RGB 与 BGR 转换、色域转换这些操作嵌入到模型里,推理时就不用在主机侧手动做这些操作了,既省开发量又省带宽。
一个典型的 AIPP 配置文件长这样(注意我这儿取的是 640 输入、RGB 顺序、0~255 输入直接转 0~1 的例子):
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: true min_chn_0: 0.0 min_chn_1: 0.0 min_chn_2: 0.0 var_reci_chn_0: 0.003921569 var_reci_chn_1: 0.003921569 var_reci_chn_2: 0.003921569 }rbuv_swap_switch: true表示把输出结果的 R 和 B 通道对调,适用的情况是模型训练时用的是 RGB 图,而摄像头/解码器给到的是 BGR 图。var_reci_chn是标准差的倒数,1 / 255 ≈ 0.003921569,配合min_chn为 0.0,完成pixel / 255的归一化。
这里最容易翻车的就是 AIPP 配置和模型输入不一致。有些模型自己内部已经做了归一化,你再通过 AIPP 做一次,数值就变成两次标准化了,推理结果自然是错的。我习惯的做法是:转换前先确认 ONNX 输入是由 U8 图像直接进模型,还是先被除以 255 再进模型。如果是后者,AIPP 里的归一化参数必须关掉或者改成 “不处理”。
转换结束后,会得到一个.om文件,这就是要部署的离线模型。可以用omg自带工具或者写个小脚本加载检查一下,确认模型结构没丢。
4. 推理代码开发:使用 AscendCL 加载 OM 并跑通
4.1 AscendCL 的核心概念:Device、Context、Stream、Model
模型转换只是第一步,真正写推理代码时会遇到 AscendCL 这套运行时 API。AscendCL 的编程模型有点像 CUDA Runtime,但抽象层级更高,核心就四个概念:
- Device:物理设备,就是那一张 300V 卡,用编号 0、1、2 区分。
- Context:上下文,承载设备上的资源状态,可以类比成进程或者线程的运行环境。一个线程在某一时刻只能绑定一个 Context。
- Stream:执行流,任务像流水线一样排在 stream 里执行。可以用多 stream 实现并行,比如预处理 stream 和推理 stream。
- Model:加载到设备上的 OM 模型。加载后会得到一个 model id,所有推理操作都基于这个 id。
代码层面,最基础的调用链是:aclInit→aclrtSetDevice→aclrtCreateContext→aclmdlLoadFromFile→aclmdlExecute→ 取结果 → 释放资源→aclFinalize。
4.2 最小可用的推理代码骨架
我直接给一份能跑通的主流程骨架(C++ 演示,Python 的pyacl思路完全一样)。注意这不是完整工程,但节点都标清楚了:
#include "acl/acl.h" #include <cstring> #include <iostream> int main() { // 1. 初始化 aclInit(nullptr); aclrtSetDevice(0); aclrtContext context; aclrtCreateContext(&context, 0); aclrtSetCurrentContext(context); // 2. 加载 OM 模型 uint32_t modelId = 0; aclmdlLoadFromFile("yolov5s_310p.om", &modelId); // 3. 获取模型描述,确定输入输出尺寸 aclmdlDesc *modelDesc = aclmdlCreateDesc(); aclmdlGetDesc(modelDesc, modelId); size_t inputSize = aclmdlGetInputSizeByIndex(modelDesc, 0); size_t outputSize = aclmdlGetOutputSizeByIndex(modelDesc, 0); // 输出个数依据模型而定,YOLOv5 通常有 3 个输出 // 如果输出有多个,需要逐个取大小,这里只演示单输出场景 void *inputBuf = nullptr; void *outputBuf = nullptr; aclrtMalloc(&inputBuf, inputSize, ACL_MEM_MALLOC_HUGE_FIRST); aclrtMalloc(&outputBuf, outputSize, ACL_MEM_MALLOC_HUGE_FIRST); // 4. 拷贝输入数据到设备 // 假设 hostInput 是一段已经做完 letterbox 的 640*640*3 的 RGB 数据 // aclrtMemcpy(inputBuf, inputSize, hostInput, inputSize, ACL_MEMCPY_HOST_TO_DEVICE); // 5. 构建输出 dataset aclmdlDataset *outputDataSet = aclmdlCreateDataset(); aclDataBuffer *outputBuffer = aclCreateDataBuffer(outputBuf, outputSize); aclmdlAddDatasetBuffer(outputDataSet, outputBuffer); // 6. 执行推理(同步接口) aclmdlExecute(modelId, nullptr, outputDataSet); // 7. 结果拷贝回 host,后处理 // aclrtMemcpy(hostOutput, outputSize, outputBuf, outputSize, ACL_MEMCPY_DEVICE_TO_HOST); // 8. 释放资源 aclDestroyDataBuffer(outputBuffer); aclmdlDestroyDataset(outputDataSet); aclrtFree(inputBuf); aclrtFree(outputBuf); aclmdlDestroyDesc(modelDesc); aclmdlUnload(modelId); aclrtDestroyContext(context); aclFinalize(); return 0; }实际工程里,aclmdlExecute后面跟的模型输入 dataset 也要按模型需求填,我把输入 dataset 的构建省略了,但逻辑和输出 dataset 一样,就是把 inputBuf 包成aclDataBuffer,再加到 input dataset 里。这个骨架跑通后,再往里面加多 batch、多线程会比较顺手。
4.3 结果排序、阈值过滤与坐标还原
拿到推理输出只是刚开始,后面还有常规的 YOLO 后处理:解码、置信度过滤、NMS、坐标还原。
一个必须强调的问题:ONNX 的三个输出是相互独立的 tensor,你需要在代码里把它们各自解析出来后,再合并成一个检测结果列表,然后做 NMS。很多人在这一步栽跟头,是因为没搞清输出的排列顺序。YOLOv5 的三个输出分别来自不同尺度的特征图,数据排布是[batch, 3, grid_h, grid_w, 5+num_classes],其中 5 表示 cx、cy、w、h、obj_conf。要按顺序展开,然后再把 shape 为 80、40、20 的网格拼起来。
坐标还原也很关键。如果输入图是 640 分辨率,原图是 1080p,那么推理前肯定做过 letterbox(等比缩放加灰边)。推理得到的框是在 letterbox 之后的坐标,要还原到原图坐标,需要先把 cx、cy、w、h 转成 x1、y1、x2、y2,然后按 letterbox 的缩放比例和 padding 偏移做逆变换。这一步漏掉的话,框的位置会整体偏移。
4.4 实测性能参考
下面这个表格是我在某台双路服务器上、单张 300V 24G 卡实测的一个参考数据。模型是 YOLOv5s,输入 640×640,驱动和 CANN 版本均为当时官方配套的稳定版本,不同环境可能会有明显差异,我只描述数量级:
| 模型 | 输入分辨率 | 精度模式 | Batch | 推理耗时(单 batch 均值) | 备注 |
|---|---|---|---|---|---|
| YOLOv5s | 640×640 | FP16 | 1 | 8~12 ms | 无 AIPP,host 端预处理 |
| YOLOv5s | 640×640 | FP16 | 4 | 15~20 ms | 多 batch 吞吐提升明显 |
| YOLOv5s | 640×640 | INT8 | 1 | 5~8 ms | 需先做量化校准 |
| YOLOv5m | 640×640 | FP16 | 1 | 18~25 ms | 模型更大,显存占用约 1.5 GB |
换算一下就知道,FP16、batch 1 的情况下,单卡跑 YOLOv5s 轻松上 80~120 FPS,这个性能对视频流的端侧/边缘侧场景来说是够用的。而且 24G 显存的好处是你可以同时加载多个模型实例,比如跑 3 路视频检测时各挂一个模型,互不干扰。
5. 性能调优与工程化避坑
5.1 数据搬运是最大的瓶颈
很多从 GPU 转过来的同学会用 GPU 上“算子越快越好”的思路调优,但在 NPU 推理卡上,经验有点不一样。小模型推理的时候,算子的计算时间短到可以忽略,反而 host 和设备之间的数据拷贝、预处理、后处理占了大头。
我测试时遇到过一个情况:YOLOv5s 推理本身只花 9ms,但读图加 BGR2RGB 加 resize 加归一化加拷贝,整个过程花了 20 多毫秒,帧率从“看起来还不错”直接掉到“有点拉胯”。优化手段有几个方向。
第一个方向是零拷贝。如果输入源是视频帧且已经存在设备内存里(比如用了 DVPP 硬件解码),就不要在 host 和 device 之间倒腾两次,直接用 device 内存做输入。
第二个方向是 AIPP 融合。把归一化、色域转换、缩放这些操作全部下沉到 AIPP,让芯片硬件去处理,host 端只负责把原始图像搬运过去,节省一大块预处理耗时。
第三个方向是批量推理。把多个请求攒起来凑成一个 batch。比如单 batch 耗时 10ms,batch 4 耗时 20ms,平均每帧只要 5ms,吞吐直接翻倍。但要注意 batch 太大时延迟会上升,适合高吞吐、低时延要求不是极端的视频分析场景。
5.2 多线程与多 Stream 的组合
AscendCL 支持在同一个 Context 下创建多个 Stream。我的一个工程做法是开三个线程:
- 线程 A 做图像采集和解码。
- 线程 B 做推理,使用
aclmdlExecuteAsync异步接口,把任务提交到推理 Stream 上。 - 线程 C 做后处理,读取上一步的输出结果。
Async 接口的好处是不阻塞主流程,模型计算期间 host 端可以继续准备下一帧数据。这里要注意同步关系:aclrtSynchronizeStream一定要在读取输出之前调用,否则读到的可能是上一帧的结果或者未定义数据。
另外,24G 的显存本身不算紧张,但如果你同时加载多个模型,或者每个模型都申请了很大的输入输出 buffer,要注意累计内存占用。用npu-smi info可以实时看到设备内存使用率,一旦接近上限,就该检查是否有aclrtMalloc后没释放的泄漏了。
5.3 AOE 与算子调优经验
昇腾工具链里有一个叫 AOE(Ascend Optimization Engine)的调优工具,我强烈建议在生产前跑一遍。它可以自动对模型做算子融合、图优化、甚至是算子实现选择,通常能带来几个点到十几个点的性能提升,而且不需要你手动改模型。
AOE 的使用很简单,命令行形式类似 ATC,指定模型路径和 soc_version,加上--job_type=2表示算子调优。跑完后会生成优化过的 OM 或者调优结果。我第一次用 AOE 时,是在一个语义分割模型上,推理时间从 32ms 降到了 26ms,效果很可观。
不过 AOE 也有它的脾气:调优时间可能比较长,一个小模型也要几十分钟,大模型几个小时很正常。所以我的建议是:先用常规 ATC 转换跑通功能和正确性,确认无误后再用 AOE 做性能压测和优化,不要一上来就调优,否则连问题出在模型还是出在调优流程上都没法判断。
5.4 生产部署的稳定经验
几件小事,都是线上环境踩出来的。
第一,模型文件要放到只读目录。OM 文件加载后运行时是不需要写原文件的,把它放在只读位置可以防止误删误改。 第二,设计好事后恢复机制。NPU 推理卡偶尔会因为算子执行异常导致 stream 错误,这种情况下最简单高效的处理是:捕获异常、释放该 Context、重新初始化,而不是反复重试同一个死掉的 stream。 第三,关注温度。前面说过 300V 是被动散热,服务器风道的风量直接影响卡的最高频率。长期 90 度以上高温运行的卡,性能和寿命都会明显下降,机房空调不给力的时候,我见过推理延迟直接翻了 1.5 倍的情况。
6. 常见问题速查表:我从报错里扒出来的经验
最后整理一份高频问题速查表,都是实际部署中反复出现的坑,按报错关键词分类写在这里:
| 现象 / 报错 | 大概率原因 | 解决办法 |
|---|---|---|
E10001加载模型失败 | OM 的 soc_version 与当前设备芯片不匹配 | 确认芯片型号后重新用正确的--soc_version转换 |
ATC 转换时提示Unsupported op | ONNX 里的算子版本太新,或者算子本身超出了当前 CANN 支持范围 | 降低 opset 版本;尝试把自定义算子替换成支持算子;检查模型导出时有没有把所有算子都静态化 |
| 推理结果全 0 或者全是 NaN | 数据预处理与 AIPP 配置不一致,比如归一化做了两次;或者模型输入数据类型不对 | 打开输入图像的数值范围检查,逐个核对 min/var_reci 参数 |
| 精度明显下降,检不出目标 | FP16 精度溢出,部分层损失过大 | 把precision_mode改成allow_mix_precision,或者对敏感层单独指定 FP32 |
aclrtMalloc报 Out of Memory | 显存被模型实例和其他 buffer 占满 | 检查是否有未释放的设备内存;尝试降低模型实例数;确认其他进程是否占用了同一张卡 |
| 固定 batch 模型加载后跑不同尺寸输入报 shape 错误 | 输入 shape 与模型转换时指定的--input_shape不一致 | 要么转换时用动态 shape,要么代码里严格按转换时 shape 做预处理 |
推理时偶发stream exec failed | Stream 上下文被异常中断,常见于多线程共享同一个 Context | 每个线程或每路业务使用独立 Context;捕获异常后重建 Context |
这其中我特别想展开说一下“推理精度下降”这个问题的排查思路。不要一上来就怀疑模型本身,基本流程是这样:先在 PyTorch 和 ONNX Runtime 上分别推理同一张图,确认 PyTorch 和 ONNX 结果一致;再转成 AIPP 关闭、FP32 精度模式的 OM,推理一次,确认 C++ 代码读输出没问题;最后再开 FP16、开 AIPP,逐项排除。这样出来的问题定位非常快,我从 3 天排查变成 1 小时排查,就是靠这套顺序。
另外,如果你发现同一张卡上跑多个模型实例时性能不升反降,大多数情况不是算力不够,而是内存带宽和 PCIe 带宽被占满了。这时候用npu-smi info观察 HBM 带宽利用率和 PCIe 利用率,如果持续接近 100%,就该考虑把模型分散到多张卡上,而不是继续叠实例。
关于“atlas 部署 YOLO”还有一个很多人忽略的细节:YOLOv5 训练时用到的数据增强、Mosaic、自动锚框这些逻辑全部发生在训练阶段,推理导出时不需要。所以导出 ONNX 之前,记得确认模型处于model.eval()状态,关闭所有随机性,否则导出的计算图里可能残留一些训练分支,影响转换和推理结果。
之前有一次我帮朋友排查他的 YOLOv5 转 OM 后推理结果中只有一部分类别能检测出来的问题,后来发现就是他导出 ONNX 时忘了关掉训练模式,模型里带上了不正确的路径,还有一层 BN 的参数也被折叠错了。这个坑很隐蔽,遇到“结果怪怪的”的时候,优先检查这一项。
最后说说版本管理。我强烈建议你写一个requirements.txt或者环境部署脚本,把驱动版本、固件版本、CANN 版本、PyTorch 版本、ONNX opset 版本全部锁死。昇腾生态更新很快,不同版本之间 API 有细微差别,上了生产环境之后最怕的就是某天有人心血来潮升了个版本,然后所有模型都起不来了。我自己的服务器上,所有的版本号都以官方配套表为准,绝不为了追新而升级,哪怕新版本广告里吹得再厉害。
还有一个小技巧:如果 ATC 转换的时候报错信息很模糊,可以先加--log=debug重新跑一次,日志会写到当前目录的atc_*.log文件里,里面的详细信息通常比控制台报错更能说明问题。我在定位算子不支持的问题时,基本都是靠 debug 日志里的算子名反查的。
这块内容能写的东西太多了,一次也讲不完。如果你已经拿着 300V 24G 跑通了 YOLOv5,下一步大概率会碰到的就是量化、多路视频流、或者接 EasyDarwin/FFmpeg 做端到端 pipeline。我后续如果再整理,可能会优先写一下 int8 量化校准的具体流程——那块才是真正把 24G 大显存的性能榨干的玩法。