Skip to content

约 6 分钟阅读

17. P/D 分离:读题和答题放在不同的机器上 ​

第 11 篇用分块预填充缓解了一个矛盾:长输入的 Prefill 会打断正在进行的 Decode。分块是在同一台机器上让两者交错进行,干扰变小了,但没有消失。这一篇讲一种更彻底的做法:P/D 分离(Prefill/Decode disaggregation),让 Prefill 和 Decode 运行在不同的 GPU 上,中间把 KV Cache 传过去。读完你应该能说清它为什么能提高 Goodput,KV 传输要花多少时间,以及什么规模、什么负载下值得这样做。

两个阶段要的东西不一样 ​

第 6 篇已经说明,Prefill 和 Decode 是两种性质完全不同的计算:

PrefillDecode
瓶颈算力显存带宽
决定的指标TTFTTPOT
理想的批token 数够多就能用满算力,不需要很多请求请求越多越好,摊薄权重搬运
理想的并行方式更多卡做 TP,缩短单次长 Prefill适中的 TP,更大的批

放在同一组 GPU 上,这两种工作只能共用一套配置,而且会互相干扰:一个长 Prefill 让所有人的出字停顿,大量 Decode 又占着显存和批的名额,让新请求的 Prefill 排队。为了同时满足 TTFT 和 TPOT 两个目标,往往只能多准备一些机器、让每台都不太忙。

把两个阶段拆开 ​

P/D 分离把 GPU 分成两个池子:

路由、Prefill 池、KV 传输、Decode 池以及失败时的清理路径

  1. 请求先到 Prefill 实例,处理完整个输入,生成第一个 token,同时得到这个请求的 KV Cache。
  2. KV Cache 通过高速网络传到 Decode 实例。
  3. Decode 实例接着逐个生成后续 token,直到结束。

这样做的好处:

  • 没有干扰。 Decode 实例上永远不会出现长 Prefill,出字节奏稳定,TPOT 的尾延迟大幅改善。
  • 分别调优。 Prefill 池可以用更大的 TP 压低 TTFT;Decode 池可以用更大的批提高吞吐。两边甚至可以用不同型号的 GPU:Prefill 需要算力,Decode 需要显存容量和带宽。
  • 分别扩容。 输入很长、输出很短的业务(比如资料问答)需要更多 Prefill 实例;输入短、输出长的业务(比如写作)需要更多 Decode 实例。两个池子的比例可以按业务调。

学术界的三个代表工作 ​

DistServe(OSDI 2024)系统地提出了这个思路,并把优化目标定为 Goodput:在满足 SLO(比如 90% 的请求同时满足 TTFT 和 TPOT 目标)的前提下,每张 GPU 能承载的最大请求速率。论文报告,相比当时的系统,它能在满足延迟约束的情况下多服务 7.4 倍的请求,或者把 SLO 收紧 12.6 倍。第 5 篇的 Goodput 概念,就是从这类工作中流行起来的。

Splitwise(ISCA 2024,微软)强调硬件上的分工:Decode 对算力需求低,可以放在更便宜、功耗更低的 GPU 上。论文报告在成本降低 20% 的情况下吞吐提高 1.4 倍,或者在相同成本和功耗下吞吐提高 2.35 倍。

Mooncake(FAST 2025 最佳论文,月之暗面 Kimi 的服务架构)把 KV Cache 放在架构的中心:除了 P/D 分离,它还利用 GPU 集群里闲置的 CPU 内存和 SSD 构建了一个全局的 KV 缓存池,让前缀缓存可以跨机器共享(第 10 篇的分层缓存)。负载过高时,它会预测请求能否按时完成,提前拒绝无法满足 SLO 的请求。论文的会议版本报告,在 Kimi 的生产环境中,A800 和 H800 集群分别多处理了 115% 和 107% 的请求。

这些数字都来自各自论文的实验环境和负载,不能直接套到其他系统上,但它们说明了这个方向的收益量级。

代价:KV 要搬过去 ​

P/D 分离多出来的核心成本,是把 KV Cache 从 Prefill 实例传到 Decode 实例。算一笔账:Llama-3.1-8B 处理 8192 个 token 的输入,KV 是 1 GiB。

数据中心里常用 RDMA 网络传输这类大块数据:网卡直接读写对方机器的内存,不经过操作系统的网络协议栈,延迟低、带宽高。

