前置知识: 大语言模型

量化

00:00
5 min Intermediate

理解 LLM 量化的原理和方法,包括 INT8、INT4、GPTQ、AWQ 和 GGUF 格式

量化

一个 70B 参数的 FP16 模型需要 140GB 内存。量化到 INT4 后只需 35GB——4 倍压缩,性能损失不到 2%。量化是让大模型在消费级硬件上运行的唯一实用方案。

类型: 构建 语言: Python 前置条件: Phase 10 Lesson 04(预训练 Mini-GPT) 预计时间: ~60 分钟

学习目标

  • 理解量化的基本原理:从 FP32/FP16 到 INT8/INT4
  • 掌握主要量化方法:训练后量化(PTQ)和量化感知训练(QAT)
  • 理解 GPTQ、AWQ 和 GGUF 的区别和适用场景
  • 实现简单的训练后量化

量化原理

量化将高精度浮点数映射到低精度整数:

FP32: [-3.4e38, 3.4e38]  →  32 位
FP16: [-65504, 65504]     →  16 位
INT8: [-128, 127]         →  8 位
INT4: [-8, 7]             →  4 位

对称量化的映射公式:

import torch


def symmetric_quantize(tensor, num_bits=8):
    """对称量化"""
    # 计算 scale
    max_val = tensor.abs().max()
    scale = max_val / (2 ** (num_bits - 1) - 1)

    # 量化
    quantized = torch.round(tensor / scale)
    quantized = torch.clamp(quantized, -(2 ** (num_bits - 1)), 2 ** (num_bits - 1) - 1)

    return quantized.to(torch.int8), scale


def dequantize(quantized, scale):
    """反量化"""
    return quantized.float() * scale


def compute_quantization_error(original, dequantized):
    """计算量化误差"""
    mse = ((original - dequantized) ** 2).mean()
    max_error = (original - dequantized).abs().max()
    return mse.item(), max_error.item()

逐通道量化

逐张量量化整个权重矩阵使用同一个 scale,可能导致精度损失。逐通道量化对每行/列使用独立的 scale:

def per_channel_quantize(weight, num_bits=8):
    """逐通道量化"""
    # 沿输出维度计算 scale
    max_val = weight.abs().amax(dim=-1, keepdim=True)
    scale = max_val / (2 ** (num_bits - 1) - 1)

    quantized = torch.round(weight / scale)
    quantized = torch.clamp(quantized, -(2 ** (num_bits - 1)), 2 ** (num_bits - 1) - 1)

    return quantized.to(torch.int8), scale.squeeze()

GPTQ

GPTQ 是最流行训练后量化方法之一。它通过逐层优化最小化量化模型输出影响

def gptq_quantize_layer(layer_weight, num_bits=4, block_size=128, group_size=128):
    """GPTQ 量化单层"""
    # 初始化 Hessian 矩阵(用校准数据计算)
    # 这里简化为使用权重本身近似
    H = (layer_weight @ layer_weight.T) / layer_weight.shape[0]
    H_inv = torch.linalg.inv(H + 1e-6 * torch.eye(H.shape[0]))

    quantized_weight = layer_weight.clone()
    errors = torch.zeros_like(layer_weight)

    n_rows, n_cols = layer_weight.shape

    for col in range(n_cols):
        # 量化当前列
        w_col = quantized_weight[:, col]
        scale = w_col.abs().max() / (2 ** (num_bits - 1) - 1)
        q_col = torch.clamp(torch.round(w_col / scale),
                           -(2 ** (num_bits - 1)), 2 ** (num_bits - 1) - 1)

        # 计算量化误差
        err = (w_col - q_col * scale) / H_inv[col, col]

        # 更新量化权重
        quantized_weight[:, col] = q_col * scale

        # 将误差分配到后续列
        if col + 1 < n_cols:
            quantized_weight[:, col + 1:] -= err.unsqueeze(1) * H_inv[col, col + 1:].unsqueeze(0)

    return quantized_weight

量化方法对比

方法内存适用场景
FP16基准基准基准训练推理
INT8 PTQ~0.5% 损失2x 加速2x 压缩服务推理
INT4 GPTQ~1-2% 损失1.5x 加速4x 压缩消费级 GPU
INT4 AWQ~1% 损失1.5x 加速4x 压缩消费级 GPU
GGUF (Q4_K_M)~1-3% 损失CPU 友好4x 压缩CPU/Mac 推理

实际使用

# 使用 bitsandbytes 进行 INT8 量化
from transformers import AutoModelForCausalLM, BitsAndBytesConfig

int8_config = BitsAndBytesConfig(load_in_8bit=True)
model = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Llama-2-7b-hf",
    quantization_config=int8_config,
)

# 使用 bitsandbytes 进行 INT4 量化(NF4)
int4_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.bfloat16,
)
model = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Llama-2-7b-hf",
    quantization_config=int4_config,
)

# 使用 AutoGPTQ
from auto_gptq import AutoGPTQForCausalLM
model = AutoGPTQForCausalLM.from_quantized("TheBloke/Llama-2-7B-GPTQ")

# 使用 llama.cpp (GGUF)
# python convert.py /path/to/model --outtype f16 --outfile model.gguf
# ./llama-cli -m model.gguf -p "Hello, world!"

关键术语

术语通俗说法实际含义
量化压缩映射到低精整数,减少内存和计算开销
PTQ训练后量化”Post-Training Quantization,在模型训练完成后进量化
QAT训练时量化”Quantization-Aware Training,在训练过程中模拟量化效果
GPTQ”逐层最优量化”基于近似二阶信息的逐层训练后量化方法
AWQ激活感知量化”Activation-aware Weight Quantization,保护重要权重通道的量化方法
GGUF”llama.cpp 式”llama.cpp 使用的量化模型式,支持 CPU 推理

延伸阅读

知识检测

学习进度

-- 已学文档
--% 知识覆盖率

学习推荐

专注模式