第10章 推理优化技术

导入:为什么推理优化如此重要

大模型推理的成本有多高?用一个具体数字说明:2024 年,OpenAI 每天处理约 10 亿次推理请求,按平均每次请求消耗 500 tokens 计算,一天的推理算力成本就在百万美元量级。如果推理效率提升 20%,每天就能省下数十万美元。

但推理优化的挑战在于:你要同时优化两个方向——速度质量。把模型量化到 INT4 可以快 3 倍,但如果输出质量明显下降,那就毫无意义。

本章覆盖从量化到解码策略的六类优化技术。这些技术大多数是正交的——可以叠加使用,效果乘积。


10.1 量化技术

量化是降低模型精度以减少计算量和内存占用的技术。FP16 模型每个参数占 2 字节,量化到 INT8 就是 1 字节(省一半),INT4 就是 0.5 字节(省四分之三)。

graph LR
    A[FP32\n4 bytes/param] --> B[FP16/BF16\n2 bytes/param]
    B --> C[INT8\n1 byte/param]
    C --> D[INT4\n0.5 byte/param]
    
    B --> E[W8A8\n权重+激活都是INT8]
    B --> F[W4A16\n权重INT4, 激活FP16]
    B --> G[W4A8\n权重INT4, 激活INT8]
    
    style A fill:#f99
    style D fill:#9f9

主要的量化方案

方法 精度 特点 质量损失
W8A8 (INT8) 权重+激活 INT8 均衡,工业界主流 <1%
FP8 浮点 8-bit 硬件原生支持(H100+) <0.5%
W4A16 (AWQ/GPTQ) 权重 INT4,激活 FP16 仅量化权重,计算仍用 FP16 1-2%
INT4 全量化 权重+激活 INT4 最激进,质量损失明显 3-5%

AWQ vs GPTQ

这是两种最流行的训练后量化(PTQ)方法,都量化到 INT4,但策略不同:

GPTQ(Generalized Post-Training Quantization)基于二阶信息(Hessian 矩阵)逐层量化权重,用少量校准数据修正量化误差。

AWQ(Activation-aware Weight Quantization)的洞察是:不是所有权重同等重要。与大幅激活值相关的权重对量化更敏感,应该保持高精度。

# 使用 AWQ 量化模型(AutoAWQ 库)
from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer

model_path = "meta-llama/Llama-3-8B-Instruct"
quant_path = "llama-3-8b-awq"

# 加载校准数据
tokenizer = AutoTokenizer.from_pretrained(model_path)
quant_config = {
    "zero_point": True,
    "q_group_size": 128,    # 分组量化,每组 128 个权重
}

# 量化
model = AutoAWQForCausalLM.from_pretrained(model_path)
model.quantize(
    tokenizer,
    quant_config=quant_config,
    # 校准数据集
    calib_data="pile",
)
model.save_quantized(quant_path)
# 在 vLLM 中加载量化模型
from vllm import LLM

llm = LLM(
    model="llama-3-8b-awq",    # AWQ 量化模型
    quantization="awq",
    dtype="float16",            # 计算精度
    tensor_parallel_size=1,
)

FP8:新一代硬件的礼物

FP8 是 H100(Hopper 架构)引入的原生 8-bit 浮点格式。与 INT8 不同,FP8 保留了指数位,动态范围更大,量化几乎无损。

# vLLM 使用 FP8
python -m vllm.entrypoints.openai.api_server \
    --model meta-llama/Llama-3-70B \
    --quantization fp8 \
    --kv-cache-dtype fp8       # KV Cache 也用 FP8
Tip

量化选择决策树: 1. 有 H100/H200?→ 用 FP8,几乎无损 2. A100/A10G,能接受 INT8?→ 用 W8A8 3. 显存极度紧张?→ 用 AWQ (W4A16) 4. 不要用朴素 INT4 全量化,质量下降太多


10.2 剪枝、蒸馏与模型压缩

