【infra之路】09-LLM 推理基础 — Prefill/Decode 两阶段与性能指标

发布时间:2026/6/30 11:48:36
【infra之路】09-LLM 推理基础 — Prefill/Decode 两阶段与性能指标 已经用过 vLLM 和 SGLang把推理的系统原理补全。训练是一次性算完整个 batch推理是一个一个生成 token——这个根本差异决定了推理系统的所有设计。推理 vs 训练本质区别训练 输入: [x₁, x₂, ..., x_N] (完整序列) 操作: 一次前向 一次反向所有位置并行计算 特点: 计算密集compute-boundGPU 算力是瓶颈 推理生成阶段 输入: [x₁, x₂, ..., x_{t-1}] (已生成的序列) 操作: 只算下一个 token x_t 输出: x_t → 追加到序列 → 再算 x_{t1} → ... 特点: 内存密集memory-bound带宽是瓶颈为什么推理是 memory-bound看一个具体计算生成一个 token 需要做的事 1. 把完整模型参数从显存读到计算单元7B 模型 14 GB FP16 2. 只做一个 token 的前向传播 3. 计算量: ~14 GFLOPS7B × 2 ops/token 4. 数据搬运: 14 GB 算术强度 14 GFLOPS / 14 GB 1 FLOP/Byte Roofline 拐点 (RTX 5060 Ti) ~51 FLOPS/Byte 1 51 → 严重 memory-bound读 14 GB 的参数只为了算 1 个 token——GPU 的计算单元大部分时间在等数据搬运。这就是推理的核心挑战。推理的两个阶段Prefill 和 Decode一次完整的 LLM 推理请求分两个截然不同的阶段用户输入: 请解释什么是 Transformer (假设 10 个 token) 模型输出: Transformer 是一种基于自注意力机制的... (假设 200 个 token) ┌─────────────────┐ ┌──────────────────────────────────┐ │ Prefill 阶段 │ │ Decode 阶段 │ │ │ │ │ │ 处理输入 prompt │ → │ 逐个生成输出 token │ │ 10 个 token │ │ 200 个 token每次 1 个 │ │ 并行计算 │ │ 串行计算 │ │ │ │ │ │ 计算密集 ✓ │ │ 内存密集 ✓ │ │ GPU 利用率高 │ │ GPU 利用率低 │ └─────────────────┘ └──────────────────────────────────┘Prefill 阶段预填充把用户输入的整个 prompt 一次性送进模型并行计算所有位置的 attention。输入: [tok₁, tok₂, ..., tok₁₀] (10 个 token) 操作: 1. Embedding: 10 个 token 并行查表 2. 每层 Transformer: 10 个位置并行做 QKV 投影、attention、FFN 3. 输出: 最后一个位置的 hidden state → 预测第 11 个 token 关键产物: KV Cache 每一层的 Key 和 Value 向量都要保存下来Decode 阶段要用 K: [10 × hidden_dim] V: [10 × hidden_dim]Prefill 是compute-bound——10 个 token 并行计算GPU 的计算单元有活干。Decode 阶段逐 token 生成每次只输入 1 个新 token利用之前保存的 KV Cache 做 attention生成下一个 token。第 1 步: 输入 tok₁₁利用 KV Cache (K₁₋₁₀, V₁₋₁₀) → 计算 attention → 输出 tok₁₂ → 把 tok₁₂ 的 K, V 追加到 KV Cache 第 2 步: 输入 tok₁₂利用 KV Cache (K₁₋₁₁, V₁₋₁₁) → 计算 attention → 输出 tok₁₃ → 追加到 KV Cache ... 第 200 步: 输入 tok₂₁₀利用 KV Cache (K₁₋₂₀₉, V₁₋₂₀₉) → 输出 EOS (结束) 此时 KV Cache 大小: [210 × hidden_dim × num_layers × 2(KV)]Decode 是memory-bound——每次只算 1 个 token但要把 14 GB 的模型参数全部从显存读一遍。每生成 1 个 token 的工作 读模型参数: 14 GB整个模型 读 KV Cache: ~seq_len × hidden × layers × 2 × 2 bytes 计算量: ~14 GFLOPS 算术强度 ≈ 1 FLOP/Byte → GPU 90% 时间在等数据搬运KV Cache推理的显存大户KV Cache 是 Decode 阶段的核心数据结构——保存每一层 attention 的 Key 和 Value避免重复计算。显存占用计算KV Cache 大小 2(KV) × num_layers × seq_len × hidden_dim × bytes_per_element 以 LLaMA-7B (FP16) 为例 num_layers 32 hidden_dim 4096 (num_heads × head_dim 32 × 128) bytes 2 (FP16) seq_len 2048: KV Cache 2 × 32 × 2048 × 4096 × 2 2 × 32 × 2048 × 4096 × 2 bytes ≈ 1.07 GB seq_len 8192: KV Cache ≈ 4.29 GB seq_len 32768: KV Cache ≈ 17.2 GB ← 比模型参数本身还大并发请求时的 KV Cache线上推理服务通常要同时处理多个请求batch serving8 个并发请求每个 seq_len2048 KV Cache 总计 1.07 GB × 8 8.56 GB 模型参数 (7B FP16): 14 GB KV Cache: 8.56 GB 其他 (激活值等): ~2 GB ━━━━━━━━━━━━━━━━━━━━━━ 总计: ~24.6 GBKV Cache 的管理效率直接决定了推理服务能同时处理多少请求——这就是 vLLM 的 PagedAttention 要解决的问题下一课。推理性能指标关键指标指标含义优化方向TTFT(Time To First Token)从请求到生成第一个 token 的时间减少 Prefill 时间TPOT(Time Per Output Token)Decode 阶段每个 token 的生成时间减少 Decode 延迟Throughput(tokens/s)每秒生成的总 token 数提高 batch 效率Latency(ms)单次请求的端到端延迟TTFT seq_len × TPOT指标之间的关系总延迟 TTFT (输出 token 数 - 1) × TPOT 例: TTFT 100ms, TPOT 20ms, 输出 200 tokens 总延迟 100 199 × 20 4080 ms ≈ 4.1 秒 Throughput (所有请求的总 token 数) / 总时间 受 batch size 影响: batch 越大单 token 延迟可能增加 但总吞吐上升因为 Prefill/Decode 的计算可以分摊Throughput vs Latency 的权衡Throughput (tokens/s) ↑ ┌──────┤ │ │ ← 饱和区再加请求只会增加排队延迟 │ │ │ │ sweet ────→ │ spot │ │ │ │ │ └──────┼──────────────────→ Latency (ms) ↑ 低并发: 延迟低 高并发: 延迟高实际系统需要在这条曲线上找甜蜜点——既保持低延迟又最大化吞吐。Batch 推理为什么一次处理多个请求更快单请求 Decode生成 1 个 token: 读模型参数: 14 GB 计算: 14 GFLOPS GPU 利用率: ~5%大部分时间在等数据Batch8 的 Decode同时为 8 个请求各生成 1 个 token: 读模型参数: 14 GB只读一次8 个请求共享 计算: 14 × 8 112 GFLOPS GPU 利用率: ~35%好了很多 每个请求的 token 延迟: 几乎不变因为参数搬运时间被分摊了 总吞吐: 提高 ~7 倍这就是 batch serving 的核心价值模型参数只读一次多个请求共享搬运成本。但 batch 也不是越大越好batch 太小的问题GPU 利用率低浪费算力 batch 太大的问题 1. KV Cache 显存不够每个请求都要独立的 KV Cache 2. 不同请求长度不同短的要等长的padding 浪费 3. 新来的请求要排队等 batch 结束Static Batching vs Continuous BatchingStatic Batching静态批处理等凑齐一个 batch一起 Prefill一起 Decode等全部生成完再处理下一批。请求 A (需要 50 tokens): ██████████████████████████████████████████████████ done 请求 B (需要 10 tokens): ██████████ done ────等待 A──────等待 A──等待 A──等待 A── 请求 C (需要 80 tokens): ████████████████████████████████████████████████████████ done 请求 B 只要 10 个 token 就完成了但要等 A 和 C 都结束才能释放资源 → GPU 资源浪费严重 → 新来的请求 D 要等整个 batch 结束才能开始Continuous Batching连续批处理— vLLM/Orca 的核心创新每个 Decode 步结束后检查哪些请求完成了移出 batch同时把新请求加入 batch。请求 A (50 tokens): ██████████████████████████████████████████████████ done 请求 B (10 tokens): ██████████ [B 完成移出] [D 加入] →████████████████████... 请求 C (80 tokens): ████████████████████████████████████████████████████████████████ done 请求 D (新来): ████████████████████████████████████████████ done B 一完成就让出位置给 D → GPU 始终满载 → 吞吐提高 2-4 倍 → 新请求不用等整个 batch 结束Continuous Batching 是 vLLM 和 SGLang 的核心调度机制第 11 课会深入讲实现细节。推理系统的核心挑战总结1. Memory-bound: 每生成 1 token 要读整个模型 → 带宽是瓶颈 → 解法: batch serving, 量化(减少模型大小), Tensor Core 2. KV Cache 管理: 每个请求的 KV Cache 大小随序列长度线性增长 → 解法: PagedAttention (vLLM), RadixAttention (SGLang) 3. 调度效率: Static batching 浪费资源新请求等待时间长 → 解法: Continuous Batching (Orca/vLLM) 4. Prefill 延迟: 长 prompt 的 Prefill 耗时长几十-几百 ms → 解法: chunked prefill, Prefill/Decode 分离 (DistServe) 5. 长序列: 128K 的 context 让 KV Cache 爆炸 → 解法: MQA/GQA, KV Cache 量化, Ring Attention本课小结概念要点Prefill处理完整 prompt并行计算compute-boundDecode逐 token 生成串行memory-bound每 token 读全模型KV Cache保存每层 K/V 向量显存随 seq_len 线性增长TTFT首个 token 延迟由 Prefill 时间决定TPOT每个输出 token 时间由 Decode 效率决定Batch Serving多个请求共享模型参数读取提高吞吐Continuous Batching动态调度完成一个加入一个避免 Static Batching 的浪费自检为什么 Decode 阶段是 memory-bound答每生成 1 token 需要读 14 GB 模型参数但只做 14 GFLOPS 计算算术强度 ~1 FLOP/Byte远低于 Roofline 拐点KV Cache 在什么情况下会比模型参数还大答长序列场景如 32K seq_len 时 KV Cache 17 GB超过 7B 模型的 14 GB 参数Static Batching 的主要浪费在哪答短请求完成后要等长请求GPU 资源闲置新请求要等整个 batch 结束才能开始