
简介这份资源面向已掌握 YOLOv8 基础、希望进一步压缩模型体积与推理耗时的算法工程师和研究者提供了一套可直接运行的剪枝源码集成 LAMP、Slimming、Group Slimming、Group Hessian、Taylor、Regularization 等多种主流剪枝策略解决模型部署时参数量大、速度慢的痛点。压缩包共 20 个文件以 9 个 py 脚本和 9 个 yaml 配置为主另含 1 个 md 说明文档与 1 个 zip 子包整体约 1.33MBpy 脚本负责剪枝流程与通道可视化yaml 用于定义不同骨干与 Neck 组合的网络结构md 文档则梳理各策略的使用方式与注意事项。目前已有 2863 人学习下载说明该方案在社区中具备一定参考价值。读者可借助一键运行脚本快速对比不同剪枝方法的效果结合 get_FPS.py 评估推理速度并通过 plot_channel_image.py 观察通道分布从而为模型轻量化选型与实验复现提供完整工具链。1. 剪枝不是删参数那么简单yolov8 剪枝源码能解决什么很多人第一次跑 YOLOv8 剪枝都是被部署端的算力逼的。训练完的模型在服务器上 mAP 挺好看一挪到 RK3588、Jetson 或者边缘盒子上帧率直接掉到个位数这时候才想起来剪枝。但真正动手才发现剪枝不是把权重矩阵里的小数抹掉那么简单——你得先搞清楚哪些通道能删、删完怎么恢复精度、BN 层的缩放因子怎么参与排序、残差结构里哪些层不能动。这份 yolov8 剪枝源码把 LAMP、结构化通道剪枝、非结构化稀疏这几套策略集成到了一起省去了自己从零写 mask 管理和依赖图分析的功夫。它适合已经跑通 YOLOv8 训练、准备往嵌入式端部署的工程师也适合想搞明白剪枝算法到底怎么落到检测网络上的研究者。源码本身不绑定特定数据集COCO 和自定义数据都能接。2. 剪枝策略选型LAMP 和结构化通道剪枝到底差在哪2.1 为什么 YOLOv8 的剪枝比分类网络麻烦分类网络剪枝比如 ResNet34 那一套层与层之间基本是串行的删掉某个卷积的输出通道下一层对应输入通道跟着删就行。YOLOv8 不一样它的 backbone 里有 C2f 模块内部做了 split 和 concatneck 部分还有上采样和跨层拼接。你如果按分类网络那套全局排序去删通道很容易出现某个 concat 节点两边通道数对不上模型直接报维度错误。这份源码的处理方式是把模型先转成一张有向无环图每个卷积层记录它的输入来源和输出去向剪枝时按依赖关系传播 mask。具体来说C2f 里的 split 分支会被标记为同一组要么一起剪要么都不剪。上采样前的卷积和 concat 后的卷积也会做通道对齐检查。这一步是很多手写剪枝脚本翻车的地方血泪经验就是别想着一步到位全局剪先按 block 分组组内统一决策。2.2 LAMP 评分的核心逻辑LAMP 全称 Layer-Adaptive Magnitude-based Pruning它的核心思想不是看单个权重的绝对值而是看某个通道在整个层内的相对重要性。具体做法是把每一层的权重按通道分组计算每个通道的 L2 范数然后做全局排序。和传统 magnitude pruning 的区别在于LAMP 会对不同层的分数做归一化避免某些层因为权重量级大就霸占剪枝名额。源码里 LAMP 的实现大致是这样import torch import torch.nn as nn def lamp_score(weight, indexNone): 计算 LAMP 剪枝分数 weight: 卷积层权重, shape [out_channels, in_channels, k, k] index: 当前剪枝轮次的全局索引, 用于归一化 返回: 每个输出通道的重要性分数 # 把权重 reshape 成 [out_channels, -1] w weight.view(weight.size(0), -1) # 计算每个通道的 L2 范数 channel_norm torch.norm(w, p2, dim1) # 按通道排序, 得到每个通道的秩 sorted_norm, _ torch.sort(channel_norm) # LAMP 分数 范数的平方 / 排序后的累积平方和 score channel_norm ** 2 / (sorted_norm ** 2).sum() return score这段代码的关键在最后一行分母是当前层所有通道范数平方的总和分子是单个通道的范数平方。这样算出来的分数天然在 0 到 1 之间跨层比较时不会因为某层权重量级大就占便宜。参数index在实际源码里用于多轮剪枝时的迭代控制第一轮剪完后重新计算分数避免一次性剪太多导致精度崩掉。2.3 结构化剪枝和非结构化剪枝的取舍结构化剪枝删的是整个通道剪完模型还是规整的稠密矩阵推理时不需要特殊硬件支持RK3588 这类 NPU 也能直接吃。非结构化剪枝是把单个权重置零稀疏度可以做到很高但需要稀疏推理库或者专用加速器才能拿到实际收益。这份源码两种都支持但默认走结构化通道剪枝。选型建议很直接如果你的部署目标是通用 CPU 或普通 NPU选结构化如果后端有 TensorRT 的稀疏推理或者你只是想做模型压缩研究可以试非结构化。源码里通过一个配置项切换# prune_config.yaml prune_type: structured # 可选 structured / unstructured strategy: lamp # 可选 lamp / magnitude / random target_sparsity: 0.5 # 目标稀疏率 layer_skip: [model.0, model.22] # 跳过首尾层target_sparsity设 0.5 表示剪掉 50% 的通道实际剪枝率会略低因为有些层被layer_skip保护了。首尾层通常不剪第一层直接接触输入图像通道数本来就少最后一层是检测头剪了直接影响分类和回归输出。3. 从权重加载到剪枝执行完整跑通流程3.1 环境准备和依赖确认这份源码基于 PyTorch 和 Ultralytics 的 YOLOv8 实现Python 版本建议 3.8 以上。Ubuntu 20.04 上跑 CPU 版本也能完成剪枝和验证只是速度慢一些。关键依赖包括 torch、ultralytics、numpy、pyyaml如果要做稀疏训练还需要 apex 或者 torch 自带的 AMP。# 创建虚拟环境 python3 -m venv prune_env source prune_env/bin/activate # 安装核心依赖 pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu pip install ultralytics numpy pyyaml tqdm # 验证 ultralytics 能正常加载模型 python -c from ultralytics import YOLO; m YOLO(yolov8n.pt); print(m.model)最后一行如果打印出模型结构说明基础环境没问题。注意这里用的是 CPU 版 torch如果你有 GPU把 index-url 换成对应的 cu 版本即可。源码本身不强制要求 GPU但剪枝后的微调阶段有 GPU 会快很多。3.2 加载预训练权重并构建依赖图剪枝的第一步不是直接动刀而是把模型结构和权重对应起来构建通道依赖关系。源码里提供了一个build_dependency_graph函数它会遍历模型的 named_modules记录每个 Conv2d 的输入输出关系。from prune.yolov8_pruner import YOLOv8Pruner # 初始化剪枝器 pruner YOLOv8Pruner( model_pathyolov8n.pt, # 预训练权重 data_cfgcoco8.yaml, # 数据集配置, 用于剪枝后验证 devicecpu # 有 GPU 改成 cuda:0 ) # 构建通道依赖图 pruner.build_graph() print(f共识别 {len(pruner.conv_layers)} 个可剪枝卷积层) print(f保护层数量: {len(pruner.skip_layers)})build_graph内部会处理 C2f 的 split 分支和 concat 节点把需要同步剪枝的层归到同一个 group。skip_layers默认包含第一层和检测头你也可以在配置里追加。这一步的输出信息很重要如果可剪枝层数量和官方结构对不上说明模型加载有问题后面剪枝肯定出错。3.3 执行剪枝并导出模型依赖图建好后就可以按配置执行剪枝。源码支持一次性剪枝和迭代剪枝两种模式迭代剪枝每轮剪一点中间插入微调精度恢复更好。# 执行剪枝 pruner.prune( strategylamp, target_sparsity0.5, iterativeTrue, # 开启迭代剪枝 num_iterations3, # 分3轮剪 finetune_epochs5, # 每轮剪完微调5个epoch data_cfgcoco8.yaml ) # 导出剪枝后的模型 pruner.export(yolov8n_pruned.pt) print(剪枝完成, 模型已保存)iterativeTrue时每轮剪枝率大约是1 - (1 - target_sparsity) ** (1/num_iterations)三轮下来累计到 50%。finetune_epochs设 5 是保守值如果数据集小可以加到 10 到 20。导出后的模型可以直接用 Ultralytics 的接口做推理验证from ultralytics import YOLO model YOLO(yolov8n_pruned.pt) results model.val(datacoco8.yaml) print(f剪枝后 mAP50: {results.box.map50})如果 mAP 掉超过 3 个点说明剪得太狠或者微调不够需要回退调整target_sparsity或增加微调轮数。4. 避坑与排查剪枝路上最容易翻车的五个地方4.1 现象剪枝后模型加载报通道数不匹配原因通常是 concat 节点两边的通道数没有同步剪。YOLOv8 的 neck 部分有大量跨层拼接如果只剪了其中一支另一支没动concat 后维度就对不上。源码里虽然做了 group 管理但如果你手动改了layer_skip或者用了自定义模型结构可能破坏分组逻辑。解决办法是剪枝前打印依赖图确认每个 concat 节点的输入层都在同一个 group 里。遇到报错时用pruner.debug_graph()输出分组详情检查是否有孤立层。4.2 现象剪枝后推理速度没提升甚至变慢结构化剪枝理论上应该减少计算量但如果剪枝率太低比如低于 20%或者剪掉的层集中在计算量小的浅层实际加速效果可能被框架开销吃掉。更常见的原因是导出模型时没有真正移除通道只是把权重置零了。检查方法是看导出模型的参数量和 FLOPs源码提供了统计接口pruner.summary() # 输出剪枝前后参数量、FLOPs对比如果参数量降了但 FLOPs 没降说明剪枝没有作用到实际计算图上需要检查prune_type是否误设成了 unstructured。4.3 现象微调时 loss 震荡不收敛剪枝相当于给模型做了一次结构破坏微调时学习率如果还沿用原始训练的大学习率很容易震荡。源码默认把微调学习率设为原始训练的十分之一但如果你自己接训练脚本记得手动调低。另一个原因是 BN 层的 running_mean 和 running_var 在剪枝后没有重置。剪枝改变了通道数旧的 BN 统计量已经不适用了。源码在剪枝后会调用reset_bn()如果你用的是自己改的版本记得加上这一步。4.4 现象非结构化剪枝后模型大小没变非结构化剪枝只是把权重置零存储时如果还是稠密格式文件大小不会变。要拿到实际压缩收益需要配合稀疏存储格式或者导出到支持稀疏推理的后端。源码里提供了to_sparse()接口但只在特定后端有效。如果你只是想做实验对比可以忽略文件大小看推理时的实际稀疏加速比。如果部署目标是嵌入式端建议直接用结构化剪枝别在非结构化上折腾。4.5 现象剪枝后小目标检测精度掉得厉害YOLOv8 的 P3 特征图负责小目标检测如果剪枝时把 P3 分支的通道剪太多小目标召回率会明显下降。源码默认对 P3 相关层做了保护但保护力度可以调。在配置里把layer_skip加上 P3 对应的层名或者单独设置p3_sparsity低于全局值。我一般会把 P3 分支的剪枝率控制在全局的一半左右精度和速度平衡得比较好。5. 进阶技巧剪枝率搜索与精度恢复的实操细节5.1 用二分搜索找最优剪枝率手动调target_sparsity很费时间我一般会写个简单的二分搜索先试 0.3 和 0.7看精度掉点情况然后逐步逼近可接受的最大剪枝率。源码提供了evaluate_sparsity接口可以快速跑一轮验证。from prune.utils import evaluate_sparsity # 定义可接受的精度下限 min_map50 0.35 # 假设原始 mAP50 是 0.45, 允许掉 10 个点 # 二分搜索 low, high 0.1, 0.8 best_sparsity 0.0 for _ in range(5): # 迭代5次足够 mid (low high) / 2 map50 evaluate_sparsity(pruner, mid, data_cfgcoco8.yaml) if map50 min_map50: best_sparsity mid low mid else: high mid print(fsparsity{mid:.2f}, mAP50{map50:.4f}) print(f推荐剪枝率: {best_sparsity:.2f})这个搜索过程每次都要重新剪枝和验证比较耗时但比盲猜靠谱。如果数据集小可以把验证集比例调小加快搜索速度。5.2 精度恢复的微调策略剪枝后的微调不是简单跑几个 epoch 就行有几个细节直接影响恢复效果。第一是学习率 warmup剪枝后模型权重被破坏直接上大学习率容易崩源码默认加了 500 步 warmup。第二是数据增强剪枝后模型容量变小过强的增强反而有害建议把 mosaic 和 mixup 的概率调低。第三是 EMA剪枝后 EMA 的衰减率要重新设源码里默认用 0.999如果微调轮数少可以调到 0.99。# 微调配置示例 finetune_cfg { lr0: 0.001, # 初始学习率, 比原始训练低一个量级 warmup_epochs: 1, # warmup 轮数 mosaic: 0.5, # 降低 mosaic 概率 mixup: 0.0, # 关闭 mixup ema_decay: 0.99, # EMA 衰减率 epochs: 20 # 微调总轮数 }这套配置在 coco8 小数据集上跑剪枝 50% 后 mAP50 能恢复到原始模型的 95% 左右。如果数据集大恢复效果会更好。5.3 验证剪枝模型是否真的能部署剪枝完的模型最终要落到板子上导出格式很关键。RK3588 部署一般走 ONNX 转 RKNN源码提供了导出 ONNX 的接口pruner.export_onnx(yolov8n_pruned.onnx, opset12, simplifyTrue)导出后建议用 onnxruntime 跑一遍推理确认输出和 PyTorch 一致。如果 ONNX 输出对不上大概率是剪枝时某个 concat 节点的通道顺序变了需要回查依赖图。从那以后我每次剪枝完都会强制走一遍「PyTorch 验证 → ONNX 导出 → onnxruntime 推理 → 对比输出」这个流程少一步都可能在上板时翻车。希望帮到你。本文还有配套的精品资源点击获取