ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

模型优化实战指南:从量化剪枝到推理引擎的调优全攻略

模型优化实战指南:从量化剪枝到推理引擎的调优全攻略 很多朋友训练阶段一路顺风顺水模型精度刷得漂漂亮亮一说到上线部署就头疼。Model-Optimizer 这个名字乍一看像是某个深度学习框架里的梯度优化器实际上它代表的东西要宽得多——也就是说整个模型优化方向实际上指的是从训练完成到真正跑在目标硬件上这一整套工程手段包括量化、剪枝、蒸馏、推理引擎选择、内存布局调整等等。我之前在公司里接手过一个分类模型离线测试 AUC 有 0.94上线后单请求延迟 80 毫秒峰值并发一上来 CPU 直接打满最后只能限流。后来做了一轮量化加结构化剪枝再把推理引擎从原生 PyTorch 换掉延迟压到 15 毫秒以内吞吐翻了四倍多精度只掉了 0.2 个点。这篇文章就是把我在类似项目里踩过的坑、用过的方案、总结出来的经验整理出来适合算法工程师、后端开发以及所有想把模型从能跑变成跑得好的人。1. 模型优化到底在解决什么问题1.1 资源约束才是模型优化的原始驱动力很多人一提模型优化第一反应就是为了快其实快只是表象。真实驱动因素是资源约束的三角算力、内存带宽、功耗。你训好的模型可能具有几百亿参数即使不计算延迟光是把权重加载进显存就需要几百 GB 的显存空间这在生产环境根本不可行。部署机器的显卡配置往往只有训练卡的几分之一边缘场景甚至只有 CPU 和几 GB 内存模型不优化就上线等于开着一辆油老虎上路却不考虑油费跑是能跑账单会让人崩溃。另外还有一个经常被忽视的问题延迟的稳定性。在线服务要求的不仅是平均延迟低更要求 P99 延迟可控。模型结构复杂、分支多会让推理引擎难以做算子融合导致延迟毛刺明显。优化之后算子被合并、访存次数减少整体执行时间更稳定也更利于做弹性伸缩。所以模型优化本质上解决的问题其实是在满足业务效果的前提下把模型装进目标硬件、跑出可预期的延迟和吞吐。1.2 为什么叫Optimizer而不是压缩器很多人问过我这个命名问题。模型压缩只是手段之一优化器这个词涵盖的面更广。好的优化方案不是单纯把模型变小而是要让整个推理链路协同起来模型变小只是给了你腾挪的空间推理引擎能不能利用好这种小算子融合做不做内存复用够不够显存分配策略是否合理这都属于优化范畴。举个很直观的例子同一个量化后的模型在 PyTorch 的 eager mode 下可能跑 30 毫秒放到经过编译器优化的推理引擎里可能只要 12 毫秒。模型本身没有任何变化优化效果完全来自框架层的计算调度。所以模型优化的内涵包括模型压缩、算子和计算图的优化、运行时调度优化、硬件特性适配甚至还包括服务端动态批处理策略。1.3 优化目标的三层拆解做优化不能眉毛胡子一把抓我一般把目标拆成三层。第一层是容量优化也就是模型占用的存储空间能不能降下来目标是装得下。第二层是延迟优化也就是单次推理的时间能不能降下来目标是跑得快。第三层是吞吐优化也就是单位时间能处理的请求数能不能提升目标是用得省。这三层目标常常互相制约。比如无脑把模型量化成 INT4确实能装得下但精度可能掉到业务不可接受或者为了极限延迟用 TensorRT 写成高度特化的引擎但此时模型灵活性变差后续想微调或者换输入尺寸都很麻烦。真正好的优化是在约束条件下做平衡先明确业务底线在哪里再去调整优化策略而不是一味追求单项指标。2. 量化把精度压力转移到硬件2.1 量化到底动了什么量化是最直观也最容易上手的优化手段核心思想很简单模型权重和激活值原本用 FP32 表示每个数占 4 字节现在改用 INT8 表示每个数占 1 字节。模型体积直接缩到四分之一推理时的矩阵乘运算因为访存压力骤降速度自然提升。量化过程本质上是一个映射问题。原始浮点数值分布在一个范围内量化后整数也有一个范围比如 INT8 是从 -128 到 127。我们需要找到一个缩放比例 scale 和一个零点 zero_point把浮点值映射到整数区间然后反推回来的值尽可能接近原始值。实际操作时权重量化一般用逐张量或者逐通道的 scale激活量化则需要在推理时根据实际数据动态统计或者用校准集提前统计。这里有个很多新手容易误会的点量化不是让模型重新学习而是把已经训练好的数值重新编码。虽然公式看起来简单但对分布在边缘的离群值极其敏感。比如某些层的权重有极少数的值特别大如果强行用整体 min-max 范围做映射绝大部分正常数值都会被压到很小的整数区间里精度损失惨重。这也是为什么后来出现很多改进量化算法本质上都是在解决离群值怎么处理的问题。2.2 PTQ 和 QAT 怎么选量化方案主要分两大类训练后量化PTQ和量化感知训练QAT。PTQ 不需要重新训练只需要准备少量校准数据在校准过程中统计每层激活值的 min-max 或者百分位范围整个过程可能只需要几分钟。QAT 则是在训练过程中模拟量化误差让模型参数逐步适应低比特表示精度通常更好但代价是训练时间和资源消耗都更大。我的实践经验是如果模型在 FP32 下精度有较大余量也就是业务指标距离红线还很远优先用 PTQ而且可以做得比较激进比如从 INT8 尝试到 INT4如果模型精度本身就在边缘或者任务对细节特别敏感比如目标检测的小目标、语义分割的边缘区域那 PTQ 大概率会翻车此时 QAT 更稳。另外值得说一句量化本身是一种正则化有时量化后的模型在某些指标上反而有小幅度提升因为它减掉了一些微弱噪声。但别把这个当作常态绝大多数情况下量化是负向精度影响只是幅度有大有小。2.3 calibration 和数据集选择的细节校准集的选择直接决定 PTQ 的效果这是我最想强调的一点。你用 COCO 的图片做校准线上跑的是工业场景的监控视频那激活值的分布对不上量化 scale 就是偏的。实际操作中我应该从线上随机采样几千条真实请求数据作为校准集覆盖典型时段和典型场景。数量不需要多几百到一两千条足够关键是分布要和真实数据保持一致。还有一个容易被忽略的细节是校准集的预处理规范。校准数据进入模型前也要做和训练一致的归一化、resize否则统计出来的激活分布完全错位。我有一次量化后模型精度掉了 5 个多点查了半天发现只是校准数据没有走训练时的预处理 pipeline修正之后精度立刻恢复正常。2.4 混合精度与离群值处理纯 INT8 量化在某些模型上不够稳所以工程上更常见的是混合精度方案。原理是不是所有层的敏感度都一样有些层量化后精度崩盘有些层量化后毫无影响。我们通过逐层看量化误差或者用具体评判指标去评估敏感度挑出容易崩的层让这些层保持 FP16 或者 FP32其余层保持 INT8。敏感度的分析其实可以自动做。我可以遍历每个层只量化这一层其他层保持高精度跑一遍验证集记录指标变化幅度。这个步骤耗时但非常值得尤其对于大模型或者多任务模型。我自己遇到过的一种情况是某一层只有几个通道对量化特别敏感换成只对这一层做通道维度的混合精度就解决了问题整体体积几乎没增加。3. 剪枝删掉模型里不干活的权重3.1 结构化剪枝与非结构化剪枝剪枝的道理很朴素模型里很多权重本来就接近于零对输出没什么影响把这些冗余权重删掉就能减体积、提速度。但真正做起来结构化和非结构化剪枝的差别非常大。非结构化剪枝是把权重的绝对值按阈值剪掉权重矩阵会变得稀疏但因为稀疏点位置随机底层计算库无法高效利用这种稀疏性。在普通 GPU 上稀疏矩阵的计算效率可能比稠密矩阵还慢因为要额外处理索引开销。所以非结构化剪枝往往只在存储上有效果推理速度上很难体现优势。结构化剪枝则是以整个滤波器、通道或者注意力头为粒度进行裁剪。剪掉一个通道意味着上层特征图少一个分支计算量实实在在地减少硬件也能高效执行。缺点是精度损失通常比非结构化剪枝大需要配合微调恢复。实际业务中我绝大多数情况会优先考虑结构化剪枝至少它对延迟的改善是实打实的。3.2 剪枝的层级与流程实操剪枝流程我建议遵循定位冗余 → 评估敏感度 → 执行剪枝 → 微调恢复 → 验证收益。定位冗余不是看权重的绝对值大小就完了更好的方式是通过统计激活值的分布来判断通道贡献度。如果一个通道在大量真实样本上的激活值输出都接近零说明这个通道基本被养成了剪掉它对后续层的影响相对较小。具体到操作层面对于卷积网络我可以对每个输出通道的权重计算 L1/L2 范数然后按阈值筛掉小范数通道。对于 Transformer可以按注意力头对最终 loss 的影响来剪也可以观察注意力分数矩阵是否有大量均匀或者沉寂的模式。剪完之后必须做短时间的微调让剩余通道尽量弥补被剪通道的信息损失。3.3 大模型时代的剪枝新思路2023 年以来大模型的剪枝思路有了一些明显变化。传统剪枝关注单个权重的重要性但大模型更关注的是消除某一层造成的连带影响。比如 SparseGPT 和 Wanda 这类方法会基于 Hessian 矩阵或者输入激活值来评估权重重要性本质上是考虑到了层与层之间的连锁反应。这类方法能在几乎没有微调的情况下保持不错的效果优势在于省去了昂贵的重训练过程。不过实操时我发现大模型剪枝依然很依赖后续的低秩适配器微调。你可以用一次性剪枝把模型缩小然后训练一个小适配器恢复性能这比全量训练便宜得多但也不能指望完全无损。如果你做的任务是垂直领域建议剪枝后用任务数据做一轮针对性微调精度恢复效果会比在大规模通用语料上微调更明显。4. 蒸馏用训练换推理效率4.1 蒸馏的本质是什么知识蒸馏的思路很有趣与其直接压缩一个大模型不如训练一个小模型去模仿大模型的输出。传统训练只学习硬标签而蒸馏还要学习大模型输出的软概率分布也就是各类别的平滑程度。软标签里包含了大模型总结出来的类间相似性信息比如一张猫的图片模型可能输出 0.8 的概率是猫、0.15 的概率是狗这个猫和狗有点像的知识是硬标签给不了的。蒸馏的公式一般用 KL 散度来约束学生模型和教师模型的输出分布训练时控制硬标签损失和软标签损失的权重。温度参数 T 用来软化概率分布T 越大分布越平滑类别间的关系越清晰。实际操作中T 的选择需要实验一般来说分类任务从 3 到 8 比较常见太低起不到迁移知识的效果太高会把噪声也一起学进来。4.2 蒸馏在工程落地中的变体蒸馏不止限于是分类概率分布。在目标检测任务中教师模型的中间特征图同样具有指导价值因为特征图里隐含了空间语义信息在细粒度匹配任务中可以蒸馏 embedding 的距离关系在生成任务中可以蒸馏 logits 甚至中间层的 KV 序列。这些本质上都是让学生的中间表示去模仿老师只是监督信号换成了更适合业务的形式。在优化任务里蒸馏经常和量化配合使用先蒸馏一个更小更紧凑的学生模型再对这个小模型做量化压缩可以做到双重收益。也可以反过来教师是精度高的原模型学生是一个量化后的模型通过蒸馏来补偿量化误差这就是量化感知蒸馏。我认为蒸馏最大的价值是可以主动设计学生结构而不只是被动压缩这意味着你可以在设计阶段就把部署硬件考虑进去。4.3 蒸馏的陷阱与资源思考蒸馏最大的陷阱是学生模型结构选得不好不管怎么调都学不到老师的知识。常见误区是让学生模型过小或者结构形态差异太大导致表征空间完全对不齐。建议先做一个中等规模的学生模型试试水确认蒸馏有效后再逐步缩小不要一开始就定一个激进的极小结构折腾半天发现是容量不够而不是蒸馏方法不行。另外蒸馏需要真实标签和教师预测一起参与训练数据处理要比普通训练复杂一些。教师模型的推理成本在大模型场景下不低所以要提前把教师对训练集的 logits 或者特征缓存下来避免每一轮都重新算。这个细节在业务项目里非常有用因为教师模型往往是几十倍于学生模型的体积一次性缓存能省下大量训练时间。5. 推理引擎与优化工具链5.1 模型优化不只是改模型模型本身优化得再好如果推理引擎不配合效果也会大打折扣。推理引擎做的事情包括计算图优化、算子融合、内存复用、内核自动调优等。算子融合是把多个相邻小算子合并成一个大算子减少内核启动开销和中间结果的访存。打个比方你做饭洗菜、切菜、炒菜分成三个人接力每次交接都要停一下如果一个人从头做到尾中间流程顺畅很多。具体来说PyTorch 默认的 eager mode 是按算子逐个执行的很多临时张量需要在显存里进进出出。而通过 torch.compile 或者把模型导出到 ONNX 之后再经过 ONNX Runtime 或 TensorRT 处理计算图被优化成更紧凑的形式这种提升在某些模型上可以达到数倍。之前我在一个文本分类模型上实测ONNX Runtime 加动态量化比 PyTorch eager mode 快了一倍多而模型权重本身没有变化。5.2 主流优化工具纵向对比我按实际项目经验把常用工具排一下。ONNX Runtime 是通用性最强的选择支持 CPU、GPU、各种加速芯片算子覆盖全适合作为第一步优化方案。TensorRT 是 NVIDIA GPU 上的性能天花板配置但算子支持收敛转换过程经常会遇到算子不被支持的问题需要回退和替换。OpenVINO 在 Intel CPU 和核显上优化效果很好边缘部署常用。llama.cpp 和 vLLM 这类工具则主要面向大模型场景。选择工具之前我建议先明确三件事部署硬件是什么、模型算子复杂度如何、是否需要动态输入。硬件决定工具选择范围算子复杂度决定转换难度动态输入决定你能否使用静态优化策略。就我的经验简单分类模型和复杂检测模型的调优路径完全不同前者可能十分钟就搞定 ONNX 导出加量化后者可能需要反复处理各种不兼容算子。5.3 显存优化和 KV Cache 技术大模型推理还有一个显存大头是 KV Cache。Transformer 生成阶段每生成一个 token 都要重复计算此前所有 token 的 key 和 value为了省算力这些中间结果会被缓存起来这就是 KV Cache。显存占用会随着序列长度和并发数线性增长所以各种推理框架都在 KV Cache 上做文章比如 PageAttention 把缓存分成小块管理减少碎片比如量化 KV Cache把缓存也压缩到 INT8。做并发服务时动态批处理也很重要连续批处理指的是当一个请求生成完成后不再等整个批次结束而是立刻插入新请求。这个策略极大提升了 GPU 利用率。如果你在做 LLM 服务没有用 continuous batching 和优化的显存管理器那吞吐大概率是不达标的。6. 一个端到端的模型优化案例6.1 优化前的情况摸底我以曾经做过的一个中文文本分类模型为例完整走一遍优化流程。模型是 BERT-base 规模的 12 层 Transformer权重约 400 MB单条文本推理延迟约 45 毫秒部署在 8 核 CPU 机器上目标是延迟降到 20 毫秒以内精度 F1 从 0.91 降到不低于 0.89。第一步没有直接动手优化而是先做了 profiling。用性能分析工具看每一层耗时结果发现主要的耗时集中在自注意力机制的多个小矩阵乘和层归一化操作上。这个信息很重要它指导了后续优化优先级如果瓶颈是矩阵乘量化会立竿见影因为访存量大幅减少如果瓶颈是某些算子调度开销则先做算子融合效果更好。6.2 分阶段优化的具体执行我按照先用通用手段再上定制手段的顺序分四步执行。第一步把模型导出到 ONNX并做算子融合延迟从 45 毫秒先降到 32 毫秒。这个阶段没有动用任何压缩手段纯粹是计算图优化收益。第二步做 INT8 动态量化此时延迟降到了 22 毫秒左右精度 F1 为 0.903损失可以接受。第三步做结构化剪枝去除注意力头数和中间层维度的部分冗余维度。这一步减掉约 25% 的参数量用少量训练数据做了微调恢复F1 回到 0.901延迟降到 17 毫秒。第四步对剪枝后的模型再用 ONNX Runtime 做一次 kernel 调优并启用多线程配置最终延迟稳定在 15 到 16 毫秒体积降到约原始模型的五分之一。6.3 用数据判断每一步是否值得每一步优化之后都要重新评估收益和风险。我给读者提供一个简易的决策表模板用来判断这一阶段要不要继续做优化阶段关键评估指标达标阈值建议不达标的处理方式计算图优化延迟改善比例至少提升 20%检查是否算子不兼容限制融合量化精度下降幅度小于 2% 业务指标尝试混合精度或 QAT剪枝参数量减少和吞吐提升减少 20% 以上才有意义减少剪枝力度或换粒度推理引擎调优P99 延迟和吞吐P99 不超过均值的 2 倍检查线程池和内存分配策略我见过很多团队一上来直接做最激进的量化加剪枝后面又花几周时间修精度问题反而不如先做计算图优化效果好、风险低、收益立竿见影。优化是一个增量过程每一步都要用小规模验证集做回归测试而不是把全部改动一股脑堆上去。7. 常见问题与排查技巧实录7.1 量化后精度大幅下降怎么办量化后精度大幅下降我第一个怀疑的是激活值分布里的离群点没有处理干净。你可以在目标层上输出激活分布看是否存在少数极大或极小的值把 range 撑爆。如果是这样可以尝试 per-channel 量化、设置百分位范围而不是 min-max或者用 KL 散度校准方式寻找最优截断位置。另外也要检查是不是某些层天然不适合 INT8比如涉及动态范围很大的连续数值预测层此时考虑对这些层使用更高精度。还有一个经常出问题的点是 BatchNorm 与量化之间的纠缠。如果模型里有 BatchNorm需要注意在量化前把 BatchNorm 的参数吸收到卷积层中否则推理阶段的归一化会因为数值偏移导致误差放大。PyTorch 的量化工具通常会处理这件事但导出的模型有时不会自动做需要手动检查。7.2 剪枝后模型变快但精度回不来剪枝后精度回不来最核心的问题往往是剪枝粒度太大或者剪掉的通道并非真正冗余。可以降低修剪比例试试比如从原来的 30% 降到 20%。同时我强烈建议微调时使用较低的学习率大约是原来预训练阶段的十分之一甚至更低因为剪枝后的模型参数已经接近一个良好局部最优过大的学习率会把参数带偏。如果模型微调后仍然起不来可以考虑在剪枝过程中做迭代剪枝也就是剪一点微调一点不要一次性剪到位。虽然耗时长一些但精度更稳。我在 BERT 类模型上验证过迭代剪枝比一次性剪枝在同等剪枝率下 F1 高出约一个点。7.3 推理引擎转换失败和性能不达预期推理引擎转换失败是常见问题尤其是 TensorRT 转换复杂模型时经常遇到算子不支持。我的处理思路是先通过工具把模型里的算子类型打印出来比对官方文档的支持列表找出不支持的算子然后改写模型结构用等价算子组合替换。比如某些自定义激活函数可以改写成标准激活函数加数学运算的形式问题就能解决。如果转换成功但性能不达预期先检查输入精度和形状是否符合引擎预期。动态形状的输入会让引擎采用保守优化策略导致性能不如静态形状。如果业务允许建议限定输入的 batch size 和序列长度的档位比如只支持 1、4、8 三个长度档位让引擎在每种档位下做专门优化通常能显著提升性能。7.4 优化上线后的持续监控最后一条经验也是我吃过亏之后才养成的习惯优化模型上线后不能只看延迟和吞吐还要持续监控精度指标。很多优化带来的问题是数据分布变化后慢慢暴露的比如量化校准集来自旧分布线上数据漂移后激活值分布偏移量化误差会被放大。建议在监控告警里加上一个特征漂移检测模块定期对比线上输入数据和校准集的 embedding 分布漂移超过阈值就触发重新校准或者回滚。此外每次优化都建议把优化前后的模型输出差异记录在一个离线评测集上这样出了线上事故可以快速定位是优化问题还是数据问题。我之前遇到过一次线上指标下跌排查后发现是某条业务链路传入了异常长的文本触发到了量化敏感区域。事后我专门针对长文本追加了校准样本并把这类输入做了长度截断问题才解决。我个人在实际操作中的体会是模型优化没有银弹必须针对具体模型和具体业务做针对性组合。先做 profiling 找到瓶颈然后再决定用哪几种手段组合每一步都在线验证这是最稳妥的路径。这套方法我后续也会持续更新比如最新的大模型投机采样、推测解码这些推理优化技术已经超出了传统模型压缩的范畴是在算法调度层面做文章。优化这件事值得持续投入。
返回列表