1. 从 ONNX 到 TensorRT 引擎:为什么 trtexec 是入门首选
如果你手里已经有一个训练好的 ONNX 模型,想快速验证它在 GPU 上的推理表现,最省事的路径不是写 C++ 或 Python 构建代码,而是直接用 TensorRT 自带的命令行工具 trtexec。它能一条命令完成 ONNX 解析、网络优化、引擎序列化,还能顺手跑一遍性能测试,非常适合做推理链路的可行性验证。
TensorRT 的核心思路是把训练框架里的模型,转换成一种针对特定 GPU 架构深度优化过的「引擎」(engine)。这个引擎里包含了算子融合、精度校准、内存复用等优化结果,运行时开销比原始框架小很多。整个流程可以概括为五步:导出模型、选择精度、转换模型、部署模型、验证性能。trtexec 覆盖了中间最关键的转换和验证环节。
这篇内容面向的是想快速跑通 GPU 推理链路的开发者,假设你已经有一个 ONNX 格式的 ResNet 模型,或者知道怎么从 PyTorch 导出。我会把重点放在可复制的 trtexec 命令、输入输出配置骨架,以及精度与吞吐的对比验证上。配套的 AI 工具 Key 和 API 通道管理,我会在第二节说明怎么用 TaoToken 统一处理。
2. TaoToken 前置:统一管理配套 AI 工具的 Key 与 API 通道
做推理部署时,除了 TensorRT 本身,往往还会用到一些辅助工具,比如模型转换脚本、性能分析工具、或者调用云端 API 做结果比对。这些工具如果各自维护一套 Key 和接入地址,配置起来很零散。TaoToken 提供的是一个统一的 Key/API 通道管理入口,把模型对话、编码辅助、API 调用这些能力收敛到一处。
你可以先到官网了解整体能力:https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。如果只是想在转换过程中用模型对话确认一些参数含义,可以直接走模型对话入口;如果是要长期做编码和 Agent 类工作,Coding Plan 更合适。API 层面的接入地址是 https://taotoken.net/api ,Key 的创建和管理在 console 里完成。
注意:TaoToken 在这里的角色是配套 AI 工具的通道管理,不是 TensorRT 的替代品。TensorRT 的引擎构建和推理仍然在本地 GPU 环境完成。
具体操作上,你可以在 console 里创建一个 Key,然后在需要调用 API 的脚本或工具里统一使用这个 Key。接入文档里有不同语言的最小示例,照着填 base_url 和 api_key 即可。这样做的实际好处是,当你同时用多个 AI 辅助工具时,不用每个都去单独申请和轮换 Key。
3. 可复制配置:trtexec 转换 ONNX ResNet 的完整命令
3.1 环境确认与模型准备
先确认 TensorRT 和 CUDA 环境可用。trtexec 通常随 TensorRT 一起安装,在容器里一般直接可用。执行trtexec --help能看到版本和参数列表就说明环境没问题。
模型方面,如果你还没有 ONNX 文件,可以从 PyTorch 导出。下面这段代码把 torchvision 的 ResNet-50 导出为 ONNX,输入固定为 1x3x224x224:
import torch import torchvision.models as models resnet50 = models.resnet50(pretrained=True, progress=False).eval() dummy_input = torch.randn(1, 3, 224, 224) torch.onnx.export( resnet50, dummy_input, "resnet50.onnx", input_names=["input"], output_names=["output"], opset_version=13 )导出后确认一下文件存在,大小通常在 90MB 到 100MB 左右。如果是从 ONNX Model Zoo 下载的 resnet50.tar.gz,解压后路径一般是resnet50/model.onnx。
3.2 基础转换命令
最简转换命令只需要指定输入 ONNX 和输出 engine 路径:
trtexec --onnx=resnet50.onnx --saveEngine=resnet50_fp32.engine这条命令会以 FP32 精度构建引擎,并保存到resnet50_fp32.engine。构建过程会打印解析日志、优化信息和构建耗时。如果看到Engine built successfully或类似输出,说明转换成功。
3.3 精度与形状参数配置
实际部署时通常需要控制精度和输入形状。下面这条命令同时指定 FP16 精度和动态形状范围:
trtexec \ --onnx=resnet50.onnx \ --saveEngine=resnet50_fp16.engine \ --fp16 \ --minShapes=input:1x3x224x224 \ --optShapes=input:8x3x224x224 \ --maxShapes=input:16x3x224x224这里--fp16开启半精度,--minShapes、--optShapes、--maxShapes分别定义输入张量的最小、最优、最大形状。最优形状是 TensorRT 做 kernel 选择时的重点优化目标,建议设成你实际业务中最常见的 batch size。
| 参数 | 作用 | 建议值 |
|---|---|---|
--onnx | 输入 ONNX 模型路径 | 必填 |
--saveEngine | 输出 engine 路径 | 必填 |
--fp16 | 开启 FP16 精度 | 有 Tensor Core 的卡建议开 |
--int8 | 开启 INT8 精度 | 需要校准集 |
--minShapes | 动态形状下限 | 按业务最小 batch |
--optShapes | 优化目标形状 | 按常见 batch |
--maxShapes | 动态形状上限 | 按业务最大 batch |
--workspace | 工作空间大小(MB) | 复杂模型适当调大 |
注意:TensorRT 10.x 之后
--workspace参数的行为有变化,部分版本不再支持直接指定。如果遇到 workspace 相关报错,先确认你的 TensorRT 版本,再决定是否加这个参数。
3.4 构建并直接跑性能测试
如果不想分两步,可以在转换的同时跑一遍性能测试:
trtexec \ --onnx=resnet50.onnx \ --saveEngine=resnet50_fp16.engine \ --fp16 \ --shapes=input:8x3x224x224 \ --iterations=100 \ --warmUp=500 \ --duration=10--iterations指定测试迭代次数,--warmUp是预热时间(毫秒),--duration是正式测试时长(秒)。跑完后会输出吞吐量(Throughput)和延迟(Latency)统计,这是验证推理性能最直接的依据。
4. 验证请求与成功结果:吞吐、延迟与精度对比
4.1 性能数据解读
trtexec 跑完后,输出里最值得关注的是这几项:
[I] Throughput: 1250.3 qps [I] Latency: min = 0.62 ms, max = 0.81 ms, mean = 0.65 ms, median = 0.64 ms [I] GPU Compute Time: min = 0.58 ms, max = 0.76 ms, mean = 0.61 msThroughput 是每秒处理的请求数,Latency 是单次推理耗时。GPU Compute Time 只统计 GPU 上的计算时间,不含数据拷贝。对比 FP32 和 FP16 两份结果,通常能看到 FP16 的吞吐有明显提升,延迟下降,而精度损失在 ResNet-50 这种分类任务上通常很小。
4.2 精度验证骨架
trtexec 本身不做精度比对,但你可以用 Python 加载 engine 跑一遍推理,和 ONNX Runtime 的结果做对比。下面是一个最小验证骨架:
import numpy as np import tensorrt as trt import pycuda.driver as cuda import pycuda.autoinit logger = trt.Logger(trt.Logger.WARNING) with open("resnet50_fp16.engine", "rb") as f: engine = trt.Runtime(logger).deserialize_cuda_engine(f.read()) context = engine.create_execution_context() input_shape = (1, 3, 224, 224) dummy = np.random.randn(*input_shape).astype(np.float32) # 分配设备内存并拷贝输入 d_input = cuda.mem_alloc(dummy.nbytes) cuda.memcpy_htod(d_input, dummy) context.set_tensor_address("input", int(d_input)) # 分配输出内存 output = np.empty((1, 1000), dtype=np.float32) d_output = cuda.mem_alloc(output.nbytes) context.set_tensor_address("output", int(d_output)) # 执行推理 stream = cuda.Stream() context.execute_async_v3(stream_handle=stream.handle) cuda.memcpy_dtoh(output, d_output) stream.synchronize() print("Top-1 class:", np.argmax(output))把同样的输入喂给 ONNX Runtime,对比 top-1 类别和置信度差异。如果 top-1 一致、置信度差异在 1% 以内,基本可以认为 FP16 转换没有引入有意义的精度损失。
4.3 成功结果判断标准
一次成功的转换和验证,应该满足:engine 文件正常生成且大小合理;trtexec 性能测试能跑完并输出吞吐/延迟数据;Python 加载 engine 后推理结果与原始模型一致。三者都通过,说明这条 ONNX 到 TensorRT 的链路是通的。
5. 本篇常见错排查
5.1 找不到 ArgMax 节点实现
报错信息类似Could not find any implementation for node ArgMax_260。这通常出现在带 argmax 输出的分割模型上,原因是 workspace 不够。可以尝试加--workspace=4096重新构建。但要注意,TensorRT 10.5 之后部分版本不再支持这个参数,如果加了报参数错误,就说明你的版本已经不需要手动指定 workspace,直接去掉即可。
5.2 动态形状相关报错
如果报Input shape not set或Cannot infer shape,检查是否在构建时指定了--minShapes、--optShapes、--maxShapes。对于动态形状模型,这三个参数至少要给 optShapes,否则 TensorRT 无法确定优化目标。另外,运行时设置输入形状时,要确保在 min 和 max 范围内。
5.3 精度下降明显
FP16 转换后如果精度掉得厉害,先确认模型里有没有对数值范围敏感的算子,比如 softmax 前的 logits 特别大。可以尝试对特定层保持 FP32,或者改用 INT8 加校准集的方式。ResNet 这类分类模型一般 FP16 损失很小,如果掉点严重,更可能是输入预处理不一致导致的,检查归一化参数是否和训练时一致。
5.4 engine 加载失败
deserialize_cuda_engine返回 None,常见原因是 engine 文件和当前 TensorRT 版本不匹配。engine 是跟版本和 GPU 架构绑定的,换环境后需要重新构建。另外确认文件读取时用的是二进制模式,且文件没有损坏。
5.5 性能不如预期
如果吞吐上不去,先看 batch size 是否太小。GPU 利用率低的时候,适当增大 batch 能明显提升吞吐。其次确认是否开了 FP16 或 INT8,FP32 在支持 Tensor Core 的卡上浪费了算力。最后检查--optShapes是否设成了实际业务中最常见的形状,设错了会导致 kernel 选择不是最优。
6. 配套工具链的 Key 与 API 统一管理
推理链路跑通之后,日常工作中还会涉及模型对话确认参数、编码辅助写部署脚本、以及调用 API 做结果比对。这些环节如果各自维护 Key,时间长了容易乱。TaoToken 的 console 里可以集中创建和管理 Key,API 接入地址统一用 https://taotoken.net/api ,接入文档里有各语言的最小调用示例。
如果你主要是做模型对话类的辅助确认,走模型对话入口;如果是长期写代码和搭 Agent,Coding Plan 更贴合。API Keys 的创建在 console 里完成,创建后复制到你的环境变量或配置文件里即可。这样一套 Key 可以覆盖多个配套工具,省去反复申请和轮换的麻烦。