AI 推理优化:从模型压缩到推测解码

量化技术(GPTQ/AWQ/GGUF)、推测解码、KV-Cache 优化与 vLLM/TGI 生产部署全解析

引言

大语言模型的推理成本是制约其大规模部署的核心瓶颈。一个 70B 参数的模型在 FP16 下需要约 140GB 显存,远超单卡容量。即使能装下,自回归解码的逐 token 生成方式导致 GPU 利用率极低——推理过程是 memory-bound 而非 compute-bound。

本文从模型压缩、推测解码、KV-Cache 优化和推理框架四个维度,系统梳理推理优化的工程实践。

模型量化技术

量化基础

量化的核心思想是用低精度数值表示模型权重,从而减少显存占用和内存带宽需求。

精度对比:

FP32  ████████████████████████████████  32 bits  (基线)
FP16  ████████████████                  16 bits  (2x 压缩)
INT8  ████████                           8 bits  (4x 压缩)
INT4  ████                               4 bits  (8x 压缩)
INT2  ██                                 2 bits  (16x 压缩, 质量损失大)

主流量化方案对比

方案 类型 精度 校准数据 推理速度 质量保持 适用场景
GPTQ 权重量化(PTQ) 4-bit 需要 128 条 快(GPU) 优秀 GPU 服务器部署
AWQ 权重量化(PTQ) 4-bit 需要少量 快(GPU) 优秀 GPU 部署,质量优先
GGUF 权重量化 2-8 bit 不需要 中(CPU/GPU 混合) 良好 本地推理,边缘设备
SmoothQuant 权重+激活量化 W8A8 需要 极快 优秀 高吞吐服务器
FP8 原生低精度 8-bit 不需要 极快 接近无损 H100/MI300X
QLoRA 量化+微调 4-bit + LoRA 训练数据 可定制 微调场景

GPTQ 量化实践

GPTQ 基于最优脑量化(OBQ)算法,逐层量化权重矩阵,通过海森矩阵逆近似最小化量化误差:

from transformers import AutoModelForCausalLM, AutoTokenizer
from optimum.gptq import GPTQQuantizer
import torch

# Load model in FP16
model_id = "meta-llama/Llama-3.1-70B-Instruct"
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(
    model_id,
    torch_dtype=torch.float16,
    device_map="auto",
)

# Prepare calibration dataset
calibration_texts = load_calibration_data(n_samples=128)
calibration_dataset = [
    tokenizer(text, return_tensors="pt", max_length=2048, truncation=True)
    for text in calibration_texts
]

# Quantize to 4-bit with group size 128
quantizer = GPTQQuantizer(
    bits=4,
    group_size=128,        # Balance between quality and speed
    desc_act=True,         # Descending activation order (better quality)
    damp_percent=0.01,
    sym=False,             # Asymmetric quantization (better quality)
)

quantized_model = quantizer.quantize_model(model, calibration_dataset)

# Save quantized model
quantized_model.save_pretrained("./llama-70b-gptq-4bit")
tokenizer.save_pretrained("./llama-70b-gptq-4bit")

# Result: ~140GB -> ~35GB, fits on 2x A100 40GB

AWQ 量化实践

AWQ(Activation-aware Weight Quantization)的核心洞察:不是所有权重同等重要,保留对激活值影响最大的 1% 显著权重通道可以显著改善量化质量。

from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer

model_path = "meta-llama/Llama-3.1-70B-Instruct"
quant_path = "./llama-70b-awq-4bit"

# Load model
model = AutoAWQForCausalLM.from_pretrained(model_path)
tokenizer = AutoTokenizer.from_pretrained(model_path)

# AWQ quantization config
quant_config = {
    "zero_point": True,        # Asymmetric quantization
    "q_group_size": 128,       # Group size for weight grouping
    "w_bit": 4,                # Target bit width
    "version": "GEMM",        # Use GEMM kernel for inference
}

# Quantize (automatically finds salient channels)
model.quantize(tokenizer, quant_config=quant_config)

# Save in safetensors format
model.save_quantized(quant_path, safetensors=True)
tokenizer.save_pretrained(quant_path)

GGUF 与 llama.cpp

GGUF 是 llama.cpp 生态的标准格式,支持 CPU 推理和 CPU+GPU 混合推理:

# Convert HuggingFace model to GGUF
python convert_hf_to_gguf.py \
  --model meta-llama/Llama-3.1-8B-Instruct \
  --outfile llama-3.1-8b.gguf

# Apply quantization (multiple strategies available)
./llama-quantize llama-3.1-8b.gguf llama-3.1-8b-Q4_K_M.gguf Q4_K_M

