16-大模型量化技术:从FP16到INT4的极致压缩

21 阅读8分钟

什么是量化?

量化(Quantization)是将模型参数和激活值从高精度表示(如FP32、FP16)转换为低精度表示(如INT8、INT4)的过程。

为什么需要量化?

以LLaMA-70B为例,看看不同精度下的存储需求:

精度每个参数大小模型总大小显存占用(推理)
FP324 bytes280 GB~300 GB
FP162 bytes140 GB~160 GB
INT81 byte70 GB~80 GB
INT40.5 byte35 GB~45 GB

问题

  • FP32:需要4个A100 (80GB)才能加载
  • FP16:需要2个A100
  • INT8:1个A100就够
  • INT4:半个A100即可

量化的三大好处

  1. 减少显存占用:可以在更小的设备上运行大模型
  2. 加速推理:INT运算比浮点运算快
  3. 降低成本:可以用更便宜的硬件

量化的基本概念

数值表示回顾

浮点数(Floating Point)

FP32: 1位符号 + 8位指数 + 23位尾数 = 32
FP16: 1位符号 + 5位指数 + 10位尾数 = 16

表示范围:
- FP32: ±3.4 × 10^38
- FP16: ±6.5 × 10^4

整数(Integer)

INT8: 有符号整数,范围 -128 到 127
INT4: 有符号整数,范围 -8 到 7

量化的本质

量化就是建立一个映射函数,将连续的浮点数空间映射到离散的整数空间:

xquant=Round(xfloatzs)x_{\text{quant}} = \text{Round}\left(\frac{x_{\text{float}} - z}{s}\right)

其中:

  • xfloatx_{\text{float}}:原始浮点值
  • xquantx_{\text{quant}}:量化后的整数值
  • ss:缩放因子(scale)
  • zz:零点(zero point)

反量化(恢复)

xfloatxquant×s+zx_{\text{float}} \approx x_{\text{quant}} \times s + z

量化的类型

1. 对称量化 vs 非对称量化

对称量化(Symmetric Quantization)

特点:零点固定为0,数值范围对称

xquant=Round(xfloats)x_{\text{quant}} = \text{Round}\left(\frac{x_{\text{float}}}{s}\right)

缩放因子计算:

s=max(xfloat)2b11s = \frac{\max(|x_{\text{float}}|)}{2^{b-1} - 1}

