假如你要把一整个衣柜搬进一辆小货车。大衣、羽绒服、床单被套全都鼓鼓囊囊,直接扔进去肯定盖不上门。会搬家的人会先把衣服叠好、抽真空,让每一件都变成扁平的方块——它们还是原来的衣服,只是把中间蓬松的空气挤掉了。模型量化就是给神经网络“抽真空”:神经网络的记忆写在 32 位浮点数(FP32)里,每个数占 4 字节;量化后改用 8 位整数(INT8)或 4 位整数(INT4)来记,体积直接缩到四分之一甚至八分之一。衣服还是那些衣服,仓库却从四米高变成一米高。当然,抽真空会压出褶子,模型精度会损失一点;而“怎么压”,就是 PTQ 与 QAT 的分野。

岗位关联

对 AI 从业者而言,量化是把云端大模型塞进手机、摄像头、汽车等边缘设备的“打包手艺”,也是衡量模型工程化能力的重要一环。

目录
  1. 可视化演示
  2. 量化基本概念
  3. 量化核心公式
  4. 量化粒度与校准
  5. INT8与INT4
  6. PTQ训练后量化
  7. QAT量化感知训练
  8. 实战落地流程
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

本节配有交互动效(进入视口后自动加载,约 3KB)。

量化基本概念

想象衣帽间里每件衣服都有一根专属衣架,FP32 就像这种宽敞的豪华间:32 个二进制位记录一个数,精确到小数点后 7 位。但手机、摄像头、耳机这些端侧设备只有“登机箱”那么大的空间。量化要做的事,就是给每个数换一个小格子:把 FP32 的 4 字节压成 INT8 的 1 字节,甚至 INT4 的 4 个二进制位——模型参数数量不变,体积却可缩到四分之一或八分之一。

神经网络的数字记忆并不需要每个参数都精确到一亿分之一。大量实践表明,参数和激活值的分布通常集中在一个狭窄区间;把过长的尾巴剪掉,把小数部分舍去,模型依然能完成分类、检测、生成等任务。量化不是“重新发明数字”,而是换一把更粗的尺子:实数世界的一个小区间,用一个整数代号表示。

工程收益很直接:模型文件变小,内存占用下降,数据搬运变快;如果硬件支持 INT8 计算,乘加运算还可以在更短的时钟周期内完成。代价是舍入误差和截断误差——好比把 173.46 厘米报成 173 厘米,单看一条数据误差微不足道,但经过几十层网络层层传递,可能被放大成明显掉点。

衣服叠好只是第一步,下一步要解决一个关键问题:如何保证每个整数代号都能还原到原来的位置?这就要靠量化的核心公式。

量化核心公式

折叠衣服需要一条刻线,否则叠出来的方块大小不一。量化的刻线由两个参数决定:缩放因子 scale 和零点偏移 zero point。scale 决定一个整数格子代表多少实数,zero point 决定浮点 0 落在整数标尺的哪个刻度。

对一个张量统计出最小值 \(x_{\min}\) 和最大值 \(x_{\max}\),b 位整数可表示 \(2^b\) 个值(INT8 是 256,INT4 是 16)。把区间 \([x_{\min}, x_{\max}]\) 均匀切成 \(2^b - 1\) 段,每段的高度就是 scale。为了防止负数在四舍五入后越界,先加 zero point,再做 clip(夹紧到边界内)。

这把折叠尺就是所有量化工具的通用语言。下一步要决定:是给整件大衣用一把尺,还是给每个口袋各配一把。

