尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

MobileNetV2图像分类模型PyTorch到TensorRT部署实战指南

MobileNetV2图像分类模型PyTorch到TensorRT部署实战指南 简介深度学习模型从训练到部署需要跨越运行时差异与性能优化等一系列挑战。以图像分类任务为例PyTorch训练的MobileNetV2在eager模式下推理效率受限而通过ONNX导出为静态计算图再借助TensorRT进行算子融合、精度校准与显存优化可显著提升推理吞吐并降低延迟。这一流程广泛适用于边缘设备、高并发服务等场景是模型工程化落地的关键环节。本文基于实战经验梳理从训练脚本、预处理对齐、ONNX导出、trtexec转换到Python/C推理接口的完整链路并总结数据口径、动态shape、版本兼容等常见坑位为图像分类模型的TensorRT高效部署提供可复现的参考路径。 博主我自己接了挺多图像分类的小项目最开始图省事PyTorch训完模型直接拿来接服务被线上性能狠狠教育过几次。后来跑通了一条从MobileNetV2训练到TensorRT部署的完整链路才意识到“训练能跑”和“上线能扛”之间隔着的不是一点点。这篇东西不是教程复读机是我把整条路从头到尾走一遍的实战记录包括训练脚本怎么写、ONNX导出有哪些暗坑、trtexec背后的逻辑以及部署端Python和C的取舍。如果你正在做图像分类想搞清楚PyTorch模型到底怎么落地到TensorRT这篇应该能帮你省下不少试错时间。1. 为什么又慢又吃显存训练代码和部署代码从来不是同一件事先把结论摆在前面MobileNetV2本身非常轻量参数只有约340万计算量在300M MACs左右。但同样一个模型用PyTorch直接跑推理和用TensorRT跑推理性能差距可以大到让你怀疑人生。问题不在模型而在两套运行时的工作方式完全不同。1.1 MobileNetV2的定位与PyTorch直接推理的痛点MobileNetV2是Google在2018年提出的轻量级网络核心卖点是深度可分离卷积和Inverted Residual Block倒残差结构也就是先升维、再卷积、再降维配合Linear Bottleneck线性瓶颈设计让网络又小又快。相比之下ResNet50参数有2500万、计算量超过4G FLOPs所以MobileNetV2很适合放在边缘设备或高并发服务里。但这里有个很容易被忽视的细节PyTorch默认的eager模式推理性能开销的主要来源不是卷积计算本身而是三件事Python解释器逐层调度算子的开销一个几百层的网络跑下来Python层累计的dispatch损耗非常可观。每个算子启动GPU kernel的固定延迟小算子越多kernel launch overhead越明显。动态shape和显存管理带来的不确定性PyTorch默认情况下不会为固定shape做极致的内存复用。举个我实际测过的例子在一张RTX 3060上torchvision里的MobileNetV2预训练模型直接FP32推理平均耗时大约4到7毫秒而同样的模型导出ONNX后转成TensorRT FP16引擎耗时可以压到1.5到2.5毫秒。看起来绝对数值都不大但当你把单路请求放到并发场景里看或者放到Jetson这类边缘设备上差距就是“能扛住”和“被打爆”的区别。1.2 全流程路线图PyTorch转TensorRT为什么要经过ONNX我见过不少人试图从PyTorch直接跳到TensorRT绕开ONNX结果在算子和导出兼容性上撞得头破血流。正规且稳的路线是这样阶段产物主要工具关键目的模型训练PyTorch权重文件(.pth)PyTorch torchvision得到可用的分类精度模型导出ONNX文件(.onnx)torch.onnx.export把动态图固化成语义清晰的静态图引擎转换TensorRT引擎(.engine)trtexec或TensorRT Python API针对目标GPU做算子融合和显存优化推理部署线上推理服务Python(pycuda)或C API低延迟、高吞吐地跑分类推理之所以要经过ONNX是因为TensorRT官方提供了解析ONNX的parser能稳定地把计算图映射到TensorRT的层上。ONNX在这个链路里相当于一个“图交换格式”把PyTorch的动态图变成静态的、算子语义明确的中间表示。直接从PyTorch转TensorRT的路径不是没有但算子覆盖和自定义逻辑会导致更多麻烦至少对于量产项目我不建议这么干。你还需要提前确定部署目标。TensorRT生成的engine是跟GPU架构强绑定的在一台机器上转好的engine换到另一块不同架构的显卡上基本跑不了。所以路线的第一步其实是搞清楚最终跑推理的平台是数据中心卡、游戏卡还是Jetson嵌入式设备然后围绕目标平台倒推训练和转换的环境准备。2. 数据集与预训练模型训练前把输入口径统一后面能少踩一半坑这一节不是废话。我见过太多人训练时用一套预处理部署时用另一套最后精度对不上层层排查到头来发现是图像缩放插值方法不一样。说实话数据口子如果一开始不统一后面所有环节都会跟着遭殃。2.1 森林图像分类数据集的组织方式与划分策略拿“森林图像分类”这类任务举例假设你有两类场景森林和非森林或者更细一点按树种分多类。数据目录建议直接组织成torchvision.datasets.ImageFolder能读的标准结构data/ train/ forest/ 001.jpg 002.jpg ... non_forest/ 001.jpg 002.jpg ... val/ forest/ ... non_forest/ ... test/ forest/ ... non_forest/ ...train/val/test的划分我一般用8:1:1但有个很重要的前提必须按类别分层划分而不是把所有图片shuffle后一刀切。分层划分的目的很简单保证每个类别在三个集合中的比例基本一致避免某个类在验证集里数量太少导致评估结果失真。你可以直接用sklearn的train_test_split配合stratify参数也可以在写脚本时先按类别分组、每组内随机抽。另外每个类别样本数量如果差得很多建议要么加权重采样要么先收集到足够均衡的数据再训练。图像分类任务里类别不平衡会直接影响最后的泛化效果MobileNetV2本身容量不大在数据不均衡时尤其明显。2.2 预处理参数必须和ImageNet保持一致MobileNetV2预训练权重是在ImageNet上训练的ImageNet的标准预处理是归一化到ImageNet的均值和标准差。如果你用预训练模型做迁移学习训练和部署都必须沿用这一套mean [0.485, 0.456, 0.406] std [0.229, 0.224, 0.225]训练阶段我会这样做数据增强from torchvision import transforms train_transform transforms.Compose([ transforms.RandomResizedCrop(224, scale(0.5, 1.0)), transforms.RandomHorizontalFlip(), transforms.ColorJitter(brightness0.2, contrast0.2, saturation0.2, hue0.1), transforms.ToTensor(), transforms.Normalize(meanmean, stdstd) ]) val_transform transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(meanmean, stdstd) ])这里的细节在于训练和验证的resize策略不同是正常的因为训练需要随机裁剪来增强鲁棒性验证用中心裁剪保证评估稳定。但部署端必须完全复刻val_transform不能自己随便换插值方式。torchvision的Resize默认用双线性插值OpenCV的resize默认用双线性插值但像素对齐方式和PIL有差异这点在部署时非常容易踩坑。2.3 加载MobileNetV2预训练权重并改造分类头新版torchvision里用weights参数替代了旧的pretrainedTrue官方建议这么做import torchvision.models as models model models.mobilenet_v2(weightsmodels.MobileNet_V2_Weights.IMAGENET1K_V1) num_classes 10 model.classifier[1] torch.nn.Linear(model.classifier[1].in_features, num_classes)MobileNetV2的classifier结构是Dropout(p0.2)加一个Linear(in_features1280, out_features1000)所以替换索引是1这个细节我见过不少同学搞错成替换整个classifier或者使用错误索引导致维度对不上。如果数据集和ImageNet差异比较大或者数据量非常少建议冻结backbone只训练分类头几个epoch再把backbone解冻做全量微调。如果数据量本身不小直接全量微调也可以但学习率要调小一点一般backbone用1e-4到3e-4的量级分类头可以用稍大的学习率。这部分没有标准答案跟数据分布、样本量都有关系但总有值得遵循的起点。3. 训练脚本配置与显存控制精度和收敛速度的平衡点在哪训练阶段看起来只要写个标准循环就行但超参数、数据增强策略、混合精度、断点续训这些点每一项都直接影响训练效率和最终精度。MobileNetV2虽然小也不是随便train两下就能收敛的。3.1 优化器、学习率与标签平滑的实战组合对于迁移学习我最常用的组合是SGD配上cosine学习率衰减或者AdamW配上warmup。以森林图像分类这种中等规模数据集为例import torch.nn as nn criterion nn.CrossEntropyLoss(label_smoothing0.1) optimizer torch.optim.SGD(model.parameters(), lr0.01, momentum0.9, weight_decay1e-4) scheduler torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max50)这里的逻辑是label_smoothing0.1能防止模型对训练标签过于自信尤其对数据量不大、类别又比较相似的场景很有帮助。SGDmomentum在图像分类上依然是非常稳妥的选择AdamW收敛快但有时候泛化略逊于SGD数据量大且训练时间充裕的时候我更推荐SGD。学习率的初始值要看batch size。如果你用的是小batch比如16或32初始学习率0.01就偏大建议降到0.001到0.005。如果你用混合精度或大batch适当上调学习率可以加速收敛但需要配合warmup来稳定训练初期。在实际项目中使用预训练权重的时候逐层解冻训练比从头训效果好很多。头几个epoch可以只训分类头backbone完全冻结等损失降下来再解冻整个网络进行微调。这样既避免一开始梯度把预训练特征破坏掉也更容易收敛到好的局部最优。3.2 batch size、输入尺寸与显存的三角关系MobileNetV2模型本身占显存不多但训练时显存大头在激活值和优化器状态上。224x224输入下batch size 128通常需要2到4GB显存具体看是否开混合精度。如果你的卡只有6GB建议这样处理先用小batch跑通整个训练流程确认数据增强、损失函数、评估逻辑没有问题。开混合精度训练torch.cuda.amp.autocast配合GradScaler显存能省差不多一半速度也快不少。如果batch实在上不去用梯度累积来模拟大batchaccumulation_steps 4 for i, (images, labels) in enumerate(train_loader): outputs model(images) loss criterion(outputs, labels) / accumulation_steps loss.backward() if (i 1) % accumulation_steps 0: optimizer.step() optimizer.zero_grad()注意这里loss要除以accumulation_steps否则等价于把标准loss放大4倍学习率也要相应调整。梯度累积不是功能完整替代真正大batchbatch norm的表现会有细微差异但多数场景够用。混合精度训练用起来很简单但有一个点要留意如果模型里有自定义的op或者数值范围很敏感的操作最好在validation时对比FP32和AMP的结果防止精度异常。MobileNetV2的BatchNorm在AMP下通常没问题但自己魔改过结构的话还是要测一下。3.3 断点续训、日志监控与最优模型保存训练十几个小时中途断电这种事我遇到过不止一次断点续训不是可选项是必须做的。checkpoint至少包含四样东西模型权重、优化器状态、学习率调度器状态、当前epoch。保存时最好同时保留最新checkpoint和验证集最优模型避免后期过拟合时把最优权重覆盖掉。checkpoint { epoch: epoch, model_state_dict: model.state_dict(), optimizer_state_dict: optimizer.state_dict(), scheduler_state_dict: scheduler.state_dict(), best_acc: best_acc, } torch.save(checkpoint, fcheckpoint_epoch{epoch}.pth)延续前面提到的“增量训练”概念如果后面要往同一个分类模型里追加新类别或者在更多数据上训练最稳妥的方式是把原始训练脚本的预处理、优化器设置原封不动沿用然后加载旧权重继续训练而不是重新换一套超参。很多增量训练不收敛的问题都是因为换了数据增强策略或者学习率设置差异太大。监控方面我强烈建议至少记录train loss、val accuracy、当前学习率。TensorBoard当然好但如果不想引入额外依赖自己用csv记录每一轮的指标也够用。真正重要的不是可视化工具而是你能够从曲线里判断是欠拟合、过拟合还是学习率设置有问题然后及时调整。4. 模型导出ONNX这一步决定了TensorRT能不能顺利接盘如果你把整个部署链路分成两段训练到导出是第一段导出到TensorRT是第二段。第一段出问题一般能通过精度看出来第二段出问题就玄学了经常是日志刷一堆warningengine生成了但推理结果全错。所以ONNX导出这步值得单独拿出来认真对待。4.1 export前的准备eval模式、dummy input和固定输入尺寸PyTorch模型导出ONNX之前必须先切到eval模式。原因很简单model.eval()会把BatchNorm和Dropout都切到推理状态BatchNorm不再用当前batch的统计量而用训练时累积的running_mean和running_var。如果你在train模式下导出导出的图里批归一化行为会出错推理结果直接乱掉。踩坑记录如果用PyTorch新版本torch.onnx.export里可以指定dynamoTrue但生产环境为了稳定一般用传统torchscript导出方式更成熟。传统方式下dummy input的形状就是模型的输入形状比如torch.randn(1, 3, 224, 224)这个形状会和后续TensorRT的输入shape绑定。4.2 opset版本与dynamic_axes的选择opset版本是导出最容易踩坑的地方。太旧不支持某些算子但也不是越高越好因为新版opset可能引入TensorRT parser还没完全适配的新表达方式。我的经验是opset 13到17这个区间比较稳妥再高需要确认当前TensorRT版本支持情况。如果你的部署服务需要支持动态batch必须在导出时指定dynamic_axesimport torch dummy_input torch.randn(1, 3, 224, 224) torch.onnx.export( model, dummy_input, mobilenetv2.onnx, input_names[input], output_names[output], dynamic_axes{input: {0: batch}, output: {0: batch}}, opset_version13, do_constant_foldingTrue )这里我只动态了batch轴没有动态H和W。MobileNetV2的全局池化对输入尺寸有一定容忍度但一旦要动态尺寸TensorRT引擎优化空间会变小显存占用也可能上升。纯图像分类服务我建议固定224x224只动态batch性能最稳。要注意的是do_constant_foldingTrue会把一些常量计算在导出时直接算掉这个加速效果虽小但无害开着就行。4.3 导出后的ONNX校验与问题排查导出后不能直接丢给TensorRT先自己验一遍。最稳的校验方法是ONNX Runtime跑一遍和PyTorch的输出对比import onnx import onnxruntime as ort import numpy as np onnx_model onnx.load(mobilenetv2.onnx) onnx.checker.check_model(onnx_model) ort_session ort.InferenceSession(mobilenetv2.onnx, providers[CUDAExecutionProvider]) x np.random.randn(1, 3, 224, 224).astype(np.float32) ort_out ort_session.run(None, {input: x})[0] model.eval() with torch.no_grad(): torch_out model(torch.from_numpy(x)).cpu().numpy() print(max abs diff:, np.abs(ort_out - torch_out).max())正常情况下最大绝对误差应该在1e-5量级左右。如果大于1e-3大概率是导出设置或模型状态有问题先别转TensorRT回去检查eval模式和opset。还有几个常见问题我直接列个表方便对照现象可能原因处理方式ONNX Runtime输出全是NaN或同值BatchNorm仍处于train模式导出前确保model.eval()转换TensorRT时提示算子不支持opset版本过高降低opset版本到13或14输出shape和预期不符classifier没改对检查Linear输出维度动态轴不生效dynamic_axes参数漏写显式指定batch维5. TensorRT引擎生成trtexec一条命令的背后逻辑ONNX导出没问题后就进入TensorRT阶段。很多人以为trtexec就是把ONNX喂进去、等engine吐出来其实背后涉及图优化、精度选择、workspace设置每一步都有讲究。5.1 trtexec基础命令与精度模式选择最基础的转换命令是这样trtexec --onnxmobilenetv2.onnx \ --saveEnginemobilenetv2_fp16.engine \ --fp16 \ --workspace2048--fp16表示把能转成半精度的层转成FP16MobileNetV2里的卷积和全连接基本都是精度不敏感算子FP16下精度损失通常非常小。--workspace给TensorRT分配多少显存做图优化一般给1到2GB就够。如果你不指定精度默认是FP32精度最稳但性能提升相对有限。INT8能进一步提速但需要额外提供校准数据校准集就是一组有代表性的输入图片。对图像分类来说INT8推理精度损失通常可控但前提是校准数据集要覆盖真实部署场景的分布。如果不想引入校准复杂度FP16是个性价比很高的中间档。我实测下来MobileNetV2的FP16和FP32在ImageNet类别上的top-1准确率差距通常在0.1%以内对大部分业务场景完全可接受。INT8则要根据任务复杂度评估类别之间特征差异很小时要特别谨慎。5.2 引擎文件与GPU架构绑定问题这是TensorRT一个特别容易让人误解的点。生成的.engine文件不是一个跨平台的模型文件它跟GPU架构绑定甚至跟TensorRT版本有关。你在RTX 30系列上转出的engine拿到RTX 20系列上基本不能直接加载更不用说放到Jetson上。所以正确做法是在目标部署机器上做转换或者把ONNX文件传给部署机再执行trtexec。也有个折中方案把ONNX文件作为发布物程序每次启动时检查是否有匹配的engine文件没有就现场转换。这样可以兼顾跨平台和启动速度。如果你用的是Jetson设备JetPack版本会自带对应版本的TensorRT比如有的JetPack版本默认TensorRT 8.x有的更高直接用系统自带的版本最稳自己另行安装反而容易和CUDA、cuDNN版本打架。5.3 引擎精度验证用同一张图对比PyTorch输出engine生成后第一件事不是接服务而是验证精度。准备一两张有代表性的图片用同一套预处理跑一遍PyTorch模型和TensorRT引擎对比输出logits和softmax后的概率分布。如果FP16引擎和PyTorch FP32的输出差异超过0.05甚至更大就要警惕了。排查顺序是确认预处理完全一致、确认输入数据没被转成奇怪的layout、确认engine确实是FP16而不是INT8、最后才怀疑TensorRT的图层融合改变了数值路径。验证脚本我建议直接写成一个独立的regression脚本每次从ONNX生成新engine之后都自动跑一遍防止TensorRT版本升级或转换参数变化引入回归。6. 部署端推理代码从Python到C的关键差异engine文件有了剩下就是写推理代码。Python加pycuda是最快上手的方式适合原型验证和中等并发场景C API适合对延迟和稳定性要求极高的生产服务。两者核心逻辑一样反序列化engine、创建context、分配显存、拷贝输入、执行推理、拷贝输出。6.1 Python端TensorRT推理的最小实现直接给一个能用的最小实现注释里标清楚每步在做什么import tensorrt as trt import pycuda.driver as cuda import pycuda.autoinit import numpy as np class TRTInference: def __init__(self, engine_path): logger trt.Logger(trt.Logger.WARNING) runtime trt.Runtime(logger) with open(engine_path, rb) as f: engine runtime.deserialize_cuda_engine(f.read()) self.context engine.create_execution_context() self.input_idx engine.get_binding_index(input) self.output_idx engine.get_binding_index(output) # 分配host和device内存 self.input_size trt.volume(engine.get_binding_shape(self.input_idx)) self.output_size trt.volume(engine.get_binding_shape(self.output_idx)) self.host_in cuda.pagelocked_empty(self.input_size, dtypenp.float32) self.host_out cuda.pagelocked_empty(self.output_size, dtypenp.float32) self.device_in cuda.mem_alloc(self.host_in.nbytes) self.device_out cuda.mem_alloc(self.host_out.nbytes) self.stream cuda.Stream() def infer(self, input_np): assert input_np.size self.input_size np.copyto(self.host_in, input_np.ravel()) cuda.memcpy_htod_async(self.device_in, self.host_in, self.stream) self.context.execute_async_v2( bindings[int(self.device_in), int(self.device_out)], stream_handleself.stream.handle ) cuda.memcpy_dtoh_async(self.host_out, self.device_out, self.stream) self.stream.synchronize() return self.host_out.reshape(-1, num_classes) engine TRTInference(mobilenetv2_fp16.engine) logits engine.infer(preprocessed_input)这段代码有几个关键点cuda.pagelocked_empty分配的是页锁定内存用于和显存之间的高速拷贝execute_async_v2是异步执行最后要synchronize等它跑完bindings传的是设备端显存地址列表。6.2 部署端预处理必须和训练对齐部署端最容易翻车的就是预处理。我见过最典型的错误是训练用PIL读图走transforms.Resize部署端用OpenCV读图忘了BGR转RGB结果模型精度直接崩掉。颜色通道顺序错误对分类精度的影响是灾难性的。一个和训练对齐的部署侧预处理流程大致如下假设输入是OpenCV读入的BGR图import cv2 import numpy as np def preprocess(image_bgr): # 转RGB对应训练时PIL读取后的通道顺序 image_rgb cv2.cvtColor(image_bgr, cv2.COLOR_BGR2RGB) # resize到256保持和torchvision Resize一致建议用双线性插值 image_resized cv2.resize(image_rgb, (256, 256), interpolationcv2.INTER_LINEAR) # 中心裁剪224 h, w image_resized.shape[:2] startx (w - 224) // 2 starty (h - 224) // 2 image_cropped image_resized[starty:starty224, startx:startx224] # 归一化 image_np image_cropped.astype(np.float32) / 255.0 mean np.array([0.485, 0.456, 0.406], dtypenp.float32) std np.array([0.229, 0.224, 0.225], dtypenp.float32) image_np (image_np - mean) / std # HWC转CHW并增加batch维 image_tensor np.transpose(image_np, (2, 0, 1)) return np.expand_dims(image_tensor, axis0).astype(np.float32)这里有一点要提torchvision的Resize和OpenCV的resize虽然都用双线性但坐标对齐方式不同直接用OpenCV几乎不可能做到像素级完全一致。如果你们对精度要求极其严格可以换成Pillow在部署环境里做resize和crop减少差异性。不过对大多数图像分类任务来说top-1精度不会因为这个差异产生明显变化但颜色通道顺序必须转。6.3 性能实测对比与并发部署建议以下是我在RTX 3060上针对batch1做的粗略数据单位是毫秒仅供量级参考推理方式平均耗时(ms)说明PyTorch FP32 eager5.2直接用PyTorch推理ONNX Runtime FP323.6ONNX Runtime CUDA EPTensorRT FP322.6默认FP32引擎TensorRT FP161.8半精度引擎batch变大时TensorRT的优势更明显因为图层融合和显存复用的收益在高吞吐场景被放大。如果做在线服务建议直接用异步推理加多stream这样一个请求在GPU上执行的同时另一个请求的预处理和显存拷贝可以并行。如果QPS需求很稳定也可以直接固定batch大小做多实例部署每个实例处理固定batch的请求这样整体吞吐更容易预估。Python部署的便利性和C的极致性能之间我的判断是如果单路推理延迟容忍度在几毫秒以上Python够用如果要求亚毫秒级的极致延迟C更稳。7. 整个链路里的坑位地图我在这条路上踩过且可以避开的最后把这条路上最容易踩的坑集中盘点一下。这些不是教科书里的标准答案是实际项目里一个接一个踩出来的。7.1 动态batch与固定batch的选择动态batch听起来很灵活但实际使用中我发现它有两个问题一是TensorRT在动态shape下不会做最激进的内存布局优化耗时往往比固定shape慢一点二是engine内部显存按最大shape分配如果你声明的maxBatch很大即使实际只跑batch1显存占用也不会降下来。所以我的建议是如果线上请求量相对稳定直接为固定batch生成engine性能最好。如果请求量波动很大可以做一个简单的batch聚合层把一定时间窗口内的请求合并成一个batch然后用固定batch的engine处理。这样比完全动态shape更可控。7.2 版本配置与环境问题TensorRT这边版本对不上是最折磨人的。CUDA版本、cuDNN版本、TensorRT版本、GPU驱动版本每一个都有可能成为瓶颈。比如在Windows上装TensorRT就比Linux麻烦一些因为还要处理大量DLL的PATH环境变量在Linux下用Docker镜像能绕开大多数环境问题。不同年份的Jetson JetPack自带的是不同版本的TensorRT比如当时我用的JetPack里TensorRT是8.x系列对应的PyTorch版本也是JetPack定制版。如果自己用pip随便装一个PyTorch经常会出现CUDA版本不匹配的问题。我的经验是先查清楚目标平台的CUDA版本和TensorRT版本再去匹配PyTorch版本不要反向操作。7.3 部署端精度对不上训练端的排查顺序一旦部署端精度和训练端差很多不要慌按顺序排查预处理是否完全一致。RGB/BGR是否转对resize尺寸是否是224而不是256插值方式是否一致归一化mean/std是否写反。输入数据布局。是NCHW还是NHWCTensorRT的engine输入layout是否和host端数据一致。引擎精度。确认加载的engine确实是FP32或FP16而不是INT8后处理是否重复做了softmax或者把logits当成概率。数据拷贝。查看device内存和host内存的大小是否正确有没有越界读写把数据污染。版本升级回归。重新生成engine后是否又跑过回归脚本。这五步走完绝大多数“部署精度对不上”的问题都能定位。最后再分享一个我个人很推荐的做法把整条链路做成一个流水线脚本从ONNX导出、engine生成、精度回归、性能测试全部串起来。这样每次环境变化、版本升级或者模型迭代都能一键重复验证而不是靠脑子记住每一步怎么操作。我开发时一度因为重复手工执行trtexec和验证脚本浪费了大量时间后来改成流水线脚本之后省心很多建议你也试试。本文还有配套的精品资源点击获取
返回列表