跳到主要内容

量化详解

量化(Quantization)是大模型推理优化的核心技术之一。通过降低模型权重和计算的精度,量化可以显著减少显存占用并加速推理,使得大模型能够在更多硬件上运行。本章详细介绍 vLLM 支持的各种量化方法及其使用技巧。

什么是量化

量化的基本思想是将高精度的浮点数(如 FP16、BF16)转换为低精度的表示(如 INT8、INT4)。这带来了两个主要好处:

显存节省:精度降低意味着每个参数占用更少的存储空间。例如,从 FP16(16 bit)量化到 INT4(4 bit),显存占用减少 4 倍。

计算加速:低精度计算在现代 GPU 上有专门的硬件加速,可以显著提升推理速度。

当然,量化也是有代价的——精度损失。好的量化方法需要在显存节省、计算加速和精度保持之间取得平衡。

量化类型概览

vLLM 支持多种量化方法,各有特点:

方法权重精度激活精度显存节省速度提升精度损失适用场景
FP16/BF1616-bit16-bit基准基准通用,精度优先
INT8 W8A88-bit8-bit~50%1.5-2x较小通用加速
FP8 W8A88-bit8-bit~50%1.5-2x较小Ada/Hopper GPU
AWQ4-bit16-bit~65%2-3x中等显存受限场景
GPTQ4-bit16-bit~65%2-3x中等显存受限场景
GGUF可变可变可变可变可变CPU 推理

理解 WxAy 表示法

  • W 代表权重(Weight)
  • A 代表激活(Activation)
  • W4A16 表示权重量化为 4 bit,激活保持 16 bit
  • W8A8 表示权重和激活都量化为 8 bit

AWQ 量化

AWQ(Activation-Aware Weight Quantization)是一种先进的权重量化方法,它考虑激活值的分布来优化量化过程。

AWQ 的原理

传统量化方法对所有权重一视同仁,但这忽略了一个重要事实:不同的权重对模型输出的影响不同。AWQ 通过分析激活值分布,识别出对输出影响最大的"显著权重",对它们采用更精细的量化策略。

具体而言:

  1. 激活分析:运行少量校准数据,收集各层的激活值统计
  2. 显著性识别:找出激活值较大的通道,这些通道的权重更重要
  3. 混合精度量化:显著通道保持较高精度,其他通道可以更激进量化

使用 AWQ 量化模型

方式一:使用已量化的模型

Hugging Face 上有许多预先用 AWQ 量化的模型:

from vllm import LLM

# 直接加载 AWQ 量化模型
llm = LLM(
model="TheBloke/Llama-2-7B-AWQ",
quantization="awq"
)

# 或者模型名称中包含 awq,vLLM 会自动识别
llm = LLM(model="TheBloke/Llama-2-7B-AWQ")

方式二:自行量化模型

如果需要量化自定义模型,可以使用 AutoAWQ 库:

pip install autoawq
from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer

# 加载原始模型
model_path = "meta-llama/Llama-2-7b-hf"
tokenizer = AutoTokenizer.from_pretrained(model_path)
model = AutoAWQForCausalLM.from_pretrained(model_path)

# 配置量化参数
quant_config = {
"zero_point": True,
"q_group_size": 128, # 量化分组大小
"w_bit": 4, # 权重位数
"version": "gemm" # 内核版本
}

# 准备校准数据
# 校准数据用于分析激活值分布,影响量化质量
calib_data = [
"The capital of France is Paris.",
"Machine learning is a subset of artificial intelligence.",
"Python is a popular programming language.",
]

# 执行量化
model.quantize(
tokenizer,
quant_config=quant_config,
calib_data=calib_data
)

# 保存量化后的模型
model.save_quantized("./llama-2-7b-awq")
tokenizer.save_pretrained("./llama-2-7b-awq")

AWQ 配置参数详解

参数说明推荐值
q_group_size量化分组大小,分组内共享缩放因子128
w_bit权重量化位数4
zero_point是否使用零点量化True
version内核版本(gemm/gemv/marlin)gemm

分组量化(Group Quantization):将权重分成多个组,每组独立量化。分组越小,精度越高,但计算开销越大。128 是精度和性能的良好平衡点。

GPTQ 量化

GPTQ 是另一种流行的 4-bit 权重量化方法,基于最优脑量化(Optimal Brain Quantization)理论。

GPTQ 的原理

GPTQ 采用逐层量化策略,通过求解优化问题来最小化量化误差。其核心思想是:

  1. 逐层处理:从第一层开始,依次量化每一层
  2. 误差补偿:量化当前层时,考虑量化误差对后续层的影响,并调整未量化的权重来补偿
  3. Hessian 矩阵:使用 Hessian 矩阵的逆来指导权重调整

相比 AWQ,GPTQ 的量化过程更慢(因为需要逐层处理),但理论上更精确。

使用 GPTQ 量化模型

from vllm import LLM

