导读:本期聚焦于叶知晏创作的《如何解决YOLO-NAS部署难题?ONNX转换与TensorRT加速完整指南》,敬请观看详情。把 super-gradients 导出的 ONNX 直接喂给 trtexec,经常会在 ScatterND、NonMaxSuppression 这些节点上报错。根因往往不是 YOLO-NAS 结构有问题,而是导出链路中没有固定输入维度,也没清理掉 TensorRT 不支持的算子。解决思路先是在导出阶段显式指定 batch、高宽和通道,再决定是否保留 NMS 后处理。随后用 onnxsim 和 polygraphy 做计算图折叠与算子裁剪,必要时切出 decode 前的输出头,以换取更好的 engine 兼容性。TensorRT 侧给出 trtexec 与 Python API 两种构建方式,覆盖 FP16、动态 shape 和显存分配细节。最后补充 ScatterND、NonZero、校准数据不足、engine 加载失败等常见问题的排查路径,帮助跳过部署 YOLO-NAS 时的重复踩坑。

YOLO-NAS 的部署链路和普通 YOLO 模型有一个明显差异:训练仓库 super-gradients 导出的 ONNX 并不总是为 TensorRT 准备好。它可能带有动态维度、默认 NMS,或者包含一些 TensorRT 当前版本不直接支持的算子。实际落地时,比较稳妥的顺序是先导出、再清洗、最后构建 engine。本文围绕这几个步骤给出一套可复用的流程,重点解决 ONNX 转换和 TensorRT 加速过程中的高频问题。

如何解决YOLO-NAS部署难题?ONNX转换与TensorRT加速完整指南

一、ONNX 导出:先把入口参数固定下来

super-gradients 的版本不同,导出 API 和默认行为也会变化。导出前需要先确认使用的版本,4.x 和 5.x 在模型加载、导出函数上的差异会直接影响后续转换结果。比较稳定的做法是调用 prep_model_for_conversion 方法,它会完成 RepVGG 分支融合、重参数化等工作,避免把训练态结构带进 ONNX。如果不调用这个方法直接导出,计算图中可能残留大量分支,TensorRT 构建 engine 时会明显变慢,甚至触发算子不支持错误。

输入形状建议在导出阶段就固定为 [1, 3, 640, 640]。不要为了后续动态 batch 而让 ONNX 保持动态维度,动态 batch 更合适在 TensorRT optimization profile 中处理。导出时可以指定 opset_version 为 16 或 17,较高的 opset 对 LayerNorm、GELU 等算子表达更自然,但也要看目标 TensorRT 版本的支持范围。下面是一个基础的导出示例:

pip install super-gradients==4.9.0
from super_gradients.training import models

model = models.get("yolo_nas_s", pretrained_weights="coco")
model.eval()
model.cuda()
model.prep_model_for_conversion(input_size=[1, 3, 640, 640])

import torch
torch.onnx.export(
    model,
    torch.randn(1, 3, 640, 640).cuda(),
    "yolo_nas_s_raw.onnx",
    input_names=["images"],
    output_names=["output0"],
    opset_version=16,
    do_constant_folding=True,
)

执行完导出后,建议先用 ONNX Runtime 加载一次,确认输出数量和名称。YOLO-NAS 如果保留了默认后处理,输出可能已经包含 boxes、scores、class_ids 三个张量;如果关闭后处理,则输出是 decode 之前的回归与分类结果。TensorRT 对带 NMS 的 ONNX 支持并不稳定,很多时候需要在下游自己实现 decode 和后处理,因此第一步就要根据业务需求决定保留哪些输出。

二、ONNX 清洗:能折叠的折叠,该切掉的切掉

从 PyTorch 导出的 ONNX 通常会带有多余的 Shape、Gather、Concat 操作,这些节点在 TensorRT 构建时会拖慢解析速度,也可能导致部分算子无法识别。清洗计算图的第一步是用 onnxsim 做常量折叠和 identity 消除。安装 onnx-simplifier 后,指定输入形状执行简化,输出通常比原始 ONNX 更小、更干净。简化命令如下:

python -m onnxsim yolo_nas_s_raw.onnx yolo_nas_s_sim.onnx --input-shape images:1,3,640,640

简化完成后,还需要扫描一次 ONNX 中的算子类型,尤其关注 TensorRT 长期不支持的节点。可以写一段 Python 脚本遍历计算图,把 ScatterND、NonMaxSuppression、NonZero、GatherND 等节点打印出来。如果发现这些节点,优先考虑从导出侧去掉对应操作,而不是等 TensorRT 报错后再猜原因。检查脚本如下:

import onnx

model = onnx.load("yolo_nas_s_sim.onnx")
unsupported_ops = ["ScatterND", "NonMaxSuppression", "NonZero", "GatherND"]
for node in model.graph.node:
    if node.op_type in unsupported_ops:
        print("unsupported op:", node.name, node.op_type)

如果确认不需要模型内部的后处理,可以用 polygraphy surgeon 做计算图裁剪。比如只保留 decode 前的输出,把 NMS 相关分支移除,能显著提高 engine 构建成功率。polygraphy 的 prune 命令可以在不破坏输入输出的前提下删掉多余节点,也可以显式指定保留哪些输出。对于 YOLO-NAS 这种默认带完整后处理的模型,切掉后期 NMS 通常比试图转换 NMS 插件更省事。

另一个常见做法是让业务端自己写 decode。模型只输出回归偏移和分类概率,之后在 C++ 或 Python 后端完成 anchor decode、置信度过滤和 NMS。这样做虽然多了一些业务代码,但部署兼容性更好,也方便针对不同硬件调整后处理性能。TensorRT 自带的 EfficientNMS plugin 也有使用场景,但它对 ONNX 的输出格式有固定要求,YOLO-NAS 原生输出往往不能直接对接,需要额外改图适配。

三、构建 TensorRT engine:trtexec 和 Python API

最快速验证 GPU 加速效果的方式是用 TensorRT 自带的 trtexec 工具。它会自动解析 ONNX、构建 engine,并可以指定 FP16、动态 shape 等参数。命令中 minShapes、optShapes、maxShapes 分别对应最小、常用和最大输入形状,三者必须满足维度一致。对于单 batch 部署,可以直接都设为 1x3x640x640;如果需要动态 batch,就把 maxShapes 改大。一个典型的 FP16 构建命令如下:

trtexec --onnx=yolo_nas_s_sim.onnx \
    --saveEngine=yolo_nas_s_fp16.engine \
    --fp16 \
    --minShapes=images:1x3x640x640 \
    --optShapes=images:1x3x640x640 \
    --maxShapes=images:8x3x640x640

Windows 环境下如果 trtexec 没有加入 PATH,可以进入类似 C:\TensorRT-8.6.1.6\bin 的目录执行命令。Linux 服务器一般安装到 /usr/src/tensorrt 或 /opt/TensorRT,路径要按实际版本调整。trtexec 构建完成后会在终端打印 engine 的 latency 和 throughput,可以作为精度和速度的基线,不过它不会做业务后处理,只能评估模型本身的推理耗时。

如果需要在服务代码中动态构建 engine,可以使用 TensorRT Python API。相比 trtexec,Python API 能更细粒度地控制 builder、network、config 和 optimization profile。下面是一个完整构建示例,注意其中显式设置了 EXPLICIT_BATCH,并启用了 FP16:

import tensorrt as trt

logger = trt.Logger(trt.Logger.WARNING)
builder = trt.Builder(logger)
network = builder.create_network(1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH))
parser = trt.OnnxParser(network, logger)

with open("yolo_nas_s_sim.onnx", "rb") as f:
    if not parser.parse(f.read()):
        for i in range(parser.num_errors):
            print(parser.get_error(i))
        raise RuntimeError("ONNX parse failed")

config = builder.create_builder_config()
config.set_flag(trt.BuilderFlag.FP16)
config.max_workspace_size = 1 << 30

profile = builder.create_optimization_profile()
profile.set_shape("images", (1, 3, 640, 640), (1, 3, 640, 640), (8, 3, 640, 640))
config.add_optimization_profile(profile)

