分离Prefill/Decode:避免长Prompt阻塞所有Token
将LLM serving的prefill和decode拆到不同资源池,消除头阻塞,p99延迟从88ms降至30ms(降66%)。
将LLM serving的prefill和decode拆到不同资源池,消除头阻塞,p99延迟从88ms降至30ms(降66%)。
一个 LLM 请求本质上是两项工作穿在同一件外套里——一个是重的、突发式的 prefill,另一个是一连串微小的、对延迟敏感的 decode。把它们跑在同一个引擎上,一个大 prompt 就会让所有人的 token 卡住。拆开就好了。
TL;DR:每个 LLM 请求实际上是两个完全不同的工作。Prefill 读取整个 prompt——重的、突发式的,长上下文时很慢。Decode 然后一个 token 一个 token 地输出——很小,但对延迟敏感。把它们跑在同一个引擎上,一个大 prefill 就会跳到所有人的 decode 前面:队头阻塞,token 流开始抖动。Prefill/decode 分离把 prefill 和 decode 放到不同的池子里,这样 decode 永远不会排在一个 prefill 后面。在一个可运行的 Go 模拟中,拆分池子让 p99 token 间延迟降低了 66%(88ms → 30ms)——用一点首 token 时间换了一个平稳得多的流。这已经是前沿 serving 栈的标准做法(DistServe、Splitwise、vLLM × Mooncake)。
心智模型:一家咖啡店只有一个员工,既磨豆子又倒浓缩。一个顾客点了一大单磨豆,后面等着简单倒一杯的顾客全被堵住了。把店分成磨豆站和倒杯站,倒杯持续流动,不管磨豆有多大。
问题:两种工作,一个队列
Serving 一个 LLM token 不是一种工作——是两种:
Prefill 处理整个 prompt 来构建 KV cache。这是一个大的、计算密集型的突发操作,耗时随 prompt 长度线性增长:100k token 的上下文是一个真的很慢的操作。
Decode 生成响应,一次一个 token,每一步都很便宜,但位于用户感受的关键路径上——token 间延迟(ITL)决定了流的平稳程度。
把它们放在一起——默认做法——它们会争抢同一份计算资源。Continuous batching 提高了吞吐但没有消除冲突:当引擎运行一个长的 prefill 时,它同时承载的 decode 必须等待。而且它们逃不到负载更低的引擎上,因为这个引擎持有它们的 KV cache。所以一个长上下文 prompt 一旦落地,那个引擎上所有活跃的 token 流都会抖动。你的 p99 ITL 被你最长的 prompt 劫持了。

模式:拆分池子
分离(DistServe、Splitwise)把两个阶段放到不同硬件上:
Prefill 池只做一件事——构建 KV cache——突发式、计算密集型的工作,隔离开来。
Decode 池只做一件事——流式输出 token——稳定的、对延迟敏感的工作,隔离开来。
KV cache 从 prefill 交接给 decode(这是移动成本高的部分——这正是 Mooncake 等快速 KV 传输层存在的意义,让这件事变得便宜)。
现在一个大 prefill 不会再阻塞任何人的 decode,因为它跑在不同的池子上。每个池子也可以独立调优和扩缩容,按各自的 SLO(prefill 的 TTFT、decode 的 ITL),而不是用一个旋钮为两者妥协。
模拟让每个请求的 decode 绑定到运行其 prefill 的引擎上(KV-cache 本地性)——这个约束正是让同位阻塞无法避免的原因:
if disaggregated {
if j.prefill {
server, dur = 0, w.prefill[j.req] // prefill 池
} else {
server, dur = 1, decodeDur // decode 池——永不在 prefill 后面
}
} else {
server = j.req % 2 // pinned 引擎持有该请求的 KV cache
// ...其 decode 被任何落地在这里的 prefill 卡住
}
Prefill/Decode 分离 — 防止长 prefill 让 token 流卡顿
之前 → 之后:p99 token 间延迟 88ms(同位) → 30ms(分离) (降低 66%)
240 请求,2 台服务器,20% 长上下文突发(700–1800ms prefill),20 步 decode × 5ms。
布局 p99 token 延迟 平均 token 延迟 平均 TTFT
同位(共享) 88 ms 15 ms 426 ms
分离(拆分) 30 ms 11 ms 1032 ms
相同的工作负载,相同的总硬件(各 2 个引擎)。同位让突发式 prefill 跳到微小 decode 前面,所以 p99 token 抖动到 88ms。分离后隔离开了 decode,p99 控制在 30ms——流平稳了 66%。诚实的代价是 TTFT:只有一台引擎专职做 prefill,首 token 来得更晚(426ms → 1032ms)。这就是分离给你的真正旋钮——通过独立配置 prefill 和 decode 池来买回 TTFT。

为什么这是 2026 年的方向
分离在两年内从研究想法变成默认做法。DistServe 证明了把 prefill 和 decode 分开并各自按自己的延迟目标配置,可以在延迟约束下多服务数倍的请求;Splitwise 为跨不同硬件拆分阶段提出了同样的论点来提高每美元和每瓦特的吞吐。到 2026 年已经产品化了:vLLM 交付了 PD 分离,而 vLLM × Mooncake 工作把它和分布式 KV cache 池配对,这样 prefill 和 decode 引擎——即使在不同机器上——也能通过快速传输共享 cache。承载这个方案的关键基础设施正是这个演示轻描淡写的 KV-cache 交接。

这个可迁移的想法在 LLM 之外也适用:当你的一条队列混合了突发重型工作和稳定延迟敏感型工作时,把它们隔离开。这和分离批处理流量与交互式流量、或者 OLAP 与 OLTP 是同样的本能——只是应用在了 token 级别。
这个演示有多可信?
它模拟的是队列和队头阻塞,不是 GPU:「prefill」和「decode」是 service times,它忽略了 continuous batching、KV-cache 传输的真实(非零)成本和内存压力——所有真实系统必须处理的事情,这也是为什么便宜的 KV 传输如此重要。两个诚实的警告:分离不是免费的(你要付一次传输和一次 TTFT 跳转,见上),而且只有在 prefill 突发真的和延迟敏感的 decode 产生竞争时才有收益。短而均匀的 prompt 在轻负载下不会显示出差距——在拆分之前先测量你的 ITL 尾延迟。
go run . # 仅用标准库

来源和进一步阅读
Zhong et al. — DistServe: Disaggregating Prefill and Decoding for Goodput-optimized LLM Serving (OSDI 2024, arXiv 2401.09670) — 拆分阶段并为每个池子按各自延迟目标配置的理论依据。
Patel et al. — Splitwise: Efficient Generative LLM Inference Using Phase Splitting (ISCA 2024, arXiv 2311.18677) — 跨不同机器拆分 prefill 和 decode,以提高每美元和每瓦特的吞吐。
Qin et al. — Mooncake: A KVCache-centric Disaggregated Architecture for LLM Serving (FAST 2025, arXiv 2407.00079) — 用更多存储换更少计算;KV-cache 池使跨引擎交接变得可行。
Serving Agentic Workloads at Scale with vLLM × Mooncake (2026) — 生产级 PD 分离,加上分布式 KV cache 池,用于多轮、agent 式的 serving。
KV Cache Offloading: LMCache vs Mooncake vs Dynamo — 分离底层 KV 传输层实际如何工作的对比。