跳到主要内容
推理优化

7.5 解耦架构的挑战与配比:调度复杂度与 P/D 池资源推导

解耦引入的调度复杂度、P/D GPU 池配比推导(基于负载特征)、池内利用率与弹性、何时该用/不该用解耦的决策框架

PD解耦资源配比调度复杂度容量规划弹性伸缩

解耦不是免费午餐:7.2 说过”解耦不免费”,这一节把账单列清楚——调度复杂度(两跳调度、状态传递、失败处理)和资源配比(P/D 池各多少卡)。配比是解耦部署最实操的问题:卡给少了 P 池排队 TTFT 爆,给多了浪费。本节给出基于负载特征的配比推导方法,以及”该不该解耦”的决策框架。

📑 目录


1. 解耦引入的复杂度全景

混合架构是一个调度器、一个池;解耦变成两个调度器、两个池、一条链

混合:  [请求] → [调度器 → GPU 池]
解耦:  [请求] → [P 调度器 → P 池] ──KV──→ [D 调度器 → D 池] → 响应
              └── 路由/队列管理 ──┘   └── 状态协调 ──┘

新增的复杂度维度:

维度混合架构解耦架构
调度器1 个2 个(P/D 各自)+ 路由层
状态单实例内存态跨实例状态(请求的 KV 归属、进度)
失败模型进程挂 = 请求挂P 挂了 D 还在等 KV(悬空请求)
调参面TP/并发/batch每池各一套 + 传输配置 + 配比

💡 提示:复杂度不是”理论上的多”,而是”排错时的多”——混合架构里一个请求的完整生命周期在一份日志里;解耦后要跨两个实例、一条传输链路才能还原一条请求。生产排错工具(trace ID 贯穿、统一日志)在解耦部署里是必需品而非可选项。


2. 调度复杂度:从一跳到两跳

2.1 路由层

请求到达后,路由层决定送哪个 prefill 实例。路由要考虑:

  • 负载均衡:P 池各实例的队列深度
  • 亲和性:相同前缀的请求尽量送同一实例(利用 APC/前缀缓存,7.3 的 KV 复用)
  • 预测性:按请求特征(prompt 长度)预估 P 实例负载

2.2 D 实例的选择

KV 传完后,还要决定 KV 送哪个 decode 实例——这引入一个混合架构没有的问题:KV 的”位置”决定了 D 实例(KV 在哪,decode 就得到哪),调度自由度下降。高端方案(Mooncake 的池化 KV)把 KV 和 D 实例解耦,让 D 实例自由选择——但代价是更重的 KV 存储层。

2.3 失败处理

  • P 实例失败:已传出的 KV 悬空——需要”孤儿请求”回收机制(D 侧超时释放)
  • D 实例失败:请求的 KV 还在池里,可以重试路由到另一个 D 实例(解耦的隐藏红利:D 层天然可重试)
  • 传输失败:需要重传语义(幂等 insert / 超时 drop_select)

📌 关键点:解耦把”请求生命周期”从单进程扩展为分布式事务——调度器必须处理部分完成状态。生产解耦系统的成熟度,很大程度看失败处理是否完备:孤儿 KV 回收、超时、重试、幂等。这些在混合架构里根本不存在,是解耦的”隐性税”。


3. 配比推导:从负载特征到 P/D 卡数

3.1 核心思路

P/D 池的配比由负载特征决定,两个关键量:

  • prefill 负载:请求速率 × 平均 prompt 长度(token/s 的 prefill 处理需求)
  • decode 负载:并发请求数 × 平均输出速率(token/s 的 decode 需求)

3.2 需要的输入

变量含义来源
RR请求速率(req/s)压测/生产监控
LpL_p平均 prompt 长度负载画像
LdL_d平均输出长度负载画像
PprefillP_{\text{prefill}}单卡 prefill 吞吐(token/s)实测(TP 配置下)
PdecodeP_{\text{decode}}单卡 decode 吞吐(token/s)实测(并发下)

