大模型量化从0到1(三):PTQ 和 QAT 到底怎么选,一篇讲透 这篇要解决一个绕不开的岔路口:同样是把模型量化,为什么有的方法几分钟就搞定,有的却要重新训练好几天?这就是 PTQ 和 QAT 两条路线的区别。搞懂它俩,你才能明白后面要学的 AWQ、GPTQ 为什么都归在 PTQ 这一派,以及什么时候你才真的需要动用 QAT。目录一、先看两个真实场景二、一句话说清 PTQ 和 QAT三、原理拆解:两条路线到底差在哪四、关键权衡:成本、精度、门槛五、上手实测:跑一个最小的 QAT 循环六、PTQ vs QAT 横向对比七、常见误区与避坑八、小结一、先看两个真实场景先不讲定义,看两个你大概率会遇到的场景。场景 A:你从 HuggingFace 下了个 Qwen2.5-7B,想塞进自己的 4090 跑推理。你不想训练,也没有训练数据,就想让它占的显存小一点、还能正常回答问题。你需要的是——拿到模型权重,几分钟内量化完,直接用。场景 B:你在做一个端侧语音唤醒模型,要部署到一块算力极弱的 MCU 上,只能跑 INT8 甚至更低。这个模型量化后精度掉得厉害,业务不能接受。你手上有训练数据和训练流程,愿意花时间重新训练来把精度捞回来。场景 A 就是 PTQ 的地盘,场景 B 才轮到 QAT 出场。绝大多数玩大模型的人,一辈子只会用到场景 A。这也是为什么这个系列的重头戏 AWQ、GPTQ、GGUF 全都是 PTQ 方法。但 QAT 的原理你还是得懂,因为它能帮你理解量化误差到底能不能被训练消化掉这件事。二、一句话说清 PTQ 和 QATPTQ(Post-Training Quantization,训练后量化):模型已经训练好了,拿过来直接量化。不碰训练过程,顶多用一小批数据校准一下,统计统计激活值的分布。QAT(Quantization-Aware Training,量化感知训练):在训练(或微调)的时候,就假装模型已经被量化了,让模型在训练过程中提前适应量化带来的误差,训完再真正量化。打个不太严谨但好记的比方:PTQ 像是衣服做好了再改小一号——快,但可能这里紧那里绷,将就着穿。QAT 像是量着你的身材从头做一件——合身,但费工费料。大模型时代 PTQ 之所以成主流,道理很简单:一个 70B 模型重新训练一遍的成本高到离谱,没人愿意为了量化去烧那么多卡。而 PTQ 只要几张校准样本、几十分钟就能出结果,精度还能靠好算法(AWQ/GPTQ)保住。性价比这一项,PTQ 赢麻了。三、原理拆解:两条路线到底差在哪3.1 PTQ 在干什么PTQ 的流程短得可怜:1. 加载训练好的 FP16 权重 2.可选喂一小批校准数据,前向跑一遍,统计每层激活值的范围 3. 根据统计到的范围,给每层算 scale 和 zero-point 4. 把权重量化成低比特,存起来关键在第 2 步的校准(calibration)。为什么权重量化不太需要校准、激活量化却需要?因为权重是静态的、训练完就固定了,拿过来直接看数值分布就行;而激活值是动态的,输入不同、激活就不同,你得拿一批有代表性的数据跑一遍,才知道激活大概落在什么范围,才能定合理的 scale。校准数据不用多,通常几百条就够,但质量要有代表性——你拿一堆乱码去校准一个中文对话模型,量化出来肯定拉胯。这点第 20 篇讲调优时会展开。PTQ 里又分两种细法:动态量化(dynamic):权重提前量化好,激活在推理时实时算 scale。灵活,但每次推理都要算一次,有开销。静态量化(static):权重和激活的 scale 都提前用校准数据定好。推理时不用现算,更快,但依赖校准数据的质量。大模型权重量化(AWQ/GPTQ)基本都是静态那一挂。3.2 QAT 在干什么QAT 的核心动作叫伪量化(fake quantization)。听着玄乎,其实就一句话:训练的时候,在前向传播里插入量化再反量化这一步,让模型看到的是量化后的近似值,但梯度还是照常回传。前向传播长这样:正常训练: x → 权重 W → 输出 QAT 训练: x → W 先量化再反量化(W_hat D(Q(W))) → 输出模型在训练时就尝到了量化误差的苦,于是会自己调整权重,让这些误差对最终结果的影响变小。等训练完,真正量化的时候,精度损失就小很多。3.3 QAT 绕不过的一个坎:round 不可导这里有个数学上的麻烦。QAT 要在训练里插量化操作,但量化里的round(取整)这个函数,梯度几乎处处为 0——你想想,round 是个阶梯函数,平的地方斜率是 0,跳变的地方又不可导。梯度是 0,反向传播就传不下去,模型根本没法学。解决办法叫STE(Straight-Through Estimator,直通估计器),思路简单粗暴到有点耍赖:前向: 正常做 round,该量化量化 反向: 假装 round 不存在,梯度直接原样穿过去(把 round 的梯度当成 1)就是前向该干嘛干嘛,反向时对 round 睁一只眼闭一只眼。这招虽然理论上不严谨,但工程上非常好使,是几乎所有 QAT 实现的标配。等下第五节的代码里你会亲眼看到它怎么写。四、关键权衡:成本、精度、门槛选 PTQ 还是 QAT,本质是在三件事上做取舍。第一是成本。PTQ 便宜到几乎可以忽略——一张校准数据集、一次前向,搞定。QAT 要跑完整的训练循环,大模型上这意味着大量的卡时和电费。这一条基本就把 QAT 挡在了大多数人门外。第二是精度。在极低比特(INT4 以下、甚至 INT2)或者对精度极度敏感的场景,QAT 确实能比 PTQ 多捞回几个点,因为模型有机会在训练中主动适应量化误差。但在 INT8、INT4 这些主流档位上,现代 PTQ 算法(AWQ/GPTQ)已经把精度做得很接近原模型了,QAT 那点额外收益往往不值得它的成本。第三是门槛和数据。QAT 要求你有完整的训练流程和训练数据。很多情况下你根本拿不到——比如你用的是别人开源的模型,人家没给训练数据,你也没法复现训练。PTQ 就没这问题,有模型权重就能干活。一句话总结这个权衡:能用 PTQ 解决就别碰 QAT。只有当 PTQ 精度实在不达标、你又有训练资源和数据时,QAT 才是那根救命稻草。五、上手实测:跑一个最小的 QAT 循环光说不练假把式。我们用 PyTorch 手写一个最小的 QAT 例子,把伪量化和 STE 都跑一遍,让你看清 QAT 到底在代码层面做了什么。为了看得清楚,我们不用现成的量化库,而是自己实现伪量化层。5.1 先实现一个带 STE 的伪量化函数importtorchimporttorch.nnasnnclassFakeQuantSTE(torch.autograd.Function):伪量化 直通估计器(STE) 前向:正常量化再反量化 反向:梯度原样穿过(假装 round 不存在) staticmethoddefforward(ctx,x,num_bits8):qmax2**(num_bits-1)-1# INT8 - 127sx.abs().max()/qmax# 对称量化 scaleqtorch.round(x/s).clamp(-qmax,qmax)x_hatq*s# 反量化returnx_hatstaticmethoddefbackward(ctx,grad_output):# STE 的精髓:梯度直接透传,round 当作不存在returngrad_output,Nonedeffake_quant(x,num_bits8):returnFakeQuantSTE.apply(x,num_bits)注意backward里直接把grad_output原样返回——这就是 STE。如果这里老老实实按 round 求导,梯度全是 0,模型一步都学不动。5.2 搭一个带伪量化的小网络classQATLinear(nn.Module):训练时对权重做伪量化的全连接层def__init__(self,in_features,out_features,num_bits8):super().__init__()self.linearnn.Linear(in_features,out_features)self.num_bitsnum_bitsdefforward(self,x):# 关键:用伪量化后的权重做前向w_quantfake_quant(self.linear.weight,self.num_bits)returntorch.nn.functional.linear(x,w_quant,self.linear.bias)classTinyNet(nn.Module):def__init__(self,num_bits8):super().__init__()self.fc1QATLinear(20,64,num_bits)self.fc2QATLinear(64,2,num_bits)self.relunn.ReLU()defforward(self,x):xself.relu(self.fc1(x))returnself.fc2(x)5.3 造点数据,跑 QAT 训练torch.manual_seed(0)# 造一个简单的二分类数据集N2000Xtorch.randn(N,20)true_wtorch.randn(20,1)y(X true_w0).long().squeeze()X_train,y_trainX[:1600],y[:1600]X_test,y_testX[1600:],y[1600:]deftrain_and_eval(num_bits,epochs200,tag):modelTinyNet(num_bitsnum_bits)opttorch.optim.Adam(model.parameters(),lr0.01)loss_fnnn.CrossEntropyLoss()forepinrange(epochs):model.train()opt.zero_grad()outmodel(X_train)lossloss_fn(out,y_train)loss.backward()opt.step()# 评估model.eval()withtorch.no_grad():predmodel(X_test).argmax(dim1)acc(predy_test).float().mean().item()print(f[{tag}]{num_bits}-bit QAT 测试准确率:{acc:.4f})returnmodel# 对比不同比特下 QAT 的效果train_and_eval(num_bits8,tagQAT)train_and_eval(num_bits4,tagQAT)train_and_eval(num_bits2,tagQAT)典型输出(数字会因随机性略有波动):[QAT] 8-bit QAT 测试准确率: 0.9725 [QAT] 4-bit QAT 测试准确率: 0.9600 [QAT] 2-bit QAT 测试准确率: 0.9075看这组数字:即使量化到 2-bit,QAT 训练出来的模型还有 90% 准确率。为什么这么抗造?因为模型在训练时就一直戴着量化的镣铐跳舞,早就学会了怎么在低精度下把活干好。5.4 对照实验:PTQ 会掉多少我们再做个对照。先正常训练一个不带量化的模型,训完再直接量化(模拟 PTQ),看它掉多少:classPlainNet(nn.Module):def__init__(self):super().__init__()self.fc1nn.Linear(20,64)self.fc2nn.Linear(64,2)self.relunn.ReLU()defforward(self,x):xself.relu(self.fc1(x))returnself.fc2(x)defptq_experiment(num_bits):# 1. 正常训练(不带量化)modelPlainNet()opttorch.optim.Adam(model.parameters(),lr0.01)loss_fnnn.CrossEntropyLoss()forepinrange(200):opt.zero_grad()lossloss_fn(model(X_train),y_train)loss.backward()opt.step()# 2. 训完直接量化权重(PTQ)defquant_weight(w,num_bits):qmax2**(num_bits-1)-1sw.abs().max()/qmaxreturn(torch.round(w/s).clamp(-qmax,qmax))*swithtorch.no_grad():forlayerin[model.fc1,model.fc2]:layer.weight.dataquant_weight(layer.weight.data,num_bits)# 3. 评估withtorch.no_grad():predmodel(X_test).argmax(dim1)acc(predy_test).float().mean().item()print(f[PTQ]{num_bits}-bit PTQ 测试准确率:{acc:.4f})ptq_experiment(num_bits8)ptq_experiment(num_bits4)ptq_experiment(num_bits2)典型输出:[PTQ] 8-bit PTQ 测试准确率: 0.9700 [PTQ] 4-bit PTQ 测试准确率: 0.9325 [PTQ] 2-bit PTQ 测试准确率: 0.6875把两组数字摆一块看:比特PTQ 准确率QAT 准确率差距8-bit0.97000.9725几乎没差4-bit0.93250.9600QAT 略胜2-bit0.68750.9075QAT 大胜结论一目了然:比特越低,QAT 的优势越明显。8-bit 时两者几乎打平,根本没必要费劲搞 QAT;可到了 2-bit,PTQ 直接崩到接近瞎猜(二分类瞎猜是 50%),而 QAT 还稳稳站在 90%。这就是低比特场景 QAT 才值得出手这句话的实验依据。提醒一句:这是个玩具级实验,真实大模型上的表现会复杂得多,PTQ 有 AWQ/GPTQ 这些强算法加持,4-bit 的差距会比这里小很多。但低比特 QAT 更抗造这个定性结论是成立的。六、PTQ vs QAT 横向对比把这两条路线放一张表里,方便你以后查:维度PTQ(训练后量化)QAT(量化感知训练)是否需要训练不需要需要完整训练/微调时间成本分钟级小时到天级是否需要训练数据只需少量校准数据需要完整训练数据精度(高比特 INT8)很好,接近原模型很好,优势不明显精度(低比特 INT4↓)靠好算法能保住明显更优落地门槛低,有权重就能干高,要训练流程大模型适用性主流选择少数极端场景代表方法AWQ、GPTQ、GGUF、bitsandbytes各类 QAT 框架一句话记住:大模型量化,先 PTQ;PTQ 不行,再考虑 QAT。七、常见误区与避坑误区 1:QAT 一定比 PTQ 精度高,所以能用 QAT 就用 QAT。不对。高比特(INT8)下两者几乎没差,QAT 白白多花几十倍成本。只有低比特或极端精度要求时 QAT 才有意义。别为了那点边际收益去烧卡。误区 2:PTQ 不需要任何数据。不全对。权重量化确实基本不用数据,但激活量化和 AWQ/GPTQ 这类方法需要校准数据。所以说 PTQ不用训练数据是对的,不用任何数据是错的。误区 3:校准数据越多越好。不对。校准数据讲究代表性而不是数量,几百条覆盖典型分布的样本通常就够了。堆几万条同质数据既慢又没额外收益,还可能因为分布偏了反而拖累效果。误区 4:QAT 里 round 不可导没关系,反正框架会自动处理。要知其所以然。框架确实帮你处理了,靠的就是 STE(直通估计器)。你得明白它是前向真量化、反向假装 round 不存在,这样当 QAT 效果异常时你才知道从哪查起。误区 5:把 QAT 当成训练时就量化,能省训练显存。恰恰相反。QAT 训练时因为要同时维护浮点权重和伪量化操作,显存和计算开销比普通训练还高。QAT 省的是推理的资源,不是训练的。八、小结PTQ 是训练后量化,拿现成模型直接量化,快、便宜、门槛低,是大模型量化的绝对主流,AWQ/GPTQ/GGUF 全在这一派。QAT 是量化感知训练,训练时就让模型适应量化误差,精度更好但成本高、要训练数据,只在低比特或极端精度场景才划算。PTQ 的关键动作是校准,尤其是激活量化,需要有代表性的校准数据。QAT 的关键技术是伪量化 STE,用前向真量化、反向透传梯度绕过 round 不可导的问题。我们用 PyTorch 手写实验实测了两者:8-bit 打平、4-bit QAT 略胜、2-bit QAT 大胜,验证了比特越低 QAT 优势越大。选型原则一句话:能 PTQ 就别 QAT,PTQ 精度不达标且有训练资源时才上 QAT。