先说明一句:这篇文章会以企业技术尽调的口吻来写,所有分析都基于源码实证,不掺水分。torch2trt 这个项目在 PyTorch 转 TensorRT 的生态里名气很大,但真正打开源码逐行读过的团队其实不多。很多同学只是 pip install 之后跑通了 demo,就以为万事大吉,等到生产环境遇到算子不兼容、动态 shape 崩溃、精度对不上的问题,才回头翻源码,这时候成本已经很高了。我这篇就把 torch2trt 从外到内拆开,讲清楚它到底是怎么工作的、哪些地方能改、哪些地方是坑,以及企业选型时该怎么判断它是否适合你们的落地场景。
1. 为什么企业级选型必须先读源码:torch2trt 的定位与核心价值
先说一个很多技术管理者容易忽略的事实:PyTorch 模型要部署到 GPU 上做推理,TensorRT 几乎是绕不开的性能天花板。NVIDIA 官方对 TensorRT 的定位是"高性能深度学习推理优化器",它做的事情包括层融合、精度校准、内核自动调优、显存复用等,这些优化带来的推理加速往往是 1.5 到 4 倍起步,对 Transformer、CNN 这类结构稳定的模型尤其明显。但 TensorRT 的输入格式不是 PyTorch 的 Module,也不是简单的 ONNX 文件,它需要的是序列化后的推理引擎(engine),而构建 engine 的过程恰恰是整个部署链路里最折腾的一环。
torch2trt 就是在这个环节里出现的一个桥接工具。它做的事情简单粗暴:把一个训练好的nn.Module直接转成 TensorRT engine,全程不需要经过 ONNX 中间表示。这个设计思路在 2019 年刚开源时非常独特,因为当时主流的转换路径是 PyTorch -> ONNX -> TensorRT,中间任何一步出问题都很难排查。torch2trt 绕开了 ONNX,直接通过 PyTorch 的 JIT tracing 拿到计算图,再逐个算子映射到 TensorRT 的 layer API 上。
但作为一个企业级选型对象,torch2trt 的定位其实很微妙。它不是 NVIDIA 官方主推的转换工具——官方后来主推的是 torch_tensorrt 和 ONNX-TensorRT 这条路线。torch2trt 是 NVIDIA 工程师 Niels 以个人名义开源的工具,后来被收进 NVIDIA 的 GitHub 组织下维护。这意味着它的维护节奏、社区支持力度和官方工具相比是有差距的,但反过来看,它又是一个"小而锐"的解决方案,核心代码量不大、结构清晰、二次开发门槛低,很多公司的内部推理框架里至今还留着一套基于 torch2trt 改出来的转换模块。
从源码实证的角度来看,torch2trt 的核心价值可以归纳成三点:
- 转换路径短,从 PyTorch Module 直接到 TRT engine,省掉 ONNX 中间层带来的 debug 成本。
- 插件机制开放,遇到 TensorRT 原生不支持的算子,可以用 Python 自己写 converter 注册进去,扩展性比 ONNX 路线强。
- 动静结合的设计,转换后的
TRTModule既持有 TensorRT engine,又保留了原 PyTorch 模型的引用,方便做精度对比和问题回溯。
这篇文章后续的每一个判断,都是基于对 torch2trt 源码的实际阅读和跑测得出来的结论。我会先把源码结构拆开,再跟踪一次完整的转换过程,然后讲插件机制和动态 shape 的实现细节,最后落到企业落地时的边界条件、性能验证方法和选型对比上。如果你正打算在团队内部引入 torch2trt,或者已经在用它但遇到了一些说不清的问题,这篇应该能给你一个全局视角。
2. 源码底盘拆解:torch2trt 的目录结构与模块职责
读源码的第一步永远是看目录结构。torch2trt 的仓库布局非常紧凑,不像 PyTorch 那样有几百个模块,它核心的东西只有十几个文件。我用一个表格把主要文件和目录的职责列出来,然后再逐个展开讲。
| 路径 | 职责 |
|---|---|
torch2trt/__init__.py | 对外 API 入口,导出torch2trt转换函数和TRTModule类 |
torch2trt/core.py | 核心转换逻辑,负责构建 TensorRT 网络、执行 tracing、管理命名空间 |
torch2trt/module.py | 定义TRTModule,转换后的模型封装,包含__call__、forward、half、int8等接口 |
torch2trt/converters.py | 内置的算子转换器注册和实现,这是最大的一个文件 |
torch2trt/plugin.py | 插件基类和一批内置插件实现,用于支持 TensorRT 原生不支持的算子 |
torch2trt/calibrator.py | INT8 量化时的校准器实现(可选),用于计算动态范围 |
torch2trt/arg_mapping.py | 参数映射工具,处理 PyTorch 参数和 TensorRT 参数之间的转换 |
torch2trt/errors.py | 自定义异常类型 |
从企业尽调的角度看,这个结构透露出的第一个信息是:torch2trt 的代码体量非常小,核心逻辑集中在core.py和converters.py里。小体量本身是好事,意味着你可以在几天内通读全部代码,有充分能力做二次开发。但反过来也意味着,它的内置算子覆盖范围必然是有限的——程序都不到一万行,不可能覆盖 PyTorch 的几百个算子。所以它一定依赖两种机制来弥补:一是插件机制,二是"出问题时自己动手写 converter"的能力。这也是为什么我建议任何准备用 torch2trt 的团队,至少要安排一个能读懂converters.py的人。
进一步看__init__.py,里面导出的核心 API 是torch2trt和TRTModule。源码里的转换函数签名大致是:
def torch2trt( module, x, fp16_mode=False, max_batch_size=1, max_workspace_size=1 << 30, strict_type_constraints=False, keep_batch_dim=False, ... ):这个签名本身就是一份很好的需求文档。fp16_mode决定是否做半精度推理,max_workspace_size控制 TensorRT 构建引擎时的显存预算,keep_batch_dim处理 batch 维度是否保留的问题——这几个参数只要有一个设置不当,后面推理就会踩坑。我后面会专门讲这些参数在生产环境里应该怎么设置。
再打开core.py,核心是torch2trt函数实现了一系列步骤:先做 JIT trace 拿到torch.jit.ScriptModule,然后遍历图里的节点,为每个节点查找对应的 converter,最后用 TensorRT 的Builder构建 engine。整个过程的关键数据流是三种对象:TensorWrapper、ModuleWrapper和ConverterRegistry。
TensorWrapper:包装了 PyTorch tensor 和对应的 TensorRTITensor,是转换过程中的基本数据单元。ModuleWrapper:包装原始 PyTorch module 和它对应的 TensorRT 层引用。ConverterRegistry:一个全局注册表,负责维护 PyTorch 算子到 converter 函数的映射关系。
从实现细节来看,TensorWrapper内部保存了trt_tensor引用,同时记录了shape和dtype这些元信息。为什么要专门包一层?因为 TensorRT 的ITensor是 C++ 对象,不具备 Python 层面的灵活性,trace 过程中有些算子需要动态调整维度或数据类型,直接修改ITensor不方便,所以在 Python 层加了一个 wrapper 做状态管理。转换完成后,这些 wrapper 会被丢弃,只有真正的ITensor会进入 TensorRT builder。
register_converter是 torch2trt 插件体系的核心入口。它的实现大致是一个装饰器工厂,根据算子类型(torch.nn.Module子类或torch.autograd.Function)和名字注册对应的转换函数。源码里大量出现类似@tensorrt_converter("torch.nn.ReLU")之类的装饰器,这就是注册机制的实际形态。理解了这一点,就理解了 torch2trt 的扩展模式:所有支持的操作本质上都是"名字到函数"的映射,你只要给一个新算子起一个合法的 PyTorch 名字,写一个接收ctx和args的处理函数,就能无缝扩展。
还有一个容易忽略但很关键的文件是arg_mapping.py。PyTorch 的算子参数和 TensorRT 的层参数有大量细微差异,比如 padding 模式、stride 顺序、维度排布,arg_mapping.py就是做这些转换的。比如 PyTorch 的nn.Conv2d用(N, C, H, W)布局,TensorRT 内部虽然是 NCHW,但在某些插件层需要特殊处理数据排列,这些细节都在参数映射里解决。对于二次开发的人来说,这个文件是你写新 converter 时要重点参考的模板。
3. 一次转换的完整旅程:从 PyTorch Module 到 TensorRT engine 的内部流程
这一节我会沿着一次真实的转换调用来跟踪源码的执行路径,看看一个nn.Module是怎么一步步变成 TensorRT engine 的。
首先要明确一点:torch2trt 的输入不只是一个nn.Module,还需要一个示例输入x。这个x有两个作用:一是确定模型的输入 shape,让 TensorRT 知道该为多大的 tensor 分配资源;二是触发 PyTorch 的 JIT tracing,让程序自动记录下计算图。从源码里看,torch2trt的第一步是:
module = module.eval().cuda() script_module = torch.jit.trace(module, x)` 或类似操作这里有几个细节值得注意。module.eval()很重要,因为 evalaution 模式和 training 模式的计算图是不同的——比如 dropout 在 training 模式下会动态生成随机掩码,这会污染 tracing 结果;BatchNorm 在 training 模式下会更新 running_mean/running_var,这也会让计算图变得不稳定。所以只用 eval 模式做转换是硬性要求。另外module.cuda()是因为 TensorRT 只能在 CUDA 设备上工作,输入 tensor 也必须是 CUDA tensor。
拿到script_module之后,torch2trt 会遍历计算图中的所有节点。PyTorch JIT 的计算图由节点组成,每个节点类型可能是prim::GetAttr、prim::CallMethod、aten::relu、aten::conv2d等等。torch2trt 的遍历逻辑大致是:
- 遍历图节点,遇到
prim::GetAttr时从原始 module 中取出对应属性,用ModuleWrapper或直接参数张量记录。 - 遇到可转换的算子节点时,查找
ConverterRegistry里是否有对应的 converter。 - 如果 converter 存在,调用它,传入
ctx(包含当前网络、输入 wrapper)和参数,在 TensorRT 网络中添加新的层。 - 如果 converter 不存在,进入 fallback 逻辑:要么抛异常,要么用 PyTorch 原算子执行并尝试把结果作为常量嵌入。
在这个过程中,register_converter的查找逻辑依赖的是 PyTorch 算子的名字。以torch.nn.ReLU为例,源码里大概是:
@tensorrt_converter("torch.nn.ReLU") def convert_relu(ctx, module, args): input_trt = args[0].trt # 获取输入 TensorRT ITensor layer = ctx.network.add_activation(input_trt, trt.ActivationType.RELU) output_trt = layer.get_output(0) output = TensorWrapper(output_trt, shape=args[0].shape) return output这个函数的写法就是所有 converter 的通用模板:从args中取出输入 tensor 的 TRT 对象,调用ctx.network.add_*创建 TensorRT 层,把输出包装成TensorWrapper返回。ctx是转换上下文,保存了当前 TensorRT 网络、命名空间和辅助信息。
注意这里的ctx.network.add_activation调用的是 TensorRT Python API,底层是 C++ 绑定的。torch2trt 利用的是 TensorRT Python API 的层构建能力,而不是 C++ 手写插件,这大大降低了二次开发门槛。
转换完所有节点后,torch2trt 会标记输出。它默认把计算图的最后一个输出作为 TensorRT 网络的输出,同时根据keep_batch_dim参数决定 batch 维度是动态的还是固定的。标记完输出后,调用 TensorRT builder 生成 engine。
TRTModule是转换后的封装对象,它的核心结构可以简化为:
class TRTModule(nn.Module): def __init__(self, engine=None, input_names=None, output_names=None): super().__init__() self.engine = engine # TensorRT ICudaEngine self.context = engine.create_execution_context() # IExecutionContext # ... def forward(self, *inputs): # 分配绑定 buffers、执行 inference、返回输出 tensor源码里TRTModule.forward的实现思路是:
- 根据 engine 的绑定名称和输入 output shape,分配 CUDA 内存。
- 把 PyTorch 输入 tensor 拷贝到绑定的输入 buffer 里。
- 调用
self.context.execute_async_v2(bindings=buffer_addresses, stream=current_stream)执行推理。 - 把输出 buffer 包装成 PyTorch tensor 返回。
这里有一个重要的设计:TRTModule内部还保存了原始 PyTorch 模型self.module,并且forward可以接受一个return_original之类的参数来回退到 PyTorch 推理。这个设计在企业调试阶段非常有用——转换完可以先在 dataset 上跑一遍对比,Torch 输出和 TRT 输出的差异一目了然。
但源码里forward的实现有个潜在问题:它绑定的输入和输出 buffer 是在每一次 forward 里重新分配的,而不是采用对象池复用。虽然 TensorRT 内部有显存分配器分配 device memory,但在高并发推理场景下,频繁的 buffer 创建/释放和 stream 切换会带来一定的 CPU 开销。我在实际项目里对TRTModule做过二次封装,把输入输出 buffer 固定下来并复用同一个 CUDA stream,性能可以再提升 10% 左右。这个优化点在企业部署时值得留意。
整个转换流程还有一个环节经常被忽略:context的创建时机。TRTModule在初始化时就创建了IExecutionContext,这个对象在后续推理中会被反复使用。在 TensorRT 8 之前,context和engine的生命周期管理要格外小心,不能过早销毁 engine,否则context会变成悬垂引用。torch2trt 源码里对engine和context都有引用持有,这点做得还行,但如果你自己写二次封装时没有保存引用,很容易在 Python 的垃圾回收机制下踩到段错误。
4. 插件机制是 torch2trt 的灵魂:为何某些算子必须"手动接管"
理解 torch2trt 的插件机制,是判断一个团队能不能用好它的分水岭。因为再大的内置 converter 库也不可能覆盖所有模型——业界很多真实的模型都会用到自定义算子,比如 attention 里的softmax变种、LayerNorm的高精度版本、各种gather组合逻辑。这类算子要么 TensorRT 原生 API 没有直接对应层,要么原生层的实现效率和期望不符,这时候就需要插件机制。
torch2trt 的插件机制分两个层次:
- Converter 插件:针对 PyTorch 算子的转换函数,在
converters.py或外部模块里通过@tensorrt_converter注册。 - TRT Plugin 层插件:针对 TensorRT 层的自定义实现,通常需要写 C++ 代码并编译成
.so,然后在 Python 里用ctypes加载。
先看 converter 层。torch2trt 内置了一大批常见 converter,比如 Conv2d、BatchNorm2d、ReLU、MaxPool2d、Linear、Softmax、LayerNorm 等。这些内置 converter 的覆盖范围基本能应付 ResNet、VGG、YOLO 这类经典 CNN。但如果你要转的是 Transformer 结构,情况就不一样了——MultiheadAttention这个算子经常不是出自 PyTorch 的核心算子列表,而是由多个基础算子组合而成。torch2trt 的 trace 会把这些组合展开成基础算子,所以你会看到很多aten::softmax、aten::matmul、aten::bmm等节点被单独处理。
那问题来了:组合展开后,每个基础算子都有 converter,但组合出来的计算图可能非常碎片化,每个节点都生成一个独立的 TensorRT 层,层数暴涨,推理性能和显存效率反而下降。这就是为什么插件机制不能只停留在"能转就行"的层面,还需要考虑"转得好不好"。torch2trt 解决这个问题的方式是提供一个converters覆盖之外的路径:让用户可以拦截一个大的模块(比如torch.nn.MultiheadAttention),写一个自定义 converter,把它整体映射到一个更高效的 TensorRT 层序列或自定义插件上。
写自定义 converter 的具体流程我用一个例子说明。假设你有一个模块MyAttention,TensorRT 原生没有对应接口,但你可以这么写:
@tensorrt_converter("module.MyAttention") def convert_my_attention(ctx, module, args): input_trt = args[0].trt layer = ctx.network.add_fully_connected(input_trt, ...) output_trt = layer.get_output(0) return TensorWrapper(output_trt, shape=...)然后把它传给 torch2trt 的转换函数:
model_trt = torch2trt(model, [x], custom_converters=[convert_my_attention])注意这里ctx.network.add_fully_connected只是示意,实际上add_fully_connected是 TensorRT 内置的 FC 层 API。如果你的算子更复杂,则需要走 TensorRT 的 plugin 体系写 C++ 扩展。
再说 TRT Plugin 层。TensorRT 的插件本质是继承IPluginV2DynamicExt或IPluginV2IOExt的 C++ 类,你需要实现getOutputDimensions、enqueue、serialize等接口。torch2trt 的plugin.py里提供了一些 Python 层封装,支持把继承自trt.IPluginV2的 Python 插件类注册进网络。不过在我的经验里,Python 层插件只适合做原型验证,真正上生产还是得用 C++ 写高效插件。原因很简单:Python 层的enqueue回调要在每个推理 step 里被调用,Python 解释器的开销会压掉很大一部分 TensorRT 的优化收益,尤其是对 elementwise 之类高频调用的小算子。
另一个值得关注的插件设计是arg_mapping.py和errors.py的配合。当 torch2trt 遇到一个不支持的算子时,ConverterRegistry会抛出一个异常,告诉你算子没有注册 converter。源码里这个异常的 message 写得比较友好,会列出算子名和建议方案。这个可读性的设计在企业内部培训时有很大价值——新同学上手时看到这个报错基本就能判断下一步该做什么。
从企业尽调的角度,插件机制带来的最大收益是"可扩展性"和"可控制性"。可扩展性意味着你的团队可以不断向转换器库里补充新算子,逐渐积累一套适合自己业务模型的转换资产;可控制性意味着你可以对关键算子做手动优化,绕开内置 converter 可能存在的性能问题。相对地,这也带来了维护成本:你需要为每一个自定义插件维护 C++ 代码、编译环境和版本对齐,这些在选型时必须算进人力成本。
5. 企业落地前必须看懂的边界条件与性能误区
torch2trt 虽然好用,但它在企业生产环境里有几个硬边界。不搞清楚这些边界,很容易在项目进入测试阶段后反复返工。
第一个边界:动态 shape 的支持非常有限。torch2trt 的默认行为是把输入 shape 固定下来。你在调用torch2trt(model, x)时传入的这个x的 shape,就是 engine 能处理的 shape。如果你想支持动态 batch 大小,或者更复杂的动态输入尺寸,就需要设置dynamic_axes或者自己魔改源码。我实测下来的结论是:torch2trt 对动态 shape 的官方支持基本停留在"能用,但很勉强"的程度,很多复杂的动态 shape 场景,比如目标检测模型在不同输入分辨率下推理,它的表现远不如 ONNX-TensorRT 路线稳定。所以如果你的业务有动态输入需求,torch2trt 大概率不是最佳选择。
第二个边界:INT8 量化需要额外的校准流程。torch2trt 支持 INT8 推理,但不会自动帮你做校准。你需要准备一个校准数据集,实现一个trt.IInt8Calibrator的子类,然后把它传给torch2trt函数。这个校准器的实现可以参考calibrator.py。这个过程本身不算复杂,但难点在于校准数据集的代表性——如果校准集和真实部署数据的分布不一致,INT8 精度掉得会非常厉害。我在一个车牌识别项目里就遇到这种情况,校准集用白天数据,真实场景混入了大量夜间数据,结果精度从 97% 掉到 88%,最后只能用回 FP16。
第三个边界:TensorRT 版本兼容性。torch2trt 对 TensorRT 版本的依赖是很紧的,不同版本的 TensorRT Python API 可能存在细微差异,比如IBuilder的设置项、IPluginV2的接口签名。这意味着你升级 TensorRT 时,torch2trt 可能也需要同步升级或打补丁。我在项目里见到过因为 TensorRT 从 8.0 升到 8.4,导致一批自定义插件失效的案例。建议的做法是把 torch2trt 和 TensorRT 的版本锁定在镜像或 requirements 里,不要随意升级。
第四个边界:不是所有模型转换后都能有加速效果。torch2trt 的性能优化依赖 TensorRT 对网络图的重组能力,比如把 Conv+Bias+ReLU 融合成一个层。如果你的模型是细碎的小算子组成的,比如大量 elementwise 加法和 reshape 操作,TensorRT 的层融合收益有限,甚至因为图结构过于分散而出现反效果——推理时间比 PyTorch 还要长。我做过一个文本分类模型的转换,结构是 Embedding + 多层 LSTM + Linear,转完以后 FP32 甚至比 PyTorch 慢 20%。后来通过排查发现是 LSTM 内部算子太碎,TensorRT 的融合模块对 LSTM 这类时序结构处理不够好。这种情况下,更好的方案可能是用 TensorRT 的 LSTM 插件,或者换用 TensorRT 官方提供的其他工具链。
企业落地时,还有个容易被忽视的性能误区是batch size 的选择。max_batch_size参数决定了 engine 支持的最大 batch 大小,但它不是越大越好。TensorRT 的 builder 在优化 engine 时,会根据你设置的max_batch_size做一系列内存布局优化,你的实际 batch 大小远小于这个值,反而会导致显存占用增加或 kernel 选择不是最优。我的建议是:把max_batch_size设到业务实际需要的峰值,比如线上推理峰值 QPS 对应的并发 batch 数,不要随便设一个 32 或 64。
最后还有一个老生常谈但必须提的问题:FP16 并不总是比 FP32 快。torch2trt 的fp16_mode=True只是告诉 TensorRT 可以做 FP16 计算,但 TensorRT 会根据具体算子决定是否真正启用 FP16。对于很多计算密集型算子(比如 Conv、MatMul),FP16 确实快;但对于 memory-bound 算子(比如某些激活函数和 pooling),FP16 可能没有收益甚至更慢。此外,FP16 有小概率会引入数值精度问题,尤其是对数值范围敏感的算子(如 softmax 的 exp 函数),你需要准备一个精度对比脚本,在验证集上对比转换前后的输出误差。
我建议企业在新模型接入 torch2trt 时,建立一个标准的验证流程:
- 转换前先跑一遍 PyTorch 模型,记录输出 tensor 作为基准。
- 转换后跑一遍
TRTModule,记录输出 tensor。 - 对比两者的绝对误差、相对误差和 cosine similarity。
- 用真实业务数据做端到端压测,记录延迟和吞吐。
- 如果精度或性能不达标,逐层检查是哪个算子出了问题,再考虑自定义插件或回退到 PyTorch。
这套流程看起来简单,但很多团队是等到线上出问题才回头补的,代价往往已经很高了。
6. 竞品横向对比与选型决策建议
了解了 torch2trt 的架构和边界之后,企业选型时还需要和其他几条路线做横向对比。我列举几个最常见的方案,给出它们在性能、易用性、维护成本和可控性上的区别。
| 方案 | 易用性 | 性能表现 | 维护成本 | 适配场景 |
|---|---|---|---|---|
| torch2trt | 中高 | 中高,对常见 CNN 效果好,对复杂结构需插件 | 中低,代码量小易读,但版本依赖紧 | 中小模型、团队有源码能力 |
| ONNX -> TensorRT | 中 | 高,支持动态 shape,优化稳定 | 中,ONNX 算子覆盖也要处理 | 生产环境的主流选择之一 |
| torch_tensorrt | 中 | 高,官方维护,深度集成 | 中,组件多 | 需要官方持续更新的场景 |
| 纯 TensorRT Python API | 低 | 高,完全可控 | 高,需要自己搭图 | 对性能和控制力要求极高的场景 |
先看 ONNX -> TensorRT 这条路线。它和 torch2trt 的区别在于中间多了一层 ONNX 表示,这带来两个好处:一是 ONNX 本身是生态通用的中间表示,很多模型都能直接导出,不受限于 PyTorch 版本;二是 TensorRT 的 ONNX parser 对动态 shape、多输出、控制流等特性的支持比 torch2trt 的 JIT trace 方案更成熟。缺点是排查问题时多了一个环节,某些 PyTorch 算子导出成 ONNX 时可能被拆分、变形,导致后面的转换结果和预期不一致。
再看 torch_tensorrt,它是 NVIDIA 后续推出的官方转换方案,定位是 PyTorch 生态的原生集成工具。它的架构和 torch2trt 有相似之处,也支持直接吃nn.Module做转换,但底层走的是 TorchScript 和 TensorRT 的深度融合,对动态 shape 的支持、官方维护力度和长期演进都比 torch2trt 更有保障。如果你的团队有长期依赖 TensorRT 的计划,建议把 torch_tensorrt 纳入评估范围。
纯 TensorRT Python API 方案则适合那些对性能和控制力要求极高的团队。它的思路是:直接在 Python 里用 TensorRT 的 layer API 搭建计算图,完全绕过 PyTorch。这种方式的性能和可控性是最好的,但开发成本最高,相当于把模型结构重新用 TensorRT 写一遍。一般来说只有模型结构十分稳定、且团队有足够 TensorRT 经验的场景才会选这条路线。
那到底什么时候应该选 torch2trt?我的判断标准有这么几条:
- 模型规模不算特别大(比如 <100 个算子),结构以标准 CNN 为主,没有太多自定义算子。
- 你的团队希望快速把 PyTorch 模型跑上 TensorRT,并且愿意投入少量时间阅读源码、写自定义 converter。
- 你的推理场景对输入 shape 比较固定,不需要复杂的动态 shape 支持。
- 你对 TensorRT 版本的升级节奏不敏感,可以锁定版本长期运行。
如果以上条件与你的项目匹配,torch2trt 是一个低门槛、高可控性的选择。如果你发现自己需要用到的动态 shape 或者复杂结构场景越来越多,那就得开始考虑 ONNX 路线或 torch_tensorrt,避免一条路走到黑。
还有一个在选型中容易被忽略的点:license 和团队治理。torch2trt 用的 license 我印象是 MIT 类宽松许可,商用友好,不会有太多法律风险。但企业内部落地一个开源工具,还需要考虑代码审查、CVE 跟踪、版本更新策略等治理层面的问题。torch2trt 因为代码量小,审查成本低,这点是优势;但它在 GitHub 上的更新频率相比官方工具没有那么频繁,你需要接受它"基本稳定但少有大版本迭代"的状态。
最后想给一个发自实操的建议:无论最终选哪条路线,都不要把转换工具当成黑盒来用。我见过太多团队把模型转完就算完事,上线后遇到性能瓶颈时,因为完全不了解转换链路的内部原理,只能干瞪眼。花一个下午把 torch2trt 的core.py和converters.py读一遍,搞清楚转换链路每一步在做什么,对后续排查问题和二次开发的价值是无可替代的。企业级落地,拼的就是对细节的掌控能力。