除了量化,还有其他减小模型体积的方法。

剪枝(Pruning)

剪枝移除模型中不重要的参数。核心假设是:大模型存在大量冗余,移除部分参数不影响表现。

graph LR
    A[原始模型\n100% 参数] --> B{剪枝策略}
    B --> C[非结构化剪枝\n设个别权重为0]
    B --> D[结构化剪枝\n移除整行/整列/整头]
    C --> E[稀疏矩阵\n需要特殊硬件支持]
    D --> F[更小的稠密模型\n直接可用]

现实是:非结构化剪枝虽然在学术界研究很多,但工业界用得少——因为稀疏矩阵在 GPU 上并没有明显加速。结构化剪枝更有用,但实现复杂且容易掉点。

知识蒸馏(Knowledge Distillation)

蒸馏的核心思想:用大模型(teacher)的输出训练小模型(student)。

# 简化的蒸馏训练循环
import torch
import torch.nn.functional as F

def distillation_loss(student_logits, teacher_logits, labels, alpha=0.5, temperature=2.0):
    """
    student_logits: 小模型的 logits
    teacher_logits: 大模型的 logits
    labels: 真实标签
    alpha: 蒸馏损失的权重
    temperature: softmax 温度
    """
    # 软标签损失(KL 散度)
    soft_loss = F.kl_div(
        F.log_softmax(student_logits / temperature, dim=-1),
        F.softmax(teacher_logits / temperature, dim=-1),
        reduction="batchmean",
    ) * (temperature ** 2)
    
    # 硬标签损失
    hard_loss = F.cross_entropy(student_logits, labels)
    
    return alpha * soft_loss + (1 - alpha) * hard_loss
Warning

蒸馏的现实:蒸馏在分类任务(如 BERT 微调)上效果极好。但对于生成式 LLM,蒸馏效果不稳定——小模型很难完全模仿大模型的生成多样性。T distilled 模型通常在知识广度上有明显短板。


10.3 KV Cache 管理与内存优化

KV Cache 是自回归生成的必然产物。生成第 N 个 token 时,需要用前面 N-1 个 token 的 Key 和 Value 矩阵。为了避免重复计算,这些矩阵被缓存在 GPU 内存中。

问题是:KV Cache 随序列长度线性增长。

# Llama-3-70B 的 KV Cache 大小估算
# 每层每 token 的 KV Cache = 2 * n_heads * head_dim * 2 bytes (FP16)
# = 2 * 64 * 128 * 2 = 32,768 bytes ≈ 32 KB
#
# 80 层 * 32 KB = 2.5 MB / token
# 8192 tokens 的请求 = 2.5 * 8192 = 20 GB!

一个 8K 上下文的请求,KV Cache 就要 20GB——这比模型权重本身还大(70B FP16 约 140GB,但分到 4 卡只有 35GB/卡)。

KV Cache 量化

将 KV Cache 从 FP16 量化到 FP8 或 INT8,内存直接减半。

# vLLM 中启用 KV Cache 量化
python -m vllm.entrypoints.openai.api_server \
    --model meta-llama/Llama-3-70B \
    --kv-cache-dtype fp8       # FP8 KV Cache

内存预算管理

# vLLM 的内存管理策略(概念说明)
# vLLM 将可用 GPU 内存分为三块:
# 1. 模型权重(固定)
# 2. KV Cache(动态,可占 ~60-70%)
# 3. 激活值和临时缓冲(~10%)

# gpu_memory_utilization 参数控制总使用比例
# 0.9 表示使用 90% 的 GPU 内存

# 如果 OOM:
# - 降低 gpu_memory_utilization(如 0.85)
# - 降低 max_model_len(如从 8192 降到 4096)
# - 启用 KV Cache 量化
Tip

经验法则:KV Cache 内存占用 ≈ 2 * num_layers * num_heads * head_dim * seq_len * batch_size * dtype_size。在部署前先算一下,确保 GPU 内存够用。很多人只算模型权重大小就买了卡,结果 KV Cache 一上来就 OOM。


