想象一下,你为一场比赛买了一双全新的碳板跑鞋。鞋很轻、弹性好,理论上能帮你提速,但如果比赛当天才拆开包装,你的脚踝大概率会被磨得血肉模糊。聪明的运动员怎么做?他们会提前三周穿上新鞋,在小区里遛弯、跑坡、走石板路,让鞋楦记住脚型,让脚掌适应鞋底。比赛日到来时,这双鞋已经成了“老朋友”——轻快,又不磨脚。这就是量化感知训练(QAT)的全部智慧。 浮点模型(FP32)就像光脚跑步——准确但沉重;低比特量化模型(INT8)像那双碳板鞋——轻快但容易“伤脚”。训练后量化(PTQ)是直接穿新鞋上赛场,精度难免暴跌;而 QAT 是在训练阶段就让模型“穿着”量化后的低比特鞋走每一步,通过伪量化算子模拟量化噪声,再借 STE 直通让梯度照常流动。这样训练出来的模型,部署时真正量化,精度却几乎不掉。

岗位关联

掌握 QAT 是模型压缩与端侧部署的必修课,它决定了你的模型能否在手机、芯片上以低比特无损落地,是算法工程师从“炼丹”走向“量产”的核心能力。

目录
  1. 可视化演示
  2. PTQ精度痛点
  3. 伪量化算子
  4. STE直通估计
  5. Observer统计
  6. QAT训练配置
  7. QAT与PTQ对比
  8. 场景选型策略
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示FP32预训练、插入FakeQuantize模拟量化误差、QAT微调恢复精度、导出4位量化模型四阶段,对比PTQ与QAT精度差异。

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

FP32预训练:基准模型精度 FP32全精度训练得到基准模型,top-1准确率76% 模型体积98MB,作为后续量化的起点 FP32模型是QAT的基线

PTQ精度痛点

有个偷懒的选手,比赛日早上才拆开新鞋,套上就跑。前两公里风驰电掣,第八公里脚踝开始火辣辣地疼,到十五公里干脆一瘸一拐。训练后量化(PTQ)就是这个画面:模型已经训练完毕,权重一个字不动,直接把 FP32 数值硬塞进 INT8 的格子——鞋码根本没量,鞋帮硬磨脚踝。

宽量化时误差还能忍,就像新鞋磨出的红印两天就消;但一旦位宽变低,或者网络本身很小、数据分布很极端,量化噪声会在网络层与层之间不断叠加,敏感层的微小偏差被逐层放大成灾难。浮点模型里权重分布是连续光滑的,PTQ 硬生生把连续值钉在离散格点上,原本的分布被“锯齿化”,推理结果自然跟着跑偏。

量化误差可以用一个简洁的模型描述:

PTQ 的悲剧在于,鞋码没量对就硬穿。而 QAT 的做法完全不同——它不等到比赛日才穿,而是把新鞋穿进每一次日常训练里。要理解它,得先认识那双“会伪装的鞋”:伪量化算子。

x^=x+ϵq,ϵq∼U(−Δ2,Δ2)\displaystyle \hat{x} = x + \epsilon_q, \quad \epsilon_q \sim \mathcal{U}\left(-\frac{\Delta}{2}, \frac{\Delta}{2}\right)
这个公式说:量化后的值等于原值加一个均匀分布的噪声,噪声最大幅度只有量化刻度的一半。刻度越宽,噪声越响,精度掉得越狠。
  1. 量化重建值$\hat{x}$ 表示输入 x 经过“量化再反量化”后得到的重建值,网络前向用它替代原始的浮点数值。
    x^=x+ϵq\hat{x} = x + \epsilon_q
  2. 量化噪声$\epsilon_q$ 是四舍五入产生的误差,均匀分布在 $[-\Delta/2, \Delta/2]$ 区间内,这是 PTQ 精度损失的直接来源。
    ϵq∼U(−Δ2,Δ2)\epsilon_q \sim \mathcal{U}\left(-\frac{\Delta}{2}, \frac{\Delta}{2}\right)
  3. 噪声上界$\Delta$ 是量化尺度,每个整数格子的宽度越大,噪声的最大振幅 $|\epsilon_q|$ 越大,模型输出偏离就越远。
    ∣ϵq∣≤Δ2|\epsilon_q| \le \frac{\Delta}{2}