engine = builder.build_serialized_network(network, config)
with open("yolo_nas_s_fp16.engine", "wb") as f:
    f.write(engine)

engine 构建完成后,加载和推理还需要处理显存分配。TensorRT 的 execute_v2 使用 bindings 列表传递输入输出指针,输入输出 buffer 的大小必须和 profile 中定义的 shape 一致。推理阶段的 Python 示例可以作为模板:

import tensorrt as trt
import pycuda.driver as cuda
import pycuda.autoinit
import numpy as np

engine = trt.Runtime(trt.Logger(trt.Logger.WARNING)).deserialize_cuda_engine(
    open("yolo_nas_s_fp16.engine", "rb").read()
)
context = engine.create_execution_context()
context.set_binding_shape(0, (1, 3, 640, 640))

input_buf = np.ascontiguousarray(np.random.randn(1, 3, 640, 640).astype(np.float32))
output = np.empty((1, 84, 8400), dtype=np.float32)

d_input = cuda.mem_alloc(input_buf.nbytes)
d_output = cuda.mem_alloc(output.nbytes)
bindings = [int(d_input), int(d_output)]

cuda.memcpy_htod(d_input, input_buf)
context.execute_v2(bindings)
cuda.memcpy_dtoh(output, d_output)
print(output.shape)

实际使用时,输出形状不能写死,最好通过 engine.get_binding_shape 或 context.get_binding_shape 动态获取。输入数据进入模型前还要做与训练一致的预处理,包括 letterbox 缩放、颜色通道顺序和归一化。例如训练阶段如果用的是 RGB 0 到 1 输入,部署端也必须保持相同方式,否则检测框位置和置信度会整体偏移。

四、常见报错和排查路径

ScatterND 报错是 YOLO-NAS 转 TensorRT 时最典型的问题之一。这个算子通常来自模型内部的索引或注意力结构,当前部分 TensorRT 版本无法原生支持。遇到这类错误,第一反应不应该是降级模型,而是检查 ONNX 导出链路是否可以绕开。简化计算图后仍然存在时,可以升级 TensorRT 到 8.6 或更高版本,或者通过裁剪网络去掉相关分支。若只在后处理中出现,直接在导出时关闭后处理即可解决。

engine 加载阶段出现 deserialization failed 或 plugin version mismatch,基本可以判定是 TensorRT 版本不一致。构建 engine 的 TensorRT 版本必须与线上加载 engine 的版本完全匹配,甚至小版本不同也可能导致反序列化失败。建议把 TensorRT、CUDA、驱动版本固定在同一套容器镜像中,避免构建环境和运行环境漂移。

如果使用 INT8 量化,还容易出现校准数据不足导致的精度大幅下降。YOLO-NAS 对 INT8 校准比较敏感,一般需要准备几百张真实业务场景的图片,而不是随机噪声或训练集的一个小角落。校准数据分布越接近线上输入,量化误差越小。没有充分校准数据时,更建议使用 FP16,性能和精度之间的平衡更好。

推理时 CUDA out of memory 通常由过大的 max batch 或 workspace 设置引起。构建 engine 时 maxShapes 定义得越大,GPU 显存占用越高,同时可能降低 kernel 选择空间。线上服务如果只跑单 batch,就尽量把 profile 的 max batch 限制到 2 或 4,不要为了以后可能用到的动态 batch 预留过大空间。输出 buffer 的分配也要根据实际 binding size 计算,不能按固定数值开大数组,否则容易在长时间运行时出现内存碎片和显存浪费。

YOLO-NAS 部署难的核心不是模型本身,而是从训练框架到推理引擎的中间链路。把 ONNX 导出参数固定、做一轮计算图清洗、再用 TensorRT 构建 engine,基本可以覆盖大多数落地场景。遇到不支持的算子时,优先选择让模型输出 decode 前的张量,把后处理移到业务侧,通常能获得更高的部署稳定性和更好的硬件适配性。

YOLO-NAS部署ONNX转换TensorRT加速修改时间:2026-10-04 12:28:35

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/1004/65554.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。