Back to writing

/ AI Infra

[Infra-4] Prefill-Decode Disaggregation

A practical walkthrough of prefill-decode disaggregation: why prefill and decode conflict, what is actually disaggregated, how it affects TTFT and TPOT, and why KV cache transfer becomes the new systems bottleneck.

4 minInfra · Distributed Inference · vLLM · KV Cache

我一开始理解 PD 分离时,很容易把它想成一个很朴素的拆分:把 prefill 放到一批 GPU 上,把 decode 放到另一批 GPU 上。

这个说法没有错,但它没有说出为什么这件事在大模型推理里会变得重要。真正关键的是:Prefill 和 Decode 虽然都属于一次 LLM 推理,但它们对 GPU、显存、网络和调度器的要求几乎是两种 workload。把它们混在同一组 worker 里,系统会同时追求“大 batch 高吞吐”和“小步低延迟”,最后两边都容易被对方干扰。

所以我现在更愿意这样理解 PD 分离:

PD 分离不是模型结构优化,而是 LLM serving 的系统架构优化。
它把资源需求不同的两个推理阶段拆开,让 Prefill 和 Decode 可以分别调度、分别扩容、分别优化。

它真正改变的不是 Transformer 怎么算,而是推理服务怎么组织计算、缓存和数据流。

1. 先把一次推理拆成两个阶段#

一次自回归 LLM 推理通常可以分成两个阶段:

用户输入 prompt

Prefill:一次性处理输入,生成初始 KV Cache

Decode:逐 token 生成输出,并持续读取与追加 KV Cache

如果用户输入 16k tokens,模型输出 1k tokens,那么系统不是用同一种方式处理这 17k tokens。前面的 16k 输入会集中进入 prefill;后面的 1k 输出会进入逐步 decode。

这个分界非常重要,因为它直接决定了性能指标该怎么拆。

Prefill 主要影响 TTFT:Time To First Token
Decode 主要影响 TPOT:Time Per Output Token

TTFT 决定用户等多久看到第一个 token。TPOT 决定回答吐出来时是否顺滑。

2. Prefill 更像大矩阵计算#

Prefill 是把完整 prompt 一次性送进模型。假设输入长度是 16k tokens,模型需要对这 16k tokens 做完整 forward,并为后续生成准备 KV Cache。

它的特点更接近:

计算密集
矩阵乘法规模大
并行度高
适合更大的 batch
主要压力在算力和大块连续计算

所以 Prefill 很像一个“大 batch、大 GEMM、高吞吐”的任务。prompt 越长,这个阶段越重。长上下文场景里,TTFT 往往首先被 prefill 成本拉高。

这也是为什么很多推理系统会单独优化 chunked prefill、prefix caching、prompt batching、长上下文调度。它们表面上是在处理 prompt,实质上是在控制首 token 之前那一大段计算和缓存生成。

3. Decode 更像高频访存任务#

Decode 则完全不同。模型已经拿到了 prompt 的 KV Cache,接下来每次只生成一个 token 或一小步 token:

step 1: 生成 token_1
step 2: 生成 token_2
step 3: 生成 token_3
...

每一步的计算量不大,但每一步都要读取历史 KV Cache。上下文越长、并发 sequence 越多,decode 对显存带宽、KV Cache 访问和调度延迟越敏感。

它的特点更接近:

逐 token 生成
单步计算量小
频繁读取 KV Cache
memory bandwidth 压力大
对延迟抖动非常敏感

所以 Decode 不是一个“单次算很久”的任务,而是一个“小步、频繁、延迟敏感”的任务。它最怕中间被重活打断。一次 decode token 原本 20 ms,如果中间被一个长 prompt 的 prefill 插进来,用户看到的就可能是回答突然卡顿。

4. 不做 PD 分离时,矛盾会发生在哪里#

如果不做 PD 分离,常见结构是每个 worker 同时负责 prefill 和 decode:

Worker 1: Prefill + Decode
Worker 2: Prefill + Decode
Worker 3: Prefill + Decode
Worker 4: Prefill + Decode

这看起来简单,但问题在于两类任务喜欢的执行形态不同:

Prefill 喜欢大 batch、高吞吐、连续计算。
Decode 喜欢低延迟、低抖动、小步快跑。

两者放在同一组 GPU worker 里,会互相影响。

4.1 Prefill 会阻塞 Decode#

假设一个 worker 正在服务多个 decode 请求,每个请求都在稳定地产生 token。这时来了一个 32k prompt 请求。如果系统把这个长 prompt 的 prefill 插入同一个 worker,GPU 会花一段时间处理大输入。

结果可能变成:

原本:
token 1: 20 ms
token 2: 21 ms
token 3: 19 ms
 
插入长 prefill 后:
token 1: 20 ms
token 2: 180 ms
token 3: 24 ms
token 4: 160 ms

平均吞吐可能还行,但用户体感会变差,因为 decode 不再稳定。在线服务里,P99 ITL、TPOT 抖动和长尾延迟经常比平均值更重要。

4.2 Decode 也会打碎 Prefill#

反过来,如果一个 worker 上已经有很多活跃 decode sequence,每一步都要抢 GPU 资源,那么新的 prefill 请求也很难形成规整的大 batch。

这会导致:

Prefill batch 被打碎
大矩阵计算不够饱满
GPU 算力利用率下降
TTFT 上升

也就是说,混跑不是单向伤害。Prefill 会让 decode 抖,decode 也会让 prefill 不够高效。

5. PD 分离到底分离了什么#

PD 分离做的事情,就是把这两个阶段拆到不同资源池:

用户请求

Router

Prefill Pool
  - Worker P1
  - Worker P2
  - Worker P3
  ↓ 生成 / 查找 KV Cache
Decode Pool
  - Worker D1
  - Worker D2
  - Worker D3
  - Worker D4

逐 token 输出

更具体地说:

Prefill worker 负责处理输入 prompt,生成或复用 KV Cache。
Decode worker 负责消费 KV Cache,持续生成输出 token。

这时两个阶段之间真正流动的核心数据不再是普通请求对象,而是:

KV Cache

所以 PD 分离的关键问题会立刻变成:

Prefill 生成的 KV Cache,如何高效传给 Decode?

这也是 Mooncake、LMCache、vLLM disaggregated prefill、SGLang HiCache 这类系统共同关注的问题。PD 分离表面上是在拆计算阶段,深一层看,其实是在把 LLM serving 从 compute-centric 推向 KVCache-centric。

6. 它为什么能提高资源利用率#

Prefill 和 Decode 对硬件的压力不同:

阶段更主要的瓶颈workload 特征
Prefill算力大矩阵乘法、计算密集、适合 batch
Decode显存带宽 / KV 读取 / 调度延迟小步迭代、访存密集、延迟敏感

如果混在一起,GPU workload 会频繁在两种形态之间切换:一会儿大块 prefill,一会儿细碎 decode。调度器既要照顾新来的 prompt,又要照顾正在生成的 sequence,还要管理 KV block、continuous batching、prefix cache、preemption 等状态。

拆开之后,目标会清晰很多:

Prefill Pool:
  追求高吞吐
  合并长 prompt batch
  控制 TTFT
 
Decode Pool:
  追求低 TPOT
  保持 token 生成稳定
  管理活跃 sequence 和 KV 读取

这不是简单“多一层架构”,而是让每类 worker 的任务更单一。任务单一之后,调度策略、batch 策略、扩缩容策略和性能指标才更容易对齐。

7. 它为什么能分别优化 TTFT 和 TPOT#

LLM serving 里经常同时看两个指标:

TTFT = Time To First Token
TPOT = Time Per Output Token

不做 PD 分离时,TTFT 和 TPOT 很容易互相牵连。一个长 prompt prefill 会拉高正在 decode 请求的 TPOT;大量 decode 请求也会让新的 prefill 排队或 batch 不规整。

做 PD 分离之后,系统可以把优化目标拆开:

Prefill 集群优化 TTFT。
Decode 集群优化 TPOT / ITL。

这也会改变 benchmark 的设计方式。比如我会分别问:

Prefill only:
  16k prompt 下,TTFT <= 1s 时最大并发是多少?
 
Decode only:
  TPOT <= 30ms 时,最多能稳定服务多少活跃 sequence?

这比只看一个总 tokens/s 更接近线上服务的真实体验。一个系统可能总吞吐不错,但首 token 很慢;也可能首 token 很快,但输出过程一顿一顿。PD 分离的价值之一,就是让这些问题可以被分开测、分开调。

8. 独立扩缩容才是线上价值#

真实业务里的输入长度和输出长度并不对称。

有些请求是长输入、短输出:

输入 64k tokens
输出 100 tokens

这类请求主要压 Prefill。比如长文档问答、代码仓库分析、长上下文 RAG,系统大部分成本花在理解输入和生成 KV Cache 上。

另一些请求是短输入、长输出:

输入 1k tokens
输出 8k tokens

这类请求主要压 Decode。比如创作、长报告生成、持续对话,系统成本会在逐 token 生成阶段持续累积。

如果所有 worker 都是 Prefill + Decode 混合角色,就很难按业务形态精确分配 GPU。PD 分离后,资源池可以独立调整:

Prefill 节点数量:按输入长度、TTFT SLO、长 prompt 占比扩容。
Decode 节点数量:按输出长度、TPOT SLO、活跃 sequence 数扩容。

这就是资源池化的意义。它让推理服务不再只能粗糙地“多加几张卡”,而是可以问:到底是首 token 慢,还是生成过程慢;到底该扩 prefill,还是该扩 decode。

9. 最大代价:KV Cache 传输#

PD 分离不是免费优化。它最大的新成本就是 KV Cache 传输。

在混合 worker 里,prefill 和 decode 通常在同一个 worker 内连续发生。KV Cache 生成后可以留在本地,decode 直接用。

但在 PD 分离里:

Prefill worker 生成 KV Cache

跨 GPU / 跨 worker / 跨节点传输

Decode worker 消费 KV Cache

KV Cache 不是小对象。它的大小大致和下面这些因素成正比:

KV Cache 大小 ∝ 层数 × token 数 × KV head 数 × head dim × batch size

长上下文下,KV Cache 很容易进入 GB 级别。如果传输路径不够快,就会出现非常尴尬的情况:

Prefill 已经算完了。
Decode 还在等 KV。
TTFT 反而变高。

所以 PD 分离能不能真正带来收益,关键不只看 prefill 和 decode 是否拆开,还要看:

KV 传输速度
网络带宽和拓扑
RDMA / GPUDirect RDMA 能否用上
缓存命中率
KV block 管理
prefill/decode 负载比例
cache-aware scheduling

这也是为什么 Mooncake 会强调 KVCache-centric scheduler、Transfer Engine、KVCache Store;LMCache 会把 KV Cache 抽象成可以跨 engine、跨查询复用和移动的缓存层。PD 分离一旦落到工程实现,核心战场很快就从“怎么拆阶段”变成“怎么搬、怎么复用、怎么调度 KV”。

10. PD 分离和 Prefix Caching 的关系#

PD 分离和 prefix caching 不是同一个概念,但两者高度相关。

Prefix caching 解决的是:

相同 prefix 不要重复 prefill。

例如很多请求共享:

system prompt
长文档上下文
固定 agent 工具说明
多轮对话历史

如果这些 prefix 的 KV Cache 可以复用,系统就不必每次都重新做完整 prefill。

PD 分离解决的是:

Prefill 和 Decode 分别由不同资源池执行。

两者结合之后,系统形态会继续变化:

Prefill worker 不仅生成 KV,也查询已有 KV。
Decode worker 不仅消费本地 KV,也可以从远端获取 KV。
Router 不仅按负载调度,也按 KV 命中情况调度。

这时 KV Cache 就不只是一个 worker 内部的临时显存状态,而更像一个可以被命中、转移、复用和淘汰的数据资产。

11. 和 TP、PP、DP 有什么区别#

PD 分离很容易和传统分布式推理里的并行方式混在一起。它们都涉及多卡,但分的东西不同。

概念分的是什么目的
Tensor Parallel模型参数矩阵单层计算并行
Pipeline Parallel模型层跨层流水
Data Parallel请求或模型副本提高服务并发
Expert ParallelMoE expert分布专家计算
PD 分离推理阶段分离 prefill 和 decode 资源

所以 PD 分离更像 serving-level disaggregation,而不是模型内部并行。

它也可以和 TP、PP、DP 同时存在。例如:

Prefill Pool 内部使用 TP=4。
Decode Pool 内部也使用 TP=4。
Prefill 和 Decode 之间通过 KV transfer 通信。
外层再用多个副本做服务级扩容。

这样看就清楚了:TP/PP/EP 主要回答“一个模型怎么切到多卡上算”,PD 分离主要回答“在线请求的两个阶段应该由哪些资源池服务”。

12. 什么时候收益最大#

PD 分离在下面几类场景里更容易有明显收益。

12.1 长上下文#

例如:

8k / 16k / 32k / 64k / 128k prompt

上下文越长,prefill 越重,单独优化 prefill 的价值越高。长文档、长代码、长对话历史、复杂 agent prompt 都属于这类。

12.2 高并发在线服务#

高并发下,请求到达时间、prompt 长度和输出长度都不一致。混跑时,prefill 和 decode 的互相干扰会被放大。PD 分离可以让 decode pool 更稳定,从而改善 TPOT 和 P99 ITL。

12.3 输入输出长度差异大#

如果业务里同时存在长输入短输出、短输入长输出,统一 worker 很难同时照顾两种压力。PD 分离可以让 prefill 和 decode 按真实压力独立扩缩容。

