第四章:onnx-tensorrt工具链:onnx2trt、trtexec命令行工具详解
好,咱们今天来聊聊onnx-tensorrt工具链。说实话,这个工具链是我日常工作中打交道最多的东西之一。你想想看,模型训练出来是PyTorch或者TensorFlow的格式,但TensorRT不认识啊。中间这个桥梁,就是ONNX和onnx-tensorrt工具链。
我个人习惯把这条链路分成两段:第一段是把训练好的模型转成ONNX,第二段是用工具把ONNX转成TensorRT的引擎。今天重点讲第二段里的两个核心工具——onnx2trt和trtexec。
4.1 onnx2trt:从ONNX到TensorRT的“翻译官”
onnx2trt,全称是ONNX-TensorRT Parser。它的作用说白了,就是把ONNX模型里的算子,一个一个“翻译”成TensorRT能理解的层。
我在项目中遇到过一个问题:同一个ONNX模型,用onnx2trt转出来的引擎,跟用trtexec转出来的,性能差了一截。后来发现是onnx2trt的版本和TensorRT版本没对齐。嗯,这里要注意,onnx2trt的版本必须严格匹配TensorRT的版本,否则会出现莫名其妙的精度问题或者算子丢失。
4.1.1 基本用法
最简单的用法,一行命令搞定:
onnx2trt my_model.onnx -o my_model.trt
但实际项目中,我很少这么用。因为默认的精度是FP32,对于推理来说太奢侈了。我一般会加上精度控制:
onnx2trt my_model.onnx -o my_model_fp16.trt -d 16
这里-d 16表示FP16精度。如果你有INT8的校准数据,还可以用-d 8。不过INT8需要额外提供校准数据集,这个后面章节会细讲。
4.1.2 动态形状支持
这是个大坑。很多同学问我:“为什么我的模型转出来,换个输入尺寸就报错?”
原因很简单——你转的时候没指定动态形状。默认情况下,onnx2trt会把输入形状固定死。如果你需要支持可变输入,必须显式声明:
onnx2trt my_model.onnx -o my_model_dynamic.trt \
--minShapes=input:1x3x224x224 \
--optShapes=input:4x3x224x224 \
--maxShapes=input:8x3x224x224
这里--minShapes、--optShapes、--maxShapes分别指定了最小、最优、最大形状。我建议--optShapes设置成你实际部署时最常用的batch size,这样TensorRT会针对这个尺寸做最优的kernel选择。
4.2 trtexec:性能测试的“瑞士军刀”
trtexec是TensorRT自带的命令行工具,功能非常强大。我个人觉得,它是调试和性能分析的第一选择。你不需要写一行代码,就能完成模型转换、推理测试、性能打点。
4.2.1 模型转换与推理
trtexec可以直接从ONNX转引擎,也可以加载已有的引擎做推理。我最常用的命令是:
trtexec --onnx=my_model.onnx --saveEngine=my_model.trt --fp16
这行命令干了三件事:加载ONNX、转成FP16引擎、保存到文件。如果你只想测试性能,不保存引擎,可以去掉--saveEngine。
4.2.2 性能测试的核心参数
trtexec的性能测试输出非常详细。但我刚开始用的时候,被一堆参数搞晕了。后来我总结出几个最关键的:
| 参数 | 作用 | 我的建议 |
|---|---|---|
--duration=60 |
测试持续时间(秒) | 至少30秒,避免冷启动影响 |
--warmUp=2000 |
预热时间(毫秒) | 2000毫秒足够,GPU需要预热 |
--batch=4 |
批处理大小 | 根据实际业务设置 |
--useSpinWait |
使用自旋等待 | 测试时开启,生产环境别用 |
举个例子,我想测试一个模型在FP16下、batch size为4的吞吐量:
trtexec --onnx=my_model.onnx --fp16 --batch=4 --duration=60 --warmUp=2000
输出里你会看到类似这样的信息:
[I] GPU Compute Time: min = 1.234 ms, max = 1.567 ms, mean = 1.345 ms, median = 1.340 ms
[I] Throughput: 2973.45 qps
这里Throughput就是每秒处理的样本数。我一般看median时间,因为它不受偶尔的抖动影响。
4.2.3 层级别性能分析
这是trtexec最强大的功能之一。当你发现模型性能不达标时,可以用--profilingVerbosity=detailed来查看每一层的耗时:
trtexec --onnx=my_model.onnx --fp16 --profilingVerbosity=detailed
输出会生成一个详细的层时间表。我曾经用这个功能发现,一个简单的卷积层居然占了总时间的40%。后来发现是输入通道数不是4的倍数,导致TensorRT没有选到最优的kernel。调整之后,性能提升了30%。
--dumpLayerInfo把信息导出到文件,然后用Python脚本分析。我经常这么干,效率高很多。
4.3 两个工具的对比与选择
你可能会问:“那我到底该用onnx2trt还是trtexec?”
我的建议是:
- 快速验证和性能测试:用trtexec。它不需要写代码,参数丰富,输出直观。
- 集成到生产流水线:用onnx2trt或者TensorRT的Python/C++ API。因为trtexec是命令行工具,不适合自动化部署。
- 调试特定算子:用trtexec的层级别分析。onnx2trt在这方面比较弱。
我记得有一次,客户要求把模型部署到Jetson上。我一开始用onnx2trt转,结果在Jetson上跑起来特别慢。后来换成trtexec,加了--device=0参数指定GPU,才发现是onnx2trt默认用了CPU模式。嗯,这种坑防不胜防。
4.4 常见问题与避坑指南
最后,分享几个我踩过的坑:
坑1:版本不匹配
我曾经把TensorRT 8.4的onnx2trt用在TensorRT 8.2上,结果转出来的引擎推理结果全是NaN。后来查了半天,发现是算子注册表变了。所以,一定要用配套版本。
坑2:动态形状的optShapes设置不当
有次我把optShapes设成了最大形状,结果推理时小batch size的性能反而下降了。因为TensorRT针对大batch做了优化,小batch的kernel不是最优的。所以,optShapes要设成最常用的尺寸。
坑3:忽略预热
很多同学测试性能时,上来就跑一次推理,然后说“怎么这么慢”。其实GPU有冷启动,第一次推理会包含驱动加载、上下文初始化等开销。所以,一定要预热,至少跑几百次再统计。
好了,关于onnx2trt和trtexec,今天就聊这么多。这两个工具虽然看起来简单,但用好了能帮你省下大量调试时间。下一章,我们会深入TensorRT的Python API,看看如何用代码控制整个推理流程。