text
400 Gb/s 的 RDMA 网卡 = 50 GB/s       → 传输下界约 21 毫秒
200 Gb/s                = 25 GB/s       → 约 43 毫秒
普通 25 Gb/s 以太网    ≈ 3 GB/s        → 约 350 毫秒

注意单位:网卡带宽通常以比特每秒标注,公式里要换算成字节每秒,差 8 倍。

这些时间会直接加到 TTFT 上。在高速 RDMA 网络上,几十毫秒换来 Decode 的稳定,通常是划算的;在普通网络上,传输可能比 Prefill 本身还慢,就不值得了。

几种降低传输成本的办法:

  • 逐层传输。 Prefill 每算完一层,就把这一层的 KV 发出去,传输和计算重叠,而不是全部算完再传。
  • 减少 KV 体积。 用 GQA、MLA 的模型,或者把 KV 量化成 FP8,要传的数据成倍减少。
  • 专门的传输库。 用支持 GPU 直接通过网卡收发数据(不经过 CPU 内存)的库,比如 NVIDIA 的 NIXL、Mooncake 的传输引擎。

传过去的也不只是 KV 字节。Decode 实例需要知道这个请求的位置、块的布局、模型配置,两边的 KV 格式和分页大小要兼容。

失败了怎么办 ​

分离部署多了一个可能出错的环节。KV 传到一半网络超时、目标 Decode 实例挂掉时,系统要做到:

  • 两边都释放资源。 Prefill 实例上暂存的 KV 和 Decode 实例上预留的块都要清理,否则会慢慢泄漏显存。
  • 由上层决定重试还是返回错误。 请求可以整个重新调度,但不能让用户已经收到的第一个 token 和重新生成的内容拼接在一起。
  • 不要假设半截 KV 能恢复。 传输不完整的 KV 应该丢弃,重新 Prefill。

这些逻辑通常由路由层或编排层负责,是 P/D 分离在工程上的主要复杂度。

引擎中的实现 ​

主流引擎都已经支持 P/D 分离,配置方式各不相同:

  • vLLM 通过 --kv-transfer-config 指定 KV 连接器(connector)和本实例的角色(生产 KV 的 Prefill,或消费 KV 的 Decode)。连接器有多种实现,比如基于 NIXL 的、基于 LMCache 的、基于 Mooncake 的。
  • SGLang 通过 --disaggregation-mode prefill 或 decode 启动两类实例,传输后端默认使用 Mooncake,也支持 NIXL。

两类实例之上还需要一个路由层:把新请求发往某个 Prefill 实例,Prefill 完成后选择一个 Decode 实例接手。NVIDIA Dynamo 等分布式推理框架提供了这类编排能力。

什么时候值得用 ​

P/D 分离不是越早用越好。它增加了一倍的部署角色、一条网络传输路径和一套故障处理逻辑。

适合的情况:

  • 规模足够大,有几十张以上的 GPU,两个池子都能保持一定的利用率。
  • 输入普遍很长(长文档、长对话、Agent),Prefill 干扰明显。
  • 对 TPOT 的尾延迟有严格要求。
  • 有 RDMA 级别的高速网络。

不适合的情况:

  • 只有几张卡。分成两个池子后,每个池子都很小,很难均衡负载。
  • 输入很短。Prefill 本来就快,干扰不明显,分块预填充已经足够。
  • 网络很慢。传输时间吃掉收益。

判断是否值得,最终要用真实的混合负载,对比合并部署(开启分块预填充)和分离部署的 Goodput、TTFT 与 TPOT 的分位数,以及每百万 token 的成本。

Prefill 计算受限、决定 TTFT,Decode 访存受限、决定 TPOT,放在一起会互相干扰、只能共用一套配置。P/D 分离把它们放在不同的 GPU 池里,各自调优、各自扩容,以 Goodput 为目标;代价是 KV 的网络传输和更复杂的故障处理。它适合大规模、长输入、有高速网络的部署;小规模部署用分块预填充通常就够了。

到这里,推理优化的主要技术都讲完了。下一篇回到软件选择,看 vLLM 与 SGLang 怎样把这些技术组织成一个完整的引擎,以及怎样在它们和其他方案之间做选择。

别急,先让缓存热一下。