做模型部署的,几乎没有谁没跟 ONNX 和 TensorRT 打过交道。训练好的模型不管是 PyTorch、Paddle 还是其他框架导出的,通常会先统一成 ONNX 这个中间格式,再落到不同的推理后端上。而如果你手头正好是 NVIDIA 的显卡,想要把推理性能压榨到极限,那 ONNX 转 TensorRT 就是绕不开的一步。
这篇文章把我实际用过、也在多个项目里验证过的三种 ONNX 转 TensorRT 的方式一次性讲清楚:trtexec 命令行、TensorRT Python API 的 OnnxParser、Polygraphy 辅助工具链。最终目标只有一个——在 Python 里把 engine 稳稳跑起来,顺利出结果。适合刚接触 TensorRT 的部署新人,也适合已经会用 trtexec、但想在 Python 里做动态 batch 和 INT8 量化控制的老手。
先说我的结论:这三种方式不是三选一的关系,而是不同阶段、不同场景下的组合。trtexec 适合批量出 engine、快速拿性能基线;Python API 适合把转换逻辑写进服务端脚本,一键完成从 onnx 到 engine 再到推理;Polygraphy 更像 NVIDIA 给你配的“调试放大镜”,转换之外还能做精度比对和性能剖析。下面直接进入正题。
1. 为什么大家都在折腾 ONNX 转 TensorRT
1.1 ONNX 只是“中间格式”,不是终点
ONNX(Open Neural Network Exchange)的设计初衷是做一个“模型界的通用语言”:训练框架导出 ONNX,推理框架读入 ONNX。这个设计很聪明,但说到底,它只是把模型描述成了一张计算图,并没有规定这张图到底怎么被执行。同一个 ONNX,交给 ONNX Runtime 跑,交给 OpenVINO 跑,交给 TensorRT 跑,得到的性能和能力完全不同。
打个比方:ONNX 相当于一份通用的施工图纸,图纸上画好了每一道工序(算子),但真正施工时用哪支施工队、用几台设备、怎么安排流水线,图纸管不着。TensorRT 就是 NVIDIA 平台上那支最了解 GPU 的施工队,它可以根据图纸重新安排施工顺序、合并工序、挑选最顺手的工具,甚至用更省料的工艺(FP16/INT8)把活干完。
所以在实际项目里,训练完 PyTorch/Paddle 模型后,最常见的链路是:导出 ONNX → 验证 ONNX 结果 → 转 TensorRT engine → 在 Python 或 C++ 里推理。我做 YOLO 检测、OCR 识别(像 PP-OCR)、人像抠图(rmbg 这类模型)的部署时,全是这条路,只是中间“转”这一步用的工具不太一样。上面热搜里的“onnx runtime / ncnn”“onnx转rknn int8”这些词,其实也都是围绕同一条主线的不同分支。
1.2 TensorRT 到底帮你做了什么
很多人以为 TensorRT 只是“把模型读进去再存出来”,其实它最少干了四件事:
- 图优化(Layer Fusion):把 Conv + BN + ReLU 这类连续算子合并成一个算子,减少 kernel 启动次数和中间显存读写。一个 100 层的网络,融合之后实际执行的层数可能只剩几十层。
- Kernel 自动调优(Auto-tuning):同一个 Conv,在不同 GPU 上可能有几十种 CUDA kernel 实现(不同的 tile 切分、不同的 shared memory 使用策略),构建引擎时它会跑一遍性能测试,选出当前 GPU 上最快的那一版。
- 数值精度优化:把模型从 FP32 降到 FP16 或 INT8。带宽减半甚至减到四分之一,很多模型的 FP16 推理能比 FP32 快一倍以上,INT8 还能更夸张。
- 显存与执行优化:提前规划好每一层的输入输出 buffer,重复利用显存,减少运行时分配带来的碎片和开销。
这也是为什么 TensorRT 的 engine 文件跟 GPU 架构、CUDA 版本、TensorRT 版本强绑定:它在构建时就把“施工方案”针对目标 GPU 定死了。换个显卡看起来只是换张图,实际连 kernel 都要重新选一遍。
1.3 三种转换方式的选型思路
既然都能转,为什么还要分三种?因为使用场景不一样。
| 方式 | 典型场景 | 上手难度 | 可控性 |
|---|---|---|---|
| trtexec 命令行 | 批量出 engine、CI 自动化出包、快速拿性能基线 | 低 | 中(靠参数) |
| Python OnnxParser | 把转换集成进 Python 服务脚本、需要动态 shape / 量化控制 | 中 | 高 |
| Polygraphy 工具链 | 精度比对、多后端对比、排错、性能剖析 | 中高 | 高(CLI + Python API) |
自己一个人写 demo,怎么快怎么来,一般 trtexec 就够了。但一旦进入工程化,你会发现转换逻辑需要跟训练脚本、评测脚本串起来,这时候 OnnxParser 的 Python 方案更合适。Polygraphy 则是在“转出来的 engine 结果对不对、快没快”这种灵魂拷问面前,帮你把答案量化出来的工具。看完下面三节的完整演示,你基本就知道自己的项目该用哪个了。
2. 动手前的环境准备:版本对齐比什么都重要
2.1 GPU 驱动 / CUDA / cuDNN / TensorRT 版本匹配
转 TensorRT 前先别急着写代码,先把环境对齐,否则后面报的错会让你怀疑人生。TensorRT 的依赖链是:GPU 驱动 → CUDA → cuDNN → TensorRT → Python 环境。每一层都要匹配,尤其要注意两个点。
第一,驱动版本决定你能用哪一版 CUDA。驱动新不代表 CUDA 工具包也新,但驱动不够新时,新版 CUDA 根本起不来。第二,TensorRT 官方文档里会给出每个版本支持的 CUDA/cuDNN 组合,比如 TensorRT 10.x 通常要求某个 CUDA 大版本区间,具体组合要对着官方表看。如果你在新出的 RTX 50 系显卡上做部署,这类新卡往往要求较新的 CUDA 版本(比如 12.8 及以上)和对应的最新 TensorRT 版本,直接用 TensorRT 10.x 会比较稳。
我自己踩过最狠的一次坑:在一台机器上构建好的 engine,拷到另一台同型号 GPU 的机器上,反序列化直接失败。排查半天才发现是两台机器的驱动、CUDA 版本不一致。所以开工前先跑一遍版本检查:
nvidia-smi nvcc --version python -c "import tensorrt as trt; print(trt.__version__)"注意 tensorrt 的 Python 包版本要跟系统里实际安装的 TensorRT 库一致,否则会出现libnvinfer.so找不到或者版本对不上的报错。
2.2 Python 环境与 tensorrt 包安装
环境这块,正常情况下以下命令能解决大部分问题:
pip install tensorrt pip install onnx pip install pycuda如果网络慢,就把 pip 源切成你习惯的国内镜像,能省不少时间。这里有三点要特别说明:
- tensorrt 这个包在不同版本下的安装方式不太一样。有些版本直接
pip install tensorrt拿到的就是完整运行库外加 Python 包;有些版本需要先从官方下载适配包,解压后再把 Python 包装入虚拟环境。具体以你安装版本的官方说明为准。 - pycuda 是让 Python 能完成 GPU 显存分配和数据拷贝的桥梁。TensorRT 只负责推理,数据从 CPU 到 GPU、再从 GPU 取回来这步,需要 PyCUDA 或者 Numba 之类的 CUDA 工具配合。
- Windows 上做 TensorRT 部署,pip 支持和运行时行为都不如 Linux 顺手,很多坑都出在 DLL 路径和 CMake 依赖上。能用 Linux 容器做部署验证,会省很多事。
装完之后顺手验证:
import tensorrt as trt import onnx import pycuda.autoinit print("tensorrt", trt.__version__) print("onnx", onnx.__version__)三行都能过,环境基本就 OK 了。
2.3 模型导出 ONNX 时的两个坑:opset 和动态维度
环境准备好之后,手头的模型得先导出成 ONNX。这一步虽然看起来跟 TensorRT 无关,但实际决定后面转换顺不顺,有两个坑最典型。
第一个是算子集版本(opset)。PyTorch 导出时如果不指定 opset,会自动用当前版本支持的最高值;但 TensorRT 的 OnnxParser 对每个算子都有个支持范围,opset 太高,它可能不认某些新算子;opset 太低,有些算子组合又没法表达。我的经验是导出时显式指定 opset,比如 17 或 19,在 TensorRT 10.x 上兼容性都不错。
import torch torch.onnx.export( model, dummy_input, "model.onnx", input_names=["input"], output_names=["output"], dynamic_axes={ "input": {0: "batch", 2: "height", 3: "width"}, "output": {0: "batch"} }, opset_version=17, )第二个是动态维度。大多数部署场景不会接受固定尺寸,OCR、检测、抠图这类任务的输入尺寸更是常变。所以在导出时就把dynamic_axes声明好,后面转 TensorRT 时才能构建动态 shape 的 engine。如果导出时把维度写死了,后面想改成动态就得重新导出,来回折腾很浪费时间。像 YOLO 类模型的输入经常是 640x640,但 batch 数可能从 1 到 16 波动,这种就非常适合一开始就把 batch 维标成动态。
3. 方式一:trtexec 离线转换,Python 加载 engine 推理
3.1 trtexec 命令生成 engine
trtexec 是 TensorRT 自带的可执行文件,装完 TensorRT 以后在安装目录的 bin 下面能找到。它的作用有两个:一是把 ONNX 转成 engine,二是对生成好的 engine 做性能测试。最基础的转换命令是:
trtexec --onnx=model.onnx --saveEngine=model.engine不带精度参数时,默认构建 FP32 engine。想用 FP16 就加一个--fp16:
trtexec --onnx=model.onnx --saveEngine=model_fp16.engine --fp16如果模型输入是动态 shape(导出时声明过 dynamic_axes),那必须显式给出范围,否则 trtexec 会直接报错,或者只按某个默认尺寸构建:
trtexec --onnx=model.onnx \ --minShapes=input:1x3x640x640 \ --optShapes=input:8x3x640x640 \ --maxShapes=input:16x3x640x640 \ --saveEngine=model_dynamic.engine \ --fp16这里input必须跟 ONNX 里的输入名完全一致,尺寸格式是 NCHW,维度之间用x分隔。trtexec 跑完以后还会自动做一组性能测试,打印平均延迟、吞吐量等指标,这些数据在验证加速效果时直接能用,等于白送一个 benchmark。
3.2 常用参数说明:别只知道 --fp16
我长期用 trtexec 之后,真正高频的参数其实就下面几个:
| 参数 | 作用 | 补充说明 |
|---|---|---|
--onnx | 指定输入 ONNX 文件 | 路径里别带中文和空格 |
--saveEngine | 保存生成的 engine | 不指定的话只做性能测试不落盘 |
--fp16/--int8 | 开启低精度构建 | int8 需要校准数据或校准缓存 |
--minShapes/--optShapes/--maxShapes | 动态 shape 范围 | 三者缺一不可 |
--memPoolSize | 设置显存池大小 | TRT 8.5 以上写法;老版本是--maxWorkspaceSize |
--verbose | 输出详细日志 | 报错时用来定位具体网络层 |
--buildOnly | 只构建不运行性能测试 | 适合批量出 engine 时用 |
--plugins | 加载自定义插件 so 文件 | 用到自定义算子的模型必加 |
这里提醒一句:--int8不是帮你自动量化,而是让构建器以 INT8 精度为目标去选择 kernel,同时要求你提供校准数据。如果模型没有适合 INT8 的层,或者校准数据没准备好,构建会失败。新手建议先跑通 FP16,再碰 INT8。
3.3 Python 端加载 engine 并完成推理
trtexec 只负责把 engine 造出来,真正跑到业务里还是在 Python 中加载。Python 加载 engine 的流程比较固定,核心是四步:反序列化 engine、创建执行上下文、分配显存并拷贝输入、执行并取回输出。
import tensorrt as trt import numpy as np import pycuda.driver as cuda import pycuda.autoinit TRT_LOGGER = trt.Logger(trt.Logger.WARNING) def load_engine(engine_path): with open(engine_path, "rb") as f: runtime = trt.Runtime(TRT_LOGGER) return runtime.deserialize_cuda_engine(f.read()) def infer_engine(engine, feed_dict): context = engine.create_execution_context() names = list(engine) bindings = [] host_buffers = {} for name in names: mode = engine.get_tensor_mode(name) if mode == trt.TensorIOMode.INPUT: data = feed_dict[name].astype(np.float32) context.set_input_shape(name, data.shape) device_ptr = cuda.mem_alloc(data.nbytes) cuda.memcpy_htod(device_ptr, data.ravel()) bindings.append(int(device_ptr)) else: shape = tuple(context.get_tensor_shape(name)) out = np.empty(shape, dtype=np.float32) device_ptr = cuda.mem_alloc(out.nbytes) bindings.append(int(device_ptr)) host_buffers[name] = (device_ptr, out) context.execute_v2(bindings) results = {} for name, (device_ptr, out) in host_buffers.items(): cuda.memcpy_dtoh(out, device_ptr) results[name] = out return results这段代码有两个关键细节。第一,bindings列表的顺序必须跟list(engine)的 binding 顺序一致,我这里是按 engine 迭代顺序 push 的,所以不会错。第二,动态 shape 的输入,必须在执行前调用context.set_input_shape,而且输出 buffer 的大小也要在设置 shape 之后再取,否则拿到的可能是 0 或默认值。如果你用的是 TensorRT 10 的较新版本,官方新接口是execute_async_v3(stream, tensor_addresses),可以直接传{输入名: 指针}字典,不用再手工维护 binding 顺序;老接口execute_v2也依然能用,不用急着改。
4. 方式二:纯 Python 用 OnnxParser 直接构建引擎
4.1 核心 API 链路与每个组件的作用
第二种方式不经过 trtexec,而是在 Python 里直接构建 engine。核心链路是:Builder → Network → OnnxParser → BuilderConfig → serialized Engine。我解释一下每一步的含义,理解了这条链,后面调参就有方向了。
- Builder:引擎构建器的总指挥,负责统筹整个构建过程。
- Network:TensorRT 内部的网络定义,相当于一张还没优化的计算图。
- OnnxParser:把 ONNX 文件解析进来,转换成 TensorRT 的 Network 结构。
- BuilderConfig:构建配置,包括精度、显存池、动态 shape 范围等。
- 最后 Builder 根据 Network 和 Config 产出序列化后的 engine 字节流。
这条链路的优势是转换过程完全可控。你可以在构建前检查网络的输入输出名,可以针对某个输入单独配置优化 profile,可以在代码里根据模型类别决定开不开 FP16,甚至可以直接修改网络层。这是 trtexec 给不了的灵活度。
4.2 完整代码:onnx 到 engine 一条龙
下面这段代码我基本是复制到各个项目里当公共函数用的,可以直接抄:
import tensorrt as trt TRT_LOGGER = trt.Logger(trt.Logger.WARNING) EXPLICIT_BATCH = 1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH) def build_engine_from_onnx(onnx_path, engine_path, fp16=False, workspace_size=1 << 30, dynamic_shapes=None): builder = trt.Builder(TRT_LOGGER) network = builder.create_network(EXPLICIT_BATCH) parser = trt.OnnxParser(network, TRT_LOGGER) with open(onnx_path, "rb") as f: if not parser.parse(f.read()): print("解析 ONNX 失败,错误信息:") for i in range(parser.num_errors): print(parser.get_error(i)) return False config = builder.create_builder_config() config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, workspace_size) if fp16 and builder.platform_has_fast_fp16: config.set_flag(trt.BuilderFlag.FP16) if dynamic_shapes: profile = builder.create_optimization_profile() for name, (min_shape, opt_shape, max_shape) in dynamic_shapes.items(): profile.set_shape(name, min_shape, opt_shape, max_shape) config.add_optimization_profile(profile) serialized = builder.build_serialized_network(network, config) if serialized is None: print("构建 engine 失败") return False with open(engine_path, "wb") as f: f.write(serialized) print("engine 已保存到", engine_path) return True调用示例:
build_engine_from_onnx( "yolo12.onnx", "yolo12_fp16.engine", fp16=True, dynamic_shapes={ "input": ([1, 3, 640, 640], [8, 3, 640, 640], [16, 3, 640, 640]) } )有几个点说明一下。EXPLICIT_BATCH这个 flag 在 TRT 8/9/10 里都兼容,导出 ONNX 时如果用 opset 16+,默认就是显式 batch 模式,这里保持一致。builder.platform_has_fast_fp16判断当前 GPU 是否支持 FP16 加速,不支持的话就算定了 FP16 flag,构建时也可能失败或退化为 FP32,加这层判断在边缘设备上更安全。TRT 8.x 里旧接口是builder.build_engine(network, config),返回trt.HostMemory,跟上面代码略有差异;TRT 10.x 推荐用build_serialized_network,我这套代码是基于 TRT 10.x 写的。
4.3 动态 Shape 的配置细节
动态 shape 是 Python 方式最值得掌握的部分。优化 profile 里有三个值:min、opt、max。它们的含义是:min 是允许的最小输入尺寸,运行时不能小于它;opt 是优化目标尺寸,TensorRT 的 kernel 选择会优先照顾这个尺寸;max 是允许的最大输入尺寸,运行时不能大于它。
选值的时候有个关键经验:opt 一定要设成线上最常用的尺寸,而不是取 min 和 max 的中间值。比如检测服务平均每张请求都是 640x640,那 opt 就设成 8x3x640x640(假设 batch 8),哪怕 min 是 1、max 是 16。因为 TensorRT 的性能优化以 opt 为基准,opt 设错了,engine 在真实业务尺寸上可能不是最优实现,跑出来的速度还不如不换 TRT。
运行时动态 shape 的用法跟第 3 节一样,必须在执行前调用context.set_input_shape。如果网络有多个动态输入,比如两路视频流的模型,每个输入都要单独设置 shape。用 profile 构建时,还可以通过set_optimization_profile_async在运行时切换 profile,不过这个场景比较少见,一般业务一个 profile 就够。
5. 方式三:Polygraphy 和它的兄弟们
5.1 用 Polygraphy 转 engine 并做精度比对
Polygraphy 是 NVIDIA 官方维护的一个工具包,定位是“帮助开发者解析、比较、调试和转换模型”。它包装了 TensorRT、ONNX Runtime 等多个后端,所以既能转换,也能做精度比对和性能压测。安装很简单:
pip install polygraphy转换命令跟 trtexec 非常像:
polygraphy convert model.onnx --output model.engine --trt --fp16动态 shape 加三个开关:
polygraphy convert model.onnx --output model.engine --trt --fp16 \ --trt-min-shapes input:[1,3,640,640] \ --trt-opt-shapes input:[8,3,640,640] \ --trt-max-shapes input:[16,3,640,640]更实用的是它的run命令,可以直接拿同一份 ONNX 在两个后端上跑,对比输出差异。比如我想验证 FP16 engine 和原始模型在数值上差多少,一条命令就能出结果:
polygraphy run model.onnx --trt --onnxrt --fp16 \ --trt-min-shapes input:[1,3,640,640] \ --trt-opt-shapes input:[8,3,640,640] \ --trt-max-shapes input:[16,3,640,640] \ --atol 1e-3 --rtol 1e-3--onnxrt表示用 ONNX Runtime 作为参照后端,--atol和--rtol是允许的绝对误差和相对误差。输出里会明确告诉你哪里 mismatch、数值差多少。我在排查 FP16 精度下降问题时,全靠这一招定位到出问题的层。跟 Polygraphy 经常搭配的还有 onnx-graphsurgeon,它可以对 ONNX 图做精细裁剪和修改,比如把某些 TensorRT 不支持的子图替换成自定义算子,属于进阶玩法。
5.2 顺带聊聊 torch2trt 和 ORT-TRT
除了这三种方式,还有两个方案经常被拿来比较,我觉得值得说清楚。
- torch2trt:第三方开源项目,模型还在 PyTorch 里时可以直接转成 TensorRT engine,省掉导出 ONNX 的环节。对快速验证很友好,但项目维护节奏不稳定,实现跟官方 TensorRT 的版本绑定比较紧,版本一升级就可能出问题,生产环境我基本不用。
- ONNX Runtime TensorRT EP:严格来说不是“转换”,而是在 ONNX Runtime 推理时把计算图交给 TensorRT 执行。好处是不用管 engine 文件、不用改推理代码,适合快速引入;坏处是图切分和调度有额外开销,控制力也不如直接使用 TensorRT,性能上限通常比显式构建 engine 低一些。用起来就是指定一下 provider:
import onnxruntime as ort sess = ort.InferenceSession( "model.onnx", providers=["TensorrtExecutionProvider", "CUDAExecutionProvider"] )我的实际分工是:日常开发用 Polygraphy 做验证和精度比对;线上服务用 trtexec 或 Python 方式构建好的 engine;ORT-TRT 只出现在一些不太在意极致性能、只想快速上线 ONNX 模型的场景里。
5.3 三种方式的优缺点对比
表格说话:
| 对比维度 | trtexec | Python OnnxParser | Polygraphy |
|---|---|---|---|
| 转换入口 | 命令行一行 | Python 代码 | 命令行 / API |
| 动态 shape | 命令行参数 | Python 配置 | 命令行参数 |
| 集成进业务脚本 | 需要 subprocess 调命令 | 直接 import | 可调用 API |
| 精度比对 | 不支持 | 要自己写 | 内置 run 对比 |
| 性能剖析 | 内置测试 | 要自己打点 | 内置 |
| 插件支持 | 参数指定 so | Python 加载 | 支持 |
| 新手上手 | 最容易 | 中等 | 中等偏上 |
如果你只打算记住一句话:临时验证用 trtexec,工程化封装用 Python OnnxParser,精度出问题或要做多后端验证时上 Polygraphy。
6. 常见问题与排查技巧实录
6.1 高频报错与解决办法速查表
| 报错或现象 | 常见原因 | 解决办法 |
|---|---|---|
Unsupported operator/ 解析失败 | ONNX opset 过高或算子不在支持范围 | 降低 opset 重新导出;检查是否有 TRT 不支持的算子 |
构建失败,日志提示No implementation | 该层在当前精度 / 架构下没有可用 kernel | 关闭该层低精度;尝试只开 FP16 不开 INT8;升级 TRT |
deserialize_cuda_engine报错 | engine 与当前 GPU / CUDA / TRT 版本不匹配 | 在目标机器上重新构建 |
set_input_shape失败 | 输入 shape 超出 profile 范围 | 确认 min/opt/max 覆盖实际使用尺寸 |
| 输出 buffer 尺寸为 0 | 动态 shape 下未先 set_input_shape 就取 shape | 先设 shape 再分配输出 buffer |
| 运行时报显存不足 | workspace 过大或 batch 过大 | 调小--memPoolSize/workspace_size;减小 batch |
| INT8 构建报校准相关错误 | 没提供校准数据 | 准备校准集或提供已有 calibration cache |
| 加载插件 so 失败 | 插件路径不对或与 TRT 版本不符 | 用--plugins指定正确路径,确保插件版本匹配 |
6.2 精度不对、性能没提上去怎么办
两个最常被问的问题,统一说下排查思路。
第一个是精度问题。FP16 后结果跟原始模型差很多,大多数时候不是框架的错,而是模型对低精度太敏感。先别急着骂 TensorRT,用 Polygraphy 的 run 对比一下 ONNX Runtime 和 TensorRT 的输出,找出差异最大的输出节点,再用--layer-precision之类的参数把敏感层强制保留 FP32。如果连 ONNX Runtime 和 TensorRT 的 FP32 结果都对不上,那要先怀疑 ONNX 导出时某些算子语义变了。
第二个是性能没提上去。我见过很多次,FP16 跑出来的速度跟 FP32 几乎一样,甚至更慢。排查方向先看输入尺寸是不是每次都在变,动态 shape 下 engine 可能每次都选不是为当前尺寸调优的 kernel;再看是不是把第一个 batch 的冷启动延迟算进了耗时,实际部署要加 warmup,跑几十次再统计;最后确认是否真的走了 GPU,有的人装的环境里 TensorRT 一直跑在 CPU 回退路径上,这种日志里通常会有 warning,很容易漏看。
6.3 我给新手的几条实操建议
最后给几条我摔过跟头换来的建议:
- 永远从 FP32 开始。先用最简单的方式把流程跑通,确认能出正确结果,再逐步开 FP16、INT8。每步都保留 baseline,出问题才有对比。
- 每次构建 engine 都把版本信息打出来。日志里记下 TensorRT 版本、CUDA 版本、GPU 型号、输入 shape 范围,排查反序列化失败时会省下大量时间。
- 把转换函数封装成公共模块。像第 4 节那样的
build_engine_from_onnx,固定好输入参数格式,所有模型共用,减少复制粘贴带来的低级错误。 - 动态 shape 的 opt 值就设线上真实尺寸,别偷懒随便填。
- 别为了精度问题直接放弃 FP16,先定位敏感层,再逐层回退 FP32,往往性能和精度都能兼顾。
我在实际项目里最深的体会是:TensorRT 的学习曲线不在 API,而在“版本和硬件”这两条暗线上。API 五分钟就能学会,但版本不匹配、GPU 架构不同带来的问题,能让人排查几天。所以我现在每到一个新环境,第一件事就是统一版本清单,再谈转换。这篇文章里写的三种方式,我都分别上过生产:trtexec 在 CI 里批量出包,Python OnnxParser 在服务端做动态构建,Polygraphy 在每次改精度配置后做回归校验,分工明确,互不冲突。
最后再分享一个小技巧:把构建好的 engine 连同输入输出信息(名称、类型、shape)一起写个小清单存下来,下次写推理代码时直接查,不用再去翻原始模型。很多“代码写一半发现 tensor name 忘了”的尴尬都能避免。如果你也在折腾 ONNX 转 TensorRT,希望这篇能让你少踩几个坑。