当你向一个大语言模型发送请求时,可能会注意到一个有趣的现象:第一个字蹦出来总是慢半拍,但随后的字却如流水般涌出。这种"先慢后快"的节奏并非偶然,而是大模型推理机制的根本特性。
这不是模型"反应慢",而是 Transformer 架构与 GPU 硬件之间一场精心编排的双人舞。理解这场舞蹈的编排逻辑,不仅能帮你优化推理性能,更能让你看清大模型推理系统的设计本质。
两阶段推理:Prefill 与 Decode 的本质区别
大模型推理过程被严格划分为两个阶段,每个阶段都有着截然不同的计算特征。
Prefill 阶段:全量并行计算
当用户输入一段 prompt,模型首先进入 Prefill 阶段。这个阶段的核心任务是处理所有输入 token,计算并存储每一层的 Key-Value 向量,构建所谓的 KV Cache,同时生成第一个输出 token。
假设用户的 prompt 包含 $N$ 个 token,Prefill 阶段会将这 $N$ 个 token 一次性送入模型。在自注意力计算中,每个 token 都需要与所有其他 token 计算注意力分数——这是一个 $N \times N$ 的矩阵运算。
以 Llama 2 7B 为例,当输入长度为 4096 个 token 时:
$$\text{Attention Computation} = 4 \cdot d \cdot N^2 + 3 \cdot N^2 \approx 8.6 \times 10^9 \text{ FLOPs per layer}$$其中 $d$ 是每个注意力头的维度(128),$N$ 是序列长度。模型共有 32 层,仅注意力层的计算量就达到约 $2.7 \times 10^{11}$ FLOPs。
这种大规模矩阵乘法天然适合 GPU 的并行架构。Prefill 是计算密集型操作——算术强度(Arithmetic Intensity,即每字节内存访问对应的计算量)极高。对于 Llama 2 7B,Prefill 阶段的算术强度约为:
$$\text{Arithmetic Intensity} \approx \frac{4dN^2 + 3N^2}{8N^2 + 8Nd} \approx 62 \text{ ops/byte}$$Decode 阶段:逐 token 的内存苦旅
第一个 token 生成后,推理进入 Decode 阶段。这是自回归生成的核心——每次只生成一个 token,将其拼接到已有序列,再预测下一个。
关键在于:每次生成新 token 时,模型不需要重新计算之前所有 token 的 Key-Value 向量,因为这些已经被缓存在 KV Cache 中。新 token 只需计算自己的 K、V 向量,然后与缓存中的历史 K、V 进行注意力计算。
但问题来了:虽然计算量很小(只需处理 1 个 token),但每个 token 的生成都需要从 GPU 内存加载整个模型的参数。一个 7B 参数的模型,即使使用 FP16 存储,也需要加载 14 GB 的权重数据。
这就是 Decode 阶段的核心矛盾:
$$\text{Compute per token} \approx 2 \times \text{Params} = 14 \times 10^9 \text{ FLOPs}$$$$\text{Memory access per token} \approx 2 \times \text{Params} = 14 \text{ GB}$$算术强度骤降到约 1 ops/byte——比 Prefill 低了两个数量级。GPU 的计算单元大部分时间都在等待数据从内存传输过来。
用 Roofline 模型看两个阶段的瓶颈
Roofline 模型是理解计算瓶颈的经典工具。它的核心思想是:一个操作的性能上限由两个因素决定——计算能力和内存带宽。
$$\text{Peak Performance} = \min(\text{Compute Peak}, \text{Memory BW} \times \text{Arithmetic Intensity})$$以 NVIDIA A10 GPU 为例:
| 指标 | 数值 |
|---|---|
| FP16 计算能力 | 125 TFLOPS |
| 内存带宽 | 600 GB/s |
| Ops:Byte 比值 | 208.3 |
当算术强度 > 208.3 时,操作是计算密集型;当算术强度 < 208.3 时,操作是内存密集型。
- Prefill(~62 ops/byte):虽然低于 208.3,但由于处理大量 token,计算总量巨大,GPU 利用率仍然很高。实际上对于长序列,Prefill 更接近计算密集型。
- Decode(~1 ops/byte):远低于阈值,是典型的内存密集型操作。GPU 大部分计算能力被浪费。
为什么第一个 Token 总是特别慢
理解了两个阶段的差异,“第一个 token 慢"的答案就清晰了:Time To First Token (TTFT) 本质上就是 Prefill 阶段的延迟。
TTFT 的构成
TTFT 包含三个部分:
$$\text{TTFT} = T_{\text{queue}} + T_{\text{prefill}} + T_{\text{network}}$$- $T_{\text{queue}}$:请求排队等待时间
- $T_{\text{prefill}}$:Prefill 计算时间
- $T_{\text{network}}$:网络传输延迟
在单请求场景下,$T_{\text{queue}} \approx 0$,TTFT 主要由 Prefill 时间决定。
Prefill 时间与 Prompt 长度的关系
Prefill 时间的计算公式为:
$$T_{\text{prefill}} = \frac{N_{\text{tokens}} \times 2 \times P}{\text{Compute BW}}$$其中 $N_{\text{tokens}}$ 是输入 token 数量,$P$ 是模型参数量,Compute BW 是 GPU 的计算带宽。
以 Llama 2 7B 在 A10 GPU 上为例,输入 350 个 token:
$$T_{\text{prefill}} = \frac{350 \times 2 \times 7 \times 10^9}{125 \times 10^{12}} \approx 39 \text{ ms}$$如果输入长度增加到 4096 个 token(约 3000 个汉字):
$$T_{\text{prefill}} = \frac{4096 \times 2 \times 7 \times 10^9}{125 \times 10^{12}} \approx 458 \text{ ms}$$这就是为什么长对话场景下,用户会明显感觉到模型"反应慢”——不是模型在思考,而是在处理大量输入文本。
与 Decode 阶段的对比
单个 token 的 Decode 时间由内存带宽决定:
$$T_{\text{decode per token}} = \frac{2 \times P \times \text{precision}}{\text{Memory BW}}$$同样的模型和 GPU:
$$T_{\text{decode per token}} = \frac{14 \times 10^9}{600 \times 10^9} \approx 23 \text{ ms/token}$$这解释了为什么流式输出时,后续 token 的生成速度相当稳定——它主要取决于内存带宽,而非 prompt 长度。
KV Cache:连接两个阶段的桥梁
KV Cache 是理解 LLM 推理的关键。它存储了 Prefill 阶段计算的所有 Key 和 Value 向量,供 Decode 阶段复用。
KV Cache 的大小计算
每个 token 的 KV Cache 大小计算公式:
$$\text{KV Cache per token} = 2 \times 2 \times n_{\text{layers}} \times d_{\text{model}} \times \text{precision}$$- 第一个 2:Key 和 Value 两个矩阵
- 第二个 2:每个数值的字节数(FP16 = 2 bytes)
- $n_{\text{layers}}$:Transformer 层数
- $d_{\text{model}}$:模型隐藏维度
对于 Llama 2 7B($n_{\text{layers}}=32$, $d_{\text{model}}=4096$):
$$\text{KV Cache per token} = 2 \times 2 \times 32 \times 4096 \times 2 = 1,048,576 \text{ bytes} \approx 1 \text{ MB/token}$$一个 4096 token 的序列需要约 4 GB 的 KV Cache。这就是为什么长上下文模型对 GPU 内存有极高要求。
图片来源: Mastering LLM Techniques: Inference Optimization - NVIDIA
KV Cache 的内存压力
KV Cache 与模型权重共享 GPU 内存:
$$\text{Total Memory} = \text{Model Weights} + \text{KV Cache} + \text{Activations}$$以 24 GB 显存的 A10 运行 Llama 2 7B 为例:
- 模型权重:14 GB
- 剩余给 KV Cache 和激活:10 GB
这限制了最大序列长度和批处理大小。如果序列长度为 4096,单个请求就需要 4 GB KV Cache,那么最多只能同时处理 2 个请求。
批处理的困境:两个阶段的资源竞争
当多个请求并发时,如何高效调度 Prefill 和 Decode 成为一个复杂问题。
静态批处理的缺陷
最简单的方案是静态批处理:等待固定数量的请求,然后一起处理。
问题在于:不同请求的 prompt 长度和输出长度差异巨大。一个 100 token 的简单问题和一篇 4000 token 的长文分析放在一起,短请求必须等待长请求完成——造成严重的资源浪费。
连续批处理(Continuous Batching)的革命
2022 年 OSDI 会议发表的 ORCA 论文提出了连续批处理,也称为迭代级调度(Iteration-level Scheduling)。
核心思想:每个迭代结束时,完成的请求立即返回结果,新请求立即加入批次。
gantt
title 连续批处理调度示意
dateFormat X
axisFormat %s
section 请求A
Prefill A :a1, 0, 100
Decode A :a2, after a1, 50
section 请求B
Prefill B :b1, 50, 80
Decode B :b2, after b1, 60
section 请求C
Prefill C :c1, 130, 90
Decode C :c2, after c1, 40
与静态批处理相比,连续批处理实现了:
- 吞吐量提升 10-20 倍:GPU 利用率大幅提高
- 延迟降低:短请求无需等待长请求
- 资源公平:每个请求按实际需求获得计算资源
Chunked Prefill:打破 Prefill 对 Decode 的阻塞
连续批处理解决了请求间的调度问题,但还有一个更深层的问题:同一个请求内部,Prefill 和 Decode 也会相互阻塞。
Prefill 阻塞问题
假设当前有一个新请求进入,同时有 10 个请求正在 Decode。新请求的 Prefill 需要处理 4000 个 token,计算密集且耗时。
在传统的 in-flight batching 中,这 4000 个 token 的 Prefill 会占据整个 GPU,10 个正在 Decode 的请求必须等待。用户看到的就是:输出突然卡住,等了几百毫秒后又恢复。
这种阻塞直接影响 Inter-Token Latency (ITL)——用户感知到的生成流畅度。
Chunked Prefill 的解决方案
Chunked Prefill 的核心思想:将大 Prefill 拆分成多个小块,每个小块与 Decode 请求混合执行。
传统方式:
[Prefill 4000 tokens ████████████████████████] [Decode □□□□□□□□□□]
Chunked Prefill:
[Prefill 512][Decode □□□][Prefill 512][Decode □□□]...
具体实现中:
- 将输入 token 分成固定大小的 chunk(如 512 个 token)
- 每个 chunk 作为一次"小 Prefill"执行
- 在 chunk 之间插入正在进行的 Decode 请求
- 第一个 chunk 完成后即可开始生成第一个 token
Chunked Prefill 的权衡
| 参数 | 小 Chunk | 大 Chunk |
|---|---|---|
| TTFT | 较好(可更快开始生成) | 较差(需等待更大块完成) |
| ITL | 较好(Decode 不被长时间阻塞) | 较差(长 Prefill 阻塞) |
| 吞吐量 | 较低(调度开销增加) | 较高(更少的调度开销) |
| 内存效率 | 较高(峰值内存降低) | 较低(需预分配更多内存) |
实践中,框架如 vLLM 默认启用 Chunked Prefill,并根据 GPU 利用率动态调整 chunk 大小。
PD 分离:极致优化的架构选择
当业务对 TTFT 和 ITL 都有极高要求时,一种更激进的方案是将 Prefill 和 Decode 完全分离到不同的 GPU。
为什么需要 PD 分离
Prefill 和 Decode 有截然不同的资源需求:
| 特性 | Prefill | Decode |
|---|---|---|
| 计算特征 | 计算密集型 | 内存密集型 |
| GPU 利用率 | 高 | 低(大部分时间等待内存) |
| 内存需求 | KV Cache 写入 | KV Cache 读取 + 权重加载 |
| 优化方向 | 高计算带宽 | 高内存带宽 |
将它们放在同一 GPU 上,必然顾此失彼:
- 如果优化 Prefill(高频率、大缓存),Decode 的内存带宽可能不足
- 如果优化 Decode(高内存带宽),Prefill 的计算能力可能被浪费
PD 分离架构
flowchart LR
subgraph PD分离架构
direction TB
A[用户请求] --> B[Prefill Worker]
B --> C[KV Cache]
C --> D[Decode Worker]
D --> E[输出]
end
分离后的优势:
- 独立扩展:可以根据负载比例调整 Prefill 和 Decode worker 数量
- 独立优化:Prefill worker 选择高计算能力的 GPU,Decode worker 选择高内存带宽的 GPU
- 并行执行:不同请求的 Prefill 和 Decode 完全并行,互不干扰
PD 分离的代价
然而,PD 分离并非银弹:
- KV Cache 传输成本:Prefill 完成后的 KV Cache 需要传输到 Decode worker。对于长序列,这可能达到数 GB 的数据
- 网络延迟:跨机器传输增加了端到端延迟
- 实现复杂度:需要精细的调度策略和高速网络(如 RDMA、NVLink)
实验数据表明:对于短 prompt 或小规模部署,PD 分离反而可能降低 20-30% 的性能。只有当 KV Cache 复用率高(如多轮对话、Agent 工作流)或负载足够大时,分离才划算。
性能指标:如何衡量推理质量
理解了技术原理,还需要知道如何量化评估推理性能。
核心指标定义
| 指标 | 全称 | 定义 | 主要影响因素 |
|---|---|---|---|
| TTFT | Time To First Token | 从发送请求到收到第一个 token 的时间 | Prefill 时间、排队时间 |
| TPOT | Time Per Output Token | 生成每个后续 token 的平均时间 | Decode 速度、内存带宽 |
| ITL | Inter-Token Latency | 连续两个 token 之间的延迟 | Decode 调度、批处理冲突 |
| Throughput | 吞吐量 | 单位时间处理的 token 数量 | 批处理大小、GPU 利用率 |
延迟与吞吐量的权衡
这是推理优化的核心矛盾:
$$\text{Latency} \approx \text{TTFT} + \text{TPOT} \times N_{\text{output tokens}}$$- 低延迟:需要小批次、快速调度,牺牲吞吐量
- 高吞吐:需要大批次、充分利用 GPU,牺牲延迟
以 vLLM 的配置为例:
# 优化 TTFT(首 token 延迟)
max_num_batched_tokens = 2048 # 较小值,减少 Prefill 对 Decode 的阻塞
# 优化吞吐量
max_num_batched_tokens = 16384 # 较大值,允许更多并行计算
实际性能基准
以 Llama 2 7B 在不同 GPU 上的推理时间(350 token 输入,150 token 输出)为例:
| GPU | Prefill 时间 | Decode 时间/token | 总生成时间 |
|---|---|---|---|
| T4 | 75 ms | 46 ms | 6.98 s |
| A10 | 39 ms | 23 ms | 3.49 s |
| A100 | 16 ms | 6 ms | 0.92 s |
数据清晰地展示了:
- A100 比 T4 快约 7.6 倍——内存带宽的巨大差异(2039 GB/s vs 300 GB/s)
- Decode 时间占总时间的 80% 以上——长输出场景下,Decode 是主要瓶颈
实践建议
基于上述原理,以下是不同场景的优化策略:
聊天/对话场景
特点:短 prompt、多轮对话、延迟敏感
策略:
- 启用 KV Cache 复用(前几轮对话的 KV Cache 可直接使用)
- 使用较小 chunk size 的 Chunked Prefill
- 适当降低 max_num_seqs 以减少排队
RAG/长文档处理场景
特点:长 prompt、输出相对较短
策略:
- 使用 Chunked Prefill 减少首 token 延迟
- 考虑 PD 分离(如果有足够 GPU)
- 启用前缀缓存(相同文档的 KV Cache 可复用)
批量处理场景
特点:吞吐量优先、延迟容忍度高
策略:
- 增大 max_num_batched_tokens
- 使用连续批处理
- 考虑多 GPU 数据并行
技术演进方向
LLM 推理优化仍在快速发展。值得关注的方向包括:
- Speculative Decoding:使用小模型预测多个 token,大模型验证,可显著提升 Decode 速度
- Attention 优化:Flash Attention、Multi-Query Attention 等持续降低注意力计算开销
- 量化技术:FP8、INT4 量化减少内存带宽需求,对 Decode 阶段收益最大
- 专用硬件:针对 LLM 推理优化的芯片(高内存带宽、大容量)
大模型推理的"先慢后快",本质上是 Transformer 架构在 GPU 上的必然表现。Prefill 的计算密集与 Decode 的内存密集,构成了推理性能优化的核心矛盾。
理解这一点,才能真正读懂 TTFT、ITL 这些指标背后的含义,才能在吞吐量与延迟之间做出正确的权衡。那些看似"慢"的第一秒,不是缺陷——它是模型在为后续的流畅输出做准备工作。
当你下次看到模型在第一个字出现前停顿片刻,不妨把它想象成一场交响乐演出前的调音——看似漫长,却是后续华彩乐章的必要铺垫。
参考资料
- NVIDIA. Mastering LLM Techniques: Inference Optimization. NVIDIA Technical Blog, 2023.
- Kwon et al. Efficient Memory Management for Large Language Model Serving with PagedAttention. SOSP 2023.
- Yu et al. Orca: A Distributed Serving System for Transformer-Based Generative Models. OSDI 2022.
- Baseten. A guide to LLM inference and performance. 2025.
- BentoML. LLM Inference Handbook: Static, dynamic and continuous batching.
- BentoML. LLM Inference Handbook: Prefill-decode disaggregation.
- NVIDIA. Streamlining AI Inference Performance with TensorRT-LLM Chunked Prefill. 2024.
- vLLM Documentation. Optimization and Tuning.
- Databricks. LLM Inference Performance Engineering: Best Practices. 2023.
- Anyscale. Understand LLM latency and throughput metrics.
- Zhang et al. Prefill vs. Decode Bottlenecks: SRAM–Frequency Tradeoffs. arXiv 2025.