Skip to content

PD 分离 ​

Prefill-Decode Disaggregation(PD 分离)把大模型推理的 Prefill 和 Decode 阶段放到不同的 Worker 池中:Prefill Worker 处理 Prompt 并生成 KV Cache,Decode Worker 接收 KV Cache 后持续生成 token。

它解决的核心问题是阶段干扰。Prefill 和 Decode 虽然执行同一模型,但负载形态、延迟目标和最佳资源配置并不相同;让两者长期共享同一批 GPU,会迫使调度器在 TTFT 与 TPOT 之间反复折中。

本文以 SGLang 的实现为例,说明 PD 分离的通用架构和执行过程。SGLang 的 Router、Bootstrap Room 和 P/D 队列是具体实现,背后的阶段隔离、KV Handoff 与状态接管则是 PD 分离的共性。

Prefill 与 Decode 为什么需要隔离 ​

对比项PrefillDecode
输入形态一次处理多个 Prompt token每轮处理一个或少量新 token
计算特征大矩阵计算,通常偏计算密集反复读取权重和 KV,通常偏访存密集
主要延迟指标TTFTTPOT / ITL
资源压力Prompt 长度、Prefill Batch并发序列数、输出长度、KV 容量

在聚合式引擎中,Prefill 与 Decode 共用 Scheduler、Batch 和 GPU。一个长 Prompt 加入执行时,可能让正在生成的请求长时间得不到下一轮 Decode,直接抬高尾部 TPOT;如果通过缩小 Prefill Chunk 保护 Decode,又会增加 Prefill 调度轮数,恶化 TTFT。

SGLang 还指出了 DP Attention 下的同步问题:不同 DP Rank 同时处理 Prefill 和 Decode 时,阶段耗时不一致会让 Decode Rank 等待较慢的 Prefill Rank。PD 分离后,同一个资源池内的请求处于相近阶段,Batch 形态更加稳定。

PD 分离改变了什么 ​

PD 分离不是减少模型计算量,而是重新组织请求和状态:

  1. 资源分离:Prefill 和 Decode 使用独立 Worker 池,可以分别扩缩容和选择并行策略。
  2. 请求拆分:Router 为一次生成选择一个 P Worker 和一个 D Worker,并把两侧请求关联起来。
  3. 状态迁移:Prompt 产生的 KV Cache 必须从 P 移交给 D。
  4. 执行接管:D 收到 KV 后,需要恢复请求状态并继续生成,Client 只看到一条连续响应。

其中,KV Handoff 是最关键的新增路径。忽略 Page Padding 和额外状态,标准 MHA/GQA 模型的 KV Cache 大小约为:

SKV=2LTHKVdhb

L 是层数,T 是 Prompt token 数,HKV 是 KV Head 数,dh 是 Head Dimension,b 是每个元素的字节数;系数 2 对应 Key 和 Value。其传输时间可近似表示为:

TKV≈SKVBWeff+Tsetup

因此 PD 分离后的 TTFT 中会增加 KV 传输和 D 侧接入开销:

TTFT≈QP+TP+TKV+QD+Tfirst

PD 的收益来自隔离和独立调度,代价来自 KV 迁移。只有前者大于后者时,分离才有实际价值。

整体架构:以 SGLang 为例 ​

PD 分离架构与请求状态流转,以 SGLang 为例

一套完整的 PD 系统通常包含 Router、P Worker、D Worker、协调通道和 KV 数据通道。SGLang 对这些角色的实现如下:

通用角色SGLang 实现职责
RouterSGLang Model Gateway分别选择 P/D Worker,并把一次请求同时交给两侧
请求关联bootstrap_room标识同一次 P→D 状态传输
协调通道P 侧 Bootstrap Server交换并行拓扑、Rank、目标 KV Page 和传输状态
Prefill 状态机PrefillBootstrapQueue等待 D 准备完成,执行 Prefill 并发送 KV
Decode 状态机DecodePreallocQueue、DecodeTransferQueue预留空间、接收 KV,并把请求交给 Decode Batch
KV 数据通道Mooncake / NIXL在 P/D 显存之间搬运 KV Cache

