文章

Prefill-Decode Disaggregation 生产实战:分离预填充与解码,降低 TTFT/TPOT 冲突

系统梳理大模型预填充与解码分离架构,深入解释长上下文场景下首 token 延迟与逐 token 延迟的冲突根源,覆盖 KV Cache 传输、动态路由、资源配比、监控指标与上线检查清单,助你平稳落地生产环境。

背景问题: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 不再重新处理完整输入,而是接收已有状态并继续生成。

这带来三个工程收益:

  1. 资源配比可以独立调整:长上下文请求多,就增加 Prefill 资源;长输出请求多,就增加 Decode 资源。
  2. 并行策略可以按阶段优化:Prefill 更偏计算吞吐,Decode 更偏显存带宽和低抖动。
  3. 尾延迟更可控:长 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 DelayPrefill 任务排队时间
Decode Active SequencesDecode Pool 中正在生成的序列数量
KV Transfer TimeKV Cache 从 Prefill 到 Decode 的端到端传输时间
Cache Hit Rateprefix 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 的敏感度不同:

业务类型延迟敏感度重点优化方向
ChatbotTTFT 和流式 TPOT低延迟、流式稳定
批量摘要吞吐和成本高吞吐、低成本
Agent 工具调用短响应和稳定延迟低抖动
RAG 长上下文问答Prefill 能力大上下文吞吐

因此不要让所有业务共享同一套分离策略。至少应支持按 tenant、route、model、prompt length bucket 配置策略。

适用场景

适合 Prefill-Decode Disaggregation 的场景:

  1. 长上下文 RAG:输入文档多、prompt 长,Prefill 压力明显。
  2. 代码仓库问答:上下文大,首 token 延迟容易波动。
  3. 企业知识库助手:多租户、长短请求混合,需要保护交互体验。
  4. 高并发流式聊天:Decode 阶段持续占用,不能被大 Prefill 频繁打断。
  5. 异构 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 互相干扰的瓶颈,不妨从流量画像和灰度实验开始,逐步验证这套架构是否适合你的生产环境。

参考资料

  1. DistServe: Disaggregating Prefill and Decoding for Goodput-optimized Large Language Model Serving — arXiv:2401.09670
  2. Splitwise: Efficient Generative LLM Inference Using Phase Splitting — arXiv:2311.18677
  3. vLLM Documentation: Disaggregated Prefilling — docs.vllm.ai
  4. TensorRT-LLM: Disaggregated Serving in TensorRT LLM — NVIDIA Tech Blog
  5. NVIDIA Dynamo Documentation: Disaggregated Serving — docs.nvidia.com
  6. NVIDIA Dynamo GitHub Repository — github.com/ai-dynamo/dynamo
  7. Sarathi-Serve: Taming Throughput-Latency Tradeoff in LLM Inference — arXiv:2403.02310
  8. NVIDIA Technical Blog: Removing the Guesswork from Disaggregated Serving — developer.nvidia.com

常见问题

Prefill-Decode 分离一定会提升吞吐吗?
不一定。它主要解决预填充和解码互相干扰、TTFT 与 TPOT 难以同时优化的问题。短输入、短输出或单机低并发场景可能得不偿失。
为什么分离后还要关注 KV Cache 传输?
因为远端 Prefill Worker 生成的 KV Cache 必须交给 Decode Worker 继续生成。传输带宽、序列长度、布局转换和超时都会影响端到端延迟。
上线时优先看哪些指标?
优先看 TTFT p95/p99、TPOT p95/p99、Prefill 队列等待、KV 传输耗时、Decode GPU 利用率、缓存命中率、失败回退率和单位 token 成本。
Prefill-Decode 分离和 Chunked Prefill 是替代关系吗?
不是。Chunked Prefill 是把长 Prefill 切成更小块,减少对 Decode 的阻塞;Prefill-Decode Disaggregation 是把 Prefill 和 Decode 放到不同资源池。两者可以组合使用。