s=xmax⁡−xmin⁡2b−1z=round(−xmin⁡s)xint=clip(⌊xs⌋+z, 0, 2b−1)x^=(xint−z)×s\begin{aligned}s &= \frac{x_{\max} - x_{\min}}{2^{b} - 1} \\z &= \mathrm{round}\left(-\frac{x_{\min}}{s}\right) \\x_{\mathrm{int}} &= \mathrm{clip}\left(\left\lfloor \frac{x}{s} \right\rfloor + z,\ 0,\ 2^{b}-1\right) \\\hat{x} &= (x_{\mathrm{int}} - z) \times s\end{aligned}
通俗地说,量化就是拿一把只有 \(2^b\) 个刻度的尺子去量实数,记录整数刻度;需要时再用同一把尺子还原,只是还原回来的数已经丢了原来的小数尾巴。
  1. 缩放因子 s把区间 \([x_{\min}, x_{\max}]\) 分成 \(2^b - 1\) 个等宽格子,每个格子的高度就是一个 scale,代表“整数走一步,实数走多远”。
    s=xmax⁡−xmin⁡2b−1s = \frac{x_{\max} - x_{\min}}{2^{b} - 1}
  2. 零点偏移 z由于实数可能是负数,需要把浮点 0 映射到整数标尺上的某个刻度,避免负数被直接切掉。
    z=round(−xmin⁡s)z = \mathrm{round}\left(-\frac{x_{\min}}{s}\right)
  3. 量化 x_int把真实值除以 scale,得到“它在第几格”,向下取整后加上 z,最后裁剪到 \([0, 2^b-1]\) 范围内,得到整数代号。
    xint=clip(⌊xs⌋+z, 0, 2b−1)x_{\mathrm{int}} = \mathrm{clip}\left(\left\lfloor \frac{x}{s} \right\rfloor + z,\ 0,\ 2^{b}-1\right)
  4. 反量化 x_hat从整数代号回到浮点估计值,因为取整已经丢了小数,所以还原结果只是真实值的近似。
    x^=(xint−z)×s\hat{x} = (x_{\mathrm{int}} - z) \times s

量化粒度与校准

逐张量量化,是整层张量共用一套 scale 和 zero point,像把整柜衣服压成一个方块;参数少、部署简单,但数值分布不均匀时误差会集中。逐通道量化,是每个输出通道独立计算尺度,像每件衣服单独叠;体积略大,却更贴身材,精度通常更好。

尺度不能凭空拍脑袋定。训练后量化需要一个校准数据集:通常是 100 到 1000 条有代表性的训练或验证样本,跑一次前向,统计每一层激活的最小值、最大值或直方图。如果校准数据与真实数据差异太大,等于拿夏装清单给冬装打包,箱子一上路就会散架。

校准还要小心离群值。用 min 和 max 会把范围撑得很大,整数刻度被拉宽,中间区域的精度因此下降;改用百分位或 KL 散度截断两端极端值,往往能换回更好的整体精度。

粒度和校准解决的是“怎么量”,接下来要回答“用多粗的尺子”——INT8 还是 INT4。

INT8与INT4

INT8 是经济舱:256 个刻度,大多数模型掉点可控制在 1 到 3 个百分点以内;INT4 是睡袋位:只有 16 个刻度,体积再砍一半,但精度风险显著上升。选哪个,取决于模型对误差的敏感度和业务允许的精度下限。

INT8 通常把 FP32 体积降到四分之一,INT4 降到八分之一。对图像分类这类冗余度高的任务,INT8 几乎无感;对需要细粒度输出的小模型或检测任务,INT4 可能让边界框抖动明显。可以通过误差分析逐层观察:有些层对量化特别敏感,就保留 FP16 或更高精度,形成混合精度。

另一个决定因素是硬件指令。x86、ARM、NPU 往往有成熟的 INT8 加速指令,但 INT4 不一定原生支持;如果硬件不支持,INT4 反而要先反量化成 FP16 再算,推理未必更快。所以不要问“哪个精度最好”,要问“目标设备支持哪种打包方式”。

尺子选好了,接下来看两条落地路线:不训练的 PTQ,和把训练融进去的 QAT。

PTQ训练后量化

PTQ 全称是 Post-Training Quantization,训练后量化。它是“行李已经装好,再抽一遍真空”:不重新训练模型,只对权重和激活做离线转换。流程像一份四步清单:准备校准数据、统计数值范围、计算 scale 和 zero point、替换浮点算子。

PTQ 最大的优点是没有训练资源门槛:几百张图片、几分钟跑前向,就能得到 INT8 模型。它适合大多数已经上线的模型,尤其适合大模型——重新训练成本太高,离线压一压就能部署。

PTQ 的挑战在于误差不可控。模型越小、激活的离群值越多,量化误差越容易被放大。常见补救方法是逐通道量化、按百分位截断范围,或对敏感层保留浮点运算。如果补救后精度仍不达标,就该考虑 QAT。

PTQ 是快但糙的打包方案;当箱子被压坏,就需要一边打包一边调整——这正是 QAT 的用武之地。

QAT量化感知训练

