1. 先说清楚模型压缩与推理加速解决的是什么问题训练一个模型和在线上把它跑起来这两个阶段之间隔着一道大多数人低估的鸿沟。我在实际项目里见过太多次这样的场景模型在训练环境里精度漂亮得不得了一上生产环境立刻露馅——单次推理延迟压不下来显存直接爆掉换到CPU上更是慢到让人怀疑模型是不是写错了。模型压缩与推理加速就是专门对付这一堆问题的工具箱。压缩负责让模型变小变轻加速负责让模型跑得更快两者经常捆在一起做但背后逻辑并不完全相同。先说压缩。压缩的目标很直白减小参数量、存储体积和运行期内存占用。一个模型的体积等于参数量乘以单参数占用的字节数。拿一个10亿参数的模型来算FP32精度下每个参数占4字节光权重就是4GB这还没算中间激活值和优化器状态。硬件能承受的体积是有限的手机跑不动GB级模型云端的GPU显存也贵不可能为了一个模型无限堆硬件。所以压缩的本质是在尽量保住精度的前提下把模型的体重降下来。再说加速。加速的目标是降低单次推理延迟、提升每秒处理请求数、摊薄单位算力成本。有人以为压缩就是加速其实不完全对压缩确实能减少计算量和内存访问通常对速度有正反馈但加速还有大量工程手段比如算子融合、计算图优化、内存复用、批量调度这些操作一个参数都不删模型体积不变速度却能翻倍。反过来说一个模型压得很小但如果落到不合适的推理引擎上跑起来照样慢。所以正规的做法是压缩和加速双线并行。这篇文章适合几类人看准备把模型从实验环境搬到业务线上的算法工程师负责模型部署和性能优化的后端同学还有自己折腾模型却总被能训练但跑不动卡住的学生和个人开发者。我不打算只罗列概念重点放在选型思路、实操步骤和踩坑点上这些东西才是真正值钱的经验。2. 四大压缩技术怎么选剪枝、量化、蒸馏、低秩分解压缩技术的名字大家都听过但什么时候用哪个、先做哪个后做哪个很多项目一开始就选错了方向。我按工程落地角度的优先级把这四类技术逐个拆开讲。2.1 量化性价比最高的第一张牌量化是我在所有压缩方案里最优先推荐先试的技术原因是它几乎不改变模型结构只改变数值的表示精度。常规训练用FP32也就是32位浮点数量化把它降到INT8也就是8位整数参数体积直接变四分之一计算速度在多数和硬件上也有明显提升因为整数运算比浮点运算快而且内存带宽压力小了很多。量化分两条路训练后量化PTQ和量化感知训练QAT。PTQ的做法是模型训练完后再做转换只需要准备一小部分校准数据跑一遍统计出每个层的数值范围然后映射到整数区间。映射公式本质上是线性缩放核心是算出缩放因子scale和零点zero_pointq round(r / scale) zero_pointscale由该层激活值或权重的最大最小值决定对称量化里zero_point通常是0非对称量化则会多一个偏移量精度略好一点但实现更复杂。我一般先试PTQ因为改动最小如果精度损失在可接受范围内就直接上线如果掉点超过一个百分点再考虑QAT。QAT是在训练过程中模拟量化误差让模型自己去适应低精度表达效果通常比PTQ好但需要重新训练成本高不少。这里必须提醒一个高频误区校准数据集的选择直接影响PTQ效果。校准集不是拿来验证精度的它的用途是让模型统计出有代表性的激活值分布。有人图省事随便拿几十张图做校准结果量化后精度崩了然后跑来骂量化不稳定。校准数据要覆盖真实业务里的典型输入分布数量可以不用太多几百到几千条足够但多样性必须保证。另外某些对数值范围特别敏感的层比如检测头的最后一层卷积或者注意力里的softmax前一层可以考虑保留FP16不做量化这种混合精度处理是实战里最常见的补丁方案。2.2 剪枝结构化与非结构化的取舍剪枝的思路是删掉模型里不那么重要的连接或者通道。一个大模型训练完之后大量参数的实际值非常接近零或者某些通道对最终输出的贡献微乎其微删掉它们对精度影响很小但能直接减少计算量和存储。剪枝分两类非结构化剪枝和结构化剪枝。非结构化剪枝是把权重矩阵里绝对值小的单个元素置零得到的是稀疏矩阵。缺点很明显稀疏矩阵如果不被底层计算库专门支持实际运行速度不但不提升反而因为存储不规则变得更慢。这类剪枝更适合跑在支持稀疏算子的专用硬件上通用场景我不太推荐优先做。结构化剪枝就务实多了它按整个通道、整个滤波器为单位来删比如卷积层的某个输出通道整条拿掉。这样删除之后网络结构仍然是规则的稠密矩阵通用的卷积计算库都能跑不需要特殊硬件配合。实际做的时候通常先对每一层的通道重要性打分然后按比例剪掉分数最低的通道。打分方式有很多种最简单常用的是看通道权重的L1或L2范数范数小说明这个通道的激活贡献弱。剪枝一定要注意剪了之后要微调。剪完直接上线精度大概率会掉需要一个恢复过程。业界常用的做法是迭代式剪枝先剪掉一小部分比如10%然后微调几个epoch让精度恢复再剪10%再微调循环推进。这个流程看着繁琐但比一次性大刀阔斧剪掉30%再猛调要稳得多。我踩过这个坑一次剪太多再训练模型直接不收敛后来改成小步快跑精度恢复就顺畅了。2.3 知识蒸馏用大模型带小模型知识蒸馏的思路不一样它不直接动大模型的参数而是训练一个结构更小的学生模型让学生模仿大模型的行为。大模型叫教师模型小模型叫学生模型。训练时不只是让学生拟合真实标签还要拟合教师模型的软输出。软输出的价值在于它携带了类间相似度的信息。比如一个三分类任务真实标签是猫但教师模型输出的概率分布可能是猫0.7、狗0.2、狐狸0.1这个0.2和0.1的信息量其实很大它告诉学生模型猫和狗在某些特征上是接近的。只学硬标签永远得不到这层信息。蒸馏的损失函数一般是两部分加权L α * KL(学生软输出, 教师软输出) (1-α) * 交叉熵(学生输出, 硬标签)软输出需要用温度T来软化温度越高概率分布越平缓信息保留得越多。T一般取2到8之间需要自己调。α通常0.7左右起步。蒸馏最大的坑在于学生模型的结构选择和训练成本。很多人以为随便选个小模型就能蒸馏成功结果学生模型容量太小根本学不进教师的知识这叫容量差距过大。如果容量差太多可以考虑把蒸馏和剪枝结合——先蒸馏出一个中等模型再剪枝而不是一步到位用极小的模型。2.4 低秩分解被低估的数学工具低秩分解用矩阵分解的思路压缩权重。一个权重矩阵W如果近似可以拆成两个小矩阵的乘积比如把m×n的矩阵拆成m×k和k×n两个矩阵当k远小于m和n时参数量就大幅下降。这个技术在传统图像压缩领域用得很多在深度学习里主要适用于全连接层和1×1卷积层因为这类层本质上是矩阵乘法。低秩分解的问题在于它是近似的拆完之后几乎必然要配合微调才能恢复精度且结构化实现不如剪枝和量化那么通用。我的判断是如果项目时间紧低秩分解可以往后放一放先做量化和剪枝如果目标是极致压缩可以把它作为补充手段叠加上去。下面这张表总结了四种技术的核心对比方便需要快速决策的人参考技术改动范围是否需要重新训练硬件依赖典型收益落地难度量化PTQ数值精度否仅校准低通用硬件都支持体积1/4速度提升明显低量化QAT数值精度是低体积1/4精度优于PTQ中结构化剪枝模型结构是需微调低体积和计算量按比例下降中非结构化剪枝权重稀疏是需微调高需专用稀疏算子理论压缩率高实机提升不确定高知识蒸馏替换模型结构是重新训练低体积可大幅缩小中低秩分解权重表达最好微调低主要压全连接层中高3. 推理加速不只是换硬件工程侧的活儿模型压完了接下来是让它在目标硬件上真正跑得快。推理加速这一块工程细节比算法细节更多翻车概率更高。3.1 推理引擎与中间表示的坑把训练好的模型变成线上能跑的推理程序中间要过一道转换。最通用的中间表示是ONNX它像一个模型世界语把PyTorch、TensorFlow等训练框架里的模型统一描述成计算图再交给各个推理引擎优化执行。ONNX转换是大多数项目的第一步也是报错重灾区。转换的核心坑是算子兼容性。训练框架里某个很冷门的自定义算子ONNX里没有对应实现转换直接失败。遇到这种情况先别慌常用的解法是改写模型结构把这个冷门算子拆成几个ONNX支持的算子组合。另一个坑是ONNX的算子集版本版本太老不支持新算子版本太新目标推理引擎又不认识所以转换之前要查清楚目标引擎支持的opset版本对齐之后再转。落地到具体引擎常见的选择有通用型和硬件绑定型两类。通用型引擎部署灵活、覆盖设备广适合快速验证和跨平台交付硬件绑定型引擎会对特定芯片做极强的算子级优化比如层融合、内核自动调优、显存池化加速效果往往是最顶的。我的建议是如果目标设备单一直接用硬件绑定型把性能压榨到极限如果需要在多种设备上交付先走通用型保证兼容性再说。某些通用型推理框架也有C端的轻量部署方案对CPU场景优化得很好适合服务端高并发。3.2 算子融合与内存布局的细节很多人以为推理引擎拿到模型就直接跑实际上引擎内部会做大量计算图优化其中收益最明显的就是算子融合。举个最常见的例子卷积后面通常跟批归一化和ReLU激活。原始计算图里这是三个独立的算子每个算子都要把中间结果写到内存再读回来。融合之后变成一个复合算子卷积算完直接就地做归一化和激活中间结果不落内存省掉的访存开销非常可观。内存布局也是隐蔽的提速点。深度学习框架里数据排布有NCHW和NHWC两种主流格式不同硬件对不同布局的支持偏好不一样。CPU端很多优化库偏爱NHWCGPU端则经常需要NCHW或者专门的向量化排布。如果推理引擎里没有自动做布局转换优化手动调整有时能让推理速度差出20%以上。这个没什么玄学主要看目标平台的向量指令集和数据搬运方式实测决定。显存和内存管理在服务端场景容易被忽略。推理引擎有自己的内存池机制避免每次推理都重新申请释放显存。如果发现部署后显存占用比预期高很多多半是内存池没配好或者模型在多个工作线程下各自申请了独立的内存池。合理配置工作线程数、共享内存池、限制最大batch都能把内存压下来。3.3 服务化阶段的批量与预热模型推理很少是单次调用更多时候是部署成服务接口不断接收请求。这个阶段的加速优化往往比模型层面的微调效果还猛。第一个手段是动态批处理。上线初期并发低的时候每个请求单独推理一次GPU利用率可能不到10%。把短时间内到达的请求攒起来凑成一个batch再统一推理吞吐量直接翻几倍。但要注意动态批处理会引入延迟攒batch的等待时间是有代价的需要根据业务容忍的延迟上限来设定攒批窗口比如5毫秒或10毫秒。这个参数我一般先用压测数据来定而不是拍脑袋。第二个手段是预热。很多推理引擎在第一次运行时会做懒初始化比如触发显存分配、建内核缓存第一次请求会特别慢有时候能慢到几百毫秒。线上习惯的做法是服务启动时用几条典型的假数据先跑几轮推理把该预热的内核和内存全部准备好再对外接收流量。小细节但直接决定上线初期的体验。第三个容易忽略的点是线程和CPU亲和性设置。在多核服务器上如果推理进程的线程被操作系统到处调度缓存命中率会很难看。把推理线程绑定到固定的物理核心上配合适当的线程池大小CPU场景下能稳定拿到几个百分点的提升。这类系统级优化的收益每次看着不大但长期跑在高负载服务上积少成多就是质变。4. 实战参考一个图像分类模型的压缩加速全流程前面把技术原理讲得差不多了这一节用一个典型的图像分类模型做例子走一遍完整的压缩加速流程。这个方案的每个环节我都在类似项目上实操过参数可以直接抄作业但还是要根据你自己的数据情况调整。4.1 先定基线和目标流程的第一步不是动手压缩而是把现状摸清楚。我先记录四项基线指标模型体积、单次推理耗时、线上峰值内存占用、测试集准确率。举个例子假设基线是模型体积120MBGPU单次推理12毫秒CPU单次推理80毫秒准确率92%。然后定目标。一次压缩加速项目最忌目标模糊越快越好等于没有目标。合理的做法是把目标量化成可验收的指标比如模型体积压缩到30MB以内即压掉75%GPU推理延迟降到8毫秒以内准确率下降不超过1个百分点。目标定完之后后面每一步都在往这个方向对齐不会做无用功。4.2 第一轮PTQ量化第一步直接上PTQ因为成本最低。准备一个覆盖业务典型分布的校准数据集大约500到1000条样本把训练好的FP32模型转成INT8。转换完成之后立刻做两件事一是对比体积和推理延迟验证收益符不符合预期二是在完整测试集上重新评估精度看有没有掉点。这个步骤我通常能拿到大概这样的结果体积从120MB降到30MB正好是四分之一GPU延迟从12毫秒降到7毫秒左右CPU延迟从80毫秒降到40毫秒上下。如果精度从92%掉到91.5%这个损失我完全可以接受项目可能到此就结束了。但如果精度掉到88%说明模型对量化敏感这时候可以尝试几个手段换用更强的校准算法、对敏感层做混合精度保留FP16、或者增加校准数据多样性。都试过还不行再升级到QAT。4.3 第二轮结构化剪枝微调如果量化之后精度损失可以接受但体积和速度离目标还有距离就上结构化剪枝。我在这个阶段的做法是逐层分析每个卷积层对最终精度的重要性给每个通道算一个L1范数分数然后按层设置不同的剪枝比例。对浅层也就是靠近输入的一两层剪枝比例放小一点一般10%到20%因为浅层提取的是基础特征剪太狠影响面大对深层比例可以放到30%到40%深层通道冗余相对更多。剪完之后必须微调。我习惯用迭代式先剪15%微调20个epoch精度恢复到接近基线之后再剪15%再微调。整个过程看着慢实际上比一次剪完再花大功夫把模型从崩溃边缘救回来要快得多。两轮下来模型体积能再缩小一半左右同时因为计算量下降推理延迟也会跟着再降一截。4.4 第三轮知识蒸馏兜底剪枝到一定程度之后继续加大剪枝比例的收益会急剧下降精度也开始守不住。这时候如果还嫌模型不够小我会考虑换成知识蒸馏的思路。具体操作是保留原始的大模型当教师挑选一个结构上更适合目标硬件的小模型当学生用带温度系数的软标签来训练学生模型。在图像分类这个例子里我会把原来120MB的模型换成20MB以内的轻量骨干网络作为学生结构。教师用精度最高的原始模型还是量化微调后的模型取决于哪个精度更高。学生模型训练的时候温度为4、蒸馏损失权重0.7是比较稳的起点再根据验证集表现微调。蒸馏的好处是它不碰教师模型学生模型可以按硬件需求自由设计结构灵活性比剪枝强代价是训练时间更长而且教师模型和学生模型的结构差异大会影响效果。4.5 最终验证与上线注意点所有压缩操作完成之后回到第一步的验收环节。完整对比基线指标体积、延迟、内存、精度。如果全部达标就可以进入上线前的最后一道工序把模型导出成ONNX再转成目标推理引擎的格式。这一步要重新做一遍精度验证因为引擎级的数值优化也可能引入微小误差一般以最终引擎格式在测试集上的精度为准。上线后的第一周我建议盯着三个指标平均延迟、P99延迟、精度监控。P99尤其关键线上抖动往往出现在长尾请求上如果P99持续变差排查方向包括动态批处理窗口太大、服务端线程竞争、内存池碎片化等。再就是做一个简单的回滚预案把旧版本模型和配置存档一旦发现问题可以五分钟内切回。5. 高频问题与排查思路压缩加速过程中踩过的坑我整理成一张速查表每一条都是实际项目中反复出现的问题。现象可能原因处理方向PTQ后精度大幅下降校准数据分布和业务数据不一致重新构建覆盖业务分布的校准集量化后某一类样本特别差该类样本的数值范围处于量化死区对关键层做混合精度保留FP16剪枝后微调不收敛单次剪枝比例过大改成小步迭代每次剪10%-15%再微调剪枝后收敛但精度恢复不了剪掉的通道包含关键信息降低该层剪枝比例或改用范数更大的重要性指标蒸馏学生模型学不动教师和学生模型容量差距过大插入中间尺寸的模型做两步蒸馏ONNX转换报算子不支持训练框架算子过新或过冷门改写模型结构将冷门算子拆成标准算子组合推理引擎首次请求特别慢运行时懒初始化服务启动时用假数据预热几轮加速比远低于预期模型过小导致访存成为瓶颈或引擎未做算子融合检查计算密度考虑批量调度或更换推理引擎并发升高后延迟急剧变差线程竞争或动态批处理窗口过大调整线程池大小、绑定CPU核心、缩小攒批窗口显存占用异常高内存池未共享或多线程各自分配配置统一内存池限制最大并发batch这里面有两个问题我想单独多说几句。第一个是加速比远低于预期这个问题特别容易在已经很轻量的模型上出现。当一个模型的计算量小到一定程度推理时间主要花在内存拷贝和算子调度上GPU的算力根本喂不饱这时候再压缩模型体积也快不起来正确的方向是增大batch做批处理或者合并多个模型推理请求。判断方法很简单用性能分析工具看看推理过程中访存时间和计算时间占比访存占大头就说明模型已经变成内存密集型了。第二个是ONNX转换失败的问题。转换报错几乎是每个做部署的人都必经的坎我的处理顺序是这样的先看报错信息里提到的算子和目标引擎的算子支持表确认是不是版本不匹配然后检查模型里有没有动态维度有些引擎不支持动态shape需要固定输入尺寸最后才考虑改模型结构。多数情况下模型里混入了一个训练时才需要的算子比如数据增强相关的操作导出时用接口把这些操作剥离干净问题就解决了。最后再分享两个小技巧做完一轮压缩加速之后我习惯把每一步的精度、体积、延迟记录保存下来形成一张完整的改造履历。这样做的直接好处有两个一是回归测试时能快速定位是哪一步改动导致的精度变化不用把整个流程重新跑一遍二是下次换一个新模型要做同样的优化直接拿历史数据做参考能省掉大量试错时间。另一个经验是给重要算子做单独的基准测试。多数时候我们只看整个模型的端到端延迟但真正定位性能瓶颈时必须知道每一类算子分别花了多少时间。我通常的做法是构造一个只包含某类算子的最小模型跑一遍比如只看卷积算子、只看矩阵乘算子这样就能算清楚哪一类算子值得花精力优化哪一类是理论极限没什么可压榨的。这个习惯帮我定位过好几次看似玄学的性能问题最终发现瓶颈根本不在模型本身而在数据预处理或者硬件配置上。模型压缩与推理加速这件事从来不是某一个单点技巧能解决的。量化和剪枝带来了模型层面的瘦身推理引擎和部署工程决定了瘦身后的模型能跑多快而每一步背后都是大量的实测和权衡。把基线定清楚、把目标量化掉、把每一步的收益和风险记录下来保持这个习惯这类项目做起来会顺利得多。