10.4 Prefix Caching 与请求复用

很多请求有相同的前缀。比如所有请求都以同一个 system prompt 开头(“你是一个有用的助手……”),或者 RAG 场景下多个请求引用同一份文档。

Prefix Caching 的思想很简单:已经计算过的前缀的 KV Cache 不要丢弃,后续请求直接复用。

graph TB
    subgraph "无 Prefix Caching"
        A1[请求1: system + 用户A] --> B1[计算 system KV]
        A2[请求2: system + 用户B] --> B2[重复计算 system KV]
    end
    
    subgraph "有 Prefix Caching"
        C1[请求1: system + 用户A] --> D1[计算 system KV → 缓存]
        C2[请求2: system + 用户B] --> D2[命中缓存 → 跳过]
        C3[请求3: system + 用户C] --> D3[命中缓存 → 跳过]
    end

# vLLM 启用 Prefix Caching
python -m vllm.entrypoints.openai.api_server \
    --model meta-llama/Llama-3-8B-Instruct \
    --enable-prefix-caching      # 启用前缀缓存

Prefix Caching 对以下场景效果显著:

场景 共享前缀 加速效果
统一 system prompt 数百 tokens TTFT 降低 50-80%
RAG(同一文档多次提问) 数千 tokens TTFT 降低 70-90%
多轮对话(带完整历史) 对话历史 TTFT 降低 40-60%
Few-shot(相同示例集) 数百 tokens TTFT 降低 40-60%
Warning

Prefix Caching 依赖请求之间的前缀一致性。如果你的 system prompt 每次都带时间戳或随机 ID,缓存几乎不会命中。检查你的 prompt 模板,把不变部分放在最前面。


10.5 PagedAttention 与 Continuous Batching

这两项技术是 vLLM 的核心创新,也是现代 LLM 推理框架的标配。

Continuous Batching(连续批处理)

传统批处理的问题:一个 batch 中不同请求的输出长度差异很大。如果请求 A 生成 10 个 token 就结束,请求 B 生成 500 个 token,那 A 必须等 B 完成后才能一起返回。

Continuous Batching 的解决方案:请求可以在任何时候加入或离开 batch

gantt
    title 传统 Static Batching
    dateFormat X
    axisFormat %s
    section Batch
    请求A (10 tokens)   :done, a1, 0, 10
    请求B (500 tokens)  :active, b1, 0, 500
    请求C (30 tokens)   :done, c1, 0, 30
    "GPU 空闲等待"       :crit, idle, 30, 500
    
    title Continuous Batching
    section 动态批次
    请求A (10 tokens)   :done, a2, 0, 10
    请求C (30 tokens)   :done, c2, 0, 30
    请求D (新加入)       :active, d2, 10, 200
    请求E (新加入)       :active, e2, 30, 150
    请求B (500 tokens)  :active, b2, 0, 500

传统方式下,GPU 在请求 A、C 完成后就空闲了,等待请求 B。Continuous Batching 在 A、C 完成后立即填入新请求 D、E,GPU 始终满载。

PagedAttention

解决了 batching 的问题后,新的问题是 KV Cache 的内存碎片。每个请求的序列长度不同,预分配固定大小的 KV Cache 块会导致大量浪费。

PagedAttention 借鉴操作系统的虚拟内存设计: - 将 KV Cache 分成固定大小的”页”(block),每页存储固定数量 token 的 KV - 通过页表映射逻辑序列到物理页 - 不需要的页可以被回收

# PagedAttention 的内存布局(概念)
# 每页存储 16 个 token 的 KV Cache

# 请求 A(30 tokens):需要 2 页
# 页 0: token 0-15   页 1: token 16-29(部分填充)

# 请求 B(100 tokens):需要 7 页
# 页 2-7: 连续分配

