做CNN模型压缩这件事绕不开剪枝、量化、知识蒸馏这三个手段。我最早接触这块是因为一个图像分类项目要从服务器挪到边缘设备模型文件一百多MB设备内存吃紧单帧推理时间更是让人绝望。当时第一反应是换更小的网络结构结果精度掉得没法看。后来老老实实把剪枝、量化、知识蒸馏逐个撸了一遍才明白它们各自解决什么问题、该怎么搭配、坑在哪里。这篇文章就围绕这三个核心技术展开讲清楚底层原理、实操步骤、常见坑和组合策略。不管你是刚开始接触模型压缩还是已经上过一两次线但效果不理想这文章都值得读完。我只讲实际操作中验证过的东西不堆理论公式但该有的原理推导会放在关键位置因为不理解原理后面出问题你根本不知道往哪儿查。1. 从服务器到边缘设备模型压缩到底在解决什么问题先说清楚一个前提模型压缩不是一个“锦上添花”的优化而是部署环节的硬性约束。很多团队在训练阶段从来不关心参数量和计算量反正GPU集群放着显存管够精度刷上去就完了。到了上线阶段硬件规格一摆出来——内存多少、算力多少、功耗上限多少——才发现原封不动地搬过去根本行不通。模型压缩要解决的核心问题可以拆成四个指标参数量模型文件有多大、计算量FLOPs一次前向要做多少次乘加、内存带宽推理时读权重有多频繁、推理时延单帧跑多久。这四个指标并不是完全正相关的。比如参数量大不代表延迟一定高有些模型结构稀疏但计算密集同样剪枝减少了参数量但如果硬件不支持稀疏计算实际延迟几乎不降反升。这也是很多人第一次做剪枝后觉得“白干了”的根本原因。三大手段分别作用于不同层面剪枝改结构。把网络里贡献小的参数或通道删掉降低参数量和FLOPs。量化改精度。把32位浮点权重和激活值换成8位整数甚至更低位数减少存储和位宽推理引擎可以用整数计算加速。知识蒸馏改训练过程。用一个精度更高的大模型教师去带一个小模型学生让学生在模仿教师输出的过程中把精度追上来。实际操作中这三者不是“三选一”而是可以叠加的。常见的压缩链路就是“剪枝→量化→蒸馏修复精度”。为什么会是这个顺序后面第五章我会用实测数据讲清楚。还有一个容易忽略的点压缩方案的选择和你目标硬件强相关。有些边缘芯片对INT8支持很好但对稀疏矩阵支持为零这种情况下剪枝带来的理论FLOPs下降根本兑现不了。所以动手之前先把你目标设备的算子支持列表翻一遍比埋头调参重要得多。2. 剪枝把网络结构里的“闲人”裁掉而不是把参数抹成零剪枝的核心思路很直白卷积网络的参数和通道之间存在大量冗余把不重要的权重置零或者直接删除对应的通道理论上模型就瘦下来了。但“瘦下来”和“跑得快”是两码事这取决于你做的是哪种剪枝。2.1 非结构化剪枝为什么“瘦而不快”非结构化剪枝也叫细粒度剪枝做法是把每个卷积核内部低于阈值的权重直接置零得到一堆稀疏的权重矩阵。优点是很灵活稀疏度可以拉得很高比如85%以上精度损失也不大。但缺点是稀疏矩阵实际存储和计算往往需要专门的稀疏算子支持还要配合稀疏索引格式常规CPU上的稠密矩阵乘法根本不吃这套。换句话说你FLOPs理论上降了跑起来还是原速度甚至因为索引开销变大反而更慢。我之前在一个自研的轻量分类网络上把权重稀疏度压到80%模型文件确实小了但推理时延纹丝不动。查了算子的实现文档才发现当前目标平台的卷积算子压根没有稀疏加速分支。所以除非你的硬件明确支持稀疏计算看芯片的算子手册不是看纸面FLOPs否则非结构化剪枝只能当研究手段很难直接用于生产。2.2 基于BN系数的通道剪枝完整流程实际部署更推荐结构化剪枝尤其是通道剪枝。它直接删除整个通道对应的卷积核网络结构的维度发生变化跑在通用硬件上能真实减少计算量。通道重要性的评判标准有很多比如用卷积核的L1范数、梯度信息、BN层的缩放系数gamma等。我用得最顺手的是基于BN层的gamma系数来挑通道。原理不复杂BN层在训练时会学一个逐通道的缩放因子gamma它反映了这个通道对后续特征的贡献。如果某个通道的gamma趋近于0说明这个通道的输出基本是个常数或者无效信息删掉它对整体表现影响很小。在训练阶段可以对gamma加L1稀疏正则逼着更多通道的gamma向0靠拢这样到了剪枝阶段可裁剪的通道就更多精度损失也更小。这也是之前对比过的几种剪枝框架里比较常用的一招。正则系数不用太大我一般是1e-4量级调大容易让训练震荡。剪枝流程分几步走用带稀疏正则的方式重新训练目标网络直到收敛。统计所有BN层gamma的绝对值按大小排序。设定全局剪枝率比如40%把对应比例的“小gamma”通道标记为待删。重新构造一个“薄”网络结构——删掉待删通道对应的卷积核、后面跟着的缩放因子、BN统计量同步把残差结构里的通道对齐。把保留的权重复制进新网络然后做微调。第4步看着简单模型代码写起来很啰嗦。如果项目里用的是动态图框架建议手工重建网络结构并把剪枝后的权重一一映射进去直接改原网络权重维度会报一堆shape mismatch。我当时是写了一个映射表记录原始通道索引和新通道索引之间的关系微调前拿一个小批量数据前向一次验证输出shape全对再继续。2.3 剪枝后微调学习率与epoch的拿捏很多人剪完枝就直接部署精度掉一截就骂剪枝没用。其实剪枝后的微调环节和训练新模型一样重要。我的经验是先用很小的学习率大约是初始训练的1/10跑几个epoch做“预热”让新网络先适应参数结构变化再把学习率放回一个相对正常的水平继续微调。更稳妥的做法是在微调时也保留一部分稀疏正则或者加一点蒸馏约束这样可以把剪枝模型往“好解”的方向推。如果你剪枝率超过30%微调的epoch建议不少于完整训练epoch的一半不然精度很难回去。我自己经历里剪枝40%通道后直接评估掉点约1.8%微调15个epoch后恢复到了只掉0.3%。这0.3%才是真实代价。2.4 两个高频翻车点稀疏度虚高与精度崩盘第一个坑是“稀疏度虚高”。有时候剪枝率看着很高但删的都是小通道FLOPs下降有限。所以剪枝之后要重新统计一次FLOPs而不是只看参数量少了多少。我见过有人宣称压缩50%实际FLOPs只降了12%原因就是剪掉的层全集中在低分辨率的高层计算量占比最高的底层一个没碰。这时候要么调整全局剪枝策略给底层网络加大剪枝比重要么采用按目标FLOPs反推每层剪枝率的做法。第二个坑是剪完精度直接崩。最常见的原因是残差结构没对齐。在带残差的网络里如果跳连分支和主分支的通道数不一致剪枝后要么代码报错要么虽然能跑但特征叠加错位精度断崖式下跌。排查方法就一条剪枝后先跑一遍对齐校验让相同输入分别经过原始网络和剪枝网络逐层对比中间feature map的shape别直接跳到微调。3. 量化用更少比特装下同样的模型量化做的事情和剪枝完全不同。剪枝是删掉“不重要的东西”量化是降低每个数字的表示精度。它不改变网络结构而是把32位浮点权重和激活换成8位整数甚至更低精度换来四个好处模型体积变成1/4访存带宽压力大幅下降推理引擎可以使用更快的整数运算指令某些平台上INT8算力是FP32的好几倍。3.1 int8为什么能把模型压到四分之一量化在工程上的核心公式是q round(clamp(r / scale zero_point)) r (q - zero_point) * scale这里r是原始浮点值scale是缩放系数zero_point是零点偏移。直白理解就是我们需要用一个等差数列来近似原始浮点范围的任意值。FP32的权重里有非常多的冗余尾数砍到8位整数后虽然单个数值的精度变了但大量参数的统计特性还在网络整体精度并不会像想象中掉得那么夸张。量化方式上按范围分有对称量化和非对称量化。卷积权重一般用对称量化zero_point恒为0计算简单激活因为有ReLU等操作通常是非对称量化需要单独统计零点。按粒度又分per-tensor和per-channel。per-channel的精度损失更小推理引擎实现更复杂但现代推理框架多数已支持per-channel量化权重建议直接用。3.2 训练后量化PTQ快速上手但精度看运气训练后量化是最省事的路径——训练好的浮点模型拿过来喂一小批校准数据统计每一层激活值的动态范围算出scale和zero_point然后转成INT8模型。这里的“校准集”极其重要。我一开始图省事拿训练集随机抽了32张图当校准数据结果某个分类任务上精度掉了快5%。排查半天发现校准集的数据分布和真实业务数据偏差太大激活值的动态范围统计得不对所有层的scale全部偏了。后来换了一批覆盖各类场景的100张图精度损失立刻降到0.4%左右。校准集选择有几个原则数量不用多但覆盖要广、要和线上真实数据分布对齐、要包含模型最容易出错的样本。如果条件允许多做几组校准对比选模型精度最高的一组scale别嫌麻烦。动态范围统计方法也有讲究。默认的min/max方案容易受离群点干扰一个异常激活值就能把整个scale拖垮。主流框架里常提供百分位、KL散度等校准方法对CNN任务来说KL散度法算出来的scale通常比min/max稳。3.3 量化感知训练QAT精度救星和它的代价如果PTQ精度掉得太多就得考虑量化感知训练。思路是在训练过程中就模拟量化误差前向计算时把权重和激活先量化成INT8再反量化回浮点让网络去适应量化带来的噪声。QAT的关键技术是“直通估计器”STE量化函数本身不可导反向传播时直接把梯度原样传过去近似绕过量化这一步。因为量化误差在训练中被建模了模型收敛后的权重分布对量化更“友好”推理再量化时精度损失通常远小于PTQ。QAT的代价是训练变慢、流程变长。我在一次检测类任务中实测PTQ掉点2.1%QAT训练约1个epoch的迭代量就把损失压回0.6%。建议做法是先把浮点模型训好再做几轮带伪量化节点的“细调”而不是从头做全流程QAT训练后者时间和资源成本太高收益并不成比例。3.4 量化部署中最容易翻车的算子兼容性量化最让工程师头疼的还不是精度而是算子兼容性。很多框架的INT8推理引擎只支持常用算子的量化版本比如卷积、全连接、ReLU、常见的池化。一旦网络里出现一些“冷门”算子——比如某些动态shape操作、特殊激活函数、自定义ROI模块——通路就断了要么直接不支持量化要么精度崩掉。我在一个项目里排查过量化后精度异常的根源模型用了一个自研的归一化模块量化时引擎把它拆成了一堆底层算子分别量化多次量化反量化之间误差累积最终精度掉了3%以上。翻出算子映射日志才看到问题。所以规划网络结构的时候就该考虑尽量用目标推理引擎友好支持的算子少写自研模块如果非要保留至少让它在量化时走“高精度保留”分支或者用等价算子替换。这个意识最好在训练前建立别等模型训完才做量化适配。4. 知识蒸馏把大模型的“手感”教给小模型剪枝和量化解决的是“模型怎么变小”知识蒸馏解决的是“小模型精度怎么上去”。它的思路是大模型不直接给小模型标注数据而是把自己的“判断倾向”作为额外监督信息让小模型不只学正确答案还学大模型面对相似输入时的自信程度和类别关联。4.1 软标签与温度参数蒸馏的核心直觉分类模型最后输出的logits会过一个softmax。当温度T1时输出是常规的类别概率当T增大概率分布变得更平缓类与类之间的“相对关系”就被暴露出来了。比如一张狗的照片狗的概率是0.7狼的概率是0.2猫是0.05狐狸是0.03这种分布比独热标签“狗1其他0”要丰富得多小模型能从中读出“狗和狼长得很像”这类知识。蒸馏loss的常规形式是让学生的软输出温度较高的softmax去逼近教师的软输出同时配合真实标签的交叉熵损失L alpha * CrossEntropy(y_true, y_student) (1 - alpha) * KL(p_teacher_T, p_student_T)alpha一般设0.7左右温度T在3到7之间比较常见。任务不同最优值差异很大多试几组。温度太高会让所有类别概率趋于均匀学生学不到区分性信息太低又跟直接用独热标签没区别。4.2 从logits蒸馏到特征蒸馏如果只看softmax输出做蒸馏小模型学到的主要是“高层决策面的知识”。但在很多视觉任务里中间层特征图的信息更丰富。所以进阶的做法是特征蒸馏把教师模型和学生模型的中间feature map对齐让学生的中间表示也接近教师。特征蒸馏需要注意两个细节教师的特征图通道数通常远大于学生需要加一个1x1卷积或线性映射把学生特征投影到和教师一样的维度再算loss两边的空间尺寸可能也不一致需要对齐分辨率。这个对齐层在训练时使用、推理时丢掉不会增加部署负担。4.3 蒸馏训练过程中的两个实用技巧第一个技巧是动态调整硬标签和蒸馏loss的比重。训练初期可以多用真实标签把学生模型稳住训练中后期逐渐加大蒸馏比重。直接全程用固定alpha学生模型经常会卡在“什么都学什么都不精”的状态。第二个技巧是教师模型提前蒸馏。如果教师模型本身也预测得不够好它给的软标签质量就差。我遇到过教师模型top1精度只有82%蒸馏出来的学生比从零训练还差。先确保教师模型精度足够好再谈蒸馏否则就是“劣师带劣徒”。4.4 蒸馏和剪枝、量化的组合打法蒸馏经常被当作“压缩后的修复手段”。比如剪枝后的模型精度掉了可以拿原始大模型当教师、剪枝后的模型当学生做一个恢复性蒸馏。量化模型同样可以这样修复——浮点大模型是教师INT8量化模型是学生用蒸馏让它在量化噪声中找到更稳的权重解。这种组合之所以有效是因为剪枝和量化本质上是给模型引入“结构扰动”或“数值噪声”而蒸馏提供了比真实标签更丰富的监督信号帮助模型在这种噪声下重新收敛到更好的局部最优。我在第五章的实测里会展示这个“剪枝量化蒸馏”组合带来的效果。5. 三种手段如何搭配一次实测对比产生的决策思路讲完单个技术来聊实操中最纠结的问题三者到底怎么组合、按什么顺序上。我在一个自研的图像分类Demo上做过一轮对比实验上的是类似VGG风格的堆叠卷积结构基线top1精度88.2%参数量约28M单帧CPU推理约45ms。压缩流程跑完记录如下方案Top1精度模型体积单帧时延相对基线精度变化原始FP32基线88.2%112MB45ms-仅通道剪枝40%通道86.9%58MB28ms降1.3%仅INT8量化PTQ87.6%28MB18ms降0.6%先剪枝后量化85.8%14MB12ms降2.4%剪枝量化蒸馏修复87.9%14MB12ms降0.3%剪枝量化QAT微调87.4%14MB12ms降0.8%注意数据本身会随任务变化但这个相对趋势是有代表性的。先剪枝再量化精度损失会叠加叠加之后如果不做任何补救是最差组合。而蒸馏修复把精度几乎拉回了基线说明蒸馏对压缩模型的价值不在“锦上添花”而在“兜底”。基于这种对比我现在的推荐路线是先做结构化剪枝目标是降低FLOPs和参数冗余切分层的剪枝率要按实际FLOPs占比来分配。再量化到INT8实现体积和带宽的大幅缩减。最后用原始大模型做教师对“剪枝量化”后的模型做蒸馏恢复。至于QAT和蒸馏哪个更优先我的判断是如果PTQ掉点超过1%先尝试QAT微调如果掉点超过2%直接把蒸馏安排上QAT蒸馏一起用也是可行的蒸馏给的是全局监督信号QAT给的是量化感知的局部调整两者互补。工具选型上我的建议是多看一眼推理引擎对INT8算子、动态shape、自定义层的支持情况再决定用哪家的量化工具链。不同引擎的量化scale、校准逻辑、算子融合策略差异挺大同一个模型在这个引擎上掉0.3%换一个引擎可能掉1.5%。项目时间允许的话做一次交叉验证非常值得。6. 我踩过的坑和现在的推荐做法压缩流程里有些问题光看教程不会发现非得自己踩过一遍才能长记性。我把印象最深的三段经历整理出来都是真实事故不是理论推演。6.1 剪枝后BN统计量失效调低学习率也没救回来的精度有次做通道剪枝剪完按规规矩矩的流程重建网络、微调了20个epoch精度死活比预期低1%左右。排查到后面发现重建网络时直接丢弃了对应通道的BN均值和方差而新BN层的统计量是从零开始的随机初始化。微调的前期网络又抱着旧权重去适应新BN相当于带着“错误统计量”跑了很久。后来我的做法是在微调正式开始前先让剪枝后的网络过一遍少量训练数据通过前向传播重新估算BN层的均值和方差把统计量“热身”到位再进入微调流程。这个操作几乎不花时间但精度能往回拉半个点以上。6.2 量化后精度异常的算子定位二分排查法还有一次量化之后精度从88%掉到了84%模型体积是变小了但业务方根本不可能接受。当时没急着改校准集而是用二分法做算子隔离先量化网络的前半段、后半段保持浮点精度看变化再调换量化范围逐步缩小嫌疑层。最终锁定在一个残差分支的相加节点上——推理引擎在量化特征图时这个节点没做高精度累加保护误差被后续网络放大了。解决方案也不复杂要么在量化配置里把异常层标记为“跳过量化”要么重写一个聚合方式更稳的子模块。类似问题出现过不止一次我的排查清单里第一条永远是先用二分法锁层再谈改算法。6.3 蒸馏温度调太高的“灾难现场”最后一个坑和蒸馏温度有关。当时为了让学生模型更快收敛把温度从3直接调到12结果训练loss一路下降、验证精度纹丝不动后来发现是软标签的分布太平滑类别间的差异被抹平了学生对“什么该学”完全失去了方向感。温度参数不是越大越好它本质上是控制信息“模糊程度”的旋钮需要针对任务做小范围搜索。我在不同任务上试出来的经验值大概是T4到T7这个区间超出这个区间就得不偿失。做模型压缩久了我的体会是这一行没有“一招鲜”。剪枝、量化、知识蒸馏不是三个孤立工具而是一个搭配使用的流水线。先把目标设备的算子能力摸清再决定压缩路径压缩完一定做蒸馏兜底上线前掐表测时延、对比精度、记录量化日志宁可多花一周做效果验证也别上新线上拿着真业务当小白鼠。这套流程虽然不性感但每次都能稳住局面。
阅读完成 · 觉得有帮助?