
量化这事这几年基本成了模型上线的标配技能。我最早接触模型量化是被一个机器人项目逼的——模型在测试机上跑得飞快一到现场工控机上就卡得怀疑人生内存还动不动就爆。后来把模型从FP32压到INT8体积直接砍掉四分之三推理速度翻了两三倍这才算真正把东西部署上去。如果你的模型也有体积大、推理慢、硬件资源紧的困扰量化就是最直接的那条路。这篇内容不聊太虚的概念从量化到底在做什么、怎么选方案、怎么手动算一遍scale和zero_point到踩坑最多的地方和工程落地时怎么观测收益一次讲透。不论你是第一次接触量化还是已经上手但被精度问题折腾到头秃应该都能从中找到能直接用的东西。1. 量化到底做了什么本质是一场精度换效率的交易1.1 为什么模型被压到四分之一还不崩先看一个经常被问到的现象一个FP32的模型每个参数占4字节转成INT8之后每个参数只占1字节文件体积直接除以4。很多人的第一反应是——那么多信息被丢掉了模型不会废掉吗实际情况是大部分场景下不会。原因在于神经网络本身有冗余。深度学习训练出来的一组权重往往存在大量不重要的波动这些波动的绝对值很小对最终输出结果的影响微乎其微。量化做的事情就是把这些不重要的小数点尾巴丢掉只保留对推理结果真正有贡献的信息。用生活化的类比就像记账时本来记到分现在只记到元大方向没变零头被抹掉了。模型的大方向就是它学到的核心特征分布这个分布基本还在。但不崩有个前提量化方案得靠谱并且你知道哪些层经得起抹零头、哪些层经不起。这就是后面要讲的重点。1.2 量化的账本缩放因子与零点偏移要真正理解量化逃不开两个术语缩放因子scale和零点偏移zero_point。它们合起来做的事情就是浮点数和整数之间来回换算的那本账本。公式不复杂。假设我们要把浮点值x映射到整数q映射关系是q clamp(round(x / scale) zero_point, q_min, q_max)反向还原时x_approx (q - zero_point) * scale这里scale决定了一个整数单位代表多大的浮点数值范围zero_point对应浮点数0被映射到哪个整数。为什么必须有zero_point因为有符号和无符号整数的取值范围不同而且浮点数据的分布也不一定关于0对称。为了让0在量化后依然是0需要把整数坐标做一次平移。一个常见的经验法则是如果你的激活值都是正的比如经过ReLU之后用非对称量化更合适如果权重分布接近关于0对称对称量化通常就够用而且推理时计算更简单很多硬件的加速算子只支持对称量化。量化误差的来源主要有两个一是round带来的取整损失二是clamp把超出范围的值直接截断带来的截断损失。理论上这两部分不可能同时消除方案设计时就是在两者之间找平衡。2. 动手量化前先选型方案选对了后面少返工2.1 训练后量化PTQ与量化感知训练QAT的取舍量化方案基本分两大派训练后量化PTQPost-Training Quantization和量化感知训练QATQuantization-Aware Training。PTQ最省事模型训练完之后直接拿一份校准数据走一遍统计出每层的数值范围算出scale和zero_point就能完成量化。整个过程不碰训练环节对算力要求低、速度快。但PTQ有个短板如果模型里有某些层对数值特别敏感或者校准数据覆盖不全量化之后精度可能掉得比较明显。这时就得考虑QAT。QAT的思路是在训练阶段就模拟量化这个过程让模型的前向计算带着量化误差去更新权重把误差带来的影响直接训练进模型参数里。代价是训练过程更复杂、耗时更长还需要准备训练集和训练环境。选型建议并不复杂优先PTQ精度回落可接受就直接用精度不行再换QAT。很多时候PTQ表现不好不是因为PTQ本身不行而是校准数据没整好或者敏感层没处理这方面后面展开。2.2 per-tensor与per-channel量化怎么选另一个经常让人纠结的选项是量化粒度——是per-tensor还是一层层单独处理还是更细的per-channel。per-tensor意思是这一层所有参数共用一个scale和zero_point实现简单推理也快但精度损失相对大。per-channel则给每个输出通道单独一套scale和zero_point精度保留更好但计算单位更多对硬件算子有要求。经验上卷积层的权重推荐per-channel量化因为不同卷积核的数值范围差异可能很大硬挤在一起用一个范围会放大误差。激活值则通常用per-tensor量化因为激活是动态产生的per-channel不太好落地。这里没有绝对标准最终判断标准只有一条精度和性能是否能同时满足你的业务指标。3. 实操手写一遍量化流程把计算过程彻底搞明白3.1 校准数据收集决定精度的隐藏环节很多人在量化上栽跟头第一原因出在校准数据上。校准数据的任务不是训练模型而是探路——通过前向推理观察每一层真实的浮点数值范围。如果校准数据的分布跟上线后的真实数据分布差异很大统计出来的scale自然不准量化就会在真实数据上露馅。校准数据怎么选要求有两条数量不用多几百到一千张典型样本即可但覆盖面必须足够广要把你上线后会遇到的各种情况都包含进去。以前我接过一个检测项目校准数据是从白天样本里抽的上线后遇到夜间低光照场景模型效果突然变差。后来把夜间样本混进校准集精度立刻回稳。这个坑我们后面还会再提。3.2 从浮点到整数一次完整的量化计算实战很多人看过量化公式还是不知道怎么落地。这里把计算过程完整走一遍。假设某一层的激活值张量经过校准统计后最小值为-1.5最大值为1.5我们用INT8非对称量化表示范围是[-128, 127]。第一步计算缩放因子scalescale (real_max - real_min) / (q_max - q_min) (1.5 - (-1.5)) / (127 - (-128)) 3.0 / 255 0.0117647...第二步计算零点偏移zero_point。非对称量化里zero_point一般这样算zero_point q_min - round(real_min / scale) -128 - round(-1.5 / 0.01176) -128 - round(-127.5) -128 - (-128) // round(-127.5)在很多实现里会取-128或-127取-128 0注意round处理负数时各家实现可能有细微差异但这不影响整体逻辑。第三步把某个浮点值比如0.3量化成INT8q clamp(round(0.3 / 0.01176) 0, -128, 127) clamp(round(25.51), -128, 127) 26反推回去x_approx (26 - 0) * 0.01176 0.30576跟原始值约差0.006这就是单个数值的量化误差在神经网络层面的影响会被后续层逐步吸收或放大。下面是完整的最小可运行逻辑示意用Python伪代码写出来便于你直接照着改import numpy as np def quantize(x, real_min, real_max, q_min-128, q_max127): scale (real_max - real_min) / (q_max - q_min) zero_point q_min - round(real_min / scale) zero_point max(q_min, min(q_max, zero_point)) q np.round(x / scale) zero_point q np.clip(q, q_min, q_max).astype(np.int8) return q, scale, zero_point def dequantize(q, scale, zero_point): return (q - zero_point).astype(np.float32) * scale # 模拟一层激活值 real_min, real_max -1.5, 1.5 x np.array([-0.5, 0.3, 1.2, -1.1], dtypenp.float32) q, scale, zero_point quantize(x, real_min, real_max) x_recover dequantize(q, scale, zero_point) print(原始值:, x) print(量化值:, q) print(反量化:, x_recover) print(scale:, scale, zero_point:, zero_point)跑完这段代码可以直观看到每个值的变换过程。量化不是玄学就是这套固定数学流程加一堆工程细节的叠加。自己在本地把这段逻辑跑一遍比看多少篇原理文章都管用。3.3 量化后的精度回归不能只看一个指标模型量化完成之后下一步不是急着部署而是做精度回归。这个环节最容易被忽略但恰恰也是最容易返工的环节。精度回归要关注的指标至少要覆盖两部分客观指标上分类任务看Top-1/Top-5准确率检测任务看mAP分割任务看mIoU另外还要看最终业务的评价口径比如质检场景的误检率、召回率。如果量化前后客观指标掉点小于约0.5%到1%基本可以认为无损超过这个幅度就要排查了。还有一件事值得留意单张图精度对比看不出来什么但可以打印几张量化前后模型的输出差异观察误差集中在什么位置。有时候是边缘区域误差大有时候是某些类别特别敏感这些信息能帮你定位到具体是哪一层出了问题。4. 最容易踩的坑校准数据、敏感层、数值一致性4.1 校准数据分布偏离带来的隐性失效先说一个我自己踩过的坑。有次做一个语音分类模型PTQ量化后单测准确率只掉了0.3%感觉很稳直接上线。结果线上反馈模型抽风某些音频片段识别得乱七八糟。排查半天问题出在校准数据我当时随手从测试集里抽了500条那批数据信噪比普遍较高而真实场景里噪声类型五花八门。校准数据探出来的数值范围偏窄导致真实高噪声输入一进来就被clamp截断信息直接丢失。校准数据不是抽样就行而是要把真实数据分布的两端都覆盖到。你可以用一小段线上日志数据作为校准集候选或者从生产环境采集不同时段的真实样本别只用干净测试集。4.2 敏感层的定位与混合精度兜底有些层对量化特别敏感比如参数量少的层、接近输出的全连接层、某些结构里的归一化层。量化时如果这些层也被一刀切精度就会明显受损。怎么定位敏感层做法不复杂逐层量化每一层量化完都在验证集上跑一次精度。工作量有点大但结果一目了然。也可以参考一些开源工具里的自动敏感层分析逻辑自己写脚本批量做。定位到敏感层之后最常见的处理手段是混合精度——把敏感层保持FP16或FP32不动其余层继续用INT8。这个方案工程上比全局QAT省事效果往往也不错代价是这部分层占用的内存和计算时间会略微上升。4.3 量化前后数值漂移怎么排查如果精度掉得厉害连混合精度都救不回来就要上数值对比分析了。做法是把量化模型和原模型在相同输入下逐层输出特征图然后计算每个通道的均值、方差和最大绝对误差。哪一层漂移最大问题基本就在那附近。有一次排查一个分割模型逐层对比发现误差在第四个卷积块之后开始指数级放大而那层的激活值分布有个特点数值跨度大、但大部分数值集中在一个很小的区间。这种偏态分布对per-tensor量化非常不友好后来把这层改成per-channel量化误差立刻压下去了。所以遇到偏态分布先试per-channel再试混合精度基本能解决八成问题。5. 工程落地从量化模型到真正变快变稳5.1 硬件算子与量化格式的匹配问题量化模型最终跑在什么硬件上直接决定你能用哪种量化格式。不同处理器的加速算子支持的量化类型、通道对齐方式、权重排列格式都不一样。模型量化得再漂亮算子不支持推理时还是会走浮点路径速度不升反降。所以先确认目标硬件和推理引擎支持哪些量化算子再反推量化方案。也不要假设INT8一定会更快——如果你的模型非常小内存访问瓶颈不大INT8算子反而可能因为反量化、对齐这些额外带到开销而得不偿失。最好先做一个最小验证用代表性输入跑一遍对比延迟和吞吐量。5.2 实测数据量化前后的关键指标对比用一组虚构但符合常见实际情况的数据来演示以下这种对比表应该怎么整理。模型是一个有两千多万参数的检测网络输入尺寸固定测试在单张加速卡上进行指标FP32模型INT8量化后变化模型体积约90MB约23MB下降约75%单次推理延迟约42ms约17ms提速约60%内存占用峰值约1.8GB约0.7GB下降约60%精度mAP0.7850.781掉点0.004这个表不是我编个好看的数字忽悠你而是想让你明白量化的收益评估就该这么量化着看。体积、延迟、内存、精度四个维度摆在同一个表里性能收益和精度损失清清爽爽。别只盯着延迟快了多少内存下降在某些嵌入式场景里才是生死指标。5.3 我的一些实操体会最后按惯例分享几条个人经验。第一别迷信开箱即用。很多推理引擎提供了现成的量化转换接口但它们的默认配置往往不是最优的。默认per-tensor权重量化能跑但精度未必最好改成per-channel处理几层敏感层效果可以明显提升。第二校准数据的优先级高于量化算法本身。同一套模型校准数据换一批精度可能差一个数量级。先把校准数据弄扎实再去折腾高级量化技巧。第三量化上线前务必固化测试集。每次改动量化配置都用同一套测试集和同一套评测脚本不然没法对比。我见过有人因为测试集没有固定把两次不同测试集的结果放在一起对比白忙活半天得出一个错误结论。模型量化是个性价比很高的优化手段尤其是马上要落地的模型花一个下午做量化调优往往比花一周去压缩模型结构划算得多。沿着上面的思路走一遍基本能把量化的核心环节摸透。如果你的模型对精度特别敏感直接上QAT之前也建议先把PTQ和混合精度的潜力榨干——大部分情况根本不需要动模型重新训练量化方案本身还有很大的优化空间可以挖掘。