
简介这是一套基于 TensorRT 与 YOLO 算法深度整合的工程化部署资源面向需要将目标检测与实例分割能力落地到实际项目中的开发者、算法研究人员及企业技术团队。资源同时提供 C 与 Python 两套实现路径并兼容 Linux 与 Windows 双平台可用于安防监控、工业质检、智能交通及医疗影像分析等场景的实时推理与性能优化。压缩包共 1523 个文件约 314.08MB以 hpp、h、cpp、cu 等 C 与 CUDA 源码py 脚本、cmake 构建文件、vcxproj 工程配置、dll/lib 动态库以及 md 文档为主兼顾编译、运行与说明查阅。目前已有 307 人学习下载。资源内含完整项目文档与目录结构读者可据此搭建环境、配置数据路径并运行程序快速理解 TensorRT 加速下的检测与分割流程适合作为跨平台部署与算法验证的参考工程。1. 从 PyTorch 到 TensorRTYOLO 实例分割与目标检测的跨平台部署到底在解决什么训练完一个 YOLO 实例分割模型mAP看着不错但一上产线就发现单帧推理要 80msC 服务里还得再套一层 Python 进程间通信Windows 工控机上更是连环境都装不齐。这是很多人从「模型能跑」到「系统能用」之间踩的第一个大坑。TensorRT 部署 YOLO 项目要解决的核心问题就一个把 PyTorch 训练出来的权重经过 ONNX 中转用 TensorRT 做层融合、精度校准和 kernel 自动调优最终在 Linux 和 Windows 上分别用 C 和 Python 两种方式加载同一个 engine让实例分割和目标检测共享一套推理后端。适合谁手里已经有 YOLOv8-seg 或 YOLO11-seg 权重、需要在边缘设备或工控机上做实时推理、并且团队里同时有 Python 算法同学和 C 工程同学的人。下面按「先立住原理再动手复现最后避坑」的节奏拆开讲。2. TensorRT 部署 YOLO 的选型逻辑为什么不是直接跑 PyTorch2.1 实例分割与目标检测在 TensorRT 里的输出差异目标检测的输出相对简单[batch, num_anchors, 4nc]后处理就是置信度过滤加 NMS。实例分割多了一条 mask 分支YOLOv8-seg 的典型输出是[batch, 116, num_anchors]其中 116 4box 80coco 类别 32mask 系数。这 32 个系数要和原型 mask[batch, 32, 160, 160]做矩阵乘法再裁剪到检测框内才能得到每个实例的二值 mask。TensorRT 对这两条分支的处理策略不同。检测头里的卷积、BN、SiLU 会被融合成少量 kernel分割原型分支因为分辨率高、通道少反而容易成为显存带宽瓶颈。我一般会把原型 mask 的输出分辨率在导出 ONNX 时就固定死比如160x160避免 TensorRT 在动态 shape 下反复重选 kernel 导致首次推理特别慢。提示实例分割的 mask 系数分支不要做 INT8 量化实测掉点比检测分支明显常见做法是检测分支 INT8、mask 分支保持 FP16。2.2 C 与 Python 两种部署方式的边界Python 方式适合算法同学快速验证 engine 是否正确、做精度对比、写测试脚本。C 方式适合最终交付尤其是 Windows 工控机没有 Python 环境、或者要求单 exe 分发的场景。两者加载的是同一个.engine文件区别只在运行时 API 的封装。维度Python (pycuda / tensorrt)C (TensorRT Runtime)开发速度快适合调参慢编译链路长部署依赖需要 Python CUDA TensorRT只需 CUDA TensorRT 动态库内存控制GC 管理峰值不可控手动管理可预分配Windows 支持需要对应版本 wheel需要 VS CUDA 工具链适合场景验证、测试、小批量产线、嵌入式、单文件交付选型建议先用 Python 把 ONNX 导出、engine 构建、前后处理全部跑通确认数值对齐后再移植 C。不要一上来就写 C否则精度对不上时你连是导出错了还是 C 后处理写错了都分不清。2.3 ONNX 导出时的三个关键参数导出 ONNX 是整条链路的第一个翻车高发点。以 Ultralytics 的 YOLOv8-seg 为例常见做法是from ultralytics import YOLO model YOLO(yolov8s-seg.pt) model.export( formatonnx, opset12, # TensorRT 8.x 对 opset 12 支持最稳 simplifyTrue, # 去掉冗余算子减少 TensorRT 解析失败 dynamicFalse, # 固定 batch 和输入尺寸避免动态 shape 重选 kernel imgsz(640, 640), # 与训练时一致不要随意改 )opset12是因为 TensorRT 8.6 对 opset 13 以上的某些算子支持不完整尤其是Resize和ScatterND。simplifyTrue会调用 onnx-simplifier 做常量折叠能消掉 YOLO 导出时产生的一批Identity和Concat。dynamicFalse是血泪经验动态 batch 在 TensorRT 里会生成多个 optimization profile首次推理时逐个调优冷启动可能超过 30 秒产线上根本等不起。导出后务必用onnxruntime跑一遍和 PyTorch 输出做数值对比确认最大误差在 1e-3 以内再往下走。3. 用 Python 在 Linux 上跑通 TensorRT engine 的最小闭环3.1 构建 engine 的完整命令与参数说明拿到 ONNX 后用trtexec构建 engine 是最稳的方式比 Python API 更少踩坑trtexec \ --onnxyolov8s-seg.onnx \ --saveEngineyolov8s-seg-fp16.engine \ --fp16 \ --workspace4096 \ --minShapesimages:1x3x640x640 \ --optShapesimages:1x3x640x640 \ --maxShapesimages:1x3x640x640 \ --verbose--fp16在大多数 NVIDIA 显卡上能带来 1.5 到 2 倍加速精度损失通常小于 0.5 mAP。--workspace4096单位是 MB给 TensorRT 4GB 显存做 kernel 调优太小会导致某些层回退到慢速实现。--minShapes/optShapes/maxShapes三个都设成一样等于告诉 TensorRT 只优化这一个 shape构建时间最短、推理最稳。--verbose第一次构建时打开能看到每一层的融合情况确认没有大量Myelin回退。构建完成后engine 文件是跟显卡架构绑定的。在 RTX 4090 上构建的 engine 不能拿到 Jetson Orin 上用必须重新构建。这是很多人部署到边缘设备时第一个翻车点。3.2 Python 推理脚本从加载 engine 到解析 maskimport tensorrt as trt import pycuda.driver as cuda import pycuda.autoinit import numpy as np import cv2 class TRTSegInfer: def __init__(self, engine_path): logger trt.Logger(trt.Logger.WARNING) with open(engine_path, rb) as f, trt.Runtime(logger) as runtime: self.engine runtime.deserialize_cuda_engine(f.read()) self.context self.engine.create_execution_context() # 绑定输入输出 self.inputs, self.outputs, self.bindings [], [], [] self.stream cuda.Stream() for i in range(self.engine.num_bindings): name self.engine.get_binding_name(i) shape self.engine.get_binding_shape(i) dtype trt.nptype(self.engine.get_binding_dtype(i)) size int(np.prod(shape)) host cuda.pagelocked_empty(size, dtype) dev cuda.mem_alloc(host.nbytes) self.bindings.append(int(dev)) if self.engine.binding_is_input(i): self.inputs.append({name: name, host: host, dev: dev, shape: shape}) else: self.outputs.append({name: name, host: host, dev: dev, shape: shape}) def infer(self, img): # 前处理letterbox 到 640x640BGR-RGB归一化 blob cv2.dnn.blobFromImage(img, 1/255.0, (640, 640), swapRBTrue, cropFalse) np.copyto(self.inputs[0][host], blob.ravel()) cuda.memcpy_htod_async(self.inputs[0][dev], self.inputs[0][host], self.stream) self.context.execute_async_v2(bindingsself.bindings, stream_handleself.stream.handle) for out in self.outputs: cuda.memcpy_dtoh_async(out[host], out[dev], self.stream) self.stream.synchronize() return {o[name]: o[host].reshape(o[shape]) for o in self.outputs}这段代码的关键在execute_async_v2和stream.synchronize()的配合。execute_async_v2只是把 kernel 塞进 CUDA stream不阻塞。如果你在synchronize之前就去读host数据拿到的就是上一帧的结果这种玄学 bug 在双缓冲场景下特别难查。pagelocked_empty分配的是锁页内存DMA 拷贝比普通内存快 2 到 3 倍但不要频繁分配释放应该在初始化时一次性建好。后处理部分检测分支做 NMS 得到框mask 分支用sigmoid(coefficients prototypes)得到 mask再按框裁剪并 resize 回原图尺寸。这一步用 numpy 实现即可不要试图在 TensorRT 里做TensorRT 不擅长动态 shape 的 mask 裁剪。3.3 验证数值对齐的检查点跑通之后拿同一张图分别过 PyTorch 和 TensorRT对比三个东西检测框的xyxy坐标误差应小于 1 像素类别置信度误差小于 0.01mask 的 IoU 大于 0.98。如果 mask IoU 偏低先检查原型 mask 的输出分辨率是否和训练时一致再检查 mask 系数是否做了 sigmoid。我见过有人把sigmoid漏掉mask 全是灰的查了一下午。4. C 在 Windows 上的部署链路从 VS 工程到单文件分发4.1 Visual Studio 工程配置的四个必改项Windows 上 C 部署 TensorRT第一关是环境。需要装 CUDA Toolkit、TensorRT 的 Windows zip 包、Visual Studio 2019 或 2022。装完 CUDA 后microsoft visual c 2015-2022 redistributable通常已经带上但 TensorRT 的nvinfer.dll、nvinfer_plugin.dll、cudnn64_8.dll需要手动加到PATH或拷到 exe 同目录。VS 工程里要改四个地方包含目录加TensorRT\include和CUDA\include库目录加TensorRT\lib和CUDA\lib\x64链接器输入加nvinfer.lib、nvinfer_plugin.lib、cudart.libC 语言标准设为 C14 以上TensorRT 8.x 的头文件用了不少 C11 特性。注意Debug 和 Release 模式下链接的库不同TensorRT 的 lib 目录下nvinfer.lib是 Release 版Debug 模式链接会报 LNK2038 运行时库不匹配。常见做法是统一用 Release 模式开发。4.2 C 推理核心代码与内存管理#include NvInfer.h #include cuda_runtime_api.h class TRTEngine { public: bool load(const std::string path) { std::ifstream file(path, std::ios::binary); std::vectorchar data((std::istreambuf_iteratorchar(file)), std::istreambuf_iteratorchar()); runtime_.reset(nvinfer1::createInferRuntime(logger_)); engine_.reset(runtime_-deserializeCudaEngine(data.data(), data.size())); context_.reset(engine_-createExecutionContext()); // 预分配所有 binding 的 device 内存 for (int i 0; i engine_-getNbBindings(); i) { auto dims engine_-getBindingDimensions(i); size_t size 1; for (int j 0; j dims.nbDims; j) size * dims.d[j]; void* ptr nullptr; cudaMalloc(ptr, size * sizeof(float)); buffers_.push_back(ptr); } cudaStreamCreate(stream_); return true; } void infer(float* input_host) { cudaMemcpyAsync(buffers_[0], input_host, 3*640*640*sizeof(float), cudaMemcpyHostToDevice, stream_); context_-enqueueV2(buffers_.data(), stream_, nullptr); cudaMemcpyAsync(output_host_, buffers_[1], output_size_, cudaMemcpyDeviceToHost, stream_); cudaStreamSynchronize(stream_); } private: nvinfer1::ILogger logger_; std::unique_ptrnvinfer1::IRuntime runtime_; std::unique_ptrnvinfer1::ICudaEngine engine_; std::unique_ptrnvinfer1::IExecutionContext context_; std::vectorvoid* buffers_; cudaStream_t stream_; };enqueueV2是异步的cudaStreamSynchronize之前不能读output_host_。buffers_的顺序必须和 engine 的 binding 顺序一致这个顺序在构建 engine 时就固定了可以用engine_-getBindingName(i)打印出来核对。C 里没有 GCcudaMalloc的显存在析构时要cudaFree否则跑几个小时就 OOM。4.3 单文件分发时缺的 DLL 怎么定位Windows 上最烦的是「在我机器上能跑拷到工控机上就缺 DLL」。用dumpbin /dependents your.exe可以列出所有依赖然后逐个确认。常见缺的是cudnn64_8.dll、nvinfer_plugin.dll、msvcp140.dll。前两个从 TensorRT 包里拷最后一个装 VC redistributable 即可。如果工控机没有显卡驱动还要确认驱动版本满足 CUDA 的最低要求这个用nvidia-smi一看便知。5. 避坑与排查实例分割部署里最容易翻车的五件事5.1 现象engine 构建成功但推理输出全零原因ONNX 导出时输入名字和 TensorRT 绑定的名字不一致或者输入没有做归一化。YOLO 导出后输入名通常是images但有些版本是inputtrtexec构建时不会报错推理时喂错 buffer 就全零。解决构建后用trtexec --loadEnginexxx.engine --dumpProfile看每层输出或者用 Python 脚本打印engine.get_binding_name(i)确认输入名和你的前处理代码一致。5.2 现象mask 结果比 PyTorch 差很多框却正常原因原型 mask 分支在 FP16 下精度不够或者后处理里 mask 系数没有做 sigmoid又或者原型 mask 的 resize 用了双线性而训练时用的是最近邻。解决先把 mask 分支单独用 FP32 构建一个 engine 对比确认是精度问题还是逻辑问题。如果是精度把原型 mask 输出层设为 FP32如果是逻辑逐行对比 PyTorch 后处理和你的 C/Python 后处理。5.3 现象Windows 上nvinfer.dll加载失败报 126 错误原因TensorRT 的 DLL 依赖 CUDA 的cudart64_xx.dll而 CUDA 的 bin 目录没在PATH里或者版本不匹配。解决把 CUDA 的bin目录加到系统PATH或者把cudart64_xx.dll拷到 exe 同目录。用Dependencies.exe或dumpbin看具体缺哪个。5.4 现象首次推理特别慢之后正常原因TensorRT 在首次enqueue时会做 kernel 自动调优尤其是动态 shape 下会遍历多个 profile。解决构建 engine 时固定 shape或者用trtexec的--saveEngine保存调优后的 engine。如果必须动态 shape在服务启动时先跑一次 warmup把调优结果缓存住。5.5 现象Linux 上跑得好好的 engine拷到 Windows 上加载失败原因engine 文件跟 GPU 架构和 TensorRT 版本绑定跨平台不通用。解决在目标平台上重新用 ONNX 构建 engine。如果目标平台没有构建环境可以在同架构的机器上构建后拷贝但 TensorRT 版本必须完全一致。6. 进阶技巧用 Python 做精度基准用 C 做吞吐压测部署完成后怎么确认这套方案值得上产线我的习惯是分两步验证。第一步用 Python 脚本做精度基准准备 200 张有标注的图分别跑 PyTorch 和 TensorRT算mAP50-95和mask mAP差值在 1 个点以内才算过关。第二步用 C 写一个压测程序连续跑 1000 次推理统计 P50、P95、P99 延迟和显存占用。# 精度基准脚本核心逻辑 from pycocotools.coco import COCO from pycocotools.cocoeval import COCOeval coco_gt COCO(annotations.json) results [] for img_path in test_images: preds trt_infer(img_path) # 返回检测框和 mask for p in preds: results.append({ image_id: img_id, category_id: p[cls], bbox: p[xywh], score: p[conf], segmentation: p[mask_rle], }) coco_dt coco_gt.loadRes(results) evaluator COCOeval(coco_gt, coco_dt, segm) evaluator.evaluate() evaluator.accumulate() evaluator.summarize()压测时要注意第一次推理不计入统计因为包含 kernel 调优。P99 延迟如果超过 P50 的 3 倍说明有显存抖动或 CPU 后处理成了瓶颈常见做法是把 NMS 也放到 GPU 上做或者用多 stream 并行。我自己的习惯是每次改完 ONNX 导出参数或 TensorRT 构建参数都重新跑一遍精度基准把mAP和延迟记在一个表格里。有一次为了省事跳过了基准结果 INT8 校准集选得不好检测分支掉了 4 个点上线后才发现又回滚重来。这个后悔药不好吃希望帮到你。本文还有配套的精品资源点击获取