先说结论:用 tensorrtx 在 CentOS 7 上部署 YOLOv5,预训练模型只是“开胃菜”,真正考验人的是自训练模型那套适配流程。这篇文章我会从环境选型、权重转换、engine 构建一直到推理验证完整走一遍,把我实际踩过的坑和验证过的参数全部摊开讲。无论你是刚接触部署的新手,还是已经在 x86 平台折腾过 ONNX 的老手,这篇都值得你花十分钟看完。
1. 环境准备:CentOS 7 上 TensorRT 部署的版本选型
1.1 为什么 CentOS 7 要特别注意 CUDA 版本
CentOS 7 是个老系统,内核停留在 3.10,glibc 版本也不高,这决定了你装不了太新的 CUDA。我第一次在这上面装 CUDA 12.x 的时候,直接碰到GLIBC_2.29 not found这种编译期错误,后来老老实实退回 CUDA 11.x 才一切正常。
如果你是自己编译 tensorrtx 源码,或者要跑 TensorRT 8.2 以上的版本,我建议按下面这套组合来配:
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| 操作系统 | CentOS 7.9 | 内核 3.10,注意不要升级内核,没必要 |
| NVIDIA 驱动 | 470.57.02 或 525.x | 驱动版本别太新,否则老系统兼容性麻烦 |
| CUDA | 11.4 / 11.6 | 建议 11.4 最稳,TensorRT 官方也支持 |
| cuDNN | 8.4.1 对应版本 | 和 CUDA 匹配即可 |
| TensorRT | 8.2.x / 8.4.x | tensorrtx 老版本对 8.4 支持最好 |
| Python | 3.8 / 3.9 | PyTorch 1.10 左右配 Python 3.8 最省事 |
这套组合我实测下来编译零报错,部署推理也稳定。你如果手头已经有 CUDA 11.2 或 11.3,也完全可以,只要别低于 11.0,TensorRT 8.2 都支持。
1.2 TensorRT 安装方式选择
TensorRT 的安装有三种方式:tar 包解压、deb/rpm 包安装、pip 安装。CentOS 7 没有 deb 包,所以我只比较 tar 包和 pip 方式。
tar 包解压(推荐)
去官网下载 TensorRT 8.2.x 的 Linux x86_64 tar 包,解压后配置环境变量就行:
tar -xzf TensorRT-8.2.5.1.Linux.x86_64-gnu.cuda-11.4.cudnn8.2.tar.gz cd TensorRT-8.2.5.1 echo "export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$(pwd)/lib" >> ~/.bashrc echo "export PATH=$PATH:$(pwd)/bin" >> ~/.bashrc source ~/.bashrc这个方式的好处是干净、可控,方便随时切换版本。坏处是 Python 包需要自己用 pip 装:
pip install tensorrt-8.2.5.1-cp38-none-linux_x86_64.whlpip 直接安装
如果你的 PyTorch 环境是 conda 管理的,其实更建议直接用 pip 装 TensorRT,依赖管理更省心:
pip install nvidia-tensorrt==8.2.5.1但注意,pip 方式装的是英伟达托管的轮子,和官网 tar 包含量不完全一样,有些工具如trtexec可能不在里面。我一般还是习惯 tar 包为主、pip 为辅。
1.3 验证 TensorRT 是否装好
装完以后先简单验证一下:
python -c "import tensorrt as trt; print(trt.__version__)"如果输出了类似8.2.5.1的版本号,说明 Python 端没问题。再执行一次trtexec --help,确认命令行工具也在。只有 Python 包但缺 trtexec 的情况我遇到过,多半是 tar 包的环境变量没配好。
2. 模型转换链路:从 PyTorch 权重到 TensorRT 引擎
2.1 用 tensorrtx 把 pt 权重导出为 wts 格式
TensorRT 不能直接吃 PyTorch 的.pt文件,所以中间要加一步转换。tensorrtx 的 yolov5 工程里提供了一个gen_wts.py脚本,作用就是把 PyTorch 权重序列化成自定义的 wts 文本格式。
我的转换命令是这样的:
git clone https://github.com/wang-xinyu/tensorrtx cd tensorrtx/yolov5 # 注意要把 tensorrtx 的模型定义路径指到你的 YOLOv5 工程 python gen_wts.py -w /path/to/yolov5s.pt -o /path/to/yolov5s.wts说下这里面容易忽略的几个点:
gen_wts.py里 import 的是 YOLOv5 官方的模型定义,所以你必须保证 YOLOv5 的代码版本和训练时一致。如果训练用的是某个 fork 版本,直接拿官方仓库的 gen_wts.py 很可能会 key 对不上,转换出来的权重是歪的。- wts 文件是个纯文本文件,第一行是权重数量,后面每行是
[name] [value]格式。你可以用head -5 yolov5s.wts看一眼格式对不对。 - 转换过程很快,基本几秒到十几秒就完事,跟模型大小无关,因为它只是把权重搬出来重排一下,不涉及计算图转换。
2.2 tensorrtx 源码编译的核心流程
tensorrtx 的编译比很多人想象的要简单,但也比很多人想象的更容易出错。最核心的一步是先改yololayer.h里的类别数/输入尺寸,再编译。
进入 tensorrtx/yolov5 目录后,打开yololayer.h:
static constexpr int CLASS_NUM = 80; // 改成你自己的类别数,比如 2 static constexpr int INPUT_H = 640; // 输入高度 static constexpr int INPUT_W = 640; // 输入宽度改完后执行:
mkdir build && cd build cmake .. make -j$(nproc)如果 cmake 找不到 TensorRT 路径,你需要手动指定。在 CMakeLists.txt 里最直接的方法是设置环境变量:
export TensorRT_INCLUDE_DIR=/path/to/TensorRT-8.2.5.1/include export TensorRT_LIB_DIR=/path/to/TensorRT-8.2.5.1/lib mkdir build && cd build cmake .. && make -j$(nproc)编译产物里有几个可执行文件,包括yolov5_det、yolov5这种。这些工具的名字每个版本略有差异,但作用类似。
2.3 构建 engine 文件
拿到 wts 后,通过 tensorrtx 的可执行文件构建 engine。这一步本质上是把 wts 加载到 TensorRT 里做网络定义和图优化,最终序列化成.engine文件。
对于预训练模型(coco 80 类),直接用默认参数跑:
./yolov5_det -s /path/to/yolov5s.wts /path/to/yolov5s.engine s最后一个参数s代表网络结构为 YOLOv5s,如果你的是 m/l/x,分别传m、l、x。这里一定不能传错,yololayer 的通道数匹配不上会直接报错或出垃圾结果。
构建过程会输出每层的信息,看到类似:
[MemcpyState] HostToDevice ... [Building engine] done就是成功了。构建时间大概几十秒,取决你的 GPU 算力和模型大小。
3. 预训练模型推理:从官网权重到实时检测
3.1 预训练模型的转换实操
预训练模型就是官方 YOLOv5s/m/l/x 的权重。我去官网下载了一个 YOLOv5s 的 pt 文件,先做权重导出:
python gen_wts.py -w yolov5s.pt -o yolov5s.wts然后构建 engine:
./yolov5_det -s yolov5s.wts yolov5s.engine s这里我建议你全程加-m参数来启用 FP16 推理,命令是:
./yolov5_det -s yolov5s.wts yolov5s_fp16.engine s -mFP16 在绝大多数 GPU 上都能用,TensorRT 只要检测到硬件支持就会优化。速度通常能提升 30% 到 50%,精度损失对检测任务可以忽略。
3.2 用 tensorrtx 的推理模块跑图片验证
推理命令也很直观:
./yolov5_det -d yolov5s.engine ./picture.jpg如果你是自己写的推理代码,核心就是加载 engine、创建 context、绑定输入输出 buffer、把图像预处理后丢进去、再拿输出的检测结果做后处理。tensorrtx 自带的detect函数会在图片上画框并存成带_det后缀的图。
实测下来,YOLOv5s 在 1080Ti 上用 FP32 engine 跑 640x640 输入,大概 8ms 一帧;开 FP16 能到 5ms 左右。如果你用 TensorRT 8.2 默认的 batch=1 推理,这个速度已经非常接近实时传输的极限了。
要注意的是,官方预训练模型的输出类别是 COCO 的 80 类,画框时默认会打印 80 类里的所有检测目标。如果你只想要某个类别的结果,需要自己改后处理里的过滤逻辑或者改类别列表。
4. 自训练模型适配:踩坑重点与全流程演示
4.1 自训练模型与预训练模型的本质差异
自训练模型和预训练模型在部署上最大的区别就三个:
- 类别数不一样,比如你训练一个只检测饼干包装的模型,可能只有 2 个类别;
- 输入尺寸可能不一样,有人用 416x416 训练,有人用 640x640;
- anchors 可能被重新聚类过,或者用默认 pytorch 的预设 anchors。
tensorrtx 的 yololayer 是直接写死类别数和输入尺寸的,所以转换前必须先把这两个参数对齐。anchors 的问题相对好处理,因为 tensorrtx 默认用的是官方 yolov5 的 anchors,如果你的模型训练时没有自定义 anchors,那是可以被忽略的。
我自己训练的模型是 2 类目标检测场景——工业零件缺陷识别,所以类别数就从 80 改成了 2。
4.2 修改源码参数的正确姿势
打开tensorrtx/yolov5/yololayer.h,改这两个宏:
static constexpr int CLASS_NUM = 2; static constexpr int INPUT_H = 640; static constexpr int INPUT_W = 640;如果训练时用的输入尺寸是 416x416,就把 INPUT_H 和 INPUT_W 都改成 416。这一步不改后面构建 engine 不会报错,但推理出来的坐标会完全错位,画框位置全乱。
然后还要改yolov5.cpp文件里的类别名称列表:
static std::vector<std::string> class_names = { "defect_type_1", "defect_type_2" };注意类别名称的排列顺序必须和训练时数据集的类别顺序完全一致。这个顺序就是你训练时data.yaml里names的顺序。我曾经因为调换了类别顺序,导致框是对的但是标签全错了,排查了半天才发现是这里的问题。
4.3 自训练模型构建 engine 的完整命令
源码改好后重新编译:
cd tensorrtx/yolov5/build cmake .. && make -j$(nproc)然后导出 wts:
python gen_wts.py -w /path/to/your-model.pt -o /path/to/your-model.wts再构建 engine:
./yolov5_det -s /path/to/your-model.wts your-model.engine s -m如果一切正常,构建完的 engine 文件就可以直接拿来做推理了。
这里有个易错点我特意提醒一下:如果你的模型是best.pt而不是last.pt,必须确保训练时的模型结构没有加什么尾层包装。常见的一个坑是训练时用了ema模型或者自定义的Model子类,在导出 wts 时 gen_wts.py 取不到正确的 state_dict。解决办法是先把 pt 还原成官方 YOLOv5 的 model 结构再导出。
4.4 推理适配:加载 engine 后注意输出 tensor 个数
自训练模型的推理流程和预训练模型一样,但要注意 TensorRT 输出 tensor 的维度。yolov5 的三个输出层每组是[batch, 3*(5+CLASS_NUM), grid_h, grid_w],CLASS_NUM 改了之后这一维度也会变。tensorrtx 的代码会自动适配,所以只要你修改的 CLASS_NUM 正确,输出 tensor 个数不会变,仍然是三个。
推理命令照旧:
./yolov5_det -d your-model.engine ./test_defect.jpg如果你是自己写的后端推理服务,记得后处理里的num_classes参数也要同步改成 2。我自己写的 C++ 推理服务里,这个参数是在配置项里传的,跟 engine 构建时的 CLASS_NUM 必须一致,否则解码出来的置信度坐标全乱。
4.5 自训练模型的验证与调优经验
模型部署完,先不要急着上生产,拿一批测试图验证三件事:
- 框位置是否准确贴合目标;
- 类别标签是否和实际目标一致;
- 置信度阈值是否要调整。
自训练模型普遍在特定场景下置信度跟 COCO 预训练模型不同,可能你的模型阈值设 0.3 就很准,也可能要设 0.6。tensorrtx 自带的检测代码里默认阈值是 0.25 置信度 + 0.45 NMS IoU,如果你训练时针对高置信度做了优化,建议在推理代码里调成 0.4~0.5 看看效果。
5. 实测中的常见问题与排查技巧
5.1 常见编译错误与解决方案速查
| 问题现象 | 原因 | 解决方案 |
|---|---|---|
GLIBC_2.29 not found | CUDA 版本太新,CentOS 7 glibc 不支持 | 换 CUDA 11.4/11.6,不要用 12.x |
cuda_runtime.h: No such file | cmake 没找到 CUDA 路径 | 确认CUDA_HOME环境变量设置,或手动改 CMakeLists |
NvInfer.h: No such file | TensorRT include 路径不对 | export TensorRT_INCLUDE_DIR 指向 include 目录 |
undefined reference to nvinfer | TensorRT lib 路径不对 | export TensorRT_LIB_DIR,并确认 LD_LIBRARY_PATH |
yololayer: Assertion failed | CLASS_NUM 或代码版本和权重不匹配 | 核对训练代码与 tensorrtx 版本对应关系 |
这些错误大多不是技术问题,而是环境变量配置问题。CentOS 7 命令行下环境变量容易在重启后失效,建议把相关 export 写进/etc/profile.d/tensorrt.sh里,开机自动加载。
5.2 推理结果错乱的排查思路
如果你构建 engine 成功,但推理出来框画得乱七八糟或者没有任何输出,按下面顺序排查:
第一,看 wts 是否正确。重新跑python gen_wts.py,注意不要用torch.load时自带的map_location参数导致设备不匹配的坑。
第二,看 CLASS_NUM 和 INPUT_H/W 是否与训练时一致。很多人改了 CLASS_NUM 忘了改输入尺寸,结果检测框整体偏移。
第三,看 anchors 设置。tensorrtx 在yololayer.cu里写死了 anchors。如果你的模型在训练时用了自定义 anchors,就得把那三个特征层的 anchors 替换成自己训练时的值。默认 anchors 在自定义小目标场景下效果会很差。
第四,看预处理和后处理参数。YOLOv5 在推理时需要做 letterbox 而不是直接 resize,tensorrtx 的检测代码里默认做了这个处理,但如果你自己写推理代码,很容易用成严格的 resize,导致框位置偏移。
5.3 性能调优:FP16、batch 和显存占用
TensorRT 的性能调优主要看三块。
FP16 是性价比最高的优化。在构建 engine 命令里加-m参数就能开启。我在自训练模型上实测下来 FP16 比 FP32 快大约 35%,精度在测试集上从 mAP 0.891 下降到 0.879,影响很小。
批处理提升吞吐。tensorrtx 的示例默认 batch=1,如果你要部署成服务端接口,可以在构建 engine 时指定-b 4这类参数。但注意,自训练模型在 batch>1 时显存占用会明显增加,要平衡好 batch 大小和显存限制。
减少显存占用的话,TensorRT 构建时加的-x参数可以降低一些工作空间内存,不过推理性能可能略降。对显存吃紧的环境比较友好。
6. 附加工具链:trtexec 的妙用
tensorrtx 之外,TensorRT 自带的trtexec工具值得单独说一下。它可以用来快速验证一个 wts 转换或者 engine 的性能,不需要写任何代码。
trtexec --loadEngine=yolov5s.engine --fp16 --shapes=input:1x3x640x640这个命令会加载 engine,做一次推理,并输出时间统计。如果你怀疑自己的推理代码性能有问题,可以先拿 trtexec 跑一遍,看基准时间是多少,排掉代码因素。
trtexec 也可以直接--buildOnly来做引擎构建的验证,省去写 CMake 的功夫。不过我大多数时候还是直接跑 tensorrtx 的可执行文件,因为后者已经带了后处理和画框,调试图像结果更直观。
7. 最后的实操建议
在整个 CentOS 7 + tensorrtx + YOLOv5 的部署过程里,我觉得最值得留意的其实是两件事:一是版本匹配,二是参数一致。版本匹配解决“能不能跑起来”的问题,参数一致决定“跑起来之后结果对不对”的问题。
我给自己的项目定了一条规范:模型的训练参数在训练结束后立刻导出一份部署配置清单,里面包含类别数、类别名称顺序、输入尺寸、anchors、置信度阈值。后面部署时直接照抄那边配置改 tensorrtx 源码,就可以把出错概率降到最低。
另一个建议是,生产环境尽量把 engine 文件的生成放到构建脚本里,不要每次启动服务都重新构建。构建 engine 是计算密集操作,在低端 GPU 上可能要几分钟,而加载已经序列化的 engine 文件只要几百毫秒。你完全可以把构建过程放进 CI/CD 流水线里,或者第一次启动时构建完存下来,之后直接用。
如果你打算把自训练模型部署成 HTTP 服务,后处理里的 NMS 自己实现一遍也不难,但更稳妥的是直接用 tensorrtx 里实现好的cudaNms解码。我最早想用自己的 NMS 替换成更高效的 YOLOv5 原版 NMS 逻辑,结果花了不少时间对坐标映射,最后发现用 tensorrtx 自带的就够了。
这套流程我还是很推荐的,尤其是 CentOS 7 这种老平台上,tensorrtx 的源码方式比 onnx-tensorrt 要少走太多弯路。你也按我上面的顺序一步步来,基本可以避开大部分初学者会踩的雷。