ARTICLE DETAIL

资讯详情

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

模型剪枝与量化实战:从原理到部署的完整避坑指南

模型剪枝与量化实战:从原理到部署的完整避坑指南 模型优化这个坑我踩了快三年了。刚开始接触剪枝和量化的时候总觉得这是训练完顺手加个参数就能搞定的事结果被精度暴跌、算子不支持、推理反而变慢这些破事反复折腾。看了一圈网上的资料要么是纯理论推导看得头大要么是只讲API怎么调用不讲为什么真正能拿来用的经验分享少得可怜。这篇文章就围绕“模型剪枝”和“模型量化”这两块内容展开把我知道的原理、实操步骤、工具链选型还有踩过的坑一次性整理出来。因为标题是“概述”我不会去死磕某个框架的源码而是把整个优化链路讲清楚让不同基础的读者都能建立自己的判断标准。适合谁看刚进入模型部署岗位的工程师、想在端侧跑模型的研究生、还有被老板要求“把模型跑在摄像头/手机/边缘盒子里”的架构师这篇应该能帮你少走不少弯路。先说句题外话。我搜资料的时候发现“量化”这个词在金融领域也特别火什么量化交易、量化指标源码之类的跟咱们聊的模型量化完全是两码事。文章里提到的“量化离散误差”“校准数据集”“QAT训练”全部指深度学习推理优化方向别混了。1. 先弄清楚模型优化到底在解决什么问题1.1 模型越来越大硬件却想越小现在的模型参数动辄上亿BERT base是1.1亿参数LLaMA这类大模型更不用说了。纯FP32精度下一份权重就占4个字节模型在显存里动不动就几GB。但实际部署的场景往往是树莓派、手机NPU、嵌入式设备内存就几百MB到1GB算力更是差着几个数量级。说白了模型优化的核心矛盾就一句话算法想要更大更准工程想要更快更小。剪枝和量化就是为了调和这个矛盾存在的。模型剪枝的思路是“既然你有那么多参数那就砍掉一部分”模型量化的思路是“既然你用FP32存参数那就换成更低精度的格式存”。一个做减法一个做降精度两者互不冲突、能够叠加使用。实际操作中我见过最激进的方案把ResNet50从45MB压到不到8MB精度损失控制在2%以内靠的就是“训练时稀疏化结构化剪枝int8量化”三连。1.2 剪枝和量化一个做减法一个做降精度剪枝本质上是在“参数重要性”上做文章。训练结束后很多参数的数值非常接近于0或者整个通道对最终输出的特征图贡献极小把这些参数删掉对精度的影响是可以接受的。量化本质上是在“数值表示范围”上做文章。FP32能把数表示得很细但很多场景并不需要这么细。比如一个权重是0.37281另一个是0.37283这两个数值对最终预测的影响微乎其微。但int8能表示256个离散整数只要把0到1的范围映射到0到255精度损失往往小到可以忽略。这两个方法组合起来能把模型的存储体积压缩到原来的1/4甚至1/8同时推理速度提升2到5倍不等。代价是什么精度下降、开发周期变长、调试难度增加。这篇文章要解决的就是怎么让代价变得更小或者至少让你心里有数。我自己的经验是剪枝量化不是“赌局”而是一套可以依赖的方法论前提是你对原理有足够的理解。2. 模型剪枝把冗余参数真正拿掉2.1 先分清两类剪枝非结构化 vs 结构化剪枝最基础的分法是看剪去的参数以什么为单位。非结构化剪枝也叫细粒度剪枝它直接把权重矩阵中绝对值小于某个阈值的单个参数置零。这样做的效果是模型变得“稀疏”了但矩阵本身没有变小需要专门的稀疏矩阵库或者硬件支持才能实际加速。我在CPU上试过非结构化剪枝如果稀疏度不超过50%推理速度基本没有变化超过70%以后用稀疏矩阵运算库确实能快一些但代码复杂度上来了而且不同的CPU指令集优化效果天差地别。结构化剪枝也叫粗粒度剪枝它把“通道”“滤波器”“注意力头”这类整体结构剪掉。以卷积神经网络为例结构化剪枝会直接删掉某一个输出特征图对应的整组卷积核这样参数量、计算量、内存占用同时减少在GPU上、CPU上、NPU上都能获得真实加速无需依赖稀疏运算库。我个人的建议是产品落地首选结构化剪枝尤其是通道剪枝性价比最高。模型研究可以用非结构化剪枝作为探索方向但要应对实际工程最好从一开始就拥抱结构化。2.2 通道剪枝的完整实操流程给一个我能跑通的通道剪枝参考路线基于PyTorch为例。第一步带稀疏化约束地训练模型。直接在损失函数里加一个L1正则项目标是让BN层的缩放因子gamma尽量接近0。BN层的gamma向量和每个通道一一对应gamma接近0意味着这个通道的输出会被缩放到很小剪掉它对整体影响最小。训练超参数上我一般把稀疏率系数设为1e-5到1e-4之间过大会让精度严重下降过小则剪出来的模型精度受影响。这一步通常需要比正常训练多train几十个epoch让gamma充分收敛。第二步确定剪枝比例并执行剪枝。这个比例没有万能答案。一个可用参考是先做一次“全局gamma值分布”统计把训练好模型的BN gamma值按通道画直方图。如果接近0的通道非常多说明冗余度大可以剪50%甚至更多如果gamma分布比较均匀那剪枝比例尽量控制在30%以内。第三步剪枝后的微调训练。这一步很关键绝对不能跳过。剪掉通道后模型结构变了需要用较低的学习率比如原学习率的1/10重新训练10到20个epoch来恢复精度。第四步导出并评估。把剪枝后的模型导出ONNX或者直接部署用同一份测试集跑精度和时延确认实际提升效果。2.3 剪枝比例怎么定一张表帮你做判断先声明不同模型、不同任务差异巨大下面的经验值需要根据项目实际情况来评估验证模型类型保守剪枝比例激进剪枝比例推荐做法ResNet系列图像分类30% - 40%50% - 60%先按30%剪微调后继续尝试40%MobileNet轻量分类网络20% - 30%40%需要QAT配合轻量网络参数本身冗余少谨慎YOLO系列目标检测30% - 50%60%精度损失明显关注mAP而不是分类准确率Transformer/BERT类30%左右结构化50%搭配知识蒸馏注意力头剪枝和FFN维度剪枝分开做2.4 我这边的剪枝踩坑记录第一个坑剪完模型测精度没变化部署后反而变慢了。原因是我只做了非结构化剪枝权重矩阵稀疏了但在普通推理引擎里没法加速反而多了稀疏处理的开销。要真实提速必须走结构化通道剪枝。第二个坑微调时把BN层更新又调回来了。我剪枝后直接用了原来的训练配置结果BN层的gamma又在微调过程中被拉回到一个很大的值重新变得冗余等于白剪了。后来我在微调阶段冻结BN层的gamma只更新卷积层和全连接层的权重效果好了很多。话说回来剪枝做到一定程度还是会遇到瓶颈。单纯的通道剪枝剪到50%以上精度通常明显下降这时候该考虑跟量化配合使用了。有时候剪枝加量化叠加比单用其中一种效果还好原因在于量化对参数分布更敏感剪枝后模型的参数分布更集中量化误差反而更小。3. 模型量化用更低精度换更高吞吐3.1 量化到底在做什么一个案例讲明白量化的本质是把连续的浮点数值映射到离散的低精度整数空间。ML领域最常用的是int8量化也就是用8bit整数表示原本用32bit浮点表示的权重和激活值。用个通俗的类比说明原来记录温度时能精确到小数点后两位比如36.58度现在只允许记录到整数比如36度、37度信息有损失但对“判断有没有发烧”这个任务的影响非常小。模型量化就是这样牺牲一点点数值精度换来4倍的参数体积压缩和明显的计算加速。工程实现上量化需要两个参数缩放因子scale和零点zero_point。对称量化会把浮点max绝对值映射到127计算过程简单整数推理不涉及零点偏移硬件实现起来很友好非对称量化会额外考虑浮点数的min和max零点不为0能覆盖的分布更广。下面这个表能帮你看清楚差异量化方式映射公式简化优点缺点适用场景对称量化fp scale * int8计算简单硬件友好对偏置分布不友好利用率低权重量化、大部分NPU非对称量化fp scale * (int8 - zero_point)能充分利用int8取值范围计算多一步零点偏移激活值量化有符号无符号混合3.2 激活值量化比权重量化麻烦得多很多人刚开始接触量化时有个误区只要把权重转成int8就行了。真正做起来才发现激活值量化才是决定成败的关键。权重量化相对好做因为训练收敛后权重分布相对稳定。但激活值不同它随着输入数据的变化不断波动特别是经过ReLU激活函数后输出全是非负数分布往往是长尾的。如果把极端的大数值也兜进量化范围那么大部分中小数值会被压缩到很少几个离散的整数档位里量化分辨率变得极低精度损失会比较明显。处理激活值量化的常用办法是**逐通道量化per-channel和按张量量化per-tensor**的选择。per-channel量化能针对每个输出通道单独计算scale精度通常好于per-tensor但推理解码的成本更高。CNN里权重一般用per-channel激活值因为计算代价问题一般用per-tensor。Transformer类模型里激活值分布更复杂很多团队开始用8bit8bit混合精度甚至GPTQ这类更细粒度的方案。还有一个实际操作中经常被忽略的坑某些层对量化很敏感。总结下来残差连接相加的位置、检测头的回归分支目标框坐标、注意力机制里的softmax输出都是量化误差放大器。想保住精度要么把这些层保持FP16要么用QAT方案针对性地训练。我试过只把最后几层保持FP32整模型int8精度掉点从2.1%降到0.4%效果很直接。3.3 PTQ与QAT两种量化路线的选择量化落地的技术路线主要有两种训练后量化PTQPost-Training Quantization和量化感知训练QATQuantization-Aware Training。PTQ的做法最简单模型训练完用一小部分校准数据统计激活值的分布范围算出scale和zero_point然后直接转换。它不需要重新训练成本低、周期短大部分CNN模型能这样直接部署。缺点是碰到对量化敏感的模型精度可能掉得受不了。QAT的做法是在训练阶段就模拟量化过程在前向传播时插入fake quantize节点把权重和激活值“假装”量化成int8再反量化回来参与计算。这样模型在反向传播时会自动学着适应量化带来的噪声推理时再真的量化精度会好很多。代价是要重新训练模型而且训练时间比正常训练长不少。对于绝大多数实际项目我会按这个顺序选择先试PTQPTQ精度满足要求就直接上PTQ不行再考虑更细致的量化策略如果还不够才上QAT。QAT要动训练代码和流程工程成本高能不动就不动。3.4 校准集选不好量化结果直接造假PTQ里有个“校准calibration”环节就是用一小部分真实数据跑一遍模型统计每一层激活值的min/max或是KL散度分布来确定量化参数。这个环节依赖校准数据集的代表性也依赖校准算法的选择。这个环节最典型的坑我遇到过一次相当隐蔽的校准数据集和验证数据集有重叠导致量化后的模型在测试集上精度虚高看起来只掉了0.5%一上生产环境就露馅实际掉了4%。后来排查半天才找到原因。量化参数是你从数据里“偷学”到的分布信息再用同分布的数据去测试就等于考试前先看了答案分数自然好看。这个问题在业界叫“信息泄露”和你在金融量化里见到的未来函数问题是同一个道理。我的建议是校准集单独从训练集里划出来数量在512到2048张图片之间覆盖各种光照、角度、类别的数据验证集保持独立校准集经过网络统计的统计信息不能反过来参与训练。而且每次量化调试都要记录校准集版本时间跨度长了以后才能追踪到问题。4. 工具链选型与端侧部署实操4.1 从PyTorch到ONNX再到推理引擎的路径量化不是训练完直接在PyTorch里做就行最终要看模型跑到什么推理后端上。现在主流的部署路径是PyTorch训练好模型导出ONNX再用ONNX Runtime或者TensorRT/RKNN转换推理。ONNX Runtime里做int8量化比较省心有两种方式一种是动态量化Dynamic Quantization这次无需校准数据直接在线把权重转成int8激活值保持原始精度简单但加速有限另一种是QDQQuantize DeQuantize量化和QAT训练产出的fake quant节点配合导出ONNX时保留QDQ节点ONNX Runtime会在推理时将QDQ节点融合成真正int8计算核心精度和加速都更好。ARM平台上的RKNN是我这两年接触比较多的。RKNN工具链支持PyTorch、ONNX格式的模型导入量化方式也支持int8和int16。它的工具链做得越来越顺手但有个雷区部分算子比如某些自定义ROIAlign或者高版本PyTorch导出的Gather操作在RKNN上会提示不支持或者直接跳过量化导致部分算子落到CPU上跑整体速度掉一半。所以转换完之后必须检查生成的模型结构看算子是否都落到了NPU上。4.2 量化后“数值不动”怎么排查在各个社区里经常看到有人问“int8量化后精度没变但输出数值完全不变”。这里先分情况讨论。先说“数值完全不变、甚至结果全部为0”的情况大概率是量化配置没有真正生效。常见原因有三个模型导出的ONNX里算子没有走量化分支校准数据量太少激活值range被算成一个极小值导致所有数映射到0附近输入的数值范围跟校准时的统计范围差距过大被clip到同一个区间。排查步骤我整理成固定的套路打印量化后模型的输入输出scale和zero_point看数值是否合理。用ONNX Runtime打开导出的模型确认对应Convolution/Gemm算子是否带QDQ节点。用同一张输入图片分别跑FP32和int8模型逐层对比中间激活值的分布。确认推理引擎确实加载了量化模型而非回退到FP32。还有一个很常见的坑量化模型在CPU上反而比FP32更慢。原因有几个。要么是int8算子没有被正确调用导致反量化、量化反复切换计算量反而增加要么是单线程推理时int8优势不明显还多了量化反量化的开销。想要看到明显的量化加速最好是让模型的卷积、矩阵乘等计算密集型算子连续执行int8计算并把推理线程放到4个以上或者直接在支持int8的NPU/GPU上测试。4.3 为什么要保留一份“精度对照基线”不管用什么工具链做量化我强烈建议在代码仓库里保留一套“精度对照脚本”把FP32模型和量化模型的输出结果、精度指标、每一层的预测结果都对比出来。这套机制很朴素但救过我很多次。有一次升级了RKNN的版本模型在测试集上精度看起来只下降了0.3%但我随手拿一套专门的困难样本一测边界框出现了偏移最后用上一版本量化的模型一对比发现是工具的算子实现出了问题。没有基线这种问题可能就悄悄溜进线上环境了。对照脚本里至少包含三块内容测试集评估的完整指标、量化模型和FP32模型在同一批输入上的输出相似度比如余弦相似度、以及随机抽取的20张图片的可视化对比结果。这三样东西能让你快速定位问题出在模型效果上还是出在量化配置上。5. 实战高频问题排查与避坑速查5.1 int8量化后精度下降的排查顺序精度下降这件事别慌按顺序排查多数情况能在半小时内定位。第一步确认校准是否真的有效。如果校准集选得不对后面做的都白搭。看看校准集里边有没有明显的分布偏差。第二步检查量化粒度。把全部per-tensor改成per-channel尤其针对卷积层权重通常能挽回一部分精度。第三步检查敏感层。把残差连接层、输出层、检测头这些层用FP16替代看精度恢复情况。定位到具体哪层导致掉点严重再针对性处理。第四步切换到Quantization Aware Training。如果前面的手段都用尽了精度还不达标就老老实实上QAT在训练时模拟量化噪声给模型时间适应。我遇到最极端的一次是int8量化后mAP掉了9%前三个步骤都没效果最后用QAT把掉点收回到1.5%。QAT训练时长大约是正常训练的2倍但为了上线这个成本得认。5.2 模型剪枝量化后精度不升反降的隐藏条件这里插一个重要提醒剪枝量化之后精度损失不等于模型变差了你要做的是和“原模型在同一精度标准下”的对比。但有一个隐藏陷阱是精度指标对“数值扰动”的敏感性在不同数据集上差异极大。比如人脸识别这种对距离阈值很敏感的任务int8量化后IQA和ROC曲线可能看着差不多但实际生产的误识率翻了一倍。原因就是量化噪声改变了特征向量的细微位置而阈值本身设在了一个很陡峭的区间。这类任务建议保留一份“线上分布数据”专门做量化验证而不是只跑公开测试集。5.3 一些容易被忽略的部署细节下面这些细节是我踩了不知道多少次坑才总结出来的如果你正在做类似的项目建议直接抄下来训练时开了Dropout推理时遗忘关闭会导致量化校准统计出错先确保模型在eval模式下完成校准。归一化层BN/LayerNorm在导出前要做融合操作一般框架会自动做但某些自定义网络需要手动融合否则量化后的计算量和精度都受影响。全连接层和Embedding层在Transformer模型里量化的敏感度高优先使用QAT方案里的量化策略。多batch推理和单batch推理的量化表现可能不同上线前测试时注意实际吞吐工况。保存量化模型时注意格式比如ONNX的external data文件别漏拷RKNN模型版本要和runtime库匹配。校准过程使用的线程数、随机种子都会影响结果建议把校准阶段固定住保证每次验证可复现。6. 工具选型与场景配合的实操心得6.1 不同硬件平台上的优化策略不一样做模型优化一定要跟着目标硬件走同一个模型在不同平台上吃到的加速效果完全不同。在GPU上TensorRT是绕不开的。它会把模型优化成层融合的结构FP16就能获得非常可观的加速int8量化通常再加一层加速。TensorRT做int8需要自己提供校准数据建议用代表性样本模型校准的时候会统计activations的分布然后决定量化参数。在ARM CPU或嵌入式NPU上RKNN和NCNN是主流选择。RKNN对瑞芯微平台支持很完整但量化方式需要匹配硬件很多API只支持对称量化。NCNN在ARM CPU上性能不错量化支持也日趋成熟但遇到Transformer类算子较多的模型会比较头疼。在纯CPU服务器上OpenVINO或者ONNX Runtime的int8表现比较突出。ONNX Runtime加int8量化在x86服务器上一般能快2到4倍内存占用降为原来的1/4。不过这些优化都要测过才知道不要光看PPT理论推测。6.2 剪枝量化和知识蒸馏的搭配如果项目对精度要求特别高或者模型本身已经非常紧凑剪枝量化后精度依旧保不住那就要考虑“知识蒸馏”这条路线。知识蒸馏的思路是训练一个小模型学生去模仿一个大模型教师的输出。它可以跟剪枝、量化结合起来使用先训练一个教师大模型再用教师模型的软标签去训练学生模型学生模型可以设计得比目标模型更小然后继续做量化。这么做精度会比直接训练一个小模型高很多我见过BERT蒸馏到1/4大小再int8量化精度只下降不到1%的成功案例。当然知识蒸馏的工程复杂度更高训练时间也更长。它的顺序我一般是这样的先确定目标模型结构和算力预算再决定剪枝比例然后用蒸馏方式从教师模型中训练学生模型最后做量化。这个组合拳打下来才可能把模型压到极致的同时保住精度。7. 模型优化的边界哪些情况不适合上剪枝量化任何技术都有适用范围。我这里罗列几类不建议强上剪枝量化的场景供你参考。第一类是对精度极其敏感的回归类任务比如医学图像里的像素级分割、某些科学计算代理模型。这类任务输出连续数值量化噪声会直接影响结果可信度这时宁愿选择模型蒸馏加更高质量的训练数据也尽量不做int8压缩实在要压用int16或者混合精度。第二类是推理时输入分布动态变化特别大的风控场景、异常检测场景。偏小模型过度拟合训练分布一旦线上数据偏移量化参数就会失真精度会剧烈波动。第三类是模型本身就特别小的情况比如只有几万参数的小MLP。这类模型的冗余本来就少剪枝基本没作用量化也省不了多少资源优化收益很低不如直接把精力放到特征工程和算法精度提升上。判断要不要做剪枝量化核心就一句话先算清楚收益和风险的账。收益是模型体积、推理时延、内存占用的改进风险是精度下降和调试成本。如果模型10MB部署设备内存512MB那优化的优先级就不高如果模型500MB部署设备只有1GB内存和400ms时延预算那剪枝量化就是必选项。8. 最后再分享一点实际操作的体会这几年做推理优化给我最深的印象是剪枝量化从来不是“跑一个脚本就能结束”的事。它需要跟训练环节深度配合需要理解模型的数值行为需要了解工具链的每一个隐藏开关还需要足够的耐心去对照实验。我个人的工作法则是每次优化都做实验管理把模型版本、剪枝比例、校准集、量化配置、精度指标、时延指标完整记录下来哪怕同一个方案在不同硬件上也要分别记录。这样做的好处是当你上线后遇到问题能在五分钟内从记录里定位到是哪个环节出的问题而不是靠回忆去猜。如果你正准备上手模型压缩我的最直接建议是先别急着追求极限压缩从30%剪枝或者PTQ int8开始跑通数据中心到端侧的推理链路确认收益和精度都符合预期再逐步加大力度。优化要留余量别把模型压到临界点再上线一遇到真实场景波动就容易翻车。跟模型优化打交道越久越会觉得它像一门“工程艺术”而不是纯粹的数学题。各种原理和工具都在快速迭代但沉淀下来的调试思路、排查顺序和数据驱动的决策方式放哪个时代都不会过时。希望这篇概述能帮你建立起自己的优化框架也欢迎在实际操作中多试试上面提到的组合路径跑通一条链路之后你会对模型优化有完全不一样的感觉。
返回列表