这里需要区分两条通路:

  • 控制面传递 Room ID、并行拓扑、KV Page 索引、内存地址和完成状态,数据量很小。
  • 数据面传输所有层的 KV Cache,数据量大,应直接走 GPU P2P、RDMA 等高速路径,而不是经过 HTTP Router。

PD 分离并不规定唯一的握手顺序。SGLang 采用的是 D 侧先准备目标空间、P 侧随后 Push KV 的方式,下面以这条链路展开。

一次请求的执行过程 ​

一次 Client 请求在 Gateway 中拆成关联的 P/D 请求,按“D 预留空间 → P 生成状态 → D 接管生成”执行。

1. Gateway 选择 P/D Worker ​

  1. Gateway 接收 Client 请求,保持 Client 侧 HTTP 连接。
  2. 根据模型从 Worker Registry 中取出 Prefill Pool 和 Decode Pool,排除不健康或 Circuit Breaker 已打开的 Worker。
  3. 分别运行 Prefill Policy 和 Decode Policy。P 侧可按 Prompt 负载或 Prefix Cache 选择,D 侧可按活跃序列数或 token 负载选择。
  4. 将选中的 P Worker 和 D Worker 组成仅对本次请求有效的逻辑 Pair。

2. Gateway 并发发送 P/D 请求 ​

  1. Gateway 生成唯一的 bootstrap_room,用于关联同一次 P→D 状态传输。
  2. Gateway 生成 P/D 两份上游请求,将 Room ID、P 的 Bootstrap 地址和生成参数写入请求。
  3. Gateway 并发请求 P 和 D,使 D 侧握手和内存预留可以提前开始。
  4. P 侧响应用于报告 Prefill 和传输结果;D 侧 HTTP 请求保持等待,后续返回生成流。

3. D 准备目标空间,P 执行 Prefill ​

  1. D 将请求放入 Prealloc Queue,初始化 KV Receiver。
  2. D 通过 P 侧 Bootstrap Server 交换 TP、CP、DP、PP 拓扑、Rank 映射和传输端点。
  3. D 执行容量准入,分配请求槽位、Metadata Buffer、Prompt KV Page 以及 Decode 预留空间。若启用 D 侧 Prefix Cache,则先匹配本地 Prefix,只为缺失部分分配 Page。
  4. D 将 Prefix 命中长度、目标 Page 索引和传输地址发给 P,然后进入 Transfer Queue。
  5. P 在 Bootstrap Queue 确认 D 就绪后进入 Prefill Waiting Queue,与其他 Prompt 组成 Prefill Batch。
  6. P 复用本地 Prefix Cache,计算未命中的 Prompt,生成各层 KV 和第一个输出 token。

4. P 向 D 移交 KV 与元数据 ​

  1. P 根据 D 提供的 Page 索引,确定待发送 KV 与目标 Page 的对应关系。
  2. P 通过 Mooncake 或 NIXL 将 KV 直接写入 D 的显存,数据不经过 Gateway。P/D Attention TP 不同时,该过程还包含 Gather、Layout Remap 或 Staging Buffer。
  3. P 将首 token、Prefix 命中长度、Logprob 和 bootstrap_room 等小型状态写入 D 预留的 Metadata Buffer。
  4. 启用 Chunked Prefill 时,P 按 Chunk 增量发送 KV,最后一个 Chunk 提交完整元数据。
  5. P 将请求放入 Inflight Queue,轮询传输状态;确认 D 接收完成后,再释放本地 KV 和 Metadata Buffer。

