本文目录
同一个 Decoder-only Transformer,在 prefill 和 decode 阶段表现得像两类不同服务。Prefill 一次处理整段 prompt,矩阵大、并行度高,主要决定首 token 何时出现;decode 每轮只推进一个位置,反复读取模型权重与历史 KV Cache,主要决定后续 token 是否平滑输出。
早期 LLM serving 系统把两个阶段放在同一组 GPU 中,通过 continuous batching 提高总体利用率。这种设计省去了跨 worker KV Cache 交接,也只需保存一份模型权重,但它把两种 SLO、两种计算形态和两套资源配置绑在了一起。
DistServe 的贡献不是最早说出“可以把 Prefill 与 Decode 分开”,而是把这件事写成一个可优化的部署问题:给定模型、请求长度分布、到达过程、TTFT/TPOT 目标和集群网络,分别选择两个阶段的并行策略与实例数,再决定它们应该放在哪些 GPU 上,使每块 GPU 能承载的合格请求率最大。
理解 DistServe,关键不是记住一次实验的倍数,而是理解它改变了 serving 系统的目标函数:从追求裸吞吐,转向追求同时满足两类延迟 SLO 的 per-GPU goodput。
一次生成为什么有两个延迟目标
设请求到达时间为 $t_0$,第一个输出 token 返回时间为 $t_1$,后续 token 时间为 $t_2,t_3,\ldots$。
Time To First Token 可以写成:
\[\operatorname{TTFT}=t_1-t_0\]它包含排队、prompt prefill、可能的 KV 读取/传输、采样与网络返回。用户发送问题后等待系统“开始回答”,主要感知这个量。
Time Per Output Token 常用后续 token 间隔的平均值表示:
\[\operatorname{TPOT} =\frac{t_N-t_1}{N-1}\]也有系统记录逐 token Inter-Token Latency 并观察 P90/P99。无论具体口径,目标都是约束 decode 流畅度。
两个阶段对应关系大致是:
request arrives
│ queue + prefill + first sampling
├────────────────────────────────► first token
│ │
│ decode steps / TPOT
│ ▼
└────────────────────────────────► remaining tokens
TTFT
不同应用对两者的容忍度不同。对话希望快速开始响应;批量摘要可能更关心整段生成完成速度;代码补全对首 token 和持续生成都可能敏感。系统不能用一个 tokens/s 数字表达所有服务质量。
Colocated Serving 的干扰从哪里来
Continuous batching 以 iteration 为单位重组 active requests。假设 GPU 正在为一组请求做 decode,此时新请求需要 prefill:
iteration 1 decode 64 requests
iteration 2 decode 64 + prefill one long prompt
iteration 3 decode 64 requests
第二轮的工作量远大于普通 decode iteration。已有请求必须等待长 prompt 的计算,TPOT 出现尖峰;新 prompt 又与 decode token 竞争算力和 memory bandwidth,TTFT 也可能变长。
Chunked Prefill 能缓解但没有消除共享
把长 prompt 切成固定 token chunk,可以限制单轮 prefill 的最大工作量:
long prefill = chunk 1 + chunk 2 + chunk 3 + ...
batch step = decode tokens + one prefill chunk
这能显著改善调度,是现代 colocated engine 的重要方案。但 chunk size 仍是一种折中:
- chunk 大,prefill 效率更好,decode stall 更长;
- chunk 小,TBT 更平滑,prefill 要经历更多轮、更多调度和较差的矩阵形状;
- decode load 变化时,固定 chunk 不一定始终合适。
DistServe 的观点是,只要两类工作仍在同一 GPU iteration 中竞争,就需要在 TTFT 与 TPOT 之间分配预算。物理分离让这类直接干扰从执行资源层消失,但会引入 KV 传输和权重复制。
第二个问题是 Resource Coupling
即使 scheduler 能完美切分时间,共用 GPU 也要求 prefill 与 decode 使用同一套模型实例和并行配置。
Prefill 倾向
Prefill 单请求已经有很多 token 可并行计算。严格 TTFT 下,增加 tensor parallel 可能缩短一次请求执行时间;高到达率下,更多 pipeline stage 或 replica 可能降低排队、提高 phase capacity。最佳选择取决于 prompt length、并行效率和到达率。
Decode 倾向
Decode 单请求每轮工作很小,需要较大 continuous batch 才能提高 GPU 利用率。严格 TPOT 下,tensor parallel 能降低单步 latency,但通信会带来递减收益;若单实例已经满足 TPOT,复制 decode instance 往往能更接近线性地增加 request capacity。
同一个配置很难同时最优
colocated model instance
├─ TP/PP chosen for prefill latency?
├─ TP/PP chosen for decode latency?
├─ memory reserved for prefill burst?
└─ memory reserved for many decode KV sequences?
DistServe 将模型权重分别部署到 P 与 D instance,使两个阶段可以拥有不同的 TP/PP、batch 策略与 replica 数。额外权重副本是成本,但解耦后的配置空间也因此打开。
Throughput 与 Goodput 不是同一个目标
Throughput
裸吞吐通常统计:
\[\text{throughput} =\frac{\text{completed requests or tokens}} {\text{wall-clock time}}\]一个系统可以通过大 batch 获得很高总吞吐,同时让大量请求等待很久。只要最终完成,它们仍计入 throughput。
SLO Attainment
设请求 $r$ 的两个约束为 $S_{TTFT}$ 与 $S_{TPOT}$,可以定义:
\[I_r=\mathbf 1[ TTFT_r\le S_{TTFT} \land TPOT_r\le S_{TPOT} ]\]请求集合上的 attainment 为:
\[A=\frac{1}{|R|}\sum_{r\in R}I_r\]如果产品要求至少 90% 的请求同时满足两项 SLO,就需要 $A\ge0.9$。
Per-GPU Goodput
DistServe 将 per-GPU goodput 定义为:在给定 SLO attainment 目标下,系统能够承载的最大请求到达率,再除以使用的 GPU 数:
\[G=\frac{\lambda_{max}(A\ge A_{target})} {N_{GPU}}\]它把成本、负载和用户体验放进同一个指标。更高 $G$ 表示同样 SLO 下每块 GPU 能服务更多请求,或同样流量下需要更少 GPU。
Goodput 仍不是模型质量指标。请求生成了不正确答案,即使延迟达标也会计入上述系统指标;质量、安全和业务成功率需要另一组 guardrail。
一个算例说明为什么 P/D 比例不是 1:1
假设 profile 得到:
one Prefill GPU group capacity: 6 req/s under TTFT SLO
one Decode GPU group capacity: 11 req/s under TPOT SLO
target traffic: 20 req/s
至少需要:
\[N_P=\left\lceil\frac{20}{6}\right\rceil=4\] \[N_D=\left\lceil\frac{20}{11}\right\rceil=2\]整体容量受较小的一侧限制:
\[\lambda_{system} \le\min(N_PG_P,N_DG_D)\]如果盲目部署 3P+3D,D 端有富余,P 端却只能提供 18 req/s;额外 D GPU 没有变成可交付的请求。
真实计算还要考虑每个 instance 使用多少 GPU、KV handoff、负载波动和 tail SLO。这个例子只说明:输入/输出长度分布决定两个阶段的 service demand,P/D ratio 不应该由架构图对称性决定。
DistServe 的离线输入是什么
Placement optimizer 不是只读一个模型名称。它需要:
- 模型层数、hidden size、权重与 KV 内存;
- 可选 TP/PP 与每种配置的执行 profile;
- GPU/node 数与显存;
- 节点内和节点间带宽拓扑;
- 请求到达过程;
- input/output length 分布;
- TTFT、TPOT 与 attainment target;
- 目标 traffic rate。
输出 placement 包含:
- Prefill instance 的并行配置;
- Decode instance 的并行配置;
- 两类 instance/replica 的数量;
- 每个 stage/instance 在物理节点上的位置。
所以它更接近 capacity planner + topology-aware deployer,而不是在线每 token scheduler。
为什么用模拟器搜索而不是一个闭式公式
均匀 prompt 和 Poisson 到达下,单个 prefill server 可近似为 M/D/1 queue。设单请求服务时间为 $D$、到达率为 $R$ 且 $RD<1$,平均响应时间为:
\[E[T] =D+\frac{RD^2}{2(1-RD)}\]第二项是排队延迟。当利用率接近 1,分母变小,延迟会迅速上升。这解释了为什么复制 instance 有时比单纯缩短执行时间更能改善高负载排队。
但真实请求长度不同、到达有 burst、decode 持续时间由输出长度决定,TP/PP 又改变 service curve。SLO attainment 是分位/比例目标,不能仅靠平均值推导。
DistServe 因此:
- 从历史 trace 拟合较长时间尺度的 workload 分布;
- 为模型与并行配置建立 latency model;
- 重新采样到达、输入和输出长度;
- 在 simulator 中回放 scheduler;
- 二分搜索满足 attainment 的最大 request rate;
- 枚举可行 placement,比较每 GPU goodput。
论文报告其测试设置中 simulator 与真实运行的 SLO attainment 误差较小,但这种准确度依赖 profile 和 workload 是否仍代表当前系统。线上模型、kernel、请求域或网络改变后必须重新校准。
P 与 D 可以分别选择怎样的并行策略
论文使用 intra-op/inter-op 的分析框架,实际结果以 TP/PP 配置呈现。用今天常见术语理解:
Tensor Parallel
把同一层矩阵与 attention heads 分到多 GPU。优点是缩短单请求/单 step 计算,代价是每层 collective communication 和分片后利用率下降。
严格 TTFT 的 prefill 或严格 TPOT 的 decode 可能需要更高 TP;但超过某点后,通信让速度不再近线性提升。
Pipeline Parallel
把模型层分成多个 stage。单个 microbatch 要串过所有 stage,端到端 latency 不会像理想 TP 那样直接缩短;多个请求/微批在流水线中并行后,吞吐可以提高。请求长度不均会造成 stage bubble。
Replication
复制完整 instance,按请求分流。若模型能放入更少 GPU 且单 instance 已满足 SLO,replica 常能近线性增加 rate capacity,并降低单队列到达率;代价是重复保存权重。
DistServe 不预设 P 一定 TP、D 一定复制,而是让 workload 与 SLO 决定。低流量、严格延迟和高流量、较宽松延迟可能得到完全不同 placement。
KV Cache 交接怎样进入成本模型
对 prompt 长度 $S$,KV payload 近似:
\[M_{KV}=2SLH_{kv}DB\]如果请求率为 $\lambda$,平均每秒 P→D 数据量约为:
\[BW_{required}\approx \lambda E[M_{KV}]\]论文用 OPT-66B、512-token 请求举例,单请求 KV 约 1.13 GB;10 req/s 已需要约 11.3 GB/s,也就是约 90 Gbps 的有效数据率。现代 GQA/MQA 模型会显著减小 KV,但长上下文和更高 RPS 仍可能把传输推成瓶颈。
需要区别:
link bandwidth 端口理论能力
effective bandwidth 扣除协议、路径、并发后的 payload rate
visible latency 没有被计算/排队隐藏、真正增加请求延迟的部分
Placement 必须看后两者,而不是只看网卡标签。
高跨节点带宽时怎样 Placement
如果节点间 fabric 足够快,P 与 D instance 可以跨节点自由组合。DistServe 的搜索可分两层:
- 分别找到 P 与 D 阶段 per-GPU goodput 最优的并行配置;
- 按目标 traffic rate 复制两种 instance,匹配整体容量。
概念上:
search P configs ─► best phase goodput Gp ─► Np replicas
search D configs ─► best phase goodput Gd ─► Nd replicas
physical placement has few P↔D locality constraints
这里的“传输可忽略”是经过相应硬件和 workload 测量后对 placement 的简化,不表示 KV 真的没有通信。若平均 payload、到达率或其他作业的网络竞争改变,这个假设需要重新验证。
低跨节点带宽时为什么要对齐 Model Stage
如果跨节点只有较慢网络,最简单的想法是把完整 P instance 和完整 D instance 放进同一节点,通过 NVLink 交接。但大模型的两份权重可能放不下:例如单个实例已经占满多张 GPU,再复制一份没有空间。
DistServe 利用 KV 只需要在对应模型层之间交接这一事实。若 P 与 D 都采用 pipeline stage:
Prefill pipeline: P-stage-0 → P-stage-1 → P-stage-2
│ │ │
KV0 KV1 KV2
│ │ │
Decode pipeline: D-stage-0 → D-stage-1 → D-stage-2
将相同 stage 的 P segment 与 D segment 放在同一节点:
Node 0: P-stage-0 + D-stage-0 → local high-bandwidth KV transfer
Node 1: P-stage-1 + D-stage-1 → local high-bandwidth KV transfer
Node 2: P-stage-2 + D-stage-2 → local high-bandwidth KV transfer
这样大模型仍跨节点分 stage,但每层产生的 KV 不需要走慢速 cross-node path。代价是 P/D placement 与 PP degree 相互约束,搜索空间不再能分开优化。
今天的系统可以用 RDMA/NIXL/Mooncake 等更强数据面放宽部分约束,但拓扑原则没有过时:对应 KV shard 应尽量沿有效带宽高、竞争少、NUMA 合理的路径交接。
在线调度还要处理 Burst
即使平均 P/D capacity 匹配,短时 burst 也可能让 P 在一段时间内完成大量请求,将大批 KV 同时推向 D。D 的 HBM 既要容纳 active decode KV,也要接收新请求,push storm 可能使其瞬间过载。
DistServe 采用 pull-style KV transfer:P 完成后暂时保留 KV,D 有能力接收时再主动拉取。
Prefill completed queue (KV stays at P)
[r1][r2][r3][r4][r5]
│
│ D has slots, pulls r1/r2
▼
Decode active set [r1][r2] ...
P 的 GPU memory 在这里临时充当交接队列。它能对 D 施加 backpressure,却也引出新的约束:
- 等待中的 KV 占用 P HBM;
- P block 满后不能继续无限接收 prompt;
- 请求取消或 D failure 必须释放源 KV;
- D 选择哪个 completed request 会影响公平性和 deadline;
- transfer lease/timeout 必须防止 block 永久 pin。
现代 KV Connector 中的 block ownership、pull/read、lease 和完成回执,正是在工程上继续解决这类生命周期问题。
一次 Request 的完整路径
1. Router admits request
│
▼
2. Select Prefill instance / queue
│
▼
3. Run prompt, produce first token + KV Cache
│
▼
4. Retain KV in completed-prefill buffer
│
▼
5. Decode scheduler reserves capacity and pulls KV
│
▼
6. Decode instance joins continuous batch
│
▼
7. Stream remaining tokens
TTFT 到底截止在第 3 步还是第 5/6 步,要看 API 是否允许 P 先返回首 token、以及 D handoff 是否在首 token 前完成。做 benchmark 时必须统一定义,否则不同实现的 TTFT 不可比较。
第一 token、sampling state、position、stop/grammar 状态也要随请求从 P 交给 D。只传 KV bytes 而漏掉逻辑状态,D 的下一步生成仍可能错误。
DistServe 与 Mooncake 的关注点不同
两者都采用 P/D disaggregation,但文章不应把它们写成同一个系统的不同名字。
DistServe
重点是 prefill/decode 干扰、per-GPU goodput,以及怎样联合选择两阶段资源、TP/PP、replica 和 bandwidth-aware placement。它将 KV 主要视为阶段交接时必须传输的中间状态。
Mooncake
进一步把 KV Cache 当成可跨请求复用、可在 CPU/DRAM/SSD 中保存、可复制与淘汰的分布式缓存对象,并让全局调度器围绕缓存位置和负载做决策。
NIXL/KV Connector
解决 P/D 或 KV Store 选定源、目标后,serving engine 怎样注册内存、交换 descriptors、异步搬运并安全管理 block 生命周期。
可以这样排列:
DistServe: 为什么分离,以及怎样按 goodput 配资源
Mooncake: 分离后怎样围绕全局 KV Cache 复用与调度
NIXL/Connector: 选定路径后怎样执行可靠高效的数据交接
DistServe 与 llm-d 的关系
DistServe 的 placement optimizer 假设掌握模型、workload、SLO 与 cluster profile,输出一套阶段部署方案。llm-d 位于更现代的 Kubernetes serving control plane:用 Gateway/调度器、模型服务 Pod、KV-aware routing 和 P/D 组件组织线上集群。
二者不是直接的代码继承关系,但 DistServe 提出的几个问题会在 llm-d 一类平台中重新出现:
- Prefill 与 Decode deployment 分别扩多少 replica;
- route request 时同时看 queue 与 KV locality;
- 网络 topology 是否支持所选 P→D pairing;
- 何时触发扩缩容,避免只扩一侧;
- 怎样用 TTFT/ITL goodput 而不是 GPU utilization 判断容量。
如果没有阶段 capacity model,Kubernetes HPA 即使能把 Pod 数调高,也可能扩错资源池。
论文结果应该怎样引用
OSDI 2024 论文在多种 OPT 模型、应用 trace、SLO 与 32 张 A100 的实验条件下,报告相对当时基线最高可承载约 $7.4\times$ 请求率,或满足约 $12.6\times$ 更严格 SLO,并以超过 90% 请求达标为主要目标。论文的某些逐 workload 对比区间小于这些最大值。
这些数字不能直接迁移到现代 GQA/MoE 模型、H100/Blackwell、不同网络与最新 vLLM/SGLang:
- baseline 已加入 chunked prefill、异步 scheduling 与更强 kernel;
- KV size、attention 算法和模型并行方式改变;
- 长 reasoning 输出让 D demand 占比上升;
- 高速 RDMA 降低 handoff,低成本 Ethernet 可能放大它;
- prefix cache 命中会减少 P 计算并改变 P/D ratio。
应该引用它所证明的系统规律和实验条件,而不是把峰值倍数写进容量预算。
一套从 Colocated 到 Disaggregated 的实验
1. 建立三个 Baseline
A. colocated continuous batching
B. colocated + tuned chunked prefill
C. P/D disaggregated + KV transfer
如果 C 只对比未调优 A,就无法判断收益来自物理分离,还是 baseline 缺少 chunked scheduling。
2. 固定 Workload Trace
保留:
- 到达时间与 burst;
- input/output token length;
- sampling/stop 配置;
- prefix reuse relation;
- service tier 与 deadline。
平均长度相同的两个 trace 可能有完全不同 tail 和排队行为,不能只生成固定长度请求。
3. 扫描 P/D 配置
对每组 P/D replica、TP、PP 与 placement,测:
- phase execution/queue latency;
- KV payload、effective bandwidth 与 visible handoff;
- TTFT、TPOT/ITL、E2E 的 P50/P90/P99;
- 两项 SLO 同时满足的 attainment;
- 每 GPU goodput;
- HBM KV/weight 占用与网络峰值。
4. 检查稳定性边界
逐步提高 request rate。接近饱和点时,排队会非线性上升。需要找:
largest λ such that attainment >= target
而不是在过载后仍统计所有最终完成请求的平均 tokens/s。
5. 故意制造偏斜
分别回放:
- long-input / short-output;
- short-input / long-output;
- reasoning 长输出;
- 高 prefix hit;
- 网络限速和拥塞;
- P 或 D 单侧 worker failure。
观察固定 placement 何时失配,以及 controller 多久才能调整 P/D capacity。
上线后的容量控制不应只看 GPU Utilization
Prefill GPU 可能在低到达率下利用率不高,却是满足严格 TTFT 必需的 headroom;decode GPU 利用率不满,也可能因为 TPOT 已接近 deadline 而不能继续加 batch。
更合适的扩缩容信号包括:
Prefill Pool
- queue delay 与 predicted TTFT;
- uncached prefill tokens/s;
- prompt length 分布;
- completed-KV buffer 占用;
- P stage SLO attainment。
Decode Pool
- active sequences 与 KV blocks;
- batch size、step latency 和 ITL tail;
- remaining output tokens 估计;
- waiting-to-transfer requests;
- D stage SLO attainment。
Handoff
- KV bytes/s 与 transfer queue;
- P block pin time;
- D load wait;
- topology/path saturation;
- retry 与 timeout。
扩容 D worker 通常还要加载完整模型并预热 kernel/Graph,不能等 TPOT 已经持续违约才开始。预测性扩容和预留容量是分离式系统真正上线时的重要补充。
哪些条件下分离可能不值得
DistServe 揭示的是一个强有力的配置空间,不是一条无条件规则。下列情形需要认真比较 colocated baseline:
- 单机/少量 GPU,复制两份权重明显挤压 KV capacity;
- 流量低,几乎没有 prefill/decode 同时执行;
- prompt 很短,chunked prefill 已能把干扰控制在 SLO 内;
- 网络慢或不稳定,KV handoff 占 TTFT 大头;
- 模型太大,同节点无法对齐 P/D stage,跨节点又没有高速 fabric;
- workload 快速变化,固定 P/D pool 经常单侧闲置;
- 多模型服务导致每个模型的独立 P/D replica 进一步碎片化资源。
也可以采用 hybrid 策略:基础流量 colocated,只有长 prompt 或严格 tail ITL 请求走 disaggregated path;或者让节点具备差异化能力,在负载变化时切换角色。选择标准仍应是同一 workload 和 SLO 下的 goodput。
小结
DistServe 把 Prefill/Decode 分离从一个架构技巧变成了可量化的资源配置问题。Colocated engine 中,长 prefill 会拖慢正在生成的 decode,decode batching 也会影响 TTFT;更深层的限制是两个阶段被迫共享同一 TP/PP、replica 数和 HBM 预算。
分离后,Prefill Pool 可以围绕 TTFT 和 prompt 分布配置,Decode Pool 可以围绕 TPOT、batch 与 KV capacity 配置。DistServe 用 simulator 在 workload、SLO 和 cluster profile 上搜索阶段并行策略、实例数与 physical placement,并用 per-GPU goodput 衡量“每块 GPU 能交付多少按时完成的请求”。
代价是模型权重复制、KV handoff 与更复杂的跨阶段状态。高带宽集群可以更自由地放置 P/D;低跨节点带宽则需要将对应 model stage 对齐到节点内高速互连。在线 pull 又把 decode backpressure 传回 prefill KV buffer。
这套思路构成后续分布式推理平台的容量基础:Mooncake 把 KV 扩展成全局缓存,NIXL/Connector 把交接实现成可靠数据面,llm-d 一类控制平面把路由和 Pod 编排带到 Kubernetes。无论工具如何变化,判断 P/D 是否该分离的起点仍是同一个:在真实请求分布下,哪种配置能以更少 GPU 同时满足 TTFT 与 TPOT。
参考资料
觉得有帮助?
分享给同样关注系统性能的人。