其中 bb 是量化位数(如INT8,b=8b=8

例子(INT8对称量化)

原始值范围:[-12.8, 12.8]
量化范围:[-127, 127]

s = 12.8 / 127 = 0.1007

量化:
  12.8 → Round(12.8 / 0.1007) = 127
  0.0  → Round(0.0 / 0.1007) = 0
  -6.4 → Round(-6.4 / 0.1007) = -64

反量化:
  127127 × 0.1007 = 12.79
  00 × 0.1007 = 0.0
  -64 → -64 × 0.1007 = -6.44

优点

  • 简单,零点为0,计算快
  • 适合权重(通常均值接近0)

缺点

  • 如果数据不对称(如ReLU激活值都是正数),会浪费一半量化范围

非对称量化(Asymmetric Quantization)

特点:零点可以不为0,更灵活

xquant=Round(xfloatzs)x_{\text{quant}} = \text{Round}\left(\frac{x_{\text{float}} - z}{s}\right)

参数计算:

s=xmaxxmin2b1z=Round(xmins)\begin{aligned} s &= \frac{x_{\max} - x_{\min}}{2^b - 1} \\ z &= -\text{Round}\left(\frac{x_{\min}}{s}\right) \end{aligned}

例子(INT8非对称量化)

原始值范围:[0.0, 12.8](ReLU激活值,全是正数)
量化范围:[0, 255](使用无符号INT8)

s = (12.8 - 0.0) / 255 = 0.0502
z = 0

量化:
  12.8 → Round((12.8 - 0) / 0.0502) = 255
  6.4  → Round((6.4 - 0) / 0.0502) = 127
  0.0  → Round((0.0 - 0) / 0.0502) = 0

反量化:
  255255 × 0.0502 = 12.8
  127127 × 0.0502 = 6.37
  00 × 0.0502 = 0.0

优点

  • 充分利用量化范围,精度更高
  • 适合激活值(通常不对称)

缺点

  • 需要存储零点z,计算稍慢

2. 逐张量量化 vs 逐通道量化

逐张量量化(Per-Tensor Quantization)

定义:整个张量使用一组 (s,z)(s, z)

# 权重矩阵 W: (4096, 4096)
# 计算全局的 s 和 z
s = (W.max() - W.min()) / 255
z = -round(W.min() / s)

# 量化整个矩阵
W_quant = round((W - z) / s)

优点

  • 简单,只需存储一个scale
  • 计算快

缺点

  • 如果张量内不同维度的数值范围差异大,精度损失大

逐通道量化(Per-Channel Quantization)

定义:每个输出通道使用独立的 (si,zi)(s_i, z_i)

# 权重矩阵 W: (out_channels, in_channels)
# 对每个输出通道独立量化
for i in range(out_channels):
    s[i] = (W[i].max() - W[i].min()) / 255
    z[i] = -round(W[i].min() / s[i])
    W_quant[i] = round((W[i] - z[i]) / s[i])

优点

  • 精度更高(考虑了每个通道的特性)
  • 现代硬件支持良好

缺点

  • 需要存储多个scale(但相比权重本身可忽略)

对比示例

假设权重矩阵的两个通道:

# 通道1:值在 [-0.5, 0.5] 范围
# 通道2:值在 [-5.0, 5.0] 范围

逐张量量化:
  全局范围:[-5.0, 5.0]
  s = 10.0 / 255 = 0.0392
  通道1的精度:0.0392(浪费了,因为它的范围小)
  通道2的精度:0.0392

逐通道量化:
  通道1: s1 = 1.0 / 255 = 0.0039(精度提升10倍!)
  通道2: s2 = 10.0 / 255 = 0.0392

结论:逐通道量化是主流选择,尤其对于权重量化。

3. 训练后量化 vs 量化感知训练

训练后量化(Post-Training Quantization, PTQ)

定义:在已训练好的模型上直接应用量化

流程

1. 加载预训练模型(FP16)
2. 校准(用少量数据确定scale和zero point)
3. 量化权重和激活
4. 保存量化模型

优点

  • 简单,无需重新训练
  • 适用于任何已训练模型

缺点

  • 精度损失可能较大(特别是INT4)
  • 需要校准数据集

量化感知训练(Quantization-Aware Training, QAT)

定义:在训练过程中模拟量化,让模型适应量化误差

流程

1. 从预训练模型开始(或从零开始)
2. 在前向传播中插入"伪量化"节点
3. 正常训练(反向传播仍使用FP32)
4. 训练完成后导出真正的量化模型

伪量化(Fake Quantization)

def fake_quantize(x, scale, zero_point, num_bits=8):
    # 量化
    x_quant = torch.round((x - zero_point) / scale)
    x_quant = torch.clamp(x_quant, 0, 2**num_bits - 1)

    # 反量化(仍用FP32表示)
    x_dequant = x_quant * scale + zero_point

    return x_dequant  # 梯度可以反向传播

优点

  • 精度更高(模型学会适应量化误差)
  • 可以达到接近FP16的精度

缺点

  • 需要重新训练(成本高)
  • 需要训练数据和计算资源

对比

方法训练成本精度适用场景
PTQ无(只需校准)中等快速部署,资源受限
QAT高(需重训练)追求极致精度

实际应用

  • 大模型通常使用PTQ(训练成本太高)
  • 小模型或关键应用使用QAT

权重量化 vs 激活量化

权重量化(Weight-Only Quantization)

定义:只量化模型权重,激活值保持FP16

流程

1. 权重存储为INT4/INT8
2. 推理时,反量化权重到FP16
3. 与FP16激活值进行FP16矩阵乘法

代码示例

class QuantizedLinear(nn.Module):
    def __init__(self, weight_int8, scale, zero_point):
        self.weight_int8 = weight_int8  # INT8权重
        self.scale = scale
        self.zero_point = zero_point

    def forward(self, x):
        # x: FP16激活值

        # 反量化权重到FP16
        weight_fp16 = self.weight_int8.float() * self.scale + self.zero_point

        # FP16矩阵乘法
        output = F.linear(x, weight_fp16)

        return output

优点

  • 显存占用减少(权重占大部分显存)
  • 实现简单
  • 精度损失小

缺点

  • 计算仍是FP16,速度提升有限
  • 需要反量化开销

适用场景

  • 显存受限但算力充足的情况
  • 大模型推理(如LLaMA-70B在单卡上运行)

激活量化(Activation Quantization)

定义:同时量化权重和激活值

流程

1. 权重和激活都是INT8
2. 使用INT8矩阵乘法
3. 输出可能需要反量化(取决于下一层)

代码示例

class FullyQuantizedLinear(nn.Module):
    def __init__(self, weight_int8, weight_scale, weight_zp):
        self.weight_int8 = weight_int8
        self.weight_scale = weight_scale
        self.weight_zp = weight_zp

    def forward(self, x_int8, x_scale, x_zp):
        # x_int8: INT8激活值

        # INT8矩阵乘法(硬件加速)
        output_int32 = torch.matmul(
            x_int8.to(torch.int32),
            self.weight_int8.to(torch.int32).T
        )

        # 计算输出的scale(scale相乘)
        output_scale = x_scale * self.weight_scale
        output_zp = 0  # 简化

        # 可选:反量化到FP16(如果下一层需要)
        output_fp16 = (output_int32 - output_zp).float() * output_scale

        return output_fp16

优点

  • 计算速度快(INT8乘法比FP16快3-4倍)
  • 显存和速度双优化

缺点

  • 精度损失较大
  • 激活值的分布难以预测(动态范围)
  • 需要硬件支持INT8算子

适用场景

  • 算力受限的边缘设备
  • 需要极致速度的场景

对比总结

类型量化对象显存节省速度提升精度损失复杂度
权重量化仅权重高(2-4倍)低(10-20%)
激活量化权重+激活很高(2-4倍)高(2-4倍)

实际选择

  • 大模型推理:主流使用权重量化(平衡精度和显存)
  • 边缘设备:使用激活量化(追求速度)

大模型量化的挑战

挑战1:异常值(Outliers)

问题:Transformer模型中存在极端的异常值

# 某层的激活值分布
大部分值:[-1.0, 1.0]
异常值:[-100, 100](占比<0.1%,但影响巨大)

逐张量量化:
  范围:[-100, 100]
  s = 200 / 255 = 0.784
  大部分值的精度:只能表示 [-1.0, 1.0] 范围内的2-3个离散值
  精度损失巨大!

原因

  • 注意力机制的Softmax后,某些位置的权重特别大
  • LayerNorm后某些通道的值异常

解决方案1:逐通道量化

# 对每个通道独立量化
# 异常值只影响自己的通道,不影响其他通道

解决方案2:混合精度(Mixed Precision)

# 对有异常值的通道使用FP16
# 其他通道使用INT8

解决方案3:异常值提取

# 将异常值单独存储为FP16
# 其他值量化为INT8
# 推理时分别计算再合并

挑战2:动态范围

问题:激活值的范围在不同输入下变化很大

输入1"Hello" → 激活范围 [-5, 5]
输入2"The quick brown fox..." → 激活范围 [-50, 50]

如果用静态scale(校准时确定),会导致精度损失或溢出

解决方案1:动态量化

def dynamic_quantize(x):
    # 每次推理时重新计算scale
    x_max = x.abs().max()
    scale = x_max / 127
    x_quant = torch.round(x / scale).clamp(-128, 127)
    return x_quant, scale

缺点:需要额外计算max操作,增加延迟

解决方案2:分段量化

# 将激活值分成多个范围,每个范围用不同的scale
ranges = [(-10, -1), (-1, 1), (1, 10)]
scales = [0.08, 0.008, 0.08]

挑战3:不同层的敏感性

观察:不同层对量化的敏感度不同

实验(LLaMA-7B量化到INT4):

量化所有层:困惑度 (Perplexity) = 15.2
只量化第1-10层:困惑度 = 7.8(接近FP16的7.5)
只量化最后10层:困惑度 = 50.3(崩溃!)

结论:最后几层对量化非常敏感

解决方案:分层量化策略

# 浅层:INT4(不太敏感)
layers[0:20] → INT4

# 中层:INT8(适中)
layers[20:60] → INT8

# 深层:FP16(敏感)
layers[60:] → FP16

主流量化方案

1. GPTQ(Accurate Post-Training Quantization)

核心思想:基于二阶信息优化量化误差

问题:朴素PTQ直接round,误差大

朴素量化:Wq=Round(W/s)×s\text{朴素量化:} W_q = \text{Round}(W / s) \times s

GPTQ方法:考虑Hessian矩阵(二阶导数),优化重建误差

minWqWXWqX2\min_{W_q} \|W \cdot X - W_q \cdot X\|^2

算法流程

def gptq_quantize(W, X):
    """
    W: 权重矩阵 (out_features, in_features)
    X: 校准数据的激活值
    """
    # 1. 计算Hessian矩阵
    H = X @ X.T / X.shape[0]

    # 2. 逐列量化(贪心算法)
    W_q = torch.zeros_like(W)
    for i in range(W.shape[1]):
        # 量化第i列
        w = W[:, i]
        q = quantize(w)  # 量化

        # 计算量化误差
        delta = (w - q)

        # 用Hessian调整后续列,补偿误差
        W[:, i+1:] -= delta.unsqueeze(1) @ H[i, i+1:].unsqueeze(0) / H[i, i]

        W_q[:, i] = q

    return W_q

特点

  • 精度高(比朴素PTQ好很多)
  • 速度快(只需几小时量化70B模型)
  • 支持INT4/INT3/INT2

缺点

  • 需要校准数据
  • 需要计算和存储Hessian(内存开销大)

实际效果(LLaMA-65B):

量化方法精度WikiText困惑度相对误差
FP1616 bit3.53-
Round PTQ4 bit11.2+218%
GPTQ4 bit3.98+13%

2. AWQ(Activation-aware Weight Quantization)

核心观察:不是所有权重都同等重要

发现:对应大激活值的权重更重要

# 某层的激活值和权重
激活值:[0.1, 0.2, 50.0, 0.3]  # 第3个通道有异常值
权重:  [2.0, 1.5, 0.8, 1.2]

朴素量化:所有权重同等量化
  误差 = 量化误差 × 激活值
  第3个通道的误差 = e × 50.0(巨大!)

AWQ:保护重要权重
  第3个通道用更高精度或跳过量化

算法流程

def awq_quantize(W, X):
    """
    W: 权重矩阵
    X: 激活值(校准数据)
    """
    # 1. 计算每个通道的激活值幅度
    salient_score = torch.abs(X).mean(dim=0)  # 每个输入通道的重要性

    # 2. 对权重进行缩放(放大重要通道)
    s = salient_score ** 0.5  # 缩放因子
    W_scaled = W * s.unsqueeze(0)

    # 3. 量化缩放后的权重
    W_q = quantize(W_scaled)

    # 4. 量化缩放因子(或保持FP16)
    s_q = quantize(s)  # 可选

    # 推理时:W_q / s_q(相当于恢复原始权重的量化版本)
    return W_q, s_q

特点

  • 专门处理异常值问题
  • 保护重要权重
  • 实现简单

实际效果(LLaMA-7B,INT4):

量化方法WikiText困惑度相对FP16
FP165.68-
GPTQ6.23+9.7%
AWQ5.87+3.3%

3. GGUF/GGML(llama.cpp量化)

特点:专为CPU推理优化的量化格式

支持的量化类型

Q4_0: 4-bit整数,逐块量化(32个元素一组)
Q4_1: 4-bit + 1-bit符号
Q5_0: 5-bit整数
Q8_0: 8-bit整数
Q4_K_M: 4-bit混合精度(K-quants)

Q4_K_M(推荐)

# 混合精度策略
attention.wq, attention.wk → Q4_K(4-bit)
attention.wv, attention.wo → Q5_K(5-bit,稍微重要)
ffn.w1, ffn.w2, ffn.w3 → Q4_K(4-bit)
attention_norm, ffn_norm → FP16(LayerNorm保持精度)
output.weight → Q6_K(6-bit,最后一层很重要)

优点

  • CPU推理友好(无需GPU)
  • 多种精度选择
  • 社区生态好(HuggingFace集成)

实际效果(LLaMA-13B):

量化方法模型大小内存占用速度(CPU)困惑度
FP1626 GB28 GB2 tok/s5.09
Q8_013.5 GB15 GB8 tok/s5.11
Q4_K_M7.3 GB9 GB12 tok/s5.23
Q4_07.0 GB8.5 GB14 tok/s5.46

4. SmoothQuant

针对问题:激活值的异常值导致激活量化困难

核心思想:将激活值的难度"转移"到权重

观察

Y = X @ W
激活X有异常值(难量化)
权重W相对平滑(易量化)

想法:能否让X变平滑,W承担更多变化?

方法:插入缩放因子

Y=XW=(Xs1)(sW)=XWY = X \cdot W = (X \cdot s^{-1}) \cdot (s \cdot W) = X' \cdot W'

其中:

  • X=Xs1X' = X \cdot s^{-1}:激活值除以缩放,变平滑
  • W=sWW' = s \cdot W:权重乘以缩放,吸收变化

缩放因子选择

sj=max(Xj)α/max(Wj)1αs_j = \max(|X_j|)^\alpha / \max(|W_j|)^{1-\alpha}

其中 α[0,1]\alpha \in [0, 1] 控制平衡(通常0.5)

代码示例

def smooth_quant(X, W, alpha=0.5):
    # 计算缩放因子
    x_max = torch.abs(X).max(dim=0)[0]  # 每个输入通道的最大值
    w_max = torch.abs(W).max(dim=0)[0]  # 每个输出通道的最大值

    s = (x_max ** alpha) / (w_max ** (1 - alpha))

    # 应用缩放
    X_smooth = X / s.unsqueeze(0)
    W_smooth = W * s.unsqueeze(1)

    # 量化
    X_q = quantize_int8(X_smooth)
    W_q = quantize_int8(W_smooth)

    # 推理
    Y_q = torch.matmul(X_q, W_q.T)

    return Y_q

特点

  • 让激活量化成为可能
  • 无需重新训练
  • 适合需要激活量化的场景

实际效果(OPT-175B,INT8激活+权重):

方法WikiText困惑度
FP1610.86
朴素INT8461.2(崩溃)
SmoothQuant11.08(+2%)

实战:量化LLaMA模型

方案1:使用GPTQ(推荐)

步骤1:安装依赖

pip install auto-gptq transformers accelerate

步骤2:量化模型

from transformers import AutoTokenizer, AutoModelForCausalLM
from auto_gptq import AutoGPTQForCausalLM, BaseQuantizeConfig

# 1. 加载模型
model_id = "meta-llama/Llama-2-7b-hf"
tokenizer = AutoTokenizer.from_pretrained(model_id)

# 2. 定义量化配置
quantize_config = BaseQuantizeConfig(
    bits=4,  # 量化位数
    group_size=128,  # 分组大小
    desc_act=False,  # 是否量化激活
)

# 3. 加载模型并量化
model = AutoGPTQForCausalLM.from_pretrained(
    model_id,
    quantize_config=quantize_config
)

# 4. 准备校准数据
from datasets import load_dataset
calibration_dataset = load_dataset("wikitext", "wikitext-2-raw-v1", split="train")
calibration_dataset = [
    tokenizer(text, return_tensors="pt").input_ids
    for text in calibration_dataset["text"][:1000]
]

# 5. 执行量化
model.quantize(calibration_dataset)

# 6. 保存量化模型
output_dir = "./llama-2-7b-gptq-4bit"
model.save_quantized(output_dir)
tokenizer.save_pretrained(output_dir)

步骤3:加载和推理

from auto_gptq import AutoGPTQForCausalLM
from transformers import AutoTokenizer

# 加载量化模型
model = AutoGPTQForCausalLM.from_quantized(
    "./llama-2-7b-gptq-4bit",
    device="cuda:0"
)
tokenizer = AutoTokenizer.from_pretrained("./llama-2-7b-gptq-4bit")

# 推理
prompt = "The future of AI is"
inputs = tokenizer(prompt, return_tensors="pt").to("cuda:0")
outputs = model.generate(**inputs, max_new_tokens=50)
print(tokenizer.decode(outputs[0]))

方案2:使用bitsandbytes(最简单)

from transformers import AutoModelForCausalLM, AutoTokenizer

# 直接加载INT8模型(无需手动量化)
model = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Llama-2-7b-hf",
    load_in_8bit=True,  # 自动INT8量化
    device_map="auto"   # 自动分配到GPU
)

tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-2-7b-hf")

# 推理(与FP16完全相同)
prompt = "Once upon a time"
inputs = tokenizer(prompt, return_tensors="pt").to("cuda")
outputs = model.generate(**inputs, max_new_tokens=100)
print(tokenizer.decode(outputs[0]))

INT4版本

model = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Llama-2-7b-hf",
    load_in_4bit=True,  # INT4量化
    device_map="auto",
    bnb_4bit_compute_dtype=torch.float16,  # 计算时用FP16
    bnb_4bit_quant_type="nf4"  # NF4量化类型
)

方案3:使用llama.cpp(CPU推理)

步骤1:转换模型

# 克隆llama.cpp
git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp
make

# 转换HuggingFace模型到GGUF
python convert.py /path/to/llama-2-7b-hf \
    --outtype f16 \
    --outfile llama-2-7b-f16.gguf

# 量化到INT4
./quantize llama-2-7b-f16.gguf llama-2-7b-q4_k_m.gguf Q4_K_M

步骤2:推理

./main -m llama-2-7b-q4_k_m.gguf \
    -p "The meaning of life is" \
    -n 128 \
    -t 8  # 使用8个CPU线程

Python接口

from llama_cpp import Llama

# 加载量化模型
llm = Llama(
    model_path="./llama-2-7b-q4_k_m.gguf",
    n_ctx=2048,  # 上下文长度
    n_threads=8  # CPU线程数
)

