这几年模型部署圈子里TensorRT 和 ONNX Runtime 基本是绕不开的两个名字。我收到过不少私信上来就问YOLOv12 转 TensorRT 用 C 跑在 5070 上怎么搞PP-OCRv6 能不能直接用 ONNX Runtimermbg-2.0 人物抠图在 Java 里怎么接还有人在树莓派 Pico 2 的 RP2350 上问 AI 推理方案。老实说模型训练只是第一步真正让模型在业务里跑起来、跑得快才是部署工程师每天面对的主战场。这篇就围绕 TensorRT 和 ONNX Runtime 两个推理引擎把选型、转换、部署、调优和排障完整过一遍。适合刚接触模型部署的算法工程师也适合要接手推理服务的后端开发。文中案例覆盖目标检测、OCR、人像抠图顺便聊聊大模型推理服务和边缘设备的边界。内容比较杂但都是实际问题你挑自己关心的章节看也行。1. 先搞清两个推理引擎的定位TensorRT 和 ONNX Runtime 到底差在哪1.1 出身决定性格闭源加速器 对阵 跨平台运行时很多人第一次接触这两个名字时容易懵它们不都是跑模型吗为什么还分来分去我用一个不那么严谨但很好懂的类比TensorRT 是英伟达给自家 GPU 开的“赛车改装厂”它只认 NVIDIA 的设备会把模型彻底拆开重排把能合并的算子合并能换成专用内核的换掉最后生成一个针对特定 GPU 架构深度优化的“赛车级引擎”。ONNX Runtime 更像是“标准集装箱货运体系”它不挑司机也不挑路况CPU 能跑GPU 能跑甚至 ARM、老显卡、树莓派上都能跑代价是没有 TensorRT 那种把硬件榨干的极致速度。这个出身决定了它们的适用范围。TensorRT 输出的不是普通模型文件而是一个 plan 文件习惯上叫 engine它绑定具体的 GPU 架构、CUDA 版本、TensorRT 版本。你把在 RTX 4090 上构建的 engine 拷贝到 RTX 5070 上大概率直接报错。ONNX Runtime 则简单很多你给一个 ONNX 格式的模型它加载后就能跑底层通过不同的 Execution ProviderEP调用不同硬件CPU EP、CUDA EP、TensorRT EP、OpenVINO EP 等。有意思的是这两者并不是零和关系。ONNX Runtime 里可以挂载 TensorRT 作为执行提供程序相当于让 ONNX Runtime 作为调度入口遇到支持的算子就交给 TensorRT 执行遇到不支持的再回退到其他 EP。很多人一开始不知道这个特性要么硬啃 TensorRT 的 C API要么忍受 ORT 纯 CPU 推理的龟速其实中间还有不少缓和方案。1.2 场景选型结合 rmbg-2.0、YOLO、PP-OCR 聊聊怎么定我平时接到最多的一类咨询就是“我已经有模型了该用哪个框架部署”。这个问题没有统一答案但可以按场景给一个基本判断。典型任务推荐引擎原因YOLOv11 / YOLOv12 目标检测跑在 NVIDIA 服务器上TensorRT追求极致吞吐和低延迟FP16 下精度几乎无损PP-OCRv6 识别流程需要快速集成到现有服务ONNX Runtime模型结构复杂、前后处理多ORT 部署简单跨平台友好rmbg-2.0 人物抠图Java 后端调用ONNX RuntimeJava 生态下 ORT 官方支持完善TensorRT 的 Java API 支持很弱移动端 / 嵌入式设备ONNX Runtime 或 ncnnTensorRT 只认 NVIDIA GPU基本不适用ncnn 在移动端更极致大模型文本生成服务sglang、vLLM、TensorRT-LLM传统 ORT/TensorRT 对大模型场景覆盖不够需要专用框架这里要特别说下 ONNX Runtime 和 ncnn 的对比。移动端或者资源受限的 Linux 设备上ncnn 因为专门针对手机 SoC 优化体积小、启动快很多 App 都选它。但 ncnn 对算子支持没那么全如果你只想“一套代码多处跑”ONNX Runtime 的兼容性明显更省心。我自己的习惯是服务端跑 NVIDIA 卡优先看 TensorRT其他一律 ONNX Runtime 起步遇到性能瓶颈再考虑更底层的方案。顺带提一句热搜里的“刘文超判断推理笔记”和“qbf推理”。前者是行测逻辑推理后者是逻辑自动求解领域的问题它们和本文聊的“深度学习模型推理”完全是两码事。经常有人在群里问“推理笔记怎么用”我会先把话题掰回来模型推理是让训练好的神经网络在前向传播时算出结果别把概念搞混了。2. 模型导入与转换从 ONNX 到 TensorRT 的完整链路2.1 ONNX Runtime 直接吃 ONNX 模型零转换起步ONNX Runtime 最友好的地方在于“不需要转格式”。你只要把 PyTorch、TensorFlow、PaddlePaddle 训练好的模型导出成 ONNXORT 就能直接加载。以 PyTorch 为例导出一段非常简单的代码import torch import torch.onnx model torch.load(yolov11.pt) # 假设是导出的模型 model.eval() dummy_input torch.randn(1, 3, 640, 640) torch.onnx.export( model, dummy_input, yolov11.onnx, input_names[images], output_names[output0], dynamic_axes{images: {0: batch}, output0: {0: batch}}, opset_version17 )导出后可以直接用 ORT 验证import onnxruntime as ort import numpy as np sess ort.InferenceSession(yolov11.onnx, providers[CUDAExecutionProvider, CPUExecutionProvider]) input_name sess.get_inputs()[0].name output_name sess.get_outputs()[0].name # 假设输入已经预处理为 CHW 归一化后的数据 result sess.run([output_name], {input_name: np.random.randn(1, 3, 640, 640).astype(np.float32)})把这个跑通你就已经完成了一次完整的“模型推理”。后面所有优化都是在保证这个结果不变的前提下让速度更快。2.2 TensorRT 转换的三种路径trtexec、torch2trt、PolygraphyTensorRT 不吃 ONNX 原文件你需要先把它构建成 engine。常用路径有三种第一种是命令行工具trtexec这也是我最推荐的。它不用写代码能快速验证模型能不能转还能输出各层耗时trtexec --onnxyolov11.onnx --saveEngineyolov11.engine --fp16第二种是 ONNX 转 TensorRT 的 Python API适合在服务启动时动态构建 engine。核心代码类似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(yolov11.onnx, rb) as f: parser.parse(f.read()) config builder.create_builder_config() config.set_flag(trt.BuilderFlag.FP16) engine builder.build_engine(network, config) with open(yolov11.engine, wb) as f: f.write(engine.serialize())第三种是torch2trt或torch_tensorrt直接把 PyTorch 模型转成 TensorRT engine。优点是方便缺点是对自定义算子支持差而且编译期与运行期环境必须一致。如果模型里有什么奇奇怪怪的前处理层很容易转一半报错。我的建议是统一导出 ONNX再用 trtexec 去转把“训练框架”和“部署引擎”彻底解耦后续排查问题会省非常多力气。这里多说一句 TensorRT 安装。很多人卡在import tensorrt报错基本是版本不匹配。TensorRT 和 CUDA、cuDNN 之间存在严格对应关系不是越新越好。以 RTX 5070 这类 Blackwell 架构显卡为例需要较新的 TensorRT 版本才能支持建议直接拉取 NVIDIA 官方 TensorRT 容器镜像环境一次性配齐避免在宿主机上把 CUDA 版本搞乱。宿主机只要能跑nvidia-smi且驱动版本足够就行容器内自带的 CUDA 运行时和宿主驱动兼容即可。2.3 动态 batch 与动态尺寸的配置细节目标检测模型经常要接收不同尺寸的输入这就涉及动态 shape。TensorRT 构建 engine 时要为每个动态维度指定三个值最小、最优、最大。比如输入是1x3x640x640但你可能要支持 batch4 甚至 batch8trtexec --onnxyolov11.onnx \ --minShapesimages:1x3x640x640 \ --optShapesimages:4x3x640x640 \ --maxShapesimages:8x3x640x640 \ --fp16 \ --saveEngineyolov11_dynamic.engine这里有个很多人忽略的点optShapes直接决定了 TensorRT 为动态 shape 预留的显存和 kernel 选择它应该贴近你线上真实的请求分布。假如线上平均 batch 是 2你非把 opt 设成 8engine 会按 8 来优化和预分配不仅浪费显存可能还跑不过精心调过 opt 的版本。动态尺寸在做推理时也需要注意。很多检测模型要求输入尺寸是 32 的倍数因为下采样倍数通常是 32。如果你直接塞一张 1000x800 的图不 resize 到合理尺寸后续的输出尺寸会和预期不符后处理时坐标换算容易出错。我之前帮人排查过一个诡异 bug小图检测正常大图偶尔漏检最后发现就是输入没有 padding 到倍数导致 TensorRT 内部计算边界不对。2.4 精度校准FP16 与 INT8 的取舍TensorRT 最香的两个加速选项是 FP16 和 INT8。FP16 基本无脑开只需要在构建时加--fp16或者set_flag(trt.BuilderFlag.FP16)大多数检测模型、OCR、抠图模型在 FP16 下精度损失可以忽略不计。INT8 则麻烦得多它需要提供一个校准数据集让 TensorRT 统计激活值的分布然后决定如何把 FP32 映射到 INT8。校准集选得好不好直接影响精度一般建议用几百张有代表性的真实图片不要用随机噪声。如果你发现模型在 FP16 下输出结果异常先别急着怀疑精度损失要检查是不是某些算子不支持 FP16导致 TensorRT 悄悄回退到性能较差的路径甚至结果不对。这时候可以在构建时打开 verbose 日志搜索WARNING看看哪些算子被降级了。对于 OCR 这类对文本细节敏感的任务我更倾向于先跑 FP32 或 FP16INT8 留到性能确实不够时再尝试。2.5 训练完之后的 conf 参数到底怎么设热词里有一个很典型的困惑“模型训练出来之后那个推理用的 conf 参数是什么”。这个 conf 是 confidence threshold即置信度阈值。模型输出的每个候选框都会有一个 0 到 1 之间的置信度表示“这里有多大的概率是目标”推理时我们会丢掉低于阈值的框这个阈值就是 conf。它不是训练参数而是推理后处理参数。和它经常一起出现的是 NMS 的 IoU 阈值NMS 负责在重叠框很多时只保留一个最优框。经验值是检测模型 conf 设在 0.25 到 0.5 之间IoU 设在 0.45 到 0.7 之间。conf 设低了会有大量误检设高了会漏检。实际调参时我会先在一个验证集上画 Precision-Recall 曲线找一个 P/R 平衡点而不是凭感觉拍脑袋。3. 实操把 YOLO12 / YOLOv11 用 TensorRT 和 ONNX Runtime 跑起来3.1 环境准备驱动、CUDA、cuDNN、TensorRT 版本匹配开始动手前先确认环境。nvidia-smi看驱动版本nvcc -V看 CUDA 工具包版本。如果你打算用 TensorRT需要额外确认 TensorRT 版本并且保持“驱动 要求版本、CUDA 运行时、cuDNN、TensorRT”四者都在兼容范围内。这个兼容矩阵在 NVIDIA 官方文档里有但很多人懒得查结果总是在部署时才爆雷。我的建议是能省则省直接用官方 TensorRT 容器。以nvcr.io/nvidia/tensorrt:24.12-py3这种镜像为例里面已经把 CUDA、cuDNN、TensorRT 都配好了你只需要把代码和 ONNX 模型挂载进去。宿主机那边只要驱动能跑nvidia-smi就行。这套流程我用了很多年几乎没有因为环境问题卡过。如果你非要在本机安装注意 TensorRT 的 Python 包和 C 库要一起装。很多初学者只pip install tensorrt就以为完事了实际 C 部署时还需要libnvinfer.so等动态库它们的路径要加入LD_LIBRARY_PATH。3.2 ONNX Runtime 推理的 C / Python 代码骨架Python 侧最简单加载 session 后直接 run。C 侧稍微复杂但也是固定套路。以 ONNX Runtime C API 为例核心步骤是创建环境、打开 session、准备输入输出#include onnxruntime_cxx_api.h Ort::Env env(ORT_LOGGING_LEVEL_WARNING, test); Ort::SessionOptions opts; opts.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_ALL); opts.SetIntraOpNumThreads(4); Ort::Session session(env, yolov11.onnx, opts); Ort::AllocatorWithDefaultOptions allocator; // 获取输入输出名 auto input_name session.GetInputNameAllocated(0, allocator); auto output_name session.GetOutputNameAllocated(0, allocator); // 拼接 Ort::Value注意 shape 是 int64_t 数组 std::arrayint64_t, 4 input_shape{1, 3, 640, 640}; Ort::Value input_tensor Ort::Value::CreateTensorfloat( allocator, input_data.data(), input_data.size(), input_shape.data(), input_shape.size()); // 推理 auto output_tensors session.Run(Ort::RunOptions{nullptr}, input_name.get(), input_tensor, 1, output_name.get(), 1); const float* output_data output_tensors.front().GetTensorDatafloat();这段代码在 CPU 上已经能跑。要切到 CUDA EP需要在Ort::SessionOptions里追加 providerOrtCUDAProviderOptions cuda_options; opts.AppendExecutionProvider_CUDA(cuda_options);用 ORT 的好处是代码模板基本通用换模型只需要改输入输出的名字和 shape非常适合做“多模型统一推理服务”。3.3 TensorRT C 推理代码关键步骤TensorRT 的 C 部署比 ORT 繁琐但性能上限更高。加载一个已构建好的 engine 并推理的核心步骤如下std::ifstream file(yolov11.engine, std::ios::binary); std::stringstream ss; ss file.rdbuf(); // 读取整个 serialized engine std::string engine_str ss.str(); trt::IRuntime* runtime trt::createInferRuntime(logger); trt::ICudaEngine* engine runtime-deserializeCudaEngine(engine_str.data(), engine_str.size()); trt::IExecutionContext* context engine-createExecutionContext(); // 如果你的 engine 是动态 shape推理前必须 setInputShape const char* input_name engine-getIOTensorName(0); // 或者按实际名字获取 context-setInputShape(input_name, trt::Dims4(1, 3, 640, 640)); // 分配 device 内存 void* input_buffer; void* output_buffer; cudaMalloc(input_buffer, 1 * 3 * 640 * 640 * sizeof(float)); cudaMalloc(output_buffer, output_size * sizeof(float)); // 拷贝输入并执行 cudaMemcpy(input_buffer, host_input.data(), input_buffer_size, cudaMemcpyHostToDevice); context-enqueueV3(stream); // 这里需要绑定 tensor 地址通常已经通过 executeV2 / tensorAddress 绑定过 cudaMemcpy(host_output.data(), output_buffer, output_buffer_size, cudaMemcpyDeviceToHost);注意新版 TensorRT 的 API 在enqueueV3之后要求先为每个 IO tensor 设置地址。我写代码时会用一个std::unordered_mapstd::string, void*保存 buffer 指针再用context-setTensorAddress(name, ptr)绑定。这一步很容易漏漏了运行时会一直报 “buffer not set for tensor”。另一个关键点是engine 文件不能跨 GPU 架构使用。你在 RTX 5070 上构建的 engine拿到 A100 或 RTX 3090 上都会失败因为内部 kernel 是针对具体 SM 架构编译的。所以生产环境里最好在每台 GPU 机器上启动时动态构建 engine或者提前用同一型号的机器构建好再分发。3.4 推理结果保存YOLOv11 保存推理结果的标准姿势模型跑完不是终点结果还得保存下来。Python 里最常见的做法是 OpenCV 画框后cv2.imwriteimport cv2 for box, score, class_id in detections: x1, y1, x2, y2 [int(v) for v in box] cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, f{class_id} {score:.2f}, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1) cv2.imwrite(result.jpg, img)C 侧同样用 OpenCV但要注意颜色通道是 BGR坐标要先按原图尺寸和模型输入尺寸的比例换算。如果你用了 letterbox 预处理保存前需要把检测框坐标减去 padding 再除以缩放比例。我这个坑踩过很多次训练时预处理和部署时预处理不一致导致检测框偏移最后对比原始推理脚本才发现是 letterbox 参数补的边不对。保存格式上调试阶段我喜欢直接存带框的图片在批处理或服务端应该把结果结构化存储比如 JSON、TXT 或数据库。常见做法是每行输出class_id score cx cy w h方便后续做指标统计或业务逻辑。4. 特定场景部署技巧人物抠图、OCR、大模型与边缘设备4.1 rmbg-2.0 人物抠图在 Java 侧的 ONNX Runtime 调用很多人以为 Java 做 AI 推理很麻烦其实 ONNX Runtime 的 Java API 已经足够成熟。以 rmbg-2.0 人物抠图为例项目里加一个 Maven 依赖dependency groupIdcom.microsoft.onnxruntime/groupId artifactIdonnxruntime/artifactId version1.18.0/version /dependency加载模型并推理的核心逻辑大概是OrtEnvironment env OrtEnvironment.getEnvironment(); OrtSession session env.createSession(rmbg-2.0.onnx, new OrtSession.SessionOptions()); // 构造输入 Tensorshape 为 {1, 3, H, W} float[][][][] input preprocess(image); // 归一化、resize、RGB、CHW OnnxTensor inputTensor OnnxTensor.createTensor(env, input); OrtSession.Result result session.run(Collections.singletonMap(input, inputTensor)); OnnxTensor output (OnnxTensor) result.get(0); float[][][][] outputData (float[][][][]) output.getValue(); // outputData[0][0][h][w] 即为前景概率按阈值得到 alpha maskrmbg-2.0 这种模型对输入分辨率比较敏感通常需要 resize 到模型要求的大小比如 1024x1024。预处理和后处理都会影响最终抠图效果Java 侧用 BufferedImage 读取图像后一定要确保像素遍历时 RGB 通道顺序正确。有人直接把 BufferedImage 的 int 像素拆成通道结果 R 和 B 反了抠出来的人像边缘全是紫边。如果你的 Java 服务追求更高吞吐可以开启OrtSession.SessionOptions的优化和线程数配置但更关键的是减少 Java 和 C 之间的张量拷贝。ONNX Runtime Java API 底层仍然是 native 调用频繁在循环里创建OnnxTensor会带来额外开销最好复用 buffer 或限制批处理大小。4.2 PP-OCRv6 的 ONNX 推理踩坑PP-OCR 系列是由检测、方向分类、识别三个模型组成的流水线。PP-OCRv6 导出 ONNX 后用 ONNX Runtime 推理整体流程不算复杂但有几个坑值得说。第一个坑是 opset 版本。部分版本导出的模型用了较新的算子如果你的 ONNX Runtime 版本太老会报 “Unsupported operator”。解决办法是升级 ORT 到最新版或者在 Paddle 导出时指定较低的 opset但这可能引入精度变化需要测试。第二个坑是动态 shape。OCR 的检测模型输入尺寸通常会设置成固定的3xHxW但文本行长度不定识别模型往往需要动态 width。在 ORT 里跑动态 shape 没问题但如果你同时开了 CUDA EP某些版本对动态 shape 的 kernel 选择不理想性能会明显下降。我的做法是把识别模型的输入 width 固定为 320 或 640超出的先等比缩放这样牺牲一点点灵活性换稳定性能。第三个坑是后处理里的文本框解码。ONNX 模型输出的是分割概率图或回归结果需要自己实现 DB 后处理或 CTC 解码。这个环节最容易出精度问题建议先用 PaddleOCR 官方 Python 脚本保存一组中间结果再和你的 ONNX Runtime 后处理结果逐一对比定位是模型输出不对还是解码逻辑不对。4.3 大模型推理sglang serve 启动推理服务与 TensorRT-LLM 的路线差异热词里出现了sglang serve 启动推理服务这里简单聊一下。对于大语言模型这类生成式任务传统 TensorRT 和 ONNX Runtime 并不直接适合因为需要处理 KV Cache、连续批处理、动态 beam search 等复杂逻辑。现在更常用的是 vLLM、sglang、TensorRT-LLM 这类专门框架。sglang 的启动方式很简洁sglang serve --model-path meta-llama/Llama-3.1-8B-Instruct --port 30000它底层做了很多算子级优化也支持通过 TensorRT-LLM 作为后端之一。你如果在调研大模型部署方案可以把这些框架理解为“为 LLM 场景特化的推理运行时”而 TensorRT/ONNX Runtime 更适合传统视觉模型和中小规模模型。选型时先想清楚任务类型别拿 OCR 的代码去套 LLM 服务。4.4 边缘设备上的推理边界RP2350 能跑 ONNX Runtime 吗经常有人拿着树莓派 Pico 2 的 RP2350 问我能不能跑 ONNX Runtime我一般会很直接地回答不能指望它跑常规深度学习模型。RP2350 是 MCU不是应用处理器内存只有几百 KB 级别ONNX Runtime 完整版连加载都费劲更别说跑 CNN。这类设备的“AI 推理”通常走 TFLite Micro、MicroPython 里的简单模型或者干脆只做传感器数据的阈值判断。如果你要在边缘做目标检测建议至少用树莓派 4/5 这类 Linux 设备跑 ONNX Runtime 的 CPU 版本还能勉强实时。选型时先看算力单位和内存资源别被“边缘 AI 推理”的营销词带偏。真正要做低成本端侧推理ncnn 在手机 SoC 上才是更现实的选择。4.5 题外话那串看起来很怪的热词顺着热搜往下翻还会看到“刘文超判断推理笔记”“qbf推理”它们跟本文确实没关系。前者是行测考试里的逻辑判断后者是布尔可满足性问题的变种属于符号推理领域。我提这一嘴是想说搜索“推理”时经常混进这些内容你只要记住在深度学习中“推理”就是模型训练完成后的前向计算和逻辑推理题完全是两码事。5. 性能调优与通用优化清单从能跑到跑得稳、跑得快5.1 用官方性能工具定位瓶颈模型能跑出正确结果后下一个问题必然是“怎么更快”。不要凭感觉优化先用工具量化。TensorRT 的trtexec不仅是转换工具还能给出详尽的性能报告。运行trtexec --loadEngineyolov11.engine --shapesimages:1x3x640x640 --fp16它会输出 average latency、p50/p90/p99 等指标还能按层统计耗时。如果发现某个算子耗时特别高说明这个算子没有完全优化可能触发了回退路径。ONNX Runtime 也有 profiling 能力。Python 中开启so ort.SessionOptions() so.enable_profiling True sess ort.InferenceSession(model.onnx, so, providers[CUDAExecutionProvider])运行几次后会生成一个 profiling 文件能看到每个算子和每个 EP 的实际耗时。这时你就能判断是算子没有分配到 CUDA EP还是模型结构本身有性能瓶颈。5.2 通用优化清单batch、显存、线程、模型输入我整理了一份可以照着做的优化清单不分引擎都适用尽量固定输入 shape。动态 shape 会让 TensorRT 预分配更多显存kernel 选择也可能不是最优。开启 FP16。绝大多数视觉模型在 FP16 下效果不变性能提升接近一倍。合理设置 batch。在线服务追求低延迟用 batch1离线批处理用大 batch先测几组再定。控制 CPU 线程数。ONNX Runtime 在小模型上开 8 线程可能比 4 线程还慢因为线程切换开销超过了并行收益。使用 CUDA Stream 并发。多个模型实例或前后处理与推理重叠时不要让 GPU 空等。减少 Host-Device 拷贝。能直接在 GPU 上做的预处理如cudaMemcpyAsynccudaGraphicsResource就不要绕回 CPU。5.3 推理结果一致性校验如何确认优化没有改坏结果每次转换精度模式或修改预处理后都要做一致性校验。我的标准流程是固定 20 张测试图片先记录 PyTorch/原始模型的输出作为 golden再用 ONNX Runtime FP32 跑一遍记录最大误差再用 TensorRT FP16 跑一遍对比 FP32 的差异。视觉模型的输出是浮点数完全一致几乎不可能但要保证最终检测框差异在可接受范围。通常最大绝对误差在 1e-3 以内是安全的如果出现明显漏检或误检就要检查是不是 INT8 校准、TF32 精度或预处理不一致导致的。TensorRT 中可以通过--noTF32关闭 TF32 来排除一种精度来源。5.4 5080 那类新卡的体验5070 上跑 YOLO 的真实数据热词里有“5070显卡”我刚好最近在一张 RTX 5070 上做过测试。RTX 5070 是 Blackwell 架构和我前几年常用的 Ampere 卡相比TensorRT 的版本要求更高但性能提升也很明显。把 YOLOv12-s 导出成 ONNX再用 TensorRT FP16 构建 engine输入 640x640固定 batch1实测在 5070 上的单帧延迟大约在 1.5ms 到 2ms 之间GPU 利用率不高说明这类小模型的瓶颈更多在 Host 侧的数据搬运和预处理上。如果只跑 ONNX Runtime 的 CUDA EP同样条件下延迟大约在 3ms 到 5ms 之间差距主要来自算子融合和 kernel 效率。这个对比能说明TensorRT 不是玄学是实打实的优化但前提是你已经把前后处理、内存拷贝这些环节做好了。否则 GPU 再快也填不满 CPU 带来的等待间隙。6. 常见问题与排查技巧实录6.1 常见问题速查表现象可能原因解决办法TensorRT 构建 engine 报 “could not find any valid kernel”TensorRT 版本不支持当前 GPU 架构或算子不兼容升级 TensorRT / 使用官方容器查看详细网络日志运行时提示 “Input binding size mismatch”动态 shape 未设置或输入 shape 与 engine 不匹配调用setInputShape或setTensorAddress前确认维度推理结果全零输入预处理错误、GPU 内存未拷贝、输出地址绑定错先用 ONNX Runtime 跑同样的输入排除模型和后处理问题ONNX 加载报 “Unsupported operator”ONNX opset 过高或 ORT 版本太老升级 ORT或在导出时降低 opsetTensorRT engine 拷贝到另一台机器跑不了engine 绑定具体 GPU 架构用目标机器重新构建 engine开启 ORT CUDA EP 后性能没有提升算子未分配 GPU回退到 CPU EP查看 profiling 文件检查 EP 优先级和算子支持FP16 下检测精度明显下降某些算子对 FP16 不敏感或后处理阈值未调整改用 FP32 对比尝试 INT8 校准或检查是否触发回退服务端多线程推理时显存飙升每个线程都创建了独立 context/buffer复用 context用 stream 重叠而不是无限增加线程6.2 独家避坑经验下面这些坑是我自己在项目里踩完总结出来的很多网上教程不会写。第一个不要在生产环境用torch2trt直接加载模型。它会绑定 PyTorch 环境升级 PyTorch 后容易碎更稳妥的方式是导出 ONNX用 trtexec 或 Python API 构建 engine这样依赖面小、可复现性强。第二个动态 shape 的 optShapes 要贴合真实请求。以前我图省事把 maxShapes 设得很大结果每条请求的显存占用都按最大预留8GB 显卡跑几个实例就 OOM。后来改成接近线上分布的 optShapes显存占用降了接近一半。第三个ONNX Runtime 同时开启 CUDA EP 和 TensorRT EP 时要小心算子回退。如果你不知道某个算子是否支持它可能悄悄回退到 CPU性能不升反降。配置完最好用 profiling 确认每一个算子都落在预期的 EP 上。第四个模型后处理中的 NMS 算子在 TensorRT 里最好自己实现或移到引擎外部。TensorRT 对 NMS 的支持版本差异大自定义 NMS 节点很容易构建失败。我现在做检测模型部署时习惯把模型输出到[num_boxes, 6]就直接导出NMS 放在 C 或 Python 侧做代码可控性高也方便调阈值。6.3 出现精度异常时的排查思路我遇到过很多次“训练时好好的部署后结果不对”的问题这类问题基本都是以下四类原因之一预处理不一致、后处理不一致、模型转换损失、推理引擎回退。排查时按顺序来固定一个输入图先用 PyTorch 或原始框架跑出结果作为标准。用 ONNX Runtime 以 FP32 跑同一个输入对比输出。如果这里就差异大多半是 ONNX 导出或预处理问题。用 TensorRT FP32 跑对比 ORT 输出。如果差异大检查网络结构支持和算子回退。再换 FP16/INT8对比 TensorRT FP32 输出。如果这一步差异大才是精度模式引起的损失。最后再检查后处理里的坐标变换、归一化参数、阈值设置尤其是输入缩放和 padding。这套流程能帮你快速圈定问题范围。我见过有人在 TensorRT 上反复折腾精度最后发现是 ONNX 导出时把图像通道顺序搞反了。所以先别急着怀疑框架先把输入输出的差异定位到最基础的一环。最后分享一个我自己的小习惯每次接手一个新模型我都会先写一个“最朴素”的 ONNX Runtime 推理程序不碰任何加速配置先跑出正确结果再往上叠加 TensorRT、FP16、动态 shape 这些优化。这样一旦出了问题我能清楚地知道是优化环节改坏了还是模型本身就跑不通。这个小习惯帮我节省了大量排查时间也让我对每个优化手段的真实收益心里有数。你下次部署模型时也可以试试先“慢”后“快”往往比一上来就追求极致性能更省时间。