第四章:量化技术——INT8/INT4量化、GPTQ、AWQ、量化感知训练、量化后的精度损失评估
量化,说白了就是给模型“减肥”。
一个70B的模型,FP16精度下光参数就要140GB显存。你想想看,普通显卡根本跑不动。我最早接触量化是在两年前,当时要给一个金融风控模型做端侧部署,模型太大,手机根本装不下。后来发现量化这招,直接把模型砍到原来的四分之一,效果还凑合。
嗯,今天我们就聊聊量化的那些事。
4.1 量化是什么?
量化就是把模型里的浮点数(比如FP32、FP16)变成整数(INT8、INT4)。
举个例子:一个权重是3.1415926,量化成INT8后可能变成125(假设缩放因子是0.025)。推理时再反量化回来,虽然精度有损失,但模型体积和推理速度都大幅提升。
我个人习惯把量化分为两类:
- 训练后量化(PTQ):模型训练完再做量化,简单粗暴。
- 量化感知训练(QAT):训练时就模拟量化误差,精度更高。
核心公式:
量化值 = round(浮点值 / 缩放因子) + 零点偏移
反量化:浮点值 = (量化值 - 零点偏移) × 缩放因子
4.2 INT8与INT4量化
INT8量化是目前最成熟的方案。每个参数从16位降到8位,模型体积直接减半。推理速度能快2-4倍。
INT4就更狠了,体积再砍一半。但精度损失也更大。我在项目中遇到过,用INT4量化一个BERT模型,下游任务F1值掉了3个点。后来改用混合精度——关键层用INT8,非关键层用INT4,才把损失控制在1%以内。
| 量化精度 | 模型体积缩减 | 推理加速比 | 精度损失(典型) |
|---|---|---|---|
| FP16→INT8 | 50% | 2-4x | 0.5%-1% |
| FP16→INT4 | 75% | 4-8x | 1%-3% |
| 混合INT8/INT4 | 60%-70% | 3-6x | 0.5%-1.5% |
注意:INT4量化对激活值敏感。如果激活值分布不均匀,量化误差会放大。我曾经踩过这个坑,模型输出全是乱码,排查了半天才发现是激活值范围没校准好。
4.3 GPTQ:基于最优脑损伤的量化
GPTQ是目前大模型量化的主流方法之一。它的思路很有意思——不是简单地把每个权重单独量化,而是考虑权重之间的相互影响。
说白了,就是找到那些“砍掉后对输出影响最小”的权重,优先量化它们。这有点像“最优脑损伤”的思想,每次剪掉一个权重,然后调整其他权重来补偿误差。
# GPTQ量化伪代码
def gptq_quantize(weights, calibration_data):
# 1. 计算Hessian矩阵(二阶梯度)
H = compute_hessian(weights, calibration_data)
# 2. 逐列量化,并补偿误差
for col in range(weights.shape[1]):
# 找到当前列的最优量化值
q_val = quantize(weights[:, col])
# 计算量化误差
error = weights[:, col] - dequantize(q_val)
# 将误差分配到其他未量化的列
weights[:, col+1:] -= error * H_inv[col, col+1:]
return quantized_weights
我建议你在使用GPTQ时,校准数据要选好。最好用和下游任务分布一致的数据。我之前用通用语料做校准,结果在代码生成任务上精度掉了2%,换成代码语料后只掉了0.3%。
4.4 AWQ:激活值感知量化
AWQ是另一种主流方法。它和GPTQ的区别在于:GPTQ关注权重,AWQ关注激活值。
为什么关注激活值?因为激活值中有些通道特别“重要”——它们的值很大,对输出影响也大。AWQ的做法是:对重要通道保留更高精度,对不重要通道可以多砍一点。
我的经验:AWQ在视觉模型上表现更好,GPTQ在语言模型上更稳。但这不是绝对的,建议你两个都试试。我一般先用AWQ快速评估,再用GPTQ做最终优化。
4.5 量化感知训练(QAT)
QAT是在训练过程中模拟量化操作。模型会“学会”适应量化带来的误差。
具体做法是:在前向传播时插入伪量化节点(fake quantize),把权重和激活值先量化再反量化。这样反向传播时,梯度就能感知到量化误差。
# QAT中的伪量化操作
class FakeQuantize(torch.autograd.Function):
@staticmethod
def forward(ctx, x, scale, zero_point):
# 量化
q_x = torch.round(x / scale + zero_point)
# 反量化(模拟量化误差)
return (q_x - zero_point) * scale
@staticmethod
def backward(ctx, grad_output):
# 直通估计器(STE):梯度直接通过
return grad_output, None, None
嗯,这里要注意:QAT的训练时间会变长,因为每次前向都要做量化反量化。我建议你只在最后几个epoch开启QAT,或者只对关键层做QAT。
避坑指南:我曾经在QAT中用了太高的学习率,结果模型直接不收敛了。QAT的学习率一般要比正常训练小一个数量级,建议从1e-5开始试。
4.6 精度损失评估
量化完怎么知道效果好不好?不能光看模型体积小了,还得看精度。
我一般从三个维度评估:
- 任务指标:比如分类任务的准确率、生成任务的BLEU/ROUGE。这是最直接的。
- 输出分布:对比量化前后模型输出的概率分布。KL散度越小越好。
- 异常检测:看量化后有没有出现NaN、Inf等异常值。如果有,说明量化参数没设对。
| 评估维度 | 指标 | 可接受范围 |
|---|---|---|
| 任务指标 | 准确率/ROUGE等 | 下降不超过1% |
| 输出分布 | KL散度 | < 0.01 |
| 异常检测 | NaN/Inf比例 | 0% |
我的建议:别只看一个指标。有时候任务指标没掉,但输出分布已经变了。这种模型上线后可能会出问题。我习惯把三个维度都跑一遍,确保万无一失。
4.7 知识体系图
下面这张图总结了量化的核心脉络:
量化这条路,说白了就是“用精度换效率”。但怎么换、换多少、换完怎么检查,每一步都有讲究。我建议你从INT8开始试,如果精度损失在可接受范围内,再考虑INT4。别一上来就追求极致压缩,容易翻车。
最后说一句:量化不是银弹。如果你的模型本身精度就不高,量化后可能会更差。先确保原始模型够好,再考虑量化。
公众号:蓝海资料掘金营,微信deep3321