# 请求 A 完成,释放页 0 和 1
# 请求 C 到达,复用页 0 和 1
Tip

这两项技术已经内置于 vLLM、TGI、SGLang 等主流框架。你不需要手动实现——但你需要理解它们的工作原理来正确调参。max_num_seqs(最大并发序列数)是最重要的参数,直接影响吞吐和延迟的权衡。


10.6 投机采样与并行解码

前面所有优化都在减少计算量或提高利用率。而投机采样(Speculative Decoding)走了一条完全不同的路:通过多生成几个候选 token 来减少总的前向传播次数。

投机采样原理

graph TB
    subgraph "标准自回归"
        S1[前向传播] --> S2[token 1]
        S2 --> S3[前向传播]
        S3 --> S4[token 2]
        S4 --> S5[前向传播]
        S5 --> S6[token 3]
        S6 --> S7[...]
    end
    
    subgraph "投机采样"
        D1[Draft 模型\n生成 4 个候选] --> D2[Target 模型\n一次前向验证]
        D2 --> D3{接受/拒绝}
        D3 -->|接受 3 个| D4[实际得到 3-4 个 token]
    end

核心思想: 1. 用一个小模型(draft model)快速生成 N 个候选 token 2. 用大模型(target model)一次性做一次前向传播,验证这 N 个 token 3. 接受与分布一致的 token,拒绝的重新采样

如果小模型猜得准,一次前向传播就能得到 N 个 token,加速 N 倍。如果猜不准,也能得到 1 个 token(与大模型自回归一致)。

# vLLM 使用投机采样
from vllm import LLM, SamplingParams

llm = LLM(
    model="meta-llama/Llama-3-70B",         # Target 模型
    speculative_model="meta-llama/Llama-3-1B",  # Draft 模型
    num_speculative_tokens=4,                # 每次猜 4 个
    tensor_parallel_size=4,
)

sampling = SamplingParams(temperature=0.7, max_tokens=256)
outputs = llm.generate(["解释量子计算"], sampling)

Medusa:多头并行解码

Medusa 是投机采样的一种变体。它不用单独的 draft 模型,而是在大模型的最后一个隐藏层上接多个”预测头”,每个头预测不同位置的未来 token。

# vLLM 使用 Medusa
python -m vllm.entrypoints.openai.api_server \
    --model meta-llama/Llama-3-8B \
    --speculative-model "[medusa]" \
    --num-speculative-tokens 5
Tip

投机采样的适用场景:当你的模型有明确的结构化输出模式时(如代码生成、JSON 输出),小模型猜得准,加速效果最好。对于高度创意性的生成(如写诗),效果有限。


小结

推理优化技术可以分为三层:

  1. 减少参数量:量化(INT8/FP8/INT4)、剪枝、蒸馏
  2. 提高内存效率:KV Cache 量化、PagedAttention、Prefix Caching
  3. 改变计算模式:Continuous Batching、投机采样、并行解码

这些技术的典型叠加效果:

优化组合 相对于 FP16 基线
FP16 基线 1x
+ W8A8 量化 1.5-2x
+ Continuous Batching + PagedAttention 3-5x
+ Prefix Caching(有共享前缀时) 5-10x
+ 投机采样(结构化输出) 6-12x

一个经过充分优化的推理服务,吞吐可以达到朴素实现的 5-10 倍。这就是为什么推理优化是 AI Infra 中最有 ROI 的工作。


延伸阅读

  • “AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration” — Lin et al., 2023
  • “GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers” — Frantar et al., 2022
  • “Efficient Memory Management for Large Language Model Serving with PagedAttention” — Kwon et al., 2023(vLLM 论文)
  • “Fast Inference from Transformers via Speculative Decoding” — Leviathan et al., 2023
  • “Medusa: Simple LLM Inference Acceleration Framework with Multiple Decoding Heads” — Cai et al., 2024
  • vLLM 文档 - 量化指南:https://docs.vllm.ai/en/latest/quantization/