QAT(Quantization-Aware Training)的核心,是让模型在训练时就“预演”被折叠后的样子。做法是在前向计算里插入伪量化节点:输入先量化成整数刻度,再反量化回浮点,网络实际看到的已经是带误差的数值;反向传播时用直通估计器近似梯度,让参数适应这种误差。

为什么要直通估计器?因为 round 的导数几乎处处为 0,常规反向传播会把梯度杀光。STE 的做法是:前向老老实实过这个“凹凸不平”的台阶,反向却假装它是一条平路,把梯度直接传回。

QAT 的训练流程是:从预训练模型开始,在权重和激活之后插入伪量化节点;用普通损失函数继续训练若干 epoch;收敛后把伪量化节点替换成真正的 INT8 kernel。QAT 精度通常比 PTQ 高,代价是需要 GPU 时间和调参经验。

QAT 让模型学会与褶皱共处。最后,把两条路线放进真实项目里走一遍。

x~=s×clip(round(xs), qmin⁡, qmax⁡)\tilde{x} = s \times \mathrm{clip}\left(\mathrm{round}\left(\frac{x}{s}\right),\ q_{\min},\ q_{\max}\right)
  1. x/s 折算把实数放到整数刻度尺上,看它落在第几格。
    xs\frac{x}{s}
  2. round 取整模拟量化时四舍五入丢掉的小数信息。
    round(xs)\mathrm{round}\left(\frac{x}{s}\right)
  3. clip 裁剪把结果限制在目标位宽能表达的范围内,防止溢出。
    clip(round(xs), qmin⁡, qmax⁡)\mathrm{clip}\left(\mathrm{round}\left(\frac{x}{s}\right),\ q_{\min},\ q_{\max}\right)
  4. s 乘回把离散后的整数刻度反量化回浮点域。注意输出仍是浮点,但取值已经变成“离散台阶”,所以叫伪量化。
    s×clip(⋅, qmin⁡, qmax⁡)s \times \mathrm{clip}\left(\cdot,\ q_{\min},\ q_{\max}\right)

实战落地流程

实战流程可以总结成“先量体,再装箱,后验收”。第一步记录 FP32 基线的精度、模型大小和延迟,作为对照;第二步选择工具,PyTorch、ONNX Runtime、TensorRT 都提供量化 API;第三步先走 PTQ,看掉点是否可接受;第四步若不可接受,做逐通道或敏感层分析,仍不行再上 QAT;第五步在目标硬件上测真实延迟、内存峰值和功耗,而不是只看理论压缩率。

工具选择要看部署链条:PyTorch 适合研究和导出,ONNX Runtime 适合跨平台移动端,TensorRT 适合 NVIDIA GPU。同一模型在不同工具下的量化算法和算子支持不同,精度和速度都可能不一样。

验收标准要量化:精度下降必须在业务容忍范围内;延迟要跑数百次取 p50 和 p95;还要检查异常输入下的稳定性。至此,行李箱真正装上车,量化才算落地。

从 FP32 的豪华衣柜到 INT8/INT4 的真空袋,折叠一定会掉精度,但有了刻度、校准和训练这三件法宝,掉点可以被控制在可接受范围。

AI 应用场景

手机端 OCR 模型压缩

阶段:模型压缩与端侧推理优化

解决的问题:FP32 的 OCR 检测模型在手机上占用内存大、首帧延迟高。传统方案靠裁剪网络或降低输入分辨率,容易损伤召回率;用 INT8 量化可以在不改变网络结构的情况下压缩体积与内存带宽。

具体案例:一款文档扫描 App 使用 ResNet 风格检测模型,原始体积 82MB。团队用 500 张不同光照的票据做校准数据,导出 ONNX Runtime INT8 模型后体积降到 21MB,主流手机 CPU 延迟从 130ms 降到 48ms,字符级准确率仅下降 0.6%。

边缘摄像头目标检测

阶段:边缘 AI 推理优化

解决的问题:停车场的车辆检测模型需要跑在低功耗边缘盒子上,FP32 只能跑到 5 FPS,不满足实时要求。单纯升级硬件成本高,必须用量化配合硬件算子来提速。

具体案例:团队先对 YOLOv5s 做 PTQ,mAP 从 0.82 降到 0.78,掉点约 4 个点;随后改用 QAT 训练 3 个 epoch,在权重和激活中插入伪量化节点,mAP 恢复到 0.81,部署到 INT8 推理引擎后帧率提升到 25 FPS。

