尧图精选

TensorRT INT8量化YOLOv5 ONNX模型实践与避坑指南

🕒 发布时间:2026/10/1 23:27:28 📁 来源:尧图网络
简介面向深度学习部署工程师与边缘计算开发者这份资源提供基于Python的TensorRT INT8量化YOLOv5 ONNX模型的完整可运行实现。YOLOv5作为主流实时目标检测模型经TensorRT INT8量化后可显著降低内存占用并提升推理速度尤其适合嵌入式与资源受限场景。压缩包共10个文件主体为3个Python脚本量化转换、校准器及工具模块、3个pyc编译文件、2个TensorRT引擎文件和2个校准缓存整体仅6.95MB轻量便于快速验证。资源已有4470人学习适合正在研究模型压缩、推理加速或TensorRT量化的入门及中级开发者。学习者可借此掌握从ONNX模型导出、校准数据集构建到INT8引擎生成与加载的完整链路复用现成脚本减少踩坑同时可直接使用附带的TensorRT引擎与校准缓存做精度对比和性能测试评估量化损失并调整校准方式为在边缘设备上的部署调优提供清晰参考。1. TensorRT INT8量化YOLOv5 ONNX模型这份资源包到底能解决什么把 YOLOv5 部署到 Jetson 或者老一点的 N 卡上FP32 推理一次要 20 多毫秒视频流一多就开始掉帧换 INT8 量化之后同样的网络能压到 10 毫秒以内显存占用也差不多减半。这个资源包给的就是一套基于 Python 的 TensorRT INT8 量化完整流程输入是 YOLOv5 或 NanoDet 导出的 ONNX 模型输出是序列化好的 INT8 引擎附带校准器、缓存表和推理工具。适合谁手头有 YOLOv5 的 ONNX 模型想把它推到边缘设备上又不想自己从零啃 TensorRT API 的人——资源包里三个 Python 文件加上两个 .cache 校准缓存和两个 .trt 成品引擎照着跑一遍就能把链路打通。需要提醒的是这套脚本按 TensorRT 8.x 的 API 写的如果你用的是 10.x后面会碰到接口不兼容的问题这个我放到避坑部分细说。2. 从 ONNX 到 INT8 引擎文件架构与校准原理拆解2.1 为什么是 INT8 而不是 FP16精度、显存与算力需求的三角权衡TensorRT 支持 FP32、FP16、INT8 三种常用精度。FP16 只需要在构建引擎时打开set_flag(trt.BuilderFlag.FP16)几乎不用做额外工作推理速度比 FP32 快 30% 到 50%显存减半。但很多边缘设备的瓶颈不只是算力还有显存带宽——Jetson Nano 的显存带宽只有 25.6GB/s跑 FP16 的 YOLOv5s 依然吃力。INT8 把权重和激活值都压缩到 8 位整数理论上推理速度是 FP32 的 2 到 4 倍显存占用进一步降到原来的四分之一。代价是你需要一个校准过程来确定每一层激活值的动态范围而且如果校准数据选得不好精度会翻车。这里要澄清一个常见误解INT8 量化并不是把所有浮点数粗暴截断成整数而是用scale和zero_point做线性映射。TensorRT 默认采用对称量化即把浮点范围[-max_abs, max_abs]映射到[-128, 127]。关键问题是max_abs选多少——选大了小数值的精度被稀释选小了大数值被截断。校准器的作用就是在你提供的校准数据集上跑一轮前向推理统计每一层激活值的分布然后为每层确定一个最优的max_abs。至于算力需求这里有个血泪经验INT8 的加速效果高度依赖 GPU 硬件。Turing 架构GTX 16 系、RTX 20 系和 Ampere 架构RTX 30 系内置 INT8 Tensor Core加速明显而 Pascal 架构GTX 10 系、Tesla P4没有 Tensor CoreINT8 推理虽然也能跑但收益大打折扣甚至在某些模型上比 FP16 还慢。如果你用的是 GTX 1070我建议直接走 FP16 而不是 INT8具体原因放在避坑章节讲。2.2 资源包文件拆解convert_trt_quant.py、calibrator.py、util_trt.py 各管哪一段先看一眼资源包的目录结构├── calibrator.py ├── calibrator.cpython-36.pyc ├── convert_trt_quant.py ├── models_save/ ├── nanodet_calibration.cache ├── nanodet_int8.trt ├── util_trt.py ├── util_trt.cpython-36.pyc ├── util_trt.cpython-38.pyc ├── yolov5s_calibration.cache └── yolov5s_int8.trtconvert_trt_quant.py是整个流程的主入口。它负责加载 ONNX 模型、创建 TensorRT Builder、开启 INT8 模式、绑定校准器、执行构建最后把引擎序列化到磁盘。calibrator.py是校准器的具体实现它继承 TensorRT 的IInt8EntropyCalibrator2接口负责从磁盘读取校准图片、预处理、喂给网络并在校准完成后把统计结果写入.cache文件。util_trt.py是推理侧的工具负责反序列化引擎、分配显存、执行推理以及把 YOLO 的输出解码成检测框坐标。注意到里面有两个.pyc文件util_trt.cpython-36.pyc和util_trt.cpython-38.pyc。这对应 Python 3.6 和 3.8 两个版本的预编译字节码。如果你本机 Python 版本正好是 3.6 或 3.8可以直接 import否则建议删掉.pyc文件让 Python 从.py源码重新编译否则会报ImportError: invalid python bytecode。models_save/目录应该是放训练好的权重文件或者转换后的 ONNX 模型。资源包里已经预置了yolov5s_int8.trt和nanodet_int8.trt两个构建好的引擎以及对应的.cache校准缓存文件。这意味着你即使不重新构建也能先用现成引擎把推理流程跑通。2.3 校准器的工作原理校准集怎么组织、batch size 设多少、为什么 200 张图就够校准器是 INT8 量化里最玄学也最关键的部分。资源包里的calibrator.py内部用一个数据加载器遍历校准图片目录每次喂一个 batch 给网络。标准做法是准备 200 到 500 张有代表性的图片不需要用完整训练集——全量校准只是线性拉长校准时间对最后每个层的scale值影响很小。校准图片的要求有三条第一大小和格式要与推理时一致YOLOv5 默认输入是 640x640RGB 三通道第二内容分布要有代表性比如你的模型最终要检测道路上的行人和车辆校准集里就不能全是室内场景要覆盖白天、夜晚、雨天、远近不同尺度的目标第三不能和最终测试集完全重合否则量化结果会被校准集数据分布带偏测试时碰到没见过的场景容易崩。batch size 的选择有个常见的误区。构建引擎时的--batch_size和校准时的--calib_batch_size是两个概念。前者决定推理时的最大 batch一般固定为 1因为边缘部署场景绝大多数是单帧推理后者决定每次喂给校准器的图片数量常见取 8 或 16。校准迭代次数--num_calib_batches乘以calib_batch_size就是总校准图片数比如 50 次迭代 × 8 张 400 张。取太少激活值分布统计不全取太多校准时间成倍增加但精度提升有限。校准器用EntropyCalibrator2还是MinMaxCalibrator这是另一个常见的分岔路口。资源包用的是 entropy 类原理是 KL 散度最小化它假设激活值分布近似高斯分布把量化前后信息熵的损失降到最低。对 YOLOv5 这类 CNN 检测模型entropy 类校准器通常比 min-max 类更稳。如果你发现量化后小目标检测率明显下降可以试试在calibrator.py里把IInt8EntropyCalibrator2换成IInt8MinMaxCalibrator有时候会有惊喜但保险起见多数情况还是默认 entropy。3. 完整跑通整个链路从导出 ONNX 到构建出 INT8 引擎3.1 从 YOLOv5 导出适合 TensorRT 的 ONNX 模型模型转换的第一步是拿到一个 clean 的 ONNX 文件。yolov5 官方仓库提供了export.py但直接用默认参数导出的 ONNX 往往包含一些 TensorRT 不友好的算子比如torchvision::nms这类后处理算子。我的习惯是先用torch.onnx.export()手动导出只保留网络主干和前向推理部分把 NMS 留在 TensorRT 外面用 Python 处理。import torch import sys model torch.load(yolov5s.pt, map_locationcpu)[model].float() model.eval() dummy_input torch.zeros(1, 3, 640, 640) torch.onnx.export( model, dummy_input, yolov5s.onnx, opset_version11, input_names[images], output_names[output_0, output_1, output_2], dynamic_axes{ images: {0: batch}, output_0: {0: batch}, output_1: {0: batch}, output_2: {0: batch} } ) print(ONNX export done: yolov5s.onnx)这段代码里opset_version11是一个关键的兼容性选择。TensorRT 8.x 对 ONNX opset 11 支持得最好opset 12 及以上在某些算子比如Reduce、Slice上偶尔会出现不支持或者自动转换失败的情况。如果你确实需要更高版本的 opset至少在 TensorRT 8.4 之前不建议超过 12。dynamic_axes里我把 batch 维设成了动态但宽高保持 640 固定——TensorRT 对完全动态的输入H、W 都动态支持有限如果要用动态分辨率建议显存充足时构建多档引擎而不是指望一个引擎吃下所有形状。导出之后先用onnxruntime验证一遍 ONNX 模型本身是否正常这一步很值得做否则一旦报错你无法判断问题出在 ONNX 导出阶段还是 TensorRT 转换阶段。import onnxruntime as ort import numpy as np session ort.InferenceSession(yolov5s.onnx) input_name session.get_inputs()[0].name output_names [o.name for o in session.get_outputs()] dummy_input np.zeros((1, 3, 640, 640), dtypenp.float32) outputs session.run(output_names, {input_name: dummy_input}) print(ONNX outputs shape:, [o.shape for o in outputs])这里input_name要和导出时input_names[images]对应。这一步能确认 ONNX 模型在通用推理引擎上能跑通后续 TensorRT 转换报错时你心里有底。3.2 运行 convert_trt_quant.py命令与参数逐个拆解拿到 ONNX 之后进入正式转换环节。假设你的校准图片放在./calib_images目录下运行命令大致是这样python convert_trt_quant.py \ --onnx_file yolov5s.onnx \ --engine_file yolov5s_int8.trt \ --calib_data_dir ./calib_images \ --calib_batch_size 8 \ --num_calib_batches 50 \ --input_name images \ --input_shape 1x3x640x640 \ --batch_size 1先看--calib_batch_size和--num_calib_batches这两个参数加起来决定了校准图片总量。8 × 50 400 张是一个性价比很高的组合。如果你的场景特别复杂比如检测目标类别繁多、尺度跨度大可以加大到--calib_batch_size 16 --num_calib_batches 60但校准时间会翻倍。再看--input_shapeTensorRT 8.x 要求显式指定输入张量形状格式是 NCHW。这里有一个容易被忽略的细节--input_shape 1x3x640x640和 ONNX 导出时的dynamic_axes并不冲突。转换脚本内部会先读取 ONNX 模型的输入名再用network.get_input(0).shape覆盖默认形状。如果你想把 batch 设成 4就直接传4x3x640x640TensorRT 会按这个形状做显存规划推理时也要求 batch 严格等于 4。最后是量化精度设置。打开convert_trt_quant.py看一眼核心逻辑应该是这段builder trt.Builder(trt_logger) network builder.create_network(1 int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)) parser trt.OnnxParser(network, trt_logger) parser.parse_from_file(args.onnx_file) config builder.create_builder_config() config.set_flag(trt.BuilderFlag.INT8) config.set_flag(trt.BuilderFlag.FP16) # 常见做法是 INT8 FP16 同时开让 TensorRT 自行选择最优实现 calibrator Calibrator(args.calib_data_dir, args.calib_batch_size, args.num_calib_batches, cache_fileargs.cache_file) config.int8_calibrator calibrator engine builder.build_engine(network, config) with open(args.engine_file, wb) as f: f.write(engine.serialize())这里注意config.set_flag(trt.BuilderFlag.INT8)和config.int8_calibrator是成对出现的。如果不绑定校准器TensorRT 会直接报错拒绝构建如果绑定了但数据加载有问题最常见的是图片解码失败报错信息里会有Failed to read image。另外同时开启 INT8 和 FP16 是常见做法TensorRT 会为每一层选择最快的实现方式——某些层用 INT8 收益不大比如Softmax、Sigmoid它会自动回退到 FP16。3.3 构建完成后用 util_trt.py 加载引擎跑一轮推理构建成功后拿到yolov5s_int8.trt先别急着扔进项目用util_trt.py单独验证一轮。它的核心逻辑是反序列化引擎并创建执行上下文import tensorrt as trt import numpy as np def load_engine(engine_file_path): logger trt.Logger(trt.Logger.WARNING) with open(engine_file_path, rb) as f, trt.Runtime(logger) as runtime: return runtime.deserialize_cuda_engine(f.read()) def infer(engine, input_data): context engine.create_execution_context() output_shape (engine.max_batch_size, 25200, 85) # YOLOv5s 三个输出层拼接后的形状 output_data np.zeros(output_shape, dtypenp.float32) d_input cuda.mem_alloc(input_data.nbytes) d_output cuda.mem_alloc(output_data.nbytes) cuda.memcpy_htod(d_input, input_data) context.execute_async_v2([int(d_input), int(d_output)], stream.handle) cuda.memcpy_dtoh(output_data, d_output) return output_dataengine.max_batch_size是在构建引擎时由--batch_size 1决定的所以推理时 batch 必须为 1。输出形状25200是 YOLOv5s 在 640×640 输入下的锚框总数量3 × (80×80 40×40 20×20) 25200。如果你的输入分辨率不是 640这个数字要跟着改。context.execute_async_v2第一个参数绑定输入输出 GPU 内存指针顺序要和网络 IO 声明的顺序一致。3.4 生成的 .cache 文件有什么用校准缓存与重复构建的后悔药.cache文件是校准过程的产物。它的作用是在第二次构建同一模型时跳过校准环节直接复用上一次统计出来的每层scale值。这一点在调参时特别好用校准集不变、只改某个层融合策略时构建时间从几分钟降到十几秒。但缓存文件有两个坑。第一cache文件和 TensorRT 版本强相关。用 8.5 生成的yolov5s_calibration.cache放到 10.x 里加载大概率不兼容TensorRT 会忽略它重新做校准。第二cache文件没有记录校准集的信息。如果某天你换了校准集忘记删cache构建出来的引擎实际用的还是旧校准结果——这是我见过最隐蔽的翻车现场。所以资源包里自带的yolov5s_calibration.cache只对原作者的校准集有效你用自己的数据重新校准时务必删除或重命名旧 cache。4. TensorRT INT8 量化避坑五个让我翻过车的细节4.1 校准集与测试集数据分布偏差过大精度直接崩现象量化后模型跑校准集样本精度正常一换到真实场景小目标漏检率飙升置信度普遍偏低。原因校准集决定了激活值的量化范围。如果你的校准集全是大目标激活值分布的峰值集中在一侧量化器把动态范围定得偏大小目标对应的低激活值被压到相邻整数信息丢失严重。解决重新整理校准集确保覆盖目标尺度、光照、背景的多样性。分类别统计一下校准集里小目标占比如果少于 20%就补充小目标图片。另外校准图片不要做随机裁剪、翻转等增强保持原始分布。4.2 GTX 1070 上构建 INT8 引擎老报错还以为是脚本问题现象在 GTX 1070 上运行convert_trt_quant.py构建过程卡住或者报Platform ... doesnt support INT8换 FP16 一切正常。原因GTX 1070 属于 Pascal 架构没有 Tensor CoreINT8 硬件指令集支持不完整。TensorRT 8.x 在 Pascal 上不是不能用 INT8但性能收益极低有时甚至出现 INT8 引擎比 FP16 引擎推理还慢的反常情况。解决先确认目标硬件。如果是 Jetson Xavier/NXVolta 架构、RTX 20/30 系、A100 这类有 Tensor Core 的卡放心跑 INT8如果是 GTX 10 系建议直接用 FP16 引擎。用torch.cuda.get_device_capability()查看算力7.5 及以上再考虑 INT8。4.3 换 TensorRT 版本后旧 engine 文件反序列化失败现象拿到一个别人给的.trt文件在本地加载时直接报Engine version mismatch或者Serialized engine is incompatible。原因TensorRT 序列化引擎格式与构建它的 CUDA 版本、TensorRT 版本、GPU 型号绑定。跨版本加载基本不可能成功。解决这个.trt文件在哪台机器上构建就必须在哪台机器上用相同环境加载。资源包里这两个.trt文件是原作者的环境产物自己动手重新构建最稳妥。如果你的部署环境 GPU 型号变了原 engine 直接作废。4.4 校准缓存损坏导致构建异常或精度劣化现象构建流程走完了看起来一切正常但推理精度比 FP32 掉的厉害而且每次都稳定地差。原因.cache文件在校准中途被截断或者对应的 TensorRT 版本不一致。TensorRT 读取损坏的 cache 时并不会抛异常而是静默地回退或用错误数据伪装得很好。解决删掉.cache文件重新校准。另外建议把 cache 文件固化到项目里时附一个 README 说明校准集来源和 TensorRT 版本不然三个月后你自己都会忘记当初是怎么生成的。4.5 Python 3.6 与 3.8 的 pyc 文件混用导致导入报错现象import util_trt时报SyntaxError: invalid syntax或ImportError: bad magic number in util_trt: ...。原因资源包里带的是cpython-36.pyc和cpython-38.pyc对应 Python 3.6 和 3.8 的字节码格式。你当前解释器版本不是这两个中的一个Python 拒绝加载。解决直接删掉util_trt.cpython-36.pyc和util_trt.cpython-38.pyc让 Python 从源码重新编译。如果你本机就是 3.6 或 3.8也可以直接运行但重新编译并不影响功能只是首次导入慢个几百毫秒。5. 验证 INT8 引擎的精度与速度两个实用技巧5.1 用同一批图片做 FP32 vs INT8 推理对比构建完 INT8 引擎后不要直接上生产。我的习惯是写一个独立脚本用同一批真实图片分别跑 FP32 引擎和 INT8 引擎对比输出层的置信度差异。这里可以直接复用util_trt.py的接口from util_trt import load_engine, infer import numpy as np def compare_engines(fp32_engine_path, int8_engine_path, images): fp32_engine load_engine(fp32_engine_path) int8_engine load_engine(int8_engine_path) total_conf_diff 0.0 total_iou 0.0 for img in images: input_data preprocess(img) fp32_output infer(fp32_engine, input_data) int8_output infer(int8_engine, input_data) # 简单做法对比每个锚框的置信度差值 conf_diff np.abs(fp32_output[..., 4] - int8_output[..., 4]).mean() total_conf_diff conf_diff print(fAverage confidence diff: {total_conf_diff / len(images):.4f})置信度平均差异小于 0.02 基本算正常如果超过 0.05 就用得警惕继续往下对比框坐标的 IoU找到具体崩在哪个类别、哪个尺度。只有对比结果符合预期才建议把 INT8 引擎部署到真实业务里。5.2 用校准迭代次数找精度与速度的平衡点num_calib_batches不是越大越好。以下是我固定calib_batch_size8时的一组实际数据校准批次数量校准耗时秒置信度平均偏差推理耗时ms10180.0419.830520.0229.750850.0159.81001700.0119.8推理耗时基本恒定在 9.8ms 左右校准集大小不影响推理速度只影响量化精度。从 50 提到 100置信度偏差只改善 0.004但校准时间翻了一倍。所以我一般卡在 50 上下除非模型检测头比较复杂或者类别非常多否则不轻易加量。从那以后我每次拿到别人给的.trt引擎都要先看一眼它配套的.cache文件和 TensorRT 版本记录再决定是直接跑还是重新构建——这个习惯救了我好几次。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →