LLM Inference 是模型利用已经训练完成的权重,根据输入 Token 自回归生成输出 Token 的过程。
核心概念:
Inference
├── Execution
│ ├── Prefill
│ ├── Decode
│ └── KV Cache
├── Scheduling
│ ├── Batching
│ ├── Continuous Batching
│ └── Chunked Prefill
├── Memory
│ ├── Model Weights
│ ├── KV Cache
│ └── PagedAttention
└── Performance
├── TTFT
├── TPOT / ITL
├── Latency
└── Throughput
1. Prefill:处理输入 Prompt
Prefill 是推理的初始阶段,负责处理输入 Prompt 并建立 KV Cache。
例如输入包含 2000 个 Token:
Prompt(2000 Tokens)
↓
Prefill
├── Token Embedding
├── Transformer Layers
├── Compute Q / K / V
├── Build KV Cache
└── Generate First-token Logits
↓
First Token
由于 Prompt 的全部 Token 已经存在,模型可以通过 Causal Mask 并行计算多个位置的表示。
主要特点:
- 输入 Token 数量通常较多。
- 大规模矩阵运算较多。
- GPU 并行度高。
- 通常更偏向 Compute-bound。
Compute-bound
表示执行速度主要受到 GPU 计算能力限制。
例如:
GPU FLOPS 是主要瓶颈
增加计算能力
↓
可能提升 Prefill 速度
不过实际性能还取决于序列长度、Batch Size、Attention 实现和内存访问。
Prefill 不一定必须一次处理完整 Prompt,也可以使用 Chunked Prefill 分块执行。
2. Decode:逐 Token 生成
Prefill 产生第一个输出 Token 后,模型继续逐步生成剩余 Token。
自回归生成要求下一个 Token 依赖之前已经生成的 Token。
Previous Generated Token
↓
Compute New Q/K/V
↓
Read Historical KV Cache
↓
Attention
↓
MLP
↓
Logits
↓
Sampling
↓
Next Token
每个 Decode Step 通常只为每条正在生成的序列产生一个新 Token。
Memory-bandwidth-bound
Decode 需要反复读取模型权重,并访问随上下文增长的 KV Cache。
当 Batch 较小时,每轮计算量相对较低,GPU 往往无法充分发挥计算能力。
因此 Decode 通常更容易受到显存带宽限制。
Decode
↓
Read Model Weights
Read KV Cache
↓
Compute Next Token
当 Batch 增大时,多个请求可以共享一次批处理中的模型权重读取,从而提高计算利用率。
因此 Decode 并不永远是 Memory-bound,较大 Batch 或长上下文可能改变性能瓶颈。
Prefill 与 Decode 对比
| 对比 | Prefill | Decode |
|---|---|---|
| 主要任务 | 处理输入 Prompt | 生成后续 Token |
| 并行度 | Token 维度并行度较高 | 单条序列逐步生成 |
| 计算特点 | 大规模矩阵计算 | 较小的逐步计算 |
| 常见瓶颈 | GPU Compute | Memory Bandwidth |
| KV Cache | 创建和填充 | 读取和扩展 |
| 主要影响指标 | TTFT | TPOT / ITL |
3. KV Cache:缓存 Attention 状态
KV Cache 保存已经处理过的 Token 在各 Transformer Layer 中的 Key 和 Value。
作用是避免每次生成新 Token 时,重新计算所有历史 Token 的 K/V。
Prefill
↓
K/V for Prompt Tokens
↓
KV Cache
↓
Decode Step 1 → Append K/V
↓
Decode Step 2 → Append K/V
↓
Decode Step 3 → Append K/V
KV Cache 内存估算
对于常见的 Decoder-only Transformer:
其中:
| 参数 | 含义 |
|---|---|
| (L) | Transformer 层数 |
| (H_kv) | KV Head 数 |
| (D) | Head Dimension |
| (S) | 每条序列缓存的 Token 数 |
| (B) | Batch 中的序列数 |
| (P) | 每个元素的字节数 |
例如:
32 Layers
8 KV Heads
128 Head Dimension
4096 Cached Tokens
1 Sequence
BF16 = 2 Bytes
理论 KV Cache 大小约为 512 MiB。
这是未考虑分页分配、内存对齐、共享前缀及其他运行时开销的估算。
重要结论:
- Context Length 越大,KV Cache 通常越大。
- Batch 中的序列越多,KV Cache 总量通常越大。
- GQA/MQA 可以降低 KV Cache 大小。
- KV Cache Quantization 可以进一步减少内存开销。
4. Context Length
Context Length 是模型在当前推理过程中允许使用的上下文 Token 数量上限。
通常:
需要注意:
- 模型架构的理论位置范围不一定等于可靠支持的上下文长度。
- 推理引擎可能设置更小的
max_model_len。 - 长上下文通常增加 KV Cache 占用与 Attention 开销。
- 实际上还可能受到模型精度、RoPE 配置和 GPU 内存限制。
Context Length 影响的是单条请求可以使用的上下文规模,而 Batch Size 影响的是同时处理的请求数量,两者共同决定 KV Cache 压力。
5. Batching:批处理
Batching 是指将多个请求组合起来,在 GPU 上执行计算。
例如:
Request A ──┐
Request B ──┼── GPU Batch
Request C ──┘
优点:
- 提高 GPU 利用率。
- 分摊模型权重读取开销。
- 提高服务器整体吞吐量。
缺点:
- 可能增加排队等待。
- 消耗更多 KV Cache 内存。
- Batch 太大时可能降低单请求响应速度。
Static Batching
传统静态 Batch 通常要求一个 Batch 内的请求作为一组被调度。
不同请求的输入及输出长度可能不同,容易产生 Padding 或等待造成的浪费。
Continuous Batching
Continuous Batching(也称 In-flight Batching)允许请求动态加入或离开正在执行的批次。
Step 1: A B C
Step 2: A B C
Step 3: A C ← B 完成
Step 4: A D C ← D 加入
Step 5: D C ← A 完成
核心价值:
- 请求不需要同时进入。
- 完成的请求可以立即释放资源。
- 新请求可以在后续调度步骤加入。
- 提高 GPU 利用率与服务吞吐量。
Continuous Batching 是 vLLM、SGLang 等现代推理引擎的重要调度机制。
6. Chunked Prefill
长 Prompt 的 Prefill 可能占用大量 GPU 计算资源,从而影响正在 Decode 的请求。
Chunked Prefill 将长 Prompt 拆成多个 Token Chunk 处理。
例如:
Prompt: 4096 Tokens
Chunk 1: 1024
Chunk 2: 1024
Chunk 3: 1024
Chunk 4: 1024
这样调度器可以在多个调度步骤之间安排 Prefill 和 Decode 工作。
GPU Scheduling
↓
Decode Batch
↓
Prefill Chunk
↓
Decode Batch
↓
Prefill Chunk
主要目的:
- 避免长 Prefill 长时间占用计算资源。
- 减少对其他请求 TPOT 的影响。
- 提供更灵活的吞吐量与延迟权衡。
但分块也可能增加单条 Prompt 完成 Prefill 的时间,因此需要合理配置调度策略。
vLLM 中 max_num_batched_tokens 等参数可影响每个调度步骤的 Token 预算。
7. PagedAttention
KV Cache 随 Token 数量动态增长。
如果为每个请求预留连续的大块显存,容易出现内存浪费和碎片化。
PagedAttention 将 KV Cache 划分为固定大小的 Block,并通过映射关系管理。
Request A
Logical Blocks
[0] [1] [2]
↓ ↓ ↓
Physical KV Blocks
[7] [3] [9]
Request B
Logical Blocks
[0] [1]
↓ ↓
Physical KV Blocks
[2] [5]
主要优势:
- KV Cache 不需要为每条请求预留完整的连续物理空间。
- 降低显存碎片化与预留浪费。
- 更灵活地分配和回收 KV Cache。
- 可以支持某些 KV Block 共享策略。
PagedAttention 主要解决 KV Cache 内存管理问题,而 Continuous Batching 主要解决请求调度问题。
两者结合可以提高推理服务的并发能力。
8. 推理性能指标
| 指标 | 含义 | 优化目标 |
|---|---|---|
| TTFT | Time To First Token | 越低越好 |
| TPOT | Time Per Output Token | 越低越好 |
| ITL | Inter-token Latency | 越低越好 |
| Latency | 请求总耗时 | 越低越好 |
| Throughput | 单位时间处理的工作量 | 越高越好 |
| Tokens/s | Token 处理或生成速度 | 越高越好 |
TTFT
从请求发出到客户端收到第一个输出 Token 的时间。
Request
↓
Queue
↓
Prefill
↓
First Token
TTFT 包含的不只是 GPU Prefill 时间,还可能包含网络、请求排队、Tokenization 和首次输出传输开销。
TPOT / ITL
TPOT 通常表示生成阶段平均每个后续 Token 所需时间。
常见计算方式:
其中 math\(N\_{\mathrm{output}}>1\)math。
例如:
TTFT = 1.0 s
Total Latency = 5.0 s
Output Tokens = 101
则:
TPOT = 40 ms/token
对应的平均后续生成速度约为 25 tokens/s。
注意:不同 Benchmark 工具对 TPOT、ITL、首 Token 和流式 Chunk 的统计方式可能有差异,比较结果时需要确认定义。
请求从开始到完成的端到端时间。
Request Start
↓
Queue + Prefill + Decode + Delivery
↓
Request Complete
对于流式生成,请求的总延迟通常可以分解为:
Throughput
服务器单位时间处理的工作量。
常见指标:
Requests/s
Input Tokens/s
Output Tokens/s
Total Tokens/s
其中 Output Tokens/s 通常用于衡量生成吞吐量。
单用户 Tokens/s 与服务器整体 Tokens/s 不是同一个指标。
例如:
- 单用户速度:30 tokens/s。
- 服务器同时处理 20 个请求。
- 服务器整体生成吞吐量可能远高于 30 tokens/s。
但整体吞吐量不会简单地随并发请求数线性增长。
9. Latency 与 Throughput 的权衡
推理服务通常同时追求:
- 较低的 TTFT。
- 较低的 TPOT。
- 较高的 Throughput。
- 较好的 GPU 利用率。
但这些目标可能互相冲突。
Smaller Batch
↓
通常较低的排队与调度开销
↓
较低整体吞吐量风险
Larger Batch
↓
更高 GPU 利用率
↓
更高整体吞吐量
↓
可能增加单请求延迟
例如增加并发量:
- GPU 利用率可能上升。
- 总 Output Tokens/s 可能上升。
- Queue Time 也可能上升。
- TTFT 和 TPOT 可能恶化。
因此不能只关注 Tokens/s。
P50 / P95 / P99
性能评测通常报告延迟分位数。
- P50:50% 请求的延迟不超过该值。
- P95:95% 请求的延迟不超过该值。
- P99:99% 请求的延迟不超过该值。
平均延迟较低并不代表高负载时不存在严重的尾部延迟。
10. 推理性能优化思路
| 问题 | 可能的优化方向 |
|---|---|
| TTFT 高 | 降低排队时间、优化 Prefill、Prefix Caching |
| TPOT 高 | Continuous Batching、Attention Kernel、提高内存效率 |
| KV Cache 显存不足 | GQA、KV Cache Quantization、PagedAttention |
| GPU 利用率低 | 调整 Batch Size、Continuous Batching |
| 长 Prompt 影响其他请求 | Chunked Prefill |
| 模型权重占用过高 | Weight Quantization |
| 高并发排队严重 | 调整调度参数、增加副本或计算资源 |
Prefix Caching
如果多个请求具有相同的 Prompt 前缀,可以在满足条件时复用已经计算好的 KV Cache Block。
例如:
Request A:
[System Prompt] + [Question A]
Request B:
[System Prompt] + [Question B]
如果系统能够复用共同的 System Prompt 前缀,就可以减少重复 Prefill 计算。
不过 Prefix Caching 的收益取决于前缀重复率、缓存命中情况和内存容量,并不能保证所有请求都明显加速。
总结
LLM 推理可以用以下结构理解:
Incoming Requests
↓
Request Queue / Scheduler
↓
Continuous Batching
│
├── Prefill
│ └── Build KV Cache
│
└── Decode
├── Read KV Cache
├── Generate Token
└── Extend KV Cache
↓
Streaming Response
需要重点掌握的关系:
| 概念 | 核心理解 |
|---|---|
| Prefill | 并行处理输入 Prompt,通常更偏计算密集 |
| Decode | 自回归逐 Token 生成,通常更偏显存带宽密集 |
| KV Cache | 减少重复计算,但增加显存开销 |
| Continuous Batching | 动态管理正在处理的请求 |
| Chunked Prefill | 降低长 Prompt 对其他请求的阻塞 |
| PagedAttention | 高效分配和管理 KV Cache |
| TTFT | 首 Token 响应速度 |
| TPOT | 后续 Token 的平均生成间隔 |
| Latency | 单请求端到端耗时 |
| Throughput | 整个推理服务的处理能力 |
实际性能测试时,应固定模型、输入输出 Token 长度、硬件和并发条件,否则不同测试结果可能不具备可比性。