ARTICLE DETAIL

资讯详情

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

Model-Optimizer实战:量化、剪枝与算子融合的模型部署优化指南

Model-Optimizer实战:量化、剪枝与算子融合的模型部署优化指南 模型优化这件事很多人第一反应是调参——学习率、batch size、权重衰减翻来覆去地试。但真正在生产环境里跑过推理服务的人都知道模型能不能上线往往不取决于你训练得多好而取决于它在目标硬件上跑得多快、占多少显存、精度掉没掉。Model-Optimizer 这个方向之所以值得单独拿出来聊就是因为它解决的正是训练完之后怎么办这一段最容易被忽视、却最影响落地效果的问题。我自己在几个实际项目里做过量化、剪枝、算子融合这些优化工作踩过的坑不算少。有些是工具链本身的限制有些是对精度损失估计不足还有些纯粹是没搞清楚优化目标和部署目标之间的对应关系。这篇文章我会把 Model-Optimizer 涉及的核心技术点拆开讲包括它到底优化什么、每种优化手段的底层逻辑、实际操作中怎么选型、以及那些文档里不会写的经验教训。不管你是刚接触模型部署的新手还是已经做过几轮优化的老手应该都能从中找到对自己有用的部分。1. Model-Optimizer 到底在优化什么1.1 从训练完成到上线服务之间的空白地带一个模型训练完了loss 曲线很漂亮验证集指标也达标然后呢直接拿去做推理服务大概率会遇到几个问题延迟太高、显存不够、吞吐上不去。训练框架关心的是梯度能不能正确回传、参数能不能收敛它并不关心推理时算子怎么调度、内存怎么复用。这两者之间的 gap就是 Model-Optimizer 要填的。具体来说Model-Optimizer 的工作范围通常包括几个层面。最上层是计算图层面的优化比如算子融合、常量折叠、死代码消除。中间层是数值精度层面的优化比如量化、混合精度。最底层是内存和调度层面的优化比如内存池化、kernel 自动调优。这三个层面不是孤立的很多时候需要配合使用才能达到最佳效果。我见过不少团队的做法是训练用一套代码部署用另一套代码中间靠一个转换脚本硬接。这种做法在模型结构简单的时候还能凑合一旦遇到动态控制流、自定义算子、或者复杂的后处理逻辑转换脚本就会变得极其脆弱。Model-Optimizer 的思路是把这个转换过程系统化、可配置化让你能针对不同的部署目标做不同的优化组合。1.2 优化目标之间的三角权衡做模型优化本质上是在三个维度之间找平衡精度、速度、资源占用。这三者构成一个不可能三角——你不可能同时让三者都达到最优。举个具体的例子。假设你有一个 BERT-base 模型原始 FP32 精度下在某个 GPU 上推理延迟是 20ms显存占用 400MB。如果你做 INT8 量化延迟可能降到 8ms显存降到 120MB但精度可能会掉 0.5 到 2 个百分点。如果你做结构化剪枝把注意力头数减少一半延迟可能降到 12ms显存降到 250MB精度掉得更多可能 3 到 5 个百分点。如果你什么都不做精度最高但延迟和显存都下不来。所以 Model-Optimizer 的核心价值不是让模型变快而是让你能可控地、可预测地在三角之间做取舍。可控意味着你知道每种优化手段会带来多少精度损失、多少速度提升可预测意味着你可以在优化之前就估算出大致的效果而不是优化完了才发现精度崩了。提示在做任何优化之前先明确你的瓶颈到底在哪里。是延迟敏感还是吞吐敏感是显存受限还是算力受限不同的瓶颈对应完全不同的优化策略。延迟敏感的场景量化通常比剪枝更有效吞吐敏感的场景算子融合和 batch 调度更关键。1.3 为什么不能只靠换更快的硬件来解决有人可能会说模型慢就换更好的 GPU 呗费那劲优化干嘛。这话在个人项目里可能成立但在生产环境里往往行不通。原因有几个成本、供应链、以及边缘部署的硬约束。成本方面高端 GPU 的价格可能是中端的好几倍但性能提升未必有那么多。对于大规模部署来说单卡成本乘以卡的数量差距会非常惊人。供应链方面特定型号的硬件不一定随时能买到优化过的模型可以在更多型号上运行降低对特定硬件的依赖。边缘部署就更不用说了手机、嵌入式设备、IoT 终端算力和内存都是硬约束不优化根本跑不起来。而且从工程角度看优化模型带来的收益是乘法效应。你优化一次所有部署实例都受益。换硬件带来的收益是加法效应你得把每个实例都换掉才能看到效果。在大规模场景下前者的投入产出比通常更高。2. 量化最常用但也最容易翻车的优化手段2.1 从 FP32 到 INT8 到底发生了什么量化的本质是用更少的比特位来表示数值。FP32 用 32 位表示一个浮点数INT8 用 8 位表示一个整数。从信息量上看INT8 能表示的数值范围从 FP32 的约 3.4e38 降到了 127精度从约 7 位有效数字降到了整数精度。但神经网络有个特点它对数值的精确度其实没那么敏感对数值的相对大小关系更敏感。这就是量化能 work 的根本原因。具体做量化的时候核心是找到一个映射关系把 FP32 的数值范围映射到 INT8 的 [-128, 127] 区间。最常见的是线性量化scale (max_fp32 - min_fp32) / (max_int8 - min_int8) zero_point round(-min_fp32 / scale) min_int8 quantized round(fp32_value / scale) zero_point这里的scale和zero_point就是量化的关键参数。scale决定了量化的粒度zero_point决定了零点对齐方式。对于对称量化zero_point固定为 0适合权重这种分布比较对称的数据对于非对称量化zero_point可以调整适合激活值这种分布可能偏移的数据。实际做的时候权重的量化通常用对称量化因为权重分布一般以 0 为中心。激活值的量化用非对称量化更合适因为 ReLU 之后的激活值都是非负的分布明显偏移。这个细节很多教程不会讲但实际做的时候如果搞反了精度损失会明显增大。2.2 训练后量化与量化感知训练的选择逻辑量化分两条路线训练后量化PTQ和量化感知训练QAT。PTQ 是拿训练好的 FP32 模型直接量化不需要重新训练QAT 是在训练过程中模拟量化误差让模型学会适应量化。PTQ 的优点是快通常几分钟到几小时就能完成不需要标注数据不需要重新训练。缺点是精度损失可能比较大尤其是对于小模型或者对量化敏感的模型。QAT 的优点是精度保持得好通常能做到和 FP32 几乎无差别。缺点是需要重新训练需要标注数据训练时间可能和原始训练差不多。怎么选我的经验是先试 PTQ如果精度损失在可接受范围内比如掉点不超过 1%就直接用 PTQ。如果 PTQ 掉点太多再考虑 QAT。但 QAT 也不是万能的有些模型结构本身就不适合量化比如包含大量小数值运算的模型QAT 也救不回来。还有一个折中方案叫部分量化只量化对精度不敏感的部分比如只量化权重不量化激活值或者只量化某些层。这种方案在 Model-Optimizer 里通常可以通过配置来实现灵活性比全量化或全不量化高很多。2.3 量化实操中的精度校准技巧做 PTQ 的时候最关键的一步是校准calibration。校准的目的是用一批代表性数据跑一遍模型统计每层激活值的分布从而确定量化的scale和zero_point。校准数据的质量和数量直接影响量化精度。我踩过的一个坑是用训练集的一小部分做校准结果量化后模型在测试集上掉点严重。后来发现是因为训练集和测试集的分布有差异校准数据没有覆盖到测试集里的某些模式。换成从测试集里抽一部分做校准后精度明显改善。这个经验告诉我校准数据一定要有代表性最好能覆盖实际部署时可能遇到的各种输入。校准的样本数量也有讲究。太少比如几十个统计不充分太多比如几万个浪费时间且收益递减。我的经验是 500 到 1000 个样本通常就够了但具体要看模型的复杂度和输入数据的多样性。对于输入变化很大的模型比如目标检测可能需要更多样本。校准算法本身也有选择。最简单的是MinMax直接取最大最小值好一点的是Moving Average MinMax用滑动平均平滑异常值更好的是Entropy或Percentile用信息熵或百分位来截断异常值。Model-Optimizer 通常支持多种校准算法实际用的时候建议都试一下选精度最好的那个。校准算法适用场景优点缺点MinMax分布均匀的数据简单快速对异常值敏感Moving Average MinMax有噪声的数据平滑异常值需要调窗口大小Entropy分布复杂的数据精度保持好计算稍慢Percentile有长尾的数据鲁棒性强需要调百分位3. 剪枝与稀疏化让模型瘦下来的正确姿势3.1 结构化剪枝与非结构化剪枝的本质区别剪枝的核心思想是去掉模型中不重要的部分。但不重要怎么定义、怎么去掉就有很多讲究了。最粗的分类是结构化剪枝和非结构化剪枝。非结构化剪枝是把权重矩阵里的一些元素置零不改变矩阵的形状。比如一个 1000x1000 的权重矩阵剪掉 90% 的元素变成稀疏矩阵但矩阵还是 1000x1000。这种剪枝的优点是精度损失小因为可以精细地选择哪些元素不重要。缺点是实际加速效果有限因为通用硬件对稀疏矩阵的支持不好除非稀疏度非常高比如 95% 以上否则计算时间不会明显减少。结构化剪枝是直接去掉整个通道、整个注意力头、整个层。比如把某个卷积层的输出通道从 256 减到 128或者把 Transformer 的注意力头从 12 个减到 6 个。这种剪枝的优点是实际加速效果明显因为矩阵形状真的变小了。缺点是精度损失可能更大因为去掉的是整个结构单元粒度更粗。Model-Optimizer 通常两种都支持但实际部署时我更推荐结构化剪枝。原因很简单非结构化剪枝的加速效果太依赖硬件和推理引擎的支持通用性差。结构化剪枝虽然精度损失大一点但加速效果是实打实的不依赖特殊硬件。3.2 重要性评分怎么判断哪些部分该剪剪枝的关键是找到不重要的部分。怎么定义重要性常见的方法有几种基于权重幅值权重绝对值小的认为不重要。这是最简单的方法计算成本低但效果一般。因为权重幅值小不一定代表贡献小有些小权重可能对最终输出影响很大。基于梯度梯度小的认为不重要。梯度反映了参数对 loss 的影响程度比单纯看幅值更准确。但需要计算梯度成本更高。基于激活值激活值小的认为不重要。这个方法直接看前向传播的贡献通常比看权重更准确。但需要跑数据统计激活值分布。基于二阶信息用 Hessian 矩阵或 Fisher 信息矩阵来评估重要性。这是最准确的方法但计算成本极高通常只用于小模型或研究场景。实际用的时候我一般先用基于权重幅值的方法快速筛一遍再用基于激活值的方法精调。二阶方法除非精度要求极高否则不太实用。Model-Optimizer 通常会提供多种重要性评分方法可以配置选择。还有一个经验是逐层剪枝比全局剪枝更稳。全局剪枝是设定一个总稀疏度然后所有层一起剪。逐层剪枝是每层单独设定稀疏度。全局剪枝的问题是不同的层对稀疏度的敏感度不同统一剪可能导致某些层被过度剪枝。逐层剪枝虽然麻烦一点但精度控制更精细。3.3 剪枝后的微调策略与恢复训练剪枝之后通常需要微调来恢复精度。微调的策略有几个关键点学习率要小。剪枝后的模型已经接近一个局部最优学习率太大会破坏已有的知识。通常用原始训练学习率的 1/10 到 1/100。训练轮数不用太多。微调不是重新训练通常几个 epoch 就够了。太多轮反而可能过拟合。数据要覆盖全面。微调数据要能代表实际部署时的输入分布否则微调后的模型可能在实际场景中表现不佳。可以逐步剪枝。不要一次性剪到目标稀疏度而是分多次剪每次剪一点然后微调。这种方法叫迭代剪枝精度保持通常比一次性剪枝好很多。比如目标稀疏度是 80%可以分 4 次每次剪 20%每次剪完微调几个 epoch。我做过一个对比实验一次性剪枝到 80% 稀疏度精度掉了 5 个百分点迭代剪枝分 4 次每次剪 20%精度只掉了 1.5 个百分点。虽然迭代剪枝总时间更长但精度优势明显。4. 算子融合与计算图优化不损失精度的加速4.1 算子融合为什么能加速算子融合是把多个连续的小算子合并成一个大的算子。比如Conv BatchNorm ReLU这三个算子在推理时可以融合成一个算子。融合之后中间结果不需要写回内存再读出来减少了内存访问次数也减少了 kernel 启动的开销。为什么内存访问这么重要因为现代 GPU 的算力增长速度远快于内存带宽增长速度很多算子其实是内存带宽受限而不是算力受限。也就是说GPU 的计算单元在等数据从内存搬过来而不是在忙着计算。算子融合减少了内存访问直接缓解了这个瓶颈。以Conv BatchNorm ReLU为例不融合的时候需要三次内存读写Conv 写输出、BatchNorm 读输入写输出、ReLU 读输入写输出。融合之后只需要一次Conv 计算完直接做 BatchNorm 和 ReLU然后写一次输出。内存访问量减少了约 2/3加速效果非常明显。而且 BatchNorm 在推理时其实是一个线性变换可以完全折叠进 Conv 的权重里。具体来说BatchNorm 的公式是y gamma * (x - mean) / sqrt(var eps) beta可以重写成y scale * x shift的形式其中scale gamma / sqrt(var eps)shift beta - gamma * mean / sqrt(var eps)。然后 Conv 的权重W和偏置b可以更新为W W * scaleb (b - mean) * scale shift。这样 BatchNorm 就完全消失了只剩下 Conv 和 ReLU。4.2 计算图层面的死代码消除与常量折叠除了算子融合计算图层面还有很多优化可以做。死代码消除是去掉对最终输出没有贡献的节点。比如训练时用的辅助分支、调试用的打印节点、被条件分支永远走不到的分支。这些节点在推理时是多余的去掉它们能减少计算量。常量折叠是在编译期计算那些输入固定的节点。比如某个节点的输入全是常量那它的输出也是常量可以直接算好存起来不用在运行时再算一遍。公共子表达式消除是找出重复计算的表达式只算一次然后复用。比如两个分支都用了同一个中间结果可以只算一次。内存复用是让不同的张量共享同一块内存。因为很多张量的生命周期不重叠可以复用内存减少峰值占用。这些优化在 Model-Optimizer 里通常是自动做的但你需要知道它们的存在才能在优化效果不理想时判断是哪里出了问题。比如如果发现某个算子的耗时异常高可能是它没有被融合如果发现显存占用比预期高可能是内存复用没有生效。4.3 针对不同推理引擎的图优化差异不同的推理引擎对计算图的理解和优化能力不同。TensorRT 对 NVIDIA GPU 的优化最深入支持大量的算子融合和 kernel 自动调优。ONNX Runtime 的跨平台性好支持多种硬件后端但优化深度可能不如 TensorRT。OpenVINO 针对 Intel 硬件优化在 CPU 上表现很好。TFLite 针对移动端优化在 ARM 芯片上效率高。Model-Optimizer 的价值在于它提供了一个统一的优化层让你可以用同一套配置生成针对不同引擎的优化模型。但要注意不同引擎支持的算子集不同有些优化在某个引擎上能做在另一个引擎上做不了。比如 TensorRT 支持 INT8 量化但某些自定义算子可能不支持ONNX Runtime 支持更广泛的算子但量化支持可能不如 TensorRT 成熟。实际选型的时候我的建议是先确定部署硬件再选推理引擎最后做优化。不要反过来先做优化再找引擎那样很可能白忙一场。5. 优化效果的评估与验证方法5.1 精度评估不能只看一个指标优化之后怎么判断模型还能不能用很多人只看一个 top-1 accuracy 或者 mAP这其实不够。不同的优化手段对不同指标的影响不同只看一个指标可能漏掉问题。比如量化对分类模型的 top-1 accuracy 影响可能不大但对 top-5 accuracy 或者某些类别的召回率影响可能很大。剪枝对整体 mAP 影响可能不大但对小目标的检测精度影响可能很大。所以评估的时候要看多个指标最好能分类别、分场景地看。还有一个重要的评估维度是一致性。优化后的模型和原始模型的输出应该尽可能一致。可以计算两个模型输出的 KL 散度或者余弦相似度如果差异太大说明优化引入了不可忽略的偏差。我通常的做法是准备一个评估集同时跑原始模型和优化模型对比多个指标并且画出误差分布图。如果误差分布是均匀的说明优化比较稳定如果某些样本误差特别大说明优化在这些样本上失效了需要针对性分析。5.2 性能测试的常见陷阱性能测试看起来简单跑一下计时就行了但实际上有很多陷阱。预热不够。GPU 在刚启动时频率可能没跑满第一次推理通常比后续慢。所以测试前要预热几次等频率稳定了再测。同步问题。GPU 操作是异步的如果你在 kernel 还没执行完就计时结束测出来的时间会偏小。需要确保计时前所有操作都完成了。批大小影响。不同的 batch size 下优化效果可能完全不同。量化在小 batch 下加速明显在大 batch 下可能不明显。剪枝在大 batch 下加速明显在小 batch 下可能被其他开销掩盖。所以测试时要覆盖实际部署时可能用到的 batch size。内存碎片。长时间运行后内存可能碎片化导致性能下降。测试时要模拟长时间运行的情况。热节流。GPU 长时间高负载运行会发热降频性能会下降。测试时要监控温度确保没有热节流。这些陷阱我都踩过尤其是预热和同步问题刚开始做性能测试的时候经常测出虚高的性能数据实际部署时才发现达不到。5.3 端到端延迟与吞吐的权衡最后说一下延迟和吞吐的权衡。延迟是单个请求的处理时间吞吐是单位时间内能处理的请求数。这两个指标通常是矛盾的增大 batch size 能提高吞吐但会增加延迟减小 batch size 能降低延迟但会降低吞吐。怎么选取决于你的应用场景。如果是实时交互场景比如语音助手延迟优先batch size 要小。如果是离线批处理场景比如视频分析吞吐优先batch size 可以大。如果是混合场景可能需要动态 batch 或者多实例部署。Model-Optimizer 通常不直接控制 batch size但它会影响不同 batch size 下的性能表现。比如量化后的模型在小 batch 下延迟优势明显在大 batch 下可能被计算密度掩盖。所以做优化的时候要结合实际的 batch size 来评估效果。场景类型优先指标推荐 batch size优化重点实时交互延迟1-4量化、算子融合在线服务平衡8-32量化、剪枝离线批处理吞吐64剪枝、算子融合边缘部署资源占用1-2量化、剪枝6. 实际项目中的优化决策链路6.1 从需求到方案的推导过程实际做优化的时候不能上来就选工具、调参数而是要先理清需求。我通常按这个链路来推导第一步明确部署目标。是云端 GPU 还是边缘设备是 CPU 还是专用加速器不同的目标对应完全不同的优化策略。第二步明确性能约束。延迟上限是多少吞吐下限是多少显存上限是多少这些约束决定了优化的空间有多大。第三步明确精度底线。精度最多能掉多少哪些指标不能掉这决定了优化的激进程度。第四步分析瓶颈。用 profiling 工具找出模型的时间主要花在哪里是某个算子特别慢还是内存带宽受限还是 kernel 启动开销大。不同瓶颈对应不同的优化手段。第五步选择优化组合。根据前面的分析选择合适的优化手段组合。通常先做算子融合无损再做量化有损但可控最后考虑剪枝有损且需要微调。第六步迭代验证。每做一步优化都验证精度和性能确保没有偏离目标。如果某一步效果不好回退重新选择。这个链路看起来简单但实际做的时候每一步都可能遇到意外。比如 profiling 发现瓶颈在某个自定义算子上但这个算子不支持量化那就只能想别的办法。或者量化后发现精度掉太多QAT 又没时间做那就只能降低量化范围。6.2 一个真实案例的完整优化过程我拿一个实际做过的项目来举例。场景是一个目标检测模型要部署到边缘设备上设备算力有限要求延迟不超过 50ms精度 mAP 掉点不超过 2 个点。原始模型是 YOLO 系列的某个变体FP32 精度下在服务器 GPU 上延迟 30ms但在目标边缘设备上延迟超过 200ms完全达不到要求。第一步分析瓶颈。用 profiling 工具发现边缘设备上耗时最多的是卷积层尤其是前几层的大卷积。这些卷积的计算量大而且边缘设备的算力有限。第二步尝试量化。先做 PTQ用 500 张代表性图片做校准。量化后延迟降到了 80ms但 mAP 掉了 3 个点超过了 2 个点的底线。第三步调整量化策略。把敏感层主要是检测头附近的层排除在量化范围外只量化主干网络。延迟降到 95msmAP 只掉了 1.2 个点达标了。但延迟还是超过 50ms。第四步加入剪枝。对主干网络做结构化剪枝剪掉 30% 的通道。剪枝后微调 10 个 epoch。延迟降到 60msmAP 又掉了 0.8 个点总共掉了 2 个点刚好在底线。第五步算子融合和内存优化。用推理引擎的图优化功能做算子融合同时优化内存分配。延迟降到 45ms精度不变。最终方案部分量化 结构化剪枝 算子融合延迟 45msmAP 掉 2 个点满足要求。这个案例里有几个关键决策点为什么先量化后剪枝因为量化是无损的相对剪枝而言先做无损优化再做有损优化可以最大化精度保持。为什么部分量化而不是全量化因为全量化掉点太多。为什么剪枝只剪主干因为检测头对精度更敏感。6.3 优化失败时的回退与排查思路优化不可能一次成功失败的时候怎么排查我总结了一个排查顺序先查数据。校准数据有没有代表性评估数据有没有问题数据问题是最容易被忽视但也最容易修复的。再查配置。量化配置对不对剪枝配置对不对有没有配置项写错了这个听起来很蠢但实际中经常发生。然后查工具链。推理引擎版本对不对算子支持不支持有没有已知的 bug有时候换个版本就好了。最后查模型本身。模型结构是不是不适合这种优化有没有特殊的算子或者结构导致优化失效如果所有都查过了还是不行那就回退到上一个能工作的版本换一种优化手段重新试。不要在一个方向上死磕有时候换条路反而更快。注意优化是一个迭代过程不要期望一次就达到最优。每次只改一个变量这样才能准确判断每个优化的效果。同时改多个变量出了问题都不知道是哪个引起的。7. 工具链选型与工程化落地7.1 主流优化工具的能力边界对比Model-Optimizer 不是一个具体的工具而是一类工具的总称。实际用的时候要选具体的工具。主流的几个TensorRTNVIDIA 的推理优化库对 NVIDIA GPU 支持最好算子融合和量化能力都很强。缺点是只支持 NVIDIA 硬件跨平台性差。ONNX Runtime微软的推理引擎跨平台性好支持多种硬件后端。量化工具链比较成熟但优化深度可能不如 TensorRT。OpenVINOIntel 的推理工具包对 Intel CPU 和集成显卡优化很好。支持量化、剪枝、算子融合工具链完整。TFLiteGoogle 的移动端推理框架对 ARM 芯片优化好。支持量化但剪枝和算子融合能力相对弱一些。TVM开源的深度学习编译器支持多种硬件后端自动调优能力强。但学习曲线陡峭工程化落地成本高。选哪个取决于你的部署目标。NVIDIA GPU 就选 TensorRTIntel CPU 就选 OpenVINO移动端就选 TFLite需要跨平台就选 ONNX Runtime。不要为了用某个工具而用某个工具要从部署目标出发。7.2 优化流程的自动化与可复现性优化流程最好能自动化原因有两个一是手动做容易出错二是优化需要迭代手动做效率太低。自动化的关键是配置化。把优化参数量化位宽、校准算法、剪枝稀疏度、微调轮数等都写成配置文件然后用脚本自动跑。这样每次调整参数只需要改配置不需要改代码。可复现性也很重要。同样的配置应该能产出同样的结果。这要求固定随机种子、固定数据顺序、固定工具版本。我见过因为工具版本不同导致优化结果差异很大的情况所以版本管理一定要做好。还有一个经验是保存中间结果。每一步优化的输出都保存下来这样如果后面出了问题可以回退到中间步骤不用从头再来。同时中间结果也可以用来做对比分析看看每一步优化到底带来了多少收益。7.3 优化后的模型版本管理与回滚优化后的模型需要版本管理。每个版本要记录原始模型版本、优化配置、优化后的精度指标、性能指标、优化日期、负责人。这样出了问题可以追溯也可以对比不同版本的效果。回滚机制也要有。如果优化后的模型上线后发现问题要能快速回滚到上一个稳定版本。这要求部署系统支持多版本共存和快速切换。我自己的做法是每个优化版本都打标签标签里包含优化配置的哈希值。部署的时候指定标签回滚的时候切换到上一个标签。同时保留原始 FP32 模型作为最后的兜底万一所有优化版本都有问题还能回到原始模型。8. 那些文档里不会写的经验教训8.1 量化不是越激进越好刚开始做量化的时候我总想着把位宽压得越低越好INT8 不够就试 INT4INT4 不够就试二值化。结果发现位宽越低精度损失越大而且不是线性的。从 FP32 到 INT8精度可能只掉 0.5 个点从 INT8 到 INT4精度可能掉 5 个点从 INT4 到二值化精度可能直接崩掉。而且低位宽对硬件的要求更高。不是所有硬件都支持 INT4支持 INT4 的硬件也不一定支持所有算子。实际部署的时候INT8 通常是性价比最高的选择INT4 只在特定场景下有意义。还有一个反直觉的发现量化后的模型不一定比 FP32 快。如果硬件不支持 INT8 加速或者推理引擎没有针对 INT8 优化量化后的模型可能因为要插入反量化操作而变得更慢。所以量化之前一定要确认目标硬件和推理引擎支持 INT8 加速。8.2 剪枝后的模型可能看起来更慢剪枝之后模型参数变少了理论上应该更快。但实际测试的时候有时候剪枝后的模型反而更慢。原因有几个一是形状不规整。剪枝后的矩阵形状可能不是硬件友好的比如不是 8 的倍数导致计算效率下降。二是并行度降低。剪枝减少了计算量但也减少了并行度GPU 可能跑不满。三是内存访问模式改变。剪枝后的权重矩阵可能变得稀疏内存访问不再连续缓存命中率下降。所以剪枝之后一定要实际测试性能不能只看参数量。如果剪枝后性能没有提升甚至下降可能需要调整剪枝策略比如保持形状规整、控制剪枝粒度。8.3 优化效果的天花板在哪里每个模型都有优化的天花板。到了某个点之后再优化精度就会崩或者性能提升微乎其微。识别天花板很重要可以避免浪费时间。怎么判断到了天花板我的经验是如果连续几次优化尝试比如调整量化配置、调整剪枝稀疏度带来的性能提升都小于 5%而精度损失都大于 0.5%那大概率是到天花板了。这时候应该考虑换模型结构而不是继续优化当前模型。换模型结构有时候比优化更有效。比如把一个大的模型换成专门为移动端设计的小模型可能直接就能满足要求不需要复杂的优化。当然换模型需要重新训练成本更高但如果优化已经走到死胡同换模型可能是唯一的出路。8.4 和训练团队的协作要点做模型优化的人通常不是训练模型的人这就需要和训练团队协作。协作的时候有几个要点尽早介入。不要等模型训练完了才开始考虑优化最好在模型设计阶段就考虑部署约束。比如如果知道要部署到边缘设备训练的时候就可以用一些对量化友好的结构。明确精度底线。训练团队通常希望精度越高越好但部署团队需要知道精度能掉多少。这个底线要提前对齐避免优化完了才发现精度不达标。共享评估数据。优化团队和训练团队应该用同一套评估数据这样指标才可比。如果各用各的很容易出现训练团队说精度 95%优化团队说精度 90%的扯皮情况。建立反馈循环。优化过程中发现的问题比如某些层对量化特别敏感应该反馈给训练团队训练团队可以在下一版模型中针对性改进。9. 面向未来的优化趋势9.1 自动化优化与神经架构搜索的结合现在的一个趋势是把模型优化和神经架构搜索NAS结合起来。传统做法是先设计模型再优化NAS 的做法是在搜索模型的时候就考虑部署约束直接搜出对部署友好的模型。这种做法的好处是优化空间更大。传统优化只能在给定模型结构上做文章NAS 可以改变模型结构本身。比如搜索出更适合量化的结构、更适合剪枝的结构。但 NAS 的成本很高需要大量的计算资源。对于大多数团队来说可能还是先用传统优化手段等有资源了再考虑 NAS。9.2 硬件感知优化的发展方向另一个趋势是硬件感知优化。不同的硬件对算子、数据布局、量化方式的支持不同通用的优化策略可能不是最优的。硬件感知优化是根据目标硬件的特性来定制优化策略。比如某些硬件对特定尺寸的卷积有加速优化的时候就可以把卷积尺寸调整成硬件友好的尺寸。某些硬件对特定数据布局有优化优化的时候就可以调整数据布局。Model-Optimizer 这类工具的未来方向之一就是更好地支持硬件感知优化让用户能针对特定硬件做深度优化。9.3 大模型时代的优化新挑战大模型LLM、大视觉模型给优化带来了新挑战。大模型的参数量巨大量化到 INT8 可能还是很大需要更激进的量化比如 INT4 甚至更低。大模型的计算量大对算力和内存带宽的要求都很高。大模型的推理通常是自回归的每一步只生成一个 tokenbatch 效率低。针对大模型的优化手段也在发展比如 KV Cache 优化、Paged Attention、投机采样等。这些手段和传统的模型优化有交集但不完全相同。Model-Optimizer 这类工具也在逐步支持大模型的优化需求。我在实际做 LLM 量化的时候发现传统的 PTQ 方法对大模型效果不好因为大模型的激活值分布更复杂异常值更多。需要更精细的量化方法比如 GPTQ、AWQ 这些专门为大模型设计的量化算法。这些算法的核心思想是对不同的权重用不同的量化策略对重要的权重保留更高精度。10. 写在最后一些个人体会做模型优化这几年最大的体会是优化不是目的落地才是。不要为了优化而优化不要追求极致的压缩率或者极致的速度而是要在满足业务需求的前提下找到最合适的方案。另一个体会是没有银弹。每种优化手段都有适用场景和局限性没有一种方法能解决所有问题。实际做的时候往往是多种手段组合使用而且需要根据具体情况调整。还有一个体会是测试比优化更重要。优化做得好不好最终要靠测试来验证。建立完善的测试体系包括精度测试、性能测试、稳定性测试比掌握多少优化技巧都重要。最后保持学习。硬件在变推理引擎在变模型结构在变优化手段也在变。今天有效的方法明天可能就过时了。保持对新技术的好奇心多动手实践才能跟上这个领域的发展。
返回列表