拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

YOLOv5剪枝与INT8量化实战:从稀疏训练到边缘部署的完整流程

YOLOv5剪枝与INT8量化实战:从稀疏训练到边缘部署的完整流程 简介YOLOv5模型剪枝与量化一键运行资源面向需要将目标检测模型部署到边缘设备、移动端或优化GPU推理速度的开发者。资源提供完整可执行的压缩与加速流程先通过结构剪枝与权重剪枝组合将模型体积压缩70%以上再用量化感知训练保证精度并配好TensorRT转换和C推理示例帮助读者跳过繁琐的环境调试直接跑通从PyTorch到TensorRT引擎的整条链路。压缩包共208个文件包含59个Python脚本、48个YAML配置文件以及C/CUDA源码、Dockerfile、ONNX与PT权重、Shell脚本和示例图片视频等覆盖数据准备、剪枝、量化、验证、部署各环节整体大小24.2MB目录结构清晰便于对照修改超参。资源还附有图文说明和示例视频已有1884人学习下载。适合有一定YOLOv5基础、希望系统掌握模型压缩与高效部署的中高级计算机视觉开发者。1. YOLOv5 剪枝和量化到底管什么用谁该现在就看这套流程做目标检测部署的人迟早要撞上同一个问题模型在服务器上跑得好好的一挪到边缘设备就开始卡。YOLOv5 作为目前落地最广的检测框架之一显存和带宽压力一直是部署组的老大难。yolov5 剪枝和量化不是两个孤立技巧而是一条把模型体积和推理耗时同时压下来的释放路径剪枝负责删掉冗余计算量化负责把 FP32 精度压到 INT8 甚至更窄。对要在边缘端做实时检测、又要保住 mAP 的团队来说这条路可以直接减少一半以上的显存占用。下面会从一个能跑通的“代码一键运行”流程出发把剪枝算法、量化步骤、参数边界和最常见的翻车点都摊开讲。2. 剪枝和量化的分工通道剪枝与 INT8 量化为什么是一对搭档2.1 剪枝的本质从权重稀疏到通道稀疏为什么最终选了通道在说剪枝之前先分清两种稀疏。权重稀疏也就是常说的非结构化剪枝是把网络里趋近于零的单个权重直接置零得到的是一个稀疏矩阵。参数确实少了但硬件加速并不直接受益因为推理时还是得把整个矩阵读出来只是乘法里多了一堆零。严格来说这是模型压缩里的一个研究方向可真正能换来推理速度提升的是通道剪枝。通道剪枝针对的是卷积层的输出通道。一个 3x3 卷积层如果某个输出通道的卷积核权重整体都很小那它产生的特征图对后续计算的贡献就接近为零。把这一整个通道删掉下一层卷积的输入通道自动跟着减整条计算链的 FLOPs 是实打实地降下来。问题是怎么判断一组权重“整体都很小”业界最通用也最好实现的指标是看这个通道对应的 BatchNorm 层 gamma 值。YOLOv5 在主干和检测头里大量使用 Conv-BN-LeakyReLU 这样的基础块。BN 层对特征做归一化后还要做一次还原y gamma * x_hat beta其中 gamma 是对归一化之后的特征做缩放的尺度。如果某个通道的 gamma 被训练到接近 0说明这个通道的输出对最终 loss 的贡献微乎其微删掉它影响最小。所以通道剪枝的标准做法是先做稀疏训练让 gamma 尽量往 0 靠再按阈值把低于阈值的通道摘掉。那为什么不能直接看普通训练完的 gamma 值来剪非要先做稀疏训练因为正常训练得到的 gamma 分布是比较平坦的整体在 0 上下但并没有集中到恰好为 0 的区间。按绝对值排序再加阈值去剪会误伤很多真正有语义信息的通道。稀疏训练的本质是在原 loss 上加一项 L1 惩罚让一部分 gamma 在梯度更新里被持续压缩最终出现一批明显贴 0 的通道这时再下剪子才是安全的。这一步骤是整个流程里唯一需要训练的环节也是很多人最容易跳过的一环后面避坑章我会单独展开。2.2 量化的本质FP32 到 INT8 的映射以及 PTQ 与 QAT 的边界模型量化做的事情是把权重和激活值从 FP32 浮点数映射到更低位的整数最常用的落地档位是 INT8。映射关系可以用 q r / scale zero_point 来表示scale 决定浮点区间怎么压缩到 256 个整数刻度zero_point 负责对齐零点。因为卷积和全连接层对数值误差有一定容忍度映射之后的推理结果不会立刻崩掉但误差会逐层累积所以量化从来不是“转一下格式”那么简单核心在校准。量化有两条技术路线业界叫 PTQ 和 QAT。PTQ 也叫训练后量化是在模型训练完之后拿一批有代表性的数据去统计每一层激活值的动态范围再用这个范围定 scale 和 zero_point。它快、不用改训练代码但对数据分布敏感校准集选得不典型的话量化后的模型直接没法用。QAT 是量化感知训练在训练过程中同步插入模拟量化节点让网络自己适应量化误差。精度通常比 PTQ 高不少代价是要重跑一轮训练。在 YOLOv5 剪枝量化这条流程里最理性的顺序是先做 PTQ跑出的 mAP 掉点在阈值以内就直接交付不满足再退回到 QAT。把导出文件和量化工具放在一起看onnx 量化 int8 的工作流现在已经比较成熟。先导出 YOLOv5 的 .onnx再交给 ONNX Runtime 的 quantization 接口做静态量化校准集只需要准备几百到上千张真实场景图片。这里要强调“静态”两个字静态量化会把激活值也固定成 INT8 范围而动态量化只压缩权重激活值还是 FP32推理速度提升非常有限。目标检测这种带多尺度输出和复杂后处理的模型为了最终加速效果尽量走静态量化。2.3 组合逻辑剪枝压缩结构量化压缩精度两者删的不是同一种冗余单独看剪枝和量化都能压缩模型但它们清除的是两类完全不同的冗余。剪枝删的是结构性冗余也就是对整个计算图贡献极低的通道量化压的是数值精度冗余把连续的浮点空间映射成不均匀的整数刻度。结构上先变瘦再做数值低位压缩两者的压缩比是相乘关系这也是为什么要把两个技术串在一起用而不是只做一个。有个容易误解的点要澄清剪枝之后的 .pt 模型文件不一定会立刻变小很多因为 PyTorch 保存的权重文件里包含优化器状态、anchors、训练超参数等一堆附加信息通道变少并不会让文件自动重组得最紧凑。真正看到体积和速度收益是在导出 ONNX 或转换到 TensorRT 之后。所以做这套流程不要拿 .pt 文件大小去判断剪枝有没有用要去测 ONNX 的推理耗时和内存占用。目标检测模型里检测头占据相当大的计算量剪枝时如果把手部输出层的通道剪得过多后处理代码里写死的解析逻辑也可能跟着失效很多从 ResNet 剪枝流程转过来的人在这里踩过坑。再从部署兼容性角度定一个判断标准剪枝改变了网络结构量化改变了数值精度叠加之后模型版本实际上已经变成一个“新模型”。后续任何复现实验都要把训练配置、剪枝率、量化校准集这三个变量记录清楚否则出了问题根本没法回滚。3. 一键运行从 YOLOv5 基础训练到剪枝到 INT8 量化的完整流程3.1 环境准备conda 起一个能稳定复现的 YOLOv5 环境跑一键脚本之前先把环境固定下来。用 conda 建独立环境是最不容易出问题的做法Python 版本与 PyTorch 版本要匹配PyTorch 的 CUDA 版本必须和显卡驱动对齐。下面这套组合在我这边测下来比较稳conda create -n yolov5_prune python3.8 -y conda activate yolov5_prune pip install torch1.13.1 torchvision0.14.1 --index-url https://download.pytorch.org/whl/cu117 cd yolov5 pip install -r requirements.txt把 torch 固定在 1.13.1主要考虑兼容性。新版 PyTorch 2.x 对 torchvision 的接口改动比较大很多剪枝实现是在老版本接口上调通的用新版容易遇到算子不匹配。如果显卡驱动不支持 cu117可以换 cu116 镜像但 CPU 和 GPU 两端的 CUDA 版本必须保持一致。requirements.txt 装完先把官方推理 demo 跑一遍确认环境本身没有问题。这一步不要省它能把“环境问题”和“算法问题”分开后面脚本出任何状况都好定位。requirements.txt 会附带安装 matplotlib 和 pandas平常不影响推理但在 Docker 镜像里会明显增大体积做容器化部署时可以把这些测试依赖拆出去。3.2 一键运行脚本稀疏训练、剪枝、微调、量化四阶段串成一条命令我所说的“代码一键运行”核心是把整条链路串成 bash 脚本从基础权重出发按顺序完成稀疏训练、通道剪枝、微调和 INT8 量化。下面是脚本主干#!/bin/bash set -e # 一键运行 YOLOv5 剪枝 INT8 量化 # 用法: bash run_prune_quant.sh data.yaml weights/yolov5s.pt DATA$1 BASE_WEIGHTS$2 SPARSITY0.005 GLOBAL_PERCENT0.35 CALIB_IMGS1000 # 阶段 1: 正常训练一个基线模型给后续对比留参照物 python train.py --data $DATA --weights $BASE_WEIGHTS \ --epochs 100 --batch-size 16 --name baseline # 阶段 2: 稀疏训练对 BN 的 gamma 施加 L1 正则惩罚 python train_sparsity.py --data $DATA \ --weights runs/train/baseline/weights/best.pt \ --sparsity $SPARSITY --epochs 100 --batch-size 16 \ --name sparsity # 阶段 3: 通道剪枝按全局 gamma 阈值生成 mask 并重建网络 python prune.py --weights runs/train/sparsity/weights/best.pt \ --global-percent $GLOBAL_PERCENT --output-dir pruned_output # 阶段 4: 剪枝后微调恢复被剪通道损失的精度 python train.py --data $DATA \ --weights pruned_output/pruned_model.pt \ --epochs 100 --batch-size 16 --name finetune # 阶段 5: 导出 ONNX 并做 INT8 静态量化 python export.py --weights runs/train/finetune/weights/best.pt --include onnx python quantize_onnx.py --int8 --calib-imgs $CALIB_IMGS \ --model-path runs/train/finetune/weights/best.onnxbatch-size 和 data 参数按你的硬件调整但阶段顺序不要乱。阶段一先训练基线模型是为了给剪枝前后留一个公平的对照如果直接从别人给的预训练权重开始稀疏训练后面掉点了你分不清是数据集差异还是剪枝带来的。阶段二的 sparsity 控制 L1 惩罚强度0.005 是我从 0.001 起步反复试出来的默认值这个值的意义后面第 4 章会详细说。阶段三的 global-percent 0.35 表示剪掉约 35% 的通道这个值不能拍脑袋定要参考稀疏训练之后的 gamma 分布。阶段四的微调是整个流程里最容易偷懒的一步很多人剪完直接在验证集上一测发现掉点就宣布剪枝无效其实是被剪网络还没恢复好。剪枝后的权重分布已经被打乱必须给它足够的训练轮次重新收敛一般不少于 50 个 epoch。阶段五的量化脚本核心代码大致长这样# quantize_onnx.py 的核心片段 from onnxruntime.quantization import quantize_static, QuantFormat, QuantType, CalibrationDataReader import numpy as np class YOLOCalibReader(CalibrationDataReader): def __init__(self, calib_images, input_nameimages): self.input_name input_name self.data calib_images self.idx 0 def get_next(self): if self.idx len(self.data): return None img self.data[self.idx] self.idx 1 return {self.input_name: img} def run_quantize(onnx_path, output_path, calib_loader): quantize_static( model_inputonnx_path, model_outputoutput_path, calibration_data_readerYOLOCalibReader(calib_loader), quant_formatQuantFormat.QDQ, per_channelTrue, weight_typeQuantType.QInt8, )这段代码里有几个关键点。YOLOCalibReader 的 get_next 每次返回一个 dict键名必须和 ONNX 模型的实际输入节点名一致通常 YOLOv5 导出后是images但最好用 onnx.load 看一眼 graph.input 确认写错会报运行时错误。calib_loader 是提前预处理好的图片列表shape 要跟导出时的输入尺寸一致比如 640x640需要经过 letterbox 而不是直接 resize。per_channelTrue 会让每个输出通道单独计算 scale 和 zero_point量化精度通常比 per-tensor 高不少代价是模型略大但目标检测这种敏感任务值得。3.3 剪枝脚本内部BN gamma 阈值怎么定shortcut 边怎么处理通道剪枝的标准流程是扫描模型中所有 BN 层收集 gamma 值按全局或分层阈值生成 mask再根据 mask 重建卷积层权重。先看 mask 生成的思路# prune.py 片段计算全局 gamma 阈值并生成剪枝 mask import torch def get_prune_mask(model, global_percent0.35): bn_weights [] for m in model.modules(): if isinstance(m, torch.nn.BatchNorm2d): # gamma 就是 BN 层的 weight 参数 bn_weights.extend(m.weight.detach().cpu().tolist()) sorted_weights torch.tensor(sorted(bn_weights)) # 取第 (1-global_percent) 分位的值作为阈值 threshold sorted_weights[int(len(sorted_weights) * (1 - global_percent))] masks {} for name, m in model.named_modules(): if isinstance(m, torch.nn.BatchNorm2d): # 绝对值低于阈值说明该通道贡献被稀疏训练压没了 mask m.weight.detach().abs() threshold masks[name] mask return masks这段代码把模型中所有 BN 的 gamma 收集起来算出全局阈值再逐通道比较。为什么用全局阈值而不是逐层阈值因为稀疏训练之后不同层的 gamma 整体高低差异很大逐层比较会把某些层剪成空壳而全局阈值让被剪通道尽量均匀地分散到各层。global_percent 设为 0.35表示剪掉按绝对值排序后最靠后的 35%这个比例不是硬指标要结合 gamma 分布调整。shortcut 连接的坑在 YOLOv5 的 C3 模块里尤其明显。C3 模块内部有残差边如果残差边两侧特征图的通道数对不上加法运算直接报错。所以剪枝要么跳过残差边连接的层要么强制对齐两侧的剪枝 mask。我一般取两者并集也就是把 shortcut 两侧涉及的卷积层看成一组要剪一起剪要留一起留。这个操作必须在重建权重之前完成否则后面一定会遇到 shape mismatch。剪枝完成之后最直观的判断是参数量和 FLOPs 统计。如果脚本跑完发现参数量和原始模型几乎一样说明 mask 根本没有落到实际结构上常见的空转原因是对模型结构遍历时把 C3 模块里的子模块漏掉了。遇到这种情况可以打印几个关键卷积层的 weight shape对比剪枝前后有没有变化逐层验证比闷头看总参数量有效得多。4. 参数怎么调稀疏度、剪枝率、校准集和微调策略的实战顺序4.1 核心参数速查表先把边界划清楚在把剪枝量化当工具用之前先给一张我自己常用的参数参考表。具体数值会因数据集和硬件略浮动但合理区间基本不会超出这个范围。参数常见范围影响我的默认值sparsityL1 正则系数0.001 ~ 0.01过大会压垮模型表达过小则稀疏化不彻底0.005global-percent剪枝比例0.2 ~ 0.5剪得越多体积越小微调成本越高0.35微调 epochs50 ~ 150决定精度恢复程度太少掉点补不回来100微调初始学习率0.001 ~ 0.01太高忘掉旧知识太低恢复慢0.002量化校准集图片数500 ~ 2000太少激活值动态范围统计不准1000sparsity 默认 0.005是我从 0.001 逐步加观察 gamma 分布后定下来的。稀疏训练时每一轮输出的 loss 里会多一项正则损失你可以打印出来看量级。如果正则项比主 loss 还大说明系数太高模型已经被压成一团浆糊。反过来训练结束后 gamma 的分布还是一条平坦带子完全没有贴近 0 的峰值说明稀疏训练没生效常见原因是正则被加错了层加到了普通卷积而不是 BN 的 gamma 上。4.2 稀疏度与剪枝率怎么配合先贝塔分布再定阈值常见误区是直接把剪枝率拉高结果模型直接废掉。做剪枝决策之前应该先看稀疏训练后的 gamma 分布。可以写一个小脚本把各层 gamma 的绝对值和分位数打印出来import torch sd torch.load(runs/train/sparsity/weights/best.pt, map_locationcpu)[model].state_dict() bn_gamma [] for k, v in sd.items(): if bn.weight in k and v.numel() 1: bn_gamma.append(v.flatten()) gamma_all torch.cat(bn_gamma) print(gamma abs 均值:, gamma_all.abs().mean().item()) print(gamma 分位数:, [torch.quantile(gamma_all.abs(), q).item() for q in [0.5, 0.7, 0.8, 0.9]])这段代码把剪枝前所有 BN 通道的 gamma 绝对值分布打出来。如果 0.8 分位数值还在 0.05 以上说明大部分通道的 gamma 都缩得不够狠这时候你定 35% 的剪枝率剪掉的只是排序靠后的那一批但阈值会切到 gamma 分布相对平滑的区域误伤概率很大。理想状态是 gamma 排序后出现一个明显的“悬崖”位置前面还是正常量级后面急剧掉到接近 0这个悬崖位置才是合理剪枝率参考值。模型规模也要考虑。YOLOv5s 有 7.2M 上下参数剪 0.4 后微调周期会长很多YOLOv5n 本身参数就少冗余空间不大我一般把剪枝率压到 0.2 以内微调 epochs 拉长到 150。小模型强行剪多的后果是关键特征通道被一并删除精度怎么微调都回不来这一点在 Nano 尺寸模型上尤其明显。4.3 量化阶段的校准集与回退策略PTQ 不够再上 QAT量化模型翻车最多的地方一只在“校准集不典型”另一只在“动态范围被异常值拉爆”。校准集要覆盖真实推理时可能遇到的光照、尺度、目标密度。如果你的应用场景是夜间摄像头校准图就别全用白天拍的素材如果检测的是小目标校准集里就要保证有一定比例的小目标样本否则量化后小目标直接消失。校准集数量下限我测下来是 500 张。500 张以下激活值统计方差很大同一个模型连续做两次量化mAP 都可能差出好几个点。2000 张以上边际收益就很小了所以默认 1000 张是个性价比很高的选择。先跑一轮 PTQ看 mAP 掉点情况。掉点在 2 个点以内直接交付掉点超过 5 个先别急着堆数据检查 per_channel 是否打开per_channelTrue 通常能救回近一半的精度损失。如果 PTQ 无论如何救不回来就得上 QAT。QAT 本质是在训练时插入伪量化节点让网络在梯度回传过程中见到量化误差。剪枝之后模型已经变薄表达能力下降对量化误差的抵抗力会比原模型更差。常见做法是在微调后半段开启量化感知训练先让模型在 FP32 下恢复能力再逐步加入伪量化节点让网络对量化噪声脱敏。这个过程多花几十个 epoch但能直接解决部署后精度崩掉再返工的麻烦。我的原则是能用 QAT 解决的不靠玄学参数去赌。5. 避坑指南剪枝后掉点、量化后 NaN、一键运行中断的排障实例这一章按“现象、原因、解决”三段式记录几个反复踩过的坑。每一条都不是孤例搜索引擎里能搜到大量同款求助帖但很少有人把原因和最小验证步骤说全。5.1 精度类问题剪枝后大规模掉点和量化后 NaN 的两次复盘问题一剪枝后 mAP 大幅下降微调 100 轮仍然恢复不了。现象剪枝完成后用同一验证集评估mAP0.5:0.95 从 0.72 掉到 0.58后面跑了 100 个 epoch 只回到 0.66怎么调学习率都上不去。原因剪枝率设定没有参考稀疏训练后的 gamma 分布。gamma 的“悬崖”根本没形成换句话说稀疏训练没做够就把 35% 通道硬剪掉了这部分通道里仍有不少携带有效语义信息。另一个隐藏原因是微调时沿用了原训练的学习率调度剪枝后的模型参数分布和初始状态完全不同过大的学习率会把刚收敛的权重又搅乱。解决先把微调初始学习率降到 0.002配合 cosine 退火让模型平滑恢复。再把剪枝率降到 0.25 重新跑。如果还掉点回到稀疏训练环节对比稀疏训练前后 gamma 分位数确认训练真的把一部分 gamma 压到了接近 0而不是日志里简单看一个正则项数值。问题二INT8 量化后推理输出出现 NaN目标框完全乱套。现象量化后的模型在 ONNX Runtime 上推理输出张量里出现 NaN或者目标框全聚集在一个点精度归零。原因最常见是校准集和真实推理数据分布差异太大激活值在量化时被截断导致动态范围统计失真。另一种情况是导出的 ONNX 里 BatchNorm 层没有完全融合进卷积量化工具处理 BN 参数出错。YOLOv5 导出 onnx 时一般已经融合 BN但剪枝后微调过的模型 BN 统计量可能变化导出时如果没有冻结 BN就会留下隐患。解决分两步排查。先用 FP32 ONNX 跑一遍校验集FP32 正常而 INT8 出 NaN问题就在量化环节。量化时把激活值裁剪方式从 min-max 换成 percentile例如用 0.999 分位数替代最大值不给个别离群点拉爆整个范围的机会。如果 FP32 也乱那问题在剪枝后的微调没收敛回到模型权重层面检查。5.2 结构类问题shortcut 通道对不上和模型体积没变小问题三剪枝后加载模型前向推理刚进 C3 模块就报 shape mismatch。现象报错形如The size of tensor a (64) must match the size of tensor b (128)指向的正好是 C3 模块的加法操作。原因shortcut 残差边两侧的剪枝 mask 没有对齐。通常剪枝脚本会按 BN gamma 独立决定每个卷积层的通道去留但 C3 的 add 运算要求两侧特征图结构一致。很多一键脚本把网络重建封装得很深反而让使用者没法定位到具体是哪个层出了问题。解决在剪枝脚本的 mask 生成完毕之后插入一道对齐检查。遍历所有 shortcut 分支把两侧的 mask 做逻辑或合并保证通道增减一致。代码思路大致如下# 对齐 shortcut 两侧的 mask避免 add 运算 shape mismatch def align_shortcut_masks(model, masks): for name, module in model.named_modules(): if hasattr(module, shortcut) and module.shortcut: left masks.get(name .cv1, None) right masks.get(name .cv2, None) if left is not None and right is not None: union torch.logical_or(left, right) masks[name .cv1] union masks[name .cv2] union return masks这里的 key 命名在不同实现里不一样思路是一致的shortcut 涉及的层必须整组对齐不能按全局阈值各自为政。如果剪枝脚本不支持这个操作就手动给 shortcut 相关的 C3 模块设置不剪枝开关优先保证结构完整。问题四剪枝后 .pt 模型文件体积没有变小怀疑剪枝无效。现象流程跑完Params明显下降但保存的 pruned_model.pt 和原始 best.pt 一样大甚至更大。原因PyTorch 保存的 .pt 文件包含优化器状态、anchors、训练超参数等大量附加信息这部分存储经常超过模型权重本身。模型真实体积要看导出的 ONNX 在 FP32 下的文件大小再到 INT8 量化后的降幅。解决用 export.py 导出 ONNX 再对比。一个 YOLOv5s 通常在 14 MB 左右0.35 剪枝后能降到 8~9 MB再经 INT8 量化到 3~4 MB每一步都有可量化指标。如果 ONNX 大小也没变化就要回头检查剪枝 mask 是否真的作用在模型结构上多打印几层权重形状对比。5.3 运行类问题一键脚本中途卡死和校准进程不终止现象跑 train_sparsity.py 或 quantize_onnx.py 时终端停在某个位置十几分钟没有日志CPU 占用率倒是很高怀疑是死循环。原因多半是 DataLoader 的 num_workers 设置不当。num_workers 为 0 时数据加载都在主进程里执行体感上很像卡死。量化脚本卡住则常发生在校准集读取器逻辑上比如 get_next 返回数据条件写错导致读取器无限循环取同一张图。解决先不带多进程做小样本校验batch_size 设为 1、num_workers 设为 0跑 2~3 个迭代确认逻辑没问题。量化校准器在 get_next 里加计数兜底读到预设数量就返回 None。训练脚本的卡死直接 CtrlC 看 traceback 停在哪个线程大概率在数据加载的并行队列阻塞处。6. 压缩模型的验证和部署mAP、体积、推理耗时一起看6.1 剪枝量化完成后的验证清单模型压缩是否成功不能只看 mAP 掉点。我建议用一张三列验证清单把“精度、体积、速度”三个维度的数据同时记录下来分别对应原始模型、剪枝后、量化后三个版本。精度看 mAP0.5:0.95 的绝对值和相对掉点体积看 ONNX 文件的 FP32 与 INT8 大小速度看同硬件、同输入尺寸下的单张推理耗时。我做过的一个项目输入 640x640要求 30 FPS。原始 YOLOv5s ONNX 单帧推理 28 ms剪枝 0.35 后降到 19 msINT8 量化后 11 ms。整体看FP32 到 INT8 的提速幅度在 1.5 倍到 2 倍之间具体取决于算子是否落到了硬件支持的 INT8 加速路径上。如果量化模型和原模型耗时几乎一样先确认推理时真正加载的是 INT8 版本很多人在这栽过导出文件名字后缀是 int8实际加载路径却指向了 FP32 老文件。6.2 ONNX Runtime 与 RKNN 的部署差异一套剪枝权重适配多个平台部署平台不同量化策略也要跟着调整。ONNX Runtime 的 INT8 加速在 x86 的 CPU 和 NVIDIA GPU 上比较成熟如果要跑到嵌入式设备YOLOv5 最常见的部署路径是 RKNN。rknn 量化有自己的校准流程通常要把 ONNX 模型交给 rknn-toolkit用它的内置量化工具重新校准而不是直接用 ONNX Runtime 的 quantize_static。两者不是互斥关系可以在同一套剪枝权重上分别做一次量化目标平台选择对应工具。另一个部署注意点剪枝后的模型结构已经偏离官方 YOLOv5后处理代码如果写死了锚点参数和输出通道数需要同步核验。YOLOv5 有三个检测尺度如果剪枝动了 Head 部分的卷积输出张量通道数会变化后处理解析必须跟着改。这也是为什么很多团队宁愿只剪 Backbone 不剪 HeadHead 一变动牵一发动全身。这几年做模型压缩我最大的习惯是保留三份文件剪枝前原始权重、剪枝后微调权重、量化后部署模型同时把每一步的命令行参数和验证指标写进同一个记录文件。剪枝和量化本身都是可重跑的流程模型重新训练能再生成但当时用的是哪条路径、哪组尝试性调参如果不记下来后面就只能靠猜。希望这套从原理到避坑再到部署验证的流程帮你顺利跑通一轮不大翻车的 yolov5 剪枝和量化。本文还有配套的精品资源点击获取
返回列表