# Q4_K_M: 4-bit with medium quality (recommended default)
# Q5_K_M: 5-bit with medium quality (better quality, slightly larger)
# Q8_0:   8-bit (near-lossless, 2x larger than Q4)
# Q2_K:   2-bit (aggressive, noticeable quality loss)
GGUF 量化策略选择:

质量优先:  Q8_0 > Q6_K > Q5_K_M > Q5_K_S
平衡推荐:  Q4_K_M (大多数场景的最佳选择)
空间优先:  Q4_K_S > Q3_K_M > Q3_K_S > Q2_K

推测解码(Speculative Decoding)

核心原理

自回归解码的瓶颈在于:每生成一个 token 都需要完整的前向传播,而 GPU 大部分时间在等待内存读取。推测解码的思路是用一个小模型快速"猜测"多个 token,再用大模型并行验证,从而在单次前向传播中确认多个 token。

传统自回归解码(每步 1 token):
  Step 1: [The]
  Step 2: [The, quick]
  Step 3: [The, quick, brown]
  Step 4: [The, quick, brown, fox]
  总计: 4 次大模型前向传播

推测解码(每步验证 K 个候选):
  Draft:  小模型快速生成 [quick, brown, fox, jumps]
  Verify: 大模型一次并行验证全部 4 个
  Accept: [quick, brown, fox] 通过, [jumps] 被拒绝, 用大模型的 token 替换
  总计: 1 次小模型 + 1 次大模型前向传播 = 3 tokens 净增

vLLM 中使用推测解码

from vllm import LLM, SamplingParams

# Configure speculative decoding
llm = LLM(
    model="meta-llama/Llama-3.1-70B-Instruct",
    speculative_model="meta-llama/Llama-3.1-8B-Instruct",
    num_speculative_tokens=5,       # Draft model generates 5 candidates
    speculative_draft_tensor_parallel_size=1,
    tensor_parallel_size=4,          # Main model on 4 GPUs
    max_model_len=8192,
)

params = SamplingParams(
    temperature=0.0,                 # Greedy decoding for best acceptance rate
    max_tokens=2048,
)

outputs = llm.generate(["Explain quantum computing in simple terms:"], params)
# Typical speedup: 1.5x - 2.5x with well-matched draft model

加速效果影响因素

推测解码的加速比取决于接受率(acceptance rate),即大模型同意小模型猜测的比例:

场景 接受率 加速比 原因
代码生成 80-90% 2.0-2.5x 代码模式可预测性强
翻译 70-85% 1.8-2.2x 语言对应关系较确定
创意写作 50-65% 1.3-1.6x 大模型选择更多样
数学推理 40-55% 1.1-1.4x 推理路径分歧大

KV-Cache 优化

问题根源

Transformer 注意力机制要求缓存所有历史 token 的 Key 和 Value 向量。对于长上下文场景,KV-Cache 可能比模型权重本身更占显存:

Llama-3.1-70B 的 KV-Cache 占用计算:
  层数: 80
  注意力头数: 64 (GQA: 8 KV heads)
  头维度: 128
  序列长度: 128K

  KV-Cache = 2(K+V) x 80层 x 8头 x 128维 x 128K序列 x 2字节(FP16)
           = 2 x 80 x 8 x 128 x 131072 x 2
           ≈ 42 GB   (仅单个请求!)

PagedAttention(vLLM 核心创新)

PagedAttention 借鉴操作系统虚拟内存分页思想,将 KV-Cache 切分为固定大小的"页":

传统连续分配:
  Request A: [████████░░░░░░░░]  (分配 16 slots, 实际用 8, 浪费 50%)
  Request B: [██████████░░░░░░]  (分配 16 slots, 实际用 10, 浪费 37.5%)
  碎片率高,难以容纳更多请求

PagedAttention 分页分配:
  Page Table A: [P1] -> [P2] -> [P3]
  Page Table B: [P4] -> [P5] -> [P6]

  物理页池: [P1][P4][P2][P5][P3][P6][free][free]
  碎片率接近 0,显存利用率 > 95%

KV-Cache 压缩技术

# vLLM configuration for KV-Cache optimization
from vllm import LLM

llm = LLM(
    model="meta-llama/Llama-3.1-70B-Instruct",
    tensor_parallel_size=4,

    # KV-Cache quantization: FP16 -> FP8
    kv_cache_dtype="fp8_e5m2",       # 50% KV-Cache reduction

    # Prefix caching: share KV-Cache across requests with same prefix
    enable_prefix_caching=True,

    # Chunked prefill: prevent long-prompt prefill from blocking decode
    enable_chunked_prefill=True,
    max_num_batched_tokens=8192,

    # GPU memory fraction for KV-Cache
    gpu_memory_utilization=0.92,
)