3.3 推导公式

P 池卡数(prefill 是”吞吐型”负载,按 token/s 计算):

NP=R×LpPprefill×利用率目标N_P = \frac{R \times L_p}{P_{\text{prefill}} \times \text{利用率目标}}

D 池卡数(decode 是”并发型”负载,按并发请求数计算):

ND=峰值并发×Ld/Ld生成速率Pdecode×利用率目标N_D = \frac{\text{峰值并发} \times L_d / L_d \text{生成速率}}{P_{\text{decode}} \times \text{利用率目标}}

等价地(用请求速率表示):

ND=R×LdPdecode×利用率目标N_D = \frac{R \times L_d}{P_{\text{decode}} \times \text{利用率目标}}

💡 提示:两个公式长得一样,但含义不同——P 池按”每请求必付”的 prompt 吞吐配卡,D 池按”输出总量”配卡。两者的比值就是 P/D 配比:

NPND=Lp/PprefillLd/Pdecode\frac{N_P}{N_D} = \frac{L_p / P_{\text{prefill}}}{L_d / P_{\text{decode}}}

配比只由”负载长度比 × 单卡能力比”决定——负载画像一变,配比就要重算。这就是为什么”配比是动态的”。

3.4 峰值余量

公式里的”利用率目标”必须 < 100%:

  • 预留 SLO 余量(7.4 的 P99 成本)——建议 60-80%
  • 突发缓冲——建议额外 20-30%
  • P 池余量更关键:P 池超载直接打 TTFT(D 池超载只打尾部 TPOT),且 P 池突发更难预测(prompt 长度方差大)

4. 配比公式的实操:一个完整例子

场景:对话服务,峰值 20 req/s,平均 prompt 2K token,平均输出 500 token。单卡实测(TP=1):prefill 吞吐 3K token/s(4K 长 prompt 场景),decode 吞吐 800 token/s(并发 64)。

P 池

NP=20×20003000×0.719 卡N_P = \frac{20 \times 2000}{3000 \times 0.7} \approx 19 \text{ 卡}

D 池

ND=20×500800×0.718 卡N_D = \frac{20 \times 500}{800 \times 0.7} \approx 18 \text{ 卡}

配比 ≈ 1:1,总 37 卡——如果负载变成”长文档总结”(prompt 8K、输出 200):

NP=20×80003000×0.776 卡,ND=20×200800×0.77 卡N_P = \frac{20 \times 8000}{3000 \times 0.7} \approx 76 \text{ 卡}, \quad N_D = \frac{20 \times 200}{800 \times 0.7} \approx 7 \text{ 卡}

配比变成 11:1——同一个服务,负载特征一变,最优配比差一个数量级!

📌 关键点:这个例子说明两件事:① 配比必须按自己的负载画像算,抄别人的配比没用;② 负载画像会变,配比要定期重算。工程实践:把配比推导做成脚本(输入监控数据、输出建议配比),每季度/负载变化时重跑。


5. 池内效率与弹性伸缩

5.1 池内效率的差异

  • P 池:burst 型负载(prompt 到达波动大),效率低时卡闲置严重——但 P 池必须留余量(TTFT 敏感)
  • D 池:稳态型负载(输出持续生成),利用率更稳定——但 decode 的并发一旦降下来(低峰),卡同样闲置

5.2 弹性策略

策略做法适用
P 池弹性低峰缩 P 池(合并到 D 池),高峰扩回prompt 峰谷明显的服务
D 池弹性按并发数伸缩(K8s HPA 按排队长度)输出量随用户量波动
角色互换(TaiChi 思路)实例可在 P/D 角色间切换负载特征变化大的多租户

5.3 弹性与 KV 的冲突

