跳到主要内容

预填充-解码分离

预填充-解码分离(prefill-decode,PD)也称为分离式推理(disaggregated inference),是一种在独立硬件资源上运行 LLM 推理两个主要阶段(预填充与解码)的服务架构。"分离式预填充"和"分离式服务"往往指同一个核心思想:为每个阶段分配专用的资源,以满足其不同的计算和内存需求。

将预填充与解码放在一起的问题

要理解为什么共置会产生问题,先简单回顾一下 LLM 推理是如何工作的,它分两步:

  • 预填充(prefill):并行处理整个序列,并将注意力层的键向量和值向量存储在 KV 缓存中。由于它使用大型矩阵运算一次性处理所有 token,预填充受计算限制,但对 GPU 内存的要求并不高。
  • 解码(decode):通过复用之前构建的 KV 缓存,逐个生成输出 token。每个生成的 token 都需要反复加载模型权重并访问不断增长的 KV 缓存。因此,解码需要快速的内存访问,但计算需求较低。
从分词到解码再到输出的端到端 LLM 推理流程

长期以来,标准的推理方式是让这两个步骤一起运行。表面上这似乎很直接。

实际上,你经常会同时收到多个请求。每个请求都有自己的预填充和解码需求,但同一时间只能运行一个阶段。当 GPU 忙于计算密集的预填充任务时,解码任务必须等待,这会增加 ITL,反之亦然。

由于预填充主要决定 TTFT,解码影响 ITL,将它们共置会使其难以同时优化这两个指标。

将预填充和解码共置导致的延迟增加

将预填充和解码共置导致的延迟增加。图片来源

为什么分离是有意义的

PD 分离的思想很简单:把这两个截然不同的任务分开,让它们互不干扰。下面是一个示例架构:

预填充-解码分离架构:编排器将用户或智能体请求路由到计算密集型的预填充节点,一次性处理整个提示词并输出第一个 token,然后将 KV 缓存传输到内存带宽密集型的解码节点,由后者逐个生成剩余 token

主要好处包括:

  • 专用资源分配:预填充和解码可以在不同硬件上独立调度和扩缩容。例如,如果你的工作负载有大量提示词重叠(如多轮对话或智能体工作流),这意味着你的大部分 KV 缓存可以被复用。结果是预填充的计算需求降低,你可以把更多资源投入到解码上。
  • 并行执行:预填充和解码阶段不再相互干扰。你可以更高效地并行运行它们,这意味着更好的并发度和吞吐量。这种分离还可以改善尾部延迟。在共置情况下,一次单独的长预填充会拖慢其后的每一个飞行中的解码请求,从而提高 P95 和 P99 延迟。
  • 独立调优:你可以为预填充和解码实施不同的优化技术(如张量并行或流水线并行),以更好地达成你的 TTFT 和 ITL 目标。

一些开源框架和项目已经添加了对 PD 分离的支持,包括 SGLangvLLMDynamollm-d

分离并非总是灵丹妙药

尽管 PD 分离听起来很有前景,但它并非一刀切的解决方案。

  • 阈值很重要:如果你的工作负载太小,或你的 GPU 配置没有针对这种方法进行调优,性能可能会下降(在我们的测试中下降了 20-30%)。

  • 本地预填充可能更快:对于较短的提示词,或者当解码引擎具有较高的前缀缓存命中率时,在解码 worker 上本地运行预填充往往更快、更简单。

  • 数据传输成本:分离要求在预填充和解码 worker 之间快速、可靠地移动 KV 缓存。这意味着你的解决方案必须支持快速、低延迟的通信协议,并且这些协议既与硬件无关也与网络无关。除非分离带来的性能提升超过了数据传输成本,否则整体性能实际上可能会下降。供你参考的现有数据传输方法: NVIDIA Inference Xfer Library (NIXL)、CXL、NVMe-oF。

    对于生产环境,请考虑以下设计问题:

    • 解码 worker 应该直接从预填充 worker 获取 KV 块,还是双方都应该使用共享的缓存层?
    • KV 块应该在预填充之后立即移动,还是等到解码真正需要时才惰性移动?
    • 路由器如何决定是复用现有缓存、运行本地预填充,还是将请求发送到独立的预填充池?

    这些问题将 PD 分离与 KV 缓存卸载前缀缓存联系起来。请把它们视为同一服务架构的组成部分,而不是孤立的调优旋钮。

  • 缓存兼容性很重要:预填充 worker 和解码 worker 必须在 KV 布局、页大小、dtype、注意力变体以及任何额外的缓存元数据上保持一致。异构的 KV 类型(例如量化 KV 缓存、VLM 编码器状态和投机解码缓存)会使这种交接比移动一个标准全注意力 KV 张量更加复杂。

