YOLOv5剪枝与量化实战:压缩至1/3,精度不掉,边缘部署指南

📅 发布时间:2026/10/9 9:26:50
YOLOv5剪枝与量化实战:压缩至1/3,精度不掉,边缘部署指南
简介这份资源提供 YOLOv5 剪枝与量化的一键运行方案面向需要将模型部署到移动端、嵌入式设备或 NVIDIA GPU 平台的算法工程师与开发者解决模型体积大、推理速度不足的痛点。压缩包共 208 个文件约 24.2MB涵盖 Python 脚本、YAML 参数配置、PyTorch 权重与 ONNX 模型并附带 Dockerfile、C 推理示例及 TensorRT 相关文件可支撑从模型优化到实际部署的完整链路。已有 1884 人学习下载适合希望快速上手模型压缩技术的实践者。通过配套脚本使用者无需深入底层剪枝与量化细节即可完成操作支持结构剪枝与权重剪枝将模型体积缩减 70% 以上量化感知训练可降低精度损失最终转换为 TensorRT 引擎在 GPU 上获得更高推理吞吐。压缩包内目录结构清晰便于按模块检索适合作为入门或生产部署的参考工程。1. yolov5 剪枝和量化把模型压到三分之一还能保住精度的落地路径把训练好的 yolov5s 压到原体积的三分之一推理速度在边缘设备上再提一截mAP 只掉两三个点这是“剪枝 量化”组合最典型的结果。剪枝负责把网络里冗余的通道删掉让模型变窄量化把 FP32 权重和激活压到 INT8让运算变轻。两者不是二选一而是要按顺序串成一条流水线稀疏训练、通道剪枝、微调恢复再做训练后量化PTQ或量化感知训练QAT。这篇笔记直接服务正在把 yolov5 往树莓派、RK3568 这类边缘设备上搬的工程师我会把每个阶段的参数、脚本和翻车点讲透最后给出一套可以直接照搬组织的一键运行流程。2. 剪枝和量化的原理把省下来的开销算清楚再动手2.1 结构化剪枝还是非结构化剪枝YOLOv5 只适合前者剪枝算法分两派非结构化剪枝把权重矩阵里绝对值小于阈值的元素直接置零模型文件变小但推理时矩阵乘法依然是稠密运算PyTorch 原生跑起来不会变快。除非你后续接 GPU 稀疏核或专门推理库否则指标好看、落地难受。结构化剪枝是另一条路它按通道删BN 层每个通道有一个可学习的缩放系数 gammagamma 小的通道对整个特征图的贡献低可以连通道带卷积核一起删掉。删除后模型真正变窄FLOPs 下降内存带宽占用下降推理框架直接受益。YOLOv5 走的是后者。它的网络结构图里布满 C3 模块、Concat 层和 shortcut 连接不是一根直筒 CNN。剪掉某个中间层的一部分通道后Concat 分支的通道数必须同步变FPN 里跨层相加的 shortcut 两侧也要一致否则模型加载时就报 shape mismatch。另外三个 Detect 检测头的输出通道和类别数绑定COCO 80 类就是 255 通道这部分一般不能剪硬剪会让后处理阶段直接失去类别映射。所以剪枝脚本必须在模型拓扑层面做通道重映射而不是简单地对每个 BN 层做 mask。实操里我一般先把模型里所有 BN 层的 gamma 拉出来画直方图。如果分布集中在 1 附近说明各通道重要性没拉开这时候剪掉任何通道都伤筋动骨正确的做法是先跑一段稀疏训练用 L1 正则把 gamma 往 0 推让分布逐渐变成双峰低峰部分的通道就是候选剪除对象。2.2 PTQ、QAT 与 INT8 部署边界量化不是免费的加速量化把 FP32 的权重和激活映射到 INT8 整数域用 scale 和 zero_point 两个参数做线性变换。按训练介入程度分两派PTQ 训练后量化拿一小部分和业务同分布的图片做标定统计每层激活的数值范围算出量化参数整个流程不需要反向传播成本最低。YOLOv5 这一类目标检测模型PTQ 通常能把 mAP 损失控制在 1 到 2 个点内前提是标定集选得准。QAT 则是在训练阶段插入伪量化节点前向时把权重和激活量化再反量化梯度在反向传播时能感知量化误差训练结束后去掉伪量化节点精度更稳代价是多一轮完整训练。对比项PTQQAT需要训练不需要需要微调标定数据500-2000 张训练集精度损失一般 1-3 个点可控制在 1 个点内耗时分钟级小时级适合场景快速上线、迭代验证精度敏感、小目标多INT8 部署还有一层硬边界CPU 没有对应的快速指令集时量化后的模型可能比 FP32 还慢。x86 平台要看是否支持 VNNI 或 AVX512ARM 平台要看是否支持 dotprod 扩展。拿树莓派 4B 来说它的 ARMv8 架构没有 INT8 dotprod 指令PyTorch 的 INT8 kernel 会走通用实现速度不升反降。反过来RK3568、RK3588 这类带 NPU 的芯片官方工具链如 RKNN-Toolkit会优先接受 INT8 模型量化反而是必选项。所以在动手之前先回答目标设备是谁它凭什么加速 INT8。2.3 先剪枝再量化还是反过来顺序决定误差叠加方式我的固定顺序是先剪枝后量化中间插入微调恢复。原因有两条剪枝改变的是网络结构通道少了特征表达变弱精度先掉一段必须通过微调把信息重新塞回剩余通道如果这时候再叠加量化误差两个误差源互相纠缠翻车后根本定位不了是哪一步出的问题。反过来如果先量化再剪枝量化噪声会污染每层输出的统计直接影响你对 BN 层 gamma 的评估剪枝阈值会被带偏。也有一种一步到位的做法在稀疏训练阶段同时开启 QAT让损失函数同时包含分类损失、BN 的 L1 惩罚和量化误差。理论上能省一轮训练但我不建议第一次做剪枝量化的团队这么干。分开跑每一步的产物都能单独验证哪怕精度出问题也知道回到哪一步调参数。先剪枝微调再做 PTQ是性价比最高的组合我手上大多数 yolov5 项目都是这么落地的。3. 一键运行背后的四个脚本稀疏训练、剪枝、微调、量化3.1 环境准备与最小可运行集合先把 yolov5 官方仓库跑起来。如果你已经有训练好的模型跳过训练环节直接进入稀疏化。git clone https://github.com/ultralytics/yolov5 cd yolov5 python -m venv .venv source .venv/bin/activate pip install -r requirements.txt # 验证环境 python detect.py --weights yolov5s.pt --source data/images/bus.jpg这里的关键点是 Python 版本和 PyTorch 版本要配对。yolov5 官方仓库更新节奏快建议拿到一个稳定 commit 后固定下来不要频繁拉新版本否则后续剪枝脚本里对模型结构解析的代码很容易因为上游改动而失效。环境验证通过后把 detect.py 的输出截图留底后面所有对比都以这个结果为基准。3.2 稀疏训练脚本给 BN 层 gamma 加 L1 正则稀疏训练的目标不是提升精度而是把 BN 层的 gamma 系数尽量压低让通道重要性分化。做法是在原始 loss 上追加一个 L1 惩罚项只作用于 BN 层的 gamma。# train_sparse.py 核心片段 import torch import torch.nn as nn def bn_l1_loss(model, sparse_lambda5e-4): l1 0.0 for m in model.modules(): if isinstance(m, nn.BatchNorm2d): l1 torch.abs(m.weight).sum() return sparse_lambda * l1 # 在训练循环里把原始 loss 拿到后 loss loss bn_l1_loss(model, sparse_lambda0.0005) loss.backward()这段代码要插在 yolov5 训练循环的 loss 计算之后、backward 之前。sparse_lambda 是控制稀疏强度的核心参数取 1e-4 到 1e-3 之间。太小gamma 收缩不动太大分类 loss 被压制模型直接不收敛。我一般从 5e-4 起步训练 50 个 epoch 后把 gamma 分布打印出来看是不是已经出现双峰形态没有就加大系数或延长稀疏训练轮数。需要强调的是稀疏化要在训练早期就加入不要在最后 10 个 epoch 才挂上去那点时间根本拉不开分布。3.3 通道剪枝脚本全局阈值与通道重映射稀疏训练结束后统计所有 BN 层 gamma按全局阈值切一刀。这一步不是简单的置零而是要生成通道掩码并重建模型结构。# prune.py 核心片段 import torch import torch.nn as nn def collect_gamma(model): gammas [] names [] for name, m in model.named_modules(): if isinstance(m, nn.BatchNorm2d): gammas.append(m.weight.detach()) names.append(name) return names, gammas def compute_threshold(gammas, prune_ratio0.5): all_gamma torch.cat([g.view(-1) for g in gammas]) # 取全局分位数剪掉最小的 prune_ratio 比例 return torch.quantile(all_gamma, prune_ratio) threshold compute_threshold(gammas, prune_ratio0.5) masks [g threshold for g in gammas]拿到 mask 后最耗时的部分才开始。YOLOv5 的模型结构搭在 model.yaml 上剪枝要按拓扑顺序重算每一层的输入输出通道Conv 层根据前一层输出的通道数调整卷积核尺寸C3 内部 Bottleneck 的通道数跟着变Concat 层要把所有输入分支的通道数相加shortcut 连接要求两侧通道相等。如果直接拿着 mask 去索引权重Concat 和 shortcut 那几层必炸。常见做法是解析 model.yaml推导出剪枝后的每层通道数重新生成一份 pruned.yaml再按新结构从旧权重里挑选保留的通道复制出来。Detect 层直接排除在剪枝列表外。3.4 量化脚本PTQ 标定与 QAT 微调剪枝微调完成后进入量化阶段。PTQ 脚本在 PyTorch 里用 torch.ao.quantization 实现先融合 ConvBN再准备标定数据集。# quantize_ptq.py 核心片段 import torch from torch.ao.quantization import get_default_qconfig, prepare, convert model.eval() # 关键先融合 ConvBN减少量化误差累积 model.fuse_model() model.qconfig get_default_qconfig(fbgemm) prepared prepare(model, inplaceFalse) # 喂标定数据统计激活值范围 with torch.no_grad(): for img_batch, _ in calib_dataloader: prepared(img_batch) quantized_model convert(prepared.cpu(), inplaceFalse)标定集要单独从训练集或验证集里划出来覆盖不同光照、不同物体尺度数量在 500 到 2000 张之间。注意模型必须处于 eval 模式否则 BN 统计会继续更新标定出的 scale 是错的。上面代码里 fbgemm 是 x86 CPU 的推理后端换到 ARM 平台可以用 qnnpack。如果 PTQ 的精度损失超过两个点就切到 QAT同样先 fuse_model再把 qconfig 换成 get_default_qat_qconfig用训练集微调几个 epoch最后 convert。3.5 一键运行的组织方式set -e 串行 Pipeline把前面四段串成一个入口脚本参数从命令行传入每一步失败立即退出不做后续计算。#!/bin/bash set -e WEIGHTSyolov5s.pt DATAcoco128.yaml SPARSE_LAMBDA0.0005 PRUNE_RATIO0.5 EPOCHS_SPARSE80 EPOCHS_FINETUNE80 # 1. 稀疏训练 python train.py --weights $WEIGHTS --data $DATA \ --epochs $EPOCHS_SPARSE --sparse-lambda $SPARSE_LAMBDA \ --name sparse # 2. 通道剪枝 python prune.py \ --weights runs/train/sparse/weights/best.pt \ --ratio $PRUNE_RATIO # 3. 微调恢复 python train.py --weights pruned.pt --data $DATA \ --epochs $EPOCHS_FINETUNE --name finetune # 4. PTQ 量化 python quantize.py \ --weights runs/train/finetune/weights/best.pt \ --calib data/images --output quantized.ptset -e 的作用是让脚本在第一个出错命令处停下避免你第二天早上发现微调根本没跑量化用的还是上一个坏权重。所有超参数集中在脚本头部改一个地方就能跑一组对比实验。第一次跑的时候建议把每个阶段单独执行、单独验证产物确认没问题后再合成一键脚本否则中间产物有缺陷时全流程跑完会浪费很多时间。4. 剪枝量化常见问题排查六次实测翻车记录4.1 剪枝后 mAP 崩掉gamma 分布没拉开现象设置 50% 剪枝率剪完微调完mAP 直接掉了 20 多个点比没微调都差。原因稀疏训练没跑够或者 sparse_lambda 太小gamma 全集中在 1 附近剪枝等于随机删通道。解决办法是训练结束后把 gamma 直方图拉出来看一眼——正确的状态是明显分成低峰和高峰两簇低峰占比和剪枝率差不多。如果只有一个峰把 lambda 往上调到 1e-3同时确认稀疏训练轮数不少于 50 个 epoch。这个环节我曾经跳过检查直接剪枝结果浪费了两天重新排查。4.2 量化后推理更慢INT8 指令集不匹配现象模型体积确实小了但 FPS 不升反降在树莓派 4B 上尤其明显。原因目标设备 CPU 不支持 INT8 快速指令PyTorch 只能用通用的慢速实现兜底。解决先确认芯片指令集x86 看是否支持 VNNI 或 AVX512ARM 看是否支持 dotprod。如果确认不支持就不要强行量化只做剪枝是更稳的方案。带 NPU 的 RK3568、RK3588 这类平台量化是必选项但要走厂商工具链转成自己的 INT8 格式而不是 PyTorch 的 PTQ 直接部署。4.3 剪枝后模型结构对不上Concat 与 shortcut 通道未同步现象加载剪枝后的权重报 shape mismatch或者前向推理中途报矩阵维度错误。原因剪枝时只处理了 Conv 层和 BN 层的对应关系忽略了 Concat 和 shortcut 的通道重新计算。这是 yolov5 剪枝里最核心的坑。解决剪枝脚本里必须按拓扑顺序重算每一层输出通道Concat 层输出等于所有输入分支之和shortcut 两侧必须严格相等。写脚本时建议先打印剪枝前后每一层的通道数人工核对一遍再继续训练。不要只用第一个 Conv 的 mask 去推后面的层。4.4 稀疏训练把精度打崩惩罚系数过大现象加了 BN L1 正则后训练 loss 一路上涨mAP 从一开始就不涨。原因sparse_lambda 太大L1 惩罚压过了分类损失。解决从 1e-4 开始观察 loss 曲线如果训到一半发现精度低于正常训练立即降系数。我一般会先在 10 个 epoch 的小规模实验上扫 1e-4、5e-4、1e-3 三档找到精度和稀疏度平衡点再跑完整训练。这个血泪经验帮我省下很多次整轮返工。4.5 PTQ 标定集与业务分布不一致现象量化前 mAP 只比原模型低 1 个点量化后夜间场景和小目标基本检测不到。原因标定用的图片全是白天大目标激活值范围估计偏差量化参数不适配真正的业务分布。解决标定集单独从真实业务数据里抽取至少覆盖晴天、夜间、遮挡、小目标四类场景数量 500 张起步。如果标定集已经铺开还是掉点明显改用 QAT量化感知训练对这种分布差异的容忍度远高于 PTQ。顺带一提yolov5 的 NMS 后处理对量化比较敏感输出节点能保留 FP32 就别硬压到 INT8。4.6 一键脚本在 Windows 上跑不通路径与多进程现象同样的命令在 Linux 上一路跑通Windows 上经常断在路径拼接或 DataLoader 卡死。原因反斜杠路径、Windows 下 multiprocessing 用 spawn 方式启动 worker。解决一键脚本不推荐用 bash 写Windows 直接写 python pipeline 脚本路径统一用 pathlib 的 Path 对象拼接DataLoader 的 num_workers 在 Windows 上先设 0跑通后逐步提高。这一步属于环境适配问题模型本身没毛病但会卡住新手半个下午。5. 验证与调优用同一把尺子量剪枝量化的收益5.1 三维评估mAP、FPS、模型体积剪枝和量化做完不要只看模型体积变小就收工。我习惯固定三把尺子同一份测试集上的 mAP、单张图片推理耗时、模型文件大小。推理耗时要测多次取平均前几次 warmup 不算数batch size 设为 1贴近真实部署场景。# evaluate.py 核心片段 import time import torch def evaluate_speed(model, device, warmup10, repeats50): dummy torch.randn(1, 3, 640, 640).to(device) model.to(device).eval() with torch.no_grad(): for _ in range(warmup): model(dummy) start time.time() for _ in range(repeats): model(dummy) avg_ms (time.time() - start) / repeats * 1000 return avg_ms把原模型、剪枝后、剪枝加量化后的三个速度值放到同一张表里能清楚看到哪一步收益最大。我遇到过一次剪枝后 FLOPs 降了 50%FPS 只涨了 10%原因是模型太小推理开销被后处理和内存拷贝主导这时候继续加剪枝率就没有意义了。5.2 参数扫描与配置化一键脚本里的超参数就是你的实验入口。把剪枝率按 0.3、0.5、0.65 扫一遍量化方式按 PTQ、QAT 各跑一轮生成一张参数表而不是靠感觉定值。我一般把每组实验的 mAP、FPS、模型体积、剪枝率、量化方式写进一个 CSV最终选择的标准不是单个指标最高而是综合体积和速度的最优解。顺着这个流程我曾经在一个检测项目上拿到过 mAP 仅掉 2.1 个点、FPS 提升 2.3 倍的效果比只做量化的方案稳定得多。最后说一个我的习惯任何剪枝量化结果都要在目标板子上实测才算数。板子在 CPU 还是 GPU、是否启用 INT8 指令、是否走厂商 NPU 工具链这些变量的影响远大于你在 PC 上看到的数字。先把流程跑通交付一版再回头逐步调参这是我压箱底的经验。希望帮到你。本文还有配套的精品资源点击获取