5. D 校验状态并接管 Decode ​

  1. D 在 Transfer Queue 轮询 KV Receiver,确认所有相关 Rank 都已完成 KV 和元数据传输。
  2. D 校验 Metadata Buffer 中的 bootstrap_room,确认收到的状态属于当前请求。
  3. D 将首 token、Prefix 命中长度和 Logprob 提交到本地请求对象,建立 Prompt token 到 KV Page 的索引。
  4. D 构造 Prebuilt Extend Batch,只补齐 Scheduler 所需的 Batch 和索引元数据,不重复执行 Prompt Forward。
  5. 请求进入 Waiting Queue,再合并到 Running Batch;D 复用已接收的 KV,从 P 产生的首 token 继续逐 token Decode。

6. Gateway 代理响应并回收资源 ​

  1. D 将首 token 和后续 token 写入与 Gateway 建立的上游 Response Stream。
  2. Gateway 将 D 的响应流代理到 Client 原有的 SSE 或 Chunked HTTP 连接。
  3. 正常结束时,D 释放请求槽位和不再保留的 KV Page,Gateway 结束 Client 响应和本次 P/D 关联。
  4. P 失败时,Gateway 取消正在等待 KV 的 D;Client 断开或传输超时时,对应组件取消请求并回收 Page、Buffer 和 Room 状态。

三个组件的状态流可以概括为:

text
Gateway: Client Request -> Select P/D -> Dual Dispatch -> Proxy D Response
Prefill: BootstrapQueue -> WaitingQueue -> Prefill Forward -> Inflight Transfer -> Finish
Decode : PreallocQueue   -> TransferQueue -> Prebuilt Batch  -> Running Batch      -> Stream

收益与代价 ​

维度收益代价
调度Prefill 不再直接打断 DecodeRouter 需要同时管理 P/D 两个请求
延迟TTFT、TPOT 可以分别优化TTFT 增加 KV 传输与接入时间
资源P/D 可以独立扩缩容、采用不同并行度两侧都要加载模型权重
稳定性Decode Batch 更稳定,尾部 TPOT 更可控P、D、传输任一环节失败都要联合清理
缓存P 池可专注处理 Prompt 和 PrefixP 侧命中的 Prefix KV 仍可能需要传到 D

PD 分离更适合长 Prompt、高并发、Prefill 经常破坏尾部 TPOT,并且 P/D 最佳资源配比差异明显的场景。对于短 Prompt、低并发或网络带宽不足的服务,KV Handoff 可能比阶段干扰更昂贵,聚合部署反而更简单。

源码入口 ​

SGLang 的实现把 Router、调度状态机和传输 Backend 分开,关键入口如下:

模块源码
P/D 请求选择与并发转发sgl-model-gateway/src/routers/http/pd_router.rs
Bootstrap 服务与 Rank 映射srt/managers/disagg_service.py、disaggregation/common/conn.py
Prefill 握手与 KV 发送disaggregation/prefill.py
Decode 预分配、接收与接管disaggregation/decode.py
Mooncake / NIXL Backend 映射disaggregation/utils.py

这些实现共同体现了 PD 分离的核心边界:Router 关联请求,Scheduler 管理阶段状态,Transfer Backend 移动 KV,Decode 在数据完整后接管生成。

总结 ​

PD 分离本质上是阶段专用调度 + KV 状态迁移。它通过独立的 P/D 资源池消除阶段干扰,让 TTFT 和 TPOT 可以分别优化;同时也引入了 KV 传输、目标内存预分配、请求状态接管和跨组件故障处理。

以 SGLang 为例,一次请求由 bootstrap_room 关联,Router 并发发给 P/D,D 先预留 KV Page,P 再将 KV 直接写入 D,最后 D 进入正常 Decode 调度。这条链路是理解 PD 分离机制最直接的方式。

参考 ​

  1. DistServe:Disaggregating Prefill and Decoding for Goodput-optimized Large Language Model Serving
  2. SGLang:PD Disaggregation
  3. SGLang:HTTP PD Router
  4. SGLang:Prefill 与 Decode 状态机