伪量化算子

训练场上的运动员会做一个聪明的动作:穿上新鞋跑两步,感受哪里挤脚,然后脱下来调整自己的步态。伪量化算子(FakeQuant)就是这个“穿上又脱掉”的过程——前向计算时,它真的把权重和激活压到低比特网格里走一遭,让模型感受量化噪声;反向传播时,它又回到浮点世界,仿佛什么都没发生。

为什么叫“伪”量化?因为它只模拟误差,并不会真的把权重存储成 INT8。它是一个插在网络里的算子,前向做一次完整的量化-反量化往返:先把连续浮点值 x 映射到整数刻度,再乘回尺度回到浮点域。模型看到的入参已经“被挤过一遍”,梯度却依然在浮点世界里流动。

伪量化的前向计算可以写成:

伪量化像是给模型穿上一双“看得见摸得着”的鞋:前向每一步都在体验挤脚的感觉,为的就是让模型学会在鞋型限制内调整姿态。但这里立刻冒出一个难题——round 的导数在数学上几乎处处为零,梯度一碰就死,模型还怎么更新?答案就在 STE 直通估计。

x^=s⋅clamp(round(xs), qmin, qmax)\displaystyle \hat{x} = s \cdot \text{clamp}\left(\text{round}\left(\frac{x}{s}\right),\ q_{min},\ q_{max}\right)
这个式子把浮点值除以尺度、四舍五入、截断到整数范围,再乘回尺度,完整复现了真实量化的数值效果。
  1. 除法归一$x / s$ 把浮点值除以量化尺度 s,映射到“以 s 为一个单位”的整数域,得到真实量化时的整数坐标。
    xnorm=xsx_{norm} = \frac{x}{s}
  2. 取整与截断$\text{round}$ 模拟整数化时的四舍五入,$\text{clamp}(\cdot, q_{min}, q_{max})$ 把超出低比特整数范围的离群值截断掉,这正是量化中“溢出”损失的来源。
    q=clamp(round(xs), qmin, qmax)q = \text{clamp}\left(\text{round}\left(\frac{x}{s}\right),\ q_{min},\ q_{max}\right)
  3. 乘法还原最后乘回尺度 $s$,把整数 q 还原到浮点数值域,得到 $\hat{x}$——一个带着量化噪声的“伪浮点值”。
    x^=s⋅q\hat{x} = s \cdot q

STE直通估计

跑者穿着新鞋,脚下有点挤,但他不会停下来挑刺,而是照样迈开步子——心里清楚“这点小挤不影响我跑完全程”。这就是 STE(Straight-Through Estimator,直通估计器)的精髓:对 round 这种不可导操作,干脆假装它的导数等于恒等函数,让梯度直接“穿透”过去。

为什么不能按真实导数算?想想 round(x) 的数学性质:它的图像是一级级台阶,台阶中间的平地上导数全是 0。如果梯度走到这里全是 0,权重就永远得不到更新信号,整个 QAT 训练直接瘫痪。STE 选择了一条务实路线:前向严格模拟量化,反向则把量化函数近似成“直通”——你来了,我就放你过去。

STE 的反向传播公式是:

STE 就像赛场上的裁判规则:步子落在跑道内,随便你跑;步子一旦踩出跑道,立即判罚出局。有了 STE,量化噪声就不再是训练的拦路虎,而成了让模型“磨出茧子”的日常训练。但要准确模拟量化,还得先量好脚的尺码——这就轮到 Observer 登场了。