# 生成
output = llm(
    "Q: What is the capital of France? A:",
    max_tokens=50,
    temperature=0.7
)
print(output["choices"][0]["text"])

量化的性能对比

LLaMA-7B性能测试

量化方法模型大小显存占用WikiText困惑度推理速度相对FP16
FP1613 GB14 GB5.68100%-
INT8 (bitsandbytes)7 GB9 GB5.72120%+0.7%
INT4 (GPTQ)3.5 GB5 GB5.87150%+3.3%
Q4_K_M (llama.cpp)4.1 GB5.5 GB5.91140%+4.0%
INT4 (AWQ)3.5 GB5 GB5.79160%+1.9%

LLaMA-70B性能测试

量化方法模型大小显存占用能否单卡(A100 80GB)困惑度增加
FP16140 GB160 GB❌ 需要2卡-
INT870 GB85 GB❌ 勉强1卡+1.5%
INT4 (GPTQ)35 GB45 GB✅ 轻松1卡+5.2%
INT4 (AWQ)35 GB43 GB✅ 轻松1卡+3.8%

不同任务的精度影响

任务类型FP16INT8INT4影响程度
多项选择(MMLU)46.746.545.2
代码生成(HumanEval)32.932.129.3中等
数学推理(GSM8K)52.351.147.8较大
对话质量优秀优秀良好