Python 演示

python31 行
import numpy as np def quantize(tensor, bits=8):    t_min = float(tensor.min())    t_max = float(tensor.max())    levels = 2 ** bits    scale = (t_max - t_min) / (levels - 1)    if scale == 0:        scale = 1.0    zero_point = np.round(-t_min / scale).astype(np.int32)    quantized = np.round(tensor / scale).astype(np.int32) + zero_point    quantized = np.clip(quantized, 0, levels - 1)    return quantized, scale, zero_point def dequantize(quantized, scale, zero_point):    return (quantized - zero_point).astype(np.float32) * scale def main():    np.random.seed(42)    weights = np.random.randn(64, 64).astype(np.float32) * 0.2    q8, scale8, zp8 = quantize(weights, bits=8)    d8 = dequantize(q8, scale8, zp8)    q4, scale4, zp4 = quantize(weights, bits=4)    d4 = dequantize(q4, scale4, zp4)    mse8 = np.mean((weights - d8) ** 2)    mse4 = np.mean((weights - d4) ** 2)    print("INT8 还原误差:", mse8)    print("INT4 还原误差:", mse4) if __name__ == "__main__":    main()
目的:用 NumPy 模拟 PTQ 中最核心的量化与反量化过程,对比 INT8 和 INT4 在随机权重张量上的还原误差。
  1. scale = (t_max - t_min) / (levels - 1)把实数范围均匀切成 \(2^b-1\) 份,得到每个整数格子的高度。
  2. zero_point = np.round(-t_min / scale).astype(np.int32)计算浮点 0 在整数标尺上的位置,避免负数被切掉。
  3. quantized = np.round(tensor / scale).astype(np.int32) + zero_point先缩放取整,再加零点,完成量化。
  4. quantized = np.clip(quantized, 0, levels - 1)把越界值夹紧到整数可表示范围内。
  5. return (quantized - zero_point).astype(np.float32) * scale反量化回浮点值,但已经无法恢复被舍去的小数。
  6. np.mean((weights - d8) ** 2)计算均方误差,直观看到 INT4 的还原误差通常远大于 INT8。

常见误区

误区初学者觉得量化就是 `int(x)` 这么简单,把小数丢掉就行,不需要考虑数值范围和零点。

正解直接四舍五入会让负数无处安放、大数溢出。标准做法是先算 scale 和 zero point,把数值范围映射到整数区间,再反量化。这像给衣服量体做模板,不是硬塞进行李箱。

为何常见:因为编程里最常见的离散化就是取整,容易把“量化”和“截断小数”画等号。

误区训练后量化是纯数学变换,不用跑模型,直接把权重转成整数就行。

正解PTQ 必须准备有代表性的校准数据,前向跑一遍收集激活分布,才能确定 scale 和 zero point。没有校准数据,激活范围可能估计错,量化误差会爆炸。

为何常见:只看权重范围时会觉得统计一下最大值最小值就够,忽略了激活范围只能通过实际推理观测。

误区只要把模型转成 INT8/INT4,不管在什么硬件上,推理都会加速。

正解加速依赖硬件是否原生支持低比特算子。如果硬件没有 INT8/INT4 指令,运行时可能先反量化回 FP32 计算,反而更慢。真正的收益要在目标设备上实测。

为何常见:厂商宣传常强调“INT8 加速”,让人误以为这种加速是白送的。

误区4 位比 8 位少太多信息,精度一定会崩,所以永远只选 INT8。

正解位数越低误差越大,但通过 QAT、逐通道量化和混合精度,INT4 在显存受限场景中已成为重要方案。选择取决于任务敏感度、硬件支持和业务精度底线。

为何常见:只看比特数,忽略了校准和训练可以补偿大量精度损失。

面试题

Q1. 量化最主要的目标是什么?

单选题 · easy

  • A.压缩模型体积并加速推理← 正确答案
  • B.增加网络的隐藏层数量
  • C.提升训练时的损失函数效果
  • D.把所有参数变成浮点型

答案:A。量化把 FP32 参数映射为 INT8/INT4,像把空气抽掉的行李箱,主要换来体积、内存带宽和推理延迟的改善,但精度通常略降。增加隐藏层和提升损失函数都不是量化的目标。

