T-Rex2模型部署实战:ONNX导出与TensorRT推理优化指南

发布时间:2026/9/4 21:46:34
T-Rex2模型部署实战:ONNX导出与TensorRT推理优化指南 1. 先把实际问题说清楚T-Rex2的ONNX和TensorRT部署到底要解决什么1.1 不是“再跑一遍Demo”而是给模型一个稳定的生产形态我第一次把T-Rex2跑起来的时候说实话体验很爽。输入一句“a person riding a bike”或者直接在图上点几个框它能识别出对话里压根没见过的目标类别这种开放词汇检测能力比当年做YOLO系列时“训死几百个类”的体验超前太多了。可真到要把它当一个服务部署上线问题就全冒出来了PyTorch推理显存占用不可控预热慢接口散在Python脚本里每次调用都得把整套推理代码再走一遍。更麻烦的是T-Rex2不是一个“输入图片、输出框”的单一模型它内部有图像侧、文本侧、提示侧多条链路。于是我开始认真考虑把T-Rex2推到ONNX再用TensorRT构建成可复用的推理引擎。这篇内容我围绕T-Rex2在ONNX和TensorRT上的完整落地路径来写。适合谁看呢主要是那些已经跑通T-Rex2开源仓库但下一步要把它塞进自己业务系统的同学也包括把T-Rex2当作候选方案、想提前评估部署成本的人。我会把导出过程中为什么要拆模型、算子坑在哪、TensorRT怎么配、量化怎么做、推理结果怎么保存这些事一次讲清楚。1.2 T-Rex2推理链路比普通检测模型长一截一个常规的YOLO系列模型推理链路是“图像预处理 → 单模型前向 → NMS后处理”边界非常清楚。T-Rex2不是这个玩法它的输入除了图像还有文本提示text prompt和视觉提示visual prompt。文本提示先要经过文本编码器变成向量视觉提示也要经过提示编码变成向量图像本身要过一个比较大的主干网络提取特征最后这些信息汇总到解码器里做目标框预测。这条链路意味着两件事。第一如果你不做拆分指望把“整个T-Rex2”导成一个ONNX文件通常会遇到大量动态分支、多模态输入混合、中间结果类型不统一的问题导出难度会成倍上升。第二文本侧和视觉侧的计算量差异很大实际业务里文本提示往往可以缓存视觉提示也不是每次都传。把这些能力分开部署在接口设计上会灵活得多。所以基于我踩过的坑从一开始就应该接受一个原则T-Rex2的部署形态是多ONNX文件、多Engine文件而不是一个大而全的单独模型。1.3 部署的目标是“可插拔”不是“能在GPU上跑”很多人误以为模型导出成TensorRT Engine就万事大吉其实背后真正的目标是得到一个“可插拔”的推理组件。什么叫可插拔外部服务传进来一张图、一行类别文本经过若干次推理后返回标准化的目标框业务方不关心你内部是不是分了三段也不关心你用的是FP16还是INT8。这一点决定了我在后续所有技术决策里的优先级宁可多花时间把输入输出边界定义清楚也不要图省事把预处理、后处理和模型前向搅在一起。T-Rex2本身支持文本提示和视觉提示但真实业务里很多场景只用文本提示就够我们不一定需要把视觉提示Encoder塞进主链路。如果哪天真的需要点选、框选能力再单独把它作为一个可选模块挂上去这样系统复杂度不会一上来就爆掉。2. 导出前必须做的一次“结构拆解”别把整个模型塞进一个ONNX2.1 图像编码器整条链路上“最重的一块砖”T-Rex2在图像侧用的是一套类DINOv2的主干网络负责把输入图变成多尺度的视觉特征。这一块算力消耗最大也是TensorRT优化收益最明显的地方。导出时你需要先确认两件事一是你的业务输入分辨率是否固定二是你需要主干输出哪几层特征。很多开源推理脚本里图像编码器输出的是多尺度特征列表因为T-Rex2的解码器要靠不同尺度的特征做目标检测。如果你在导出时只留最后一层后面解码器会缺输入整个推理链就断了。所以我做导出时的习惯是先跑一遍PyTorch原模型把张量形状和名称用代码打印出来整理成一张“形状映射表”再决定ONNX的输入输出怎么命名。你别嫌这一步麻烦后面所有排错都建立在“我知道每一段输出应该是什么形状”上面。2.2 文本编码器很多精度问题其实出在文本侧文本编码器是T-Rex2里最容易轻视的一个模块。它接收的是tokenized之后的类别文本输出的是文本特征在纯视觉检测任务里你可能觉得文本侧很简单但实际上一旦文本模板和训练时不匹配检测效果会肉眼可见地变差。T-Rex2的文本提示通常不是直接把“cat”丢进模型而是要套一层带上下文的prompt模板比如类CLIP风格的一句描述。我在部署时一定复用官方推理脚本里的tokenizer和template拼接逻辑绝不在前端自己重新发明一套文本处理。关于导出我建议把输入定义为input_ids和attention_mask两个张量输出直接给解码器需要的文本特征。如果你在文本编码器导出时偷懒把tokenizer也一起包进ONNX后面换业务类别时会非常痛苦因为每次新增类别都要重新跑一遍图。2.3 解码器与提示编码想清楚哪些东西要跟着一起进图T-Rex2的解码器负责做跨模态融合和框预测。它接收图像特征、文本特征以及可选的视觉提示特征最终输出归一化的候选框和置信度。视觉提示部分如果你只是做文本指代检测那视觉提示Encoder可以暂时不导出留在Python侧甚至暂时不启用但如果你要做点选、框选交互提示特征就必须进入解码器的输入。从工程角度我的建议是解码器单独导成一个ONNX视觉提示Encoder如果业务需要就单独再导一个不要尝试把解码器和视觉提示Encoder强行揉成一个文件。多一个Engine只是多一次加载和调用不会显著增加延迟但会让每个模块的职责清晰很多。我自己实际部署时用的是“图像编码器 文本编码器 解码器”三段式视觉提示能力只在需要交互式检测的场合才启用。2.4 我的默认拆分方案和命名习惯模块输入输出是否必须Text Encoderinput_ids, attention_mask文本特征必须Image Encoder图像Tensor多尺度图像特征必须Visual Prompt Encoder点/框坐标、对应图像特征提示特征按需Decoder图像特征、文本特征、提示特征boxes, scores必须我给ONNX文件命名是这样的trex2_text.onnx、trex2_image.onnx、trex2_decoder.onnx如果后面加了视觉提示就是trex2_prompt.onnx。小写加下划线避免容器之间拷贝文件时出现大小写问题。这个习惯帮我少踩了很多坑特别是文件一多以后你能从文件名一眼看出这个Engine是干嘛的不会出现“到底该加载哪个”的困惑。3. ONNX导出实操Opset、动态Shape和算子兼容性排坑记录3.1 先给出一份能跑通的导出模板PyTorch转ONNX最常用的是torch.onnx.export。我一般在模型eval()模式下固定随机种子准备好一个真实形状的样例输入然后逐个模块导出。以文本编码器为例import torch text_encoder.eval() sample_input_ids torch.randint(0, 1000, (1, 77), dtypetorch.long) sample_attention_mask torch.ones(1, 77, dtypetorch.long) with torch.no_grad(): torch.onnx.export( text_encoder, (sample_input_ids, sample_attention_mask), trex2_text.onnx, input_names[input_ids, attention_mask], output_names[text_embeddings], dynamic_axes{ input_ids: {0: batch, 1: seq_len}, attention_mask: {0: batch, 1: seq_len}, }, opset_version17, do_constant_foldingTrue, )关于opset版本T-Rex2这种带Transformer结构、大量Attention和Resize操作的模型我用opset 17比较多。不是越高越好新版opset有时会引入TensorRT还没有完全覆盖的算子写法。opset 17在TensorRT 8.6及以上版本里兼容性比较稳。如果你本地的TensorRT版本比较老可以先降到16甚至15试试不要一上来就追最新。3.2 我实际踩过的“算子墙”Resize、Einsum和Attention系第一次导出解码器时我在ONNX Runtime里跑得很正常一拿到trtexec就报算子不支持。后来逐个定位发现主要卡在几类算子上。第一类是Resize。DINOv2和Decoder里都有多尺度特征上采样的逻辑PyTorch导出Resize时不同opset版本对coordinate_transformation_mode的默认处理不一样TensorRT又对Resize的模式很挑剔。解决办法是手动指定modebilinear和坐标变换模式或者干脆把预处理里的resize搬到CPU/GPU上的通用图像处理库去不依赖模型内部去改变输入尺寸。第二类是Einsum。部分注意力实现里会出现Einsum算子这个算子在PyTorch里很好用但ONNX导出后不一定被TensorRT原生支持。我的经验是在导出前把模型里的Einsum改成显式的matmul、transpose、reshape组合或者直接在模型代码里改forward的实现。改的时候要小心改完最好先跑一遍PyTorch确认输出一致。其实这不算T-Rex2特有的问题所有Transformer系模型转TRT都会遇到这个坎提前改能省很多调试时间。第三类是Attention算子里对attention_mask的处理。T-Rex2的文本编码器有padding mask如果导出时mask的形状、dtype和原始实现不一致很容易出现结果全对、只有padding位置出错的情况肉眼很难看出来但对最终框的精度会有一点影响。我的做法是让attention_mask保持int32或bool类型进入ONNX同时在导出前用全1的mask和带mask的输入对比一遍输出。3.3 用onnxsim和onnx-graphsurgeon做减负模型导出后的原始ONNX往往包含大量冗余节点。自注意力内部的transpose、reshape链更是又多又碎。直接拿这样的ONNX去构建TensorRT Engine不仅编译时间变长还可能出现“Engine构建成功但显存占用很高”的情况。我的做法是先过一遍onnxsimpip install onnxsim python -m onnxsim trex2_decoder_raw.onnx trex2_decoder.onnxonnxsim能做常量折叠、公共子表达式消除对DINOv2这种结构比较规整的主干尤其有效。但onnxsim不会帮你裁剪模型如果T-Rex2的官方代码在推理时取主干中间的多个输出你需要用onnx-graphsurgeon手动指定ONNX的输入输出节点把用不到的分支裁掉。这个裁剪过程有点繁琐但能明显降低后续TensorRT编译的复杂度和运行时显存占用值得做。3.4 导完先做数值验证别急着进TensorRT每导出一个ONNX我都先让ONNX Runtime跑一遍再和PyTorch输出对比确认没问题才继续。import onnxruntime as ort import numpy as np sess ort.InferenceSession(trex2_text.onnx, providers[CPUExecutionProvider]) onnx_out sess.run( [text_embeddings], { input_ids: sample_input_ids.numpy(), attention_mask: sample_attention_mask.numpy(), }, )[0] torch_out text_encoder(sample_input_ids, sample_attention_mask).detach().numpy() np.testing.assert_allclose(onnx_out, torch_out, rtol1e-3, atol1e-4)这里注意T-Rex2的输出经过softmax之前或之后其实数值范围差别很大我通常会在解码器输出层用更松一点的容差。做这一步的目的不是追求零误差而是尽早发现“结构不对、算子语义不一致”这种大问题。如果这一关没过就急着去构建TensorRT Engine后面排错会非常难定位。4. 从ONNX到TensorRTFP16、动态Shape与INT8量化的实测取舍4.1 为什么跳过ONNXRuntime直接上TensorRT有人会问既然ONNX Runtime的GPU后端也能跑为什么非要费劲转TensorRT从我实际测试看T-Rex2这种模型主干部分卷积和Transformer结构都有大量算子TensorRT的层融合和kernel自动调优带来的收益比ONNXRuntime明显高不少。后者更像一个兼容性很好的基础运行库前者则专注于在NVIDIA GPU上把性能榨干。如果你只是做原型验证ONNXRuntime完全够用。但生产环境里延迟和显存都有硬指标TensorRT通常更合适。另外TensorRT还支持将Engine序列化保存服务启动时直接反序列化加载不用每次启动都重新做图优化这也是工程上很舒服的一点。4.2 trtexec构建还是自己写构建器我一般在探索阶段用trtexec快速验证因为它不需要写代码一条命令就能看出ONNX能不能构建成功。trtexec --onnxtrex2_text.onnx \ --saveEnginetrex2_text_fp16.engine \ --fp16 \ --minShapesinput_ids:1x77,attention_mask:1x77 \ --optShapesinput_ids:4x77,attention_mask:4x77 \ --maxShapesinput_ids:8x77,attention_mask:8x77如果模型没有动态维度直接把--minShapes、--optShapes、--maxShapes去掉就行。需要注意动态Shape模式会在优化时考虑多种Shape组合Engine会更大显存占用也更高。如果你的业务分辨率是固定的比如图像侧统一缩放后送到模型我强烈建议图像编码器直接固定Shape导出这样TensorRT能针对固定尺寸做出更激进的优化延迟表现会更好。只有文本编码器这种真正需要动态batch或动态长度的模块才值得开动态Shape。还有一种情况是构建时需要指定每层精度策略用trtexec就不够灵活了。这时候可以写一个Python构建脚本用TensorRT的OnnxParser加载ONNX设置网络层精度。个人经验是T-Rex2整条链路上90%的情况用全网络FP16就够了个别精度敏感的层可以单独设回FP32。不要一开始就在代码里做大量逐层精度控制先把FP16整体跑通再根据精度对齐结果去调少数层。4.3 FP16和INT8的取舍建议很多热词里都在问“onnx量化int8”但T-Rex2这种开放词汇检测模型我不建议一上来就上INT8。原因很简单模型里大量LayerNorm、softmax、sigmoid这些操作对低精度非常敏感主干部分用INT8以后检测框的置信度往往会偏移甚至出现小目标漏检。我的建议顺序是先跑FP32得到基线再跑FP16。FP16在绝大多数GPU上都能带来可观的加速而且精度损失很小肉眼几乎看不出差别。如果FP16已经能满足业务延迟要求那就不需要INT8。只有当FP16还是慢且目标场景对功耗、显存有硬性要求时才考虑INT8。做INT8时校准数据的质量比数量更重要。我用过的校准集在500到1000张图左右覆盖了业务里真正的目标类别、光照条件和成像角度。校准集如果只选几十张公开图类别和实际场景差得很远校准出来的量化参数往往会严重拉低精度。校准最好用TensorRT的EntropyCalibrator2输入数据直接用预处理后的模型输入格式不要在图像读取格式上出岔子。4.4 Docker镜像解决版本依赖问题TensorRT最折磨人的一点是版本依赖。构建Engine时的TensorRT版本、CUDA版本如果和运行时不一致常常会碰到反序列化失败或者算子行为不同的问题。我现在的做法是在NVIDIA官方的TensorRT容器里统一做构建。这样省去了自己折腾CUDA和TensorRT安装的时间也避免了我本机“装了一套CUDA结果TensorRT链接到另一套”的尴尬。容器运行时需要带GPUdocker pull nvcr.io/nvidia/tensorrt:23.12-py3 docker run -it --gpus all -v $(pwd):/workspace nvcr.io/nvidia/tensorrt:23.12-py3 bash构建好的Engine文件可以拷出来放到实际服务环境里运行。但运行环境的TensorRT主版本最好和构建环境保持一致至少也要保证Engine文件的序列化版本兼容。如果你换了一台GPU型号更老的机器就别指望Engine还能直接反序列化基本都要重新构建一次。5. 端到端推理框架Engine加载、三段调用与结果保存5.1 一次完整推理的调用顺序有了三个独立的TensorRT Engine以后接下来要解决的是编排问题。以文本提示检测为例一次推理的逻辑顺序是文本侧类别文本经过tokenizer和模板拼接得到input_ids和attention_mask送入Text Encoder得到文本特征。图像侧原始图像预处理成Tensor送入Image Encoder得到多尺度图像特征。解码把文本特征、图像特征一起送入Decoder得到候选框和置信度。后处理把归一化坐标还原到原图坐标做置信度过滤和NMS保存可视化结果或JSON。这个顺序在PyTorch里很自然但换到Engine推理后你要额外注意两个问题。第一文本特征可能只和类别有关、和图像无关如果同一批类别要反复用可以把文本特征缓存起来不用每次都跑Text Encoder。第二多尺度图像特征不要提前concatDecoder的输入名如果分成了image_feat_small、image_feat_large你在组织输入字典时就要按Engine实际绑定的名字给千万别靠位置猜。5.2 TensorRT Engine封装类三段调用的代码骨架TensorRT推理本身不复杂但对着Python API写封装时容易犯一个低级错误每次推理都重新分配显存。我建议写一个极简的引擎类在初始化时分配好所有bindings的显存后续推理只需要把输入拷进去、执行、再把输出拷回来。import tensorrt as trt import numpy as np import pycuda.driver as cuda class TRTEngine: def __init__(self, engine_path): logger trt.Logger(trt.Logger.WARNING) runtime trt.Runtime(logger) with open(engine_path, rb) as f: engine_bytes f.read() self.engine runtime.deserialize_cuda_engine(engine_bytes) self.context self.engine.create_execution_context() self.stream cuda.Stream() self._allocate_buffers() def _allocate_buffers(self): self.inputs {} self.outputs {} self.bindings [] for i in range(self.engine.num_bindings): name self.engine.get_binding_name(i) shape self.engine.get_binding_shape(i) size trt.volume(shape) dtype trt.nptype(self.engine.get_binding_dtype(i)) host_mem cuda.pagelocked_empty(size, dtype) device_mem cuda.mem_alloc(host_mem.nbytes) self.bindings.append(int(device_mem)) if self.engine.binding_is_input(i): self.inputs[name] {host: host_mem, device: device_mem, shape: shape} else: self.outputs[name] {host: host_mem, device: device_mem, shape: shape} def infer(self, feeds): for name, arr in feeds.items(): arr np.ascontiguousarray(arr) cuda.memcpy_htod(self.inputs[name][device], arr) self.context.set_binding_shape(self.engine.get_binding_index(name), arr.shape) self.context.execute_async_v2(bindingsself.bindings, stream_handleself.stream.handle) cuda.stream.synchronize(self.stream) results {} for name, out in self.outputs.items(): cuda.memcpy_dtoh(out[host], out[device]) results[name] out[host].copy() return results这个类是通用的但有几个前提你要自己确认输入输出的Shape、dtype和Engine构建时定义的一致动态Shape的话需要调用set_binding_shape之后再跑。T-Rex2三段模型各自的输入输出都不一样但用同一个封装类没问题。编排调用时Text Encoder先跑text_emb text_engine.infer({ input_ids: token_ids, attention_mask: attn_mask, })[text_embeddings]图像Encoder跑一遍image_feats image_engine.infer({image: img_tensor})[multi_scale_features]我这里特地把输出名写成multi_scale_features实际导出时它可能被拆成好几个输出。你不要照搬这个名字一定要去看导出ONNX时定义的output_names。最靠谱的做法是导出前打印一遍官方模型forward的返回值结构保证每个输出都和Decoder的输入完全对应。这一环没有捷径代码里写错一个名字跑起来报错都算好的最怕名字对上了、顺序却是错的那样出来的检测框会荒谬到起飞。5.3 坐标还原从模型输出到原图像素坐标Decoder输出的框坐标通常是归一化的还带有可能的letterbox或padding偏移。我在做后处理时会先记录图像缩放和letterbox的参数再把框还原到原图。比如图像预处理时你把原图长边缩放到1024并且做了padding那么模型预测的(cx, cy, w, h)要先把归一化坐标乘以模型输入尺寸然后减去padding区域的偏移最后再除以缩放系数才能得到原图像素坐标。这里有一个很容易踩的坑如果你没有对图像做padding只是单纯resize那就不要画蛇添足减去偏移如果你用了padding但没有把偏移量存下来后续画框就会整体错位。我的做法是把预处理封装成一个函数返回处理后的Tensor、缩放系数scale、padding偏移量pad_x和pad_y让后处理永远只依赖这几个返回值而不是在外部再算一遍。这样即使以后改了预处理逻辑后处理也不容易崩。置信度过滤和NMS也没什么特别。T-Rex2一次推理可能输出很多冗余框设置一个较低的conf_threshold再配合IoU阈值做NMS比只调高置信度阈值更稳妥。我在实际测试中发现如果只用高置信度过滤小目标比较容易漏NMS都做完之后再统一过滤一次置信度比较合理。5.4 结果落盘可视化图片和JSON的常见坑我习惯同时输出两种结果一张画了框的图片用于人工查看一份结构化JSON用于给上层业务消费。保存图片时先用OpenCV把原图读进来注意此时图像是BGR格式画框直接用BGR坐标画即可不需要再转换。画框用的颜色、线宽要注意别把文字和框画得糊在一起T-Rex2支持多类别同屏检测我通常给不同类别分不同颜色但就算只用一种颜色也要在框上标注类别名和置信度方便排查。JSON保存时一个常见的坑是置信度保留的小数位数不一致。后面做精度分析时你可能会因为“同一个框两次推理结果差0.0001”这种噪声浪费半天时间。我的做法是统一保留6位小数坐标保留2位小数输出格式固定下来。另外一个坑是类别名和类别序号要同时保存别只存序号不然业务方拿到JSON还要自己查类别映射表。如果类别多建议把所有支持的类别列表也一起写到JSON的meta字段里方便回看时对得上。6. 精度对齐、性能调优和几条没人会提醒你的工程细节6.1 精度对齐别用肉眼用IoU和距离指标说事T-Rex2框架转到TensorRT之后最容易被忽略的就是精度验证。很多人跑一两张图看一眼框画得差不多就上线了。可等到业务方反馈“有些框位置不对”“置信度偏低”的时候你很难判断是模型本身的问题、量化精度损失还是预处理不一致。我的做法是写一个自动对齐脚本读取同一批测试图分别用PyTorch原模型和TensorRT推理链跑一遍对输出的框做匹配计算匹配框的IoU和中心点距离。如果匹配框IoU低于0.9再单独打印出来看是哪些类别、什么尺寸的目标。这个环节看着麻烦但一旦跑起来后面改动任何精度策略比如从FP16换到INT8都能用同一套脚本快速评估。FP16通常不会引起大面积掉点如果某个类别掉得特别明显优先怀疑预处理不一致如果所有类别的框置信度都整体下降再怀疑量化和精度策略。6.2 性能测试要用真实业务输入别只测单张图T-Rex2的性能和文本长度、图像分辨率、目标数量都有关。单张图测出来的毫秒数只能用来做初级参考不能直接用来评估线上容量。我一般会准备一组模拟线上分布的测试集包含不同分辨率、不同目标密度的图片然后统计P50、P95和P99延迟。这样做的好处是能暴露出某些图片特别慢的问题。目标数量多的时候解码器输出的候选框数量也会变多如果你在CPU上做的NMS成为瓶颈会把整体耗时拉高一大截。这种情况下把NMS放到CUDA上做或者用TensorRT的BatchNMS插件会有明显改善。另一个很容易被忽视的坑是预热。TensorRT第一次推理时会做kernel选择和显存初始化耗时明显偏高。生产中服务启动后必须做一次“假推理”预热否则第一个真实请求的延迟会非常难看。我把预热逻辑放在服务启动阶段用一张固定尺寸的假图跑两三轮确保所有kernel都被触发后再对外提供流量。6.3 多路部署与显存复用如果你的服务需要同时处理多路视频流或者多个请求建议直接在进程内共享同一个Engine实例并复用上下文执行而不是为每个线程单独加载Engine。同一个Engine可以被多个线程并发执行推理只要上下文各自创建或者按顺序提交即可。不过要留意TensorRT的Python API在并发时的GIL问题实际并发吞吐要求高的话我建议把推理放到单独的C服务里或者用子进程的方式隔离避免Python GIL拖累整体的多路吞吐。显存方面T-Rex2主干模型在FP16下显存占用可控但如果你同时加载了Text Encoder、Image Encoder、Decoder三个Engine显存占用要提前算好。我的经验是把不常用的Text Encoder和Visual Prompt Encoder的Engine在空闲时释放在需要时重新反序列化加载。不过频繁加载Engine也有反序列化开销更稳妥的做法是保活一个最小集合把图像编码器这种最重的Engine常驻文本侧如果特征做了缓存就不需要一直持有。6.4 如果整条链路慢了先查CPU端数据搬运有几次我做性能优化盯着Decoder Engine的耗时看了很久优化了个寂寞最后发现瓶颈在CPU端的图像预处理和四次显存拷贝上。T-Rex2三段推理意味着输入输出张量要在CPU和GPU之间来回搬运如果预处理、Engine推理、后处理没有做好流水线GPU会有大量时间在空等。我在这类部署里的习惯是尽量把图像预处理放到GPU上做至少也要用带GPU后端的预处理库让Camera采集到的数据直接在显存里完成缩放和归一化避免先从GPU拷到CPU、处理完再拷回GPU。文本侧的特征如果被缓存了整体链路会短很多大多数请求只需要跑一次Image Encoder和Decoder显存拷贝次数也能降下来。这套东西做完之后我把T-Rex2从PyTorch脚本慢慢改造成了一个三个Engine组成的推理服务整个过程里最花时间的其实不是算子排错而是每个环节的输入输出边界定义。如果你最近也在折腾类似的项目我给你的建议是先花半天时间把每个模块的张量形状和数值范围理清楚再去碰ONNX和TensorRT你后面会少走很多弯路。文本编码器这种小模块能缓存就缓存图像侧能固定Shape就固定Shape量化的事放到FP16验证完之后再纠结一步一步来这个模型的生产落地没有想象中那么玄乎。