跳转至

推理优化(Inference Optimization)

LLM 的推理(Inference)成本极高——每次生成都需要遍历数十亿参数,逐个 Token 产出。推理优化的目标是在不显著降低输出质量的前提下,让模型跑得更快、用更少的显存


LLM 推理的基本流程

LLM 生成文本的过程分为两个阶段:

Prefill 阶段(预填充)

一次性处理用户输入的所有 Token(Prompt),并行计算

输入 "今天天气怎么样" → 模型并行处理 6 个 Token → 生成第一个输出 Token

这个阶段是计算密集型(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 虽然省了计算,但它需要大量显存来存储:

\[\text{KV Cache 显存} = 2 \times n_{\text{layers}} \times n_{\text{heads}} \times d_{\text{head}} \times \text{seq\_len} \times \text{batch\_size} \times \text{bytes}\]

以 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 是独立的。

标准 MHA:Q₁K₁V₁, Q₂K₂V₂, Q₃K₃V₃, ... (每个头独立的 KV)
MQA:     Q₁KV, Q₂KV, Q₃KV, ...           (所有头共享一组 KV)
  • 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),按需动态分配:

传统方式:为每个请求预留 2048 Token 的空间 → 实际只用了 500 → 浪费 75%
PagedAttention:按需分页,用多少分多少 → 内存利用率接近 100%

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)

用一个大模型(教师模型)来训练一个小模型(学生模型),让小模型"学到"大模型的能力:

教师模型(70B)→ 生成训练数据 → 训练学生模型(7B)
  • 学生模型不仅学"正确答案",还学教师模型的输出概率分布(软标签),携带了更丰富的信息
  • 典型案例: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 的影响:

\[P(x_i) = \frac{e^{z_i / T}}{\sum_j e^{z_j / T}}\]
  • \(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 等框架的标配。