
做模型部署的人几乎都绕不开一个词量化。我第一次听到它的时候还以为是金融里的那个量化交易后来才发现完全不是一回事。在机器学习与深度学习语境下量化Quantization指的是把模型里那些 FP32、FP16 的浮点权重和激活值用更低位宽的整数INT8、INT4 甚至更低来表示和计算。它解决的问题非常具体模型太大装不下、推理太慢撑不住、显存带宽吃满但算力闲着。一个 7B 参数的模型FP16 存下来要 14GBINT8 只要 7GBINT4 压到 3.5GB 左右这在单卡部署、边缘设备、端侧推理场景里是生死线级别的差异。这篇内容适合三类人看一是刚做完模型训练、准备上线做推理优化的工程师二是被显存和延迟卡住、想搞清楚量化到底安不安全的算法同学三是对底层数值表示感兴趣、想弄明白“为什么 8bit 几乎不掉点、4bit 就开始翻车”的读者。我会从数学定义一路讲到实操流程和踩坑记录尽量把每一步为什么这么设计都说清楚。1. 量化到底在做什么把连续浮点数塞进离散格子里1.1 从一次线上延迟排查说起几年前我参与过一个语音唤醒模型的端侧移植。模型本身不大参数量三百万出头FP32 存下来 12MB 左右看起来完全不是问题。但真正跑到目标硬件上单次推理延迟是 90ms远超 30ms 的预算。当时第一反应是算力不够后来用性能分析工具一拆发现算力占用只有 20%剩下全是内存搬运——权重从外部存储搬进片上缓存的耗时占了七成以上。这就引出了量化的第一个、也是最容易被忽略的价值它首先是省带宽的其次才是省算力的。深度学习推理的瓶颈分两种。一种是计算密集compute-bound比如大 batch 的卷积层乘加次数多硬件算力打满另一种是访存密集memory-bound比如自回归解码阶段的矩阵向量乘batch 只有 1每一层都要把全部权重从显存读一遍算力单元大部分时间在等数据。参数量 N、位宽 b 位那么每生成一个 token 需要搬运的字节数约为 N × b / 8。拿一个 70 亿参数的模型举例FP16 是 14GB假设显存带宽 2TB/s理论上限是每秒 2000/14 ≈ 143 个 token换成 INT8权重降到 7GB同样的带宽下理论上限翻倍到 286 个 token。这个推算很粗糙忽略了 KV 缓存和实际带宽利用率但足够说明问题位宽直接决定了访存密集场景的性能天花板。所以量化的本质是把“精度”这个维度当成一种可以交易的资源。你放弃一部分数值表示精度换回来的是更小的存储、更低的带宽压力、更高的缓存命中率以及在支持低精度矩阵乘的硬件上成倍的算力提升。关键在于这份精度损失能不能被模型本身的冗余度吸收掉。实测下来大部分视觉和语言模型对权重扰动有相当强的容忍度8bit 量化往往连 0.5 个百分点的精度都掉不到这就是量化能大规模落地的前提。1.2 量化的数学定义与误差来源把浮点数映射到整数本质是一个仿射变换。给定一个浮点张量 x量化过程写成q clamp(round(x / S) Z, q_min, q_max) x_hat S * (q - Z)其中 S 是缩放因子scaleZ 是零点zero pointq_min 和 q_max 是目标整数类型的表示范围比如 INT8 是 -128 到 127。round 是就近取整x_hat 是反量化回来的近似值。注意这个映射是“多对一”的连续区间里的无数个浮点数被压到同一个整数格子上信息在这一步不可逆地丢了。误差有三个来源必须分开看因为它们的应对手段完全不同。第一是舍入误差也就是 round 带来的偏差它的量级是 S/2均匀分布在每个格子内属于白噪声性质对模型输出影响相对温和。第二是截断误差也就是 clamp 那一刀超出 [q_min, q_max] 范围的值被硬砍到边界上这部分误差不是噪声而是系统性偏移杀伤力最大典型的元凶就是激活值里的离群点。第三是共享误差当多个权重共用同一个 S 时动态范围大的那部分会挤占分辨率导致小幅度的权重被量化得极其粗糙。用一个具体例子感受一下。某层权重的范围是 [-0.8, 0.8]用对称 INT8 量化S 0.8 / 127 ≈ 0.0063。那么任何小于 0.00315 的权重都会被量化成 0直接消失。如果这一层里恰好有大量接近零的细碎权重它们承担着模型输出的“微调”作用集体归零之后输出就会整体漂移。这就是为什么按张量per-tensor量化在深层网络里经常出问题——整层共享一个 S而层内不同通道的数值尺度可能差几十倍。所以业内常用一个指标来量化这种损失量化信噪比SQNR定义为信号功率与量化噪声功率之比。理论上位宽每增加 1 bitSQNR 提升约 6dB。从 FP32 到 INT8 看起来掉了 24bit但 FP32 在深度学习里本来就是极大浪费——权重实际需要的有效位数往往只有 8 到 10 位剩下的位数都在表示那些对输出毫无影响的微小差异。1.3 缩放因子怎么算对称与非对称的取舍缩放因子 S 的确定方式决定了量化的上限。最朴素的是 MinMax 校准S max(|x|) / 127对称或者 S (max(x) - min(x)) / 255非对称。这种做法实现简单但它对离群点极度敏感——只要有一个值特别大整个张量的分辨率就被它一个人吃掉了。对称量化的特点是零点固定为 0整数范围关于原点对称INT8 用 -127 到 127 而不是 -128 到 127就是为了保持对称性。它的优势是计算简单反量化时不需要减去零点两个对称量化的张量做点积结果仍然是对称的硬件实现可以省掉一次减法。权重天然是零中心近似对称分布的所以权重几乎总是用对称量化。非对称量化允许零点偏移INT8 用满 -128 到 127 共 256 个格子。它的优势是能更好地贴合非负分布比如 ReLU 之后的激活值全在 [0, ∞)用非对称量化可以避免一半的整数范围被白白浪费。代价是每次计算都要处理零点偏移卷积和矩阵乘里要额外做修正项。维度对称量化非对称量化零点固定为 0随数据分布偏移整数范围-127 到 127-128 到 127适用对象权重、零中心分布ReLU 后激活、非负分布计算开销低无反量化偏移稍高需处理零点离群点敏感度高中等实现复杂度简单需要额外的零点存储与修正实操中还有一个细节容易被忽略S 的计算到底用不用绝对值最大。有些工具链会采用百分位截断比如取 99.99% 分位数的绝对值作为 max故意把极少数离群点砍掉。这样做虽然引入了截断误差但换来了更高的整体分辨率。我在一个图像分割模型上试过把激活值的 max 从绝对最大值换成 99.9% 分位数mIoU 反而涨了 0.3 个点——因为那几个离群点本身就是噪声把它们砍掉等于顺带做了一次去噪。2. 方案选型PTQ、QAT 与量化粒度怎么定2.1 训练后量化与量化感知训练的真实取舍训练后量化Post-Training QuantizationPTQ指的是拿一个已经训练好的浮点模型用一小批校准数据跑一遍前向统计各层的数值分布直接算出 S 和 Z然后把权重量化掉。整个过程不需要反向传播不需要标签GPU 上几十分钟就能跑完一个大模型。它的吸引力在于成本极低工程上几乎是“改一行配置”的事。量化感知训练Quantization-Aware TrainingQAT则是在训练或微调阶段就把量化的影响模拟进去。具体做法是在前向图里插入“伪量化”节点先按量化公式把浮点数量化再反量化让数值带上真实的量化误差然后正常走前向和反向。问题在于 round 操作不可导梯度到这里就断了。解决办法是直通估计器Straight-Through EstimatorSTE反向传播时假装 round 是一个恒等映射梯度原样传过去。前向 y round(x / S) * S 反向 dy/dx 1 忽略 round 的导数STE 在数学上显然是错的但工程上极其有效。原因在于 round 的导数在几乎所有点都是 0只在整数点不可导如果老老实实按真实导数传梯度会全线归零模型根本学不动。用恒等映射相当于告诉模型“你就当量化不存在但前向时感受一下它的扰动”模型会自己学会把权重调整到量化格子的中心附近减少量化误差。什么时候必须上 QAT我的经验是8bit 权重量化加 8bit 激活量化PTQ 基本够用精度损失通常在 1 个百分点以内一旦降到 4bit 权重PTQ 的损失开始明显尤其是小模型和已经精调过的模型降到 4bit 权重加 4bit 激活或者 3bit 以下PTQ 基本不可用必须上 QAT 或者更精细的重构式方法。这个门槛不是绝对的跟模型的冗余度强相关——参数量大、训练不充分的模型对量化更宽容。方案数据需求时间成本典型精度损失适用位宽PTQMinMax100-500 条校准样本分钟级0.5-1.5 个点8bitPTQKL/MSE 校准200-1000 条十分钟级0.3-1.0 个点8bit勉强 6bitQAT完整训练集或子集数小时到数天0.1-0.5 个点8bit / 4bit重构式低位量化校准集 二阶信息单卡数小时4bit 下 1-3 个点4bit / 3bit2.2 量化粒度为什么要按通道甚至按组粒度是量化方案里对精度影响最大的一个旋钮比对称与否重要得多。per-tensor 是所有元素共用一个 S实现最简单但对层内动态范围差异大的模型很不友好。per-channel 是每个输出通道一个 S权重量化里这是标配因为卷积核的每个输出通道在数值上是相对独立的尺度差异可达百倍。per-group 则是把每个通道再切成固定大小的组每组一个 S这是 4bit 以下权重量化的事实标准。组大小group size的选择是个纯粹的工程权衡。组越小每组内的动态范围越集中量化误差越小但每组都要额外存一个 scale存储开销随组数线性增长。算一下4bit 权重组大小 128每组的 scale 用 FP16 存那么每个权重额外负担 16 / 128 0.125 bit实际平均位宽是 4.125bit。如果把组大小降到 32额外开销变成 0.5bit平均位宽 4.5bit存储增加约 9%。组大小降到 16额外开销 1bit平均 5bit存储增加 21%。组大小额外 scale 开销平均位宽精度表现存储增幅per-channel可忽略4bit 少量一般基准1280.125 bit4.125 bit良好3%640.25 bit4.25 bit较好6%320.5 bit4.5 bit好12%161 bit5 bit很好25%我的实际选择习惯是语言模型权重量化组大小 128 是性价比拐点再往下收益递减视觉模型因为通道数少、每通道元素多per-channel 就足够如果是小模型参数量小于一亿建议直接上组大小 32 或 64因为它本身冗余度低经不起粗粒度量化的折腾。2.3 激活量化为什么比权重量化难权重是训练完就固定的量化误差可以事先精确计算和补偿。激活值不一样它依赖输入是动态变化的每一批数据过来分布都可能不同。这带来的核心难题是校准阶段统计出来的 S在推理时遇到没见过的输入分布就可能失效导致大量值被 clamp 到边界输出直接崩掉。更麻烦的是激活值里的离群点。业界有个被反复观察到的现象某些通道的激活值幅度会是其他通道的几十甚至上百倍而且这些离群点集中在固定的几个通道上跟输入内容关系不大。如果按 per-tensor 量化这几个通道会把整个张量的 S 拉大其他通道全被压成零附近。解决办法通常是按通道per-channel给激活也算 scale代价是硬件上要额外做一次逐通道缩放部分推理后端不支持这种混合粒度实测会掉回 FP16 执行。工程上的折中方案是权重必做 per-channel 或 per-group 量化激活保持 per-tensor 但用更聪明的校准算法或者干脆做混合精度——大部分层 INT8离群点严重的少数层保留 FP16。这个“少数层”的比例通常不到 5%性能损失很小但精度能救回来一大截。3. 完整实操从 FP32 到 INT8 的落地流程3.1 校准集怎么构造才算合格校准集是整个 PTQ 流程里最容易被敷衍、又最影响结果的环节。我见过太多人直接从训练集里随机采样 100 条就开跑然后在测试集上发现精度掉了 3 个点回头怀疑量化算法不行——其实问题出在校准集没代表性。合格的校准集要满足三个条件。第一是覆盖真实输入分布如果线上输入以某种特定场景为主比如都是低光照图像、都是短文本校准集就应该反映这个偏斜而不是均匀采样训练集。第二是数量适中一般 100 到 500 条足够太少统计不稳定太多收益递减还拖慢流程。第三是不能用训练时做过强增强的数据因为校准的目标是估计推理时的真实数值范围增强后的数据分布和线上分布有偏差。具体做法我一般是这样从线上日志或者验证集里抽 200 条左右确保覆盖到各类边界情况最短输入、最长输入、极端亮度、特殊字符然后单独存成一个校准数据集跟训练集解耦。这一步多花半天后面能省掉好几轮来回调试。注意校准集千万不要包含标签泄漏或者分布外数据。用一批明显偏离真实场景的数据做校准算出来的 scale 会严重失真量化后的模型在正常输入上反而表现更差这种问题极难排查因为报错信息里什么都不会提示。3.2 校准算法MinMax、KL 散度与 MSE 搜索MinMax 最简单直接取校准过程中出现的最大最小值。它的缺点是只要出现一个极端值整个 range 就被撑大。改进思路有两种一种是百分位截断取 99.9% 或 99.99% 分位数人为砍掉离群点另一种是基于分布匹配的搜索在候选阈值里找一个让量化前后分布差异最小的。KL 散度校准的思路是把浮点激活值的直方图当成真实分布 P把量化后的直方图当成近似分布 Q遍历不同的截断阈值 T计算 KL(P||Q)取使散度最小的 T。这个方法的理论依据是信息论——最小化分布差异等价于最小化信息损失。实现上要注意直方图的 bin 数量通常用 2048 或 4096 个 binbin 太少分辨率不够太多计算慢且容易过拟合噪声。MSE 搜索更直接对每个候选阈值 T模拟一遍量化再反量化计算重建误差的均方值取最小的那个。它比 KL 散度更贴近“最小化输出误差”这个真实目标实测在视觉模型上通常略优于 KL在语言模型上差异不明显。计算量比 KL 大一些因为要做完整的量化反量化模拟。伪代码MSE 阈值搜索 for T in 候选阈值列表 (从 0.5*max 到 max步进 0.01*max): S T / 127 q clamp(round(x / S), -127, 127) x_hat q * S err mean((x_hat - x)^2) 记录 (T, err) 选出 err 最小的 T 作为最终截断阈值有个实操细节不同层的激活分布差异很大不要全局用同一个校准策略。浅层靠近输入的激活通常范围大但有结构深层激活范围小但离群点更顽固。我的做法是统一用 MSE 搜索但对浅层额外允许更激进的截断99% 分位对深层保守一些99.99% 分位。3.3 敏感层识别与混合精度策略不是所有层都适合量化。有三类层我一般默认保留高精度第一层直接接触原始输入数值范围最不可控、最后一层直接决定输出误差无法被后续层吸收、以及归一化层和 softmax 相关的算子它们涉及指数运算和除法对微小扰动极其敏感。识别敏感层的标准做法是逐层敏感度分析把模型的所有层先保持 FP32然后一次只量化一层测一次精度记录下降幅度最后按下降幅度排序。这个流程要跑几十次推理成本不低但对大模型值得。更省事的近似方法是看数值统计——如果某层的激活值动态范围超过 1000 倍或者量化前后输出的余弦相似度低于 0.99就直接列入保留名单。层类型建议精度原因首层卷积/embeddingFP16 或 INT8 保守校准直接接触原始输入范围不可控中间卷积/线性层INT8冗余度高量化损失可被吸收归一化层FP16涉及除法与方差扰动放大注意力 softmaxFP16指数运算对输入偏移极敏感残差累加FP16 或 INT16 累加累加误差会逐层累积输出投影层FP16直接决定最终输出分布混合精度的实现方式有两种一是逐层指定精度工具链里给每层打标记二是整体量化后再做误差补偿把敏感层的量化误差用低秩分解或者额外的 FP16 副本补回来。前者简单直接后者适合不能改模型结构的场景。3.4 4bit 权重量化分组加误差补偿到了 4bit纯粹的四舍五入已经不够用了。原因很简单4bit 只有 16 个格子量化步长是范围除以 15一个标准差范围内的权重可能只落到 3 到 4 个格子上误差大到模型无法承受。这时候主流做法是引入误差补偿把量化看成一场“逐列的最优化问题”。核心思路是量化是一列一列或一行一行做的每量化完一列就把这一列引入的误差分摊到还没量化的列上让后续列在优化时顺带把这个误差抵消掉。数学上这等价于求解一个带约束的最小二乘问题用二阶信息海森矩阵的近似加权可以解析地求出每一列的最优补偿量。伪代码带误差补偿的逐列量化 H 校准数据上的输入二阶统计近似海森矩阵 for 每一列 j: w 当前列权重 q quantize(w) # 4bit 量化 err (w - dequantize(q)) / H[j,j] # 该列的量化误差 W[:, j1:] - err 外积 传播项 # 把误差摊给后续列这套方法的实测效果相当惊人4bit 权重量化在十亿参数级别的语言模型上困惑度上升通常能控制在 5% 以内比朴素四舍五入好一个数量级。代价是需要跑一遍校准数据来收集二阶统计单卡上要花几十分钟到几小时而且实现复杂度高一般直接用现成工具链。另一个 4bit 场景的关键技巧是激活感知缩放。前面提过激活值有离群通道如果权重按通道量化而某个通道的激活特别大那么这一通道的权重误差会被放大。解决办法是在量化前先做一个等价变换把权重这一列除以缩放系数 s把对应的激活乘上 s数学上完全等价但量化友好度大幅提升。s 的选取通常用激活的逐通道最大值开平方让权重和激活的动态范围均衡。注意等价变换必须作用在相邻的线性层之间遇到非线性层ReLU、LayerNorm就不能跨过去否则等价性被破坏模型输出会直接崩。做这类变换时一定要先在验证集上测通过再上生产。4. 常见问题排查与避坑实录4.1 精度掉点定位从哪一层开始坏量化后精度掉得一塌糊涂最常见的排查误区是直接怀疑量化算法。实际上八成的问题出在工程细节上校准集不对、某层没有被正确量化、量化后又混进了错误的反量化、或者某个算子根本没有低精度实现静默回退到了 FP32 但周边层的数值范围假设全乱了。我的排查顺序是这样的。第一步确认量化覆盖率统计模型里有多少算子真正跑在 INT8 上如果发现关键路径上的算子静默回退了那精度问题和量化算法无关是后端支持问题。第二步逐层比对输出把 FP32 模型和量化模型的每一层输出都 dump 出来算余弦相似度找第一个相似度跌破 0.99 的层。第三步单独量化那一层做验证确认是这一层本身敏感还是前面层的误差传播过来放大了。现象可能原因排查动作精度掉 0.3 个点以内正常量化噪声无需处理可接受掉 1-3 个点校准集不匹配或校准算法太粗换校准集改用 MSE 搜索掉 5 个点以上某层被截断或存在离群通道逐层 dump 输出找断层位置输出全是常数或 NaN零点/缩放因子为 0 或溢出检查该层激活是否全零、S 是否除零只有特定输入出错校准集未覆盖该分布补充边界样本到校准集有个特别隐蔽的坑某些模型在训练时用了混合精度FP16权重里本来就有大量极小值。这些值在 FP32 下看不出来一旦量化到 INT8 就直接归零而且是整批整批地归零。我遇到过一次某层有 40% 的权重绝对值小于 1e-5量化后全部变成 0那一层相当于被剪枝了一大半。解决办法是量化前先做一次权重检查统计接近零的权重比例超过 10% 就要警惕可能需要调整该层的量化范围或者改用非对称量化。4.2 量化了但速度没涨几种典型原因比精度掉点更让人崩溃的是精度没掉、速度也没涨。这种情况我遇到过三次原因各不相同值得单独说说。第一种是反量化开销吃掉了收益。有些后端在每次矩阵乘之前都要把权重从 INT8 反量化回 FP16 再计算等于多了一次内存读取和一次类型转换。原本瓶颈在带宽现在带宽没省INT8 读完还要写一遍 FP16反而多了一次计算。判断方法很简单看性能分析结果里有没有大量类型转换算子如果有说明硬件后端不支持原生的低位矩阵乘。第二种是算子融合缺失。量化带来的收益很大一部分来自减少内存往返如果卷积和后面的激活函数没有融合中间结果要写回内存再读出来省下的带宽又还回去了。这时候需要检查推理引擎的融合策略配置通常有开关可以打开。第三种是内存布局转换。某些硬件对大位宽数据的对齐有要求读 INT8 权重时如果需要按特定步长访问会产生非合并访存实际带宽利用率只有理论值的一半。这种问题只能靠调整张量的内存排布比如改成 NHWC来缓解。提示验证量化收益时一定要在目标硬件上用真实 batch size 测。在 CPU 上用 batch 32 测出来的加速比和在端侧设备上 batch 1 的表现可能完全相反。解码场景的 batch 1 是纯访存瓶颈量化收益最大大 batch 训练推理是计算瓶颈量化收益取决于硬件是否真有低精度算力单元。4.3 几个不看文档学不到的实操心得第一量化后的模型一定要做端到端的输出分布对比而不是只看一个聚合指标。分类任务看准确率检测任务看 mAP这些指标对局部输出漂移不敏感。我曾经有个模型量化后 mAP 只掉了 0.2看起来完美但实际部署后发现小目标的检测框系统性地偏了半个像素——原因是输出层的量化步长正好落在坐标回归的敏感区间。后来我把输出层保留 FP16问题就消失了。做对比时除了聚合指标还要看输出的均值、方差、最大值分布以及分桶统计下的指标变化。第二校准顺序会影响结果。如果模型有分支结构或者残差连接按拓扑序逐层校准和一次性全量校准的结果可能差不少。前者每一层的输入分布是基于前面已经量化的层统计的更接近真实推理链路后者是基于纯 FP32 统计的会有偏差累积。工具链一般默认一次性校准遇到精度问题时可以试试改成按依赖顺序逐段校准。第三不要迷信“8bit 无损”。这个说法在很多场景下成立但前提是模型有足够冗余、校准集足够代表性。对于已经做过蒸馏、剪枝、精调的紧凑模型8bit 量化掉 2 个点并不罕见。量化前先评估模型本身的状态比事后调参有效得多。第四4bit 量化的推理速度不一定比 8bit 快。位宽减半了但如果硬件没有对应的 4bit 计算指令实际执行时还是要解包成 8bit 或 16bit 再算多了解包开销性能可能反而下降。判断依据是看目标硬件是否原生支持 sub-byte 访存和计算不支持的话4bit 的价值主要在于省存储模型能塞进更小的内存而不是提速。第五永远保留一份浮点模型做回归对比。量化模型的失败模式往往是“大部分输入正常、少数输入离谱”靠人工抽查很难发现。我现在的做法是维护一套小规模的黄金测试集几百条每次量化后自动跑一遍对比浮点输出的逐元素差异超过阈值就报警。这套机制帮我拦下过好几次校准集带偏的发布。最后分享一个小技巧如果你的模型量化后精度不达标、又不想上完整的 QAT可以试试只对最后一小部分层做微调比如最后两个 block 加输出层冻结其余层学习率设得很小训练一两百步。这个做法的成本比完整 QAT 低一个数量级很多场景下能把量化损失补回来大半。我在几个视觉模型上试过4bit 量化后原本掉 3 个点微调之后回到掉 0.8 个点性价比相当高。