12.4 Prefix cache 命中率高#

如果大量请求共享 system prompt、文档上下文或 agent 模板,那么 KV Cache 复用价值很高。PD 分离和 prefix caching 结合后,调度器可以围绕 KV 命中情况做更精细的路由。

12.5 多节点部署#

单机多卡也能做 PD 分离,但真正的系统价值通常在多节点集群中更明显。因为这时资源池化、跨节点 KV transfer、缓存层和调度器会共同决定服务上限。

13. 什么时候不一定值得做#

PD 分离不是万能方案。下面几类场景收益可能有限,甚至可能变差。

13.1 Prompt 很短#

如果输入只有几十到几百 tokens,prefill 本身不贵。拆开后增加的调度、路由和 KV 传输成本,可能超过 prefill/decode 分离带来的收益。

13.2 KV 传输太慢#

如果网络、拓扑或 KV transfer 实现不够好,就可能出现:

省下的 prefill 干扰 < 新增的 KV 传输成本

这时 PD 分离会把瓶颈从 GPU 计算转移到网络和缓存层。

13.3 请求之间几乎没有复用#

如果每个请求的 prompt 都完全不同,prefix cache 命中率很低,那么 KV-centric 架构的收益会下降。当然,PD 分离仍然可能通过降低干扰来改善稳定性,但缓存复用这一块价值就不明显。

13.4 系统规模很小#

单机单卡、小 batch、短 prompt benchmark 里,PD 分离往往只是增加复杂度。它更适合已经出现 prefill/decode 干扰、长尾延迟、资源比例失衡或 KV Cache 复用需求的服务。

14. 一个 4 卡例子#

假设有 4 张 GPU,不做 PD 分离时:

GPU0: Prefill + Decode
GPU1: Prefill + Decode
GPU2: Prefill + Decode
GPU3: Prefill + Decode

某一时刻来了一个 32k prompt 请求。这个请求会被放到某个 GPU 上做很重的 prefill。如果这张 GPU 上还有正在 decode 的请求,它们的 token 生成就会被拖慢。

做 PD 分离后,可以变成:

GPU0-GPU1: Prefill Pool
GPU2-GPU3: Decode Pool

32k prompt 先去 Prefill Pool 处理。生成 KV Cache 后,再传给 Decode Pool 继续逐 token 输出。

这个例子里,PD 分离的直接效果不是“prefill 本身一定更快”,而是 decode pool 不再直接被长 prefill 打断。它改善的是资源隔离、调度目标和输出稳定性。

15. 我最后怎么记 PD 分离#

如果只记一句话,我会这样记:

PD 分离的本质,是把 LLM 推理从“一个 worker 同时处理输入和输出”,改成“Prefill 专门处理输入,Decode 专门生成输出,中间用 KV Cache 连接”。

它的价值主要体现在五件事上:

  1. Prefill 和 Decode 的资源特征不同,混在一起会互相干扰。
  2. 拆开后,TTFT 和 TPOT 可以被分别测量、分别优化。
  3. Prefill Pool 和 Decode Pool 可以按业务压力独立扩缩容。
  4. KV Cache 可以从 worker 内部状态变成可复用、可调度的数据资产。
  5. 代价也很明确:KV Cache 传输、缓存管理和调度复杂度会成为新的系统瓶颈。

所以 PD 分离不是“拆开两个函数”这么简单。它代表的是 LLM serving 的一个架构转向:系统不再只围绕 GPU 算力组织请求,而是开始围绕 TTFT、TPOT、KV Cache、缓存命中率、资源池化和 SLO 来组织请求。

真正做工程判断时,我会先问:

现在的问题是 prefill 重,还是 decode 抖?
KV Cache 能不能高效传输和复用?
拆开之后节省的干扰,能不能覆盖新增的 KV transfer 成本?

如果这三个问题都有清晰答案,PD 分离才会从一个听起来漂亮的架构词,变成一个真正能改善线上服务的系统手段。

参考资料#

  1. Mooncake: A KVCache-centric Disaggregated Architecture for LLM Serving: https://arxiv.org/abs/2407.00079
  2. Efficient Memory Management for Large Language Model Serving with PagedAttention: https://arxiv.org/abs/2309.06180
  3. LMCache: An Efficient KV Cache Layer for Enterprise-Scale LLM Inference: https://arxiv.org/abs/2510.09665
  4. vLLM Documentation, Automatic Prefix Caching: https://docs.vllm.ai/en/latest/features/automatic_prefix_caching/
  5. vLLM Documentation, Disaggregated Prefilling: https://docs.vllm.ai/en/latest/features/disagg_prefill/