∂L∂x≈∂L∂x^⋅1[xmin,xmax](x)\displaystyle \frac{\partial L}{\partial x} \approx \frac{\partial L}{\partial \hat{x}} \cdot \mathbf{1}_{[x_{min}, x_{max}]}(x)
这个公式的意思是:梯度照常从损失流回量化输出,但对输入 x 来说,只有在量化范围内它才会继续传递;超出范围,梯度直接置零。
  1. 直通近似$\frac{\partial L}{\partial \hat{x}}$ 是损失对量化输出的梯度,STE 把它直接当作对输入的梯度,等价于把取整函数当成恒等函数求导。
    ∂L∂x^≈∂L∂x\frac{\partial L}{\partial \hat{x}} \approx \frac{\partial L}{\partial x}
  2. 指示函数$\mathbf{1}_{[x_{min}, x_{max}]}(x)$ 在 x 位于量化范围内时取值为 1,表示允许梯度通过。
    1[xmin,xmax](x)={1,x∈[xmin,xmax]0,其他\mathbf{1}_{[x_{min}, x_{max}]}(x) = \begin{cases} 1, & x \in [x_{min}, x_{max}] \\ 0, & \text{其他} \end{cases}
  3. 截断保护当 x 超出量化范围时指示函数为 0,梯度被阻断,防止权重被推到无效区间继续累积误差。
    ∂L∂x=0,x∉[xmin,xmax]\frac{\partial L}{\partial x} = 0, \quad x \notin [x_{min}, x_{max}]

Observer统计

新鞋合不合脚,关键看鞋码。伪量化中的尺度 s 就是“鞋码”,它告诉模型:一个整数刻度代表多大的浮点数值范围。这个 s 不能拍脑袋定,需要一个专门的“量脚工具”——Observer(观察器)——在训练过程中持续观察权重和激活的实际分布,实时算出合适的尺度。

Observer 做的事,说穿了就是统计张量的动态范围。每训练几个 batch,它就扫描一遍当前层的权重或激活值,记下最大值和最小值,然后用总宽度除以整数格数,得到每个刻度对应的浮点宽度 s。MinMax 是最朴素的算法,直接取全体数据的最小最大值;但极端离群值会把范围撑得很大,导致整体精度下降,所以工程上更常用 Percentile(百分位法),只保留 99.999% 的数据范围,把那些“过尖的脚趾”修剪掉。

Observer 计算尺度的核心公式:

量脚不能只在早上量一次,脚会肿、会出汗、会随着训练变化。Observer 用滑动窗口不断刷新统计,让鞋码始终贴合当前状态。有了合适的尺码,QAT 的训练才算真正就位——但怎么训,还得有一套磨合计划。

s=xmax−xminqmax−qmin\displaystyle s = \frac{x_{max} - x_{min}}{q_{max} - q_{min}}
直观理解:把脚长(动态范围)除以鞋码格数(整数比特数),得到每个小格该有多宽。
  1. 动态范围$x_{max} - x_{min}$ 是当前张量观测到的数值跨度,相当于脚的“总长度”。
    r=xmax−xminr = x_{max} - x_{min}
  2. 整数格数$q_{max} - q_{min}$ 是低比特整数域的总跨度,例如 INT8 非对称量化时从 0 到 255,共 255 个区间。
    n=qmax−qminn = q_{max} - q_{min}
  3. 均分刻度用总跨度除以格数,得到每一个整数刻度对应的浮点宽度 s——也就是量化的“最小分辨率”。
    s=rns = \frac{r}{n}

QAT训练配置

磨合新鞋不能一上来就冲 42 公里。聪明的运动员会安排三周计划:第一周散步,第二周慢跑,第三周间歇跑,让脚和鞋逐渐契合。QAT 的训练配置也是同一个道理:在预训练好的浮点模型上,插入伪量化算子,然后用更小的学习率微调几个 epoch,让权重在低比特网格附近“重新落稳脚跟”。

训练时,伪量化后的权重 $\hat{W}$ 和激活 $\hat{x}$ 取代原始浮点值进入前向计算,损失函数用普通的交叉熵,反向借 STE 传递梯度。由于模型已经预训练过,学习率不宜过大,一般取预训练末期学习率的十分之一到百分之一,epoch 数控制在 1~5 轮,否则会破坏已有的特征表达——就像新鞋还没磨合好,就疯狂加训练量,反而把脚跑伤了。

QAT 训练的目标函数:

训练结束后,模型已经像穿习惯的跑鞋一样,对量化噪声“脱敏”了。接下来真正关键的问题:QAT 比 PTQ 强多少?这笔账得好好算一算。

Ltotal=LCE(y,f(W^,x^))+λ∥θ∥22\displaystyle \mathcal{L}_{total} = \mathcal{L}_{CE}\left(y, f(\hat{W}, \hat{x})\right) + \lambda \|\theta\|_2^2
这个公式表明 QAT 的损失仍然是常规的分类损失加正则,但模型的输入已经被伪量化算子“处理过”。
  1. 伪量化输入$\hat{W}$ 和 $\hat{x}$ 是经过伪量化算子处理后的权重与激活,模型在每一步都感受到低比特量化带来的数值扰动。
    W^=sw⋅clamp(round(Wsw),qmin,qmax)\hat{W} = s_w \cdot \text{clamp}\left(\text{round}\left(\frac{W}{s_w}\right), q_{min}, q_{max}\right)
  2. 交叉熵损失$\mathcal{L}_{CE}(y, f(\hat{W}, \hat{x}))$ 用量化模拟后的模型输出与真实标签计算分类损失,优化目标依然是最小化任务误差。
    LCE=−∑cyclog⁡(pc(W^,x^))\mathcal{L}_{CE} = -\sum_{c} y_c \log \left(p_c(\hat{W}, \hat{x})\right)
  3. 正则约束$\lambda \|\theta\|_2^2$ 用 L2 正则限制权重幅度,防止某些权重过大超出量化范围,让模型更容易被压缩到低比特。
    ∥θ∥22=∑iθi2\|\theta\|_2^2 = \sum_{i} \theta_i^2

QAT与PTQ对比

比赛日成绩单出来了。穿磨合过新鞋的选手(QAT)跑出 2 小时 10 分,几乎追平光脚训练的纪录;直接穿新鞋的选手(PTQ)后半程掉速,勉强完赛。训练场上的差距,会在实战中被放大成致命的胜负差。

量化模型的精度保持率是一个直观指标:把量化模型的精度除以浮点基线的精度,得到它保留了原模型多少“战斗力”。

| 维度 | PTQ | QAT |

|------|------|------|

| 精度保持 | 8bit 尚可,低比特明显掉点 | 贴近 FP32,掉点 1%~2% |

| 所需数据 | 少量无标注校准集即可 | 需要训练集与标签 |

| 训练成本 | 几乎为零,几分钟完成 | 数小时 GPU 微调 |

| 部署复杂度 | 低,算子替换即可 | 稍高,需同步 scale 参数 |

QAT 的“额外磨合期”换来的是比赛日的从容;PTQ 的“省事”换来的是精度风险。但问题来了——不是每场比赛都需要磨合新鞋,到底什么时候该上 QAT,什么时候 PTQ 就够用?

η=accQATaccFP32×100%\displaystyle \eta = \frac{acc_{QAT}}{acc_{FP32}} \times 100\%
这个公式可以衡量任何量化方案相对浮点版本的精度百分比,越接近 100% 越好。
  1. 精度保持率$\eta$ 表示量化模型相对 FP32 浮点模型的精度百分比,数值越高说明量化损失越小。
    η=accquantaccFP32×100%\eta = \frac{acc_{quant}}{acc_{FP32}} \times 100\%
  2. 对比参照分母换成 PTQ 的精度,就能得到 PTQ 的保持率,用于和 QAT 做横向对比。
    ηPTQ=accPTQaccFP32×100%\eta_{PTQ} = \frac{acc_{PTQ}}{acc_{FP32}} \times 100\%

场景选型策略

小区里慢跑,穿旧鞋就行;但如果是越野赛、暴雨天、脚踝还带伤,那必须精心磨合新鞋。给模型选量化方案,也得先看看赛道条件。选错了,轻则训练资源白烧,重则模型上线就崩。

业界一般用两个信号做决策:一是看量化后的精度落差,二是看位宽。如果 PTQ 量化后精度掉幅超过业务可接受的阈值 θ,说明这双“新鞋”直接穿不行;如果目标位宽低于 8bit(比如 INT4、INT2),PTQ 的风险会指数级上升,QAT 几乎是必选项。反之,网络很宽、位宽足够、精度敏感度低时,PTQ 完全够用,没必要花大价钱去磨合。

选型判据:

选型决策表,就是跑步教练手边的那本手册:什么赛道、什么天气、什么鞋底,决定要不要花三周去磨合。而 QAT 这双“磨合好的鞋”,正是低比特时代让模型既快又准的关键装备。

方案={QAT,Δacc>θ 或 bit<8PTQ,其他情况\displaystyle \text{方案} = \begin{cases} QAT, & \Delta acc > \theta \ \text{或} \ bit < 8 \\ PTQ, & \text{其他情况} \end{cases}
这是一个简洁的分流规则:精度跌太多或者位宽太低,就选 QAT,否则优先 PTQ 省成本。
  1. 精度落差$\Delta acc = acc_{FP32} - acc_{PTQ}$ 表示 PTQ 相对浮点模型掉了几点精度,这是最直接的信号。
    Δacc=accFP32−accPTQ\Delta acc = acc_{FP32} - acc_{PTQ}
  2. 阈值判断$\Delta acc > \theta$ 说明掉点超出业务容忍限度,方案切换为 QAT,θ 一般取 1%~3%。
    Δacc>θ⇒QAT\Delta acc > \theta \Rightarrow QAT
  3. 低比特强制$bit < 8$ 时量化噪声剧烈,PTQ 大概率崩精度,系统直接走 QAT 通道。
    bit<8⇒QATbit < 8 \Rightarrow QAT

AI 应用场景

端侧语音唤醒芯片

阶段:模型压缩与端侧部署优化

解决的问题:智能音箱、手机的 always-on 语音唤醒模型需要常驻内存,FP32 版本占用约 4MB,INT8 只需 1MB,但直接 PTQ 量化后,唤醒词在嘈杂环境下误触发率飙升——就像新鞋在雨天打滑。QAT 让模型在训练阶段就适应 8bit 数值网格,唤醒率几乎无损。

具体案例:某智能音箱厂商把一个 6MB 的唤醒 CNN 量化到 INT8,PTQ 后唤醒率从 97% 掉到 88%;改用 QAT 在带噪训练集上微调 2 个 epoch,唤醒率回到 96.2%,内存占用直降 75%,电池续航显著提升。

自动驾驶感知模型 INT8 部署

阶段:感知模型落地与车端推理

解决的问题:车端推理芯片的 INT8 算力是 FP32 的 3 倍以上,但 PTQ 会让感知模型 mAP 掉 2~3 个百分点,对 30 米外的行人、障碍物检测是致命的。QAT 像让模型提前在“低比特的雨天赛道”上训练,部署时才能稳稳识别。

具体案例:某 L2+ 辅助驾驶项目将 YOLO 系列检测模型量化为 INT8,PTQ 后 mAP 从 0.82 跌至 0.76,远距离小目标漏检明显;用 QAT 在自动驾驶数据集上微调 3 个 epoch,mAP 恢复到 0.81,推理延迟从 30ms 降到 11ms。

大模型低比特权重量化

阶段:大模型推理优化与部署

解决的问题:7B 参数的大模型 FP16 需要 14GB 显存,4bit 量化后仅需 3.5GB,能塞进单张消费级显卡。但 PTQ 的 4bit 量化会让困惑度(PPL)显著恶化,回答质量明显下降。QAT(配合 LoRA 微调)让低比特权重“重新磨合”,接近 16bit 的生成质量。

具体案例:开源 LLaMA/ChatGLM 类模型采用 W4A16 方案时,PTQ 后 PPL 增加 0.3~0.5,生成明显胡言乱语;用 QAT 对注意力权重做量化感知微调后,PPL 增加仅 0.02,KVCache 显存占用减少约 40%。

Python 演示

python61 行
import torchimport torch.nn as nnimport torch.nn.functional as F  class FakeQuant(torch.autograd.Function):    """伪量化算子:前向走量化-反量化,反向走 STE 直通。"""     @staticmethod    def forward(ctx, x, scale):        qmin, qmax = 0, 255        xq = torch.clamp(torch.round(x / scale), qmin, qmax)        ctx.save_for_backward(x, scale)        ctx.qmin, ctx.qmax = qmin, qmax        return xq * scale     @staticmethod    def backward(ctx, grad_output):        x, scale = ctx.saved_tensors        low = scale * ctx.qmin        high = scale * ctx.qmax        mask = ((x >= low) & (x <= high)).float()        return grad_output * mask, None  class QuantLinear(nn.Module):    def __init__(self, in_f, out_f, bits=8):        super().__init__()        self.weight = nn.Parameter(torch.randn(in_f, out_f) * 0.1)        self.bits = bits     def forward(self, x):        qmax = 2 ** (self.bits - 1) - 1        scale = self.weight.abs().max() / qmax        wq = FakeQuant.apply(self.weight, scale)        return F.linear(x, wq.t())  def main():    torch.manual_seed(42)    model = QuantLinear(4, 1, bits=8)    opt = torch.optim.SGD(model.parameters(), lr=0.05)    x = torch.randn(128, 4)    y = (2.0 * x[:, 0] - 1.0 * x[:, 1] + 0.3).unsqueeze(1)     for epoch in range(300):        opt.zero_grad()        loss = F.mse_loss(model(x), y)        loss.backward()        opt.step()        if epoch % 60 == 0:            print("epoch", epoch, "loss", round(loss.item(), 5))     qmax = 2 ** (model.bits - 1) - 1    scale = model.weight.abs().max() / qmax    wq = torch.round(model.weight / scale) * scale    print("量化后权重样例:", wq.detach().numpy().ravel()[:4])  if __name__ == "__main__":    main()
目的:手写一个 FakeQuant 自定义算子,演示 QAT 的核心——前向模拟量化、反向 STE 直通,并训练一个线性模型验证量化感知训练可行。
  1. xq = torch.clamp(torch.round(x / scale), qmin, qmax)伪量化前向的“除-舍-截”三连击,把浮点值钉到整数网格。
  2. return xq * scale反量化回浮点域,让前向数值带上量化噪声,但不改变张量类型。
  3. mask = ((x >= low) & (x <= high)).float()计算 STE 的指示函数,量化范围外为 0,范围内为 1。
  4. return grad_output * mask, None反向梯度直通:范围内梯度原样传递,范围外梯度置零。
  5. scale = self.weight.abs().max() / qmax用权重的最大绝对值推导对称量化尺度,等价于 Observer 的 MinMax 策略。
  6. wq = FakeQuant.apply(self.weight, scale)将伪量化施加到训练中的权重上,每次前向都“试穿”量化鞋。
  7. loss = F.mse_loss(model(x), y)QAT 不需要特殊损失函数,普通任务损失加反向传播即可更新权重。

常见误区

误区很多人觉得 QAT 无非是在训练时往模型里注入一些随机噪声,当成一种数据增强手段,跑几个 epoch 就算完事。

正解QAT 的核心是伪量化算子:前向把权重和激活真正投影到低比特网格再反量化,反向用 STE 让梯度穿越不可导的取整函数。它让权重在 INT8 网格附近“安家落户”,而不是简单地增强鲁棒性。数据增强是让模型见多识广,QAT 是让模型在限定格子里学会走路,两者机制完全不同。

为何常见:伪量化前后数值差异确实像加了噪声,加上“感知训练”这个词容易让人联想到扰动训练,从而忽略其“网格约束”的本质。

误区以为 Straight-Through 就是不管三七二十一,把损失对量化输出的梯度当作对输入的梯度,量化误差对训练毫无影响。

正解STE 有一个重要的“护栏”——指示函数。当输入 x 超出量化范围时,梯度会被置零,权重不再更新。这相当于跑者步子踩出跑道就被裁判拦下。如果没有这个截断保护,权重会被推到离群区,量化误差越滚越大。

为何常见:“直通”这个名字太有误导性,让人以为梯度全程畅通无阻,忽略了公式里那个默默把关的指示函数。

误区初学者常以为 QAT 跑完,模型文件里的权重全是 INT8 整数,直接拷到推理引擎就能跑。

正解QAT 后的权重依然是 FP32 浮点。伪量化只在训练的前向中“模拟”量化效果,并不真正把权重存成低比特整数。部署时,工具链会根据训练时统计的 scale 和 zero point,把浮点权重做真实量化转换。这也是 FakeQuant 中"Fake"一词的来源——模拟,而非真做。

为何常见:伪量化算子名字里带“量化”,训练过程又全程在低比特“感受”,很容易让人误以为权重已经被真实量化存储。

误区有人在宽模型、8bit 下做了几次实验,发现 QAT 和 PTQ 精度相近,于是断言“QAT 纯属浪费时间”。

正解8bit 宽量化时噪声本来就小,两者差距容易被掩盖。但位宽降到 4bit/2bit,或者网络很小、数据分布极不均匀时,PTQ 的精度会断崖式下跌,QAT 能补回大部分损失。选型要看精度差阈值和位宽,不能拿一个宽松场景的结论套所有场景。

为何常见:人的直觉容易基于有限的几次实验以偏概全,忽视了“位宽越低、网络越敏感,QAT 优势越大”的单调趋势。

误区听到“量化感知训练”就以为要像预训练那样从头训一个大模型,几周 GPU 算力烧不起,于是干脆放弃 QAT。

正解QAT 几乎总是在预训练权重上做轻量微调:插入伪量化算子,学习率缩小一到两档,训练 1~5 个 epoch 即可。成本远低于预训练,通常只需几个小时 GPU 时间,却能换回低比特部署的精度保障。它不是另起炉灶,而是给老模型“换鞋磨合”。

为何常见:名字里的“训练”两个字给了人“重头再来”的心理暗示,忽略了它本质上是微调(fine-tuning)的一种特殊形式。

面试题

Q1. 伪量化算子的“伪”主要体现在哪里?

单选题 · easy

  • A.训练中模拟量化误差← 正确答案
  • B.量化结果完全不准
  • C.无需任何超参数参与
  • D.只在前向推理时执行

答案:A。伪量化在训练前向中完整执行量化-反量化,但模型权重仍以浮点存储,只是“模拟”低比特效果,故称为“伪”。B 说它不准则误解了模拟的意义;C 错,scale 等超参数必不可少;D 错,它主要在训练时执行,推理时是真实量化。

Q2. PTQ 与 QAT 的量化时机差别是什么?

单选题 · easy

  • A.PTQ 在训练完成后量化← 正确答案
  • B.QAT 在推理时动态量化
  • C.两者都在训练中量化
  • D.两者都在部署后量化

答案:A。PTQ 是训练完毕再做量化,不动原始权重;QAT 是在训练过程中就插入伪量化让模型适应低比特。这就像新鞋是比赛日才穿,还是提前三周穿,差别全在时机。

Q3. STE 中 round 函数反向求导如何近似处理?

单选题 · mid

  • A.直接视为恒等函数求导← 正确答案
  • B.导数记为零不更新权重
  • C.用 sigmoid 替代取整函数
  • D.对导数取绝对值再回传

答案:A。STE 的核心是把不可导的取整近似成恒等函数,让梯度直接穿过。B 是 round 的真实导数,那样梯度会全灭;C、D 是其他领域的技巧,与 STE 无关。

Q4. 下列哪些属于伪量化前向计算的操作?

多选题 · mid

  • A.除以尺度得到整数域← 正确答案
  • B.四舍五入模拟取整← 正确答案
  • C.截断到限定整数范围← 正确答案
  • D.直接存储 int8 权重

答案:ABC。伪量化前向是“除-舍-截-乘”四步,A/B/C 分别对应前三步。D 是部署时的真实量化操作,伪量化训练阶段并不会真的把权重存成 int8。

Q5. Observer 在 QAT 中的核心作用是什么?

单选题 · mid

  • A.统计张量动态范围求尺度← 正确答案
  • B.计算交叉熵损失值
  • C.自动调整网络学习率
  • D.裁剪冗余的神经元

答案:A。Observer 就像量鞋码的尺子,统计权重/激活的 min-max 或分位点,再算出量化尺度 s。B 是损失函数职责,C 是优化器职责,D 是剪枝相关,都与 Observer 无关。

Q6. 当权重数值超出量化范围时,STE 的反向梯度会发生什么?

单选题 · hard

  • A.被指示函数置为零← 正确答案
  • B.放大十倍再继续回传
  • C.保持原值继续传播
  • D.只更新对应的偏置项

答案:A。STE 中的指示函数在范围外取 0,梯度被阻断,权重不再更新。这相当于跑者步子踩出跑道就被裁判拦下,防止权重被推向无效区间。

Q7. 相比 PTQ,QAT 需要付出哪些额外代价?

多选题 · hard

  • A.需要额外训练多个 epoch← 正确答案
  • B.需要带标签的训练数据← 正确答案
  • C.部署流程完全不需要变化
  • D.需要更多计算资源开销← 正确答案

答案:ABD。QAT 要在预训练模型上微调,自然需要训练数据和算力开销。C 是错的——QAT 部署时仍要同步 scale 等量化参数,流程比 PTQ 略复杂。

Q8. QAT 微调时学习率一般如何设置?

单选题 · mid

  • A.取预训练末期的较小值← 正确答案
  • B.比预训练用更大的学习率
  • C.学习率设为固定常数 1
  • D.学习率必须设置为零

答案:A。QAT 是轻量微调,学习率取预训练末期的小值(约为其 1/10~1/100),避免破坏已学特征。B 会导致灾难性遗忘,C/D 无法正常优化。

Q9. 哪些场景下应当优先选择 QAT 而非 PTQ?

多选题 · mid

  • A.低比特如 INT4 精度敏感场景← 正确答案
  • B.PTQ 精度掉幅超出业务阈值← 正确答案
  • C.完全没有任何训练数据可用
  • D.只追求最短的部署时间

答案:AB。位宽低于 8bit 或 PTQ 掉点超阈值(通常 1%~3%)时,QAT 是保精度首选。C 没数据 QAT 无法训练;D 只求快应选 PTQ,而不是 QAT。

Q10. QAT 训练完成后,部署时权重如何变成真正的低比特?

单选题 · easy

  • A.仍是浮点,按尺度转为整数← 正确答案
  • B.权重会直接保存为整数
  • C.部署时不再需要尺度参数
  • D.权重会变成二进制表示

答案:A。QAT 训练后的权重是 FP32,部署时由推理工具链按训练时统计的 scale/zero point 做真量化。B 是训练阶段的误解,C 错,尺度恰恰是关键参数,D 描述不准确。