首页 / 资讯中心 / 文章详情

TensorRT部署YOLOv7:PTQ与QAT量化实战指南

TensorRT部署YOLOv7:PTQ与QAT量化实战指南 ★ FEATURED ARTICLE
简介一份面向目标检测部署的YOLOv7量化与推理方案涵盖PTQ训练后量化和QAT量化感知训练两种路径并配套TensorRT的C部署代码。适用对象是具备一定模型训练基础、希望在GPU环境实现低精度实时推理的算法工程师或C开发者。资源共134个文件含Python脚本35个、YAML配置33个、C源码与头文件、CUDA kernel、Dockerfile及CMake构建脚本整体约35MB能够支撑从环境搭建到推理验证的完整闭环。已有486人学习下载。方案围绕PTQ快速量化与QAT精度保持展开代码中覆盖TensorRT模型加载、输入预处理、推理执行、结果解析及NMS后处理等关键环节并配有详细注释、示例图片和演示视频便于理解每一步意图并定制优化。通过这份资料可减少自行摸索量化与部署链路的时间快速获得高效且节省资源的YOLOv7应用。1. 从 FP32 到 INT8YOLOv7 落地为什么绕不开 PTQ 和 QAT用 TensorRT 部署 YOLOv7绝大多数人不是卡在模型跑不起来而是卡在“跑得快”和“跑得多”上。比如你手里只有一张 T4要同时接几路 1080p 25fps 的实时视频流FP32 的 engine 可能只能撑一两路换成 INT8 后吞吐能翻 2 到 3 倍多路并发的方案才立得住。把 FP32 压到 INT8靠的就是两条路训练后量化 PTQ和量化感知训练 QAT。PTQ 是拿现成权重让 TensorRT 重新校准数值范围几分钟出结果QAT 则是把量化误差当成训练的一部分让模型先学会和 INT8 共存。前者快后者稳两者不是二选一而是排查顺序上的前后手。这篇笔记会把 YOLOv7 从 PTQ 到 QAT、再到 TensorRT 部署的完整路径和踩坑点写清楚适合准备把 YOLOv7 塞进生产环境的人。2. TensorRT 训练后量化校准集、Calibrator 与最小复现2.1 先理解 PTQ 在做什么才知道它为什么经常“一次就成”PTQ 的核心不是把权重简单截断成 INT8而是给每个算子和每个激活张量找一组 scale。TensorRT 的 INT8 engine 结构上还是那套卷积、add、激活函数只是把 FP32 输入和权重换成 INT8 表示再在算子内部用 scale 反量化恢复精度。难的是激活权重训练完就定死了分布范围一眼能看出来激活的输出分布取决于输入图像内容曝光、遮挡、目标大小都会改变它。所以 TensorRT 需要一个校准器去“看”一批样本统计每个激活张量的数值范围再按信息熵选一个裁剪阈值让量化后的分布和原始分布尽量接近。这个机制决定了 PTQ 适用的场景校准集和真实部署数据不能差太远。检测任务里常见画面是几十个目标、背景复杂激活分布通常很稳定PTQ 往往能把 mAP 掉点压在 0.5% 到 1% 以内。这也是业界普遍建议先跑 PTQ 的原因——只有明显掉点才考虑 QAT。QAT 要重训、要换模型结构、要绑定训练管线不能当救火工具用。对比项PTQQAT是否需要训练不需要需要至少 1 到 2 个 epoch 微调数据需求50 到 200 张校准图需要原训练集或接近分布的采样工程成本低一个 calibrator 就能跑高要改模型替换、导出和验证部署精度多数场景掉点小于 1%更接近 FP32尤其对低比特敏感结构2.2 导出 ONNXbatch、opset 和预处理都要一次定准PTQ 的第一步是把 PyTorch 的 YOLOv7 导出成 ONNX。官方仓库自带导出脚本但我习惯不直接用它默认参数而是自己控制动态轴和 opset。导出时先把模型加载为 eval再喂一个 1×3×640×640 的 dummy 张量因为 TensorRT 构建 engine 时必须知道输入尺寸。dynamic_axes 只给 batch 这一维宽高保持固定 640这样跑 INT8 校准时少一个变化维度。import torch from models.yolo import Model # 构造和训练时一致的 YOLOv7然后加载权重 model Model(cfg/training/yolov7.yaml, ch3, nc80).eval() ckpt torch.load(runs/train/exp/weights/best.pt, map_locationcpu) model.load_state_dict(ckpt[model].float().state_dict()) model.eval() dummy torch.zeros(1, 3, 640, 640) torch.onnx.export( model, dummy, yolov7.onnx, input_names[images], output_names[output], opset_version12, dynamic_axes{images: {0: batch}, output: {0: batch}}, ) print(exported yolov7.onnx)这段代码里最重要的参数是 opset_version12。TensorRT 对 opset 11 以上支持比较稳opset 9 的旧图在 parse 时容易缺算子opset 13 以上则要确认你安装的 TensorRT 版本能覆盖。dynamic_axes 只展开 batchYOLOv7 的检测头输出在三个尺度上拼接输出张量 shape 是 batch×25200×85这个 25200 等于 80×80、40×40、20×20 三个特征图点数之和再乘 3 个 anchor。如果导出后 shape 对不上后面 calibrator 算内存、部署时申请输出 buffer 都会差一层。导出后建议先验证一次用 onnx.checker 检查模型结构再用 onnxruntime 跑一遍 dummy 输入确认输出 shape 确实是 batch×25200×85。这一步翻车的人不少很多是训练时改了 nc 或者加了 P6 检测头但导出的模型还是按原来 80 类假设写的。2.3 写一个真正能跑的 Int8CalibratorTensorRT 的 INT8 calibration 必须有一个校准器类。它要做的事只有三件提供一批图像、把图像预处理成和训练一致、把数据拷到显存。最常见的实现是继承 IInt8EntropyCalibrator2它统计的是激活分布的信息熵对检测模型效果稳比 MinMaxCalibrator 对离群点的容忍度更好。import tensorrt as trt import pycuda.driver as cuda import numpy as np import cv2 class YOLOv7Calibrator(trt.IInt8EntropyCalibrator2): def __init__(self, calib_images, batch_size8): super().__init__() self.batch_size batch_size # 只取能整除 batch_size 的图片数避免最后一批不满 self.images calib_images[:len(calib_images) // batch_size * batch_size] self.batch_idx 0 self.calib_input np.zeros((batch_size, 3, 640, 640), dtypenp.float32) # 显存要在 init 里先分配否则 get_batch 返回空指针 self.device_input cuda.mem_alloc(self.calib_input.nbytes) def get_batch_size(self): return self.batch_size def get_batch(self, names): if self.batch_idx self.batch_size len(self.images): return None batch self.calib_input for i in range(self.batch_size): img cv2.imread(self.images[self.batch_idx i]) img letterbox(img, new_shape(640, 640))[0] # 注意这个函数要按训练时的写法 img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 训练用 RGB这里必须转 img img.astype(np.float32) / 255.0 batch[i] img.transpose(2, 0, 1) self.batch_idx self.batch_size cuda.memcpy_htod(self.device_input, batch.ravel()) return [int(self.device_input)] def read_calibration_cache(self): try: with open(yolov7_calibration.cache, rb) as f: return f.read() except FileNotFoundError: return None def write_calibration_cache(self, cache): with open(yolov7_calibration.cache, wb) as f: f.write(cache)这段代码里最容易翻车的有三个地方。第一letterbox 必须和训练时尺寸缩放一致YOLOv7 官方用的是填充值 114YOLOv5 系模型都是这个值你要是随手用 cv2.resize 拉伸校准出来的 scale 会带着畸变。第二cv2 读出来是 BGR必须转成 RGB否则校准得的激活范围和部署时实际输入通道错位框的置信度会整体下压。第三校准图片 50 到 200 张就够不需要多但分布要覆盖白天、夜间、不同分辨率不能只在晴天中下午的素材里选。2.4 用 trtexec 和 Python 构建 INT8 engine生成 INT8 engine 有两条路命令行业简单适合验证Python 适合直接嵌入服务。命令行最常用的是 trtexec前提是你已经导出 onnx并且 calibrator 已经生成过 calibration cachetrtexec \ --onnxyolov7.onnx \ --int8 \ --calibyolov7_calibration.cache \ --saveEngineyolov7_int8.engine \ --memPoolSizeworkspace:2048--int8 表示开启 INT8--calib 指定校准缓存没有缓存时 TensorRT 会用默认校准器重新生成但控制力不如自己写的 calibrator。--memPoolSize 是新版 TensorRT 的命令行写法老版本叫 --workspace指定构建时可用工作内存2048MB 对 YOLOv7 来说足够给太大并不会让 engine 更快。Python 构建版本则是这样import tensorrt as trt logger trt.Logger(trt.Logger.WARNING) builder trt.Builder(logger) network builder.create_network( 1 int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH) ) parser trt.OnnxParser(network, logger) with open(yolov7.onnx, rb) as f: parser.parse(f.read()) config builder.create_builder_config() config.set_flag(trt.BuilderFlag.INT8) config.int8_calibrator YOLOv7Calibrator(calib_images, batch_size8) config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, 1 30) # 1024MB engine_bytes builder.build_serialized_network(network, config) with open(yolov7_int8.engine, wb) as f: f.write(engine_bytes)这里我刻意用 build_serialized_network 而不是 build_engine因为新版 TensorRT 返回序列化字节流直接落盘成 .engine 文件就好。config.int8_calibrator 挂上的就是 2.3 写的 calibrator 实例TensorRT 在构建过程中会反复调用它的 get_batch 采样激活分布最后把 scale 写进 engine。Windows 上 pycuda 分配显存有时会出问题如果你不想碰 CUDA 内存可以先用 trtexec 生成 cache再在 Python 里通过 read_calibration_cache 复用效果一样构建时间还能省几分钟。3. QAT 量化感知训练先换量化模块再让模型“带着 INT8 学”3.1 QAT 解决什么伪量化是核心但别只盯着训练PTQ 是拿训练好的权重看它“天然”的分布然后硬裁QAT 则是把量化误差以假乱真地塞进前向计算让损失函数直接看到 INT8 的精度损失。这里的关键是伪量化节点前向时把权重和激活先缩放到 INT8 范围再缩放回 FP32数值上已经变成“量化后”的反向时取整操作没有梯度常见做法是 straight-through estimator让梯度绕过取整直接回传。这样带来 PTQ 没有的两个效果。一是权重分布在训练中会主动朝“裁剪后损失小”的方向调整而不是被动被校准阈值裁掉二是激活分布对校准集的敏感度降低部署换一批环境数据精度波动更小。代价是训练流程变复杂得保留训练代码、数据加载、学习率调度还得想清楚 YOLOv7 哪些层量化、哪些层保持 FP32。3.2 替换 YOLOv7 的卷积模块quant_modules 要在构造模型前初始化NVIDIA 的 pytorch-quantization 库提供和 TensorRT 对标的量化模块这是官方 QAT 路线里最常用的一套。它会把 torch.nn.Conv2d 替换成 TensorRTQuantizeConv2d内部多出两个伪量化器一个管输入激活一个管权重Linear 同理。BN 层不替换保持 FP32因为 TensorRT 构建 INT8 engine 时会把 BN 融合进卷积融合发生在量化之后QAT 阶段强行量化 BN导出后反而容易出问题。from pytorch_quantization import quant_modules # 必须在这之后构造模型否则替换不生效 quant_modules.initialize() # 重新用官方 yolo.py 构造模型此时 Conv2d 已被替换为量化版本 model Model(cfg/training/yolov7.yaml, ch3, nc80).eval() ckpt torch.load(best.pt, map_locationcpu) model.load_state_dict(ckpt[model].float().state_dict(), strictFalse)quant_modules.initialize() 的作用是让之后代码里所有 torch.nn.Conv2d 的构造自动替换成量化版本所以这一步必须在重新构造模型之前调用。如果模型已经构造好再调 initialize替换不会生效。strictFalse 是必须的因为量化模块多出的 quantizer 参数没有对应预训练权重不关掉严格模式会直接报错。替换完还要检查一遍 Detect 头。YOLOv7 的 Detect 头会把三个尺度预测拼起来做后处理这部分建议保持 FP32 不量化把 INT8 损失集中在 Backbone 和 Neck输出不会因为最后的拼接被二次放大。3.3 先校准再微调两个阶段必须分开做QAT 训练不能直接从 FP32 权重硬训得先做一次类似 PTQ 的校准。pytorch-quantization 里每个 quantizer 都支持 calibrate 模式这时前向只统计激活的 min/max不真正做伪量化。# 第一阶段开启所有 quantizer 的校准模式 for name, module in model.named_modules(): if hasattr(module, input_quantizer): module.input_quantizer.enable_calib() module.weight_quantizer.enable_calib() module.input_quantizer.disable_quant() # 只统计不算量化 module.weight_quantizer.disable_quant() # 跑一批校准数据统计激活分布 model.eval() with torch.no_grad(): for images, _ in calib_loader: model(images) # 第二阶段锁定范围关闭校准模式恢复量化 for name, module in model.named_modules(): if hasattr(module, input_quantizer): module.input_quantizer.enable_quant() module.weight_quantizer.enable_quant() module.input_quantizer.disable_calib() module.weight_quantizer.disable_calib()跑完校准之后进入微调。学习率要压得很低我习惯把 FP32 预训练模型的初始学习率除以 10用一个 epoch 在训练集上过一遍让权重在量化误差影响下重新收敛。量化器在校准模式下会把 min/max 固定住所以微调阶段 quantizer 本身不再更新参数更新的只有卷积权重。微调阶段的 BN 行为要单独盯。YOLOv7 训练时 BN 的 running_mean 和 running_var 会持续更新但 QAT 微调只有一个两个 epochBN 按默认 momentum 大步长更新反而会把分布带偏。我通常会在微调一开始锁定 BN 参数只更新卷积权重如果你的数据集量很大也可以放开 BN 用 batch 统计试一轮对比 loss 再决定。3.4 导出带 QuantizeLinear 节点的 ONNX 才算结束QAT 训练完不能直接拿去 TensorRT 部署先要把模型导出成带 QDQ 节点的 ONNX。pytorch-quantization 给 quantizer 提供了 ONNX 导出开关导出前要逐个模块打开否则导出的 ONNX 里只有普通 FP32 卷积TensorRT 拿去构建 INT8 engine 时根本看不到量化信息。# 导出前把所有量化器切到 ONNX 导出模式 for name, module in model.named_modules(): if hasattr(module, input_quantizer): module.input_quantizer.enable_onnx_export() module.weight_quantizer.enable_onnx_export() torch.onnx.export( model, dummy, yolov7_qat.onnx, input_names[images], output_names[output], opset_version12, dynamic_axes{images: {0: batch}, output: {0: batch}}, )导出后花一分钟验证打开 ONNX 文件搜索 QuantizeLinear 和 DequantizeLinear如果这两个节点在卷积前后成对出现说明 QAT 信息已经带进图里。没有的话检查是不是有自定义算子或内联逻辑绕过了量化模块YOLOv7 的 Detect 头最容易出这类问题。QAT 导出的 ONNX 在 TensorRT 里构建 INT8 engine 时不需要再挂 calibrator或者说不应该再挂因为量化范围已经在训练中确定了继续用 EntropyCalibrator2 会把这些尺度覆盖掉。4. TensorRT 部署与调参从 engine 缓存到多路推流4.1 构建 engine 的参数精度开关、显存池和动态 batchQAT 导出的 ONNX 在 TensorRT 里构建 INT8 engine 时不需要再挂 calibrator或者说不应该再挂因为量化范围已经在训练中确定了继续用 EntropyCalibrator2 会把这些尺度覆盖掉。这一步和 PTQ 构建的主要区别就在 calibrator 参数上。import tensorrt as trt logger trt.Logger(trt.Logger.WARNING) builder trt.Builder(logger) network builder.create_network( 1 int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH) ) parser trt.OnnxParser(network, logger) with open(yolov7_qat.onnx, rb) as f: parser.parse(f.read()) config builder.create_builder_config() config.set_flag(trt.BuilderFlag.INT8) config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, 1 30) # 动态 batch最小 1常规 4最大 8 profile builder.create_optimization_profile() profile.set_shape(images, (1, 3, 640, 640), (4, 3, 640, 640), (8, 3, 640, 640)) config.add_optimization_profile(profile) engine_bytes builder.build_serialized_network(network, config) with open(yolov7_qat_int8.engine, wb) as f: f.write(engine_bytes)动态 batch 的优化 profile 三个参数分别是最小、常规、最大 shape。TensorRT 会按常规 shape 优化 kernel 选择部署时单个 batch 的延迟和最大 batch 的吞吐都会受影响。你如果只跑单路视频把常规设成 1 就行如果要压多路常规设成 4 往往比设成 1 更稳因为 TensorRT 选 kernel 时会按这个基准做算法选择。构建完成后我习惯先跑一次 trtexec 看每层的 profile 和总延迟不直接接业务代码。命令很简单trtexec --loadEngineyolov7_qat_int8.engine --shapesimages:1x3x640x640它会输出每层耗时和 GPU 利用率比自己在 Python 里数时间戳直观得多。4.2 推理binding、execute_async_v2 与输出解析engine 构建出来之后推理阶段最容易被忽略的是 binding 顺序和输出大小。YOLOv7 的 ONNX 只有两个 binding输入 images 和输出 output输出在固定 640 分辨率下是 batch×25200×85。下面是一段能直接跑的最小推理代码import tensorrt as trt import pycuda.driver as cuda import numpy as np def run_engine(engine, input_np, batch1): context engine.create_execution_context() context.set_binding_shape(0, (batch, 3, 640, 640)) stream cuda.Stream() out_size batch * 25200 * 85 d_input cuda.mem_alloc(batch * 3 * 640 * 640 * 4) d_output cuda.mem_alloc(out_size * 4) cuda.memcpy_htod_async(d_input, input_np.ravel(), stream) bindings [d_input, d_output] context.execute_async_v2(bindings, stream.handle) output np.empty(out_size, dtypenp.float32) cuda.memcpy_dtoh_async(output, d_output, stream) stream.synchronize() return output.reshape(batch, 25200, 85)input_np 必须提前做和 calibrator 完全相同的预处理包括 letterbox、RGB 顺序、除以 255三样缺一不可。output 的 25200 是一维排开的每一行是 x、y、w、h、objectness、80 类置信度共 85 个值。拿到之后要按类别过滤置信度再做 NMS这段逻辑和 PyTorch 推理时完全一致不用因为换了 TensorRT 就改后处理。如果你在 Jetson 这类嵌入式设备上跑CUDA 上下文初始化和显存分配要比桌面卡慢不少建议在服务启动时一次性把 engine 加载、context 创建、显存分配全部做完不要在每一帧里反复创建。这样吞吐能拉开很大差距。4.3 T4 上 1080p 25fps 多路估算先测单路 FPS 再算余量热词里经常有人问“T4 1080p25帧每秒用 tensorrt yolo 640 分辨率检测可以支持多少路”。这个问题的坑在于单看每秒帧数会高估路数因为每帧推理完之后还有预处理、后处理、显存拷贝这些不可忽略的开销。常见做法是先测单路 batch1 的稳定 FPS假设你测得 F路数上限就是 F 除以 25这是理想值再留出 20% 到 30% 余量给预处理、NMS 和系统抢占所以实际上会再打七折。另一个容易忽略的点是跑多路不要开多线程各自调用 engineTensorRT context 本身是线程不安全的。正确做法是每路一个 context或者把多路拼成 batch 一次推理。拼 batch 的吞吐通常比多 context 更高但会增加单帧延迟如果你的业务对延迟敏感比如要求端到端延迟低于 150ms那么多 context 并行更合适。这个取舍没有银弹只能拿你自己的输入帧率去压测。5. PTQ / QAT / TensorRT 部署避坑清单5.1 校准集画面和真实场景对不上部署现场漏检翻倍现象PTQ 在验证集上 mAP 只掉了 0.5%部署到现场后漏检翻倍夜间目标几乎全丢。原因calibration cache 是根据校准集统计的激活范围。你校准集全是白天的公路车辆现场是夜间或者逆光画面激活分布整体变了TensorRT 按旧范围裁掉的信息恰好是夜间目标的关键特征。这不是量化本身的问题而是域偏移被量化放大了。解决校准集必须覆盖真实部署场景。做法是按场景分桶白天、夜间、雨天、低分辨率各抽 50 到 80 张混在一起做校准。不用追求数量多分布广比张数大重要得多。换场景后记得删掉旧 cache重新生成。5.2 calibrator 和部署预处理不一致置信度整体被压低现象INT8 engine 推理出来的框位置大致正确但每类置信度普遍比 FP32 低 5 到 10 个百分点阈值一调高就漏检。原因calibrator 里用了 RGB部署代码里用的是 BGR或者 calibrator 里做归一化是除以 255部署代码忘了除。量化 scale 是在 calibrator 的输入分布上算出来的部署输入分布一但错位每个激活张量都会被嵌套的 scale 误差放大。解决把预处理抽成一个函数calibrator 和部署共用不要各写一份。函数里固定写清楚letterbox 尺寸、填充值、通道顺序、归一化分母。我见过最隐蔽的问题是训练时用 PIL 加载图片PIL 转完是 RGB但 calibrator 里图省事用 cv2.imread忘了转通道结果全链路错位还查不出来。最简单验证方法取同一张图分别用 FP32 和 INT8 跑推理对比热力图分布差异大的基本就是预处理不一致。5.3 QAT 微调正常导出后却没有量化节点现象QAT 训练时 loss 在降精度也恢复正常但导出 ONNX 后用 Netron 打开找不到 QuantizeLinear / DequantizeLinear拿去 TensorRT 构建时也没触发 INT8。原因导出前没有把量化器切到 ONNX 导出模式。pytorch-quantization 的伪量化器默认在训练模式下输出带梯度的伪量化值导出 ONNX 时必须显式开启 enable_onnx_export否则 torch.onnx.export 看到的只是一堆普通浮点运算量化信息被吞掉。解决导出前遍历模型所有量化模块统一调用 enable_onnx_export导出后立刻搜索 QDQ 节点确认。如果还是没有检查 Detect 头的自定义 forward 逻辑YOLOv7 这类自定义结构最容易绕过量化的地方就在这里。5.4 动态 batch 和 INT8 calibration 一起用时构建失败现象PTQ calibrator 用 batch8 能正常生成 cache但同一个 cache 加上 optimization profile 构建动态 batch engine 时抛错或者构建成功但推理输出明显异常。原因TensorRT 构建动态 shape engine 时校准过程本身需要固定 shape 的输入calibrator 提供的 batch 大小必须和网络实际使用的 shape 匹配。另外个别算子比如某些 reshape在动态 shape 下没有 INT8 kernel构建时会被标记为 unsupported直接打断整个构建。解决先固定 batch 和宽高生成 calibration cache再用带 profile 的配置去构建 enginecache 是 per-tensor scale和 batch 大小无关可以复用。如果构建报 unsupported 算子先定位是哪一层把这一层单独设成 FP16 或 FP32其他层保持 INT8通常能保住大部分收益。5.5 calibration cache 或 engine 版本混用结果“时好时坏”现象同一份代码同一台机器昨天构建的 INT8 engine 精度正常今天重新构建后 mAP 掉得厉害甚至构建失败。换 TensorRT 版本后旧 cache 读进来直接报错。原因calibration cache 里记录的是每个张量的 scale它和 TensorRT 版本、onnx 图结构强绑定。改了训练代码、换了 TensorRT 版本、或者 onnx 里一个节点名字变动旧 cache 就会错位。更隐蔽的是你项目里散落着多个同名 cache 文件构建脚本不小心读了旧的。解决把 calibration cache 当作模型资产一样管理。cache 文件名带上模型版本和 TensorRT 版本比如 yolov7_v1_trt8610.cache每次构建时指定明确的路径升级 TensorRT 后先删除旧 cache 重新校准再对比一次精度再进版本库。6. 验证收益四个指标与一个反复出现的坑6.1 四个必看指标量化做没做成不能只看“能跑起来”。我每次都会对比这四个指标mAP 掉点、单帧延迟、显存占用、engine 文件大小。mAP 掉点用同一份验证集分别跑 FP32 和 INT8 得出正常控制在 1% 以内QAT 介入后目标是回到 FP32 的 99% 以上。延迟用 TensorRT 内置 profile 看不自己打点避免预处理干扰。显存占用决定了同一张卡能开多少个 context这直接关系到 4.3 里算的路数。6.2 搭建可靠的验证流程我的验证流程分三步第一步用 trtexec 确认延迟和 kernel 选择正常第二步在验证集上跑 mAP这一步要拿同一个 TensorRT Python 推理接口FP32 和 INT8 共用一套避免手写两边逻辑不一致第三步拿真实摄像头回放数据在长视频上观察漏检率而不是只看单帧指标。回放这一步通常能抓到校准集里没覆盖到的场景这也是 PTQ 和 QAT 是否真的合格最有效的检验。6.3 把量化缓存当资产管理最后说一个我反复踩过的习惯问题。量化东西一旦混进部署最怕版本不干净。我自己现在的规矩是calibration cache 和模型权重一起进模型仓库文件名里带模型版本和 TensorRT 版本每次更新训练数据或模型结构之后先删干净旧 cache重新校准再对比一遍 FP32 基线。这套流程看着繁琐但能省掉排查“时好时坏”的时间。希望我的这些经验能帮到你少走一段我走过的弯路。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站