跨集群预填充-解码分离

在许多分离式系统中,预填充和解码机器是同一集群内的邻居,通过高速、低延迟的互连相连。

跨集群(或跨数据中心)PD 分离意味着你将工作流拆分,让预填充和解码阶段在完全独立的集群中运行,有时甚至位于不同的数据中心或区域。

为什么要在集群间拆分预填充和解码?

有两个主要驱动因素正推动基础设施走向这种分布式模式:

  • 合适芯片用在合适的任务上。预填充计算密集,而解码内存带宽密集。芯片设计正朝着匹配这些不同需求的方向分化:

    • NVIDIA Rubin CPX 旨在最大化预填充吞吐量。
    • Groq LPU 专为高速解码带宽而设计。

    问题在于这些芯片并不总是在同一个地方。它们常常按硬件类型分组部署在独立的集群中。如果你强制预填充和解码待在一起,就无法充分使用每个阶段的最佳硬件。将它们拆分,你可以让长预填充运行在计算优化的机器上,同时将解码保留在带宽优化的机器上。

  • 灵活性。在生产环境中,预填充和解码并不会均匀地扩展。流量和提示词长度会变化。前缀缓存命中率会变化。有时预填充成为瓶颈,有时解码成为瓶颈。

    如果两个阶段都被锁在同一个集群中,你就被固定在了固定的资源比例上。这可能导致一侧过度配置,另一侧出现瓶颈。将阶段拆分到不同集群,可以让你分别扩展每一侧。

核心问题:KV 缓存的移动

预填充完成后,KV 缓存必须被发送到解码集群。在集群内部,这很便宜。跨集群则可能很昂贵。有两件事让这件事变得棘手:

  • KV 缓存可能很大。通过网络传输它可能会抹掉更快预填充带来的任何收益。
  • 并非所有请求都能受益。短提示词或缓存命中提示词从远程预填充中获益不多,但你仍然要付出网络成本。

所以,如果你天真地把所有东西都发送到远程预填充集群,性能实际上可能会变差。

预填充即服务

这篇论文 将预填充即服务(Prefill-as-a-Service,PrfaaS)作为一种让这种配置可行的实用方式提出。其思想很简单:不要把所有东西都跨集群发送。要有选择性。

  • 将短请求或已缓存的请求留在本地
  • 只把长的、未缓存的预填充发送到远程计算集群

这使跨集群 PD 变成了一个路由问题。调度器根据提示词长度(尤其是未缓存的 token)、前缀缓存局部性、预填充队列压力、解码容量和可用网络带宽来决定每个请求的去向。

论文报告,这种选择性方法相对标准 PD 基线取得了显著收益:

  • 吞吐量提高 54%
  • P90 TTFT 降低 64%

这些收益来自更好的资源利用,而不只是更快的硬件。

何时应该使用跨集群 PD 分离?

这种部署选项在以下情况下效果最好:

  • 你的工作负载包含许多长的、未缓存的提示词
  • 你有一个理解缓存局部性、排队和带宽的调度器

如果你的大多数请求都很短或以缓存为主,通常把所有东西都放在一个集群里更简单、也更快。

其他资源