PD 分离详解:Prefill 与 Decode 的瓶颈、拆分与代价

为什么要 PD 分离

LLM 推理服务的性能瓶颈,不只在模型大小和 GPU 算力。副本增加后,请求落到哪个实例、长 Prompt 是否干扰流式输出、KV Cache 能否复用,都会直接影响延迟、吞吐和成本。

其中,Prefill/Decode 分离(PD 分离)是把两类截然不同的计算负载拆到独立资源池中的方案。本文从问题出发,说明它解决什么、代价是什么,以及何时不该使用它。

推理服务真的无状态吗?

先问大家一个问题:推理服务是无状态的吗

是,也不是。

只要客户端每次请求都带上完整的对话上下文,把请求调度到哪个后端服务,通常都不会影响推理的语义正确性。模型看到的上下文是完整的。

至于采样带来的输出差异,和请求落在哪个副本上是两回事。

但平台看到的是另一层状态。每个实例都维护着自己的请求队列、Prefix Cache 和 KV Cache。随机或者轮询不会破坏结果的正确性,却可能让请求反复落到没有对应缓存的实例上,造成 Cache Miss、重复 Prefill 和更多 GPU 计算,最终表现为更高的 TTFT、更低的吞吐和更高的资源成本。

同一段系统提示词、长文档或者多轮对话前缀,可能已经在实例 A 上完成 Prefill,并进入 Prefix Cache。下一次请求如果仍然落到实例 A,就有机会复用计算结果;如果被分给实例 B,实例 A 上的缓存没有损坏或失效,只是实例 B 无法使用,只能重新执行 Prefill。

即:LLM 推理服务在用户层面是无状态的,但在运行时层面却不是。

如果只关心接口是否可用、推理结果是否正确,可以把 LLM 推理服务看成无状态服务;但只要开始关注延迟、吞吐和 GPU 成本,就不能再忽略实例上的运行时状态。

普通负载均衡不懂 LLM 的运行时状态。它可能知道哪些实例 Ready,可以完成基础的流量分配,却不知道哪个实例的队列已经堆满、KV Cache 还剩多少,也不知道当前请求的前缀曾经在哪个实例上计算过。

所以,多副本 LLM 服务需要的不只是负载均衡,而是能够感知负载和缓存局部性的智能路由

为什么要拆分 Prefill 和 Decode?

Prefill 与 Decode 的计算特征不同

一次 LLM 推理请求可以粗略拆成 Prefill 和 Decode 两个阶段:

  • Prefill 阶段处理输入 Prompt,并生成后续 Decode 所需的 KV Cache。Prompt 越长,需要完成的计算越多,因此这一阶段通常是 compute-bound,主要受 GPU 计算能力限制。

  • Decode 利用已有 KV Cache,一次生成一个 Token。生成过程中,Attention 需要持续读取此前 Token 对应的 KV Cache;上下文越长,需要读取的历史 KV 越多,因此这一阶段通常是 memory-bandwidth-bound,更依赖显存带宽与 KV Cache 容量。

性能指标与资源差异

两个阶段对应的性能指标也不同:

  • Prefill 侧主要关注 TTFT(Time To First Token):也就是从请求发出到收到第一个 Token 的时间。它不仅包含 Prefill 计算,还包括此前的排队和路由等开销。

  • Decode 侧主要关注 TPOT(Time Per Output Token)ITL(Inter-Token Latency)

    • TPOT 表示第一个 Token 之后,平均生成一个输出 Token 所需的时间;
    • ITL 表示相邻两个输出 Token 之间的时间,更能反映流式输出是否稳定、有没有明显抖动。

在 Long Context 场景下,Prefill 和 Decode 对计算、显存带宽与容量的需求更加不平衡。

如果把它们拆开,Prefill Pool 可以偏向计算能力更强的 GPU,Decode Pool 则可以偏向显存带宽更高、容量更大的 GPU

两边的 Batch 策略也不同。Prefill 的 Batch 增大到一定程度后,计算单元会逐渐饱和,继续增加 Batch 对吞吐的提升趋缓,还可能推高 TTFT,因此通常需要控制 Batch Size。Decode 每一步只为每条活跃序列生成一个 Token,适当增大 Batch 可以提高 GPU 利用率与整体输出吞吐,但仍然要受 TPOT、ITL 和 KV Cache 容量约束。

不同 Batch Size 和输入长度下 Prefill、Decode 两个阶段的吞吐变化

图源:《DistServe: Disaggregating Prefill and Decoding for Goodput-Optimized Large Language Model Serving》,Figure 3。展示了模型在不同 Batch Size 与输入长度下的测试结果。具体数值取决于模型、硬件和推理引擎,这里主要观察两条趋势:Prefill 吞吐较早趋于平缓,而 Decode 吞吐可以随 Batch Size 增大继续提升。