弹性伸缩和 KV 传输有冲突:D 实例缩容时,它持有的 KV 怎么办? 池化 KV(Mooncake/LMCache)能解耦”KV 存储”与”D 实例”,让弹性无痛;非池化方案(直接 P→D 传输)缩容会丢 KV,需要重 prefill 或优雅排空。

💡 提示:这解释了为什么生产解耦系统(Mooncake、LMCache)都往”KV 池化”方向走——池化不只是为了复用,更是为了让池子可伸缩。KV 从”跟着实例走”变成”存在池里”,实例就可以随意增删。


6. 决策框架:什么时候该解耦

把 7.1-7.5 的知识收拢成一个决策清单:

解耦的强信号(占 2 条以上值得做):

  • P95/P99 TPOT 抖动明显(7.1 的干扰特征)
  • 长 prompt 占比高(>2K token 的请求多)
  • TTFT 和 TPOT 都有严格 SLO(P95 级)
  • 峰值并发下 D 池排队(尾部持续劣化)
  • 有”独立调 TTFT/ITL”的诉求(模型/卡型要分开升级)

解耦的反对信号(占 2 条以上谨慎):

  • 短 prompt + 短输出(传输摊薄不了)
  • 单实例吞吐已满足 SLO(问题根本不存在)
  • 无严格分位点 SLO(均值能交代就行)
  • 网络 < 200Gb/s(传输吃掉收益,7.3)
  • 运维能力不足(跨实例排错、失败处理是硬门槛)

📌 关键点:决策的最终标尺还是 Goodput——在目标负载和 SLO 下,解耦前后的 Goodput 差多少。先按 7.4 的方法量出混合架构的 Goodput 基线,再估算解耦后的 Goodput 上限(用本节公式配卡),差值就是解耦的”投资回报率”。数字说话,别拍脑袋。


7. 解耦的运维风险清单

即使决策通过,运维上还有这些坑要提前排:

风险缓解
跨实例请求追踪困难统一 trace ID(路由层生成,贯穿 P/D)
孤儿 KV 泄漏(P 挂/超时)过期回收任务(TTL 扫描)
传输链路故障传输健康检查、自动重路由
配比过时(负载漂移)配比脚本定期重算 + 弹性伸缩
版本不一致(P/D 引擎版本不同)发布流程强制同版本
扩容时 KV 分布不均池化 KV / 优雅排空后扩容

💡 提示:解耦部署的验收不只是”功能通”,要专门演练故障场景:P 池挂一台卡、传输断 30 秒、D 池缩容一半——每个场景都要有明确的行为预期(重试?降级?拒绝?)。把故障演练写进发布清单,解耦才敢上生产。


📝 总结

  • 复杂度税:两跳调度、跨实例状态、孤儿 KV、分布式失败处理——排错成本翻倍
  • 配比公式NP=R×Lp/(Pprefill×u)N_P = R \times L_p / (P_{\text{prefill}} \times u)ND=R×Ld/(Pdecode×u)N_D = R \times L_d / (P_{\text{decode}} \times u),配比 = 负载长度比 × 能力比
  • 配比是动态的:负载画像一变配比就差一个数量级,要脚本化重算
  • 弹性与 KV 冲突:池化 KV(Mooncake/LMCache)是弹性伸缩的前提
  • 决策框架:强信号/反对信号对照 + Goodput 差值算 ROI
  • 运维:trace ID、孤儿回收、故障演练三件套

🎯 自我检验清单

  • 解耦引入的三类复杂度是什么?为什么排错成本翻倍?
  • P/D 配比公式的每个变量怎么获取?配比为什么随负载变化?
  • 给定负载(R、Lp、Ld、单卡能力)口算配比?
  • 为什么”P 池余量比 D 池余量更关键”?
  • 弹性伸缩与 KV 传输的冲突是什么?池化 KV 为什么能解?
  • 用决策清单评估你的服务该不该解耦?
  • 解耦上生产前要演练哪三类故障?

📚 参考资料