Multi-Query / Grouped-Query Attention

现代模型架构层面的 KV-Cache 优化:

Multi-Head Attention (MHA):
  Q heads: 64    K heads: 64    V heads: 64    (KV = 100%)

Multi-Query Attention (MQA):
  Q heads: 64    K heads: 1     V heads: 1     (KV = 1.6%)

Grouped-Query Attention (GQA, Llama 3 使用):
  Q heads: 64    K heads: 8     V heads: 8     (KV = 12.5%)
  平衡了质量和效率

推理框架选型

vLLM vs TGI vs SGLang

特性 vLLM TGI SGLang
核心优势 PagedAttention, 高吞吐 HuggingFace 生态集成 结构化输出, RadixAttention
调度策略 Continuous batching Continuous batching RadixAttention + 前缀复用
推测解码 支持 支持 支持
量化支持 GPTQ/AWQ/FP8/GGUF GPTQ/AWQ/EETQ GPTQ/AWQ/FP8
多模态 支持 支持 支持
OpenAI 兼容 完整 完整 完整
适用场景 通用高吞吐服务 HF 模型快速部署 复杂 Agent 工作流

vLLM 生产部署

# Production vLLM server configuration
# Start with: python -m vllm.entrypoints.openai.api_server

from vllm import AsyncLLMEngine, AsyncEngineArgs

engine_args = AsyncEngineArgs(
    model="meta-llama/Llama-3.1-70B-Instruct",
    tensor_parallel_size=4,
    max_model_len=32768,
    gpu_memory_utilization=0.92,

    # Quantization
    quantization="awq",

    # KV-Cache optimization
    kv_cache_dtype="fp8_e5m2",
    enable_prefix_caching=True,
    enable_chunked_prefill=True,

    # Batching
    max_num_seqs=256,
    max_num_batched_tokens=8192,

    # Speculative decoding
    speculative_model="meta-llama/Llama-3.1-8B-Instruct",
    num_speculative_tokens=5,
)

engine = AsyncLLMEngine.from_engine_args(engine_args)
# Docker deployment
docker run --gpus all \
  -v ~/.cache/huggingface:/root/.cache/huggingface \
  -p 8000:8000 \
  vllm/vllm-openai:latest \
  --model meta-llama/Llama-3.1-70B-Instruct \
  --tensor-parallel-size 4 \
  --quantization awq \
  --max-model-len 32768 \
  --enable-prefix-caching \
  --kv-cache-dtype fp8_e5m2

推理优化决策树

开始
  │
  ├─ 目标硬件?
  │   ├─ GPU 服务器 (A100/H100)
  │   │   ├─ 延迟优先 → vLLM + FP8 + 推测解码
  │   │   └─ 吞吐优先 → vLLM + AWQ-4bit + Continuous Batching
  │   │
  │   ├─ 消费级 GPU (RTX 4090)
  │   │   └─ GPTQ/AWQ 4-bit + vLLM (单卡)
  │   │
  │   ├─ CPU 服务器
  │   │   └─ GGUF Q4_K_M + llama.cpp
  │   │
  │   └─ 边缘设备 / 手机
  │       └─ GGUF Q2_K/Q3_K + llama.cpp / MLC-LLM
  │
  └─ 模型规模?
      ├─ < 13B   → 单卡可装, 量化可选
      ├─ 13B-70B → 必须量化或多卡并行
      └─ > 70B   → 多卡 + 量化 + Pipeline Parallel

性能基准参考

以 Llama-3.1-70B-Instruct 在 4x A100-80GB 上为例:

配置 吞吐 (tok/s) 首 token 延迟 显存占用
FP16 原始 ~1200 ~350ms 140GB
AWQ 4-bit ~2800 ~180ms 38GB
AWQ 4-bit + KV FP8 ~3200 ~160ms 36GB
AWQ 4-bit + 推测解码 ~4500 ~200ms 42GB
FP8 (H100) ~3800 ~120ms 72GB

数据为近似参考值,实际性能取决于输入长度、batch size 和具体硬件配置。

总结

推理优化是一个系统工程,需要在模型质量、推理速度、显存占用和部署成本之间寻找平衡点。核心建议:

  1. 量化是性价比最高的优化手段,AWQ 4-bit 是大多数场景的安全选择。
  2. PagedAttention + Continuous Batching 是高吞吐服务的基础,优先选择 vLLM。
  3. 推测解码对代码和结构化输出场景效果最好,创意任务加速有限。
  4. KV-Cache FP8 量化几乎无质量损失,应该默认启用。
  5. 长上下文场景下 KV-Cache 是真正的显存杀手,需要特别关注。

Maurice | maurice_wen@proton.me