在同一设备上同时运行 Prefill 和 Decode,意味着这颗芯片必须同时具备强大的算力(满足 Prefill 需求)巨大的显存容量/带宽(满足 Decode 的 KV Cache 需求)

然而,受限于芯片面积、功耗、良率和成本,芯片设计需要在"计算单元"和"显存接口/HBM 容量"之间做面积与功耗的 trade-off。因此,一颗"通吃"两颗阶段需求的芯片必然成本高昂,且在任一单一阶段都难以达到最优性价比。

PD 分离的本质,就是承认这种硬件层面的 trade-off,让 Prefill 实例和 Decode 实例各自采用最适合自己工作负载特征的硬件配置(如 Prefill 用高算力卡,Decode 用高 HBM 容量卡),从而在系统层面实现总体拥有成本(TCO)的最优

共置部署为什么会互相干扰

即使每次都选对了实例,也只解决了路由问题。Prefill 和 Decode 如果继续放在同一个实例里,就会共享 GPU 和调度队列。长 Prompt 带来的 Prefill 峰值,可能抬高其他请求的 Token 间延迟。

这就是执行层的问题:怎样减少 Prefill 和 Decode 共享 GPU、共享调度队列带来的相互干扰。对应的解决思路,就是进一步把 Prefill 和 Decode 拆到不同实例,也就是 PD 分离。

PD 分离到底拆了什么?

PD 分离(Prefill/Decode Disaggregation) 将 LLM 推理的两个阶段解耦,部署到独立的 Prefill 实例组和 Decode 实例组:

  • Prefill 实例组接收完整输入 Prompt,构建本次请求所需的 KV Cache,并生成首个输出 Token;
  • Decode 实例组通过高速网络获取 Prefill 产出的 KV Cache,以首 Token 为起点进行自回归解码,逐 Token 生成直至请求结束;

两个阶段通过高效的 KV Cache 传输机制衔接,并可以独立扩缩容,从而在合适的资源配比和传输网络下,同时优化吞吐量与延迟。

聚合式推理与 PD 分离对比

这里拆分的是执行位置和资源池,不是模型的计算顺序。一次请求仍然要先完成 Prefill,才能进入 Decode,只是两个阶段不再运行在同一个实例里。

因此,一次请求会像接力一样经过两组实例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
请求进入
Prefill Pool:处理 Prompt,生成 KV Cache 和首 Token
Decode Pool:取得 KV Cache,继续生成后续 Token
返回响应

拆到这里,一个新的问题也随之出现:Prefill 和 Decode 不在同一个实例,KV Cache 怎么交接?

KV Cache 可以理解为模型处理 Prompt 后留下的中间状态。Decode 要从 Prefill 结束的位置继续生成,就必须获得本次请求对应的 KV Cache(否则就需要重新计算)。

聚合式推理中,两个阶段在同一个实例内运行,Decode 可以直接继续使用 Prefill 已经构建的 KV Cache。PD 分离后,两个阶段位于不同实例,各自管理本地显存,因此必须增加一次显式的交接:

  1. Prefill 完成计算,将 KV Cache 保留在本地,同时向请求编排组件返回 KV Transfer 元数据。
  2. 编排组件携带这些元数据发起 Decode 请求。
  3. Decode 根据元数据,通过 NIXL 等 KV Connector 从 Prefill 实例获取 KV Cache,然后继续逐 Token 生成。

这里有两条不同的链路:编排组件传递的是如何取得 KV Cache 的元数据;真正的 KV Cache 数据不经过 Router / Proxy。至于数据是在 Prefill 和 Decode 之间 P2P 传输,还是经由共享存储或缓存服务交接,则取决于具体使用的 Connector。

这也不等于 Prefix Cache 命中。

  • Prefix Cache 复用解决的是 Prefill 能否利用以前计算过的相同前缀,从而少做一部分计算;
  • KV Cache 传输解决的是本次 Prefill 的结果如何交给 Decode。即使没有命中 Prefix Cache,Prefill 也可以完整处理 Prompt,再把新生成的 KV Cache 传给 Decode。

KV Cache 共享方式有哪些?

在 vLLM 中,KV Cache 的交接由 KV Connector 抽象。它让作为 KV producer 的 Prefill 实例,将缓存交给作为 KV consumer 的 Decode 实例;调度器侧的 Connector 负责安排传输,Worker 侧的 Connector 负责实际读写数据。