结论

  • INT8几乎无损
  • INT4在大多数任务上可接受
  • 数学推理对量化最敏感

量化的最佳实践

1. 选择合适的量化方法

决策树

需要CPU推理?
├─ 是 → llama.cpp (Q4_K_M)
└─ 否 → 需要激活量化?
    ├─ 是 → SmoothQuant (INT8)
    └─ 否 → 需要极致压缩?
        ├─ 是 → GPTQ/AWQ (INT4)
        └─ 否 → bitsandbytes (INT8)

2. 校准数据的选择

原则

  • 使用与实际应用相似的数据
  • 数量:128-1000个样本即可
  • 覆盖不同长度和风格

推荐数据集

  • 通用:WikiText-2
  • 对话:ShareGPT
  • 代码:The Stack
  • 多语言:mC4

3. 验证量化效果

评估指标

# 1. 困惑度(Perplexity)
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch

def compute_perplexity(model, tokenizer, text):
    encodings = tokenizer(text, return_tensors="pt")
    with torch.no_grad():
        outputs = model(**encodings, labels=encodings.input_ids)
    return torch.exp(outputs.loss).item()

# 2. 下游任务评估
from lm_eval import evaluator
results = evaluator.simple_evaluate(
    model=model,
    tasks=["mmlu", "hellaswag", "arc_easy"],
    num_fewshot=5
)

