
前阵子帮某团队把一个图像分类模型往嵌入式设备上搬模型在服务器上跑得飞快一上板子就露馅内存直接顶满跑一帧要两秒多。问题根本不是精度不够而是模型太大、计算太重。这时候能想到的第一个手段就是模型量化——把 FP32 的权重和激活从 32 位浮点压到 8 位整数模型体积直接缩到四分之一推理速度能提好几倍。这篇内容就把模型量化这块最基础的知识一次性讲清楚包括它解决什么问题、有哪些关键概念、实际落地怎么做、又会踩到哪些坑。不管你是刚接触部署优化的同学还是已经在做端侧推理但一直没系统梳理过量化原理的工程师这篇都适合当一份入门地图来用。1. 为什么模型越跑越慢量化要解决的问题1.1 部署现场的三个老大难模型训练的时候大家关心的是精度、收敛速度、显存占用模型一旦要部署问题就完全变了。我见过很多项目在服务器上表现挺好一换到手机、摄像头、机器人控制器这些端侧设备立刻遇到三个拦路虎内存不够、算力吃紧、带宽卡脖子。先说内存。一个常见的分类模型如果权重是 FP32那就是每个参数占 4 个字节。模型不大还好一旦涉及到检测、分割这类大模型动辄几百兆字节很多嵌入式设备根本装不下更别说还要给激活值、中间缓存留空间。再说算力大部分端侧芯片的浮点计算单元和整数计算单元数量差距悬殊同一个芯片上 INT8 的运算速度往往是 FP32 的好几倍这也是为什么量化之后“跑得快”。最后是带宽模型推理时要把权重从内存搬到计算单元内存带宽越窄搬运时间越长。如果你把 4 字节的参数压成 1 字节搬运量直接变成原来的四分之一很多存储带宽瓶颈一下子就解开了。这三个问题量化都能从根上缓解。所以它不只是“省内存的小技巧”而是端侧部署绕不开的核心手段。1.2 量化的本质一次带精度的“单位换算”量化听起来高大上其实本质就是一次单位换算。我用一个生活化的类比FP32 相当于一台高精度电子秤能精确到 0.1 克INT8 相当于一把带刻度的量杯只有 256 个刻度。你不可能用量杯称出电子秤那么精细的值但只要量程设计得合理绝大多数东西都能量个八九不离十。数学上量化就是把浮点数值映射到整数。假设浮点数的范围是 [r_min, r_max]目标整数范围是 [q_min, q_max]比如 INT8 就是 [-128, 127]。量化公式长这样q clamp(round(r / S) Z, q_min, q_max)其中 S 是缩放因子Z 是零点。反量化则是r ≈ S * (q - Z)S 和 Z 怎么算也很好理解S (r_max - r_min) / (q_max - q_min) Z round(q_min - r_min / S)举一个具体例子。假设某层权重范围是 [-1.28, 1.27]要量化到 INT8那么 S (1.27 - (-1.28)) / (127 - (-128)) 2.55 / 255 ≈ 0.01Z round(-128 - (-1.28) / 0.01) 0。 这时候权重里的 0.14 就会变成 q round(0.14 / 0.01) 0 14反量化回来就是 0.14。整个过程相当于确定了“1 个整数刻度代表多大的浮点数”然后做一次四舍五入。这里的核心矛盾在于量化一定会引入精度损失因为 round 这一步把信息丢了。好的量化方案就是要让这个损失尽量不影响最终任务指标。1.3 换来的收益和付出的代价量化带来的收益非常直接。模型体积通常能变成原来的四分之一因为 32 位变 8 位推理延迟在很多整数加速单元上能降低一半甚至更多内存占用和能耗也会显著下降。这些收益在端侧设备上比在服务器上更突出因为服务器本身算力富裕省下来的时间未必明显但芯片资源紧缺时差距就是“能跑”和“跑不动”的区别。代价也没法回避。首先是精度可能掉点尤其对于小模型、检测任务或者分布特别集中的层掉点会更明显。其次是量化得做额外的工作至少你得准备校准数据、做精度对比实验。最后是兼容性不是每一层、每个算子都能被量化工具无缝处理有些层的计算在整数域里很麻烦可能需要回退到浮点。所以我的建议是不要一上来就把量化当银弹。先搞清楚瓶颈在内存、算力还是带宽再决定要不要量化、量化到多少位。2. 先别急着动手量化方案和关键概念怎么选2.1 PTQ 与 QAT训练后量化还是量化感知训练量化方案按“什么时候做”分成两大类训练后量化 PTQ 和量化感知训练 QAT。理解这两者是迈过量化的第一道门槛。PTQ 的做法是模型已经训练好了我不动训练过程只拿一部分数据做“校准”统计出每一层权重的数值分布算出 S 和 Z然后把模型转成整数版本。优点是快通常几十分钟就能跑完缺点是精度损失相对不可控如果模型原本就很敏感可能掉点严重。这里说的校准意指用一小批真实数据在模型里跑一遍前向记录每层激活值的分布范围——激活范围是从数据里统计出来的不能凭空猜。QAT 则在训练阶段就模拟量化误差。做法是在模型里插入“伪量化节点”前向传播时把权重和激活先量化再反量化让模型在训练过程中逐渐适应这种精度损失权重会被优化得更加“抗量化”。精度通常比 PTQ 好不少但需要你有训练流程、GPU 资源和时间成本。我自己的选型经验是模型比较大、任务指标容忍度高的场景先试 PTQ如果精度损失在可接受范围就直接用如果是小模型或者部署到安全敏感的场景直接做好 QAT 的心理准备。先量化一个副本跑对比实验再决定投不投入训练成本这是最稳妥的顺序。2.2 对称量化与非对称量化零点“归不归零”的差别另一组绕不开的概念是对称量化和非对称量化。区别就在零点上。对称量化把浮点范围映射成对称的整数范围零点 Z 强制等于 0公式变成q clamp(round(r / S), q_min, q_max)它的好处是实现简单很多硬件原生支持坏处是如果数据分布不对称比如激活值经过 ReLU 之后全是非负数范围 [0, 6.2]对称量化却要把 [-6.2, 6.2] 的一段范围白白浪费掉整数刻度的利用率就低了。非对称量化则允许零点不为 0可以让量化范围贴着真实数据的分布走比如激活范围 [0, 6.2] 会映射到 [-128, 127] 的完整区间量化分辨率更高。代价是实现复杂一点推理时多一步零点偏移的计算。实际落地时有个很常见的配置组合权重用对称量化激活用非对称量化。为什么因为权重分布通常围绕 0 上下展开对称量化浪费很少而激活经过各种激活函数之后往往带偏移用非对称量化更合适。当然这只是经验配比具体要对齐目标硬件支持情况。2.3 校准粒度per-tensor、per-channel 与 per-group很多人第一次接触量化时不理解为什么还要区分 per-tensor 和 per-channel。说白了就是一个网络层里是一整套参数共用一个 S还是每个输出通道单独用一个 S。per-tensor 最简单整层权重算一个统计范围计算量小硬件实现也方便。问题是卷积层的不同输出通道权重数值分布可能差异很大。想象一下一个通道权重范围是 [-1.28, 1.27]另一个通道范围是 [-0.01, 0.01]如果共用同一个 S第二通道的小数值会几乎全部被“压”成同一个整数信息几乎全丢。per-channel 则是每个输出通道计算自己的 S 和 Z相当于给每个通道单独配了一把刻度合适的“量尺”精度会好很多这是目前 CNN 量化里非常实用的做法。代价是部分硬件不支持或者在某些算子实现里会引入额外的计算开销。后来还有个折中方案 per-group即把参数分成几个组每组一个量化刻度。它比 per-tensor 精细比 per-channel 灵活在部分低比特量化场景里很流行。选粒度的时候先看硬件支持列表再看精度对比结果别只看理论精度。2.4 校准方法min-max、百分位与 KL 散度刚才一直提到校准其实就是确定浮点范围 [r_min, r_max] 的过程。范围选得越准量化精度越好。这个环节常用三种办法。min-max 最简单直接取校准数据里观测到的最大值和最小值。它的弱点是对离群点非常敏感如果某个样本的激活值突然冒出一个超大值整个量化范围被拉宽大部分数据的分辨率就会被稀释。百分位法比 min-max 稳一些取观测分布的第 0.01 或 0.001 百分位作为边界主动忽略极端异常值减少离群点的影响。代价是需要缓存足够的统计量计算分布。KL 散度法更讲究它的思路是在给定整数精度下找一个最优截断阈值让量化前后的分布差异最小。很多主流工具的校准方式就是基于 KL 散度。使用它的时候校准数据集和分布选择对结果影响更大样本越有代表性效果越好。我个人的理解是min-max 适合快速验证百分位法适合直觉调整KL 散度适合作为默认选择。在实操里可以在同一份校准集上分别跑一遍比较验证集精度来定。3. 实操记录从 FP32 到 INT8 的完整流程3.1 先搭最小验证闭环讲完概念接下来是动手环节。我强烈建议不要一上来就接项目里的完整生产链路而是先搭一个最小验证闭环模型、校准集、量化工具、评估脚本四样东西齐了就开跑。我这里以某开源深度学习框架配合常见推理工具链为例主要用示意图代码表达流程因为不同版本 API 名称会有差异但步骤高度一致。整体流程就是加载 FP32 模型 → 准备校准数据 → 配置量化参数 → 跑校准 → 导出量化模型 → 对比评估。model load_model(model.pth) # 加载原始 FP32 模型 calib_loader build_calib_loader(data_dir, count500) # 准备校准数据 quant_model prepare_ptq(model, calib_loader) # 进入量化模式 quant_model.calibrate() # 执行校准统计范围 quant_model.export(formatint8) # 导出量化模型 fp32_metric evaluate(model, val_loader) int8_metric evaluate(quant_model, val_loader) print(FP32 top-1:, fp32_metric[top1]) print(INT8 top-1:, int8_metric[top1])这段流程短但信息量不少。prepare_ptq 不只是把模型“变成”量化模式它还会把一些算子融合比如把卷积和后面的 ReLU 合并成一个算子减少计算和内存访问calibrate 阶段会跑几次前向统计每层的激活分布并据此确定缩放因子。3.2 校准集准备别用训练集硬凑校准集就是用来统计激活值分布的那一批数据很多人在这里翻车。第一不要直接拿训练集来校准。训练集和真实推理场景的数据分布不一定一致而且训练集通常做了大量数据增强统计出来的分布会“更宽”导致量化分辨率被稀释。我一般从验证集里均匀抽 500 到 1000 张图片或样本尽量覆盖真实场景中的各种典型情况。第二样本数量不用太多但要足够“杂”。有人觉得越多越好其实校准数据和训练不一样它只统计分布范围。500 张代表性样本通常就够了再多意义不大。关键是覆盖面要广比如明暗、大小、远近、不同类别都要有。如果校准集只有“晴天”的图片部署时来了“雨夜”的数据分布一偏量化精度就会出问题。第三校准前要把数据预处理流程对齐推理时的流程。归一化参数、尺寸缩放这些不一致的话校准出来的是另一个分布。3.3 量化与导出一天内跑通第一个量化模型跑通第一个量化模型的目标应该是一天内完成不要拖。对 PTQ 来说大多数配置都有默认参数我的建议是先用“比较稳”的一组默认参数跑通per-channel 权重量化、非对称激活量化、KL 散度校准。先不管极致精度拿到量化前后精度对比再说。导出模型时注意两个事情。一是要确认工具链导出的是不是“真量化”。有些时候导出的模型只是把权重从浮点转成了整数但计算时还是会“反量化回浮点来算”这种叫伪量化速度提升非常有限。真正的整数推理是整个计算图尽量都保持在整数域里运算。你可以通过工具链的 profile 功能看每一层实际执行的数据类型一眼就能识别。二是保留 FP32 的原始模型不要覆盖。量化模型只是部署产物后续调参、定位问题都需要原模型。我见过有人把原模型直接覆盖掉最后只能从 git 历史里翻非常被动。3.4 精度评估量化前后到底差多少评估是量化流程里最容易糊弄的环节。这里的核心是必须用和上线一致的数据和指标来对比。分类任务看 top-1、top-5检测任务看 mAP分割任务看 mIoU如果是特征提取模型可以看特征向量的余弦相似度。不要只用 loss 值判断loss 的绝对值下降看不出任务层面的影响。评估时还要注意FP32 和 INT8 模型要跑同一份验证集、同样的预处理、同样的 batch size。我曾见过同事对比时一个用了原始尺寸一个用了缩放后的尺寸结果精度差异根本不是量化造成的。如果量化前后指标差距在 0.5% 以内通常直接上线超过 1%就要去做敏感层定位了。可以把量化误差大的层单独回退到 FP32看指标能否恢复借此找到需要混合精度的层。4. 摸爬滚打后总结的常见坑位4.1 精度掉点到底是谁的锅精度掉点是最常见的现象但背后的原因五花八门。我排查时有个固定顺序先看校准集再看敏感层最后看量化配置。校准集不代表性是最容易出问题的地方。如果校准数据和真实数据分布存在差异统计出的范围就不准。简单验证方法是换一批校准数据重新跑一遍如果精度波动很大说明校准集没选好。敏感层问题也常见。有些网络层对量化特别不友好比如检测任务里的回归头、分割任务的上采样层、注意力机制里的 Softmax 与矩阵乘这些层稍微量化一下就会放大误差。定位方法很简单逐层或者逐块做量化回退实验把某一层保留 FP16其他层量化看谁对精度影响最大。找到敏感层之后配合混合精度方案解决。还有一种情况是量化配置本身不合理。比如激活用对称量化、权重用非对称量化又比如 per-tensor 在通道差异大的层上用了都会造成额外损失。把配置切换成 per-channel 权重、非对称激活通常就能改善。4.2 校准集和真实场景分布不一致这个问题在部署后才会暴露——量化时验证集精度挺正常一上真实环境就掉点。典型的例子是图像分类模型在校准时只用了室内光线数据上线后遇到户外强光、夜间低光分布直接漂移。解决思路有两个方向。一是让校准集更贴近真实分布尽量从线上日志或代表性场景里采集样本如果做不到考虑做一定程度的数据增强再校准。二是给模型保留少量“灵活性”比如把输入输出层或对分布敏感的层留在高精度让整体模型对分布漂移不那么敏感。另外提醒一句量化模型的分布漂移比 FP32 模型更剧烈。因为浮点模型对输入分布的容忍度高量化后每个值都经过了刻度映射分布一偏就可能导致大量值落在一个较差的量化区间里。4.3 量化后速度反而更慢很多人第一次量化后会困惑模型变小了为什么推理时间没有变短甚至更长了这种情况通常是“量化没有真正落地”。第一种可能是硬件不支持整数加速算子。有些平台对 INT8 卷积有优化但对 INT8 的某些激活函数、上采样、拼接操作没有原生支持于是推理引擎只能把数据“反量化回浮点”来计算算完再“量化回去”。来回转换的开销可能比省下来的还大。方案是检查 target 平台的算子支持表尽量把不支持算子回退到 FP16而不是跟着整个网络一起转 INT8。第二种可能是模型太小带宽收益不明显。对于已经很小或计算量很低的模型量化的主要收益是体积和内存推理速度未必有质的提升。量化一个非常轻量的分类模型经常出现 INT8 和 FP32 差不多的情况因为耗时大头可能在预处理或者数据搬运。第三种可能是高层 API 默认配置不适合当前芯片。有些推理引擎需要手动指定“推理设备”和“精度策略”如果默认策略是 FP32你量化了个寂寞。性能调优时必须看 profiler 输出确认实际跑起来的每一层是什么精度。4.4 算子不支持导致的“伪量化”伪量化是一个隐蔽的坑。从模型文件看权重是 INT8 了但推理引擎拿到之后先把权重反量化成浮点再用 FP32 计算最后输出。表面上是量化模型实际上计算没有省。出现这种情况往往是不支持某些自定义算子、某些动态形状的分支、或者注意力类算子。排查办法就是前面提过的 profiler看每一层的实际执行精度。如果发现某几层全部是 FP32那它们就是干掉性能的元凶。处理方式分为几类一是用算子融合把不支持的算子合并到支持的操作里二是把不支持的层调度到浮点通道混合执行三是修改模型结构用更友好的算子替换。多数情况下混合精度方案足够解决问题不必为了让整个模型“全 INT8”而大改结构。5. 进阶玩法混合精度与量化边界5.1 混合精度让敏感层留在高精度当全模型 INT8 精度不达标时下一步通常就是混合精度也就是让某些层保持 FP16 甚至 FP32其余层走 INT8。相比全量化混合量化通常在精度和速度之间取得比较好的平衡。怎么决定哪些层保留高精度我的经验是先对关键层做精度实验再结合 profiler 看哪些层延迟占比高。延迟占比高、量化误差小的层优先量化延迟占比低、但对精度影响大的层就保留高精度。理论上这样性价比最高。输入输出层也值得单独考虑。输入层的通道数少、计算量小量化与否对速度影响不大但保留 FP32 往往能让模型对输入分布变化更稳健。输出层同理直接决定最终数值质量。另外混合精度方案的“整数/浮点切换”会产生一定的转换开销所以不要把模型切得太碎。一般以模块为单位切比如某个残差块整体走 INT8某个检测头整体走 FP16减少切换次数。5.2 量化和剪枝、蒸馏怎么配合在部署优化这个领域量化经常不是单独使用的。模型压缩有三大件剪枝、蒸馏、量化它们可以叠加使用。剪枝是把不重要的连接或通道删掉减少计算量量化是在剩余结构上进一步降低精度。这两者叠加时有个顺序问题一般建议先剪枝再量化。剪完的模型更小量化后的精度损失也可能更小但剪枝比例过大会让模型变得过于脆弱量化掉点反而更严重。蒸馏则是用一个大模型“教”一个小模型。如果蒸馏和量化一起用可以在训练阶段就让小模型适应量化误差通常做法是teacher 是 FP32 大模型student 是量化感知训练的小模型这样训练出来的模型精度往往比“先普通训练再量化”好很多。如果你做 QAT可以顺便把蒸馏损失加上几乎不增加太多训练成本但精度提升明显。5.3 低比特量化的现实约束INT8 是目前最主流的量化位宽因为绝大多数硬件对其支持最成熟。但最近 INT4、甚至混合的 INT4/INT8 方案也逐渐变多主要是为了进一步压内存和带宽。INT4 带来的体积收益更诱人但现实约束不小。一是硬件支持参差不齐很多平台对 INT4 乘法的加速能力有限甚至根本没有二是精度损失显著一般需要更强的 QAT 手段和更复杂的混合精度策略来兜底。有人会在权重上试 INT4在激活上保留 INT8也算一种折中方案。我个人建议除非目标硬件明确支持且你的任务精度宽容度很高否则先老老实实把 INT8 做扎实。低比特是优化方向不是默认选项。先把 INT8 的校准、混合精度、算子融合这些都调明白了再去试低比特才有意义。最后分享一点个人体会。量化这件事入门门槛并不高几行配置就能跑通一个 PTQ但真正把它调好需要对模型结构、数据分布和底层硬件都有理解。我踩过最深的坑从来不是公式看不懂而是校准集随便准备、量化配置照抄默认、评估不做对齐——这三个问题不解决量化结果就是碰运气。如果你正在做部署项目建议花点时间把“校准集怎么选”“敏感层怎么找”“profile 怎么看”这三件事挨个实践一遍之后遇到量化问题都会顺很多。