推理优化(Inference Optimization)¶
LLM 的推理(Inference)成本极高——每次生成都需要遍历数十亿参数,逐个 Token 产出。推理优化的目标是在不显著降低输出质量的前提下,让模型跑得更快、用更少的显存。
LLM 推理的基本流程¶
LLM 生成文本的过程分为两个阶段:
Prefill 阶段(预填充)¶
一次性处理用户输入的所有 Token(Prompt),并行计算:
这个阶段是计算密集型(Compute-Bound),GPU 算力是瓶颈。
Decode 阶段(逐步解码)¶
一个 Token 一个 Token 地生成输出,串行计算:
这个阶段是内存带宽密集型(Memory-Bound),GPU 数据搬运速度是瓶颈。
为什么 Decode 阶段慢?
每生成一个 Token,都需要将模型的全部参数从显存(HBM)加载到计算单元。对于 70B 模型(FP16),这意味着每生成一个 Token 就要搬运 140GB 数据。而 A100 的内存带宽是 2TB/s,光搬运就需要 70ms——这还没算计算本身的时间。
KV Cache ⭐ 最基础也是最重要的优化¶
问题:重复计算¶
在自回归生成中,每一步都需要对之前所有 Token 做注意力计算。如果不做任何优化:
- 生成第 1 个 Token:计算 Prompt 的 KV
- 生成第 2 个 Token:重新计算 Prompt + 第 1 个 Token 的 KV
- 生成第 3 个 Token:重新计算 Prompt + 前 2 个 Token 的 KV
- ...
计算量随序列长度二次方增长,大量计算被重复执行。
解决方案:缓存 K 和 V¶
KV Cache 的核心思想极其简单——把已经计算过的 K 和 V 向量缓存起来,之后直接复用。
步骤 1:处理 Prompt,计算 K₁V₁, K₂V₂, K₃V₃ → 全部缓存
步骤 2:只计算新 Token 的 Q₄, K₄, V₄ → K₄V₄ 追加到缓存
注意力计算:Q₄ × [K₁, K₂, K₃, K₄]ᵀ → 加权求和 → 输出
步骤 3:只计算新 Token 的 Q₅, K₅, V₅ → K₅V₅ 追加到缓存
注意力计算:Q₅ × [K₁, K₂, K₃, K₄, K₅]ᵀ → ...
每一步只需计算一个新 Token 的 QKV,而非重新计算全部序列,复杂度从 \(O(n^2)\) 降为 \(O(n)\)。
KV Cache 的显存占用¶
KV Cache 虽然省了计算,但它需要大量显存来存储:
以 LLaMA-2-70B 为例
- 80 层 × 64 头 × 128 维 × 4096 序列长度 × FP16(2字节)
- 单条请求的 KV Cache ≈ 5.2 GB
- 如果同时处理 10 条请求 → 52 GB,比模型本身还大!
KV Cache 的显存优化是推理优化的核心战场。
KV Cache 优化技术¶
Multi-Query Attention (MQA)¶
原始的多头注意力中,每个注意力头都有独立的 K、V 矩阵。MQA 让所有注意力头共享同一组 K 和 V,只有 Q 是独立的。
- KV Cache 内存降低:降至 \(\frac{1}{n_{\text{heads}}}\)
- 推理速度提升:显著减少内存搬运
- 质量略有下降:K、V 的表达能力减弱
Grouped-Query Attention (GQA) ⭐¶
GQA 是 MHA 和 MQA 的折中方案——将注意力头分成若干组,组内共享 KV,组间独立。
MHA(32 头): 32 组独立的 KV → KV Cache × 32
GQA(32 头, 8 组):8 组独立的 KV → KV Cache × 8
MQA(32 头): 1 组共享的 KV → KV Cache × 1
GQA 在几乎不损失质量的情况下将 KV Cache 减少到 \(\frac{1}{4}\),被 LLaMA 2/3、Qwen 2 等主流模型广泛采用。
PagedAttention(vLLM)¶
受操作系统虚拟内存管理的启发,PagedAttention 解决了 KV Cache 的内存碎片化问题:
传统方式为每个请求预分配一大块连续内存,导致大量浪费(因为不知道会生成多少 Token)。PagedAttention 将 KV Cache 切分为固定大小的"页"(Page),按需动态分配:
vLLM 框架基于 PagedAttention 实现,是目前最流行的 LLM 推理引擎之一。
模型压缩¶
量化(Quantization) ⭐¶
量化是将模型权重从高精度浮点数压缩到低精度整数的技术,直接减少模型体积和显存占用。
| 精度 | 每个参数占用 | 70B 模型大小 | 说明 |
|---|---|---|---|
| FP32 | 4 字节 | 280 GB | 训练精度 |
| FP16 / BF16 | 2 字节 | 140 GB | 标准推理精度 |
| INT8 | 1 字节 | 70 GB | 质量基本无损 |
| INT4 | 0.5 字节 | 35 GB | 质量略降,但极其实用 |
训练后量化(Post-Training Quantization, PTQ):
不需要重新训练,直接对已训练好的模型进行量化:
- GPTQ:基于近似二阶信息的逐层量化,INT4 质量优秀
- AWQ(Activation-aware Weight Quantization):根据激活值的分布来决定量化策略,保护重要权重
- GGUF(llama.cpp):专为 CPU 推理优化的量化格式,适合在个人电脑上运行 LLM
实用建议
- INT8 量化几乎不损失质量,是最安全的选择
- INT4 量化(GPTQ/AWQ)让 70B 模型能跑在单卡 A100(80GB)上
- GGUF 量化让 7B~13B 模型能跑在消费级显卡甚至纯 CPU 上
知识蒸馏(Knowledge Distillation)¶
用一个大模型(教师模型)来训练一个小模型(学生模型),让小模型"学到"大模型的能力:
- 学生模型不仅学"正确答案",还学教师模型的输出概率分布(软标签),携带了更丰富的信息
- 典型案例:Alpaca(Stanford 用 GPT-3.5 的输出训练 LLaMA-7B)
剪枝(Pruning)¶
移除模型中对最终输出贡献最小的参数(将其置为 0):
- 非结构化剪枝:逐个移除不重要的权重,压缩率高但硬件不友好
- 结构化剪枝:移除整个注意力头或 FFN 神经元,硬件友好
解码策略优化¶
推测解码(Speculative Decoding) ⭐¶
核心思想:用一个小模型快速"猜"多个 Token,再让大模型一次性验证。
传统解码(大模型逐个生成):
Token1 → Token2 → Token3 → Token4 → Token5 (5 步)
推测解码:
小模型快速猜:Token1, Token2, Token3, Token4, Token5 (1 步,很快)
大模型一次验证:Token1 ✓, Token2 ✓, Token3 ✓, Token4 ✗ (1 步)
从 Token4 重新开始 → ...
总步数:2 步(而非 5 步)
为什么有效? LLM 生成的大部分 Token 其实很"简单"(如"的"、"是"、"。"),小模型就能猜对。只有少数需要深度思考的 Token 才需要大模型出马。
推测解码在不损失任何质量的前提下,可将推理速度提升 2~3 倍。
采样策略¶
LLM 的最后一步是从概率分布中选择下一个 Token,不同策略影响输出质量:
| 策略 | 说明 | 适用场景 |
|---|---|---|
| Greedy | 每次选概率最大的 Token | 确定性任务(代码、事实问答) |
| Temperature Sampling | 温度越高,分布越平(随机性越大) | 创意任务用高温,精确任务用低温 |
| Top-K | 只从概率最高的 K 个 Token 中采样 | 控制候选范围 |
| Top-P (Nucleus) | 从累计概率达到 P 的最小 Token 集中采样 | 比 Top-K 更灵活 |
Temperature 的影响:
- \(T \to 0\):退化为贪心搜索,确定性最强
- \(T = 1\):原始概率分布
- \(T > 1\):分布更平坦,随机性增大
推理框架¶
| 框架 | 核心特性 | 适用场景 |
|---|---|---|
| vLLM | PagedAttention、连续批处理 | 高吞吐量在线服务 |
| TGI | HuggingFace 出品,部署简单 | 快速部署 |
| TensorRT-LLM | NVIDIA 极致优化,FP8 支持 | 追求极限性能 |
| llama.cpp | 纯 C++ 实现,支持 CPU 推理 | 个人电脑/边缘部署 |
| Ollama | 基于 llama.cpp,一键运行 | 本地快速体验 |
| SGLang | 高级 Prompt 编程、RadixAttention | 复杂 LLM 应用 |
批处理优化¶
连续批处理(Continuous Batching)¶
传统批处理中,一个 batch 内最短的请求处理完后需要等待最长的请求,导致 GPU 空闲。
连续批处理(也叫 Iteration-Level Batching)的做法是:一旦某个请求生成完毕,立即用新请求填充空位,保持 GPU 持续满载。
传统批处理:
请求A [████████████████]
请求B [████████] ← B 完成后 GPU 空闲
请求C [████████████]
连续批处理:
请求A [████████████████]
请求B [████████] 请求D [████████] ← B 完成后立即插入 D
请求C [████████████] 请求E [████]
这一优化让 GPU 利用率从约 50% 提升到接近 100%,是 vLLM、TGI 等框架的标配。