1. 模型优化器到底在解决什么问题
第一次接触 Model-Optimizer 这个概念,是在一个推荐系统的排序模型上。当时线上推理延迟死活压不下去,单次请求要跑 180ms,业务方要求降到 50ms 以内。我试过换更小的模型、砍特征、加机器,效果都不理想——换小模型掉点太狠,加机器成本扛不住。后来一位做推理优化的朋友点了我一句:“你试过在模型本身上做量化压缩吗?”那是我第一次认真研究 Model-Optimizer 这类工具。
Model-Optimizer 本质上是一个面向深度学习模型的压缩与加速工具集。它做的事情可以拆成三条主线:量化(Quantization)、剪枝(Pruning)、蒸馏(Distillation)。量化是把 FP32 的权重和激活值映射到 INT8 甚至 INT4,直接减少内存占用和计算量;剪枝是把模型中贡献小的连接或通道去掉,让网络变稀疏;蒸馏则是用大模型教小模型,让小模型学到接近大模型的表现。这三条路可以单独用,也可以组合用,核心目标只有一个:在精度损失可控的前提下,把模型跑得更快、更小、更省资源。
这套东西适合谁?如果你是把模型部署到服务器、边缘设备、移动端的工程师,或者你在做 LLM 推理成本优化,再或者你手上有模型但推理速度卡着业务指标,那 Model-Optimizer 这类工具就是你必须掌握的。它不要求你重新训练模型,大多数场景下只需要一个校准数据集,就能把已有模型压下来。对于刚入门的朋友,我建议先从量化入手,因为量化的收益最直接、工具链最成熟、踩坑成本最低。
我写这篇东西的出发点很简单:网上讲量化的文章很多,但大多停留在“调个 API 就完事”的层面,真正部署时会遇到精度掉点、算子不支持、校准集选不对、per-channel 和 per-tensor 分不清这些实际问题。我把这两年踩过的坑和验证过的方案整理出来,希望能帮你少走弯路。
2. 量化方案选型:为什么不是所有模型都能直接压
2.1 训练后量化与量化感知训练的分水岭
量化最粗的分类是PTQ(Post-Training Quantization,训练后量化)和QAT(Quantization-Aware Training,量化感知训练)。PTQ 是拿训练好的 FP32 模型,用一批校准数据跑一遍,统计激活值的分布,然后确定量化的 scale 和 zero-point,直接转成 INT8。QAT 则是在训练阶段就模拟量化的误差,让模型自己去适应低精度表示。
我一般这样选:如果模型结构规整、对精度不敏感、时间紧,优先 PTQ。比如 ResNet、MobileNet、BERT-base 这类成熟结构,PTQ 掉点通常在 0.5% 以内,完全能接受。如果模型是自定义结构、有大量小算子、或者 PTQ 掉点超过 2%,那就得上 QAT。QAT 的代价是要重新训练,需要原始训练数据和训练流程,周期长,但精度能拉回来。
这里有个经验值:PTQ 掉点超过 1% 的时候,先别急着上 QAT,检查两件事——校准集是不是和真实数据分布差太远,以及模型里有没有 LayerNorm、Softmax 这类对量化敏感的算子。很多时候问题出在校准集上,不是量化方法本身。
2.2 per-tensor 还是 per-channel:一个容易被忽略的精度开关
权重的量化粒度分per-tensor和per-channel。per-tensor 是整个张量共用一个 scale,per-channel 是每个输出通道一个 scale。听起来差别不大,实测差距很明显。
我拿一个图像分类模型做过对比:per-tensor 量化后 top-1 掉了 1.8%,换成 per-channel 只掉了 0.3%。原因是卷积层的不同通道权重分布差异很大,共用一个 scale 会让小通道的权重被压成 0,信息直接丢失。所以我的默认选择是:卷积层权重一律用 per-channel,全连接层可以用 per-tensor。激活值因为动态范围变化大,通常还是 per-tensor,但要用移动平均或者百分位截断来抑制离群值。
注意:per-channel 量化在推理时对算子有要求,部分老版本推理引擎不支持,部署前一定要确认目标硬件和推理框架的支持情况。
2.3 对称量化与非对称量化的取舍
对称量化把零点固定在 0,公式是q = round(x / scale),反量化是x = q * scale。非对称量化有 zero-point,公式是q = round(x / scale) + zero_point。对称量化计算简单,适合权重;非对称量化能更好覆盖非对称分布,适合激活值(比如 ReLU 之后的输出全是非负的)。
我的实操习惯是:权重用对称,激活用非对称。这样在精度和计算效率之间取平衡。如果推理引擎对非对称支持不好,那就统一对称,但要在校准阶段用更细的截断策略补偿。
3. 校准集:量化精度的隐形决定因素
3.1 校准集不是随便抽几百张图就行
很多人做 PTQ 的时候,随手从训练集里抽 100 张图当校准集,结果精度掉得莫名其妙。校准集的作用是统计激活值的动态范围,它必须代表真实推理时的数据分布。如果训练集和线上数据分布有偏移,用训练集校准就会出问题。
我的做法是:从线上真实流量里采样,而不是从训练集里抽。采样数量一般 500 到 1000 个样本足够,太少统计不稳,太多收益递减。采样的时候要注意覆盖不同类别、不同场景,别全抽同一类。如果是 NLP 模型,校准集的序列长度分布也要和线上一致,否则 padding 部分的激活值会污染统计。
3.2 校准算法的选择:MinMax、MSE、Percentile
常见的校准算法有几种:
| 校准算法 | 原理 | 适用场景 | 精度表现 |
|---|---|---|---|
| MinMax | 取激活值最大最小值 | 分布均匀、无离群值 | 一般 |
| MSE | 最小化量化前后均方误差 | 通用场景 | 较好 |
| Percentile | 截断百分位外的离群值 | 有离群值的激活 | 好 |
| KL 散度 | 最小化分布差异 | 分类模型 | 好 |
我实测下来,Percentile(99.99%)和 KL 散度在大多数场景下表现最稳。MinMax 最怕离群值,一个异常大的激活值就能把整个量化范围拉爆,导致正常值全被压到很小的区间。Percentile 通过截断解决了这个问题,代价是极少数大值会被饱和,但影响可控。
提示:如果你的模型里有 attention 的 softmax 输出,这类值分布极度集中,建议单独处理,不要和普通激活混在一起校准。
3.3 校准集与验证集必须隔离
这是个低级但高频的错误:有人拿验证集当校准集,然后又在验证集上评估精度,结果虚高。校准集和评估集必须严格隔离,否则你看到的精度是假的。我一般会从线上数据里划三份:校准集、验证集、测试集,比例大概 1:1:1,互不重叠。
4. 剪枝实操:从结构化到非结构化的落地路径
4.1 非结构化剪枝为什么在通用硬件上跑不快
非结构化剪枝是把单个权重置零,理论上能带来很高的稀疏度(90% 以上),但问题是通用 GPU 和 CPU 对稀疏矩阵的加速支持很有限。你剪完之后模型文件是小了,但推理速度可能一点没变,因为硬件还是按稠密矩阵在算。
我早期踩过这个坑:把一个 BERT 模型非结构化剪到 70% 稀疏,文件从 400MB 降到 120MB,但推理延迟只降了 5%。后来才明白,除非你用支持稀疏加速的专用硬件或推理库,否则非结构化剪枝的收益主要在存储,不在速度。
4.2 结构化剪枝:剪通道、剪头、剪层
真正能在通用硬件上提速的是结构化剪枝。它剪的是整个通道、整个 attention head、甚至整个层,剪完之后模型结构是规整的,推理引擎能直接受益。
以剪通道为例,流程是这样的:
- 对每个卷积层或全连接层,计算每个输出通道的重要性(常用 L1 范数或 L2 范数)
- 按重要性排序,剪掉最低的百分之多少
- 因为剪了输出通道,下一层的输入通道也要对应剪掉,保持维度匹配
- 剪完之后微调几个 epoch 恢复精度
这里的关键是重要性评估准则。L1 范数简单有效,但对某些层不敏感;BN 层的 scaling 因子(来自 Network Slimming 那篇工作)效果更好,因为它是在训练中学习出来的。我一般优先用 BN scaling,没有 BN 的层再用 L1。
4.3 剪枝率怎么定:别一刀切
不同层对剪枝的敏感度差异巨大。第一层和最后一层通常最敏感,剪多了直接崩;中间层冗余度高,可以多剪。我的策略是分层设置剪枝率:浅层 10% 到 20%,中间层 40% 到 50%,深层 20% 到 30%。整体剪枝率控制在 50% 左右,精度损失能压在 1% 以内。
如果不想手工调,可以用自动剪枝方法,比如基于梯度的敏感度分析,逐层试探剪枝率,找到精度和压缩率的帕累托前沿。代价是搜索时间长,适合对精度要求极高的场景。
5. 蒸馏:让小模型学到该学的东西
5.1 软标签为什么比硬标签信息量大
蒸馏的核心是软标签(soft label)。硬标签只告诉学生模型“这张图是猫”,软标签告诉它“这张图 80% 像猫,15% 像狗,5% 像兔子”。后者的信息量大得多,学生模型能学到类间关系,泛化更好。
温度参数 T 控制软标签的平滑程度。T=1 就是普通 softmax,T 越大分布越平滑,类间关系越明显。我一般从 T=4 开始试,分类任务上 T 在 3 到 6 之间效果最好。T 太大反而会让分布过于均匀,丢失区分度。
5.2 蒸馏损失的设计:KL 散度加交叉熵
标准蒸馏损失是软标签的 KL 散度乘以 T 的平方,再加上硬标签的交叉熵。T 的平方是为了补偿温度缩放带来的梯度衰减。权重上,我一般让软标签损失占 0.7,硬标签占 0.3。如果学生模型容量很小,硬标签权重要调高,否则学不动。
还有一种进阶玩法是中间层蒸馏,不光对齐输出,还对齐中间特征图。FitNets 那篇工作就是这么做的。中间层蒸馏对小模型提升明显,但实现复杂,需要处理特征维度不匹配的问题,一般用 1x1 卷积做投影。
5.3 蒸馏和量化的组合顺序
如果既要蒸馏又要量化,顺序很重要。我的经验是先蒸馏后量化。先让大模型教出一个小模型,再对小模型做量化。反过来先量化大模型再蒸馏,量化误差会被蒸馏放大,效果差很多。
6. 部署环节的坑:算子支持与精度对齐
6.1 量化算子不支持怎么办
量化模型部署时最常见的报错是“算子不支持量化”。比如某些推理引擎不支持量化版的 LayerNorm、GELU、或者自定义算子。解决办法有两个:一是把不支持的算子保留为 FP32,只量化支持的部分,这叫混合精度量化;二是用算子融合把不支持的算子合并掉,比如把 LayerNorm 融合进前面的矩阵乘。
混合精度量化在 LLM 上特别常见。通常只量化矩阵乘和 attention 的 QKV 投影,LayerNorm 和 softmax 保留 FP32。这样既能拿到大部分加速收益,又不会因为算子不支持卡住。
6.2 精度对齐:为什么离线评估和线上不一致
离线评估精度 99%,上线后掉到 95%,这种问题我遇到过好几次。原因通常有三个:校准集和线上数据分布不一致、推理引擎的量化实现和训练框架不一致、前后处理引入了额外误差。
排查顺序是:先确认校准集,再对比推理引擎和训练框架在同一批数据上的输出,最后检查前后处理。我一般会写一个脚本,把同一批输入分别喂给训练框架和推理引擎,逐层对比输出,定位误差从哪一层开始放大。
6.3 常见问题速查表
| 问题现象 | 可能原因 | 排查方向 | 解决方案 |
|---|---|---|---|
| 量化后精度掉点超过 3% | 校准集分布不对 | 对比校准集与线上数据统计 | 重新采样校准集 |
| 推理速度没提升 | 算子未真正量化 | 检查推理日志的算子类型 | 确认量化算子被使用 |
| 部分样本输出异常 | 激活值离群 | 统计激活值分布 | 用 Percentile 校准 |
| 模型加载失败 | 算子不支持 | 查看推理引擎支持列表 | 混合精度或算子融合 |
| 精度离线线上不一致 | 前后处理差异 | 逐层对比输出 | 对齐前后处理逻辑 |
7. 我踩过的几个真实坑
第一个坑是校准集用了增强后的数据。有一次我图省事,直接拿训练时的数据增强 pipeline 生成校准集,结果量化后精度掉了 4%。原因是增强引入了训练集里没有的分布,校准统计偏了。后来改成用原始未增强的数据,精度恢复正常。
第二个坑是per-channel 量化在某个推理引擎上退化成 per-tensor。我明明配置了 per-channel,但推理引擎版本太老,静默忽略了配置,实际按 per-tensor 跑。这个问题很隐蔽,因为不报错,只是精度不对。后来我养成了习惯:量化后一定用推理引擎跑一遍验证集,和训练框架的输出对比,确认量化配置真正生效。
第三个坑是剪枝后忘了更新 BN 的 running statistics。剪枝改变了通道数,BN 层的 running mean 和 variance 还是旧的,导致推理时输出偏移。解决办法是剪枝后用一批数据重新跑一遍 BN 的 forward,更新统计量,再微调。
第四个坑是蒸馏时温度设得太大。T=20 的时候,软标签几乎变成均匀分布,学生模型学不到任何类间区分信息,精度反而比不用蒸馏还差。后来老老实实从 T=4 开始调,才找到合适的点。
8. 一套可复用的量化落地流程
把上面的经验串起来,我给出一套我常用的量化落地流程,你可以直接照着走:
- 基线评估:在验证集上跑 FP32 模型,记录精度和延迟,作为对比基准
- 校准集准备:从线上采样 500 到 1000 个样本,确保分布覆盖全面,与验证集隔离
- 量化配置:权重 per-channel 对称量化,激活 per-tensor 非对称量化,校准算法用 Percentile 99.99%
- PTQ 转换:用 Model-Optimizer 做训练后量化,生成量化模型
- 精度验证:在验证集上对比量化前后精度,掉点超过 1% 就回到第 3 步调配置
- 推理引擎验证:用目标推理引擎加载量化模型,逐层对比输出,确认量化真正生效
- 性能测试:测延迟、吞吐、内存占用,确认达到业务指标
- 灰度上线:先小流量灰度,监控线上指标,确认无异常后全量
如果 PTQ 掉点实在压不下来,再考虑 QAT。QAT 的流程是在训练脚本里插入伪量化节点,用原始训练数据重新训练几个 epoch,然后导出量化模型。代价是训练成本,但精度通常能拉回到 FP32 的 99% 以上。
提示:量化不是一次性的工作,模型更新后要重新做量化校准。建议把量化流程脚本化,集成到模型发布 pipeline 里,每次模型更新自动跑一遍。
9. 关于工具选型的一点个人看法
Model-Optimizer 这类工具,市面上有好几个选择。我的建议是优先选和你推理引擎配套的那个。比如你用 TensorRT 部署,就用 TensorRT 自带的量化工具;用 OpenVINO,就用 POT。配套工具对算子的支持最全,精度对齐最容易。
如果推理引擎没有配套量化工具,那就选社区活跃、文档全的。我一般会看三个指标:支持的算子列表是否覆盖我的模型、是否有 per-channel 量化、是否支持混合精度。这三个决定了工具能不能真正落地。
最后说一句,量化、剪枝、蒸馏这些技术,原理都不复杂,难的是工程落地中的细节。校准集怎么选、算子怎么对齐、精度怎么排查,这些才是真正花时间的地方。我上面写的这些,都是我一个个坑踩出来的,希望能帮你省点时间。如果你在做量化的过程中遇到奇怪的问题,大概率不是原理错了,而是某个工程细节没对齐,顺着数据流一层层查,总能找到原因。