在AI推理优化这条路上,量化是我见过投入产出比最高的手段。很多人以为量化就是把模型从FP32变成INT8,体积缩小、推理变快,但实操后才发现,AI模型量化对推理性能的影响并不只有正向:有时速度反而下降,精度却掉得莫名其妙。这篇文章会用落地视角拆解量化到底改变了什么、不同量化的适用范围、以及我在真实项目中遇到过的问题和排查方法。如果你正准备做模型部署,尤其需要控制显存和时延,这篇文章可以让你少走不少弯路。1. 为什么要做模型量化:把收益和成本先算清楚1.1 量化最直接的收益:体积缩小和显存翻倍先看最容易被感知到的收益:模型体积。以100MB的FP32模型为例,改成FP16后体积降到50MB左右,改成INT8后降到25MB,改成INT4后甚至可以压到12.5MB。这种压缩不是“压缩包”,而是把每个权重存储的物理位宽直接减下来,GPU显存、CPU内存、磁盘加载量全部跟着缩小。这个收益在服务端尤其明显。我有一次部署一个中等规模的模型,原FP32权重约400MB,单机并发8路推理时,显存占用很快就顶到上限。换成INT8之后,权重占用直接减半,显存空出来一大块,batch size可以调大,吞吐立刻上来。原因很简单:显存带宽有限,每次前向传播要反复读取权重,位宽减半意味着同样时间可以搬运更多的张量。不同精度的存储和带宽占比大致如下:精度权重位宽体积相对FP32权重带宽占用相对FP32FP3232bit100%100%FP16/BF1616bit50%50%INT88bit25%25%INT44bit12.5%12.5%这里要提醒一句:带宽占用是“相对”的,真实影响取决于算力瓶颈还是带宽瓶颈。如果模型是卷积、矩阵乘法这类计算密集型算子为主,带宽占比未必是主要矛盾;但如果是大规模语言模型、深层Transformer,权重体量巨大,带宽往往更先成为瓶颈。1.2 带宽和算力:真正影响推理时延的两块硬骨头很多人以为量化之后计算量会减少,这是个误区。量化通常不会减少FLOPs,卷积还是要做那么多乘法累加,矩阵乘还是要做那么多FLOPs。它真正改变的是数据搬运量和单个运算的位宽效率。现代CPU、GPU和端侧NPU往往对INT8有更高效的SIMD或张量指令。比如同一种处理器,FP32吞吐可能是1,INT8吞吐可能是2到4倍。这时候模型如果恰好在这些算子上,量化后的确会明显变快。反过来,如果硬件对INT8支持很差,走的是通用算术单元,那INT8可能不但不变快,反而因为精度变低导致结果错误。还有一个经常被忽略的问题:数据布局。很多推理引擎在量化后需要把张量从NCHW转换成NHWC,或者插入连续的反量化和再量化算子。这些额外操作本身需要时间。所以,衡量量化影响时不要看“推理引擎声称的支持”,要看实际profile出来的单算子耗时占比。1.3 量化成本:精度损失和工程复杂度要一起看量化不是免费午餐。把权重从FP32压缩到INT8,意味着每个值丢掉了约24bit的尾数精度。模型本身容错性强的,比如常见图像分类模型,精度损失可能控制在零点几个百分点;但检测、分割、语音识别这类对数值范围敏感的模型,可能掉1%到3%的mAP或ACC,甚至更多。工程成本也不能忽略。静态量化需要准备校准集,设置量化配置,处理不支持的算子,排查精度回退原因,还要做不同引擎的验证。一个小型模型可能一天搞定,一个大模型或者结构复杂的模型,量化调优用掉一周很正常。所以在动手量化前,先想清楚这个成本是不是值得。我的习惯是先做一个“收益预估表”:把模型现在的体积、时延、精度记录好,再算一下目标硬件对INT8/INT4的支持情况,如果预期收益不超过20%而且模型体积没压力,那不如不量化,直接保留FP16部署。2. 量化原理和流派选型:先理解再动手2.1 量化映射公式中的关键参数不管哪种量化,核心都是把一个浮点数范围映射到离散整数范围。以INT8为例,非对称量化的映射可以简化为:x_int = round(x / scale) + zero_point然后范围限制在[-128, 127]。反量化就是反过来:x_dequant = (x_int - zero_point) * scale这里最关键的参数是scale和zero_point。scale代表浮点数值范围被压缩的程度,zero_point用来处理非对称范围。对称量化一般让zero_point固定为0,对权重友好;非对称量化更适合激活值,因为激活经常不是以0为中心分布。量化粒度也要说清楚。per-tensor是整层用一个scale和zero_point,简单但容易让数值范围被个别离群点拉大,导致大部分数值精度变低。per-channel是按通道单独统计,精度明显更好,在卷积层和全连接层里是常用选择。还有更细的group量化,在部分大规模模型里很流行,它把通道分成多个组,每组一套参数,精度更好,但索引和存储复杂度更高。我在工程里的默认选择是:权重用per-channel对称量化,激活用per-tensor或per-row非对称量化。这样在精度和推理引擎支持度之间比较平衡。2.2 PTQ、QAT、动态量化各自的适用场景量化方式主要分三类:训练后量化(PTQ)、量化感知训练(QAT)、动态量化。它们的区别和适用场景差异很大。方案是否需要训练精度表现工程成本适用场景训练后动态量化不需要中等,激活保持浮点低CPU端、控件类模型、对内存敏感训练后静态量化不需要较好,需要校准集中大多数CV/NLP模型量化感知训练需要很好,误差提前建模高精度敏感场景、超低比特模型动态量化最大的特点是只量化权重,激活在推理时动态计算scale,因此不需要校准集,实现快。但它通常对算力提升有限,因为激活的计算很多时候仍是浮点。静态量化会把权重和激活都转成INT8,收益最大,但需要校准集来确定激活的范围。QAT则是在训练时就引入“伪量化”操作,前向传播模拟量化误差,让模型参数适应低精度范围。精度通常比PTQ稳定,但训练流程复杂,调参成本高。一般情况下,先用PTQ跑一遍,如果掉精度在可接受范围就够用;不行再上QAT,而不是一上来就改训练流程。2.3 量化粒度、对称与非对称的选择建议很多人会忽略量化粒度和对称性对性能的影响。其实它们直接影响精度和推理引擎对算子的实现方式。对称量化在硬件上更友好,因为可以去掉zero_point计算,在某些向量指令上少几步计算。激活的对称量化缺点是如果激活大量集中在0到10,对称范围会浪费一半,量化分辨率变差。非对称量化利用zero_point把0到10映射到0到255,分辨率更高,但会增加一点计算开销。实际数据中,权重分布通常接近0均值,所以对称量化是安全选择。激活分布往往偏在正区间,非对称量化更合适。如果检测到某一层激活分布极端,优先尝试per-channel量化而不是直接把整个模型切到QAT。对于超低比特场景,比如INT4,单纯per-tensor几乎必然掉点很多。这时候要么用group量化,要么用混合精度,把关键层保留INT8。这比盲目追求全部INT4要可靠很多。3. 实操:从FP32到INT8的完整流程与性能验证3.1 推理引擎选型思路:CPU、GPU和端侧分开考虑不同硬件平台给的量化支持差异很大。服务端GPU通常有专门的高效INT8推理路径,模型转换后可以直接利用硬件张量核心;CPU端则要看一下指令集是否支持向量化低精度计算;移动端或嵌入式端经常需要组合不同的后端算子。选型时先问三个问题:目标硬件是什么,模型推理的瓶颈在带宽还是算力,部署框架是否支持该模型的所有关键算子。如果某个引擎不支持某一类算子,它会自动回退到FP32计算,这就导致“表面量化了,实际还有大半层是浮点”,速度自然上不去。我的经验是,先做一个最小化验证:把一个单层模型量化后跑通,确认引擎能够识别量化算子,再扩展到完整模型。这可以避免一上来就在上百层模型里找问题。下面这段话是伪代码,用来表达我通常采用的静态量化准备流程:# 伪代码:量化准备、校准、转换三步 model.config.quant_mode = "static" # 静态量化 model.config.weight.quant_type = "int8" # 可选 int4 model.config.weight.quant_granularity = "per_channel" # 权重逐通道 model.config.activation.quant_type = "uint8" # 激活非对称 model.config.activation.quant_granularity = "per_tensor" prepared_model = quantize.prepare(model, calibration_loader) quantized_model = quantize.convert(prepared_model)这里的关键点是calibration_loader,也就是校准集,后面的会专门展开。3.2 校准集准备与量化配置:这一步直接决定精度校准集在整个量化流程里最容易被低估。它负责统计激活值的分布范围,校准集选不好,后续精度怎么调都不对。校准集不能直接用训练集,也不能只拿最简单的样本。训练集往往和推理真实分布有偏差,太常见的样本会让激活范围偏窄,导致推理时遇到更极端值在整数范围外直接截断。我一般会从验证集中随机采样,覆盖不同类别、亮度、文本长度、语速等多种情况,样本量控制在100到200个就够,多了也只是浪费时间。校准集里也要留意离群点。一个超大的浮点激活值会把scale拉得非常大,其他正常激活值的量化分辨率就被稀释了。遇到这种情况,可以先统计激活值的百分位,比如把最大值放到99.9百分位而不是全局max,然后把离群点截断。很多推理引擎的校准方法支持这种可选设置,默认不一定是全局max。配置细节上,我建议权重用per-channel,激活用per-tensor。如果你发现精度还不够,不要立刻把整个模型切成QAT,先试着把某个关键层改成更高精度,比如保持FP16或INT16,先看误差是局部还是全局。3.3 性能基准测试流程:用数据说话性能验证不能只看推理引擎文档里写的加速比,要自己跑一套可复现benchmark。我的流程固定五步:固定输入尺寸和batch size;先用相同的输入各跑20次预热,让内部分配和缓存稳定;再连续测200次,记录单次时延和平均时延;分别统计FP32/FP16/INT8三组的P50和P99;再测一组batch size变化后的吞吐量。伪代码可以写成这样:import time def benchmark_inference(model, inputs, warmup=20, repeat=200): # 预热 for _ in range(warmup): model.predict(*inputs) latency_list = [] for _ in range(repeat): start = time.perf_counter() model.predict(*inputs) latency_list.append(time.perf_counter() - start) latency_list.sort() avg = sum(latency_list) / len(latency_list) p50 = latency_list[int(len(latency_list) * 0.50)] p99 = latency_list[int(len(latency_list) * 0.99)] return {"avg": avg, "p50": p50, "p99": p99}测的时候还要注意记录硬件功耗和温度。很多服务器端推理在高温下会降频,如果量化模型和原模型的测试顺序不对,可能把温度因素算到量化头上。我习惯先把两个模型交替各跑一轮,而不是连续测完一个再测另一个,这样能减少环境漂移。4. 常见坑与排查实录4.1 量化后精度跳水,怎么定位和恢复量化后精度下降最常见的原因是激活范围统计不准,其次是某些层本身对数值变化敏感。我一般按下面的顺序排查:现象可能原因处理方向整体精度下降校准集不具代表性重新采样,增加覆盖范围个别类别精度崩离群点导致scale过大用百分位截断输出边界值异常激活量化范围过窄激活改为per-channel关键层误差大该层对量化敏感混合精度,关键层保持浮点误差分布均匀但明显模型本身容错差转QAT或使用更高精度排查时不要凭感觉调。先把量化前后的逐层输出偏差打印出来,专门找那些误差增长最快的层。我遇到过某个残差结构里的分支,只有几十个权重值,但量化后误差被残差连接不断放大。把这一层单独保留FP16后,整体精度立刻回到了可接受范围。如果单层排查太麻烦,还有个偷懒但有效的方法:从后往前逐步把各层恢复成FP32,观察精度什么时候达标。这个方法虽然粗,但在黑盒模型上很好用。4.2 量化后速度反而变慢的五个原因很多人在第一次量化后会碰上一个很沮丧的结果:模型变小了,速度却慢了。根据我的经验,原因大概逃不出这几个。第一,算子不支持。目标推理引擎里没有该算子的低精度实现,运行时会插入反量化转回浮点,计算完再重新量化,一来一回多了好几遍内存拷贝。这种情况最典型,profile里能看到大量Dequant节点。第二,算子重排不够好。数据布局切换,比如NCHW到NHWC,如果每次推理都要转换,开销非常大。推理引擎一般会做权重重排,但动态输入尺寸常常导致激活也需要重复转换。第三,模型太小。量化在某些模型上省掉的权重带宽不足以抵消额外开销。比如一个只有几MB的小模型,或者batch size为1时的短序列推理,INT8的指令优势可能发挥不出来。第四,CPU核数或GPU算力不够。低精度指令确实快,但如果硬件本身弱到连指令的开销都比浮点高,那就没有意义。老一些的移动处理器尤其明显。第五,batch size和并发设置不合理。INT8量化让每路显存占用变小之后,你可能想开更大batch,但如果带宽已经饱,更大batch反而会增加排队时间。不要只看单batch延迟,要看整体吞吐。遇到速度变慢,第一步先profile算子级耗时,而不是调代码。把耗时前五名的算子打印出来,看看里面有没有反量化节点,事情就清楚了大半。4.3 不同引擎之间的量化结果为什么对不上同一个FP32模型,在两个推理引擎里各自量化到INT8,最后的输出结果经常不一样,甚至性能差异很大。这不是玄学,主要是量化参数选法、算子实现和数据类型定义不同导致的。比如有的引擎默认权重采用对称量化,有的默认使用非对称。同一个权重矩阵,scale和zero_point都不一致,输出自然会有一点点不同。再比如激活范围,A引擎每次采样100张图做百分位校准,B引擎只取32张图且用全局max,分布不同结果就不同。这种差异不是bug,但会造成部署事故。我的做法是:先明确一个“参考量化参数”,最好由模型侧团队统一给出,再在各个推理引擎里尽量保持一致性。如果某个引擎不支持某类配置,我会单独比较它和参考方案的输出误差,再决定是否接受。我曾经遇到过两个引擎同样宣称INT8,但一个跑完mAP掉了0.5个点,另一个掉了1.8个点。最后发现是后者把激活量化从非对称改成了对称,而池化层的激活分布刚好是正区间,对称量化白白浪费了一半取值范围。这不是引擎坏,是配置不匹配。5. 不同场景下的量化方案决策5.1 动手量化前先回答这四个问题在真实项目里,我不建议直接拿着模型就开始跑量化脚本,先回答四个问题更稳妥。第一个问题:模型卡在哪个资源上?如果显存只用了不到一半,速度瓶颈也可能不在显存,那么量化对性能的帮助未必明显。如果模型已经出现显存换页或者CPU内存占用过高,量化的收益会立竿见影。第二个问题:目标硬件到底支持什么?有些平台对INT8支持很好,但对INT4几乎没有加速能力;有些平台只支持权重对称量化,不支持激活非对称。这些约束要先查清楚。第三个问题:精度预算有多少?模型最终指标能不能接受掉0.5%或1%?如果量化后掉点不可接受,就要提前规划混合精度或QAT的预算。第四个问题:工程周期是多少?一周内要上线,就选静态量化,不吃QAT。一个月以上的项目,可以考虑QAT。时间长短决定你用哪条方案。5.2 我常用的量化落地节奏我自己现在比较稳定的节奏是五步走:第一步,先做FP32/FP16基线,记录精度和性能,这个基线后面所有版本都要对齐。 第二步,用静态量化跑一遍INT8,校准集用验证集随机采样,别用训练集。 第三步,对比精度,如果掉点可控,直接进入性能压测和长期稳定性测试。 第四步,如果掉点不可控,先做逐层误差分析,找到敏感层做混合精度回退。 第五步,混合精度仍不行,再启动QAT,同时重点监控早期训练的稳定性。这个节奏的核心思想是“逐步升级”。好处是每一步都能验证,不会因为一次大动作导致问题范围扩大。很多刚接触量化的同学喜欢一步到位直接部署INT4,结果发现精度崩了又不知道是校准集问题、算子问题还是模型本身问题,最后只能回滚,反而浪费更多时间。大模型场景其实是另一个变体。参数量很大的情况下,整个模型甚至无法完整加载进FP16推理,这时候量化成了“能不能部署”的硬约束。遇到这种情况,我的顺序依然是先做INT8静态量化,确保算子都支持;如果内存仍然紧张,再考虑把部分结构用更低比特位折中处理,并专门量化权重、保留激活更高精度。这类方案的优化手段往往比普通模型更复杂,但决策逻辑依然是先满足“能跑”再追求“快”。我个人不太迷信“尽量把每个算子都量化”这种思路。量化完成率不是越高越好,精度和性能之间永远存在一个平衡点。只要能满足业务的精度要求和时延预算,哪怕有几个算子留在FP16,也比为了追求100%量化而反复调QAT更符合工程现实。最后再分享一个小习惯:一旦量化模型跑通,我会把量化后的权重二进制也保存一份,和FP32权重做对比。这样后续如果线上出现数值异常,可以直接比较输入输出哈希,快速判断是量化模型出了问题还是数据分布发生变化。量化不是一次性工程,后面监控和回归测试同样重要。
在AI推理优化这条路上,量化是我见过投入产出比最高的手段。很多人以为量化就是把模型从FP32变成INT8,体积缩小、推理变快,但实操后才发现,AI模型量化对推理性能的影响并不只有正向:有时速度反而下降,精度却掉得莫名其妙。这篇文章会用落地视角拆解量化到底改变了什么、不同量化的适用范围、以及我在真实项目中遇到过的问题和排查方法。如果你正准备做模型部署,尤其需要控制显存和时延,这篇文章可以让你少走不少弯路。1. 为什么要做模型量化:把收益和成本先算清楚1.1 量化最直接的收益:体积缩小和显存翻倍先看最容易被感知到的收益:模型体积。以100MB的FP32模型为例,改成FP16后体积降到50MB左右,改成INT8后降到25MB,改成INT4后甚至可以压到12.5MB。这种压缩不是“压缩包”,而是把每个权重存储的物理位宽直接减下来,GPU显存、CPU内存、磁盘加载量全部跟着缩小。这个收益在服务端尤其明显。我有一次部署一个中等规模的模型,原FP32权重约400MB,单机并发8路推理时,显存占用很快就顶到上限。换成INT8之后,权重占用直接减半,显存空出来一大块,batch size可以调大,吞吐立刻上来。原因很简单:显存带宽有限,每次前向传播要反复读取权重,位宽减半意味着同样时间可以搬运更多的张量。不同精度的存储和带宽占比大致如下:精度权重位宽体积相对FP32权重带宽占用相对FP32FP3232bit100%100%FP16/BF1616bit50%50%INT88bit25%25%INT44bit12.5%12.5%这里要提醒一句:带宽占用是“相对”的,真实影响取决于算力瓶颈还是带宽瓶颈。如果模型是卷积、矩阵乘法这类计算密集型算子为主,带宽占比未必是主要矛盾;但如果是大规模语言模型、深层Transformer,权重体量巨大,带宽往往更先成为瓶颈。1.2 带宽和算力:真正影响推理时延的两块硬骨头很多人以为量化之后计算量会减少,这是个误区。量化通常不会减少FLOPs,卷积还是要做那么多乘法累加,矩阵乘还是要做那么多FLOPs。它真正改变的是数据搬运量和单个运算的位宽效率。现代CPU、GPU和端侧NPU往往对INT8有更高效的SIMD或张量指令。比如同一种处理器,FP32吞吐可能是1,INT8吞吐可能是2到4倍。这时候模型如果恰好在这些算子上,量化后的确会明显变快。反过来,如果硬件对INT8支持很差,走的是通用算术单元,那INT8可能不但不变快,反而因为精度变低导致结果错误。还有一个经常被忽略的问题:数据布局。很多推理引擎在量化后需要把张量从NCHW转换成NHWC,或者插入连续的反量化和再量化算子。这些额外操作本身需要时间。所以,衡量量化影响时不要看“推理引擎声称的支持”,要看实际profile出来的单算子耗时占比。1.3 量化成本:精度损失和工程复杂度要一起看量化不是免费午餐。把权重从FP32压缩到INT8,意味着每个值丢掉了约24bit的尾数精度。模型本身容错性强的,比如常见图像分类模型,精度损失可能控制在零点几个百分点;但检测、分割、语音识别这类对数值范围敏感的模型,可能掉1%到3%的mAP或ACC,甚至更多。工程成本也不能忽略。静态量化需要准备校准集,设置量化配置,处理不支持的算子,排查精度回退原因,还要做不同引擎的验证。一个小型模型可能一天搞定,一个大模型或者结构复杂的模型,量化调优用掉一周很正常。所以在动手量化前,先想清楚这个成本是不是值得。我的习惯是先做一个“收益预估表”:把模型现在的体积、时延、精度记录好,再算一下目标硬件对INT8/INT4的支持情况,如果预期收益不超过20%而且模型体积没压力,那不如不量化,直接保留FP16部署。2. 量化原理和流派选型:先理解再动手2.1 量化映射公式中的关键参数不管哪种量化,核心都是把一个浮点数范围映射到离散整数范围。以INT8为例,非对称量化的映射可以简化为:x_int = round(x / scale) + zero_point然后范围限制在[-128, 127]。反量化就是反过来:x_dequant = (x_int - zero_point) * scale这里最关键的参数是scale和zero_point。scale代表浮点数值范围被压缩的程度,zero_point用来处理非对称范围。对称量化一般让zero_point固定为0,对权重友好;非对称量化更适合激活值,因为激活经常不是以0为中心分布。量化粒度也要说清楚。per-tensor是整层用一个scale和zero_point,简单但容易让数值范围被个别离群点拉大,导致大部分数值精度变低。per-channel是按通道单独统计,精度明显更好,在卷积层和全连接层里是常用选择。还有更细的group量化,在部分大规模模型里很流行,它把通道分成多个组,每组一套参数,精度更好,但索引和存储复杂度更高。我在工程里的默认选择是:权重用per-channel对称量化,激活用per-tensor或per-row非对称量化。这样在精度和推理引擎支持度之间比较平衡。2.2 PTQ、QAT、动态量化各自的适用场景量化方式主要分三类:训练后量化(PTQ)、量化感知训练(QAT)、动态量化。它们的区别和适用场景差异很大。方案是否需要训练精度表现工程成本适用场景训练后动态量化不需要中等,激活保持浮点低CPU端、控件类模型、对内存敏感训练后静态量化不需要较好,需要校准集中大多数CV/NLP模型量化感知训练需要很好,误差提前建模高精度敏感场景、超低比特模型动态量化最大的特点是只量化权重,激活在推理时动态计算scale,因此不需要校准集,实现快。但它通常对算力提升有限,因为激活的计算很多时候仍是浮点。静态量化会把权重和激活都转成INT8,收益最大,但需要校准集来确定激活的范围。QAT则是在训练时就引入“伪量化”操作,前向传播模拟量化误差,让模型参数适应低精度范围。精度通常比PTQ稳定,但训练流程复杂,调参成本高。一般情况下,先用PTQ跑一遍,如果掉精度在可接受范围就够用;不行再上QAT,而不是一上来就改训练流程。2.3 量化粒度、对称与非对称的选择建议很多人会忽略量化粒度和对称性对性能的影响。其实它们直接影响精度和推理引擎对算子的实现方式。对称量化在硬件上更友好,因为可以去掉zero_point计算,在某些向量指令上少几步计算。激活的对称量化缺点是如果激活大量集中在0到10,对称范围会浪费一半,量化分辨率变差。非对称量化利用zero_point把0到10映射到0到255,分辨率更高,但会增加一点计算开销。实际数据中,权重分布通常接近0均值,所以对称量化是安全选择。激活分布往往偏在正区间,非对称量化更合适。如果检测到某一层激活分布极端,优先尝试per-channel量化而不是直接把整个模型切到QAT。对于超低比特场景,比如INT4,单纯per-tensor几乎必然掉点很多。这时候要么用group量化,要么用混合精度,把关键层保留INT8。这比盲目追求全部INT4要可靠很多。3. 实操:从FP32到INT8的完整流程与性能验证3.1 推理引擎选型思路:CPU、GPU和端侧分开考虑不同硬件平台给的量化支持差异很大。服务端GPU通常有专门的高效INT8推理路径,模型转换后可以直接利用硬件张量核心;CPU端则要看一下指令集是否支持向量化低精度计算;移动端或嵌入式端经常需要组合不同的后端算子。选型时先问三个问题:目标硬件是什么,模型推理的瓶颈在带宽还是算力,部署框架是否支持该模型的所有关键算子。如果某个引擎不支持某一类算子,它会自动回退到FP32计算,这就导致“表面量化了,实际还有大半层是浮点”,速度自然上不去。我的经验是,先做一个最小化验证:把一个单层模型量化后跑通,确认引擎能够识别量化算子,再扩展到完整模型。这可以避免一上来就在上百层模型里找问题。下面这段话是伪代码,用来表达我通常采用的静态量化准备流程:# 伪代码:量化准备、校准、转换三步 model.config.quant_mode = "static" # 静态量化 model.config.weight.quant_type = "int8" # 可选 int4 model.config.weight.quant_granularity = "per_channel" # 权重逐通道 model.config.activation.quant_type = "uint8" # 激活非对称 model.config.activation.quant_granularity = "per_tensor" prepared_model = quantize.prepare(model, calibration_loader) quantized_model = quantize.convert(prepared_model)这里的关键点是calibration_loader,也就是校准集,后面的会专门展开。3.2 校准集准备与量化配置:这一步直接决定精度校准集在整个量化流程里最容易被低估。它负责统计激活值的分布范围,校准集选不好,后续精度怎么调都不对。校准集不能直接用训练集,也不能只拿最简单的样本。训练集往往和推理真实分布有偏差,太常见的样本会让激活范围偏窄,导致推理时遇到更极端值在整数范围外直接截断。我一般会从验证集中随机采样,覆盖不同类别、亮度、文本长度、语速等多种情况,样本量控制在100到200个就够,多了也只是浪费时间。校准集里也要留意离群点。一个超大的浮点激活值会把scale拉得非常大,其他正常激活值的量化分辨率就被稀释了。遇到这种情况,可以先统计激活值的百分位,比如把最大值放到99.9百分位而不是全局max,然后把离群点截断。很多推理引擎的校准方法支持这种可选设置,默认不一定是全局max。配置细节上,我建议权重用per-channel,激活用per-tensor。如果你发现精度还不够,不要立刻把整个模型切成QAT,先试着把某个关键层改成更高精度,比如保持FP16或INT16,先看误差是局部还是全局。3.3 性能基准测试流程:用数据说话性能验证不能只看推理引擎文档里写的加速比,要自己跑一套可复现benchmark。我的流程固定五步:固定输入尺寸和batch size;先用相同的输入各跑20次预热,让内部分配和缓存稳定;再连续测200次,记录单次时延和平均时延;分别统计FP32/FP16/INT8三组的P50和P99;再测一组batch size变化后的吞吐量。伪代码可以写成这样:import time def benchmark_inference(model, inputs, warmup=20, repeat=200): # 预热 for _ in range(warmup): model.predict(*inputs) latency_list = [] for _ in range(repeat): start = time.perf_counter() model.predict(*inputs) latency_list.append(time.perf_counter() - start) latency_list.sort() avg = sum(latency_list) / len(latency_list) p50 = latency_list[int(len(latency_list) * 0.50)] p99 = latency_list[int(len(latency_list) * 0.99)] return {"avg": avg, "p50": p50, "p99": p99}测的时候还要注意记录硬件功耗和温度。很多服务器端推理在高温下会降频,如果量化模型和原模型的测试顺序不对,可能把温度因素算到量化头上。我习惯先把两个模型交替各跑一轮,而不是连续测完一个再测另一个,这样能减少环境漂移。4. 常见坑与排查实录4.1 量化后精度跳水,怎么定位和恢复量化后精度下降最常见的原因是激活范围统计不准,其次是某些层本身对数值变化敏感。我一般按下面的顺序排查:现象可能原因处理方向整体精度下降校准集不具代表性重新采样,增加覆盖范围个别类别精度崩离群点导致scale过大用百分位截断输出边界值异常激活量化范围过窄激活改为per-channel关键层误差大该层对量化敏感混合精度,关键层保持浮点误差分布均匀但明显模型本身容错差转QAT或使用更高精度排查时不要凭感觉调。先把量化前后的逐层输出偏差打印出来,专门找那些误差增长最快的层。我遇到过某个残差结构里的分支,只有几十个权重值,但量化后误差被残差连接不断放大。把这一层单独保留FP16后,整体精度立刻回到了可接受范围。如果单层排查太麻烦,还有个偷懒但有效的方法:从后往前逐步把各层恢复成FP32,观察精度什么时候达标。这个方法虽然粗,但在黑盒模型上很好用。4.2 量化后速度反而变慢的五个原因很多人在第一次量化后会碰上一个很沮丧的结果:模型变小了,速度却慢了。根据我的经验,原因大概逃不出这几个。第一,算子不支持。目标推理引擎里没有该算子的低精度实现,运行时会插入反量化转回浮点,计算完再重新量化,一来一回多了好几遍内存拷贝。这种情况最典型,profile里能看到大量Dequant节点。第二,算子重排不够好。数据布局切换,比如NCHW到NHWC,如果每次推理都要转换,开销非常大。推理引擎一般会做权重重排,但动态输入尺寸常常导致激活也需要重复转换。第三,模型太小。量化在某些模型上省掉的权重带宽不足以抵消额外开销。比如一个只有几MB的小模型,或者batch size为1时的短序列推理,INT8的指令优势可能发挥不出来。第四,CPU核数或GPU算力不够。低精度指令确实快,但如果硬件本身弱到连指令的开销都比浮点高,那就没有意义。老一些的移动处理器尤其明显。第五,batch size和并发设置不合理。INT8量化让每路显存占用变小之后,你可能想开更大batch,但如果带宽已经饱,更大batch反而会增加排队时间。不要只看单batch延迟,要看整体吞吐。遇到速度变慢,第一步先profile算子级耗时,而不是调代码。把耗时前五名的算子打印出来,看看里面有没有反量化节点,事情就清楚了大半。4.3 不同引擎之间的量化结果为什么对不上同一个FP32模型,在两个推理引擎里各自量化到INT8,最后的输出结果经常不一样,甚至性能差异很大。这不是玄学,主要是量化参数选法、算子实现和数据类型定义不同导致的。比如有的引擎默认权重采用对称量化,有的默认使用非对称。同一个权重矩阵,scale和zero_point都不一致,输出自然会有一点点不同。再比如激活范围,A引擎每次采样100张图做百分位校准,B引擎只取32张图且用全局max,分布不同结果就不同。这种差异不是bug,但会造成部署事故。我的做法是:先明确一个“参考量化参数”,最好由模型侧团队统一给出,再在各个推理引擎里尽量保持一致性。如果某个引擎不支持某类配置,我会单独比较它和参考方案的输出误差,再决定是否接受。我曾经遇到过两个引擎同样宣称INT8,但一个跑完mAP掉了0.5个点,另一个掉了1.8个点。最后发现是后者把激活量化从非对称改成了对称,而池化层的激活分布刚好是正区间,对称量化白白浪费了一半取值范围。这不是引擎坏,是配置不匹配。5. 不同场景下的量化方案决策5.1 动手量化前先回答这四个问题在真实项目里,我不建议直接拿着模型就开始跑量化脚本,先回答四个问题更稳妥。第一个问题:模型卡在哪个资源上?如果显存只用了不到一半,速度瓶颈也可能不在显存,那么量化对性能的帮助未必明显。如果模型已经出现显存换页或者CPU内存占用过高,量化的收益会立竿见影。第二个问题:目标硬件到底支持什么?有些平台对INT8支持很好,但对INT4几乎没有加速能力;有些平台只支持权重对称量化,不支持激活非对称。这些约束要先查清楚。第三个问题:精度预算有多少?模型最终指标能不能接受掉0.5%或1%?如果量化后掉点不可接受,就要提前规划混合精度或QAT的预算。第四个问题:工程周期是多少?一周内要上线,就选静态量化,不吃QAT。一个月以上的项目,可以考虑QAT。时间长短决定你用哪条方案。5.2 我常用的量化落地节奏我自己现在比较稳定的节奏是五步走:第一步,先做FP32/FP16基线,记录精度和性能,这个基线后面所有版本都要对齐。 第二步,用静态量化跑一遍INT8,校准集用验证集随机采样,别用训练集。 第三步,对比精度,如果掉点可控,直接进入性能压测和长期稳定性测试。 第四步,如果掉点不可控,先做逐层误差分析,找到敏感层做混合精度回退。 第五步,混合精度仍不行,再启动QAT,同时重点监控早期训练的稳定性。这个节奏的核心思想是“逐步升级”。好处是每一步都能验证,不会因为一次大动作导致问题范围扩大。很多刚接触量化的同学喜欢一步到位直接部署INT4,结果发现精度崩了又不知道是校准集问题、算子问题还是模型本身问题,最后只能回滚,反而浪费更多时间。大模型场景其实是另一个变体。参数量很大的情况下,整个模型甚至无法完整加载进FP16推理,这时候量化成了“能不能部署”的硬约束。遇到这种情况,我的顺序依然是先做INT8静态量化,确保算子都支持;如果内存仍然紧张,再考虑把部分结构用更低比特位折中处理,并专门量化权重、保留激活更高精度。这类方案的优化手段往往比普通模型更复杂,但决策逻辑依然是先满足“能跑”再追求“快”。我个人不太迷信“尽量把每个算子都量化”这种思路。量化完成率不是越高越好,精度和性能之间永远存在一个平衡点。只要能满足业务的精度要求和时延预算,哪怕有几个算子留在FP16,也比为了追求100%量化而反复调QAT更符合工程现实。最后再分享一个小习惯:一旦量化模型跑通,我会把量化后的权重二进制也保存一份,和FP32权重做对比。这样后续如果线上出现数值异常,可以直接比较输入输出哈希,快速判断是量化模型出了问题还是数据分布发生变化。量化不是一次性工程,后面监控和回归测试同样重要。