# 3. 人工评估
# 生成样本,人工打分

4. 混合精度策略

分层量化

# 示例:LLaMA-70B混合精度
quantization_config = {
    "layers.0-10": "INT4",    # 浅层:激进量化
    "layers.11-60": "INT8",   # 中层:平衡
    "layers.61-79": "FP16",   # 深层:保持精度
    "lm_head": "FP16",        # 输出层:不量化
    "layernorm": "FP16"       # 归一化层:不量化
}

5. 量化后微调(QAT-Lite)

对于重要应用,可以在量化后做轻量级微调:

# 1. 加载量化模型
model = load_quantized_model("llama-7b-int4")

# 2. 冻结大部分层,只训练adapter
for name, param in model.named_parameters():
    if "adapter" not in name:
        param.requires_grad = False

# 3. 在目标任务上微调
trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=train_dataset
)
trainer.train()

量化的未来趋势

1. 更低精度(INT2, INT1)

研究方向

  • BitNet(1-bit权重)
  • Ternary Quantization(三值量化:-1, 0, 1)

挑战

  • 精度损失大
  • 需要特殊训练方法

2. 自适应量化

动态精度调整

# 根据输入难度动态选择精度
def adaptive_quantization(input_text):
    difficulty = estimate_difficulty(input_text)

    if difficulty > 0.8:
        return "FP16"  # 困难任务,高精度
    elif difficulty > 0.5:
        return "INT8"  # 中等任务
    else:
        return "INT4"  # 简单任务,低精度

