跳转至

02 - 量化算法详解

深入了解 PTQ 、 QAT 、 GPTQ 、 AWQ 等量化算法

📖 章节概述

本章将详细介绍各种量化算法的实现原理和特点,包括 PTQ 、 QAT 、 GPTQ 、 AWQ 等。

🎯 学习目标

完成本章后,你将能够:

  • 理解各种量化算法的原理
  • 掌握量化算法的实现方法
  • 了解不同算法的优缺点
  • 能够根据场景选择合适的算法

1. PTQ (训练后量化)

1.1 算法原理

PTQ 在模型训练完成后直接进行量化,无需重新训练。主要步骤:

  1. 收集统计信息:使用校准数据收集激活值的统计信息
  2. 计算量化参数:基于统计信息计算 scale 和 zero_point
  3. 应用量化:将权重和激活值量化为目标精度

1.2 实现代码

Python
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 在训练过程中模拟量化误差,使模型适应量化后的精度。主要特点:

  1. 前向传播:模拟量化操作
  2. 反向传播:使用直通估计器( STE )
  3. 参数更新:更新浮点数参数

2.2 实现代码

Python
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 信息进行量化,通过最小化量化误差来优化量化参数。核心思想:

  1. 计算 Hessian 矩阵:基于校准数据计算 Hessian
  2. 优化量化参数:使用 Hessian 信息优化 scale
  3. 迭代量化:逐层或逐组量化

3.2 实现代码

Python
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 实现代码

Python
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. 练习题

基础练习

  1. 实现简单的 PTQ
Python
# 练习: 实现简单的PTQ
class SimplePTQ:
    def __init__(self, bits=8):
        # 你的代码
        pass

    def quantize(self, weight):
        # 你的代码
        pass
  1. 实现量化层
Python
# 练习: 实现量化层
class QuantizeLayer(nn.Module):
    def __init__(self, bits=8):
        # 你的代码
        pass

    def forward(self, x):
        # 你的代码
        pass

进阶练习

  1. 实现 GPTQ
Python
# 练习: 实现GPTQ
class GPTQ:
    def __init__(self, bits=4, group_size=128):
        # 你的代码
        pass

    def quantize(self, weight, hessian):
        # 你的代码
        pass
  1. 实现 AWQ
Python
# 练习: 实现AWQ
class AWQ:
    def __init__(self, bits=4):
        # 你的代码
        pass

    def quantize(self, weight, activation_stats):
        # 你的代码
        pass

📝 参考答案提示

以下为练习题的关键实现思路,建议先独立完成后再对照。

练习 1:实现简单的 PTQ

Python
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:实现量化层

Python
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. 最佳实践

✅ 推荐做法

  1. 根据需求选择算法
  2. 快速部署选 PTQ
  3. 高精度选 QAT
  4. 大模型选 GPTQ

  5. 充分校准

  6. 使用代表性数据
  7. 充分校准
  8. 验证校准效果

  9. 测试验证

  10. 在多个数据集上测试
  11. 记录量化前后性能
  12. 评估实际应用效果

❌ 避免做法

  1. 盲目追求低精度
  2. 考虑应用需求
  3. 评估精度损失
  4. 平衡性能和精度

  5. 忽略校准质量

  6. 使用高质量校准数据
  7. 充分校准
  8. 验证校准效果

  9. 单一算法

  10. 尝试多种算法
  11. 对比效果
  12. 选择最优方案

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