PD 分离实战:Prefill/Decode 拆池、配比拐点与容量规划
大模型推理基础设施实战系列 · 第 2 篇
1. 背景与问题:两种性格迥异的工作负载,为什么要抢同一批 GPU
Prefill 和 Decode 是两种资源性格完全不同的负载:
| Prefill | Decode | |
|---|---|---|
| 计算特征 | 大批量矩阵乘,计算密集 | 低批量常受访存限制;大批量也可能受计算或通信限制 |
| 持续时间 | 长输入可达数百秒 | 每步几十毫秒,持续整个生成 |
| 对延迟的影响 | 决定 TTFT | 决定 TPOT/ITL |
| 批处理收益 | 受形状、算子和通信影响 | 增大 batch 可能提高效率,也增加排队和工作区 |
长 prefill 与 decode 混跑可能造成相互干扰。项目 Kimi-K3 的历史双 PP4 配置曾观察到 538.91 秒流式停顿,但后续 0.56 秒结果来自 FlashInfer、分层、分块和准入等组合调整,并未拆成 P/D 池。该对照不能充当 PD 分离的收益证据。
PD 分离将 Prefill 和 Decode 放到独立实例,目标是减少计算调度干扰,并允许两侧分别扩容;收益需要连同 KV 传输与排队成本实测。
第二个动机是独立扩缩容:长文档高峰期需要更多 prefill 算力,闲聊高峰期需要更多 decode 容量。统一部署只能整体加副本,PD 分离后可以单独加 P 或加 D。
2. 技术选型:原生引擎 + 官方 Gateway,而不是自研转发层
我们评估过三条路线:
| 路线 | 评估结论 |
|---|---|
| 自研代理层做 PD 调度 | 曾用于快速验证,但准入语义、取消传播、生命周期管理都要自己维护,项目最终采用官方组件;这项选型不等于通用的技术禁令 |
| Ray Serve + SGLang | 当时的 Ray SGLang 适配未接入 PD,引入它只解决副本管理,不解决 PD 路由,放弃 |
| 原生 SGLang PD + 官方 Model Gateway | 引擎内建 P/D 角色、bootstrap 配对与 KV 传输,Gateway 提供登记、选路、健康检查——采用 |
两个刻意的保守决定:
- 同构 P/D:首版 P 和 D 使用相同的 TP/PP 组合与制品,通过增减完整副本切换 1P1D、2P1D、3P1D;异构 P/D(不同 TP/PP)不在第一轮引入。
- 副本增减,不做在线角色翻转:上游有"排空后运行时角色切换"的 PR,但其验证集中在特定 AMD 纯 TP 组合;另一个 SM120 + PP8 候选设计未因此获得验收,首版不开启。
3. 架构设计:共享池共享的是"候选实例集合"
数据流:客户端 → 有界接入(鉴权 + 限流)→ Gateway 选择一个健康 P 和一个健康 D → bootstrap 交换配对元数据,P 完成 prefill 后通过配置的传输后端把 KV/状态传给 D → D 持续 decode,流式结果经 Gateway 返回。
架构上有四条容易理解错的规则,都来自实践教训:
① 登记的单位是完整实例,不是 GPU。 例如一个跨机 TP8×PP8 候选副本只作为一个服务实例登记;下文矩阵实际使用 TP8/PP1,不能把 64 个 rank 登记成 64 个 P。所有 rank 健康、模型就绪、准入通过后才接业务。
② 准入是全生命周期的。 “HTTP 连接释放"不等于"GPU 工作结束”:客户端断开必须关联取消 D 并验证资源最终释放。我们曾观察到 Gateway 配置并发 16、queue=0,实际却出现大量在途请求和 D 预分配等待——许可是否持有到请求结束,必须实测验证,不能按参数名声称严格封顶。
③ 在途请求保持原配对。 已生成一半的请求不承诺无损迁移;失败或状态未知的生成请求不自动重放——这是账本纪律(第 5 篇详述)。
④ 扩缩容走完整排水流程。 缩 P 的标准动作:退出新请求候选(先验证该 API 不中断在途请求)→ 等在途请求与 KV 传输收尾(按 request ID 逐条核,覆盖全部 rank,不只看 GPU busy)→ 两次独立空状态检查 → 按 PID/容器身份精确停止并验证 cgroup 为空 → 持久化新拓扑 → 留回退条件。注意:被摘除 P 的独有前缀缓存不会自动迁移,缩容后要预期冷输入短暂变差。
4. 已测最高点与配比扫描:128 卡 12P4D 的 708 万总 TPM
4.1 矩阵坐标:64 个已测点中的最高观察值
我们对两款 Flash 模型跑过 8 种 P:D 组合 × 4 种负载的完整矩阵(各 32 点、2,520/2,520 请求全部完成)。硬件均为 RTX 6000D;模型分别为 GLM-5.3-Flash 与 DeepSeek-V4.1-Flash,均为当时固定 SGLang 制品。最高总 TPM 观察值来自 GLM 的 12P4D:
| 模型 | 负载(输入/输出) | 已测配置 | C / N | 总 TPM | 输出 TPM | TTFT p99 |
|---|---|---|---|---|---|---|
| GLM 系 | 80K / 600 | 12P4D / 128 卡 | 48 / 192 | 7,079,236 | 52,691 | 33.72 s |
| GLM 系 | 380K / 7000 | 8P4D / 96 卡 | 16 / 32 | 2,360,829 | 42,701 | 81.50 s |
| DeepSeek 系 | 80K / 600 | 12P4D / 128 卡 | 48 / 192 | 1,610,132 | 11,982 | 156.36 s |
| DeepSeek 系 | 380K / 7000 | 12P4D / 128 卡 | 16 / 32 | 676,175 | 12,230 | 434.77 s |
128 卡 12P4D、48 并发下,GLM 系模型达到 708 万总 TPM、输出 5.27 万 TPM、TTFT p99 为 33.72 秒——这是该矩阵的最高观察值,尚非饱和产能。80K/600 两行相差约 4.40 倍,但模型、算子和制品路径共同变化,不能仅归因于注意力架构;380K 两行卡数也不同。各行均为 ignore_eos 合成容量轨,不能承诺正文质量或延迟 SLO。
4.2 配比扫描实验设置
以 DS 系模型做精细配比扫描:
- 模型与并行:每副本 TP8/EP8/PP1 共 8 卡;12 台 96 卡固定预算;
- 负载:合成容量轨(ignore_eos),实际输入 160K/224K/288K token,固定输出 1300 token;
- 计量:总 TPM = 60 × 成功请求的实际 (prompt+completion) token ÷ 整轮墙钟;
- 样本:160K 点三轮确认,224K/288K 为单轮观察。
4.3 配比扫描结果:吞吐随 P 占比上升,边际收益递减
160K/1300 的完整并发扫描揭示了更多细节:
| P/D(比例) | 并发 C | 总 TPM | 输出 TPM | TTFT p99 | 备注 |
|---|---|---|---|---|---|
| 8P4D(2:1) | 8 | 688,345 | N/A¹ | 69.3 s | |
| 8P4D(2:1) | 16 | 1,080,916 | N/A¹ | 141.6 s | |
| 8P4D(2:1) | 32 | 1,091,671 | N/A¹ | 277.3 s | C16→C32 吞吐几乎不动 |
| 8P4D(2:1) | 64 | 1,091,671 | N/A¹ | 546.9 s | 再加并发只涨延迟 |
| 10P2D(5:1) | 16 | 1,301,401 | N/A¹ | 141.3 s | |
| 10P2D(5:1) | 32 | 1,306,779 | N/A¹ | 254.3 s | C16→C32 仅 +0.41%,TTFT +80% |
¹ 本表沿用旧窗口报告的总 TPM 摘要,尚未从该组逐请求账本独立复算输出 TPM,不按目标输入/输出比例补造。完整窗口局限见来源报告。
三轮确认:3:1/C24 中位 1,220,736 TPM(极差/中位 3.96%),5:1/C32 中位 1,306,779(极差 0.82%),中位数之比 +7.05%。224K、288K 点上 5:1 相对 3:1 分别高 9.32%、13.68%。
结论不是"5:1 最优",而是:
- 提高 P 占比对长输入吞吐有正向收益,但 C16→C32 的边际收益已接近零——该点继续加并发没有明显吞吐收益;排队发生在哪一侧还需阶段计时证明;
- 5:1 的 TTFT p99(252–254s)显著差于 3:1(204–209s)——两行并发也由 C24 变成 C32,不能将延迟差异全部归因于 D 数量。配比是吞吐与延迟的权衡,应由业务 SLO 决定;
- 352K/1300 只有 8P4D 成功(1,036,437 TPM),其他比例出现 HTTP503 和协议失败——记为"该配置尝试失败",不能写成"352K 以上全部失败",未尝试的格子更不能画进曲线。
5. 难点与解决:参数探索中的 OOM 与归因纪律
380K 输入在基线参数(chunk/max-prefill 4096、static 0.60、context 1048576)下直接报 KV 相关错误。我们做了 T1–T6 六组参数探索(均为 8P4D/C32 探针):
| 实验 | 变更 | 380K 探针结果 | 当时的解读 vs 复核结论 |
|---|---|---|---|
| T1 | chunk 16384 | 1/4 成功,其余 OOM | 大 chunk 下工作区不足的线索 |
| T2 | T1 + static 0.75 | 0/4 OOM | 提高 static 未改善,不继续盲增 |
| T3 | chunk 16384 + static 0.50 | 1/4 仍 OOM | 降 static 尚不足 |
| T4 | T3 + context 524288 | 1/4 OOM | 需核对 KV 池与 indexer 工作区 |
| T5 | T4 + MR2 | 0/4,OOM + 传输异常 | 新变量与故障混合,不能认定 MR2 有害 |
| T6 | chunk 8192 + static 0.50 + context 524288 + MR4 | 完成窗口:1,016,882 TPM | 相对基线是多变量组合 |
OOM 堆栈指向稀疏注意力 indexer(_low_ratio_index_topk_extend)单次约 5.5 GiB 的分配——支持"chunk 过大挤占工作区"的假设,但"chunk 减半即修复根因"仍需同配置对照证明。
6. 效果与总结
PD 分离在我们实践中的定位:
| 收益 | 证据 | 边界 |
|---|---|---|
| 最高观察值 | 128 卡 12P4D 达 708 万总 TPM(80K/600,GLM 系矩阵最佳) | 单模型单负载峰值,不可跨模型移植 |
| 隔离调度干扰 | P/D 运行在独立实例 | 本文没有同资源、同负载的拆分前后停顿对照,不报收益倍数 |
| P/D 独立扩缩 | 1P1D→2P1D→3P1D 只需增减完整副本 | 扩 P 只处理新流量;缩 P 不增加 D 算力 |
| 配比可按负载调整 | 96 卡上 5:1 比 3:1 高 7.05%(160K) | 单模型单负载结论,不可移植 |
给准备做 PD 分离的团队四条建议:
- 先把单实例和 1P1D 做正确(鉴权、取消、传输失败、排水),再谈池化;池化收益的对照要求"固定组 vs 共享池、同 GPU 总数",1P1D 本身证明不了池化收益;
- 配比扫描固定 GPU 总预算,同时报告总 TPM、TPM/卡、TTFT——只报总 TPM 会把"加卡"伪装成"配比优";
- 每个模型的并发与配比自己扫,并区分"已测失败"与"未尝试";
- 多变量参数包有效就先当"候选包"用,别急着写成"某参数的最佳实践"。
下一篇深入 PD 分离的另一半——KV 如何跨实例、跨介质流动:GPU→内存→SSD 三级 KV 缓存:Mooncake 分层实战与跨引擎复用陷阱。
系列目录
- KV Cache 与前缀缓存:从原理到 99.9% 命中率的生产实践
- PD 分离实战:Prefill/Decode 拆池、配比拐点与容量规划(本篇)
- GPU→内存→SSD 三级 KV 缓存:Mooncake 分层实战与跨引擎复用陷阱
- Kimi-K3 原始 MXFP4 权重512K 长上下文生产部署:双机 16 卡 TP8×PP2 与 CUDA Graph 调优
- 大模型压测方法论:如何让性能数据可信
- 推理集群可观测性:64 卡监控体系与缓存证据链
- MiniMax-H3视频生成服务化:ComfyUI 多 GPU 编排与长任务生命周期
- 掉卡排查实录:RTX 5070 反复 Xid 79,如何排查建立证据链
- GPU对调定位法:8 卡 RTX 6000D 掉卡,如何用对调缩小故障范围
- CUDA 802 不是 NCCL 网络故障:H20 NVSwitch 物理链路排查实录
- DeepSeek-V4.1-Flash 双机 16 卡 H20 部署压测:TP8×DP2 与跨机 EP 的边界
数据声明:本文数据来自受控内部测试环境;380K/7000 等重负载成绩为合成容量轨(ignore_eos),不代表正文质量或可售产能。