ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Windows下TensorRT 7.2.3.4部署与INT8量化实践

Windows下TensorRT 7.2.3.4部署与INT8量化实践 简介TensorRT 7.2.3.4 是 NVIDIA 面向 Windows 10 64 位系统推出的深度学习推理优化库与 CUDA 11.0、cuDNN 8.1 严格配套可解决模型参数解析、计算图优化、内存复用等部署环节中的关键问题适合自动驾驶、视频分析、智能物联网等需要低延迟推理的开发者。压缩包共 367 个文件约 493MB内部结构清晰67 个头文件与 47 个 C 源文件支撑二次开发50 个批处理脚本和 13 个 Python 脚本用于自动化构建及模型转换同时包括动态链接库、静态库、ONNX/UFF/Caffe 样例模型、API 文档、PDF 指南以及若干工具与示例配置能在 Visual Studio 等主流 IDE 中直接引用和调测。借助这些材料开发者可快速掌握 TensorRT 的模型解析、层优化、INT8 量化与多 GPU 分配等能力从而显著降低推理延迟、提升吞吐量。包内附带的性能分析工具和示例脚本还可帮助对比优化前后效果缩短应用落地周期。目前已有 186 人浏览学习适合中高级算法工程师和推理平台开发者据此搭建高可用推理环境。1. 为什么 7.2.3.4 这个老版本现在还有人在翻拿到手的是一个名为TensorRT-7.2.3.4.Windows10.x86-64.cuda-11.0.cudnn8.1.zip的压缩包解压后除了常见的 include、lib、bin 目录还有一串batch_calibration0.batch到batch_calibration41.batch之类的文件。这些文件不是多余的垃圾而是 INT8 量化校准时生成的激活值分布缓存。在 Windows 10 上用 TensorRT 做推理加速很多人第一反应是去官网下载最新版但实际部署时往往会遇到显卡驱动、CUDA 运行时、cuDNN 以及 TensorRT 四者版本必须相互咬合的问题。7.2.3.4 这个版本恰好对应 CUDA 11.0 与 cuDNN 8.1适合还在使用 RTX 30 系列、A100 或者在老驱动环境里维护生产项目的工程师。需要先说明TensorRT 本身不负责训练它只干一件事把训练好的模型解析成高度优化的推理引擎再通过算子融合、精度校准、显存复用等手段把 GPU 用满。下面直接从版本匹配讲起先搞清楚为什么不能随便换 CUDA 或 cuDNN 版本。2. TensorRT 7.2.3.4 的版本匹配CUDA 11.0 与 cuDNN 8.1 缺一不可TensorRT 对运行环境的依赖是嵌套的TensorRT 编译时链接了某个版本的 CUDA runtime比如 cudart64_110.dll以及 cuDNN 的 cudnn64_8.dll。如果你本机装的是其他版本比如 CUDA 11.1 或者 cuDNN 8.0DLL 符号不匹配就会在加载 nvinfer.dll 时直接报Could not load library cudnn64_8.dll或CUDA runtime version mismatch。在 Windows 上这种错误尤其常见因为 Windows 不像 Linux 那样能通过ldd轻松追踪依赖错误信息也经常被吞掉。2.1 先看驱动nvidia-smi、nvcc 与动态库三层检查很多用户下载了 TensorRT 之后直接开始转换模型报错再去查原因效率很低。正确顺序是先确认三个层面的版本驱动层、工具包层、运行时层。驱动层用nvidia-smi查看工具包层用nvcc --version运行时层则是检查 CUDA 安装目录下的 DLL 文件。这个层级关系在 Windows 上尤其容易绕晕因为nvidia-smi显示的“CUDA Version”实际是驱动支持的最高版本并不表示你已经装好了对应版本的 CUDA Toolkit。nvidia-smi nvcc --version如果nvidia-smi输出中显示 Driver Version 为 461.33 以上而nvcc显示 release 11.0说明基本路径正确。但 TensorRT 运行时不依赖nvcc它依赖 bin 目录下的cudart64_110.dll。如果这个 DLL 不在 PATH 里TensorRT 会在加载时失败。你可以在 CMD 中执行where cudart64_110.dll来确认当前解析到的路径是不是C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.0\bin。如果解析到了别的版本目录说明 PATH 顺序有问题。cuDNN 的检查则更直接打开 CUDA 安装目录下的include\cudnn_version.h8.x 是这个文件旧版是cudnn.h搜索CUDNN_MAJOR和CUDNN_MINOR。TensorRT 7.2.3.4 要求 CUDNN_MAJOR 为 8CUDNN_MINOR 为 1补丁号任意。下面是实际依赖组件版本对照表组件版本要求检查方式GPU 驱动461.33 及以上nvidia-smiCUDA Toolkit11.0.x必须存在 cudart64_110.dllwhere cudart64_110.dllcuDNN8.1.xCUDNN_MAJOR8, CUDNN_MINOR1查看 cudnn_version.hMSVC 运行库VS2015~2019 的 VC_redist控制面板查看已安装程序Python可选3.6~3.8对应 cp36/cp37/cp38 wheelpython --version2.2 MSVC 运行库和 Python 版本的隐性约束很多人忽略 MSVC 运行库直到出现VCRUNTIME140.dll缺失。TensorRT 7.2 的 nvinfer.dll 是用 MSVC 编译的目标机器上必须有对应的 VC Redistributable。建议直接安装 VS2019 x64 运行库这会在系统目录里放好vcruntime140.dll、msvcp140.dll等文件。另外如果你打算用 Python API7.2.3.4 的官方 wheel 只发布到 Python 3.8。在 Python 3.9 以上的环境里强行 pip 安装会报“没有匹配的版本”这不是网络问题而是这个版本确实没有对应的轮子。我一般用 conda 建一个 Python 3.7 环境来跑校准脚本C 推理仍然走 trtexec 或独立进程。这里还要注意一个常见误用TensorRT 7.2.3.4 支持 Ampere 架构的sm_86但前提是驱动版本不能太老。如果你的显卡是 RTX 3060、3080 或 A100而驱动停在 460 以下那 CUDA 11.0 的 runtime 虽然能加载但运行时算子编译会报no kernel image available for execution on the device。反过来如果你在 Jetson Orin 上看到有人讨论“降 TensorRT 版本”那和桌面版是两码事Orin 必须用 JetPack 自带的 deb 包不能把 Windows 的 nvinfer.dll 直接拷贝过去。总之版本匹配不是“装上就行”的事。我建议把上面三个检查命令写成一个 bat 脚本在新机器上第一步先跑能避免后面所有隐性故障。接下来进入实际部署。3. Windows 10 x86-64 下的解压部署与 trtexec 自检压缩包解压后顶层有bin、include、lib、doc、data、targets等目录。其中lib下包含nvinfer.dll、nvparsers.dll、nvonnxparser.dll等动态链接库bin里有trtexec.exe——这是最直接的命令行工具不需要写代码就能完成模型转换和性能测试。doc目录里是 API 参考 PDFdata目录一般放着示例模型权重但在 Windows 版本里可能为空。3.1 解压后目录结构里哪些文件真正参与运行运行时最关键的是lib\nvinfer.dll所有上层的 ONNX 解析、Caffe 解析都汇聚到它身上。nvonnxparser.dll负责解析 ONNX 模型如果你只用trtexec --onnx这个 DLL 缺了会在加载时立刻报错。nvparsers.dll则是给旧版 Caffe/UFF 用的现在基本用不上。include目录下的头文件用于自己写 C 推理程序比如NvInfer.h、NvOnnxParser.h。targets里主要存放 x86-64 架构的静态库和 Python 包如果你要用 Python建议直接用pip install tensorrt-7.2.3.4-cp37-cp38-none-win_amd64.whl位置通常在targets\x86_64-windows-msvc下。先把环境变量配好。Windows 下推荐用 PowerShell 以用户级别临时设置避免污染系统 PATH$env:CUDA_PATH C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.0 $env:TENSORRT_ROOT D:\AI\tensorrt\TensorRT-7.2.3.4 $env:PATH $env:TENSORRT_ROOT\lib;$env:CUDA_PATH\bin;$env:PATH这段命令的作用是让系统在运行时找到nvinfer.dll、cudart64_110.dll和cudnn64_8.dll。如果你的 cuDNN 是手动解压并拷贝到 CUDA 目录里的就不需要额外加路径如果 cuDNN 放在独立目录还要把 cuDNN 的bin也加入 PATH。注意 PowerShell 中拼接 PATH 时不要漏掉最后的$env:PATH否则会清空所有系统路径。3.2 环境变量配置与 trtexec 命令行验证配好后换到 TensorRT 的 bin 目录跑一下自检cd D:\AI\tensorrt\TensorRT-7.2.3.4\bin .\trtexec.exe --help如果能看到一长串参数说明说明 DLL 装载成功。接着用一个小型 ONNX 模型做一次真实转换比如你先在 PyTorch 里导出resnet18.onnx然后执行.\trtexec.exe --onnxresnet18.onnx --saveEngineresnet18.engine --fp16这里--onnx指定输入模型--saveEngine指定输出的 engine 序列化文件--fp16开启半精度推理。该命令会加载 ONNX 模型、分析层图、融合算子最终生成二进制 engine。如果这一步能顺利跑完你的基础环境就没有问题了。观察输出尾部会看到Average over 10 runs: 2.3 ms这样的时间报告它是 batch size 为 1 时的平均推理耗时。如果转换失败用--verbose重新跑.\trtexec.exe --onnxresnet18.onnx --saveEngineresnet18.engine --verbose日志里会打印出[E]开头的错误信息绝大多数是算子类型不支持或维度不匹配。7.2.3.4 对动态 shape 的支持是存在的但要求 ONNX 模型明确声明 dynamic axes。如果你导出的模型把 batch 维固定成了 1后面想换 batch 就会报维度错误。因此导出 ONNX 时最好显式设置 dynamic_axesimport torch.onnx torch.onnx.export( model, dummy_input, resnet18.onnx, input_names[input], output_names[output], dynamic_axes{input: {0: batch}, output: {0: batch}} )dynamic_axes里声明了第 0 维可变这样 TensorRT 才有条件做动态 batch 优化。如果你的模型还包含可变分辨率的输入还需要把第 2、3 维也设置为动态后面第 5 章再展开讲。3.3 DLL 装载失败时的排查顺序很多用户在 Windows 上第一次跑trtexec会遇到The code execution cannot proceed because nvinfer.dll was not found。这个问题往往不是 nvinfer.dll 不存在而是 PATH 没有生效。先确认当前终端是管理员权限再执行echo $env:PATH看 TensorRT 的 lib 和 CUDA bin 是否都在列表里。如果路径没问题但依然报错考虑是否缺少cudnn64_8.dll——你可以在 TensorRT 的 lib 目录下创建一个包含cublas64_11.dll、cudnn64_8.dll的软链接目录然后把该目录加入 PATH但更推荐直接使用官方 cuDNN 安装包避免手工拷贝导致的版本混乱。还有一种情况是系统里装了两个 CUDA 版本。Windows 的环境变量是全局的C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.0\bin和v11.1\bin同时存在时PATH 靠前的一个会先被搜索。如果 TensorRT 需要cublas64_11.dll它会在 PATH 里找到 v11.1 的版本然后因为符号版本不同而崩溃。我一般在项目根目录写一个set-env.bat每次编译或运行前先执行它把三个关键路径固定到最前面echo off set CUDA_PATHC:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.0 set TENSORRT_ROOTD:\AI\tensorrt\TensorRT-7.2.3.4 set PATH%TENSORRT_ROOT%\lib;%CUDA_PATH%\bin;%PATH%4. 用 trtexec 做 ONNX 转换与 INT8 量化校准缓存你手里的压缩包里出现了batch_calibration0.batch到batch_calibration41.batch这样一堆文件它们是在 INT8 量化校准过程中生成的中间产物。TensorRT 做 INT8 量化不是简单地把权重变成 int8而是通过一个校准数据集观察每一层激活值的分布然后选择合适的量化阈值。这里的.batch文件正是每个 batch 校准数据的临时存储通常由自定义的IInt8Calibrator在getBatch阶段写入磁盘以备后续重放或复现。4.1 batch_calibration*.batch 是从哪里来的在 TensorRT 7.x 中INT8 校准器有几种IInt8EntropyCalibrator、IInt8EntropyCalibrator2、IInt8MinMaxCalibrator。最常用的是EntropyCalibrator2它基于信息熵选择量化阈值。校准过程是这样的先从校准集中取一个 batch 的输入数据preprocess 成模型输入的 NCHW 格式然后通过get_batch返回给 TensorRT。TensorRT 前向跑一遍 FP32 模型统计每一层输出的激活值直方图。跑完若干个 batch 后再根据直方图计算每个张量的量化 scale。如果你在校准器的get_batch里加了 dump 逻辑把原始输入数据保存成.batch文件就得到了压缩包里那一串文件。下面是一个校准器伪代码描述.batch文件的写入逻辑import tensorrt as trt import numpy as np class MyCalibrator(trt.IInt8EntropyCalibrator2): def __init__(self, data_loader, cache_file): super().__init__() self.data_loader data_loader self.cache_file cache_file self.buffers [] self.cur_idx 0 def get_batch(self, names): # 从数据迭代器中取一个 batch batch next(self.data_loader, None) if batch is None: return None # 将 batch 拷入 GPU 或 pinned memory for i, buf in enumerate(self.buffers): buf.copy_from(batch[i]) # 将当前 batch 的原始数据 dump 到磁盘 np.save(fbatch_calibration{self.cur_idx}.batch, batch) self.cur_idx 1 return self.buffersget_batch被 TensorRT 在校准循环中反复调用每调用一次返回一组输入数据。如果校准数据集有 336 张图分成了 42 个 batch就会生成 42 个.batch文件。文件名从 0 开始编号与当前项目的模型结构、输入分辨率强相关。这也解释了为什么压缩包里会出现几十个 batch 文件——它们不是模型而是能被校准器重新加载的数据镜像。4.2 INT8 转换命令与参数逐项拆解在 7.2.3.4 中如果已经有了校准缓存文件calib.batch可以用trtexec直接加载并完成 INT8 engine 转换.\trtexec.exe --onnxyolov5s.onnx --saveEngineyolov5s_int8.engine ^ --int8 --calibcalib.batch --batch8 --workspace2048逐项拆解参数--int8启用 INT8 精度TensorRT 会把部分层转换成 INT8 计算。不是所有层都会被量化遇到不支持的层会自动保留 FP32。--calibcalib.batch指定校准缓存文件。如果该文件不存在TensorRT 会尝试重新生成校准缓存但此时必须配合--calibrationData参数指定包含原始图像的目录。--batch8校准和推理时的 batch size。7.2.3.4 支持动态 batch但静态 batch 的 engine 不能直接跨 batch 复用所以生产环境一般用动态 shape。--workspace2048指定 GPU 临时工作区大小单位 MB。设置太小会导致某些层无法融合新版本 TensorRT 已经改名--memPoolSize但在 7.2 中仍是旧写法。如果你打算用 Python API 而不是命令行流程大致是创建Builder设置INT8标志把校准器对象传给config.int8_calibrator然后build_engine。校准器必须能正确处理get_batch_size和get_batch两个方法。需要注意的是get_batch返回的地址数组必须是输入张量在 GPU 上的内存地址而不是 CPU 数据。如果直接从 CPU 传numpy数组TensorRT 读取时会出现非法内存访问导致程序崩溃。在 Windows 上这种崩溃常常表现为“程序停止工作”没有明确错误码。如果你用trtexec --calib时发现加载缓存文件报错大概率是格式不一致。TensorRT 官方的校准缓存文件其实是文本格式内容形如TRT-7000-EntropyCalibration2 conv1_1: 0.123456 0.654321 ...trtexec的--calib参数接受的是这种官方缓存文本不是原始的.batch二进制文件。这一点非常容易混淆。你压缩包里如果是自定义的.batch文件那必须通过自己的校准器重新生成官方格式的缓存或者把加载逻辑写进 Python API。很多网上教程没把--calib和.batch文件区分开导致用户直接拿--calibbatch_calibration0.batch去跑结果 trtexec 立刻报Calibration file does not contain a valid header。4.3 校准分布不一致造成的精度陷阱INT8 量化最大的坑不在技术细节而在校准数据的选择。我接过一个项目对方直接拿训练集里增强后的图片做校准结果 INT8 模型在真实摄像头数据上精度掉到 30 个点以下。正确做法是从验证集里随机抽 500~1000 张清晰、带标注的图片覆盖不同光照和目标大小然后统一做和推理时相同的预处理。校准集的规模不需要大但分布必须贴近线上数据。在 Windows 上如果数据读取太慢get_batch会显著拖慢校准过程此时可以把图片预处理成 npy 或 raw 文件放内存再喂给校准器。此外7.2.3.4 的trtexec对某些 ONNX 节点的显式量化支持并不好。如果你试图直接加载 QDQQuantize/Dequantize格式的 INT8 模型这个老版本常会报Unsupported ONNX data type。解决思路是直接加载 FP32 ONNX然后让 TensorRT 的校准器自己决定量化阈值。这一点和 TensorRT 8.5 以后的--int8 --skipLocalCalibration行为不一样老版本没有捷径老老实实跑一遍校准最稳妥。5. 动态 shape、多 profile 与那批 batch_calibration 文件怎么复现最后讲一个进阶技巧把那些.batch文件变成可复现的校准缓存并在动态 shape 下让同一个 engine 处理不同分辨率的输入。TensorRT 7.2.3.4 支持动态 shape但需要在转换时显式声明 profile。用 trtexec 的命令行表示是.\trtexec.exe --onnxdetector.onnx --saveEnginedetector_dyn.engine ^ --minShapesimages:1x3x320x320 --optShapesimages:1x3x640x640 --maxShapesimages:1x3x1280x1280minShapes、optShapes、maxShapes分别指定输入张量 dimensions 的最小、最优、最大范围。TensorRT 会在 optShapes 处做常量折叠与算子选择并在运行时根据实际输入 shape 在 min 和 max 之间选择一段子图优化。但需要注意的是不同 profile 之间校准缓存可能不通用。如果你在 640x640 下做的 INT8 校准再把 engine 切换到 1280 分辨率运行有时会出现严重的精度退化因为激活范围已经超纲。这时候回头检查.batch文件里保存的 shape 就很有用。5.1 动态 shape 的 min/opt/max 声明与校准缓存的 shape 绑定在动态 shape 下校准缓存的唯一标识除了模型结构还包含输入张量的 shape 范围。TensorRT 的内部实现会为每个 profile 生成独立的网络实例如果你用--calib加载旧缓存但它对应的 min/opt/max 范围与当前声明的 profile 不一致TensorRT 可能会忽略部分缓存并重新校准。这会导致每次构建 engine 时耗时暴增还可能得到不同的量化参数。因此建议在保存batch_calibration*.batch文件时把 profile 参数也一并记录到一个 JSON 文件里{ model: yolov5s, min_shapes: {images: [1, 3, 320, 320]}, opt_shapes: {images: [1, 3, 640, 640]}, max_shapes: {images: [1, 3, 1280, 1280]}, calibrator: EntropyCalibrator2, calibration_batches: batch_calibration*.batch }这样下次构建时可以先对比 JSON 里的 shape 与当前命令行参数避免误用。5.2 用 Python API 重新加载旧 batch 文件如果你不想重新跑校准可以写一小段代码把该目录下的.batch文件重新组装成校准集再喂给校准器。由于.batch文件是你自己 dump 的格式由你定义必须知道它的内存布局。常见做法是用 Pickle 或np.save保存整个 batch 数组加载后按 NCHW 顺序 reshape 即可import os import numpy as np import tensorrt as trt class BatchCalibrator(trt.IInt8EntropyCalibrator2): def __init__(self, batch_dir, batch_size, input_shape): super().__init__() self.batch_files sorted( [os.path.join(batch_dir, f) for f in os.listdir(batch_dir) if f.startswith(batch_calibration)]) self.batch_size batch_size self.input_shape input_shape self.cursor 0 self.d_input trt.cuda_engine.DeviceMemoryAllocator() # 示意 def get_batch_size(self): return self.batch_size def get_batch(self, names): if self.cursor len(self.batch_files): return None data np.load(self.batch_files[self.cursor]) # data 的 shape 应为 (batch, C, H, W)与 input_shape 一致 assert data.shape tuple(self.input_shape) # 拷贝到 GPU 显存这里用 cuda.memcpy_htod 示意 self.cursor 1 return [int(self.d_input.ptr)]注意这只是一个示意代码实际使用中需要调用 PyCUDA 或cuda-python来分配 GPU 显存并完成 H2D 拷贝。TensorRT 7.2.3.4 自带的 Python 示例sample_int8.py里有完整的EntropyCalibrator2实现可以直接改造成从.batch文件读取。改造时留意get_batch的返回值必须是列表元素是输入张量的 GPU 设备指针如果返回 CPU 指针TensorRT 会在内部做非法拷贝导致引擎构建崩溃。最后一个小技巧在 Windows 上用 TensorRT 7.2.3.4 时把trtexec.exe的返回码当作自检指标。成功返回 0任何推理失败都会返回非零值。你可以把转换命令封装成一个批处理脚本循环遍历--calib参数逐个尝试加载batch_calibration*.batch先转换成官方缓存格式这样能快速确认哪些文件还能复用、哪些已经和当前 ONNX 结构对不上。脚本逻辑很简单但能帮你避免在部署阶段才发现量化缓存失效的尴尬。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进