模型量化参数详解:位宽、scale、校准与QAT的工程调参指南
发布时间:2026/10/21 5:23:19 锦皓数字建站

我们内部前段时间排查过一个典型问题某个检测模型在服务端用 FP32 推理一切正常到了移动端却被嫌又慢又占内存。同事按网上流传的标准做法直接把它压成 INT8精度从 92% 掉到 85% 左右继续压 INT4输出直接乱套。这不是个例——模型量化听起来就是把 32 位浮点换成 8 位整数真正落地时面对的却是一整组互相牵连的参数位宽选多少、scale 和 zero point 怎么定、按张量还是按通道量化、校准数据怎么挑、要不要打开量化感知训练。这些参数里任何一个拍脑袋决定后续的精度和加速效果都会一起还回去。这篇文章就围绕 AI 模型量化里的关键参数逐项拆解。我按自己实际做部署时调参的顺序来讲先讲量化到底在换什么再讲位宽、scale、zero point、校准、感知训练这些参数各自的取值逻辑最后给出一套参数调歪以后的排查路径。适合正在把模型搬向边缘设备、或者在做大模型推理压缩的工程师参考。1. 先搞懂量化在换什么省内存带宽还是省计算1.1 量化本质是让数值在更窄的离散网格上表达模型里所有数据都是张量量化做的就是把这些浮点值映射到数量有限的整数档位上。标准映射关系写出来只有一行r S * (q - z)其中 r 是原始浮点值q 是量化后的整数S 是缩放因子 scalez 是零点 zero point。这个式子等价于把实数轴上的一个区间等分成 2 的 N 次方个格子然后看每个数落在哪个格子里。位宽 N 决定格子数量scale 决定格子宽度zero point 决定格子从哪里开始。理解这一点对后面所有参数都重要量化误差本质上就是真实值和它在哪个格子里之间的差异。格子的宽度太大舍入误差变大格子没盖住数据实际分布的范围截断误差又会进来。所谓调参其实就是在调整这个网格的位置、宽度和密度。1.2 省内存带宽和节省计算是两笔账量化带来的收益分成两类。第一类是存储和内存带宽的节省FP32 一个数占 4 字节INT8 只占 1 字节同一个模型从 1GB 缩到 256MB加载快、搬运快对缓存友好。第二类是计算量的减少很多硬件对 INT8 有原生的整数点积指令比浮点乘加快得多。但要注意省内存带宽和节省计算不是同一个量级也不是每个算子都能同时占到两种便宜。像卷积、全连接这类算子INT8 通常两边都赚但像残差相加、激活函数、LayerNorm 这些逐元素操作计算量本来就小主要卡在数据搬运上这时量化的收益几乎全部来自内存带宽。我用一个不严谨的类比同样是搬一箱水拧瓶盖和走路的两段时间里量化只优化了走路那部分拧瓶盖的时间基本没动。这解释了一个常见现象同一个模型在两个不同的推理引擎里量化加速比可能差很多。某个引擎对 INT8 算子覆盖得全、融合做得好收益就明显覆盖不全会频繁地在 INT8 和 FP 之间来回切换反而变慢。所以判断量化是否值得做先要看模型里到底哪种算子在主导别只盯着压缩比。1.3 关键参数的总览把整个量化过程涉及的参数列出来后面每一节会逐个展开参数含义主要影响位宽量化后数值占多少 bit存储压缩比、网格密度、能否用原生指令scale实数区间到整数的缩放因子决定网格宽度是量化误差的直接来源zero point零点在整数域的偏移量决定网格起点影响非对称映射的精度量化粒度整个张量、按通道还是按分组共享一组参数精度与索引开销的折中对称/非对称映射区间是否关于零对称权重和激活各自更适合的方案不同校准方式如何统计出 scale 和 zero point决定对真实数据分布的贴合程度QAT 开关是否让网络在训练阶段适应量化噪声PTQ 精度不足时的兜底手段2. 位宽不是越小越好存储、算力和精度的三角博弈2.1 存储层面的账谁都会算位宽选择最直观的就是存储压缩比。以 FP32 为基准各档位的存储系数如下数据类型比特数相对 FP32 的存储FP32321xFP16 / BF16160.5xINT880.25xINT440.125xINT220.0625x这组数背起来很容易但落到工程上要小心。INT4 虽然理论上只有 INT8 的一半体积但绝大多数硬件内存的最小寻址单位是字节4bit 数据需要打包存储、解包读出。如果推理引擎没有做好的解包实现一次随机访问反而可能比直接读 INT8 更慢。所以压缩比好看不等于实际跑得快。2.2 算力层面的账要挑剔很多INT8 在移动端 NPU、DSP 和不少服务端加速器上都有原生支持卷积、矩阵乘可以直接走低精度指令这是量化收益的大头。到 INT4 和更低位宽情况完全不同很少有硬件提供原生的 4bit 点积指令引擎常见的做法是先把 4bit 权重解包成 INT8再在 INT8 通道上计算。解包本身要开销计算量也没有减少只有内存搬运减半。这带来的结论很反直觉对以逐元素、数据搬运为主的算子INT4 可能划算对以计算为主的密集卷积INT4 反而是负优化。我在实际项目里的经验是CNN 类模型压到 INT8 是甜点位继续往下通常要接受算子膨胀和精度崩盘的双重代价。INT4 真正的主场是权重主导的大模型推理——解码阶段每生成一个 token 都要读一遍权重权重体积直接决定访存时间这时 INT4 的存储减半是实打实的收益。2.3 精度层面的账网格密度和动态范围的关系精度账的核心是给定 N bit你只有 2 的 N 次方个整数档位。假设某个张量实际分布区间是 [-6, 6]INT8 非对称S 12 / 255 ≈ 0.047量化误差约为半步 0.023INT4 非对称S 12 / 15 0.8量化误差约为 0.4。0.4 的误差对落在 [-0.5, 0.5] 区间里的小数值来说几乎是把信号淹没了。这就是为什么位宽压低以后模型必须扛得住更大的量化噪声。具体到不同任务扛噪声能力差异很大分类网络往往掉一两个点还能接受目标检测里的回归头、分割任务里的边界细节、大模型的 logits 分布往往一压位宽就敏感得多。所以位宽不是越大越稳、也不是越小越省而是要看模型结构和部署硬件的算子支持面去选。先看目标硬件支持什么位宽的原生算子再看该位宽下精度能否守住任务红线最后才轮到内存收益的计算。2.4 我的一般选型参考结合这几年的落地经验不同场景我的起点如下场景推荐位宽备注移动端 CNNINT8通常配合 per-channel 权重量化服务端 CNNINT8首尾敏感层 FP16精度不够时做混合精度LLM 权重压缩INT4group128 或 64配合权重补偿类算法LLM KV CacheINT8 或进一步分组量化主要是省解码带宽端到端大模型加速INT8 权重 低精度激活需要处理激活异常值选型不是一锤子买卖。我的习惯是先跑一个最小配置模型直接 INT8参数默认用单批数据打通正确性再逐步调参数而不是在理论上纠结太久。3. scale 与 zero point量化等式中最容易被调错的两个值3.1 映射公式写在纸上只有一行调起来却不简单量化正反变换的核心公式如下q clamp(round(r / S z), q_min, q_max) r_approx S * (q - z)对非对称 INT8通常取 q 的范围是 [0, 255]对称 INT8 的范围是 [-128, 127]。给定期望覆盖的实数区间 [r_min, r_max]scale 和 zero point 的初值可以这样算S (r_max - r_min) / (q_max - q_min) z q_min - round(r_min / S)举个例子某个激活分布区间是 [-5, 10]用非对称 UINT8S 15 / 255 ≈ 0.0588z round(5 / 0.0588) ≈ 85。于是实数 -5 映射到整数 85实数 10 映射到整数 255。看起来简单但 r_min 和 r_max 本身怎么定就是后面校准那节的全部内容。3.2 scale 和 zero point 的误差是两种性质完全不同的东西scale 定得太大会让网格过宽原本集中在中部的数值只有很少的档位可用舍入误差迅速放大。scale 定得太小会让网格盖不住真实范围超出部分被 clamp 截断变成截断误差。舍入误差是均匀分布在各处的小噪音截断误差是集中在极端值上的大失真。多数时候我们宁可让极少数的极端值被截断也要保住大部分数据的有效档位。但这个宁可的比例需要拿数据说话不能拍脑袋。zero point 的影响经常被低估。非对称量化的 zero point 如果算错会导致整个映射偏移相当于网格平移了一段距离。更隐蔽的是zero point 在整数卷积里有额外的偏移项某些工程实现为省事会把非对称激活的 zero point 直接做成 0等于把非对称映射退化成对称映射白白损失那部分精度提升。3.3 对称与非对称权重偏爱对称激活偏向非对称权重和激活在数值分布上有本质差异。卷积层权重基本都在零附近正负对称用对称量化 z0 就够了这样还能省掉 zero point 相关的额外计算。激活则不一样经过 ReLU 之后的值普遍是非负的范围集中在 [0, r_max]。这时用非对称量化把整个区间都用来编码正数相当于把一个量化区间切成 255 份而不是 127 份有效的相对精度直接翻倍。所以行业里最常见的搭配是权重用对称量化激活用非对称量化。这两个选择背后不是习惯而是分布形状决定的。可以用一个简单实验去验证随便卷积层的输出跑一下把值域打出来看到的几乎都是0 以上拖一条长尾的形状。3.4 per-tensor、per-channel、per-group粒度的选择量化粒度是另一个常被忽略但影响很大的参数。per-tensor 是整个张量共享一个 scale 和 zero point最简单per-channel 是每个输出通道一组参数per-group 则是更细的粒度常见于大模型权重压缩比如每隔 128 个元素共享一组参数。为什么要细分粒度因为同一个卷积层里不同输出通道的权重幅度可能相差一个数量级。某个通道最大绝对值是 30另一个通道只有 1.5如果整个层共享一个 scale小幅度通道的网格精度就被大幅度通道毁掉。per-channel 量化让每个通道用自己的网格这是学术界测了无数遍、对 CNN 权重几乎必选的方案。激活侧为什么通常只用 per-tensor因为激活的 scale 要在推理时统计per-channel 的索引开销太大而且很多加速器不支持动态的通道级激活缩放。近两年大模型推理里出现的一种折中是 per-token 或 per-group 的激活量化——权重按 group 分块激活也按 token 或按块统计本质上是把粒度的收益从权重侧延伸到激活侧代价是每次推理多算一点 scale 元数据。3.5 scale 的乘法融合为什么这个参数会影响速度很多人以为量化后的网络推理还要在每一层反量化回浮点那就错了。整数卷积的输入是整数、权重是整数卷积核天然算出来是整数真正需要的反量化只是最后把整数结果乘一个标量这个标量正好是权重 scale 和激活 scale 的乘积。也就是说反量化可以融合进下一层或者输出处理里几乎不增加成本。这也是权重对称 per-channel 激活 per-tensor成为默认组合的原因输出侧的修正系数是各通道一个标量融合起来非常干净。如果你用了非对称激活则要额外处理 zero point 贡献的偏置项工程上稍微啰嗦一点但可控。理解这一点你去看推理引擎的算子融合日志时就不容易对着ScaleMul之类的算子名发懵了。4. 校准阶段藏着半个项目数据集、统计窗口与截断策略4.1 校准到底在校什么权重的 scale 和 zero point 可以直接对着张量统计这是静态的、确定的。但激活的分布只有把模型真正跑一遍才知道——不同输入中间层激活的值域完全不同。校准就是挑一批代表真实数据分布的输入喂给模型让各层把激活范围记录观测下来再算出 scale 和 zero point。校准阶段通常分两个步骤一是把模型切到推理模式并完成 BatchNorm 折叠让统计出来的激活范围接近部署时的真实形态二是逐批喂数据用观测器记录每个量化层的 min、max 或更多分布信息。这里最容易犯的错误是用训练模式跑校准训练模式下的 BN 还在用批次统计激活范围完全不等于部署时的情况校准出来的参数自然是不准的。4.2 校准集要多少张、该怎么选校准样本数量是个隐性参数。我的经验是CNN 分类和检测模型100 到 300 张有代表性的图通常够用大模型做激活校准128 到 256 段文本也可以。太少会漏掉真实场景里的边界样本太少导致 scale 偏乐观线上遇到稍大一点的激活值直接截断太多又会把不同模态的分布混在一起让统计口径被稀释。比数量更重要的是代表性。校准集要覆盖部署场景的常见情况最好也包含少量边界情况。我踩过的一个坑是用纯室内场景图做校准模型上线后在户外强光照下精度崩了查下来是激活最大值被低估。后来我在校准集里故意塞了几张高动态范围的图问题就消失了。注意别拿训练集做校准那会把模型见过的分布当成真实分布属于作弊式的乐观估计。4.3 MinMax、百分位和 KL 散度三种统计口径的取舍统计方式原理优点缺点MinMax用观测的全局最小/最大值简单、稳定对极端异常值极其敏感百分位截掉 99.9% 或 99.99% 以外的点对长尾分布友好截断比例需要试MSE / KL搜阈值使量化前后分布差异最小精度最可控计算量稍大MinMax 是很多框架的默认值但它对激活里的个别异常值几乎没抵抗力。工业界真实数据的激活分布经常带长尾头部的几个大值会把 scale 撑得很大导致绝大多数正常值只有很少的量化档位。百分位法等于手动指定我允许多少比例的点被截断一般从 99.9% 起步如果精度和性能都还稳可以收到 99.99%。KL 散度这类方法则是自动搜索截断点让量化前后张量的分布差异最小代价是要多跑几轮统计。要注意不同的统计口径对最终精度的影响往往比换一个位宽还大。我见过不少项目一上来就怀疑位宽不够其实只是默认 MinMax 被激活长尾带偏了换成百分位或者 KL 之后 INT8 直接达标。4.4 大模型激活里的 outlier校准参数必须应对的那个大头大模型量化绕不开激活 outlier 问题。LLM 的某些特征通道经常出现幅度特别大的激活值比如大部分值在 [-1, 1]个别通道冲到 20 以上。MinMax 一统计scale 被这些极端通道撑大其余值几乎全部落在网格的低端量化完的信息量趋近于零。对付 outlier 不是只靠校准确能达到的标准做法通常组合使用用百分位法截断牺牲少数极大值做通道级缩放平滑比如 SmoothQuant 思路把激活侧的计算负担迁移到权重侧把 outlier 通道单独用高精度保留其余通道走 INT8这就是混合精度分解的思路或者干脆开 QAT让网络自己去适应这种分布。校准这一节被低估的原因在于它不像位宽那样有个直观的数字但它直接决定 scale 和 zero point 的质量。项目里我通常把校准当作一个独立的小实验去管理固定模型和引擎只在校准集、统计口径、样本数量三个维度上做对比先用少量数据把方向试出来再放大到全量校准。5. 感知训练的开与关QAT 的半步参数5.1 PTQ 打底QAT 兜底训练后量化 PTQ 不需要反向传播不需要原始训练数据是多数项目的第一选择。但 PTQ 有极限——模型越小、任务越精细、激活分布越别扭PTQ 的精度缺口就越明显。当 INT8 掉了 3 个点以上且校准手段都试过以后就该考虑量化感知训练 QAT。QAT 的思路是让模型在训练阶段就见到量化噪声把量化误差当成另一种正则化网络自动学出对低位宽鲁棒的权重分布。代价是要重跑训练流程、要处理训练和推理行为不一致的问题成本明显高于 PTQ。5.2 fake quantization 和 STEQAT 里两个容易被误会的名词QAT 的前向过程插入伪量化节点输入先量化成整数、再反量化回浮点。输出仍然是浮点但已经被注入了量化噪声。这样损失函数可以直接在浮点图上计算梯度也能照常用。难点在反向传播。round 函数在几乎处处导数为 0直接用链式法则会把梯度杀光。STE 思路是让梯度绕过 round 直接传给前面的浮点输入相当于假装量化噪声不存在。这个近似很粗糙但实践中足够有效。它的副作用是因为梯度传回来时看不见量化噪声学习率稍大就会让权重在取整边界附近反复震荡。所以 QAT 通常会把学习率降低一个量级甚至更多。5.3 QAT 里隐藏的几个参数第一个是伪量化节点的范围初始化。很多实现会用 PTQ 阶段统计出来的 scale 和 zero point 作为起点随后让这些参数参与训练或继续用滑动平均更新。初学者容易忽略的是训练到一半才发现范围没有跟随分布变化导致量化精度被锁死。第二个是 BatchNorm 和量化之间的顺序。QAT 里 BN 的统计量要折叠进卷积权重之后再做伪量化折叠发生在训练结束后还是训练中会直接影响训练和部署行为的一致性。各家框架实现不一我建议在项目里固定一套折叠-量化顺序并用导出的推理图结果对照 QAT 训练时的伪量化输出来校验。第三个是敏感层的豁免。哪怕整体模型都被量化第一层卷积和最后的分类层通常都建议保留 FP。第一层的输入是原始图像范围大、结构复杂最后一层直接决定输出 logits量化误差会被放大到最终指标上。我一般默认首尾豁免 FP16作为起点再根据逐层精度报告决定是否扩大豁免范围。5.4 PTQ 和 QAT 的决策参考维度PTQQAT数据要求少量无标注校准数据需要训练流程和适量训练数据时间成本几十分钟到几小时训练一轮的 10%-20%精度多数情况下够用通常能追平甚至超过 INT8 红线适用场景大模型、迭代频繁、快速上线小模型、任务敏感、精度告急我的习惯是永远先把 PTQ 的参数调到不能再调再开 QAT。因为 QAT 一旦打开后续每次模型更新都要重训成本是乘法级别的而 PTQ 只是推理侧扫参数而已。很多项目其实 PTQ 就能收尾QAT 是被位宽不够的错觉逼出来的。6. 参数调歪后的典型症状与排查路径6.1 别急着归因到模型不适合量化先做逐层体检量化精度出问题时最常见的错误做法是全局试参数比如把位宽从 INT8 换成 INT4或者把所有层都豁免成 FP16然后看精度是否回升。这种盲试效率很低而且经常掩盖真正的根因。正确做法是对量化前后的模型做逐层对比把同一批输入分别跑 FP32 模型和量化模型逐层计算输出的余弦相似度。正常层相似度应该在 0.99 以上如果某一层突然掉到 0.9 以下就找到了嫌疑区。再到这一层去看权重 per-channel 的最大绝对值分布、激活的 scale 值通常很快就能定位到异常。这个体检动作应该在调参之前就做它能帮你把排查范围从整个模型缩小到某一两层。6.2 我见过的高频症状和对应的参数根因症状最可能的参数原因优先处理整体精度掉 3% 以上校准集不具代表性或 MinMax 被长尾带偏换校准集、改百分位、再考虑 QAT单层输出全 0 或全部饱和该层 scale 过大或 zero point 溢出检查该层 S/zp改 per-channel量化模型在真机上性能抖动dynamic 量化分支开销或者算子来回切换改 static 量化固定输入 shape校准集精度好线上精度崩校准分布与部署分布不一致采集真实流量分布重新校准首层或末层贡献了大部分掉点敏感层没有豁免首尾层改 FP16/FP326.3 一个典型的排查过程用一个虚构但很常见的例子完整走一遍。某检测模型 INT8 量化后 mAP 从 0.72 掉到 0.63。第一步做逐层体检发现第 12 层卷积的输出余弦相似度只有 0.88其余层都在 0.98 以上。范围锁定。第二步查看第 12 层权重的 per-channel 最大值分布发现大部分通道的最大值集中在 [1, 3]但有两个通道的最大值到了 30。问题清晰了per-tensor 的 scale 被这两个通道撑大其他通道的权重几乎没有有效档位。第三步把该层权重改成 per-channel 量化余弦相似度恢复到 0.97但整体 mAP 只回到 0.66。继续复查激活侧。第四步看该层激活的观测范围校准阶段 MinMax 统计出来的是 [-4, 12]但改成 99.9% 百分位之后范围变成 [-2.8, 6]。说明激活侧也存在长尾。改用百分位截断后mAP 回到 0.70。最后一步为了确认剩余掉点来源把第一层和最后输出层豁免为 FP16mAP 稳定在 0.71。到这里量化配置才算真正收工。这个流程里有几个操作细节值得保留每次只动一个变量、保留一组固定的 golden 输入做回归对比、量化后的结果一定要在真实部署引擎上验证而不是在训练框架里看数值。不同推理引擎对算子融合和填充的处理不一样同一个量化参数在两个引擎里跑出来的精度可能差零点几个点这是正常现象不必惊慌。6.4 我最后想提醒的一点量化参数之间不是独立的。位宽影响 scale 的取舍校准方式决定 scale 的质量量化粒度决定 scale 的有效性QAT 又反过来影响整个分布的形状。单独调任何一个参数都可能有效但真正稳定可靠的方案往往是在这几个维度之间做了系统性权衡的结果。我自己这几年的体会是量化落地最花时间的不是数学而是建立一套能量化也能定位问题的工程流程。先把逐层对比、校准集管理、参数版本记录这些基本功做扎实调参就不再是碰运气。这篇文章里给的经验都是以个人实践为基础的通用路径具体数值你还是要拿自己的模型和推理引擎跑一遍再下结论。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。