TensorRT与ONNX Runtime全流程实战:选型、转换与推理优化

发布时间:2026/9/21 1:57:46
TensorRT与ONNX Runtime全流程实战:选型、转换与推理优化 在深度学习模型从“能跑”到“能上线”这件事上推理引擎的选择和调优几乎决定了最终体验。TensorRT 和 ONNX RuntimeORT是我最近一年多里用得最多的两套推理方案也是社区里讨论热度一直居高不下的组合。如果你正在做模型部署或者刚接触推理性能优化这篇文章应该能帮你省下不少绕弯路的时间。我会从选型思路、环境安装、模型转换、真机实测到问题排查把完整链路拆开讲清楚顺便聊聊我在 5070 这类新显卡上踩过的坑和解决办法。1. 推理引擎选型TensorRT 与 ONNX Runtime 到底该选谁1.1 两者的定位与核心差异先说结论TensorRT 和 ONNX Runtime 不是替代关系而是互补关系。ONNX Runtime 是微软主导的跨平台推理引擎核心优势在于对 ONNX 生态的完整支持和多种硬件后端的统一接口。你同一份 ONNX 模型在 CPU、CUDA、DirectML、甚至树莓派上都能跑只是性能表现差异很大。TensorRT 则是 NVIDIA 专为自家 GPU 打造的闭源推理引擎核心思路是把模型结构做层融合、精度校准、kernel 自动调优把 GPU 的全部算力榨干。从这几个维度对比会更直观对比维度ONNX RuntimeTensorRT易用性极高pip 安装即用中等依赖 CUDA/cuDNN 环境支持的硬件CPU、GPU、NPU、移动端等仅 NVIDIA GPU模型格式ONNX通用性能上限较低但处于“够用”水平高FP16/INT8 下可提升数倍量化能力有限支持动态量化强支持 PTQ/QAT 等高级量化动态形状原生支持较好支持但需要显式配置优化范围我自己的经验是如果项目周期紧、要快速在多个硬件平台验证效果ORT 是第一优先级。它几十行 Python 代码就能跑起来而且现在 ONNX 生态的算子覆盖率已经很高大部分 PyTorch 模型都能顺利导出转换。但如果你要上生产环境、追求极致延迟和吞吐量尤其是做高并发服务那 TensorRT 基本是绕不开的选项。1.2 方案选型背后的决策逻辑说到选型很多朋友喜欢一上来就套 TensorRT但实际项目里真不一定合适。我举一个例子我之前做一个 OCR 服务端的推理优化模型本身不大最开始用 ORT 的 CUDA 执行加速单张图片延迟在 15ms 左右业务方说可以接受但希望再降一降。这时候我把模型用 TensorRT 的 FP16 模式重新转换延迟降到了 7ms 左右整体效果非常明显。但同样的思路放在另一个项目上就不太顺利——那个项目需要动态输入不同长宽比的图像TensorRT 的动态 shape 配置如果没设置好性能反而会退化甚至直接构建失败。所以选型的时候我会先问自己三个问题模型上线后跑在什么硬件上如果明确是 NVIDIA GPU 且型号固定TensorRT 可以全力投入。推理输入的 shape 是否固定如果每张图尺寸都不一样ORT 的动态能力会省心很多。团队对 C 和底层优化的熟悉程度如何TensorRT 的很多高级特性和最佳延迟路径需要 C 配合纯 Python 虽然能用但会有额外开销。最终我的建议是原型和迭代阶段用 ORT攒够数据和经验后再切 TensorRT 做性能优化。这两个工具混用不但不冲突反而能省下大量调试时间。2. 环境准备与安装TensorRT 安装最容易踩坑的环节2.1 ONNX Runtime 的安装与版本选择ORT 的安装相对简单但版本选择上有个小细节容易让人困惑就是 CPU 版和 GPU 版的区分。如果你只跑 CPU 推理直接pip install onnxruntime就行。如果要跑 GPU必须安装onnxruntime-gpu而且要注意 CUDA 版本和 cuDNN 版本对应关系。以一个常见的 CUDA 12.x 环境为例pip install onnxruntime-gpu安装完成后可以通过下面的代码验证 CUDA execution provider 是否被正确识别import onnxruntime as ort print(ort.get_available_providers())如果输出里包含[CUDAExecutionProvider, CPUExecutionProvider]就说明 GPU 加速已经可用了。这里要注意有时候即使你安装了 GPU 版本实际运行时如果 CUDA 和 cuDNN 版本版本不兼容它也会偷偷回退到 CPU导致推理速度没有提升这个很坑。我会习惯性地用上面这段代码和onnxruntime-gpu的版本号、CUDA 版本来对照确认一次。2.2 TensorRT 安装与真机实测新显卡尤其注意TensorRT 的安装是一个典型的“环境地狱”。NVIDIA 官方的 TensorRT 安装包有 deb 和 tar 两种格式我个人的经验是用 deb如果系统支持或者直接走 pip 方式安装官方 TensorRT 包省去手动配置环境变量的麻烦。但这里有一个前提——你需要确认自己的 CUDA 和 cuDNN 版本在官方支持列表里。我用的是 5070 显卡在装 TensorRT 时遇到过一个比较典型的问题网上很多教程还在推荐老版本的 TensorRT 8.x安装后 Python 侧直接报错找不到libnvinfer.so或者运行时报Failed to load library。这是因为新版显卡和新的 CUDA 版本需要对应更新的 TensorRT老版本根本没法正常加载。我现在用下来比较稳的方式是安装与显卡驱动兼容的 CUDA 12.x 以上版本安装配套版本范围的 cuDNN使用pip install tensorrt安装 TensorRT或者从 NVIDIA 官网下载对应版本的 tar 包并解压安装后可以用这个命令验证 TensorRT 是否正常python -c import tensorrt as trt; print(trt.__version__)如果输出版本号比如10.x.x基本就装好了。另外在 5070 等新显卡上我建议把驱动升级到较新的版本不然即使 TensorRT 装好了运行时也可能报显存分配失败或者 kernel 无法启动的错误。这些错误往往会被误判成代码 bug浪费大量排查时间。3. 模型转换与推理实现全流程3.1 从 PyTorch 到 ONNX 的标准导出方式在走 TensorRT 或者 ORT 之前把模型从 PyTorch 转到 ONNX 是必经之路。这一步做得好不好直接影响后面的转换和推理效果。我常用的导出代码模板如下import torch import torch.onnx model load_model() model.eval() dummy_input torch.randn(1, 3, 640, 640) torch.onnx.export( model, dummy_input, model.onnx, opset_version17, input_names[images], output_names[outputs], dynamic_axes{images: {0: batch_size, 2: height, 3: width}} )这里有几个关键参数值得注意。opset_version选择不要太低也不能过高我一般选 17 或 18因为太低的 opset 会导致某些算子无法导出太高的话老版本的 ORT 或 TensorRT 反而不支持。dynamic_axes决定了模型是否支持动态输入如果你的业务场景输入尺寸固定强烈建议不要设置动态输入因为静态 shape 在 TensorRT 里能获得更好的性能优化效果。如果确实需要动态尺寸依然要设置好dynamic_axes但后面在 TensorRT 构建 engine 时要额外配置min、opt、max三组 shape。导出完成后建议先用onnxruntime跑一遍对比原始 PyTorch 模型的输出确认结果一致性。这一步非常重要很多模型在导出时出现算子在 CPU 和 GPU 上的精度差异如果不提前检查后面到了 TensorRT 阶段会更难排查。同时推荐用onnxsim做一次静态图优化把冗余节点清掉pip install onnxsim python -m onnxsim model.onnx model_sim.onnx3.2 ONNX 转 TensorRT 的两种方式trtexec 与 Python APIONNX 转 TensorRT 我一般用两种方式直接用官方自带的可执行工具trtexec或者通过 Python API 写构建脚本。前者适合快速验证模型能否转换、大概能跑多少性能后者适合集成进自动化部署流程比如在服务启动时自动检查并构建 engine。trtexec的使用方式非常直接一条命令就能把 ONNX 转成 TensorRT enginetrtexec --onnxmodel.onnx --saveEnginemodel_fp16.engine --fp16这里加--fp16是启用半精度推理。如果你的模型对精度不敏感比如检测类模型FP16 影响通常很小这一步就能拿到很可观的性能提升。如果模型输入是动态 shape还需要加--minShapes、--optShapes、--maxShapes来指定优化范围否则构建会报错。Python API 方式更灵活但代码会稍多一些。核心逻辑是创建Builder、读取 ONNX 模型、配置网络属性如 FP16、计算最大 batch、然后构建 engineimport tensorrt as trt def build_engine(onnx_path, engine_path, fp16True): 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(onnx_path, rb) as f: parser.parse(f.read()) config builder.create_builder_config() if fp16: config.set_flag(trt.BuilderFlag.FP16) # 动态 shape 配置示例 profile builder.create_optimization_profile() profile.set_shape(images, (1, 3, 640, 640), (1, 3, 640, 640), (4, 3, 640, 640)) config.add_optimization_profile(profile) engine builder.build_serialized_network(network, config) with open(engine_path, wb) as f: f.write(engine) build_engine(model_sim.onnx, model.engine)这段代码需要注意两点。第一builder.build_serialized_network在 TensorRT 10.x 中是推荐做法而不是旧的build_cuda_engine。第二动态 shape 的profile.set_shape传入的三个 shape 分别代表最小、最优和最大输入尺寸这三个值直接影响 TensorRT 的优化质量尤其optShape要接近实际推理时的常见尺寸不然性能会打折扣。3.3 C 端推理实现结合 YOLO 类模型场景如果你想追求极致的推理延迟C 几乎是必经之路。TensorRT 的 Python API 底层虽然也是 C但 Python 解释器的开销和 GIL 限制会导致一定的性能损失和调用延迟。我在做 YOLOv12 的 ONNX 转 TensorRT 推理时用 C 写的推理程序单帧延迟比 Python 版本低了 2ms 左右在高并发场景下这个差距会更明显。C 推理的核心流程可以拆成几步加载 engine、创建 context、分配输入输出显存和内存、执行推理、拿到结果。我用的是enqueueV3接口这是 TensorRT 10 中推荐的执行方式对比老的enqueueV2更简洁// 加载 engine 文件 std::ifstream file(model.engine, std::ios::binary); std::vectorchar data(std::istreambuf_iteratorchar(file), {}); auto runtime nvinfer1::createInferRuntime(logger); auto engine runtime-deserializeCudaEngine(data.data(), data.size()); auto context engine-createExecutionContext(); // 分配显存 void* buffers[2]; cudaMalloc(buffers[0], batch_size * 3 * 640 * 640 * sizeof(float)); cudaMalloc(buffers[1], batch_size * 25200 * sizeof(float)); // 推理 context-setTensorAddress(images, buffers[0]); context-setTensorAddress(outputs, buffers[1]); context-enqueueV3(stream); cudaStreamSynchronize(stream);这里有一个容易被忽视的点TensorRT 10.x 以后enqueueV3依赖setTensorAddress来绑定输入输出而不再使用旧版的setBindingDimensionsenqueueV2组合。如果你参考的是老博客或者老代码很容易在这里踩坑编译通过但运行报错。我当时排查了很久才发现是 API 版本差异导致的。C 侧推理完成后输出通常是一维数组你需要根据自己的模型结构做解析。比如 YOLO 类模型输出是[batch, num_anchors, 5 num_classes]其中前 4 个数是框坐标第 5 个是置信度后面是类别概率。后处理阶段可以直接在 GPU 上用 CUDA kernel 并行做 NMS也可以拉回 CPU 再做轻量级 NMS。如果业务量不大CPU 后处理完全够用但要追求高吞吐CUDA NMS 才是最终方案。4. 性能实测与调优经验4.1 如何做一次可信的推理性能对比在做性能对比时最忌讳直接跑一次就跑出平均值这对 GPU 推理来说极不准确。因为 GPU 有初始化延迟、warmup 机制和显存分配的开销。我第一次做 ORT 和 TensorRT 对比时没有做预热结果 TensorRT 的延迟比 ORT 高出一截让我一度怀疑转换出了问题。后来才发现TensorRT 的 engine 反序列化后第一次推理包括 kernel 加载和显存初始化的过程这部分时间不能被计入稳定状态。一个更可信的测试流程是先做 10 次左右的预热推理然后用 PyTorch 的torch.cuda.synchronize()或 C 的cudaStreamSynchronize强制同步再记录连续 100~1000 次推理的时间计算 P50、P95、P99 延迟和吞吐量。下面是一个简单但有效的 Python 侧性能测试模板import time import numpy as np import onnxruntime as ort sess ort.InferenceSession(model.onnx, providers[CUDAExecutionProvider]) input_data np.random.randn(1, 3, 640, 640).astype(np.float32) # warmup for _ in range(10): sess.run(None, {images: input_data}) latencies [] for _ in range(500): start time.perf_counter() sess.run(None, {images: input_data}) torch.cuda.synchronize() latencies.append((time.perf_counter() - start) * 1000) latencies np.array(latencies) print(fP50: {np.percentile(latencies, 50):.2f} ms) print(fP95: {np.percentile(latencies, 95):.2f} ms) print(fP99: {np.percentile(latencies, 99):.2f} ms)注意这里我用了np.percentile而不是简单np.mean因为在高并发场景 P99 更能反映真实体验——少数极慢请求会让用户感知到卡顿而平均延迟掩盖了这个问题。如果你做线上服务建议把 P99 和 max 延迟一起作为上线标准。4.2 精度损失排查与 FP16/INT8 量化的权衡很多朋友第一次转 TensorRT 后会发现模型输出和 PyTorch 结果对不上或者精度明显下降。这大部分时候不是代码问题而是精度模式开启后的正常现象。FP16 精度下某些对数值范围敏感的层比如大输出值或者连续累积计算多的层会出现一定的舍入误差。对于检测类模型YOLO、SSD、RetinaNet 等FP16 通常不会明显降低 mAP但分割类模型或者对像素值敏感的任务就需要谨慎。如果你发现 FP16 下精度退化明显有几个排查和补救的手段开启 FP16 的同时对关键层指定保持 FP32 精度。TensorRT 里可以用layer.precision trt.float32以及layer.set_output_type()来指定某层不参与半精度计算。这个操作会在一定程度上牺牲性能但能保住精度。用 INT8 量化时必须准备一个代表性校准数据集。TensorRT 会分析每一层输出的数值分布来生成量化缩放因子scale。如果校准数据集和实际数据分布差异很大量化后的精度会崩得非常厉害。我通常用大约 500~1000 张来自真实业务场景的图片作为校准集效果远比随机噪声图片好得多。量化不是灵丹妙药每次都要结合具体模型实测。如果一个模型 FP16 已经可以跑到目标延迟那就没有必要强行上 INT8量化带来的精度风险不值得。4.3 动态 shape 与批处理对性能的影响动态 shape 是个双刃剑。ONNX Runtime 在选择动态输入时非常灵活运行时不需额外配置。但 TensorRT 的 dynamic shape 需要预先定义优化 profile实际输入 shape 如果和optShape偏差过大TensorRT 虽然也能跑但会用保守策略降级性能可能不升反降。我测试过一个检测模型长边固定在 640短边从 320~640 浮动。保持optShape为 (1,3,640,640) 时短边为 320 的推理延迟反而比固定 640 输入还高原因是 kernel 没有按最优尺寸优化。后来我把 optShape 改成实际最常出现的输入尺寸短边 640性能立刻回到正常水平。所以用动态 shape 时一定要结合真实业务数据分布来设置optShape。batch size 的影响同样值得关注。TensorRT 在 batch1 时的优化程度通常很高但 batch8 时吞吐量会有明显提升因为 GPU 的计算并行度得到了更充分的利用。如果你的业务不是单帧请求模式可以考虑在服务端做动态 batching——把多个请求攒起来一次性推理。这个策略在 TensorRT 和 ORT 里都有落地方式ORT 有内置的 batching 策略TensorRT 则需要在应用层自己实现请求队列和 batch 组装。我自己实测过一个场景单帧推理延迟在 5ms 时batch4 的推理延迟是 11ms但吞吐量从 200 FPS 提升到 360 FPS。如果你的服务能容忍一点批处理等待时间这个收益非常可观。5. 常见问题与排查技巧实录5.1 高频问题速查表以下是我在给多个项目做推理优化时遇到的高频问题整理成速查表方便你对照排查问题现象可能原因排查与解决方案构建 engine 报Failed to load libraryTensorRT 版本和 CUDA/cuDNN 不匹配用trtexec --version确认版本重新安装对应版本推理输出全为 0 或 NaN输入数据未正确拷贝到显存检查 setTensorAddress 和 cudaMemcpy 行为FP16/INT8 精度退化明显某些层对精度敏感指定关键层保持 FP32或重新选择校准数据集动态 shape 推理性能退化optShape 设置和实际输入偏差大统计实际数据 shape 分布修改 optShapeORT GPU 推理速度没提升execution provider 未生效打印ort.get_available_providers()确认 CUDA providerYOLO 后处理结果错位模型输出 shape 和解析逻辑不匹配打印输出维度确认 anchor 数量和类别数高并发下延迟抖动明显未启用动态 batching 或者显存不足优化请求队列减小单 batch 或增加显存监控5.2 一次真实排障记录与心得有一次我在部署一个新的检测模型时TensorRT engine 构建成功推理也能跑但输出框位置整体偏移看起来像后处理解析错了。我第一反应是网络结构参数不匹配检查了 anchor 数量和输出维度都没问题。后来一条一条打印 TensorRT 输出和 PyTorch 输出逐元素对比发现前 1000 个元素完全一致1000 之后误差越来越大。最后才发现问题出在 TensorRT 的调优策略上——它会对连续的内存访问做自动排列导致输出数据的顺序和 ONNX 里定义的顺序不完全一致。解决办法有两个一是在转换时禁用某些层重排二是用engine.get_tensor_mode()检查输出张量的布局描述。这类问题在 TensorRT 10.x 中不太常见但遇到一次就会让你对推理引擎的“黑盒”属性有深切体会。从那次以后我建立了一个习惯每次用 TensorRT 转换完模型都会先写一个最小化的精度验证脚本逐元素对比 TensorRT 和 ONNX Runtime 的输出误差。虽然多花一点时间但能为后面的调试省出大量时间。个人心得TensorRT 与 ONNX Runtime 的最佳配合方式写这篇文章的时候我特意回顾了最近做的几个项目最大的感触是不要在选型上过度纠结更不要一步到位追求极致优化。合理的路径是先用 ONNX Runtime 跑通流程确认模型精度和业务效果然后再针对性能瓶颈做针对性的 TensorRT 优化。TensorRT 强在把底层 kernel 调优做到极致ONNX Runtime 强在生态兼容和开发效率两者配合才是性价比最高的方案。最后分享一个我用了很久的小技巧在 C 和 Python 之间切换时建议把 engine 构建的过程独立出来不要每次启动服务都重建 engine。我就是把 ONNX 转 TensorRT 的构建步骤放在 CI/CD 流水线里编译完模型后自动生成 engine 文件然后部署端直接加载序列化好的 engine镜像启动时间能减少一半以上。另外在保存和加载 engine 时注意使用磁盘缓存TensorRT 的反序列化在大部分场景里比重新构建快得多这在高频重启的服务中尤其划算。如果你正在做类似的项目可以从今天聊到的这些步骤入手来验证性能差异。动起手来你会有很多细微的发现这些经验远比任何博客教程更有价值。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询