想象你是一家餐厅的主厨,后厨有一台精确到 0.000001 克的电子秤,招牌菜全靠它。突然老板下令:为了省成本,必须换成一把只有 8 个刻度的粗汤勺——所有配料量都要落成 8 档刻度里的某个近似值。这个“换秤”动作,在 AI 世界里叫量化:把高精度的 FP32 数字变成低精度的 INT8。换秤的方式有两种:PTQ 是先用电子秤按原菜谱做一桌菜,再把菜谱翻译成粗刻度版交给别人复刻,不再重新做菜;QAT 则是从第一天起就用粗勺做菜,边做边尝、边微调配方,让菜在粗刻度下依然好吃。前者快但味道容易跑偏,后者费时但更稳。本文用这间厨房的故事,带你搞懂两种方案的工作原理、对比维度与选型思路。
掌握 PTQ 与 QAT 的对比与选型,是 AI 工程师把模型塞进手机、摄像头等边缘设备前的必修课,直接决定部署速度与线上精度。
目录
可视化演示
对比后训练量化和量化感知训练在精度恢复、训练成本、部署灵活性与适用场景上的差异,帮助选择合适量化方案。
本节配有交互动效(进入视口后自动加载,约 3KB)。
PTQ vs QAT PTQ:无需训练,快速验证,精度略降 QAT:伪量化训练,精度损失小,成本高 acc=98.8% QAT在精度要求严格场景优势明显
量化核心概念:从电子秤到粗汤勺
想象你是一家小餐馆的主厨。后厨那台电子秤精确到 0.000001 克,你靠它记菜谱、调酱汁,招牌菜百试百灵。可老板为了省钱,要求换成一把只有 8 个刻度的粗汤勺——所有 123.456789 克一类的配料量,都得落成 8 档刻度里的某一个近似值。这一换,味道必然有偏差,只是偏差大小的问题。
在深度学习世界里,“电子秤”是 FP32 浮点数格式,它能表示约 40 亿种不同的数值档位;而“粗汤勺”是 INT8 定点格式,只有 256 个档位。把一个训练好的模型从 FP32 变成 INT8,就是量化(Quantization)。换秤的目的是省内存、省带宽、加快推理:4 个字节变成 1 个字节,模型体积瞬间缩为四分之一。
最常用的均匀量化(Uniform Quantization)像一把刻度均匀的尺子,把一段连续的数值范围切成 256 格。每格宽度叫缩放因子(Scale, S),尺子零点对应的真实数值叫零点(Zero Point, z)。给定真实数值 r,先除以 S 落到“第几格”,四舍五入取整,再加上零点偏移,就得到整数编码 q;反量化时用 S(q - z) 找回近似值。
换秤的损失取决于刻度的粗细和数据的分布。INT8 的 256 个档位如果恰好覆盖数据的大多数区域,味道偏差就小;若范围定得不准,误差就大。所以,后续所有方法——PTQ 和 QAT——本质上都在回答同一个问题:如何让粗刻度带来的偏差最小。
- 真实值 r电子秤读出的连续数值,比如某层激活值 0.738291。
- 缩放因子 S一个刻度格代表多大的数值范围,相当于粗汤勺一勺的重量。
- 取整 round(·)落在哪一格就取哪一格,这是舍入误差的来源。
- 零点 z尺子的 0 刻度对应真实世界的什么数值,用于对齐非对称分布。
- 反量化 r̂用格子的索引反推出一个近似真实值,重建成接近原数的浮点结果。
PTQ原理:先做菜,后改谱
回到厨房。PTQ(Post-Training Quantization,训练后量化)的思路是:先不管换秤的事,用电子秤按原菜谱认认真真做一桌菜。菜做好了,再把菜谱里的每个数字四舍五入译成粗刻度版本,交给其他小店照做。至于译完后味道如何,全凭原菜谱本身的容错能力。
映射到 AI 里,这个流程分两步:第一步,用原本训好的 FP32 模型跑一批推理,这就是“做一桌菜”;第二步,统计每一层激活值的分布范围,把权重和激活从 FP32 翻译成 INT8。第一步中用于统计的样本叫校准数据集(Calibration Set)——它们不参与训练,只是用来“目测”数据大致落在哪个范围。
权重量化的数学本质就是上节的均匀量化,只不过被量化对象是权重矩阵 W:先为每层(或每个通道)求出缩放因子 S_w 和零点 z_w,再取整、截断到 0 到 2^b - 1 的整数区间。激活量化同理,但激活的范围每次推理都会变,所以只能用校准集统计出一个“典型范围”。
PTQ 的代价是便宜——不需要重新训练,只要准备几百张校准图,就能把菜谱翻成粗刻度版。但碰上对误差敏感的模型,翻出来的菜可能明显走味。这时就需要一点“误差补偿”(比如修正量化后的均值偏移)来补救,或者干脆换 QAT 这条路。
- 权重 W 与近似权重 Ŵ原浮点权重与量化后再还原的近似权重。
- 缩放因子 S_w由该层权重的取值范围除以整数档位数决定,一格代表多大。
- 取整 round(·)把连续权重挪到最近的整数格,产生舍入误差。
- 截断 clip(·)把超出 0 到 2^b-1 范围的极端值强行切回边界,防止溢出。
- 整体还原整数码乘以 S_w 后得到新的浮点权重,部署推理时直接用这个近似矩阵。
QAT原理:边做边适应粗刻度
QAT(Quantization-Aware Training,量化感知训练)换了一种做法:从第一天起,主厨就故意拿着粗汤勺做菜,但同时在脑海里保留着电子秤的记忆。每次下料先按粗刻度取,出锅前尝一口,根据误差悄悄微调脑海里的配方,再继续做下一道。如此反复,最终得到的配方即使在粗刻度下操作,味道也能接近原版。
技术上的实现是一套“伪量化(Fake Quantization)”机制。训练过程里权重本身仍是 FP32 高精度数字,但前向传播时会插入一个模拟量化器:把数值先量化成 INT8,再立刻反量化回 FP32。梯度无法穿过 round 的阶梯,于是研究者用直通估计器(STE,Straight-Through Estimator)——让梯度像穿过恒等函数那样绕过去。
具体地,前向传播中伪量化节点对输入 x 做量化-反量化得到 x̂;损失对 x̂ 的梯度可以正常计算。反向传播时,STE 把 ∂L/∂x̂ 直接当作 ∂L/∂x 传下去,等于假装 round 对梯度没有影响。于是权重真正更新时,走的是“量化误差最小的方向”,模型逐渐学会容忍粗糙刻度。
QAT 自然更费功夫:需要准备有标签的训练数据、控制学习率(通常用小学习率微调,防止震荡),还要跑完整个训练流程。它适合那些“招牌菜绝不能翻车”的场景——毕竟边做边适应粗刻度的菜谱,上线后才能经得起食客的考验。
- 损失函数 L训练时监督模型输出好坏的“食客评分”。
- 量化后输出 x̂经过伪量化节点的数值,它才是前向真正用到的输入。
- 梯度 ∂L/∂x̂评分对量化后数值的敏感程度,按链式法则正常计算。
- 指示函数 1[−α,α]x 落在量化范围 [−α, α] 内时为 1,否则为 0,越界时梯度清零。
评估对比维度:四把尺子量方案
现在你手里有两套菜谱方案,该用哪套?聪明的做法是准备四把尺子:最终味道(精度恢复)、额外工时(训练成本)、换店适应速度(部署灵活性)、适合什么客人(适用场景)。
第一把尺子量精度。PTQ 不做任何重训,误差全看模型本身的鲁棒性;QAT 在训练中反复适应量化误差,通常能把精度恢复到接近 FP32 的水平。第二把尺子量成本:PTQ 只需一份校准集和几分钟到几十分钟的统计;QAT 则需要完整的训练流程、数据和算力,成本高出几个数量级。
第三把尺子看部署灵活性:PTQ 不依赖训练环境,拿到一个模型随时可以尝试,适合快速横向比较多个模型;QAT 则要求能访问训练代码、数据和环境,灵活性大打折扣。第四把尺子看场景:模型越深、激活分布越宽,PTQ 越容易翻车,此时 QAT 的价值越明显。用公式表达,就是看两种方案与 FP32 的精度差距 δ:
四把尺子各有各的读数:要精度选 QAT,要省事选 PTQ,要灵活选 PTQ,要稳选 QAT。没有绝对赢家,只有按场景的权衡。这就像评价两套菜谱,好不好吃只是其中一项,还要看后厨有没有时间、能不能换着店用。
- 满分基准线全精度模型的准确率,是味道的“满分基准线”。
- 量化方案精度PTQ 或 QAT 量产后模型的准确率。
- 差距 δ与满分线的距离,衡量精度损失。
- 对比两方案通常 QAT 的误差远小于 PTQ。
选型决策指南:快餐还是米其林
既然没有绝对赢家,怎么拍板?我们把场景分成两类:一类是快餐窗口——客人多、翻台快、对口味包容;另一类是米其林餐厅——招牌菜绝不能砸,但备料时间长。前者更像是 PTQ 的主场,后者则是 QAT 的用武之地。
资源受限场景(边缘摄像头、手机端、嵌入式设备)内存和算力都有限,且通常希望快速验证模型能不能跑——先上 PTQ 探路;如果精度损失可以容忍,就直接交付。若对精度要求严格(医疗影像、工业质检、自动驾驶),或者 PTQ 后精度跌得没法看,就该动用 QAT。
工程上常用一个精度阈值 ε 来决策:先跑 PTQ,看它与 FP32 的差距 δ_PTQ;若 δ_PTQ 不超过 ε,直接采用 PTQ;若超过了,再判断是否有训练数据和算力,有则转 QAT,无则只能换更大的模型或接受损失。这个决策可以写成:
这套规则的本质是“按精度预算采购”:预算够,就买 QAT 的确定性;预算紧,就用 PTQ 的性价比。工业化部署时,往往先用 PTQ 快速试跑全链路,再把最敏感的模型单独升级 QAT,既控成本又保招牌菜不翻车。
- 实测损失 δ_PTQPTQ 相对 FP32 的精度损失,来自量化实践中的实测值。
- 容忍阈值 ε业务方给出的“最多能接受掉几个点”,比如 1%。
- 条件分支损失超过阈值才上 QAT,否则保持低成本方案。
- 工程决策策略的最终输出,要么 QAT 要么 PTQ。
实践与趋势:统一菜谱与自动化厨房
最后,后厨还要面对一个现实:菜谱要能跨店通用。今天在自家灶台(PyTorch)调好的配方,明天要拿到别家灶台(TensorFlow、TensorRT)上做,格式必须统一。量化方案同样如此——工具链、模型格式、跨框架互操作,决定了方案能否真正落地。
主流框架已经把 PTQ 与 QAT 封装成开箱即用的模块:PyTorch 提供 torch.ao.quantization,TensorFlow 有 Model Optimization Toolkit,NVIDIA TensorRT 负责部署侧的 INT8 优化。它们就像是标准化的粗刻度汤勺,不需要主厨自己手工刻线。
模型格式层面,ONNX 用 QuantizeLinear / DequantizeLinear 一类算子(通常叫 QDQ 格式)统一描述量化信息:哪一层用了哪种缩放因子、哪个零点、多少位宽,全部写进一个中间文件,让不同推理引擎都能读懂。跨框架协作让量化模型可以在训练框架、转换工具、推理引擎之间自由流转,而自动化趋势(如自动精度分析、自动重试 QAT)正让“选 PTQ 还是 QAT”越来越像是平台自动完成的事。
从一把秤到一套标准化的厨房流水线,PTQ 与 QAT 的故事已经超越了“二选一”:它们是整个量化生态的两条腿。工具链负责把粗刻度造好,格式负责让菜谱通用,工程师则负责在精度与成本之间踩好平衡点。
- 原模型耗时FP32 模型跑一次推理的耗时。
- 量化模型耗时INT8 模型的推理耗时。
- 加速比大于 1 说明量化带来加速,小于 1 说明硬件拖后腿。
- 实测提醒加速依赖硬件指令集,换框架格式后要重新实测。
AI 应用场景
边缘摄像头部署
阶段:模型压缩与部署
解决的问题:FP32 模型在低算力芯片上内存不足、帧率不达标。传统做法是盲目重训或人工调参,周期长;AI 方案先用 PTQ 配合校准集快速量化验证,精度不达标再用 QAT 微调。
具体案例:某安防厂商要在 4GB 内存的边缘盒子部署检测模型。工程师先用 PTQ 量化,实测精度下降 2.3%,超出客户 1% 的红线,于是切换到 QAT,用少量标注数据微调 3 个 epoch,精度恢复到只差 0.4%,顺利上线。
工业缺陷检测
阶段:模型训练与优化
解决的问题:质检漏检会造成重大损失,精度红线极严,PTQ 无法接受;QAT 在训练阶段就模拟 INT8 误差,能最大化保住精度。
具体案例:电池表面缺陷检测模型 FP32 精度 99.2%,PTQ 后跌到 97.1%,漏检率翻倍。团队改做 QAT,用原有训练数据在伪量化下微调,最终 INT8 精度 98.9%,部署到产线 GPU 服务器,吞吐提升 3 倍。
模型服务平台自动选型
阶段:MLOps 与模型管理
解决的问题:平台有上千个模型要压缩,人工逐个判断 PTQ/QAT 成本太高;由平台自动跑 PTQ,若精度损失超阈值则自动进入 QAT 流水线。
具体案例:某公司的统一模型服务平台新增“量化套餐”:上传模型后自动用校准集跑 PTQ、生成精度报告,超阈值模型自动排队 QAT。该流程把模型上线时间从两周压缩到两天。
Python 演示
import numpy as np np.random.seed(42) def make_data(n=600): X = np.random.randn(n, 4).astype(np.float32) w = np.array([2.0, -1.5, 0.8, 3.1], dtype=np.float32) y = (X @ w > 0.5).astype(np.float32) split = int(n * 0.7) return X[:split], y[:split], X[split:], y[split:] def fake_quant(x, bits=8): level = float(2 ** (bits - 1) - 1) m = np.max(np.abs(x)) scale = m / level if m > 0 else 1.0 qx = np.clip(np.round(x / scale) * scale, -scale * level, scale * level) return qx def predict(X, W1, b1, W2, b2, quantize): if quantize: h = np.tanh(fake_quant(X @ W1 + b1)) out = 1.0 / (1.0 + np.exp(-fake_quant(h @ W2 + b2))) else: h = np.tanh(X @ W1 + b1) out = 1.0 / (1.0 + np.exp(-(h @ W2 + b2))) return out def train(mode, steps=80, lr=0.2): X, y, Xv, yv = make_data() W1 = np.random.randn(4, 8).astype(np.float32) * 0.6 b1 = np.zeros(8, dtype=np.float32) W2 = np.random.randn(8, 1).astype(np.float32) * 0.6 b2 = np.zeros(1, dtype=np.float32) quantize = mode == "qat" for _ in range(steps): out = predict(X, W1, b1, W2, b2, quantize) ycol = y.reshape(-1, 1) loss = -np.mean(ycol * np.log(out + 1e-8) + (1 - ycol) * np.log(1 - out + 1e-8)) dout = (out - ycol) / X.shape[0] h = np.tanh(X @ W1 + b1) dh = dout @ W2.T * (1 - h ** 2) W1 -= lr * X.T @ dh b1 -= lr * dh.sum(axis=0) W2 -= lr * h.T @ dout b2 -= lr * dout.sum(axis=0) return W1, b1, W2, b2 def evaluate(W1, b1, W2, b2, quantize): X, y, Xv, yv = make_data() out = predict(Xv, W1, b1, W2, b2, quantize) return float(np.mean((out > 0.5).astype(np.float32).ravel() == yv)) def ptq_quantize(W1, b1, W2, b2): return fake_quant(W1), b1, fake_quant(W2), b2 def main(): W1s, b1s, W2s, b2s = train("fp32") W1q, b1q, W2q, b2q = ptq_quantize(W1s, b1s, W2s, b2s) acc_fp32 = evaluate(W1s, b1s, W2s, b2s, False) acc_ptq = evaluate(W1q, b1q, W2q, b2q, True) W1a, b1a, W2a, b2a = train("qat") acc_qat = evaluate(W1a, b1a, W2a, b2a, True) print("FP32: %.3f | PTQ: %.3f | QAT: %.3f" % (acc_fp32, acc_ptq, acc_qat)) if __name__ == "__main__": main()
def fake_quant(x, bits=8):定义伪量化函数,是 QAT 的核心模拟器。level = float(2 ** (bits - 1) - 1)INT8 正负非对称时的最大档位 127。m = np.max(np.abs(x))用最大绝对值决定一格多大,对应缩放因子 S。qx = np.clip(np.round(x / scale) * scale, -scale * level, scale * level)取整再还原,量化后数值仍是浮点形式。quantize = mode == "qat"只有 QAT 模式在前向模拟低精度。dout = (out - ycol) / X.shape[0]二分类交叉熵对输出层的梯度。h = np.tanh(X @ W1 + b1)反向传播用未量化路径,这就是直通估计的近似。W1q, b1q, W2q, b2q = ptq_quantize(W1s, b1s, W2s, b2s)PTQ 只在推理时量化,训练完全不动。acc_qat = evaluate(W1a, b1a, W2a, b2a, True)QAT 训练出的权重在推理时也要过一遍伪量化验证。
常见误区
误区PTQ 只是把权重四舍五入,训练时又没碰过数据,所以完全不需要数据就能做。
正解PTQ 需要少量“校准数据”来估计每层激活的数值范围,没有校准数据就无法确定缩放因子和零点。就像把菜谱改成粗刻度版,总得先看一看每道菜平时用的配料上限在哪,否则范围定错,整本菜谱都废了。
为何常见:因为 PTQ 不更新权重,容易被误以为与数据完全无关,忽略了“统计分布”这层需求。
误区把权重先转成 INT8 存下来,然后拿这个 INT8 模型继续训练就行了。
正解QAT 的关键是“伪量化”——训练时权重仍是高精度浮点,只是前向传播模拟低精度运算,反向用 STE 传梯度;真正把权重存成 INT8 是部署时才做的事。就像做菜时故意用粗勺量,但配方本上仍记精确数字。
为何常见:混淆了“训练时的模拟”与“部署时的真实存储”两个时刻,以为量化发生在训练之前。
误区既然 QAT 精度高,那把所有模型都做 QAT 最稳妥,不用纠结。
正解QAT 需要训练数据和算力,调参耗时更长;如果 PTQ 精度损失在容忍范围内,PTQ 的快速部署价值更高。选型本质是成本与精度的权衡,就像米其林菜谱虽然好吃,但快餐窗口没时间等它备料。
为何常见:只看精度单一指标,忽略了训练成本、部署灵活性等其他评估维度。
误区位数变少了,速度自然变快,所以量化完就万事大吉。
正解加速依赖硬件的 INT8 算力支持和算子融合;如果设备没有高效的 INT8 内核,反而可能因反量化、格式转换而变慢。就像换了粗汤勺,但切菜流程没优化,总时长未必下降。
为何常见:把“更小的表示”与“更快的执行”直接画等号,忽略了硬件指令集与运行时开销。
面试题
Q1. 把模型的 FP32 权重换成 INT8 存储,理论上参数占用约为原来的多少?
- A.四分之一大小← 正确答案
- B.二分之一大小
- C.八分之一大小
- D.三分之二大小
答案:A。FP32 占 4 字节,INT8 占 1 字节,32 位缩到 8 位,体积变为四分之一。就像 4 克一勺改成 1 克一勺,同样容积能装四倍的勺数,这就是量化省内存的直接来源。
Q2. 下列哪一项是 PTQ(训练后量化)的必经步骤?
- A.用少量数据校准← 正确答案
- B.重新训练整个模型
- C.增加深层网络结构
- D.更换新的损失函数
答案:A。PTQ 不重新训练,但必须用校准集统计激活分布,才能确定每层的缩放因子和零点。这相当于做菜前先看一眼食材常规用量,而不是把整道菜重做一遍。
Q3. QAT 中“伪量化”节点的核心作用是什么?
- A.前向模拟低位误差← 正确答案
- B.后向丢弃所有梯度
- C.替代全部训练数据
- D.直接降低浮点精度
答案:A。伪量化在前向传播中先量化再反量化,让模型提前感知 INT8 误差;权重本身仍保持浮点。就像主厨故意用粗勺量料,但配方本上仍记精确数字,靠误差反馈微调配方。
Q4. 直通估计器(STE)在 QAT 反向传播中的作用是?
- A.让舍入近似可导← 正确答案
- B.让舍入彻底不可导
- C.把梯度直接清零
- D.将梯度放大十倍
答案:A。round 的阶梯处处不可导,STE 用恒等近似让梯度穿透取整操作,保留可用的学习信号。就像下料时看刻度,但脑子里的配方微调仍然按直觉连续进行,不会被刻度卡死。
Q5. 下列哪种场景更适合优先选择 QAT?
- A.精度严格的工业质检← 正确答案
- B.凌晨赶工的原型演示
- C.无训练数据的快速部署
- D.对延迟无感的离线任务
答案:A。QAT 成本高但精度恢复好,适合精度红线严格的场景;原型演示和无训练数据场景应优先 PTQ。对应到厨房,招牌菜不能砸,才值得花时间边做边适应粗刻度。
Q6. 量化中的“零点(zero point)”的主要作用是?
- A.对齐非对称数值范围← 正确答案
- B.缩放网络的所有梯度
- C.消除全部量化误差
- D.成倍增加模型参数量
答案:A。零点让尺子的 0 刻度对应真实世界的某个非零数值,适应激活值全为正等非对称分布。它只能对齐范围,无法消除舍入误差,更不会影响梯度或参数量。
Q7. PTQ 中校准数据集的主要用途是?
- A.估计各层激活数值范围← 正确答案
- B.重新训练全部的权重
- C.扩充成倍的训练样本
- D.随意增大缩放因子
答案:A。校准集只用于统计激活分布以确定量化参数,不参与权重更新,也不能代替训练数据。就像只用来“目测”食材大致用量,而不是把菜谱推翻重写。
Q8. 下列哪些环节属于 QAT 的典型训练流程?
- A.前向伪量化模拟← 正确答案
- B.反向直通近似梯度← 正确答案
- C.小学习率微调权重← 正确答案
- D.删除全部激活层
答案:ABC。QAT 的前向用伪量化模拟低位误差,反向用 STE 传近似梯度,并用小学习率微调防止震荡;删除激活层与量化无关,是错误的干扰项。
Q9. 下列哪些是选择 PTQ 而非 QAT 的合理理由?
- A.训练资源极其有限← 正确答案
- B.精度损失可以被容忍← 正确答案
- C.需要快速上线验证← 正确答案
- D.完全不需要任何数据
答案:ABC。PTQ 省算力、见效快,只要精度损失在红线内就够用;但它同样需要校准数据,说“完全不需要任何数据”是错误的。
Q10. 下列哪些属于量化误差的主要来源?
- A.舍入取整带来的误差← 正确答案
- B.数值截断导致的误差← 正确答案
- C.量化后精度完全无损
- D.有限数值范围的溢出← 正确答案
答案:ABD。round 舍入、clip 截断、数值范围有限导致的溢出都会引入误差;“完全无损”只存在于理想情况。就像粗汤勺的每一勺都免不了多一克少一克。