# 加载 GPTQ 量化模型
llm = LLM(
model="TheBloke/Llama-2-7B-GPTQ",
quantization="gptq"
)

自行量化模型

pip install auto-gptq
from auto_gptq import AutoGPTQForCausalLM, BaseQuantizeConfig
from transformers import AutoTokenizer

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

# 配置量化参数
quantize_config = BaseQuantizeConfig(
bits=4, # 量化位数
group_size=128, # 分组大小
desc_act=True # 是否使用激活值排序
)

model = AutoGPTQForCausalLM.from_pretrained(
model_path,
quantize_config
)

# 准备校准数据
from datasets import load_dataset
dataset = load_dataset("c4", split="train[:1000]")
calib_data = [doc["text"] for doc in dataset]

# 执行量化
model.quantize(calib_data)

# 保存
model.save_quantized("./llama-2-7b-gptq")
tokenizer.save_pretrained("./llama-2-7b-gptq")

GPTQ vs AWQ 对比

特性GPTQAWQ
量化速度较慢(逐层处理)较快(并行处理)
推理精度略高(理论最优)略低(但实际差距小)
推理速度相当相当
显存占用相当相当
校准数据需求需要较多需要较少

选择建议

  • 如果需要量化速度优先,选择 AWQ
  • 如果需要精度优先,选择 GPTQ
  • 实际使用中,两者差距很小,可根据可用工具选择

FP8 量化

FP8(8-bit Floating Point)是一种新兴的量化格式,特别适合 NVIDIA Ada 和 Hopper 架构的 GPU。

FP8 的优势

与 INT8 不同,FP8 使用浮点表示,具有以下优势:

更大的动态范围:浮点表示可以覆盖更大范围的数值,减少溢出风险

更简单的量化流程:不需要复杂的校准过程,RTN(Round-to-Nearest)量化即可

硬件加速:Ada 和 Hopper GPU 有专门的 FP8 Tensor Core

使用 FP8 量化

动态量化(推荐)

from vllm import LLM

# FP8 动态量化,无需预先量化
llm = LLM(
model="meta-llama/Llama-2-7b-hf",
quantization="fp8"
)

FP8 动态量化在模型加载时自动进行,无需额外的校准步骤。

使用预量化模型

llm = LLM(
model="neuralmagic/Llama-2-7b-hf-FP8",
quantization="fp8"
)

FP8 的硬件要求

GPU 架构FP8 支持代表 GPU
Volta (SM 7.0)V100
Turing (SM 7.5)RTX 2080, T4
Ampere (SM 8.0/8.6)A100, RTX 3090
Ada (SM 8.9)RTX 4090, L40
Hopper (SM 9.0)H100, H200

如果在不支持的 GPU 上尝试 FP8,会回退到其他精度。

INT8 量化

INT8(8-bit Integer)量化是一种通用的加速方法,兼容广泛的硬件。

使用 INT8 量化

方式一:使用 bitsandbytes

from vllm import LLM

llm = LLM(
model="meta-llama/Llama-2-7b-hf",
load_format="bitsandbytes"
)

bitsandbytes 支持 CPU 和 GPU 卸载,适合在资源受限的环境中运行大模型。

方式二:使用 INT8 W8A8

llm = LLM(
model="meta-llama/Llama-2-7b-hf",
quantization="int8"
)

W8A8 将权重和激活都量化为 INT8,可以获得更大的加速,但精度损失也更大。

INT8 的适用场景

INT8 量化特别适合以下场景:

  • 需要在较旧的 GPU 上运行大模型
  • 对推理速度有严格要求
  • 模型对量化不敏感(如某些分类模型)

BitsAndBytes

BitsAndBytes 是一个灵活的量化库,支持多种量化格式和 CPU 卸载。

8-bit 量化

from vllm import LLM

llm = LLM(
model="meta-llama/Llama-2-7b-hf",
load_format="bitsandbytes",
load_8bit=True
)

4-bit 量化

llm = LLM(
model="meta-llama/Llama-2-7b-hf",
load_format="bitsandbytes",
load_4bit=True
)

NF4(NormalFloat 4-bit)

NF4 是一种针对正态分布权重优化的 4-bit 格式,通常比标准 INT4 精度更高:

llm = LLM(
model="meta-llama/Llama-2-7b-hf",
load_format="bitsandbytes",
load_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype="float16",
bnb_4bit_use_double_quant=True # 双重量化,进一步减少显存
)

GGUF 格式

GGUF(GGML Universal Format)是 llama.cpp 项目开发的模型格式,特别适合 CPU 推理。

使用 GGUF 模型

from vllm import LLM

llm = LLM(
model="TheBloke/Llama-2-7B-GGUF",
quantization="gguf"
)

GGUF 的优势

跨平台兼容:支持 CPU、GPU、Apple Silicon 等多种平台

灵活的量化等级:提供从 Q4_0 到 Q8_0 多种量化等级

内存映射加载:可以部分加载模型,减少启动时间

