
简介YOLOv5模型压缩实战工具包面向目标检测开发者、算法工程师与模型部署人员覆盖剪枝、量化与TensorRT部署完整链路用于解决移动端与嵌入式设备上模型体积过大、推理过慢的痛点。剪枝方案可将模型体积压缩70%以上配合量化感知训练与TensorRT加速在不显著降低精度的前提下大幅降低计算量与内存占用。包内共208个文件以Python脚本、C源码、YAML配置、ONNX权重为主并包含Dockerfile、Shell部署脚本与说明文档整体约24.2MB目录结构清晰便于调用。压缩包提供一键运行工具链依赖安装、剪枝参数配置、量化转换与TensorRT导出均已脚本化无需深入底层即可完成模型优化。已有1884人学习下载适合需要快速落地模型压缩方案的中高级开发者。1. 剪枝量化的第一道坎yolov5 剪枝和量化代码一键运行到底一键了什么又一个模型在服务器上mAP 0.78、一到Jetson上只有4FPS的项目这种“训练完部署不动”的现场在边缘设备落地时几乎都会遇到。把yolov5做剪枝和量化市面上不少仓库都写着“代码一键运行”但实际跑下来你会发现这个“一键”有两层含义脚本能跑通和模型真的能上板。前者只要环境没坑就够后者需要你理解整条链路的节奏——稀疏化训练、通道剪枝、微调、INT8量化、部署验证每一步都有一批参数在起作用漏一步后面就找补不回来。本文围绕“yolov5剪枝和量化”这条完整路径按真实落地顺序拆解稀疏化训练的地基怎么打、通道剪枝的安全比例怎么定、量化的校准策略怎么选以及我实际跑下来的几个翻车现场和排查思路。适合正在做检测模型小型化、手上有数据集和部署板子、想少走弯路的工程师。2. 稀疏化训练剪枝的地基比剪枝本身更值得调参2.1 BN层的gamma就是剪枝的记分牌yolov5的通道剪枝思路在很多开源仓库里都是同一套把卷积层后面接的BatchNorm层的gamma值当作通道重要性的评分。BN层的计算里有一个缩放系数gamma训练收敛后如果某个通道的gamma趋近于0说明这一通道的输出基本是常数去掉它对后续特征图的影响很小。通道剪枝的本质就是在推理前把gamma接近0的通道裁掉顺便把上一层的卷积核也删掉。所以“剪枝”这个动作本身不复杂复杂的是怎么让gamma“学会”变零。常规训练收敛后gamma分布是散的直接按大小砍会误伤重要通道。要让gamma主动往0推需要在loss里加一项对gamma的L1惩罚这个过程就叫稀疏化训练。很多一键脚本里只写了一个--sparse开关但它背后改的是loss把gamma绝对值之和乘一个系数加到总loss里梯度下降时gamma就会持续被推向0方向。# 稀疏化训练的核心在原始loss上叠加BN层gamma的L1正则 def compute_loss_sparse(pred, batch, sparse_lambda0.0005, modelNone): loss compute_loss_original(pred, batch) # 原yolov5的分类回归loss if model is not None: sparse_reg 0.0 for m in model.modules(): if isinstance(m, nn.BatchNorm2d): sparse_reg m.weight.abs().sum() # 对gamma取L1范数 loss sparse_lambda * sparse_reg return loss这段代码要做两件事先算原来的分类损失和框回归损失再遍历模型里所有BN层把gamma的绝对值求和乘上一个稀疏系数sparse_lambda后叠加到总loss。注意这里的sparse_lambda和BN层里的gamma同名不同物前者是惩罚力度后者是通道权重别搞混。稀疏系数的选择常见区间是0.0001到0.001。太小gamma推不到零剪枝时找不到足够多的低分通道太大会把模型精度直接打崩后期微调也很难救回来。我常用的试法是先跑0.0005训练结束后画BN gamma的分布直方图如果大部分gamma还集中在0.1以上下一轮把系数加到0.001如果精度没掉但gamma也没有明显接近0的就先维持剪枝时用比例去适配不要盲目加系数。2.2 稀疏化训练的三个关键参数怎么设跑稀疏化训练之前yolov5本身有一些全局超参数需要先固定住。很多人直接在官方代码上开了稀疏开关就跑到后面剪枝完精度回不去才发现是学习率、batch size和epoch配合得不对。第一个是学习率。稀疏化训练的本质是在loss面里加了一个惩罚项如果学习率太大惩罚项会引起震荡gamma忽大忽小剪枝时会看到同一层里gamma分布很散、没有明显截断。我一般把初始学习率从默认的0.01降到0.005配合余弦退火调度训练末尾gamma会被推得更稳更干净。第二个是batch size。这个受显存约束但尽量别低于16——BN的统计量在小batch下波动大gamma的梯度噪声也更大。如果显存只够跑batch 8建议同步把稀疏系数减半否则惩罚项的梯度相对噪声来说太强精度掉得快。第三个是稀疏化训练的epoch数。常见做法是在原训练epoch基础上增加30%左右。比如数据集常规训练量是100轮稀疏化就跑到130轮。太短gamma推不到位太长模型开始过拟合剪枝后微调的收益变小。# 一键脚本里常见的稀疏化训练启动命令承接yolov5官方train.py python train.py --data your_dataset.yaml --weights yolov5s.pt \ --epochs 130 --batch-size 32 --img 640 \ --sparse 0.0005 --lr 0.005 --cos命令里--sparse 0.0005开启稀疏化并把L1惩罚系数设为0.0005--cos表示使用余弦学习率调度。代码内部稀疏化开关通常会接管BN层的梯度处理将gamma的梯度叠加一个符号函数值这等价于在梯度上施加L1方向的推力让gamma以恒定速度逼近0。跑完看一下runs/train/exp/weights/last.pt这就是后续剪枝的直接输入。提示稀疏化训练结束后的“稀疏率”不等于可剪枝比例。gamma接近0的通道比例通常在30%到70%之间剩下的需要靠剪枝时的全局阈值来定不要凭稀疏率直接决定剪多少。3. 通道剪枝一剪子下去怎么保证还能出框3.1 剪掉哪些通道安全比例和层间差异剪枝的输入是稀疏化后的模型真正剪的时候要考虑两个维度每个BN层剪掉多少通道以及哪些层可以剪。有些层一剪就崩比如靠近检测头的层通道信息高度压缩硬剪会把小目标的语义信息剪没。yolov5有三个检测头分别对应P3、P4、P5三层特征这三层前面的卷积通道普遍不能大比例剪。常见的策略是全局阈值剪枝把所有BN层的gamma收集起来按大小排序取某个百分位作为阈值低于阈值的通道剪掉。这个做法的好处是代码简单坏处是不同层灵敏度不同一刀切容易误伤。更稳的做法是叠加单层最大裁剪比例比如每层最多剪80%同时保证每层至少保留16个通道防止中间某一层被剪成空壳。# 通道剪枝的核心逻辑BN层gamma全局阈值 单层比例保护 def prune_channels(model, prune_ratio0.3, layer_max_ratio0.8, min_channels16): bn_modules [m for m in model.modules() if isinstance(m, nn.BatchNorm2d)] # 收集所有BN层的gamma计算全局阈值 all_gamma torch.cat([m.weight.data.view(-1) for m in bn_modules]) threshold torch.quantile(all_gamma, prune_ratio) prune_plan [] for m in bn_modules: gamma m.weight.data mask gamma.abs() threshold # 单层保护剪得太多就回退到上限 keep_ratio mask.float().mean().item() if keep_ratio (1 - layer_max_ratio): k max(int(len(gamma) * (1 - layer_max_ratio)), min_channels) _, topk_idx gamma.abs().topk(k) mask torch.zeros_like(gamma, dtypetorch.bool) mask[topk_idx] True prune_plan.append(mask) return apply_prune_plan(model, prune_plan)这里prune_ratio0.3表示全局要剪掉30%的通道layer_max_ratio0.8表示单层最多剪80%min_channels16保证每层保留最少16个通道。apply_prune_plan是真正改模型结构的函数要做的事包括删掉当前卷积层输出通道中mask为False的维度同步删掉下一个卷积层输入通道的对应维度并重算BN层的统计量。这一步不能只删权重还要同步修改Conv2d的out_channels和下一个Conv2d的in_channels否则PyTorch加载时会报维度不匹配。从实际效果看yolov5s在常规检测任务上剪30%到40%通道mAP掉1到2个点很正常微调后能找回来。剪到50%以上小目标漏检开始明显尤其是近距离重叠目标。剪枝比例低于20%基本没必要部署时省不了多少算力还要多搭一个微调周期性价比很低。3.2 剪完微调恢复精度的节奏剪完的模型精度必然掉掉了之后要做的微调和稀疏化训练是相反方向的动作稀疏化要压gamma微调要放开gamma让模型重新适应新的结构。微调的epoch不需要多常见做法是原本训练量的15%到20%就够重点是学习率要放低。# 剪枝后的常规微调命令注意关闭稀疏开关 python train.py --data your_dataset.yaml --weights pruned_model.pt \ --cfg pruned_model.yaml --epochs 20 --batch-size 32 --img 640 \ --finetune --lr 0.001 --cos微调时如果模型结构被改过train.py需要能加载自定义结构的权重。带剪枝的开源仓库通常会生成pruned_model.pt和对应的pruned_model.yaml微调时同时传--weights和--cfg让训练代码用新的结构定义文件组装网络。这一步是新手最容易卡住的地方直接用原yaml加载剪枝后的权重结构对不上报错信息通常是key的数量对不上。微调的学习率不要太高。0.001起步前5轮观察mAP曲线恢复缓慢就提到0.002震荡就降到0.0005。结束条件看验证集mAP是否恢复到剪枝前的98%以上。如果达不到说明剪多了回到剪枝阶段把prune_ratio下调5个百分点再试。整个微调周期不必追求完整训练那么多轮模型的大部分权重已经收敛缺的只是适应新通道结构的几个epoch。4. 模型量化从FP32到INT8的精度换体积4.1 PTQ和QAT怎么选量化是把模型权重和激活值从FP32压缩到INT8四个字节变一个字节模型体积直接缩到四分之一推理速度在支持INT8算力的设备上有明显提升。yolov5的量化落地常见两条路线训练后量化PTQ和量化感知训练QAT。PTQ做起来最快一次前向推理收集激活值分布完成校准即可。对yolov5s这类规模的模型PTQ通常能控制在1到3个点的mAP损失数据集简单甚至可以不掉点。QAT需要在训练时模拟量化噪声让权重适应精度损失适合PTQ掉点严重的场合但训练时间和调参成本高不少。我的建议是优先PTQ因为yolov5的检测头对数值敏感度比分类网络更高先看PTQ的结果再决定要不要上QAT能省下大量时间。以TensorRT部署为例PTQ的代码路径比较固定import tensorrt as trt def build_int8_engine(onnx_path, calibration_data, save_path): 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(onnx_path, rb) as f: parser.parse(f.read()) config builder.create_builder_config() config.set_memory_pool_size(trt.MemoryPoolType.WORKSPACE, 1 30) config.set_flag(trt.BuilderFlag.INT8) # 熵校准器统计每层激活值的分布 calib EntropyCalibrator2(calibration_data) config.int8_calibrator calib engine builder.build_serialized_network(network, config) with open(save_path, wb) as f: f.write(engine)这段代码里EntropyCalibrator2是TensorRT的熵校准器它在INT8量化时遍历校准数据统计每层激活值的分布选择最小化信息损失的阈值。校准数据一般取验证集的300到500张图不要用训练集——训练集的分布偏乐观部署时遇到真实数据误差会放大。校准集的图片分辨率要和训练时一致否则激活值分布统计失真。4.2 敏感层保护与calibration策略PTQ掉点的根源通常是激活值里的离群点。ReLU之后的大激活值会撑大量化范围导致大部分正常值被压到很少的量化步长里。yolov5的检测头最后一层输出是坐标回归值范围大且稀疏量化时容易被离群点带偏。应对手段有两个。第一个是敏感层保护把检测头部分的卷积层从量化中排除保留FP16精度只量化backbone的特征提取层。yolov5是单阶段检测网络head对数值精度最敏感保护住这一小块整体掉点能明显收窄。第二个是校准数据的构成不要只挑简单的大目标样本把密集小目标、遮挡、暗光样本混进去让校准集和真实部署场景的分布一致。常见做法是从每个类别采样10到20张代表性图片凑成300张左右。还有一点容易忽略ONNX导出时的opset版本会直接影响量化结果。导出时建议用opset 12到16之间的稳定版本太老的opset缺乏量化算子支持太新可能引入不稳定的算子融合。导出命令一般不复杂关键是确认ONNX模型在onnxruntime里能正常推理再进量化流程。5. 避坑yolov5剪枝量化常见问题排查5.1 剪完推理变慢因为没开底层加速算子现象剪枝后模型参数量小了一大截但在板子上实测速度反而慢了。原因PyTorch的CPU推理不会自动利用剪枝后的稀疏结构。卷积层通道数变小如果算力库没有做对应的kernel优化访存开销占比反而上升。很多一键脚本只做了模型瘦身没接底层推理引擎导致剪枝收益没兑现。解决部署阶段改用TensorRT、ncnn或RKNN等推理框架配合INT8量化才能发挥剪枝收益。剪枝和量化要组合用单独剪枝在嵌入式上的提速有限尤其在CPU端。跑通脚本之后先确认目标硬件的推理引擎再对比剪枝前后的端到端延迟。5.2 BN层融合后剪枝崩了现象用已经做过BN融合的模型文件去剪枝报结构key错误或者剪出来的模型没法加载。原因yolov5导出阶段常常把BN层融合进卷积层融合后模型里不再有独立的BatchNorm2d节点剪枝脚本按BN层名去定位通道自然找不到。解决务必保留原始未融合的PyTorch模型做剪枝。导出的ONNX或融合后的权重直接用作部署不要拿来做结构化剪枝的输入。如果仓库里只有融合后的权重需要先转换回带BN的结构这一步比较费劲所以最好在训练阶段就留一份原始权重。5.3 量化后精度掉5个点以上现象INT8量化后mAP从78掉到72以内明显超预期。原因校准数据分布和真实场景偏差太大或者检测头的坐标输出层被极端激活值污染了量化范围。解决第一步检查校准集确保覆盖暗光、遮挡、小目标等长尾场景样本数不低于300。第二步逐层看量化范围把坐标输出层设置为per-channel量化或直接排除量化保留FP16。若还不行就换QAT训练时加入伪量化节点让模型权重适应INT8的精度损失。5.4 一键脚本把训练超参也改了现象跑完一键脚本模型精度和原配置对不上复现结果和仓库README描述不一致。原因有些一键脚本为追求“跑通”附带改了输入分辨率、anchor设置或数据增强开关属于隐性的超参漂移。来源如果不可靠超参改动的风险更高。解决跑任何剪枝脚本之前先用git diff把超参改动拉出来对比一遍。只保留和稀疏化、剪枝、微调相关的改动其余全部还原。yolov5的超参数文件在data/hyps目录下对比完再启动训练。5.5 剪枝后输出全是NaN现象模型前向推理输出NaNloss直接变nan验证集mAP为0。原因剪枝后某些层的通道数变成0或者BN层的统计量里混入了除零。常见根源是全局阈值过高某个中间层被剪成全零通道后续卷积输入变成常数BN计算分母为0。解决在剪枝逻辑里加单层min_channels限制至少保留8到16个通道同时检查BN层的eps参数。剪枝后重算BN统计量时把eps从1e-5改成1e-3先跑一轮验证能过滤掉大部分数值不稳定问题。6. 验证与部署剪枝量化后的模型到底快了多少6.1 用mAP和FPS做验收模型合不合格不能只看体积小了多少得看两个数mAP损失和FPS提升。mAP用验证集算和剪枝量化前的原模型逐一对比。FPS必须在目标硬件上测不要在服务器上用PyTorch的测速来代表部署指标那只会让上板后的预期对不上。# 用yolov5自带的val.py做精度验收 python val.py --data your_dataset.yaml --weights quantized_engine.pt \ --img 640 --batch-size 1 --device 0跑完看两个输出mAP0.5和mAP0.5:0.95。如果mAP0.5:0.95掉点超过5个点大概率是量化calibration没调好如果mAP0.5掉点超过3个点说明检测头敏感层被过头量化。FPS的测试不要用batch维度部署场景通常batch为1测的就是单帧延迟。6.2 一键跑通本方案的最小命令序列把整条链路串起来一个可复现的最小命令顺序长这样# 第1步稀疏化训练产出稀疏模型 python train.py --data your_dataset.yaml --weights yolov5s.pt \ --epochs 130 --batch-size 32 --img 640 \ --sparse 0.0005 --lr 0.005 --cos # 第2步通道剪枝产出剪枝后的新结构和权重 python prune.py --weights runs/train/exp/weights/last.pt \ --prune-ratio 0.3 --save pruned_model.pt # 第3步微调恢复精度 python train.py --data your_dataset.yaml --cfg pruned_model.yaml \ --weights pruned_model.pt --epochs 20 --batch-size 32 \ --finetune --lr 0.001 # 第4步导入推理引擎做INT8量化 python export.py --weights pruned_finetuned.pt --include onnx python build_int8_engine.py --onnx pruned_finetuned.onnx \ --calib-data val_images --output quantized.engine这是我把一键脚本拆开之后得到的最小集。实际工作时我会按微调曲线再调一次——如果10轮就恢复到目标精度就不必跑满20轮。稀疏系数、剪枝比例、校准集这三处是出手调参数时最值得花时间的变量其他保持默认即可。这么多年踩下来我的体会是代码一键运行只是起点不是终点。剪枝量化这条路没有玄学每一步都有对应的验证动作——剪枝前看gamma分布剪枝后看单层存活通道量化后看per-layer的精度影响这样排查起来方向才能对。希望帮到你。本文还有配套的精品资源点击获取