假如你要把一整个衣柜搬进一辆小货车。大衣、羽绒服、床单被套全都鼓鼓囊囊,直接扔进去肯定盖不上门。会搬家的人会先把衣服叠好、抽真空,让每一件都变成扁平的方块——它们还是原来的衣服,只是把中间蓬松的空气挤掉了。模型量化就是给神经网络“抽真空”:神经网络的记忆写在 32 位浮点数(FP32)里,每个数占 4 字节;量化后改用 8 位整数(INT8)或 4 位整数(INT4)来记,体积直接缩到四分之一甚至八分之一。衣服还是那些衣服,仓库却从四米高变成一米高。当然,抽真空会压出褶子,模型精度会损失一点;而“怎么压”,就是 PTQ 与 QAT 的分野。
对 AI 从业者而言,量化是把云端大模型塞进手机、摄像头、汽车等边缘设备的“打包手艺”,也是衡量模型工程化能力的重要一环。
可视化演示
本节配有交互动效(进入视口后自动加载,约 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把区间 \([x_{\min}, x_{\max}]\) 分成 \(2^b - 1\) 个等宽格子,每个格子的高度就是一个 scale,代表“整数走一步,实数走多远”。
- 零点偏移 z由于实数可能是负数,需要把浮点 0 映射到整数标尺上的某个刻度,避免负数被直接切掉。
- 量化 x_int把真实值除以 scale,得到“它在第几格”,向下取整后加上 z,最后裁剪到 \([0, 2^b-1]\) 范围内,得到整数代号。
- 反量化 x_hat从整数代号回到浮点估计值,因为取整已经丢了小数,所以还原结果只是真实值的近似。
量化粒度与校准
逐张量量化,是整层张量共用一套 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 折算把实数放到整数刻度尺上,看它落在第几格。
- round 取整模拟量化时四舍五入丢掉的小数信息。
- clip 裁剪把结果限制在目标位宽能表达的范围内,防止溢出。
- s 乘回把离散后的整数刻度反量化回浮点域。注意输出仍是浮点,但取值已经变成“离散台阶”,所以叫伪量化。
实战落地流程
实战流程可以总结成“先量体,再装箱,后验收”。第一步记录 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 演示
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()
scale = (t_max - t_min) / (levels - 1)把实数范围均匀切成 \(2^b-1\) 份,得到每个整数格子的高度。zero_point = np.round(-t_min / scale).astype(np.int32)计算浮点 0 在整数标尺上的位置,避免负数被切掉。quantized = np.round(tensor / scale).astype(np.int32) + zero_point先缩放取整,再加零点,完成量化。quantized = np.clip(quantized, 0, levels - 1)把越界值夹紧到整数可表示范围内。return (quantized - zero_point).astype(np.float32) * scale反量化回浮点值,但已经无法恢复被舍去的小数。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. 量化最主要的目标是什么?
- A.压缩模型体积并加速推理← 正确答案
- B.增加网络的隐藏层数量
- C.提升训练时的损失函数效果
- D.把所有参数变成浮点型
答案:A。量化把 FP32 参数映射为 INT8/INT4,像把空气抽掉的行李箱,主要换来体积、内存带宽和推理延迟的改善,但精度通常略降。增加隐藏层和提升损失函数都不是量化的目标。
Q2. 在量化公式中,缩放因子 scale 的含义是?
- A.整数零点的位置偏移
- B.每个整数刻度代表的实数范围← 正确答案
- C.量化后张量的总字节数
- D.模型最后一层输出的大小
答案:B。scale 是把实数范围切成若干等份后每一份的高度,也是整数走一格时实数走的距离。零点偏移是另一个参数,总字节数和输出大小都与 scale 无关。
Q3. 某模型参数从 FP32(4 字节)转为 INT8(1 字节),参数量不变时,权重体积约为原来的多少?
- A.二分之一
- B.四分之一← 正确答案
- C.八分之一
- D.十六分之一
答案:B。1 字节除以 4 字节等于四分之一。INT4 才是八分之一,十六分之一是更高压缩比,不是 INT8 的数值。
Q4. 训练后量化(PTQ)中的“校准数据集”主要用来做什么?
- A.重新训练模型权重
- B.估计激活值的分布范围← 正确答案
- C.替换模型中的所有算子
- D.把训练损失降至零
答案:B。PTQ 不重新训练,它通过校准数据跑前向,观察各层激活的真实范围,从而确定 scale 和 zero point。校准数据不是用来更新权重,也不是用来保证损失为零。
Q5. QAT 与 PTQ 最本质的区别是什么?
- A.离线映射与训练中模拟误差← 正确答案
- B.一个不需要数据一个需要数据
- C.一个改权重一个只改激活
- D.两者在数学上完全相同
答案:A。PTQ 是训练后离线映射,QAT 是在训练过程中插入伪量化节点,让模型提前适应舍入和截断误差。两者都会改变权重,也都需要数据,因此 B、C、D 都不准确。
Q6. 逐通道量化相比逐张量量化,通常?
- A.精度更稳但算子实现更复杂← 正确答案
- B.精度更差但速度一定更快
- C.不需要任何校准数据
- D.体积会减少到一半以下
答案:A。逐通道为每个输出通道独立计算尺度,更贴合不同通道的数值分布,因此精度更稳;但需要存储更多尺度参数,算子实现也更复杂。它并不能保证一定更快,也不可能不校准。
Q7. QAT 中直通估计器(STE)的核心思想是?
- A.前向模拟量化反向直通梯度← 正确答案
- B.前向反向都用真实整数计算
- C.反向把梯度也四舍五入成整数
- D.前向跳过量化只更新缩放因子
答案:A。round 的导数几乎处处为 0,普通反向传播会把梯度杀光。STE 在前向老老实实使用伪量化结果,反向却把梯度原样传回,相当于告诉网络“把凹凸不平的台阶当成平路走”。
Q8. 以下哪些属于 PTQ 的常见步骤?
- A.准备有代表性的校准样本← 正确答案
- B.统计权重和激活的数值范围← 正确答案
- C.对网络重新训练几个 epoch
- D.将浮点映射到整数并验证← 正确答案
答案:ABD。PTQ 包括准备校准样本、统计数值范围、计算 scale/zero point、映射整数和验证。PTQ 的核心就是不重新训练,因此 C 错误。
Q9. 关于 INT4 与 INT8,下列说法正确的是?
- A.INT4 表示范围比 INT8 更窄← 正确答案
- B.INT4 精度风险通常高于 INT8← 正确答案
- C.INT4 在任何硬件上都比 INT8 快
- D.硬件算子决定实际收益大小← 正确答案
答案:ABD。INT4 只有 16 个刻度,范围更窄、精度风险更大。但它是否更快完全取决于硬件是否原生支持 INT4 算子,不能一概而论,所以 C 错。
Q10. 以下哪些因素会导致量化后精度下降?
- A.取整带来的舍入误差← 正确答案
- B.数值范围截断导致离群值被削平← 正确答案
- C.校准数据与真实数据分布不符← 正确答案
- D.模型参数量变得比原来少
答案:ABC。量化误差主要来自四舍五入、范围截断和校准数据分布偏差。量化不改变参数数量,只改变每个参数的存储精度,因此 D 错误。