不同 Connector 的差异,主要在于 KV Cache 放在哪里,以及 Prefill 和 Decode 之间通过什么方式传输:

  • P2P 传输NixlConnector 通过 NIXL 在实例之间传输 KV Cache,适合高速网络和异步收发场景。
  • 共享存储或缓存服务ExampleConnector 使用本地共享目录,主要用于验证 Connector 接口和 PD 分离流程;LMCacheConnectorV1 由 LMCache 管理 KV Cache,可使用 NIXL 作为底层传输,也支持独立的 LMCache Server;MooncakeConnectorFlexKVConnectorV1 则分别使用 Mooncake 和 FlexKV 作为 KV Cache 的传输、管理或分布式存储层。
  • 卸载、多级缓存与特定硬件OffloadingConnector 将 KV Cache 转移到 CPU 内存或文件系统等更低层级的存储中;MultiConnector 按顺序组合多个 Connector,形成多级 KV Cache 路径;MoRIIOConnector 面向 ROCm 环境。

vLLM 通过 KVTransferConfig 选择 Connector,并传递具体实现需要的参数。例如 NIXL Connector 的配置可以写成:

1
2
--kv-transfer-config \
'{"kv_connector":"NixlConnector","kv_role":"kv_producer"}'

Connector 解决的是 KV Cache 如何被生产、保存和消费,不负责决定请求应该选择哪个 Prefill 或 Decode 实例。后者属于路由和请求编排问题,两者可以组合使用,但不是同一层能力。

PD 分离的收益与代价

PD 分离之后,最直接的收益是隔离两种计算负载。 vLLM 官方文档特别强调了 tail ITL:

  • 聚合模式会在 Decode 过程中插入 Prefill 工作,导致部分 Token 的生成间隔突然升高;
  • PD 分离以后,长 Prompt 留在 Prefill Pool,不再直接占用 Decode Pool 的调度队列,逐 Token 输出会更稳定。

Prefill 与 Decode 还可以独立设置副本数、GPU 类型和并行策略

  • 文档问答通常输入很长、输出较短,可能需要更多 Prefill 能力;
  • 聊天或推理场景输出很长,则可能更需要 Decode 能力。 通常可以用 xP:yD 表示两组实例的比例,具体取值要根据真实的输入长度、输出长度、并发和延迟目标压测出来。

PD 分离也不是减少阶段干扰的唯一方案。

vLLM 默认支持 Chunked Prefill,它会把较长的 Prefill 拆成较小的 Chunk,优先调度 Decode,再利用剩余的 Token Budget 插入 Prefill。这样可以在同一实例中平衡 compute-bound 的 Prefill 和 memory-bound 的 Decode,部署与调度也更简单; 但两个阶段仍然共享 GPU,max_num_batched_tokens 的取值也需要在 TTFT、ITL 和吞吐之间权衡。只有当这种共置优化仍无法同时满足两个阶段的 SLO,或者两边需要完全不同的资源配置和扩缩容策略时,PD 分离的价值才会更加明显。

当然,PD 分离也不是银弹

  • 模型权重要在 Prefill 和 Decode 两组 GPU 上分别加载
  • 一次请求多了 Endpoint 选择和阶段编排
  • KV Cache 还要跨实例传输
  • 还需要额外考虑 NIXL、RDMA、故障恢复和两组工作负载的独立扩缩容。

如果模型很小、Prompt 很短,或者网络只能走普通 TCP,这些额外开销可能超过隔离带来的收益。

因此,单 GPU vLLM 不会因为换成 PD 架构就自动变快,vLLM 也明确提醒,Disaggregated Prefilling 本身并不保证提高吞吐。 只有当 Prefill 对 Decode 的干扰已经影响 TTFT 或 ITL,或者两边的资源需求明显不对称时,PD 分离才值得引入

小结

PD 分离的核心原因,是 Prefill 和 Decode 的瓶颈并不相同:

  • Prefill 通常是 compute-bound,更依赖 GPU 算力;
  • Decode 则通常是 memory-bandwidth-bound,更依赖显存带宽和 KV Cache 容量。

把两个阶段放在同一个实例里,就要求同一套硬件同时满足两种不同的资源需求。

这不表示不存在同时拥有强算力和大 HBM 的高端芯片。问题在于,增加计算单元不一定能改善 Decode,堆叠更大的 HBM 和更宽的显存接口又会带来额外的面积、功耗和成本。硬件本身就在算力与内存系统之间做取舍,不存在能在所有输入输出比例、延迟目标和成本约束下都对两个阶段最优的芯片配置。

因此,PD 分离不是把一次推理变成两次推理,而是把同一次推理中的 Prefill 与 Decode 放到各自合适的资源池:Prefill 可以使用偏算力的硬件,Decode 可以使用偏显存容量和带宽的硬件,再通过 KV Cache 交接把两个阶段衔接起来。

当然,PD 分离不是银弹,这套架构也需要额外的调度、网络和容量规划。先用 Chunked Prefill 等共置优化解决问题;只有 TTFT、ITL 或资源配比已经成为明确瓶颈时,再引入 PD 分离。

0%