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
第10章 推理优化技术
导入:为什么推理优化如此重要
大模型推理的成本有多高?用一个具体数字说明:2024 年,OpenAI 每天处理约 10 亿次推理请求,按平均每次请求消耗 500 tokens 计算,一天的推理算力成本就在百万美元量级。如果推理效率提升 20%,每天就能省下数十万美元。
但推理优化的挑战在于:你要同时优化两个方向——速度和质量。把模型量化到 INT4 可以快 3 倍,但如果输出质量明显下降,那就毫无意义。
本章覆盖从量化到解码策略的六类优化技术。这些技术大多数是正交的——可以叠加使用,效果乘积。
10.1 量化技术
量化是降低模型精度以减少计算量和内存占用的技术。FP16 模型每个参数占 2 字节,量化到 INT8 就是 1 字节(省一半),INT4 就是 0.5 字节(省四分之三)。
主要的量化方案
| 方法 | 精度 | 特点 | 质量损失 |
|---|---|---|---|
| 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量化选择决策树: 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蒸馏的现实:蒸馏在分类任务(如 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 量化经验法则: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% |
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
这两项技术已经内置于 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投机采样的适用场景:当你的模型有明确的结构化输出模式时(如代码生成、JSON 输出),小模型猜得准,加速效果最好。对于高度创意性的生成(如写诗),效果有限。
小结
推理优化技术可以分为三层:
- 减少参数量:量化(INT8/FP8/INT4)、剪枝、蒸馏
- 提高内存效率:KV Cache 量化、PagedAttention、Prefix Caching
- 改变计算模式: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/