ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

YOLOv7 INT8量化部署实战:PTQ、QAT与TensorRT避坑指南

YOLOv7 INT8量化部署实战:PTQ、QAT与TensorRT避坑指南 简介面向需要完成YOLOv7模型量化与TensorRT高效部署的C开发者这套资料覆盖PTQ与QAT两条量化训练路线清晰区分了PTQ直接转换权重的快速做法与QAT在训练阶段模拟量化、追求更高精度的做法并给出从模型加载、输入预处理、推理执行、结果解析到NMS后处理的完整工程实现适合正在做实时目标检测落地或优化推理性能的中高级读者。资源包为zip压缩包共134个文件、约35.14MB包含35个Python脚本、33个YAML配置、14个Notebook说明、5个C源码与CMake构建文件另有Dockerfile、CUDA核函数、模型图片和样例视频便于按训练、量化、部署三阶段快速定位所需内容。已有485人学习下载。借助示例工程与详细代码注释读者可掌握TensorRT API调用方式、量化参数调节思路和常见部署避坑方法并可将PTQ或QAT产出的模型直接改造接入自己的检测业务减少从训练到上线的重复踩坑成本。1. 把 YOLOv7 压到 INT8 之前先想清楚 PTQ 和 QAT 这笔账边缘盒子和车端部署 YOLOv7FP32 模型往往跑得动但显存、功耗和算力一算账就得把模型压到 INT8。YOLOv7 走 INT8 有两条路PTQ 拿训练好的权重直接量化快但精度跟着数据走QAT 把量化误差塞回训练里补救周期更长但更稳。再往下就是 TensorRT 部署engine 构建、校准器配置、QAT 的 fake quant 导出一步一个坑。这篇内容按“PTQ 试水 → QAT 保精度 → TensorRT 收尾”的顺序把命令、参数和避坑记录一次讲清楚适合手里已有 YOLOv7 权重、准备压到 INT8 上线的工程同学。定位是偏目标检测的量化部署不涉及 NLP 或者生成式模型的量化原理相通但细节完全不同。2. 量化基础知识与选型INT8 的 S 和 Z 怎么算PTQ 和 QAT 贵在哪2.1 INT8 量化的数学直觉S 和 Z 负责解决映射问题量化说白了是一个数值映射过程。FP32 权重和激活的取值通常落在一个区间内量化的任务就是把这个区间压到 INT8 的 256 个离散值上。数学上写成r S × (q - Z)其中 r 是原始浮点值q 是量化后的整数S 是缩放因子Z 是零点。反推 q 的公式是q round(clip(r / S Z, -128, 127))S 决定精度级别Z 负责对齐零点。选定 S 和 Z 的过程就是校准过程。TensorRT 在实际处理权重时喜欢做对称量化也就是 Z 0这样可以让卷积计算里的乘法更干净激活值有时用非对称量化因为像 ReLU 之后的特征图分布只有正区间对称量化会把一半的表示能力浪费在负数上。实际计算卷积时量化后的运算是把两个 INT8 整数相乘再通过事先算好的 scale 缩放回 FP32。这里最容易掉进一个误区INT8 的加速很大程度上取决于硬件有没有对应的整数运算单元以及有没有 Tensor Core 支持。Pascal 架构的老卡只有 DP4A 这类整数指令速度提升幅度远不如 Turing 和 Ampere 上用 Tensor Core 跑 INT8 那么明显。这个在第 5 章避坑里会专门展开说。为了直观理解 S 的计算拿 YOLOv7 的一个 3×3 卷积层举例。该层激活值从数据里统计出来最大值是 6.0如果做对称量化S max(|min|, |max|) / 127 ≈ 0.0472。权重的分布一般更集中最大值可能只有 0.5 左右S ≈ 0.0039。这两者的差异决定了权重和激活需要分别维护自己的 scale而不是共用一个缩放因子。影响量化精度的核心矛盾是浮点数值域覆盖越宽分配到每个整数档位的分辨率越差。如果一个层里出现少量很大的离群值为了覆盖这些离群值整个量化区间被拉宽普通值域上的精度就被挤没了。这是后续选校准算法和理解掉点问题的关键。2.2 为什么目标检测模型比分类模型更容易被量化打穿同样是模型压缩YOLOv7 这类检测模型的 PTQ 掉点往往比 ResNet 这类分类网络严重。根本原因有两点检测头包含回归任务量化误差对边界框回归的影响不是线性而是指数级的另外特征金字塔结构里小目标的特征层通道数少动态范围波动更大量化会让原本微弱的目标响应被噪声吞掉。更具体地说YOLOv7 里几个对量化特别敏感的位置分别是第一个卷积层输入 RGB 像素分布和特征图完全不同、检测头前面的 1×1 卷积负责通道压缩、以及上采样之后的融合层。ptq 量化时如果使用全局统一策略这几个位置一旦失守mAP 掉个 3~5 个点并不意外。QAT 之所以能救回来本质上就是让模型在训练过程中适应这种噪声。前向传播时模拟量化误差反向传播时仍然使用浮点梯度模型学到的特征表达逐渐变得对离散化不敏感。这个差异说起来简单做起来需要处理很多细节比如哪些层需要跳过、哪些层需要冻结。2.3 PTQ 省事、QAT 贵这笔账到底怎么记选 PTQ 还是 QAT不是看哪个更高级而是看数据、算力和精度余量这三个条件。先看一张对比表维度PTQQAT需要的训练数据少量无标注数据即可需要训练集或接近训练分布的样本时间成本半小时到两小时数天取决于训练轮次典型 mAP 损失1%~5%看模型和数据0.3%~1%工程复杂程度低TensorRT 原生支持高需要改训练代码并处理导出适合场景原型验证、快速上线、数据敏感精度要求严格、长期维护的模型从项目决策角度看我会按这几个问题来问手上有没有可用的训练集没有数据或者数据不能离开业务环境直接做 PTQ别碰 QAT。模型转换后 mAP 掉了多少如果掉点都在 1 个点以内不值得为 QAT 投入额外时间。如果掉点超过 3 个点而且业务指标卡得很死那 PTQ 只是用来摸底最终大概率要走 QAT。还有一种折中方案叫部分量化先用 PTQ 跑一遍找出掉点最严重的两三个层只对这几层做 QAT其余层保持 PTQ 的尺度。这个方案在工程上很实用因为训练数据不足的时候集中力量修改少数敏感层的收益最高。2.4 动手前的精度验收基线别拿一个 mAP 打天下很多项目翻车不是因为量化没做好而是验收标准定得太粗。YOLOv7 这种多类别检测模型全局 mAP 掉 2 个点听起来不多但可能全部掉在小目标类别上而这部分恰恰是业务重点。我一般建议把验收指标拆成这样指标用途量化后的合格线全部类别 mAP 0.5整体性能相对 FP32 掉点不超过 2%小目标 mAP检测量化对大物体不敏感的问题掉点不超过 3%每类 AP 对比定位某个类别是否被量化打穿单类 AP 不掉超过 5%极端场景 recall夜间、遮挡、模糊样本不出现系统性失效这个表不是让你全都跑一遍而是提醒构建一个可比较的验证脚本。把 FP32 模型在验证集上的输出结果导出成 JSON 或 pickle保存起来然后所有量化版本都基于同一份真实标签做对比。这样即使后续换了 TensorRT 版本也能快速定位是量化问题还是部署环境变化。3. 用 TensorRT 给 YOLOv7 做 PTQ标定数据、校准器和最小构建脚本3.1 准备阶段从 YOLOv7 权重到结构精简的 FP32 ONNXPTQ 的第一步不是量化而是先把训练好的 PT 权重转换成 TensorRT 能吃的计算图。YOLOv7 的官方仓库里有导出脚本常见的做法是使用类似python export.py --weights yolov7.pt --grid --end2end --simplify这样的命令导出 ONNX导出的模型输入是 1×3×640×640 的 RGB 张量输出根据不同配置有两种形式带 NMS 的端到端输出或者原始特征图输出。这里给一个非常关键的工程建议如果只是做量化验证导出时不建议把 NMS 放进计算图。TensorRT 对自定义 NMS 插件的支持在不同版本上有差异插件不注册直接 build 失败就算 build 成功后面做 QAT 再导出时也会遇到算子兼容问题。保留原始输出把 NMS 和非极大值抑制放到推理阶段的 Python 或 C 代码里实现。性能上会有几毫秒损失但如果目标是量化模型可以把这损失放到后面的优化阶段再处理。导出的 FP32 ONNX 建议用onnxsim做一遍简化消除冗余的 reshape 和 transpose。YOLOv7 的计算图被简化后TensorRT 在做层融合时更容易匹配得到优化 pattern。这个步骤对 QAT 导出同样有效。3.2 标定数据的选择原则数量不重要分布才重要PTQ 需要一批数据去统计激活值分布这批数据叫校准集。常见误区是校准集越多越准实际上一千张分布正确的图像比五千张单场景的图像效果好得多。校准集的作用是让模型“看”到正常的输入分布从而确定激活值的 min/max并不是训练数据不需要覆盖所有边界情况。选择校准集的三个原则必须来自与业务接近的分布比如部署场景是夜间道路就不要全部用白天图像。类别要均衡如果一个类别在训练集中占 60%校准集里也应该接近这个比例。避免过度集中的背景检测框比较少的大背景图会让特征图分布偏向低激活值区间。校准集的数量一般取训练集的 1% 到 2%对 YOLOv7 这种模型500 到 2000 张是一个合理区间。小于 200 张时熵类校准器容易过拟合到少量样本上大于 5000 张几乎不再带来精度提升只是浪费时间。可以写一个简单的脚本生成校准文件列表便于后续读取import os import random def build_calib_list(train_dir, output_path, num_samples1000, seed42): img_exts (.jpg, .jpeg, .png, .bmp) files [os.path.join(train_dir, f) for f in os.listdir(train_dir) if f.lower().endswith(img_exts)] random.seed(seed) if len(files) num_samples: files random.sample(files, num_samples) with open(output_path, w, encodingutf-8) as fout: for item in sorted(files): fout.write(item \n) print(ftotal {len(files)} images - {output_path}) if __name__ __main__: build_calib_list(data/train/images, data/calib_list.txt, 1000)这个脚本通过固定 random seed 保证校准集可复现这也是很重要的细节。因为 TensorRT 校准时的 batch 划分顺序会影响最终统计结果同样代码跑两次得到不同的 engine 是很正常的带 seed 可以避免排查问题时分不清是数据问题还是校准随机性。3.3 一个最小 Python 校准器基于 TensorRT EntropyCalibrator2TensorRT 的 PTQ 并不直接吃图片文件而是由开发者提供一个校准器对象TensorRT 在里面预定义好的get_batch接口里取数据。下面是一段我常用的 YOLOv7 INT8 校准器实现包含 letterbox 预处理和缓存文件机制import os import cv2 import numpy as np import tensorrt as trt class YOLOCalibrator(trt.IInt8EntropyCalibrator2): def __init__(self, calib_list, input_size(640, 640), batch_size8, cache_fileyolov7_ptq.cache): super().__init__() self.input_size input_size self.batch_size batch_size self.cache_file cache_file self.batch_idx 0 with open(calib_list, r) as f: self.image_paths [line.strip() for line in f if line.strip()] self.letterbox self._get_letterbox_fn() self.calib_data self._preload_data() def _get_letterbox_fn(self): # YOLOv7 训练时使用 letterbox 灰度填充这里保持一致 pass def _preload_data(self): # 一次性把所有校准图读入内存并转成 NCHW FP32 # 如果图片数量较大可以按 batch 延迟读取避免占用大量显存 data np.zeros((len(self.image_paths), 3, *self.input_size), dtypenp.float32) for i, path in enumerate(self.image_paths): img cv2.imread(path) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # letterbox resize padding 到 input_size # 归一化到 0~1和训练保持一致 data[i] img.transpose(2, 0, 1) / 255.0 return data def get_batch_size(self): return self.batch_size def get_batch(self, names, current_batch0): start self.batch_idx * self.batch_size end start self.batch_size if start len(self.calib_data): return None self.batch_idx 1 batch self.calib_data[start:end] return [np.ascontiguousarray(batch).astype(np.float32)] def read_calibration_cache(self): if os.path.exists(self.cache_file): with open(self.cache_file, rb) as f: return f.read() return None def write_calibration_cache(self, cache): with open(self.cache_file, wb) as f: f.write(cache)这个校准器把图片一次性预载进内存省去了每次 build 时重新读盘的耗时。要注意的点是get_batch返回的数组必须是连续内存所以加上np.ascontiguousarray避免 TensorRT 读内存越界。缓存机制的作用是首次 build engine 时运行完整标定流程之后再次 build 如果网络结构没变可以直接读缓存文件跳过标定过程。接着是构建 engine 的代码import tensorrt as trt TRT_LOGGER trt.Logger(trt.Logger.WARNING) def build_int8_engine(onnx_path, calibrator, save_path, workspace4): builder trt.Builder(TRT_LOGGER) network builder.create_network(1 int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)) parser trt.OnnxParser(network, TRT_LOGGER) with open(onnx_path, rb) as f: assert parser.parse(f.read()), ONNX 解析失败请检查简化后的模型 config builder.create_builder_config() config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, workspace * 1024 * 1024) config.set_flag(trt.BuilderFlag.INT8) config.int8_calibrator calibrator engine builder.build_serialized_network(network, config) if engine is None: raise RuntimeError(TensorRT engine 构建失败) with open(save_path, wb) as f: f.write(engine) print(fengine saved to {save_path})这里workspace参数控制 TensorRT 在做算子融合和 kernel 选择时可用的显存上限。YOLOv7 的检测头比较复杂如果 workspace 设置太小会导致部分 fused kernel 无法选中从而影响 INT8 的实际加速效果。一般 4GB 起步显存够用就再往上加。3.4 校准器选型Entropy 与 MinMax 的差异TensorRT 提供三种校准算法MinMax 校准、Entropy 校准和 Entropy 2即 IInt8EntropyCalibrator2校准。我实际用得最多的是 Entropy 2因为它通过对分布求 KL 散度来寻找信息损失最小的量化边界对大多数卷积网络效果稳定。下面这张表可以帮你快速选型校准算法原理优点适用场景MinMax直接用所有样本的 min/max 作为量程实现简单速度最快激活值分布接近均匀离群点较少Entropy基于 KL 散度经典熵校准对分布适应性比 MinMax 好数据量大时效果稳定Entropy 2改进版熵校准融合更多直方图策略更稳定受异常分布影响较小YOLOv7 这类复杂检测模型的默认选项这里需要提醒的是如果校准数据分布和真实部署分布差距太大再好的校准算法也救不回来。遇到 mAP 掉点严重时先怀疑数据分布再怀疑算法选择顺序不能反。3.5 拿验证集给 INT8 引擎验收mAP 对比怎么算构建完 INT8 engine下一步不是直接上线而是和 FP32 做一次严格对比。做法是用同一份验证集分别跑 FP32 ONNX 和 INT8 TensorRT 引擎或者用 FP32 TensorRT engine输出检测结果后统一用同一套 mAP 评估脚本计算指标。def evaluate_engine(engine_path, onnx_path, val_json, img_dir): # 伪代码实际工程中按自己的推理框架填充 # fp32_result run_fp32(onnx_path, val_images) # int8_result run_tensorrt(engine_path, val_images) # mAP_fp32 eval_coco(fp32_result, val_json) # mAP_int8 eval_coco(int8_result, val_json) # print(fFP32 mAP: {mAP_fp32:.4f}, INT8 mAP: {mAP_int8:.4f}) pass这个验收脚本的价值不只是看一个数字更重要的是让你能看到掉点集中在哪些类别。如果某个业务核心类别的 AP 掉了 8 个点而全局 mAP 只掉了 1.5 个点这时候全局指标就是骗人的。重点类别的单独评估必须在量化上线前完成。4. YOLOv7 QAT 训练pytorch_quantization 接入、冻结与超参调优4.1 QAT 在做的事把“模拟量化”当成训练的一部分QAT 的全称是 Quantization-Aware Training核心思想是在前向传播时插入伪量化节点让模型提前感受量化噪声。所谓伪量化就是前向计算时把浮点值量化成 INT8 再反量化回浮点这个过程不可导但反向时使用直通估计器把梯度原样传递过去。模型在训练中不断调整权重使得量化后的表现尽量接近量化前。和 PTQ 最大的区别是PTQ 是在模型已经收敛之后被动地找一组量化参数而 QAT 主动让模型适应量化。这个区别让 QAT 往往能把 mAP 损失控制在 1% 以内但也意味着你需要改训练代码、准备训练数据、并且花额外的训练时间。在 YOLOv7 上做 QAT业界最常用的套路是先加载 FP32 的预训练权重用一小段训练数据做激活值校准然后以比较小的学习率对整个网络做微调。不需要从零训练那样既浪费时间又可能破坏原始特征提取能力。4.2 在 YOLOv7 代码里接入 pytorch_quantization 并加载权重常见的实现库是 NVIDIA 的pytorch_quantization它提供quant_modules.initialize()来替换 PyTorch 里的标准卷积层和全连接层。下面这段代码展示了如何在 YOLOv7 训练代码里接入量化模块from pytorch_quantization import quant_modules from pytorch_quantization import nn as quant_nn # 这一行会把 torch.nn.Conv2d/Linear/ReLU 替换为伪量化版本 quant_modules.initialize() # 然后正常创建或加载 YOLOv7 模型 from models.yolo import Model # 根据实际仓库结构导入 model Model(cfgcfg/yolov7.yaml, ch3, nc80) ckpt torch.load(yolov7.pt, map_locationcpu) model.load_state_dict(ckpt[model].float().state_dict()) # 统计哪些层被替换成了量化版本 quantized_modules [name for name, m in model.named_modules() if isinstance(m, quant_nn.TensorQuantizer)] print(ftotal quantized modules: {len(quantized_modules)})quant_modules.initialize()是自动替换全局的它会作用在之后创建的所有模型上。如果只想量化部分层手动换模块会更稳妥但代码量会大不少。我一般先全局量化跑一轮看掉点情况后再决定是否跳过某些敏感层。加载 FP32 权重后还需要一个“热启动”阶段。因为初始化时量化尺度都是 1直接开始微调会出现训练初期 loss 剧烈抖动。建议先让模型对着一小批真实数据跑几次前向激活值的 scale 会自动有初值。这一步不需要太多数据256 张图足够。4.3 冻结与导出让 PyTorch 模型变成带 Q/DQ 节点的 ONNXQAT 训练结束时权重仍然是 FP32 的但模型里的每个量化节点已经记录好了每个激活值和权重的 scale 信息。要导出成 TensorRT 认识的格式需要把 scale 冻结住并设置 fake quant 模式这样导出的 ONNX 里会包含 TensorRT 能识别的 Q/DQ 节点。import torch from pytorch_quantization import nn as quant_nn # 将模型切换为 eval 模式 model.eval() # 冻结 fake quant 参数导出时转换为 Q/DQ 节点 quant_nn.TensorQuantizer.use_fb_fake_quant True # dummy input 必须和部署输入一致 dummy_input torch.randn(1, 3, 640, 640).cuda() with torch.no_grad(): torch.onnx.export( model, dummy_input, yolov7_qat.onnx, opset_version13, input_names[input], output_names[output], dynamic_axesNone ) # 导出完把开关恢复避免影响后续训练 quant_nn.TensorQuantizer.use_fb_fake_quant False导出 ONNX 时最容易犯的错误是忘了设置use_fb_fake_quant True这样导出的是普通 FP32 计算图没有任何量化信息TensorRT 会把它当成普通 ONNX 来处理之前 QAT 的工作全部白费。还有一个小坑opset_version最好选 13 以上低版本的 ONNX 算子对 Q/DQ 的支持存在问题会导致 TensorRT 解析失败。4.4 QAT 必调的四个参数学习率、训练轮次、数据增强和批大小QAT 训练和正常微调完全不是一回事参数设置错了会让 loss 明明下降但量化精度反而更差。我总结四个最关键的参数学习率是最容易出问题的。FP32 微调常用的学习率在 0.001 左右但 QAT 建议降到 0.0001 到 0.0004。因为模型已经在预训练权重附近收敛了过大的学习率会让权重跑离原分布量化误差重新被拉大。训练轮次不需要多5 到 10 个 epoch 就够。有些模型在 2 个 epoch 后精度就接近最优后面再多轮次只是在过拟合校准分布。可以每个 epoch 结束后用验证集测一次 mAP选最好的那个 checkpoint而不是用最后一轮的。数据增强要保守。随机翻转和轻微颜色抖动可以用但像 RandomErase、强马赛克增强这种对检测器扰动较大的增强手段建议关闭。QAT 的目的是让模型适应量化不是做数据泛化。批大小影响 BN 层的统计量更新。QAT 里伪量化节点的 scale 在训练过程中是逐步更新的batch 太小会让 scale 抖动剧烈。如果显存允许batch size 至少保持和正常训练时一致不能为了频繁验证而把 batch 降到 4 以下。4.5 导出后部署TensorRT 处理 Q/DQ 的方式带 Q/DQ 节点的 ONNX 不需要再提供校准数据给 TensorRT因为每个量化点的 scale 都已经写在图里了。构建 engine 时TensorRT 会自动识别并利用这些量化信息把算子在 INT8 精度下执行。这意味着 build 时也要注意需要在 builder config 里设置 INT8 flag但不需要设置int8_calibrator。构建过程和第三章的代码几乎一样只多了几个配置项config.set_flag(trt.BuilderFlag.INT8) config.set_flag(trt.BuilderFlag.FP16) # 可选提升部分算子速度 # 不再需要 config.int8_calibrator calibrator有个容易忽略的细节如果 QAT 导出的 ONNX 里某些算子的量化尺度在训练时被关闭了也就是这个层实际是 FP32 精度TensorRT 依然会在 INT8 引擎里尝试量化它。为了避免这种情况技术上需要保证导出的 ONNX 里所有层都带量化节点或者显式地把不想量化的层包装成独立子图。这个可以通过导出的 ONNX 用 Netron 可视化来检查重点看是否所有 Conv 前后都有 Q/DQ 节点。5. TensorRT 部署常见问题与避坑记录现象、原因和解决5.1 校准集没问题但 mAP 掉点超过 5%问题可能出在 letterbox 逻辑做 PTQ 时校准数据和训练数据用了同一条图像路径但 mAP 还是崩得厉害。后来逐层对比 FP32 和 INT8 的输出特征图发现激活值分布完全对不上。原因不是量化而是校准器里的 letterbox 预处理和训练时不一样填充色用的灰度值不是训练代码里的 114输入图像的长宽缩放比例也没有保持一致。解决方式是重新编写 letterbox 预处理函数确保和训练代码同步。这里需要特别注意校准数据里每一张图都要经过完全相同的前处理任何一个像素分布的偏移都会影响统计结果。5.2 校准缓存文件不生效每次 build 都在重新跑标定构建 INT8 引擎时首次运行会生成.cache文件里面保存着每个张量算子的量化边界。第二次构建时如果网络结构完全一致TensorRT 应该直接读取缓存而不是重新跑标定。但实际上换一个 ONNX 文件路径或改一个 workspace 大小缓存就失效了。原因是 cache 文件中记录了输入张量维度和网络签名任何影响网络结构的改动都会导致它失效。解决方式是确认 ONNX 文件内容不变、输入 shape 不变然后只改 cache 文件的路径后缀来强制复用。如果死活不生效直接删除缓存文件重新标定不要在这件事上浪费太多时间。5.3 QAT 训练 loss 下不去除了学习率问题还有 Bert 里的 scale 竞争QAT 训练时 loss 一直在高位震荡怎么调学习率都没用。后来发现是训练初期没有进行热启动伪量化节点的 scale 在反向传播中被梯度推来推去模型参数也在同时调整两头都在动很难收敛。解决方式是加载 FP32 权重后先用 200 张左右真实数据前向传播几步让 scale 稳定下来再进入正常训练循环。另外如果用的是 NVIDIA 的pytorch_quantization还要注意某些模块比如quant_nn.Conv2d里的权重是闭包里的直接model.load_state_dict可能把原来的标准卷积权重复制过去后并没有真正作用在量化权重上。检查方式是把model.state_dict()的 key 打印出来确认是否包含weight_quantizer相关字段。5.4 TensorRT 10.x 在 GTX 1070 上的表现不要把希望全压在 Tensor Core 上不少人拿着发布较早的 GPU 型号来问 TensorRT 10.x 的 INT8 支持情况。以 GTX 1070 为例它属于 Pascal 架构支持 INT8 计算指令DP4A但不具备 Turing 和 Ampere 上专门处理 INT8 矩阵运算的 Tensor Core。TensorRT 10.x 依然会为这张卡生成 INT8 引擎也能正常跑但加速幅度远没有数据宣传里说的那么夸张。实测幅度根据模型而定有些模型 INT8 和 FP32 的延迟差距甚至可以不到 20%。解决方式是部署前先只看这张 GPU 的 INT8 能够带来的真实收益。如果延迟指标卡得很紧建议先做一轮 profiler 看看瓶颈是卷积计算还是预处理和后处理。如果是后者即使把模型压到 INT8 也不会有太多整体收益不如先优化图像解码和 NMS 的 CPU 部分。5.5 INT8 引擎延迟没有下降反而上升多数是 end2end 输出没有解码导致最终部署时往往会直接用带 NMS 的端到端 ONNX 构建 TensorRT 引擎这本身没有问题但对 YOLOv7 来说NMS 在 INT8 引擎里的执行开销会让量化带来的收益被抵消。原因很简单NMS 处理的是候选 box这个模块主要吃 CPU 和少量 GPU 逻辑INT8 卷积加速几乎对它没有帮助。解决方式是分两条路线处理如果选择端到端模型先测一下 INT8 engine 和 FP32 engine 的端到端延迟对比假设差距仍然不够就改成原始输出 外部 NMS 的架构。后处理瓶颈在解码和 NMS 里量化优化不到这块这是很多项目最后发现 INT8 部署性能提升不大的真实原因。5.6 build 过程中报 Tactic 相关错误或显存不足怎么处理TensorRT 构建 INT8 引擎时会在每个算子上做策略搜索tactic搜索过程会占用额外显存。原本 FP32 构建没问题的显存容量换到 INT8 构建时报错大多不是引擎本身显存不够而是搜索时临时显存溢出。解决方式是把 workspace 调小比如从 4GB 降到 1GB然后重新尝试。另一个更可控的方案是设置算法选择器关闭不必要的 tactic避免搜索范围过大。如果项目对构建时间敏感可以接受牺牲一点点 kernel 性能来换取更短的搜索时间这也是常见做法。6. 量化模型的验证技巧与我现在保留的习惯做完整套 PTQ 和 QAT 之后验证环节必须足够严谨否则量化过程中的隐患会在上线后爆发。我现在的习惯是在缓存和 engine 上都保留两个信息——采用什么校准算法和量化策略以及输出结果相对 FP32 的 mAP diff。因为项目周期拉长后容易忘记这个 engine 是怎么构建的后期重新构建时如果换了 TensorRT 版本行为可能不一样。具体验证时会做三件事。先跑精度对比用同一份验证集分别跑 FP32 和 INT8 引擎记录每个类别的 AP 以及小目标、中目标、大目标的 mAP。然后跑稳定性验证把同样的输入连跑三轮确认输出稳定一致。量化推理具有确定性如果输出有抖动要么是内存分配问题要么是后端插件状态被污染。最后跑延迟验证用预热后的 engine 测量 200 次推理的平均延迟记录预热前后的差异这能帮判断 CPU 初始化和显存分配是否在拖后腿。一个比较实用的小技巧是在构建 INT8 引擎前用同样的校准数据集分别跑一次 Entropy2 和 MinMax 两种校准得到两个 cache 文件然后各构建一次 engine 并对比 mAP。大多数情况 Entropy2 胜出但碰到激活值分布偏均匀的模型时 MinMax 反而更稳。两种方案对比后选更优的这个过程读作多重校准比盲目相信某个校准器要可靠得多。最后说一个在项目里翻过车才总结出来的习惯量化模型上线前我会把 FP32 模型的输出结果以 pickle 格式存档。这个存档有两个作用一是后续任何一次改动都能快速对比不用重新去跑 FP32 模型二是如果小流量验证中发现量化模型有问题可以回溯对比是训练误差还是部署误差。这个文件一般就几十兆占用空间可以忽略但排查问题时能省出大量时间。QAT 训练时同样做 checkpoint 对比每个 epoch 保存一次避免最后一轮 loss 下降但 mAP 反而回头的情况。量化部署从来没有银弹PTQ 是性价比最高的试水方式QAT 是保住业务精度的最终手段TensorRT 则是把两者落地成可用引擎的骨架。希望这个方向的踩坑记录能帮你少折腾几个晚上。本文还有配套的精品资源点击获取
返回列表