Q2. 在量化公式中,缩放因子 scale 的含义是?

单选题 · easy

  • A.整数零点的位置偏移
  • B.每个整数刻度代表的实数范围← 正确答案
  • C.量化后张量的总字节数
  • D.模型最后一层输出的大小

答案:B。scale 是把实数范围切成若干等份后每一份的高度,也是整数走一格时实数走的距离。零点偏移是另一个参数,总字节数和输出大小都与 scale 无关。

Q3. 某模型参数从 FP32(4 字节)转为 INT8(1 字节),参数量不变时,权重体积约为原来的多少?

单选题 · easy

  • A.二分之一
  • B.四分之一← 正确答案
  • C.八分之一
  • D.十六分之一

答案:B。1 字节除以 4 字节等于四分之一。INT4 才是八分之一,十六分之一是更高压缩比,不是 INT8 的数值。

Q4. 训练后量化(PTQ)中的“校准数据集”主要用来做什么?

单选题 · mid

  • A.重新训练模型权重
  • B.估计激活值的分布范围← 正确答案
  • C.替换模型中的所有算子
  • D.把训练损失降至零

答案:B。PTQ 不重新训练,它通过校准数据跑前向,观察各层激活的真实范围,从而确定 scale 和 zero point。校准数据不是用来更新权重,也不是用来保证损失为零。

Q5. QAT 与 PTQ 最本质的区别是什么?

单选题 · mid

  • A.离线映射与训练中模拟误差← 正确答案
  • B.一个不需要数据一个需要数据
  • C.一个改权重一个只改激活
  • D.两者在数学上完全相同

答案:A。PTQ 是训练后离线映射,QAT 是在训练过程中插入伪量化节点,让模型提前适应舍入和截断误差。两者都会改变权重,也都需要数据,因此 B、C、D 都不准确。

Q6. 逐通道量化相比逐张量量化,通常?

单选题 · hard

  • A.精度更稳但算子实现更复杂← 正确答案
  • B.精度更差但速度一定更快
  • C.不需要任何校准数据
  • D.体积会减少到一半以下

答案:A。逐通道为每个输出通道独立计算尺度,更贴合不同通道的数值分布,因此精度更稳;但需要存储更多尺度参数,算子实现也更复杂。它并不能保证一定更快,也不可能不校准。

Q7. QAT 中直通估计器(STE)的核心思想是?

单选题 · extreme

  • A.前向模拟量化反向直通梯度← 正确答案
  • B.前向反向都用真实整数计算
  • C.反向把梯度也四舍五入成整数
  • D.前向跳过量化只更新缩放因子

答案:A。round 的导数几乎处处为 0,普通反向传播会把梯度杀光。STE 在前向老老实实使用伪量化结果,反向却把梯度原样传回,相当于告诉网络“把凹凸不平的台阶当成平路走”。

Q8. 以下哪些属于 PTQ 的常见步骤?

多选题 · mid

  • A.准备有代表性的校准样本← 正确答案
  • B.统计权重和激活的数值范围← 正确答案
  • C.对网络重新训练几个 epoch
  • D.将浮点映射到整数并验证← 正确答案

答案:ABD。PTQ 包括准备校准样本、统计数值范围、计算 scale/zero point、映射整数和验证。PTQ 的核心就是不重新训练,因此 C 错误。

Q9. 关于 INT4 与 INT8,下列说法正确的是?

多选题 · hard

  • A.INT4 表示范围比 INT8 更窄← 正确答案
  • B.INT4 精度风险通常高于 INT8← 正确答案
  • C.INT4 在任何硬件上都比 INT8 快
  • D.硬件算子决定实际收益大小← 正确答案

答案:ABD。INT4 只有 16 个刻度,范围更窄、精度风险更大。但它是否更快完全取决于硬件是否原生支持 INT4 算子,不能一概而论,所以 C 错。

Q10. 以下哪些因素会导致量化后精度下降?

多选题 · mid

  • A.取整带来的舍入误差← 正确答案
  • B.数值范围截断导致离群值被削平← 正确答案
  • C.校准数据与真实数据分布不符← 正确答案
  • D.模型参数量变得比原来少

答案:ABC。量化误差主要来自四舍五入、范围截断和校准数据分布偏差。量化不改变参数数量,只改变每个参数的存储精度,因此 D 错误。