3. 硬件协同设计

专用量化加速器

  • Google TPU:INT8原生支持
  • NVIDIA Tensor Core:INT4/INT8加速
  • 未来:INT2/INT1硬件支持

4. 训练时量化(PTQ的替代)

直接训练量化模型

# 从头开始训练INT4模型(而不是先FP16再量化)
model = QuantizedTransformer(
    bits=4,
    from_scratch=True
)
# 训练

优点

  • 无精度损失
  • 训练和推理统一

挑战

  • 训练困难(梯度消失)
  • 需要新的优化器

小结

量化技术对比

技术压缩比精度速度适用场景
权重量化(INT8)2x+20%显存受限
权重量化(INT4)4x中等+50%极致压缩
激活量化(INT8)2x中等+3x边缘设备
混合精度2-3x+2x平衡方案

量化方法选择

需求推荐方法原因
快速部署bitsandbytes零配置,自动
最佳精度AWQ专门优化异常值
极致压缩GPTQ支持INT4/INT3
CPU推理llama.cpp高度优化
激活量化SmoothQuant解决异常值问题

关键要点

  1. INT8几乎无损:适合所有场景,应作为默认选择
  2. INT4需谨慎:虽然压缩比高,但某些任务(如数学推理)精度损失明显
  3. 激活量化看硬件:CPU场景必备,GPU场景可选
  4. 分层量化很有效:浅层激进,深层保守
  5. 校准数据很重要:选择与应用相似的数据

实际建议

开发阶段

  • 使用FP16,保证精度
  • 快速迭代

部署阶段

  • 默认INT8(bitsandbytes)
  • 显存紧张用INT4(GPTQ/AWQ)
  • CPU推理用llama.cpp

关键应用

  • 先PTQ测试
  • 精度不够再考虑QAT
  • 或使用混合精度

量化是大模型部署的必备技术,掌握量化可以:

  • 降低70%以上的显存占用
  • 提升50%以上的推理速度
  • 让大模型在消费级硬件上运行

随着硬件和算法的发展,量化技术会越来越成熟,未来INT4甚至INT2可能成为主流!