第1章:LLM 推理基础
理解 LLM 自回归生成的 Prefill/Decode 两阶段、KV Cache 机制和推理关键性能指标
LLM 训练只是万里长征第一步,真正服务用户的是推理。本章建立推理优化的全部基础概念:Prefill / Decode 两阶段为什么计算特性截然不同、KV Cache 是什么/占多少显存/怎么管理、TTFT/TPOT/Throughput/Goodput 这些指标各自衡量什么——这些是后面所有推理优化(PagedAttention、量化、Speculative、PD 解耦)的共同前提。
📑 目录
- 1. LLM 自回归生成机制
- 2. Prefill vs Decode:计算特性对比
- 3. KV Cache:显存刺客
- 4. KV Cache 显存账本
- 5. 推理性能指标全集
- 6. 推理请求的完整链路
- 7. 采样策略
- 自我检验清单
- 参考资料
1. LLM 自回归生成机制
LLM 生成是严格的串行过程:
Prompt: "今天天气"
↓
Step 1: 模型看 [今天, 天气] → 生成 "真"
Step 2: 模型看 [今天, 天气, 真] → 生成 "好"
Step 3: 模型看 [今天, 天气, 真, 好] → 生成 "<EOS>"
每一步生成 1 个 token,需要把所有历史 token 都”看”一遍。Naive 实现下,每步都要重新算所有历史的 Q/K/V——这就是 KV Cache 要解决的问题。
2. Prefill vs Decode:计算特性对比
整个推理被切成两个阶段:
2.1 Prefill 阶段
一次性处理完整 prompt(几百到几千 token),算出所有位置的 KV Cache,生成第 1 个 token。
Input: prompt 的所有 token (B, S_prompt)
Output: 第一个生成的 token + 完整 KV Cache (B, S_prompt, ...)
计算特性:
- 输入是大矩阵 ,GEMM 矩阵很大
- Tensor Core 利用率高
- Compute Bound
2.2 Decode 阶段
每一步只生成 1 个 token,Q 的序列长度 = 1。
Input: 上一步生成的 1 个 token
+ 之前所有 KV Cache
Output: 下一个 token
+ 把这一步的 KV 追加到 Cache
计算特性:
- 输入退化成小向量 ,GEMM 变成 GEMV
- Tensor Core 利用率低
- 大部分时间在搬 KV Cache(~10 GB 量级)
- Memory Bound
2.3 对比表
| 维度 | Prefill | Decode |
|---|---|---|
| Q 序列长度 | S_prompt (大) | 1 |
| 算力利用率 | 高(>50%) | 低(<10%) |
| 主要瓶颈 | Compute | Memory |
| 单 token 耗时 | ~10-100 ms | ~10-50 ms |
| 优化方向 | FlashAttention / Tensor Core | KV Cache 管理 / Speculative |
🌟 核心矛盾:Prefill 算力跑满,Decode 带宽跑满——这就是 PD 解耦的动机(第 6 章详讲)。
3. KV Cache:显存刺客
3.1 为什么需要 KV Cache
每一步 Decode,Attention 需要历史所有 token 的 K 和 V。如果每步都重新算,复杂度 ,生成 4K 长度要重算 4K × 4K 次——根本不可行。
KV Cache:把历史所有 token 的 K、V 算出来后存起来,每次新 token 只算自己的 K、V 并 append:
Step 1 之后: KV[0:S_prompt] (Prefill 算出)
Step 2: Q_new (1 token) 算 K_new, V_new → KV[S_prompt: S_prompt+1]
Attention(Q_new, KV[0:S_prompt+1])
Step 3: 继续追加...
3.2 KV Cache 的生命周期
分配 → Prefill 填充 → Decode 不断追加 → 请求结束 → 释放
关键挑战:不同请求的生成长度不同,显存分配是动态的、不可预测——这是 PagedAttention 要解决的核心问题。
3.3 KV Cache 的计算图
每个 layer 有自己的 KV:
KV Cache 形状: (num_layers, 2, batch, num_heads, seq_len, head_dim)
其中 2 表示 K 和 V
4. KV Cache 显存账本
4.1 公式
(MHA 时 ;GQA 时 )
4.2 LLaMA-2-7B 实例
| 超参 | 值 |
|---|---|
| L (num layers) | 32 |
| H_kv (KV heads, MHA) | 32 |
| D (head dim) | 128 |
| S (seq len) | 4096 |
| bytes (BF16) | 2 |
单个请求 KV Cache =
如果 batch=16,总 KV Cache = 32 GB —— 比 7B 模型本身(14 GB)还大!
4.3 长上下文场景
LLaMA-3 支持 128K 上下文:
(LLaMA-3 用 GQA,)
单请求就吃 17 GB! 这就是为什么 KV 量化(KIVI、INT8)对长上下文场景至关重要。
4.4 KV Cache 占比公式
显存预算 80 GB 的卡上:
模型权重(7B BF16): 14 GB
临时 buffer / activation: ~5 GB
剩余给 KV Cache: ~60 GB
每请求 KV(4K, MHA): 2 GB
最大并发: 60 / 2 = 30 个请求
KV Cache 直接决定推理服务的并发能力。
5. 推理性能指标全集
| 指标 | 含义 | 用户感知 |
|---|---|---|
| TTFT (Time To First Token) | 首 token 延迟 | ”等了多久才开始有反应” |
| TPOT (Time Per Output Token) | 每 token 延迟 | ”字一个一个蹦的速度” |
| TBT (Time Between Tokens) | 同 TPOT | 同上 |
| Throughput | 总吞吐(token/s) | 服务总产出 |
| Latency | 端到端延迟 | TTFT + TPOT × output_len |
| QPS | 每秒请求数 | 服务并发能力 |
| P50 / P95 / P99 | 尾延迟分布 | 慢请求严重程度 |
| Goodput | 满足 SLO 的有效吞吐 | 真正可用的服务质量 |
5.1 一个典型场景
聊天应用:
- TTFT 目标 < 500 ms(用户可接受)
- TPOT 目标 < 50 ms(20 token/s,接近阅读速度)
如果 P95 TPOT 是 200 ms,即使平均 TPOT 50 ms 用户体验也很差——5% 请求会卡顿。
5.2 Goodput vs QPS
QPS: 每秒处理请求数
Goodput: 每秒"满足 SLO"的请求数
服务过载时 QPS 还在涨,但 latency 飙升,实际用户体验崩了——Goodput 才是真实的产出,这就是 PD 解耦优化的目标。
6. 推理请求的完整链路
客户端发起请求
↓
HTTP / gRPC 接收
↓
Tokenizer:文本 → token ids (~1ms,CPU)
↓
Scheduler 排队 / 批合并(Continuous Batching)
↓
Prefill 阶段:模型一次性算完 prompt (10-200ms,Compute Bound)
↓
返回 first token (TTFT 计时点)
↓
Decode 循环(逐 token):
┌──────────────────────────────────────┐
│ 1. KV Cache 取出 + 追加新 K/V │
│ 2. Attention + FFN (memory bound) │
│ 3. Sampling(Top-p / Top-k / temp) │
│ 4. 返回 token 给客户端(SSE 流式) │
│ 5. 检查终止条件(EOS / max_len) │
└──────────────────────────────────────┘
↓
请求结束,释放 KV Cache
↓
Detokenize:token ids → 文本(增量)
每一环都有可能成为瓶颈,需要分阶段排查。
7. 采样策略
LLM 输出是概率分布,采样策略决定”选哪个 token”。
7.1 几种主流方法
| 方法 | 公式/规则 | 用途 |
|---|---|---|
| Greedy | 总取最高概率 | 确定性场景(代码、翻译) |
| Top-k | 从概率前 k 个里采 | 控制随机性 |
| Top-p (nucleus) | 累积概率达 p 的最小集合里采 | 通用 |
| Temperature | scale logits 调整尖锐度 | 控制创造性 |
| Beam Search | 同时维护 k 条候选序列 | 翻译、确定性 |
7.2 PyTorch 实现
import torch
import torch.nn.functional as F
def sample(logits, temperature=1.0, top_k=50, top_p=0.9):
logits = logits / temperature
# Top-k
if top_k > 0:
top_k_vals, _ = torch.topk(logits, top_k)
logits[logits < top_k_vals[..., -1:]] = -float('inf')
# Top-p
if top_p < 1.0:
sorted_logits, sorted_idx = torch.sort(logits, descending=True)
cumulative_probs = F.softmax(sorted_logits, dim=-1).cumsum(dim=-1)
sorted_indices_to_remove = cumulative_probs > top_p
sorted_indices_to_remove[..., 1:] = sorted_indices_to_remove[..., :-1].clone()
sorted_indices_to_remove[..., 0] = False
indices_to_remove = sorted_indices_to_remove.scatter(-1, sorted_idx, sorted_indices_to_remove)
logits[indices_to_remove] = -float('inf')
probs = F.softmax(logits, dim=-1)
return torch.multinomial(probs, num_samples=1)
7.3 性能影响
Sampling 通常很快(< 1 ms / token),但Speculative Decoding 时温度高会让 draft 命中率骤降(第 5 章会讲)。
✅ 自我检验清单
- 两阶段直觉:能向小白解释为什么 Prefill 快、Decode 慢
- KV Cache 必要性:能解释不用 KV Cache 时复杂度从 变成
- KV Cache 算账:不查资料能算 LLaMA-2-7B 在 batch=16, S=4096 时的 KV Cache 总量(32 GB)
- GQA 影响:能算 LLaMA-3 用 GQA 时 KV Cache 比 MHA 少多少倍
- 指标辨析:能解释 TTFT / TPOT / Throughput / Goodput 各自衡量什么
- 链路拆解:能从 HTTP 到 detokenize 完整画出一次推理请求的链路
- 瓶颈定位:给定指标(TTFT 高、TPOT 高、显存满、P95 抖动),能指出最可能的根因
- Sampling 实现:能手写 Top-k + Top-p + temperature 的采样函数
- 并发估算:给定显存预算和模型,能算出最大并发请求数
📚 参考资料
- 琳琅阿木:图文详解 LLM inference——KV Cache —— 知乎深度长文
- Towards Efficient Generative LLM Serving: A Survey (CMU, 2023):https://arxiv.org/abs/2312.15234
- vLLM Blog:How continuous batching enables 23x throughput:https://www.anyscale.com/blog/continuous-batching-llm-inference
- Inference Performance Engineering (NVIDIA):https://docs.nvidia.com/deeplearning/performance/
- 方佳瑞:深入浅出 LLM 推理性能 —— 实战长文
- AI Systems Performance Engineering(Chris Fregly, O’Reilly 2025):learning.oreilly.com —— Ch1 系统讲解 goodput 度量、Ch9 推理服务架构,本章 goodput vs QPS 概念的方法论延伸