Prefill/Decode 分离原理
核心要点:
- 三条独立观察:prefill compute-bound / decode memory-bound; TTFT / TPOT SLO 正交;并行策略偏好不同
- 代价:KV cache 必须跨集群转移一次,KV 转移延迟 < TTFT 预算时才划算
- Chunked prefill 是折中:解决 colocated 干扰但并行策略仍耦合,TTFT 被切碎拉长
- SLO 正交分解:TTFT 归 prefill 池,TPOT 归 decode 池,Goodput = 二者乘积
- KV 转移可隐藏:Layer-wise pipelined + zero-copy RDMA + GPU staging,占总延迟可降至 0.1%
本文只讲 PD 分离的原理与调度模型。具体系统实现 (Mooncake / DistServe / SGLang / Dynamo / Splitwise) 在本知识域下各有独立文档展开。
Prefill 跟 Decode 负载特征差在哪
核心问题:计算量 / 访存量 / batch 友好度 / 主导 SLO 各自的差异是什么?
计算量与访存量
设模型隐藏维度为 $d$,层数为 $L$,输入 prompt 长度为 $s$, batch 内有 $B$ 条请求。
Prefill 阶段每条请求的浮点运算量 (不含 attention 的二次项) 近似:
$$\begin{equation} \text{FLOPs}_{\text{prefill}} \approx 2 \cdot s \cdot N_{\text{params}} \label{eq:pd-disagg-prefill-flops} \end{equation}$$$N_{\text{params}}$ 是模型参数量。
Decode 阶段每生成 1 个 token 的浮点运算量:
$$\begin{equation} \text{FLOPs}_{\text{decode/token}} \approx 2 \cdot N_{\text{params}} \label{eq:pd-disagg-decode-flops} \end{equation}$$但 decode 每步还需读取整个 KV cache。KV cache 在第 $t$ 步的大小 (FP16):
$$\begin{equation} \text{KVSize}(t) = 2 \cdot L \cdot t \cdot d_{\text{kv}} \cdot \text{sizeof(fp16)} \text{ bytes per request} \label{eq:pd-disagg-kv-size} \end{equation}$$$d_{\text{kv}}$ 是 KV head 的总维度 (GQA 下显著小于 $d$)。
参考量级:OPT-66B 上 512 个 token 的单条 KV cache 约 1.13 GB (DistServe §2.1[1])。
性能维度对比
| 维度 | Prefill | Decode |
|---|---|---|
| Bottleneck | 计算 (compute-bound) | 显存带宽 (memory-bandwidth-bound) |
| Arithmetic intensity | 随 $s$ 线性增长,prompt 长时远超 GPU ridge point | $\approx B$, B 不够大就无法饱和算力 |
| Batch 友好度 | 单条长 prompt 已能打满,组 batch 收益小 | 必须靠大 batch 摊销显存读取,对 batch 高度敏感 |
| 主导 SLO | TTFT | TPOT |
| 并行策略偏好 | TP 较大、PP 看模型规模 | DP / PP 摊销 KV 容量,TP 主要为带宽 |
| 单步耗时 | 与 $s$ 成正比,可达数百 ms | 与 KV 长度弱相关,单步约 10–50 ms |
@tbl-pd-disagg-phase-compare 负载特征对比
DistServe 的概括是 "a prefill step often takes much longer than a decoding step"[1]; Splitwise 的概括是 prefill compute-intensive、decode memory-intensive[2]; Mooncake 用 "high compute, low memory" vs. "low compute, high memory" 描述[3]。
合并部署的问题在哪
核心问题:colocated 干扰是什么?Chunked prefill 能解决多少?
直接 colocated 的相互干扰
把 prefill 与 decode 放在同一 batch 同一组 GPU 上轮转执行,会出现:
- Prefill 阻塞 decode:一个长 prompt 的 prefill step 单独耗费数百 ms,期间所有正在 decode 的请求都被推迟,P99 TPOT 严重抖动
- Decode 拖累 prefill:decode 步频繁插入会让 prefill 的大矩阵乘被打断,TTFT 抬升
- 并行策略只能折中:prefill 偏好的 TP 配比与 decode 偏好的 batch 容量配比互相矛盾
DistServe 把这两条干扰命名为 "prefill-decoding interference",并指出它 "couple the resource allocation and parallelism plans for both phases"[1]。
Chunked prefill 的折中与局限
SARATHI 提出的 chunked prefill 方案:把长 prompt 切成固定大小的 chunk (如每 chunk 512 tokens),每个 step 用一个 prefill chunk 填满 batch 剩余位置给 decode 请求 "piggyback"[4]。这种 hybrid batch 让 colocated 部署的单步时长更均匀,pipeline bubble 减少。
但 chunked prefill 仍然在同一组 GPU 上做两件事,无法解决:
- 并行策略仍然耦合:只能选一套 TP / PP 配比,对 prefill 与 decode 都不是最优
- TTFT 被 chunk 化拉长:切成 chunk 后每个 chunk 之间夹杂 decode 步,prompt 端到端处理时间增加
- 硬件型号无法分化:prefill 对算力敏感、decode 对显存带宽敏感,统一机型不能各自最优
SARATHI 报告 chunked prefill 在 LLaMA-13B / A6000 上 decode 吞吐最多提升 10×,端到端 1.33×[4]; DistServe 与 Splitwise 进一步分离硬件后报告比 chunked prefill 类方案更高的 goodput。
PD 分离的调度模型怎么搭
核心问题:物理拓扑 / 单请求生命周期 / 池容量比例怎么配?
物理拓扑
PD 分离把推理集群划分为两个独立池:
| 池 | 职责 | 优化目标 | 典型并行 |
|---|---|---|---|
| Prefill 池 | 接收请求、计算 prompt 的 KV cache | 最小化 TTFT P90/P99 | TP 较大,batch 较小 |
| Decode 池 | 接收 KV cache、自回归生成 token | 最大化 token 吞吐同时满足 TPOT SLO | batch 较大,TP / PP 看 KV 容量 |
@tbl-pd-disagg-pool 两池职责
两池之间通过 RDMA / NVLink / RoCE / EFA 网络转发 KV cache (Mooncake / SGLang 等都支持多种 transport)。
单请求生命周期
一条请求的端到端流程:
- Router 选址:全局调度器把请求路由到一台 prefill 实例
- Prefill 执行:prefill 实例算完整个 prompt,产出 KV cache
- Decode 选址:调度器选择一台 decode 实例 (可能基于负载、KV 亲和性、可用显存)
- KV 转移:prefill 实例把每层 KV cache 通过 RDMA / NVLink 推送给 decode 实例的显存
- Decode 自回归:decode 实例从转移好的 KV cache 开始生成 token,直到 EOS 或达到 max_tokens
SGLang 的实现把 router 称为 Model Gateway, KV transfer 后端可选 Mooncake / NIXL / ASCEND[5]。Mooncake 让 decode 端作为 consumer 主动拉取,prefill 端做 zero-copy RDMA[3]。
池容量比例
Prefill 与 decode 的算力需求比依赖工作负载。Mooncake 用 XpYd 记号描述部署比例:X 个 prefill 节点、Y 个 decode 节点。这个比例与下列因素相关:
- 平均 prompt 长度 $\bar{s}$ 越大,prefill 占比越大
- 平均输出长度 $\bar{n}$ 越大,decode 占比越大
- KV cache 越大 (长上下文、未启用 GQA),decode 池显存需求越大
实际选址通常用 binary search + simulation 离线确定 (DistServe 的做法[1]),或在线根据队列长度动态调整。
SLO 怎么正交分解
核心问题:TTFT / TPOT / Goodput 在 PD 分离下怎么各管一池?
PD 分离的关键好处是把端到端 SLO 正交分解:
| 端到端 SLO | 归属阶段 | 由谁负责 | 主要影响因素 |
|---|---|---|---|
| TTFT P90/P99 | prefill | prefill 池容量 + KV 转移延迟 | prompt 长度、TP 配比、网络带宽 |
| TPOT P90/P99 | decode | decode 池 batch 大小与显存带宽 | KV cache 总量、batch 大小、TP / PP |
| Goodput | 二者乘积 | 整池规划 | XpYd 比例、SLO attainment 目标 (如 90%) |
@tbl-pd-disagg-slo SLO 拆分
DistServe 把 SLO attainment 定义为"百分比请求同时满足 TTFT 与 TPOT 约束",并报告 P90 attainment 下相比 colocated 系统能服务 7.4× 更多请求、容忍 12.6× 更紧的 SLO[1]。
需要注意 KV 转移延迟实质上挤占 TTFT 预算:从用户视角,TTFT 是 prompt 到达到首个 token 返回的全程时间,包含 prefill 计算 + KV 转移 + decode 首步。如果 KV 转移占 TTFT 预算超过一定比例 (典型 < 10%–20%),分离方案的收益就被吃掉了。
KV cache 跨集群传输代价怎么算
核心问题:转移数据量公式 / 转移时间模型 / 怎么隐藏?
转移数据量
单条请求 prompt 长度 $s$、KV cache 大小由公式 $\eqref{eq:pd-disagg-kv-size}$ 给出。一条 13B 模型 ($L=40$, $d_{\text{kv}}\approx 5120$, FP16) 的 prompt 1024 tokens 的 KV cache 约:
$$\begin{equation} 2 \times 40 \times 1024 \times 5120 \times 2 \approx 838 \text{ MB} \label{eq:pd-disagg-kv-example} \end{equation}$$GQA / MLA 会显著降低 $d_{\text{kv}}$,使 KV 转移量降低一个量级。
转移时间模型
设 prefill 与 decode 之间的有效带宽为 $BW$ (GB/s),单条请求的转移时间:
$$\begin{equation} T_{\text{transfer}} = \frac{\text{KVSize}(s)}{BW} + T_{\text{setup}} \label{eq:pd-disagg-transfer-time} \end{equation}$$$T_{\text{setup}}$ 是握手、远端内存注册、RDMA WR 提交的固定开销。对于 200 Gbps (25 GB/s) 的 RoCE / IB, 838 MB 转移约 33 ms; NVLink-300GB/s 量级下只需约 3 ms。
转移方式与隐藏
为减少 KV 转移对 TTFT 的拖累,主流做法:
| 优化 | 机制 | 出处 |
|---|---|---|
| Layer-wise pipelined transfer | prefill 算完第 $i$ 层就启动第 $i$ 层 KV 的转移,与后续层计算 overlap | DistServe / Mooncake |
| Zero-copy RDMA | 双方注册 GPU 显存为 RDMA buffer,避免 CPU 中转 | Mooncake TransferEngine |
| 异步预拉取 | decode 端主动 pull, prefill 端无需阻塞等待 ack | Mooncake |
| GPU staging buffer | 当 prefill 与 decode 的 TP 不一致时,先 gather 到连续 buffer 再批量 RDMA,避免 per-token slice 的小包碎片,报告 2–5× 吞吐提升 | SGLang heterogeneous TP |
@tbl-pd-disagg-transfer-opt 转移优化
Heterogeneous TP 的额外开销:当 prefill 池的 TP 与 decode 池的 TP 不相等时 (很常见,因为两者最优 TP 不同),KV cache 在两侧的分片布局不一致,需要额外的 reshape / gather 步骤。SGLang 的 GPU staging buffer 优化正是为此设计。
PD 分离适合什么 / 不适合什么
核心问题:哪些场景值得做 PD 分离?哪些场景反而拖慢?
适用场景
| 场景 | 原因 |
|---|---|
| 长 prompt (chat / RAG / 代码补全) | prefill 阶段长,prefill-decode 干扰严重 |
| 长输出 (reasoning / agent) | decode 阶段长,TPOT SLO 是主要约束 |
| 严苛的 SLO 双约束 | colocated 干扰导致 P99 难达标 |
| 异构 GPU 池 | prefill 用高算力卡 (H100), decode 用大显存 / 低成本卡 (Splitwise 报告 1.4× 吞吐、20% 成本降低[2]) |
| 大模型 + 高并发 | goodput 收益足以摊销 KV 转移开销 |
不适用场景
| 场景 | 原因 |
|---|---|
| 超短 prompt + 超短输出 | KV 转移延迟占 TTFT 比例过大 |
| 集群间带宽不足 | $T_{\text{transfer}}$ 接近甚至超过 prefill 时间,分离反而拖慢 |
| 极小规模部署 (单节点) | 没有足够 GPU 划分两池 |
| 工作负载高度均匀且短 | chunked prefill 已能解决 colocated 干扰 |
@tbl-pd-disagg-applicability 适用与不适用场景
Takeaway
| 知识点 | 核心结论 |
|---|---|
| 三条独立观察 | prefill compute-bound / decode memory-bound; SLO 正交;并行策略偏好不同 |
| Chunked prefill 局限 | 解决干扰但并行策略仍耦合,TTFT 被切碎拉长 |
| 调度模型 | 两池 + Router 选址 + 5 步生命周期 + XpYd 比例配置 |
| SLO 分解 | TTFT 归 prefill, TPOT 归 decode, Goodput = 二者乘积;KV 转移占 TTFT < 20% 才划算 |
| KV 转移代价 | 13B / 1K prompt ≈ 838 MB; 200 Gbps RoCE ~33 ms; NVLink ~3 ms |
| 隐藏方法 | Layer-wise pipelined + zero-copy RDMA + GPU staging,最低可降至 0.1% |
| 适用 | 长 prompt / 长输出 / 严 SLO / 异构 GPU / 大模型高并发 |
| 不适用 | 超短 + 超短 / 跨 DC 带宽不足 / 单节点 / 工作负载均匀且短 |
参考资料
- Zhong et al., DistServe: Disaggregating Prefill and Decoding for Goodput-optimized LLM Serving, OSDI 2024. https://arxiv.org/abs/2401.09670
- Patel et al., Splitwise: Efficient Generative LLM Inference Using Phase Splitting, ISCA 2024. https://arxiv.org/abs/2311.18677
- DeepWiki Mooncake 7.5 Prefill-Decode Disaggregation. https://deepwiki.com/kvcache-ai/Mooncake/7.5-prefill-decode-disaggregation
- Agrawal et al., SARATHI: Efficient LLM Inference by Piggybacking Decodes with Chunked Prefills, arXiv:2308.16369. https://arxiv.org/abs/2308.16369
- SGLang Advanced Features: PD Disaggregation. https://sgl-project.github.io/advanced_features/pd_disaggregation.html