适合边缘设备:在资源受限的环境中表现优异

KV Cache 量化

除了权重量化,vLLM 还支持 KV Cache 量化,进一步减少推理时的显存占用。

启用 KV Cache 量化

from vllm import LLM

llm = LLM(
model="meta-llama/Llama-2-7b-hf",
cache_dtype="fp8" # 将 KV Cache 量化为 FP8
)

KV Cache 量化的收益

KV Cache 是 Transformer 推理中显存占用的大头。对于长序列推理,KV Cache 的显存占用可能超过模型权重本身。

启用 KV Cache 量化可以:

  • 减少 50% 的 KV Cache 显存占用
  • 支持更长的上下文长度
  • 提高并发能力

适用场景

KV Cache 量化特别适合:

  • 长文本推理(如文档分析、代码生成)
  • 高并发服务场景
  • 显存受限的环境

硬件兼容性

不同量化方法在不同硬件上的支持情况:

方法VoltaTuringAmpereAdaHopperAMD GPU
AWQ
GPTQ
Marlin✅*
INT8
FP8
bitsandbytes
GGUF

* Turing 不支持 Marlin MXFP4

GPU 架构说明

  • Volta:V100,计算能力 7.0
  • Turing:T4、RTX 2080 等,计算能力 7.5
  • Ampere:A100、RTX 3090 等,计算能力 8.0/8.6
  • Ada:L40、RTX 4090 等,计算能力 8.9
  • Hopper:H100、H200 等,计算能力 9.0

最佳实践

选择量化方法

根据场景选择合适的量化方法:

显存最优先

  • 消费级 GPU(24GB 以下):AWQ 或 GPTQ(4-bit)
  • 专业 GPU(24GB-80GB):AWQ 或 GPTQ 或 INT8
  • Hopper GPU:FP8

精度最优先

  • 使用 INT8 或 FP8,避免 4-bit 量化
  • 对于关键应用,进行量化后的精度测试

速度最优先

  • 使用 FP8(Ada/Hopper GPU)
  • 或使用 AWQ/GPTQ 并启用 Marlin 内核

量化后的验证

量化后务必验证模型质量:

from vllm import LLM, SamplingParams
import json

# 加载量化模型
llm = LLM(model="your-quantized-model")

# 准备测试用例
test_cases = [
{"input": "What is the capital of France?", "expected": "Paris"},
{"input": "2 + 2 = ?", "expected": "4"},
{"input": "Write a hello world program in Python", "expected": "print"},
]

sampling_params = SamplingParams(temperature=0.0, max_tokens=50)

# 测试
for case in test_cases:
output = llm.generate([case["input"]], sampling_params)
result = output[0].outputs[0].text
print(f"输入: {case['input']}")
print(f"输出: {result}")
print(f"期望包含: {case['expected']}")
print(f"通过: {case['expected'].lower() in result.lower()}")
print()

混合精度策略

对于要求高精度的应用,可以采用混合精度:

from vllm import LLM

# 主模型使用 INT8
llm = LLM(
model="meta-llama/Llama-2-7b-hf",
quantization="int8",
dtype="float16" # 激活使用 FP16
)

量化与并行的配合

量化可以与分布式并行结合使用:

# 量化 + 张量并行
llm = LLM(
model="meta-llama/Llama-2-70b-hf",
quantization="awq",
tensor_parallel_size=4
)

先量化再并行,可以大幅降低每个 GPU 的显存需求,让更大的模型能够在有限硬件上运行。

常见问题

量化后输出质量下降明显

可能原因

  • 校准数据不足或不具代表性
  • 量化方法不适合该模型
  • 量化等级过低(如 4-bit 对某些任务太激进)

解决方案

  • 增加校准数据的数量和多样性
  • 尝试不同的量化方法(AWQ → GPTQ)
  • 提高量化精度(4-bit → 8-bit)

量化模型加载失败

可能原因

  • 量化格式与 vLLM 版本不兼容
  • 硬件不支持该量化方法

解决方案

# 更新 vLLM
pip install -U vllm

# 检查硬件兼容性
python -c "import torch; print(torch.cuda.get_device_capability())"

推理速度没有提升

可能原因

  • 硬件没有对应的优化内核
  • 批处理大小过小,无法充分发挥低精度计算的优势

解决方案

  • 检查是否使用了 Marlin 等优化内核
  • 增加并发请求数或批处理大小

小结

量化是大模型推理优化的关键技术,vLLM 提供了丰富的量化支持:

  1. AWQ:快速量化,适合大多数场景
  2. GPTQ:精度较高,量化速度较慢
  3. FP8:适合 Ada/Hopper GPU,无需校准
  4. INT8:广泛兼容,适合较旧硬件
  5. KV Cache 量化:减少推理时显存占用

选择合适的量化方法需要综合考虑硬件条件、精度要求和性能目标。在实际应用中,建议进行充分的测试验证,确保量化后的模型满足业务需求。

参考资料