1. 先搞清楚Atlas 300V的真实身份,再谈部署
1.1 它是推理加速卡,不是通用运算卡
我刚拿到这张Atlas 300V 24G的时候,第一反应是把它当成一张类似GPU的“运算加速卡”,插上服务器,去找对应驱动,然后装一套熟悉的CUDA生态。结果显而易见,这套思路根本不成立。它和我们平时用的GPU、或者那些拿来做科学计算的加速卡,并不是同一类东西。
Atlas 300V本质上是昇腾310P系列芯片做成的一张PCIe推理卡,主战场是把已经训练好的神经网络模型高效地跑起来,而不是从零开始训练一个大模型。训练卡要算梯度、来回搬运权重、应对各种动态shape的算子,它需要的是完整且灵活的通用计算能力。而推理卡只需要做一件事:前向推理。路线固定、算子固定、输入尺寸也尽量固定,这种情况下就可以把算力集中到矩阵乘法和内存带宽上,把能砍掉的通用性全部砍掉。
我用一个类比理解这件事:训练卡像一支能接各种装修活的工程队,你给它什么图纸它都能想办法干;而推理卡更像一条专门生产固定型号产品的流水线,换一个产品类型,就得重新把流水线调整一遍。对应到Atlas上,“调整流水线”就是后面要讲的模型转换,把PyTorch或者ONNX模型转换成它看得懂的OM格式。
所以如果你是第一次接触这张卡,请先放下“运算加速卡”这个笼统的概念。它是一张AI推理加速卡,目标是让YOLO这类检测模型在固定的输入尺寸下跑出稳定、低功耗、高吞吐的推理结果。想清楚了这一点,后面所有部署步骤才不容易走偏。
1.2 24G显存意味着什么:能跑多大的推理负载
热搜词里有人问“atlas 300v 24g是运算加速卡吗”,还有一个隐含的疑问是:24G显存在推理场景里到底能干什么。先说结论:24G对于YOLO这种检测模型来说,单模型完全用不满,它的价值在于多模型驻留和多batch并发。
一个YOLOv5s的模型权重大约只有几十MB,就算展开成浮点中间特征图,单路640×640输入也占不了太多内存。24G真正的意义在于:
- 可以同时把多个不同模型加载到内存里,按业务请求动态切换,不用反复加载卸载。
- 可以在一个模型实例上开较大的batch,比如一次同时推理8路、16路视频帧,大幅提升吞吐。
- 可以支撑输入分辨率较大的任务,比如YOLO在1280×1280甚至更高分辨率下做推理,中间特征图占用会明显上涨,小显存卡很容易爆。
另外要明确一点:显存大不等于算力强。推理卡的算力指标是TOPS(每秒万亿次整数运算),而Atlas 300V系列走的是INT8/FP16路线,最适合的是经过量化或半精度推理的模型。它不会像一张大显存的训练卡那样在FP32通用计算上有多亮眼的表现。买这张卡的人,冲的是“低功耗+高吞吐+长稳运行”,不是拿它去跑乱七八糟的浮点计算。
1.3 用推理卡,第一反应不该是“找CUDA”
我在刚开始部署时的最大障碍,其实是习惯问题。以前用GPU,模型训练完拷过去,装上CUDA、cuDNN、PyTorch的GPU版本,基本直接就能跑。但Atlas这张卡不支持CUDA,它有自己的生态:
| 生态层 | GPU部署时的习惯 | Atlas上的对应物 |
|---|---|---|
| 驱动查询 | nvidia-smi | npu-smi |
| 基础软件栈 | CUDA/cuDNN | CANN |
| 模型格式 | PyTorch/TensorRT | OM(离线模型) |
| 推理API | CUDA Runtime/TensorRT | AscendCL |
也就是说,在Atlas上部署YOLO,至少要经过“导出中间格式→转换OM→写推理代码”这样的链路,不能像GPU那样用PyTorch直接加载权重就跑。这个过程对不熟悉的人来说有点绕,但走通一次之后你会发现,它和TensorRT的部署思路其实很接近:都是先把模型固化成高度优化过的离线文件,再在运行时用专用API加载执行。
2. 环境搭建:驱动、固件、CANN的版本匹配是第一道坎
2.1 安装顺序为什么必须是“驱动→固件→CANN”
如果你在搜索引擎里翻过Atlas的部署文档,大概率会被一堆安装包和版本号绕晕。我第一次搭环境时一口气装了驱动和CANN,结果npu-smi完全看不到卡。后来才发现,驱动和固件必须匹配,而且安装顺序不能乱。
推荐的安装顺序是:
- 安装驱动(Driver)和固件(Firmware),通常下载对应操作系统架构的驱动包后,执行安装脚本,然后重启服务器。
- 重启后用
npu-smi info确认系统能看到板卡,驱动版本和固件版本正常。 - 再安装CANN toolkit,安装完成后source环境变量。
- 最后验证CANN是否生效,可以检查
/usr/local/Ascend/ascend-toolkit目录是否存在,以及能否正常调用atc命令。
为什么必须先驱动后CANN?因为CANN里的工具链和运行库要依赖底层驱动提供的设备访问能力。如果驱动没装好,CANN装得再完整也找不到设备,还会在ATC转换或者运行推理时爆出一堆让人摸不着头脑的报错。而且驱动和固件版本之间强绑定,社区文档里通常会给出一个“驱动固件配套表”,我踩过的坑就是没看配套表,固件版本高了半级,结果npu-smi显示状态异常,后来降回配套版本才恢复。
提示:安装前先确定你的操作系统架构是x86还是ARM,下载对应架构的安装包。Atlas 300V是PCIe卡,通常插在x86服务器上使用,但服务器架构不同,安装包也不同。
2.2 npu-smi:学会看懂这张卡的状态
npu-smi info是我在后续调试中用得最多的命令,它的地位相当于GPU里的nvidia-smi。装好驱动后,第一件事就是跑一下这个命令。正常输出会列出:
- 板卡编号和芯片编号(Chip),比如物理槽位对应的编号。
- 芯片温度、功耗、HBM/板上内存使用量。
- AI Core利用率,这个字段对判断推理是否在真实跑非常关键。
- 当前驱动版本和固件版本。
我在部署YOLO时,经常遇到“推理代码没报错,但速度很慢”的情况。这时候我第一个动作就是打开npu-smi看AI Core利用率。如果利用率一直很低,说明瓶颈不在板卡算力,而在数据搬运或者预处理;如果利用率接近100%,才需要考虑改模型结构或减少batch。
2.3 开发环境与运行环境怎么分离
Atlas的部署里存在两个容易混淆的概念:开发环境和运行环境。开发环境是用来做模型转换、代码交叉编译的,它不一定需要有卡;运行环境是实际插着Atlas 300V、执行推理的机器,它必须有驱动和CANN的运行库。
实操中我建议至少准备两台机器,或者在同一台机器上把角色分开:
- 开发机:安装完整的CANN toolkit,包括ATC工具、算子编译工具、pyACL开发包。模型转换在这里完成。
- 运行机:安装驱动、固件和CANN的run包,提供推理运行所需的库。推理代码在这里跑。
如果你的算力资源紧张,也可以只用一台物理机:先装好驱动和固件,再装toolkit,既做转换也做推理。唯一要注意的是,不要在运行环境把一堆开发组件全塞进去,太占空间且容易引起版本冲突。我个人的习惯是在Docker里建两个容器:一个专门做ATC转换,另一个挂载Ascend Docker Runtime跑推理,这样环境隔离最干净,换版本也方便回滚。
3. YOLO模型迁移的核心链路:从PyTorch权重到OM文件
3.1 整条链路先在心里过一遍
部署YOLO到Atlas上,最关键的一步不是写推理代码,而是把PyTorch训练出来的权重变成Atlas能直接加载的OM文件。一个典型的转换链路是这样的:
- 用PyTorch训练或下载YOLO权重(比如yolov5s.pt)。
- 导出为静态shape的ONNX文件,把输入尺寸固定,比如1×3×640×640,输出去掉NMS,保留原始检测头输出。
- 用ATC工具把ONNX转换成OM文件,这个过程中可以选择是否使用AIPP预处理、是否做INT8量化。
- 在推理代码中加载OM文件,执行前向计算。
这条链路每一步都有坑,但最集中也最容易被卡住的就是前两步。下面我把每个步骤的具体操作和原理拆开讲。
3.2 PyTorch导出ONNX最容易出错的三个细节
第一步是用PyTorch导出ONNX。很多人直接拿官方仓库的export.py导出,结果ATC转换时各种报错,或者推理结果完全不对。问题往往出在下面这三件事上。
第一,**输入尺寸必须固定。**Atlas推理卡最讨厌动态shape,模型转换阶段会把输入shape写进OM文件里,推理时只能按这个shape跑。所以导出时要把dummy input固定为1×3×640×640,并关闭dynamic_axes。虽然ATC也支持动态shape的转换,但会牺牲不少性能,还会让AIPP配置变得异常复杂。对于大多数YOLO部署场景,固定输入尺寸是更务实的选择。
import torch model = torch.load("yolov5s.pt", map_location="cpu")["model"].float() model.eval() dummy_input = torch.zeros(1, 3, 640, 640) torch.onnx.export( model, dummy_input, "yolov5s.onnx", opset_version=12, input_names=["images"], output_names=["output"], dynamic_axes=None, ) print("export done")第二,**输出层要自己处理一下。**YOLOv5原模型有三个检测头,直接导出的话输出会是三个不同尺度的特征图,后处理时要把它们解码、cat在一起。为了让Atlas上的处理更简单,我建议在导出前把三个检测头的原始输出reshape后cat成一个张量,形状为[1, 25200, 85](以COCO 80类为例,85=4个坐标+1个目标置信度+80个类别置信度)。这样OM文件的输出就只有一个,后处理代码清晰很多。
第三,**opset版本不要太新也不要太旧。**opset太低会缺少一些算子支持,太高又可能引入ATC不支持的新算子。我验证下来,opset_version=12或13在Atlas 300V上比较稳妥。如果导出时遇到“Unsupported operator”之类的提示,优先检查是不是opset版本的问题。
3.3 ATC转换命令与参数逐个拆
拿到ONNX文件后,下一步就是用ATC工具转换成OM文件。命令看起来不复杂,但参数必须理解清楚,否则转换过程会莫名其妙失败。
我刚装好CANN后的第一条转换命令大概是这样的:
atc --model=yolov5s.onnx \ --framework=5 \ --output=yolov5s_ascend \ --soc_version=Ascend310P3 \ --input_shape="images:1,3,640,640" \ --insert_op_conf=aipp.cfg \ --output_type=FP32说明一下每个参数:
--framework=5:表示输入模型是ONNX格式。这是ATC里指定的数字枚举,ONNX对应5。--soc_version:指定芯片型号。这里一定要查清楚你的卡具体是哪个系列,可以用npu-smi查看,或者在社区文档里按Atlas 300V的型号对照。我用的示例值是Ascend310P3,实际以你手里的卡为准。--input_shape:和导出ONNX时保持完全一致,固定为1,3,640,640。--insert_op_conf:插入AIPP预处理配置,这个后面单独讲。--output_type=FP32:指定输出数据类型。后处理用Python写的话,FP32比较方便。
转换完成后会生成一个.om文件,同时会用日志打印出这个模型的输入输出信息。我建议转换成功后不要急着扔日志,先看一眼其中列出的输入张量名称和shape,确认和后面的推理代码对得上。如果ONNX里输入名不叫images,这里就会对不上,推理时分配输入内存就会出问题。
3.4 AIPP预处理:要不要用,怎么配
AIPP是Atlas上的硬件图像预处理单元,可以在模型推理前帮你在硬件侧完成resize、crop、颜色格式转换、归一化等操作。这样做的好处是省掉CPU端一部分工作,而且数据到了Device内存后直接走硬件预处理,省一次显存拷贝。
但在实际部署YOLO时,我对AIPP的态度是:**能不用就不用,除非你把细节完全吃透。**为什么?因为AIPP的配置和模型导出时的输入约定是强耦合的,一旦搞错,模型输出的数值就对不上,而且这种错误非常隐蔽,不容易排查。
我见过一个最常见的坑:模型导出时输入是归一化后的float32张量(比如/255.0做完的),但AIPP配置里又设置了一遍归一化,推理结果出来全是乱的。正确的做法是二选一:
- 方案A:在Python/OpenCV里做完全部预处理,导出ONNX时把模型当作接收原始RGB float数据的模型,ATC转换时不插入AIPP。
- 方案B:导出ONNX时模型输入就是原始uint8图像,ATC转换时通过AIPP完成resize和归一化。
方案A更适合快速跑通流程,方案B适合追求极致性能。如果你非要试方案B,一个供参考的aipp.cfg长这样(注意不同CANN版本字段名可能略有差异,以官方文档为准):
aipp_op { aipp_mode: static input_format: RGB888_U8 src_image_size_w: 640 src_image_size_h: 640 crop: false resize: true resize_w: 640 resize_h: 640 mean_chn_0: 0 mean_chn_1: 0 mean_chn_2: 0 min_chn_0: 0.003921569 min_chn_1: 0.003921569 min_chn_2: 0.003921569 }这里mean_chn是每个通道减去的均值,min_chn是缩放系数,0.003921569也就是1/255,对应YOLO训练时图像归一化的操作。对应的,模型导出的ONNX输入张量应该就是一个NCHW布局的uint8图像,而不是float32。你要是选方案B,代码里喂给模型的就不是预处理后的float数组,而是原图转换维度后的uint8数组。
我自己的推荐是:第一次部署先走方案A,把整条链路跑通,确认检测效果没问题之后,再回头研究AIPP做优化。否则你在“模型输出不对”和“AIPP配置”这两个变量之间排查,很容易把自己绕进去。
4. 用AscendCL写一遍完整推理流程
4.1 初始化、设备选择和模型加载
模型转换完成,接下来就是写推理代码。Atlas上最常用的推理API是AscendCL,有C++版和Python版。对快速验证来讲,Python的pyACL已经够用,而且调试起来方便。
重点提一下pyACL的环境变量。装完CANN后,通常需要把pyACL的路径加到PYTHONPATH里,版本不同路径会有差异,最常见的是类似:
source /usr/local/Ascend/ascend-toolkit/set_env.sh export PYTHONPATH=/usr/local/Ascend/ascend-toolkit/latest/pyACL/python/site-packages:${PYTHONPATH}如果import acl失败,先检查路径是不是存在,路径名里的版本号是不是和你实际装的一致。核心调用流程如下:
import acl import numpy as np # 初始化 ret = acl.init() ret = acl.rt.set_device(0) # 创建context context, ret = acl.rt.create_context(0) # 加载OM模型 model_id, ret = acl.mdl.load_from_file("yolov5s_ascend.om")这里有一个很容易漏掉的点:必须要创建context。Atlas的设备管理模型里,context相当于一个执行上下文,不创建context后续调用execute的时候会报错或者在多线程场景下互相干扰。我第一次写的时候跳过了create_context,结果单线程能跑,一上多线程各种诡异报错,后来查文档才发现是context没管理好。
4.2 输入数据从OpenCV图像到Device内存的搬运
模型加载好之后,接下来要做的是把一张普通图片变成模型输入,然后拷贝到Device内存里。这里以方案A为例(预处理在Python里完成,模型输入是归一化后的float32 NCHW张量)。
第一步,用OpenCV读图,做预处理:
import cv2 img = cv2.imread("test.jpg") img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img = cv2.resize(img, (640, 640)) img = img.astype(np.float32) / 255.0 img = np.transpose(img, (2, 0, 1)) # HWC -> CHW img = np.expand_dims(img, axis=0) # 变成 1x3x640x640 img = np.ascontiguousarray(img)第二步,把输入数据拷贝到Device。AscendCL里有相应的内存申请和数据拷贝接口,这里我用一段简化代码示意:
# 获取模型输入的内存大小 input_desc = acl.mdl.create_tensor_desc(model_id, 0) input_size = acl.mdl.get_tensor_desc_size(input_desc) # 在Device上申请内存 input_data, ret = acl.rt.malloc(input_size, 2 * 1024 * 1024) # 把准备好的数据拷贝到Device内存 ret = acl.rt.memcpy(input_data, input_size, img.tobytes(), img.nbytes, acl.const.ACL_MEMCPY_HOST_TO_DEVICE)这里面的输入张量描述符和内存申请比较繁琐,但逻辑不复杂:先知道这个模型输入需要多少字节,再申请对应大小的Device内存,最后把预处理后的数据整块拷过去。把所有数据都准备好之后,用acl.mdl.execute执行推理,再把输出从Device内存拷回Host,才能做YOLO的后处理。
提示:图像数据的dtype、layout、尺寸,任何一个和OM模型转换时的约定不一致,推理结果都会不对。排查时先确认这三样,能省很多时间。
4.3 拿到推理输出之后的YOLO后处理
YOLOv5的输出经过我之前说的“cat成一个张量”的导出方式后,形状是[1, 25200, 85]。85个维度的含义是:前4维是预测框的中心点坐标和宽高(cx, cy, w, h),第5维是目标置信度(objectness),后面80维是COCO各类别的分类置信度。
在Atlas上拿到原始输出后,后处理的步骤是:
- 把输出数据从Device内存拷贝到Host侧,转成numpy数组。
- 将cx,cy,w,h转换成x1,y1,x2,y2的矩形坐标。
- 把目标置信度和类别置信度相乘,得到每个类别的最终分数。
- 设置一个阈值(比如0.5),过滤掉低分框。
- 用NMS(非极大值抑制)去除重复框。
核心代码概略如下:
def xywh2xyxy(boxes): y = np.copy(boxes) y[..., 0] = boxes[..., 0] - boxes[..., 2] / 2 y[..., 1] = boxes[..., 1] - boxes[..., 3] / 2 y[..., 2] = boxes[..., 0] + boxes[..., 2] / 2 y[..., 3] = boxes[..., 1] + boxes[..., 3] / 2 return y def nms(boxes, scores, iou_thres=0.45): 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) 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:]]) w = np.maximum(0.0, xx2 - xx1) h = np.maximum(0.0, yy2 - yy1) inter = w * h iou = inter / (areas[i] + areas[order[1:]] - inter) inds = np.where(iou <= iou_thres)[0] order = order[inds + 1] return keep # 假设output是从Device拷回来的numpy数组,形状为 [1, 25200, 85] pred = output[0] boxes = xywh2xyxy(pred[:, :4]) obj_conf = pred[:, 4:5] cls_conf = pred[:, 5:] scores = obj_conf * cls_conf.max(axis=1, keepdims=True) cls_ids = cls_conf.argmax(axis=1) mask = scores[:, 0] > 0.5 ...这段代码在训练框架里实现过一次然后搬到Atlas部署时,逻辑完全一样。很多人会把重心放在AscendCL的API调用上,反而忽略了后处理,其实后处理也是整个部署链路里最容易出奇怪问题的地方。
4.4 没有后处理的模型与“输出异常”的排查
如果你用的是YOLOv8、YOLOX或者自己魔改过的检测头,输出的shape和解码方式会和YOLOv5不一样。YOLOv8的输出通常已经解码好了,每行是box坐标加80个类别分数,不再有objectness这一维。这个不要套YOLOv5的后处理代码硬解,否则结果一定乱。
我在调试中遇到“推理结果全错”的时候,有个固定排查顺序:
- 先打印输出张量的shape,确认和导出时预期是否一致。
- 检查预处理:是否做了归一化?是除以255还是减均值?通道顺序是不是RGB?
- 检查AIPP:如果开了AIPP,确认ONNX输入被当成了uint8还是float32,两边对齐了吗?
- 检查后处理解码逻辑:坐标系是中心点还是左上角,有没有做letterbox导致坐标偏移。
其中letterbox这个坑也值得单独说。很多YOLO训练代码会在推理时用letterbox保持宽高比,而不是直接resize。如果你的图像是cv2.resize直接拉伸到640×640,那么后处理画框坐标是建立在拉伸后的图像上的,这个没问题;但如果你训练时习惯用letterbox,而部署时又直接resize,精度会有误差但不会完全坏掉。最怕的是训练用letterbox、部署也用了letterbox、但坐标没有做对应的偏移还原,框就会全部错位。这个属于经典低级错误,但特别容易犯。
5. 性能调优和实测踩坑记录
5.1 单帧延迟不惊艳,但多路并发很能打
跑通第一版推理后,我测了一下单张图片的推理耗时,发现并没有想象中那么快,甚至和一张中端GPU比有点差距。这不是卡的问题,而是定位问题。Atlas 300V这种推理卡的设计目标是把大量推理任务稳定地、持续地跑起来,而不是把单帧延迟压到极致。
实际业务里,YOLO部署最常见的场景是视频分析,可能同时有8路、16路摄像头画面需要检测。在这种场景下,Atlas 300V的低功耗和并行能力优势才会明显体现出来。我的建议是,评估这张卡时不要只测单帧耗时,要测以下两个指标:
- 多batch推理时的吞吐量(FPS)。
- 长时间跑满负荷时的稳定性和功耗。
很多时候你会发现单帧跑6毫秒,但把batch从1调到8之后,GPU端已经被过多控制逻辑拖慢了,而Atlas 300V反而能稳定地把吞吐拉上去,这正是这类推理卡存在的意义。
5.2 把多路视频流换成多batch推理
如果你手里有8路视频流要处理,最直接的方案不是开8个线程各自调一遍模型,而是尽量把多路画面拼成一个batch一次性推理。原因很简单:推理卡的大部分计算资源执行的是矩阵乘法和卷积,batch越大,单位时间内的有效计算密度越高。
实操上有两种做法:
- 在预处理时把8帧画面resize到相同尺寸后,沿batch维度拼接成
8×3×640×640,然后一次性送入模型。 - 使用
acl.mdl.execute异步模式,在等待推理结果的同时做下一批预处理,把CPU和NPU尽量并行起来。
我在代码里实现时发现,第一种做法的收益最直接,而且代码改动最小,只需要把原来处理单张图的逻辑套一层循环,再把numpy数组从1×3×640×640变成N×3×640×640。第二种做法的收益更依赖整体架构设计,适合已经有一定工程基础的项目。
5.3 内存复用:别在推理循环里反复malloc
另一个常见的性能杀手是在推理循环里反复申请、释放内存。每一次acl.rt.malloc和acl.rt.free都是一次系统级调用,如果在视频流场景下每帧都这么做,可能比推理本身还耗时。
的正确做法是提前分配好一块足够大的Device内存,在循环里反复复用:
- 输入内存:按batch大小和模型输入shape一次性申请。
- 输出内存:按模型输出shape一次性申请。
- 中间临时数据:如果能用
acl.rt.mem_alloc做固定池,也尽量固定下来。
我实测过,把内存申请挪出循环之后,整体推理吞吐大约提升了一个十分位数级别,而且因为减少了碎片化,长时间运行也更稳定。类似的,Host侧的后处理临时数组也尽量复用,Python里把几个大numpy数组预先创建好,循环里只做数值填充,能明显减少GC带来的卡顿。
5.4 我遇到过的报错与定位思路
最后把我在部署过程中遇到过的几个典型报错和定位思路分享出来,虽然没有编造错误码,但这些问题足够典型,遇到时可以直接对照排查。
| 现象 | 排查思路 |
|---|---|
npu-smi info看不到卡 | 先确认驱动和固件是否匹配,看看系统日志里有没有加载失败的信息;再检查物理插槽是否有接触问题 |
| ATC转换时提示算子不支持 | 检查ONNX导出时的opset版本;简化模型,排除自定义算子;尝试升级CANN版本 |
| 推理时输出全为0或明显错误 | 按4.4的顺序排查预处理、AIPP、后处理解码逻辑 |
| 运行一段时间后速度明显下降 | 大概率是内存泄漏或临时对象累积,检查是否有循环内重复malloc/free;观察npu-smi里的AI Core和内存利用率是否异常 |
| 多线程推理时随机报错 | 检查每个线程是否共享了同一个context,尝试为每个线程创建独立context或串行化执行 |
其中多线程context的问题我说一下:pyACL在多线程场景下,如果多个线程共享一个context,且同时执行acl.mdl.execute,容易出现资源竞争。最稳妥的方案是在每个线程内单独创建context,或者用线程锁把推理调用串行化。串行化看似降低了并发度,但在batch已经拉满的情况下,对吞吐影响其实不大,反而稳定很多。
我的经验是,遇到报错先别急着改代码,先看是发生在哪一层:是驱动层、还是转换层、还是运行时推理层。把问题定位到具体阶段,再回到对应文档去查,效率会高很多。如果一上来就在推理代码里反复试,很容易被相邻阶段的错误带偏。
最后再分享一个小经验:Atlas这张卡和GPU的调试节奏非常不一样,在GPU上你可能改两行代码就能看到结果,在Atlas上,模型转换阶段多花一点时间把ONNX导出和AIPP配置捋清楚,后面一路跑通会非常快。我踩过最大的坑,就是一开始急着写推理代码,结果在“预处理数据格式”和“模型输入约定”之间来回折腾。先把模型转换做扎实,再让代码接手,整条链路就走得顺了。