
模型量化这个词这两年在端侧部署、推理加速的圈子里被提得越来越多。但凡你碰过把模型塞进手机、嵌入式设备或者想用更低的显存跑大模型绕来绕去最后都会撞上它。但很多人对量化的理解停留在“把 float32 换成 int8”这一句话上真到动手调的时候发现精度掉得离谱、速度没快多少、算子还跑不起来问题一大堆。这篇东西我打算把模型量化从最底层的浮点表示一路讲到 PTQ 和 QAT 这两大范式把中间那些容易被跳过的坑一个个填上。不管你是刚接触推理优化的新手还是已经用过一些量化工具但没搞明白背后逻辑的工程师应该都能从里面找到对自己有用的部分。整篇内容会偏实操和理解不会堆一堆公式吓人但关键的原理该讲清楚的地方一定讲透。1. 从浮点表示说起为什么量化能省资源1.1 浮点数到底是怎么存的要理解量化得先搞清楚被量化的对象长什么样。计算机里的浮点数最常用的格式是 IEEE 754 标准定义的。以 float32 为例它用 32 个 bit 来存一个数分成三段1 位符号位、8 位指数位、23 位尾数位。符号位决定正负指数位决定这个数大概在什么量级尾数位决定精度。这套表示法的好处是能覆盖极大的动态范围从 10 的负 38 次方到 10 的正 38 次方都能表示而且精度是浮动的——大数附近精度低小数附近精度高。但代价也很明显存一个数要 32 位做一次乘法要处理指数对齐、尾数相乘、规格化、舍入这一整套流程硬件电路复杂功耗和延迟都上去了。我经常用一个类比来解释浮点数就像用科学计数法写数字比如 6.02×10²³你既知道量级10 的 23 次方也知道有效数字6.02。而定点数就像直接写一个整数比如 602000000000000000000000位数固定没有“小数点浮动”这回事。量化本质上就是把科学计数法换成定点整数表示牺牲动态范围换存储和计算效率。1.2 定点整数的表示与映射关系量化的核心操作是把一个浮点张量映射到一个整数张量。最常用的映射是仿射量化公式长这样real_value scale × (quantized_value - zero_point)反过来就是quantized_value round(real_value / scale) zero_point这里的scale是一个浮点缩放因子zero_point是一个整数偏移量。为什么要 zero_point因为浮点数的 0 在量化后不一定对应整数 0。比如你量化一个范围是 [-1.0, 1.0] 的张量到 int8范围 [-128, 127]浮点 0 映射过去正好是某个整数但如果范围是 [-0.5, 1.5]浮点 0 就不在整数 0 上了需要一个偏移来对齐。scale的计算方式取决于你选的量化范围。对称量化里scale max(abs(min), abs(max)) / 127zero_point 固定为 0。非对称量化里scale (max - min) / (qmax - qmin)zero_point qmin - round(min / scale)。对称量化实现简单、计算快但遇到分布不对称的激活值比如 ReLU 之后的输出全是非负就会浪费一半的表示范围。非对称量化能更充分地利用整数范围但计算时多一个 zero_point 的处理。提示实际工程里权重通常用对称量化因为权重分布一般围绕 0 对称激活值用非对称量化因为 ReLU 等非线性会引入偏移。这是很多推理框架的默认策略不是随便定的。1.3 量化到底省了什么搞明白表示方式后量化带来的收益就很好理解了。存储上float32 变 int8 直接省 4 倍空间一个 7B 参数的模型从 28GB 降到 7GB这是最直观的。计算上整数乘加运算比浮点运算简单得多在支持 int8 指令的硬件比如带 DP4A、VNNI 指令的 CPU或者各种 NPU上吞吐能提升好几倍。功耗上整数运算的能耗远低于浮点这对电池供电的移动设备是刚需。但这里有个容易被忽略的点量化省的是“推理”阶段的资源训练阶段一般还是用浮点。而且量化不是无脑省它引入了误差误差控制不好精度就崩。所以整个量化技术的核心矛盾就是怎么在尽量压缩的同时把精度损失控制在可接受范围内。PTQ 和 QAT 就是解决这个矛盾的两种不同思路。2. PTQ 与 QAT两种范式的本质区别2.1 PTQ 的工作机制与适用边界PTQPost-Training Quantization训练后量化。顾名思义模型已经训练好了你拿现成的浮点权重通过统计一些数据来确定量化的 scale 和 zero_point然后直接转换。整个过程不需要反向传播不需要梯度甚至不需要标签只要一小批校准数据跑一遍前向就行。它的流程大致是准备一个校准集通常几百张图或几百条文本就够把浮点模型跑一遍在每一层记录激活值的分布然后用某种策略min-max、KL 散度、百分位截断等算出这一层的量化参数最后把权重和激活都转成整数。PTQ 最大的优势是快、简单、成本低。一个模型几十分钟甚至几分钟就能量化完不需要训练资源。但它的边界也很清楚当模型对量化误差敏感或者激活值分布有长尾、离群点特别多的时候PTQ 的精度会明显下降。尤其是那些层与层之间耦合紧密的模型一层量化误差会往后累积放大。我实测下来的经验是PTQ 在卷积网络CNN上通常表现不错int8 量化后精度掉 1 个点以内很常见。但到了 Transformer 类模型尤其是大语言模型激活值里存在大量离群点outlier直接 PTQ 往往掉得很惨这时候就得上更精细的手段。2.2 QAT 的工作机制与代价QATQuantization-Aware Training量化感知训练。它的思路是既然量化会引入误差那我在训练阶段就模拟这个误差让模型学会适应它。具体做法是在前向传播时插入伪量化节点fake quantization把权重和激活按量化的方式取整再还原模拟量化带来的精度损失反向传播时用直通估计器STE把梯度原样传过去让参数继续更新。QAT 的精度通常明显优于 PTQ因为它让模型在训练中“看到”了量化误差并做了补偿。代价是你要重新训练模型需要完整的训练数据、训练资源和时间。而且 QAT 的训练不是从头训一般是在预训练好的浮点模型上做微调学习率要调小训练轮数也不用太多几个 epoch 往往就够。这里有个关键细节QAT 里的伪量化节点在前向时是“量化再反量化”输出还是浮点但数值已经被量化网格约束了。这样做的目的是让梯度能正常回传同时让模型感受到量化的影响。STE 是个近似它假设量化函数的梯度为 1虽然不精确但实践中效果很好。2.3 两种范式的选择逻辑到底选 PTQ 还是 QAT不是拍脑袋决定的。我一般按这个逻辑判断判断维度优先 PTQ优先 QAT模型类型CNN、传统检测/分割Transformer、LLM精度要求掉点可接受掉点极其敏感时间成本紧张充裕训练资源没有有 GPU 集群数据可得性只有少量校准数据有完整训练集迭代频率需要快速验证一次到位实际项目里我通常先跑 PTQ 看基线如果精度达标就直接用省时省力。如果 PTQ 掉点超过阈值再考虑上 QAT。也有折中方案比如只对敏感层做 QAT其他层保持 PTQ这样能省不少训练成本。注意不要一上来就 QAT。很多团队觉得 QAT 精度高就直接上结果发现训练调参、数据准备、时间成本加起来远超预期而 PTQ 稍微调调校准策略其实就够了。先 PTQ 后 QAT 是更稳妥的路径。3. PTQ 实操校准策略与参数确定3.1 校准数据的准备要点PTQ 的第一步是准备校准数据。很多人随便拿几张图就跑结果量化参数统计不准精度自然差。校准数据的核心要求是“有代表性”——它要能反映模型在实际推理时遇到的输入分布。数量上一般 100 到 500 个样本就够太多收益递减太少统计不稳。内容上要覆盖实际场景的各种情况比如做目标检测校准集里就得有不同光照、不同尺度、不同类别的图。如果实际部署时输入分布和校准集差异大量化参数就会偏精度掉得厉害。我踩过的一个坑是用训练集的子集做校准但训练集经过了强数据增强分布和真实推理输入不一致导致量化后精度虚高上线就崩。后来改成从验证集或真实业务数据里采样问题就解决了。所以校准集要尽量贴近真实推理时的数据分布这一点比数量更重要。3.2 常见的校准算法对比校准的核心任务是确定每一层激活值的量化范围 [min, max]。不同算法对这个范围的估计方式不同效果差异很大。Min-Max 校准是最简单的直接取校准集上激活值的全局最小和最大值。优点是实现简单、不丢信息缺点是容易被离群点带偏。如果某一层偶尔出现一个极大的激活值整个量化范围就被拉大导致正常值的量化精度被稀释。KL 散度校准也叫熵校准是 TensorRT 里常用的方法。它的思路是找一个截断阈值使得截断后的分布和原始分布的 KL 散度最小。这样能过滤掉长尾离群点的影响让量化范围更集中。实测下来KL 校准在 CNN 上比 Min-Max 稳定不少。百分位校准Percentile更直接取激活值的某个百分位比如 99.9%作为最大值把超出部分截断。这个方法参数直观调起来方便但百分位的选择需要根据模型试。MSE 校准则是找一组 scale 和 zero_point使得量化再反量化后的值和原始值的均方误差最小。它直接优化量化误差理论上更合理但计算量稍大。校准算法优点缺点适用场景Min-Max简单、无信息丢失对离群点敏感分布均匀、无长尾KL 散度抗离群点、稳定计算稍复杂CNN、激活分布有长尾百分位参数直观、可控百分位需调通用MSE直接优化误差计算量大精度要求高3.3 逐层与逐通道量化的取舍权重量化有个重要选择逐层per-tensor还是逐通道per-channel。逐层量化是整个权重张量共用一个 scale逐通道是每个输出通道卷积核的每个 filter用各自的 scale。逐通道量化的精度明显更好因为不同通道的权重分布差异可能很大共用一个 scale 会互相拖累。但逐通道量化在推理时计算稍复杂需要为每个通道单独处理 scale。实际工程里权重基本都用逐通道量化激活值用逐层量化因为激活值逐通道量化在硬件上支持不好收益也不大。提示如果你用的推理框架默认权重是逐层量化而精度不达标第一件事就是检查能不能改成逐通道。这个改动往往能带来明显的精度提升成本却很低。3.4 PTQ 精度不达标时的调优手段PTQ 跑完发现精度掉了别急着放弃有几个调优方向可以试。第一换校准算法。Min-Max 换成 KL 或百分位往往有改善。第二调整校准集增加样本多样性或换成更贴近真实分布的数据。第三对敏感层单独处理比如某些层保持浮点或者用更高的位宽比如 int16。第四检查是否有算子不支持量化导致回退到浮点破坏了整条量化链路。我遇到过一个典型案例某检测模型 PTQ 后 mAP 掉了 5 个点排查发现是某个后处理算子不支持 int8框架自动把它前后的层都回退成浮点了导致量化不连续。后来手动调整了量化配置把不支持的算子隔离出来精度就恢复到只掉 1 个点。4. QAT 实操伪量化节点与训练技巧4.1 伪量化节点的插入位置QAT 的核心是在模型里插入伪量化节点。插入位置有讲究权重的伪量化一般插在卷积或全连接层之前激活的伪量化插在激活函数之后。这样前向传播时权重和激活都经过量化模拟模型能感受到量化误差。伪量化的实现是“量化再反量化”先按量化公式把浮点值映射到整数再映射回浮点。输出还是浮点但数值被约束在量化网格上。这样做的关键原因是保持梯度可传——如果直接输出整数梯度就没法算了。插入位置还要注意一点不是所有层都值得量化。第一层和最后一层通常对精度影响大很多实践里会保持这两层为浮点。第一层直接接触输入量化误差会被放大最后一层直接输出结果量化误差直接影响最终精度。保持它们浮点能显著减少精度损失代价只是少量计算开销。4.2 直通估计器 STE 的原理与局限STEStraight-Through Estimator是 QAT 能训练的关键。量化函数round()的导数几乎处处为 0因为它是阶梯函数如果按真实导数回传梯度全是 0参数根本没法更新。STE 的做法是前向用真实的量化函数反向直接把梯度原样传过去假装量化函数的导数是 1。这个近似显然不精确但实践中效果出奇地好。原因在于量化误差相对较小梯度方向大体上还是对的模型能朝着减小量化误差的方向调整。STE 的局限在于当量化位宽极低比如 2bit、3bit时量化误差很大STE 的近似就不太准了训练容易不稳定。注意用 STE 训练时学习率要比正常训练小一般用预训练模型微调时的学习率再降一个量级。因为量化本身已经引入了扰动学习率太大会让训练震荡。4.3 QAT 训练的超参设置经验QAT 训练不是从头训是在浮点预训练模型上微调。超参设置有几个经验值可以参考。学习率方面一般用浮点训练时学习率的 1/10 到 1/100。比如浮点训练用 0.01QAT 就用 0.001 或 0.0001。优化器用 SGD 或 Adam 都行但 SGD 更稳。训练轮数不用多5 到 20 个 epoch 通常够具体看精度收敛情况。还有一个技巧是“量化参数冻结”训练初期让 scale 和 zero_point 也跟着更新训练后期把它们冻结只更新权重。这样能让量化参数先稳定下来再让权重去适应收敛更稳。另外QAT 训练时最好监控两个指标浮点精度和量化后精度。如果两者差距一直很大说明模型对量化不适应可能需要调整量化配置或增加训练轮数。4.4 QAT 与 PTQ 的混合策略纯 QAT 成本高纯 PTQ 精度可能不够混合策略是个好选择。常见做法是先用 PTQ 跑一遍找出精度损失最大的那些层敏感层只对这些层做 QAT其他层保持 PTQ 的结果。这样既保证了精度又大幅降低了训练成本。还有一种做法是分阶段量化先量化一部分层训练适应后再量化下一部分逐步推进。这样每步的量化误差都较小模型更容易适应。不过这种做法的工程实现复杂一些需要框架支持分层量化配置。我个人的经验是对于大多数业务场景PTQ 加上针对敏感层的局部 QAT就能达到很好的性价比。全量 QAT 一般只在精度要求极其苛刻、或者量化位宽极低的情况下才需要。5. 量化落地的常见问题与排查5.1 精度掉点的定位思路量化后精度掉了第一步是定位问题出在哪。我的排查顺序是先看整体掉点幅度如果掉得不多1 个点内可能是正常误差如果掉得多就要逐层排查。逐层排查的方法是每次只量化一层其他层保持浮点看精度变化。掉点最多的那层就是敏感层。找到敏感层后可以针对性地保持它浮点或者用更高位宽或者对它做 QAT。还有一个常见原因是量化配置不一致。比如权重用了逐通道激活用了逐层但某些算子不支持这种组合导致回退。检查框架的量化日志看看有没有算子被跳过或回退。5.2 算子不支持量化的处理不是所有算子都支持量化。常见的如某些特殊的激活函数、自定义算子、复杂的后处理往往没有 int8 实现。遇到不支持的算子框架一般会把它回退成浮点但这会打断量化链路影响前后层的量化效果。处理办法有几种一是把不支持的算子隔离出来让它前后的量化边界清晰减少影响二是用支持的等价算子替换三是自己实现量化版本。实际项目里第一种最常用成本最低。5.3 量化后速度没提升的原因有时候量化完发现速度没快多少甚至更慢这通常有几个原因。一是硬件不支持 int8 加速比如某些老 CPU 没有相关指令int8 运算反而要模拟更慢。二是量化链路被打断大量算子回退浮点实际还是浮点计算。三是内存带宽成了瓶颈量化省了计算但没省访存速度上不去。排查时先确认硬件是否支持 int8 指令再看量化覆盖率有多少算子是真正 int8 跑的最后看是不是访存瓶颈。如果是访存瓶颈可以考虑算子融合、内存布局优化等手段。5.4 常见问题速查表问题现象可能原因排查方向解决手段精度大幅下降敏感层量化、离群点逐层排查敏感层保浮点、换校准算法速度无提升硬件不支持、链路打断查指令支持、量化覆盖率换硬件、修复量化链路量化后报错算子不支持查框架日志隔离算子、替换实现精度不稳定校准集不具代表性检查校准数据分布换校准集、增加样本显存没降权重未真正量化检查权重存储格式确认量化配置生效6. 量化之外几个容易被忽略的细节6.1 量化与推理框架的配合量化不是孤立的一步它和推理框架紧密相关。不同框架对量化的支持程度、量化格式、算子覆盖都不一样。选框架时要看它对你目标硬件的支持以及量化工具链的成熟度。比如有些框架的量化是离线做的生成量化模型文件有些是运行时动态量化。离线量化一般性能更好但灵活性差动态量化灵活但开销大。实际选型要结合部署场景。6.2 量化对模型结构的影响量化有时会反过来影响模型结构设计。比如为了让量化更友好可以在训练时就避免使用对量化敏感的算子或者调整网络结构让激活分布更集中。这种“量化友好”的设计思路在端侧模型里越来越常见。一个典型例子是用 ReLU6 替代 ReLU把激活值限制在 [0, 6]量化范围更可控。MobileNet 系列就大量用了这个技巧。还有用对称的激活函数、避免极端值等都是为了让量化更好做。6.3 低比特量化的前沿方向int8 是当前最成熟的量化位宽但研究界已经在往更低比特走。int4、甚至二值化网络都有不少工作。低比特量化的难度在于误差急剧增大PTQ 基本不可行必须 QAT而且训练技巧要求高。不过低比特的收益也诱人int4 相比 int8 又能省一半存储和带宽。在大模型推理场景int4 量化已经很常见了。这块内容展开又是一大篇涉及分组量化、混合精度等技巧后面有机会再单独聊。我在实际项目里最大的体会是量化不是一锤子买卖而是一个需要反复调试、结合具体模型和硬件去打磨的过程。同样的方法换个模型、换个硬件效果可能完全不同。所以别指望有一套万能配置理解原理、掌握排查方法比记住某个参数更重要。踩过的坑多了自然就有手感了。