背景问题:LLM 推理不是一个均匀负载
很多团队第一次部署大模型服务时,会把推理过程理解为”一个请求占用 GPU,模型逐步生成答案”。这个理解过于粗糙。对 decoder-only LLM 来说,一次请求通常可以拆成两个阶段:Prefill(预填充)和 Decode(解码)。
- Prefill 负责一次性处理输入 prompt,计算每一层的 Key/Value 状态,并生成第一个输出 token。它通常偏计算密集型,尤其在长上下文、RAG、多轮对话和代码仓库分析场景中,输入 token 很长,矩阵计算量大。
- Decode 则是在已有 KV Cache 基础上,一个 token 一个 token 地自回归生成后续内容。它更容易受显存带宽、KV Cache 读取、批处理调度影响,单步计算量相对小,但需要持续、稳定、低抖动地执行。
问题出在生产环境的混合流量:有的请求输入很长,有的请求输出很长,有的只需要几十个 token,有的要流式生成上千 token。如果把 Prefill 和 Decode 始终放在同一组 GPU 上调度,长 prompt 的 Prefill 可能插入正在进行的 Decode,导致用户已经开始接收流式输出后,后续 token 突然变慢。最终表现为:TTFT 看起来还能接受,但 TPOT 或 ITL 尾延迟恶化;或者为了保护 Decode 体验,不得不限制 Prefill,导致新请求首 token 等待时间变长。
Prefill-Decode Disaggregation 的目标,就是把这两个负载特征不同的阶段拆开,让系统分别优化 TTFT(Time To First Token)和 TPOT(Time Per Output Token),而不是让两者在同一套 GPU、同一套并行策略、同一条调度队列中互相抢资源。
核心原理:把”读懂输入”和”持续生成”拆成两个资源池
传统聚合式推理
在聚合式 serving 中,一个请求通常在同一批模型实例内完成:
Client Request → Router → Model Worker: Prefill → Same Model Worker: Decode → Streaming Response
这种方式简单,调试成本低,单机部署和中小流量场景足够可用。但它把两类负载绑定在一起:
- Prefill 和 Decode 共享同一组 GPU。
- Prefill 与 Decode 使用同一套 tensor parallel / pipeline parallel 配置。
- 长 prompt 会影响正在流式输出的请求。
- 资源扩缩容只能按”完整模型副本”扩展,很难单独增加 Prefill 或 Decode 能力。
分离式推理
在分离式 serving 中,系统将请求拆成两个阶段:
Client Request → Router → Prefill Queue → Prefill Worker Pool → KV Cache Transfer → Decode Worker Pool → Streaming Response
Prefill Worker 处理输入 prompt,生成用于后续解码的 KV Cache。随后系统通过高速网络、共享缓存、KV connector 或专门的 KV transfer runtime,把 KV Cache 交给 Decode Worker。Decode Worker 不再重新处理完整输入,而是接收已有状态并继续生成。
这带来三个工程收益:
- 资源配比可以独立调整:长上下文请求多,就增加 Prefill 资源;长输出请求多,就增加 Decode 资源。
- 并行策略可以按阶段优化:Prefill 更偏计算吞吐,Decode 更偏显存带宽和低抖动。
- 尾延迟更可控:长 Prefill 不会频繁打断 Decode,流式输出的 token 间隔更稳定。
但它也引入一个新成本:KV Cache 传输。当 prompt 很短、Decode 端 prefix cache 命中率很高,或者跨节点带宽不足时,远端 Prefill 反而可能不划算。因此,生产落地的重点不是”所有请求都拆”,而是按请求特征动态决定是否拆。
关键机制一:按请求决定远端 Prefill 还是本地 Prefill
成熟的分离式系统通常不会把全部请求都送到远端 Prefill Pool。更合理的做法是设计一个 Disaggregated Router,根据以下因素做决策:
| 决策因素 | 说明 |
|---|---|
| 输入 token 数量 | 长输入更适合远端 Prefill |
| 输出长度预估 | 长输出更需要保护 Decode Pool 的连续生成 |
| Prefill Queue 等待时间 | 队列过长时,短请求本地处理可能更快 |
| Decode Worker 的 prefix cache 命中率 | 如果本地已有大量缓存,远端 Prefill 可能造成重复传输 |
| 租户优先级与 SLA | 交互式业务、批处理任务、后台总结任务应走不同策略 |
一个简化规则可以写成:
pd_disaggregation_policy:
remote_prefill:
min_input_tokens: 2048
max_prefill_queue_wait_ms: 300
min_expected_output_tokens: 128
require_decode_pool_pressure: true
local_prefill:
max_input_tokens: 1024
prefer_when_prefix_cache_hit: true
fallback:
on_kv_transfer_timeout: local_aggregated_serving
on_prefill_queue_overload: local_prefill
这个配置不是推荐默认值,只是说明策略形态。真正上线前必须结合模型大小、GPU 类型、网络带宽、上下文长度分布和业务 SLA 压测。
关键机制二:KV Cache 传输决定分离是否划算
Prefill-Decode 分离不是免费午餐。它要求系统把 Prefill 阶段产生的 KV Cache 从 Prefill Worker 传到 Decode Worker。这里有几个容易被低估的细节。
传输量随上下文和模型规模增长
KV Cache 与层数、hidden size、注意力头配置、序列长度、精度有关。上下文越长,传输体积越大。对长上下文模型来说,KV 传输可能成为新瓶颈。
KV layout 不一定天然兼容
不同推理引擎、attention backend、paged KV 管理方式和 tensor parallel 配置可能使用不同布局。跨 Worker 传输时,可能需要 layout transformation。如果这一步没有异步化或与计算重叠,会直接增加 TTFT。
传输必须可观测、可超时、可回退
生产系统里不能只统计模型 forward 时间。至少要把以下阶段拆开:
request_queue_wait
prefill_compute_time
kv_cache_pack_time
kv_cache_transfer_time
kv_cache_unpack_time
decode_queue_wait
first_token_emit_time
per_token_decode_time
否则当用户反馈”首 token 变慢”或”流式输出卡顿”时,很难判断是 Prefill 排队、网络传输、Decode 拥塞,还是缓存命中率下降。
关键机制三:容量规划要同时看 TTFT 和 TPOT
普通在线服务常用 QPS、平均延迟和 p95 延迟做容量规划。但 LLM serving 需要更细的指标体系:
| 指标 | 含义 |
|---|---|
| TTFT | 用户发起请求到收到第一个 token 的时间 |
| TPOT / ITL | 后续每个 token 的平均或分位耗时 |
| Goodput | 在满足 TTFT 与 TPOT SLO 前提下可承载的有效请求率 |
| Prefill Queue Delay | Prefill 任务排队时间 |
| Decode Active Sequences | Decode Pool 中正在生成的序列数量 |
| KV Transfer Time | KV Cache 从 Prefill 到 Decode 的端到端传输时间 |
| Cache Hit Rate | prefix cache 或共享 KV cache 的命中率 |
更实用的容量目标不是”GPU 利用率越高越好”,而是:在 TTFT p95、TPOT p95、错误率和成本预算都满足目标时,让 goodput 最大化。
如果只追求 GPU 利用率,系统可能会通过塞入更多 Prefill 任务提高表面利用率,却牺牲 Decode 的流畅性。用户看到的不是”GPU 更忙”,而是输出卡顿。
工程落地:一条可执行的改造路线
阶段 1:先做基线画像
在改造前,先采集 3 到 7 天真实流量画像:
- 输入 token 长度分布
- 输出 token 长度分布
- RAG / 非 RAG 请求占比
- 多轮对话上下文长度
- 流式输出用户占比
- 当前 TTFT、TPOT、E2E latency、GPU 利用率
- batch size、active sequence、KV cache 占用
如果 90% 请求都是短输入短输出,Prefill-Decode 分离很可能不是优先项。此时更应该先做 continuous batching、prefix caching、chunked prefill、量化或 prompt 压缩。
阶段 2:引入远端 Prefill 但保留本地回退
第一版不建议全量切换。可以先按模型、租户、接口或输入长度做灰度:
rollout:
enabled_models:
- qwen-long-context-service
- rag-answer-service
traffic_percent: 5
remote_prefill_when:
input_tokens_gte: 4096
stream_response: true
fallback_to_aggregated: true
必须保留本地聚合式路径。一旦远端 Prefill 队列、KV 传输或 Decode 接收出现异常,可以直接回退到原有 serving 模式。
阶段 3:独立扩缩容
分离架构的价值之一是可以单独扩 Prefill Pool 或 Decode Pool。扩缩容策略不要只看 GPU 利用率,还要看阶段级队列:
- Prefill Queue 长、TTFT 升高:增加 Prefill Worker 或放宽本地 Prefill。
- Decode Active Sequences 高、TPOT 升高:增加 Decode Worker 或限制长输出任务。
- KV Transfer Time 升高:检查网络、布局转换、传输并发和缓存策略。
- Cache Hit Rate 下降:检查路由是否破坏了前缀局部性。
阶段 4:多租户隔离
不同业务对 TTFT 和 TPOT 的敏感度不同:
| 业务类型 | 延迟敏感度 | 重点优化方向 |
|---|---|---|
| Chatbot | TTFT 和流式 TPOT | 低延迟、流式稳定 |
| 批量摘要 | 吞吐和成本 | 高吞吐、低成本 |
| Agent 工具调用 | 短响应和稳定延迟 | 低抖动 |
| RAG 长上下文问答 | Prefill 能力 | 大上下文吞吐 |
因此不要让所有业务共享同一套分离策略。至少应支持按 tenant、route、model、prompt length bucket 配置策略。
适用场景
适合 Prefill-Decode Disaggregation 的场景:
- 长上下文 RAG:输入文档多、prompt 长,Prefill 压力明显。
- 代码仓库问答:上下文大,首 token 延迟容易波动。
- 企业知识库助手:多租户、长短请求混合,需要保护交互体验。
- 高并发流式聊天:Decode 阶段持续占用,不能被大 Prefill 频繁打断。
- 异构 GPU 集群:可以让更适合计算的卡处理 Prefill,让更适合显存带宽的卡处理 Decode。
不太适合的场景:
- 单 GPU 或小规模部署。
- 输入输出都很短的低并发业务。
- 网络带宽弱、跨机传输成本高的环境。
- 缺少阶段级监控和回退机制的早期系统。
常见误区
误区一:分离后吞吐一定更高
不一定。vLLM 文档也提醒,disaggregated prefilling 的主要价值在于分别调 TTFT 和 ITL、控制尾部 ITL,而不是无条件提高吞吐。短请求比例高时,远端 Prefill 的排队和 KV 传输可能抵消收益。
误区二:只要网络够快就没问题
带宽只是一个维度。KV Cache 还涉及打包、解包、布局转换、内存申请、同步点、错误回退和跨 Worker 状态管理。任何一个环节阻塞,都可能污染 TTFT。
误区三:只监控 GPU 利用率
GPU 利用率高不代表用户体验好。Prefill 太多会提高计算利用率,但可能牺牲 Decode 的稳定输出。上线时应把 TTFT、TPOT、队列等待、传输耗时和失败率放到同一张面板。
误区四:所有请求都远端 Prefill
正确做法是条件式分离。短输入、本地 prefix cache 命中、Prefill 队列拥塞时,本地 Prefill 可能更优。
误区五:把分离当成推理引擎参数
它不是简单开关,而是调度、缓存、网络、并行策略、监控、回退和容量规划共同组成的系统工程。
上线检查清单
工作负载画像
- 已统计输入 token 长度分布。
- 已统计输出 token 长度分布。
- 已区分 RAG、Chat、Agent、批处理等业务类型。
- 已明确 TTFT 与 TPOT 的 p95/p99 SLO。
路由策略
- 已定义远端 Prefill 与本地 Prefill 的条件。
- 已考虑 prefix cache 命中率。
- 已配置 Prefill Queue 过载时的回退策略。
- 已支持按模型、租户、接口灰度。
KV Cache 传输
- 已监控 KV 打包、传输、解包耗时。
- 已验证不同并行配置下的 KV layout 兼容性。
- 已设置传输超时和失败回退。
- 已评估网络带宽与跨节点拓扑。
调度与容量
- 已分别压测 Prefill Pool 和 Decode Pool。
- 已验证 burst 流量下的 Prefill Queue 增长。
- 已配置 Decode Pool 的最大 active sequence 限制。
- 已建立基于 SLO 的扩缩容规则。
可观测性与回滚
- 已建立 TTFT、TPOT、E2E latency 分位面板。
- 已记录每个请求的 prefill/decode worker、路由决策和传输耗时。
- 已配置异常告警:传输超时、队列积压、TPOT 抖动、缓存命中率下降。
- 已支持一键回退到聚合式 serving。
总结
Prefill-Decode Disaggregation 不是万能银弹,但它为长上下文、高并发、混合流量的 LLM 服务场景提供了一条清晰的分层优化路径。核心在于三点:按请求特征动态路由、控制 KV Cache 传输成本、围绕 TTFT 与 TPOT 建立可观测的容量体系。如果你的团队正在面对 Prefill 与 Decode 互相干扰的瓶颈,不妨从流量画像和灰度实验开始,逐步验证这套架构是否适合你的生产环境。
参考资料
- DistServe: Disaggregating Prefill and Decoding for Goodput-optimized Large Language Model Serving — arXiv:2401.09670
- Splitwise: Efficient Generative LLM Inference Using Phase Splitting — arXiv:2311.18677
- vLLM Documentation: Disaggregated Prefilling — docs.vllm.ai
- TensorRT-LLM: Disaggregated Serving in TensorRT LLM — NVIDIA Tech Blog
- NVIDIA Dynamo Documentation: Disaggregated Serving — docs.nvidia.com
- NVIDIA Dynamo GitHub Repository — github.com/ai-dynamo/dynamo
- Sarathi-Serve: Taming Throughput-Latency Tradeoff in LLM Inference — arXiv:2403.02310
- NVIDIA Technical Blog: Removing the Guesswork from Disaggregated Serving — developer.nvidia.com