02 - 量化算法详解¶
深入了解 PTQ 、 QAT 、 GPTQ 、 AWQ 等量化算法
📖 章节概述¶
本章将详细介绍各种量化算法的实现原理和特点,包括 PTQ 、 QAT 、 GPTQ 、 AWQ 等。
🎯 学习目标¶
完成本章后,你将能够:
- 理解各种量化算法的原理
- 掌握量化算法的实现方法
- 了解不同算法的优缺点
- 能够根据场景选择合适的算法
1. PTQ (训练后量化)¶
1.1 算法原理¶
PTQ 在模型训练完成后直接进行量化,无需重新训练。主要步骤:
- 收集统计信息:使用校准数据收集激活值的统计信息
- 计算量化参数:基于统计信息计算 scale 和 zero_point
- 应用量化:将权重和激活值量化为目标精度
1.2 实现代码¶
import torch
import torch.nn as nn
import numpy as np
class PTQQuantizer:
"""
PTQ量化器
"""
def __init__(self, bits=8, symmetric=False):
self.bits = bits
self.symmetric = symmetric
self.qmin = -2**(bits-1) if symmetric else 0
self.qmax = 2**(bits-1) - 1 if symmetric else 2**bits - 1
def calibrate(self, model, dataloader):
"""
校准模型:收集激活值统计信息(非权重)
PTQ 校准的核心是收集每层激活值的分布,
用于确定量化参数 scale 和 zero_point。
Args:
model: 要校准的模型
dataloader: 校准数据加载器
"""
model.eval()
# 收集激活值统计信息(注意:校准关注的是激活值,不是权重)
activation_stats = {}
hooks = []
def stat_hook(name):
def hook_fn(module, input, output):
# 收集输出激活值的统计信息
act = output.detach()
if name not in activation_stats:
activation_stats[name] = {
'min': float('inf'),
'max': float('-inf'),
'abs_max': 0.0,
'count': 0
}
activation_stats[name]['min'] = min(
activation_stats[name]['min'], act.min().item()
)
activation_stats[name]['max'] = max(
activation_stats[name]['max'], act.max().item()
)
activation_stats[name]['abs_max'] = max(
activation_stats[name]['abs_max'], act.abs().max().item()
)
activation_stats[name]['count'] += 1
return hook_fn
# 注册 hook 到每个 Linear 层
for name, module in model.named_modules():
if isinstance(module, nn.Linear):
hooks.append(module.register_forward_hook(stat_hook(name)))
# 运行校准数据
with torch.no_grad():
for batch_x, _ in dataloader:
_ = model(batch_x)
# 移除 hook
for hook in hooks:
hook.remove()
return activation_stats
def quantize_weight(self, weight, stats):
"""
量化权重
Args:
weight: 权重张量
stats: 统计信息
"""
# 计算scale和zero_point
if self.symmetric:
scale = max(abs(stats['min']), abs(stats['max'])) / (2**(self.bits-1) - 1)
zero_point = 0
else:
scale = (stats['max'] - stats['min']) / (2**self.bits - 1)
zero_point = -round(stats['min'] / scale)
# 量化
quantized = torch.round(weight / scale) + zero_point
quantized = torch.clamp(quantized, self.qmin, self.qmax)
# 反量化
dequantized = (quantized - zero_point) * scale
return quantized, dequantized, scale, zero_point
# 使用示例
# quantizer = PTQQuantizer(bits=8, symmetric=False)
# stats = quantizer.calibrate(model, dataloader)
# quantized, dequantized, scale, zero_point = quantizer.quantize_weight(weight, stats['layer1'])
2. QAT (量化感知训练)¶
2.1 算法原理¶
QAT 在训练过程中模拟量化误差,使模型适应量化后的精度。主要特点:
- 前向传播:模拟量化操作
- 反向传播:使用直通估计器( STE )
- 参数更新:更新浮点数参数
2.2 实现代码¶
import torch
import torch.nn as nn
class QuantizeLayer(nn.Module):
"""
量化层
"""
def __init__(self, bits=8, symmetric=False):
super().__init__() # super()调用父类方法
self.bits = bits
self.symmetric = symmetric
self.scale = nn.Parameter(torch.ones(1))
self.zero_point = nn.Parameter(torch.zeros(1))
def forward(self, x):
"""
前向传播
"""
# 量化
quantized = torch.round(x / self.scale) + self.zero_point
# 截断
qmin = -2**(self.bits-1) if self.symmetric else 0
qmax = 2**(self.bits-1) - 1 if self.symmetric else 2**self.bits - 1
quantized = torch.clamp(quantized, qmin, qmax)
# 反量化(使用直通估计器)
dequantized = (quantized - self.zero_point) * self.scale
return dequantized
class QATModel(nn.Module):
"""
QAT模型
"""
def __init__(self, base_model):
super().__init__()
self.base_model = base_model
self.quantize_layers = nn.ModuleList()
# 为每个线性层添加量化层
for name, module in base_model.named_modules():
if isinstance(module, nn.Linear): # isinstance检查对象类型
self.quantize_layers.append(QuantizeLayer(bits=8))
def forward(self, x):
"""
前向传播
"""
# 简化实现,实际需要更复杂的处理
x = self.base_model(x)
return x
# 使用示例
# qat_model = QATModel(model)
# # 训练qat_model...
3. GPTQ¶
3.1 算法原理¶
GPTQ 基于 Hessian 信息进行量化,通过最小化量化误差来优化量化参数。核心思想:
- 计算 Hessian 矩阵:基于校准数据计算 Hessian
- 优化量化参数:使用 Hessian 信息优化 scale
- 迭代量化:逐层或逐组量化
3.2 实现代码¶
import torch
import torch.nn as nn
class GPTQQuantizer:
"""
GPTQ量化器
"""
def __init__(self, bits=4, group_size=128):
self.bits = bits
self.group_size = group_size
def quantize_layer(self, weight, hessian):
"""
量化层
Args:
weight: 权重张量
hessian: Hessian矩阵
"""
# 分组量化
quantized_weight = torch.zeros_like(weight)
for i in range(0, weight.shape[0], self.group_size):
for j in range(0, weight.shape[1], self.group_size):
# 获取权重组
weight_group = weight[i:i+self.group_size, j:j+self.group_size]
# 计算量化参数
scale = weight_group.abs().max() / (2**(self.bits-1) - 1)
# 量化
quantized_group = torch.round(weight_group / scale)
quantized_group = torch.clamp(
quantized_group,
-2**(self.bits-1),
2**(self.bits-1) - 1
)
# 反量化
dequantized_group = quantized_group * scale
# 更新量化权重
quantized_weight[i:i+self.group_size, j:j+self.group_size] = dequantized_group
return quantized_weight
# 使用示例
# quantizer = GPTQQuantizer(bits=4, group_size=128)
# quantized_weight = quantizer.quantize_layer(weight, hessian)
4. AWQ¶
4.1 算法原理¶
AWQ 基于激活值的分布进行量化,考虑激活值的统计特性来优化权重量化。
4.2 实现代码¶
class AWQQuantizer:
"""
AWQ量化器
"""
def __init__(self, bits=4):
self.bits = bits
def quantize_with_activation(self, weight, activation_stats):
"""
基于激活值统计量化
Args:
weight: 权重张量
activation_stats: 激活值统计信息
"""
# 基于激活值分布调整量化参数
scale = activation_stats['std'] * 3 / (2**(self.bits-1) - 1)
# 量化
quantized = torch.round(weight / scale)
quantized = torch.clamp(
quantized,
-2**(self.bits-1),
2**(self.bits-1) - 1
)
# 反量化
dequantized = quantized * scale
return dequantized
# 使用示例
# quantizer = AWQQuantizer(bits=4)
# quantized_weight = quantizer.quantize_with_activation(weight, activation_stats)
5. 算法对比¶
| 算法 | 精度 | 速度 | 复杂度 | 适用场景 |
|---|---|---|---|---|
| PTQ | 中 | 快 | 低 | 快速部署 |
| QAT | 高 | 慢 | 高 | 高精度要求 |
| GPTQ | 高 | 中 | 高 | 大模型量化 |
| AWQ | 高 | 中 | 中 | 激活值敏感模型 |
6. 面试题¶
基础题¶
Q1: PTQ 和 QAT 的主要区别是什么?
A: PTQ 在训练后直接量化, QAT 在训练过程中模拟量化。 QAT 精度更高但需要重新训练。
Q2: GPTQ 的核心思想是什么?
A: GPTQ 基于 Hessian 信息优化量化参数,通过最小化量化误差来提高精度。
进阶题¶
Q3: 如何选择合适的量化算法?
A: 需要考虑精度要求、数据可用性、计算资源和模型类型等因素。
Q4: AWQ 相比其他算法的优势是什么?
A: AWQ 考虑激活值分布,对激活值敏感的模型效果更好。
7. 练习题¶
基础练习¶
- 实现简单的 PTQ
# 练习: 实现简单的PTQ
class SimplePTQ:
def __init__(self, bits=8):
# 你的代码
pass
def quantize(self, weight):
# 你的代码
pass
- 实现量化层
# 练习: 实现量化层
class QuantizeLayer(nn.Module):
def __init__(self, bits=8):
# 你的代码
pass
def forward(self, x):
# 你的代码
pass
进阶练习¶
- 实现 GPTQ
# 练习: 实现GPTQ
class GPTQ:
def __init__(self, bits=4, group_size=128):
# 你的代码
pass
def quantize(self, weight, hessian):
# 你的代码
pass
- 实现 AWQ
# 练习: 实现AWQ
class AWQ:
def __init__(self, bits=4):
# 你的代码
pass
def quantize(self, weight, activation_stats):
# 你的代码
pass
📝 参考答案提示¶
以下为练习题的关键实现思路,建议先独立完成后再对照。
练习 1:实现简单的 PTQ
class SimplePTQ:
def __init__(self, bits=8):
self.bits = bits
self.qmin = 0
self.qmax = 2**bits - 1
def quantize(self, weight):
# 1. 计算 scale 和 zero_point
scale = (weight.max() - weight.min()) / (self.qmax - self.qmin)
zero_point = self.qmin - round(weight.min().item() / scale.item())
# 2. 量化 + 截断 + 反量化
q = torch.clamp(torch.round(weight / scale) + zero_point, self.qmin, self.qmax)
return (q - zero_point) * scale # 反量化结果
练习 2:实现量化层
class QuantizeLayer(nn.Module):
def __init__(self, bits=8):
super().__init__()
self.bits = bits
self.scale = nn.Parameter(torch.ones(1))
self.zero_point = nn.Parameter(torch.zeros(1))
def forward(self, x):
q = torch.round(x / self.scale) + self.zero_point
q = torch.clamp(q, 0, 2**self.bits - 1)
return (q - self.zero_point) * self.scale # STE: 梯度直通
练习 3-4 提示: GPTQ 核心是逐行利用 Hessian 逆矩阵优化量化误差 δw = -w_q * (H^{-1}_{col,col})^{-1}; AWQ 核心是找到激活值最大的通道并放大对应权重再量化,保护重要通道精度。参考本章 3.2 和 4.2 节代码。
8. 最佳实践¶
✅ 推荐做法¶
- 根据需求选择算法
- 快速部署选 PTQ
- 高精度选 QAT
-
大模型选 GPTQ
-
充分校准
- 使用代表性数据
- 充分校准
-
验证校准效果
-
测试验证
- 在多个数据集上测试
- 记录量化前后性能
- 评估实际应用效果
❌ 避免做法¶
- 盲目追求低精度
- 考虑应用需求
- 评估精度损失
-
平衡性能和精度
-
忽略校准质量
- 使用高质量校准数据
- 充分校准
-
验证校准效果
-
单一算法
- 尝试多种算法
- 对比效果
- 选择最优方案
9. 总结¶
本章详细介绍了各种量化算法:
- PTQ: 训练后量化,简单快速
- QAT: 量化感知训练,精度高
- GPTQ: 基于 Hessian 优化,适合大模型
- AWQ: 考虑激活值分布,效果稳定
掌握这些算法的原理和实现是面试的关键。
10. 下一步¶
继续学习03-量化精度损失评估,了解如何评估量化的精度损失。
💡 进阶补充:K-means 量化与线性量化深度对比(参考 awesome-compression 第 4 章)
除了本章介绍的 PTQ/QAT/GPTQ/AWQ,awesome-compression 教程还详细介绍了两种基础量化方法:
K-means 量化(基于聚类的量化) - 原理:将权重聚类为 K 类,每类共享一个质心值,存储时只需保存索引 + 质心表 - 压缩示例:16 个 FP32 权重(512 bit)→ 16 个 2-bit 索引 + 4 个 FP32 质心(160 bit)= 68.75% 压缩 - 推理:读取转换表,根据索引获取对应值 - 训练:梯度按聚类方式累加,反向传播更新质心 - 优势:对权重分布不规则的情况效果更好
线性量化(对称 vs 非对称) - 对称量化:
r = S × q,零点 Z=0,适用于权重分布近似对称的场景 - 非对称量化:r = S × (q - Z),零点 Z≠0,适用于激活值(如 ReLU 后全为正数) - S 和 Z 的计算: -S = (r_max - r_min) / (q_max - q_min)-Z = round(q_max - r_max / S)量化策略进阶(awesome-compression 第 4.4-4.8 节) | 策略 | 核心思想 | 适用场景 | |------|---------|---------| | 逐层量化 | 每层独立计算量化参数 | 通用基线方法 | | 逐通道量化 | 每个通道独立量化 | 卷积层精度更优 | | 混合精度量化 | 不同层使用不同比特数 | 精度-效率平衡 | | 量化感知训练(QAT) | 训练时模拟量化误差 | 高精度要求 | | 训练后量化(PTQ) | 训练完成后直接量化 | 快速部署 |
🔥 实战经验:大模型量化的工程选择
- LLM.INT8():通过混合精度分解,对异常值通道保持 FP16,其余 INT8
- GPTQ:基于 Hessian 矩阵的逐层量化,适合 4-bit 量化
- AWQ:基于激活值感知的权重量化,保护重要权重通道
- GGUF 格式:llama.cpp 使用的量化格式,支持 CPU 推理
- 推荐路径:快速验证用 PTQ/INT8 → 精度不够用 GPTQ/AWQ → 极致压缩用 2-4 bit 混合精度
⚠️ 核验说明(2026-03-26):本页已纳入 2026-03-26 全站统一复核批次。若文中涉及外部模型、API、版本号、价格或第三方产品名称,请以官方文档和实际运行环境为准。
最后更新日期: 2026-03-26