PP × 组合场景
从主报告拆出的组合场景专辑:TP / DP-attention / CP / EP 与流水线并行的组合机制、 chunked prefill 动态分块、PD 分离 × Mooncake、EAGLE 推测解码及其传递链。
→ / 空格 下一页 · ← 上一页 · ← 返回主 deck
本页引用 — 总纲 issue #22607 · 代码基线 sglang/srt(行号基于 2026-08 基线,链接指向 main)
DPA + PD + PP + Mooncake + EAGLE:先说结论
server_args.py:6832 硬断言 pp_size > 1 → speculative_algorithm is None。
且 decode 端要跑 EAGLE,prefill 端必须同配 EAGLE(才会产出 hidden states / topk 给 decode 起 draft)。
所以现实形态只有两种:A = PP-prefill + 普通 decode(无 EAGLE);B = TP/DPA-prefill(+EAGLE 采集) + EAGLE-decode(无 PP)。下图画的是形态 B。
| 组合 | 结论 | 依据 |
|---|---|---|
| PP × 任意 speculative | ❌ 硬断言互斥 | server_args.py:6832(PP 同时强制关 overlap schedule) |
| EAGLE × DP attention | ✅ 允许 | 可加 --speculative-skip-dp-mlp-sync;STANDALONE / DFLASH / NGRAM × DPA 均 ❌ |
| PD-decode radix cache × spec | ❌ 互斥 | pd_disaggregation_hook.py:41;decode 默认本就关 radix(chunk cache) |
| PD-prefill | 强制 disable_cuda_graph | pd_disaggregation_hook.py:78 |
| DP attention × PP | ✅ 允许 | DPController 按 pp_rank × tp_rank 双重循环拉起(data_parallel_controller.py:481) |
| TP × PP | ✅ 基础组合 | 三个通信平面正交(第 3 页);pp>1 强制关 overlap schedule(server_args.py:6832) |
| CP × PP | ✅ 互斥已解除 | 旧 assert pp_size==1 已删(server_args.py:5166 起仅整除检查);DSA prefill CP 专门适配了 PP 传输(第 5 页) |
| EP × PP | ✅ 无互斥断言 | EP 只活在每层 MoE 内部,与 PP 互不知晓(第 5 页);moe_dp>1 时例外(server_args.py:5187) |
本页引用 — 源码:data_parallel_controller.py · server_args.py(行号基于 2026-08 基线,链接指向 main)
TP × PP:切宽 × 切深,三个通信平面
TP 把每一层切「宽」(head / 矩阵列),PP 把模型切「深」(层段)。world = pp × tp,每个 (pp_rank, tp_rank) 都是一个独立 Scheduler 进程(engine.py:618 双重循环拉起)——两把刀维度正交,靠三个各司其职的通信平面粘合。
send_tensor_dict 的带宽优化);① 每调度步一次、纯 CPU 消息。三者互不阻塞——① 走 gloo,②③ 走 NCCL 设备流。请求经「ZMQ → TP 广播 → PP 点对点接力 → 再 TP 广播」三级分发:PP 间只有 attn_tp0 互相 relay,到站后组内广播(scheduler_pp_mixin.py:2141)。KV cache:字节分家,元数据必须同构
- MHA:
token_to_kv_pool每 rank 只存num_heads/tp个 head 的 K/V——同一 token 的字节散在 tp 个 rank 上,缺一个 rank 数据即不完整;MLA:latent 每 rank 全量相同 → L3 只让 tp0 写(主 deck 第 4 页卡片 ③)。 - radix 树 / req_to_token:每 rank 一棵/一份,树形与 slot 编号必须跨 tp × pp 全员同构(主 deck 第 23 页)——slot 编号即「第 i 个 token」,各 rank 用同一编号找到自己那份字节。
- 推论:HiCache 的元数据共识要覆盖整个网格——prefetch 命中长度在
_create_sync_groups建的 attn_cp / attn_tp / tp / pp 各同步组上逐组all_reduce(MIN)(cache_controller.py:1141),PP 维靠 pp_sync / MIN 投票(主 deck 第 13 / 16 页)。
一致性原语与两种失效模式
- 采样一致:last stage 各 tp rank 各自采样,靠「同输入 + 同 seed」确定性;grammar 或
SYNC_TOKEN_IDS_ACROSS_TP时改为all_reduce(MIN)硬对齐 token id(sampler.py:382;DPA 下同步组换成 attn_tp 组)。 - 失效模式不对称(主 deck 第 11 页):TP 错位 = 两个不同 collective 相遇 → 立即挂死(形态 C,#30760);PP 错位 = proxy tensor 行数不符 → 迟发 shape mismatch(形态 A)。TP 是快刀,PP 是慢性病。
- 组合约束:pp > 1 强制关 overlap schedule、禁 speculative(
server_args.py:6832);chunked prefill 不但允许,还有 PP 专属的动态分块机制(第 6 页)。
本页引用 — PR / issue:#30760 | 源码:cache_controller.py · engine.py · parallel_state.py · sampler.py · scheduler.py · scheduler_pp_mixin.py(行号基于 2026-08 基线,链接指向 main)
DP attention:attention 各算各的,MoE 合起来算
global_num_tokens——这就是调度侧每步都要做 MLP sync 的原因。(layers/dp_attention.py:596 / communicator.py:1078)# 调度侧每步的全局对齐(dp_attn.py:143) local = num_tokens(batch) # decode=bs, extend=Σextend, 空=0 MLPSyncBatchInfo.all_gather() # 一次拿全局 num_tokens/mode if max(global_num_tokens) > 0 and 本 rank 没活: batch = get_idle_batch() # 必须发起 idle batch!否则 MoE 的 # all-gather 缺席 → 全员挂死
- rank 布局:
attn_tp_size = tp_size / dp_size / cp_size,layout = (dp, cp, tp),tp 变化最快(dp_attention.py:245)。 - 收请求:每 dp 组的 attn-tp-rank0 各收各的;工作请求只在组内 broadcast,控制消息全 tp 组 broadcast(
request_receiver.py:246)。 - 参数副作用:开 DPA 后
schedule_conservativeness ×0.3、chunked_prefill_size ÷= dp_size(server_args.py:5207)。 - overlap 决策要用全局量:spec+DPA 下用 all_gather 出的
is_extend_in_batch而非本地 mode,否则各组决策分叉 → 死锁(scheduler.py:1753)——与 Part 2 同款「本地状态→全局决策」定律。
本页引用 — 源码:dp_attention.py · scheduler.py · server_args.py(行号基于 2026-08 基线,链接指向 main)
CP × PP:互斥已解除;EP × PP:天然正交
各并行维度与 PP 的关系分三档:TP / DPA / EP 直接叠(切的维度不相交),speculative / PD-Multiplexing 被断言禁止,CP 则走完了「从 assert pp_size == 1 到打通」的全程——最新代码已移除互斥断言,并为 DSA prefill CP 适配了 PP 传输层。本页讲 CP 切了什么、怎么打通,以及 EP 为何对 PP 完全无感。
token_idx % cp_size 让每个 rank 摊到长短均匀的一篮子。输出经 all_gather + transpose 复原全序列,CP 的存在对上下游算子完全透明。CP 切的是什么:序列维,且只切计算
- 从 TP 里再分一刀:
attn_tp_size = tp / dp / cp,rank layout (dp, cp, tp)(dp_attention.py:245)——CP 组内各 rank 持相同权重,各算一部分 token;DSA CP 下要求attn_tp_size == 1(o_proj 的部分和不做 attn-TP all-reduce)。 - 两种策略(
cp_strategy,server_args.py:3712):interleave(上图,token_idx % cp_size,要求 dp_size==1;旧名 round-robin-split)与 zigzag(要求 DPA + DeepEP +ep=tp)。 - 现状边界:仅 prefill、仅 DeepSeek V3.2 DSA、Hopper 实验性、跨机有精度问题 → 限单机
tp_size ≤ 8(server_args.py:3723);PD 分离下只在 prefill 侧。
CP × PP:互斥已解除,靠传输层适配
旧版曾对通用 attn_cp 直接 assert pp_size == 1;最新代码已移除这条断言(server_args.py:5166 起只剩整除与 fusion 检查),仅 moe_dp_size > 1 仍禁 PP(:5187)。
DSA prefill CP 与 PP 同开还做了专门适配(#16380 引入,后随 NSA→DSA 更名):CP 下 attention 权重各 rank 复制、hidden 全量,PP 边界因此跳过第 3 页的「切 1/tp + all_gather」、proxy tensor 全量直发(require_attn_tp_allgather=False,scheduler_pp_mixin.py:1744)。
能打通的原因正是主 deck 第 9 页判据:CP 切分纯在算子内部、不给 ③ 组批引入 per-rank 输入——所以只需改传输层,不需要新共识。
EP × PP:天然正交,零断言
- EP 切 MoE 的 expert 维:
moe_tp_size = tp / ep / moe_dp(parallel_state.py:2198起),expert 权重按 rank 分桶,token 靠 all-to-all(DeepEP)去找 expert、算完回来。 - 全程发生在每层 MoE 内部:PP 边界传的仍是 hidden states,EP 与 PP 互不知晓——代码里没有任何 ep × pp 互斥断言。
- 对 KV / HiCache 零影响:EP 不碰 attention,KV 字节与元数据与纯 TP 完全相同。约束都在 TP 侧:
ep × moe_dp ≤ tp,且ep > 1时须== tp(server_args.py:5185)。
server_args.py:6832);CP 正因为两条都不沾,互斥断言已被移除、DSA 路靠传输层适配打通(本页),只有 moe_dp>1 的组合仍 ❌(:5187)。
本页引用 — PR / issue:#16380 | 源码:dp_attention.py · parallel_state.py · scheduler_pp_mixin.py · server_args.py(行号基于 2026-08 基线,链接指向 main)
chunked prefill × PP:动态分块(--enable-dynamic-chunking)
chunked prefill 与 PP 相性天生不好:同样 token 数的 chunk,history 越长 attention 越贵,而 PP 的 microbatch 流水在 ★ 同步点锁步——最慢的 chunk 拖住整环。最新修复给 PP 配了专属方案:把「等 token 数」换成「等耗时」,且延迟这个 per-rank 量只在启动阶段进入系统一次。
per-rank 量只进入系统一次:画像 → 广播 → 纯函数
- PP0 单点画像(
scheduler_pp_mixin.py:1776):启动时构造 128 个 token 数递减的 dummy 请求逐个前向实测延迟;DPA 下还须填global_num_tokens(本 dp 组 = 长度、其余 0)发起 idle batch 参与 MLP sync(:1831,#17339)——呼应第 4 页的「必须发起 idle batch」。 - 广播原始样本:先 attn_tp 组内 broadcast,再
pp_group.broadcast_object_list(src=0)(:1930)——发的是 (seq_lens, latencies) 原始数据,不是各 rank 自己测。 - 全员同拟合:每个 rank 用同一份数据跑同一个确定性
lstsq(:2654;丢弃首样本防无 warmup 偏差 #17198,样本 < 8 或 a ≤ 0 拒绝拟合)。此后predict(history_len)是纯函数,history_len 本受请求流契约保护——全网格解出同一个 chunk size,运行时零通信。 - 失败安全:画像抛异常 → 告警并退回固定 chunk(
scheduler.py:1058);预测无解返 None → 用 base。
与 #27285 / #27010 同款范式(主 deck 第 13 / 16 页):不对齐 IO、只对齐决策输入——延迟这个 per-rank 量在启动时被采样一次,随即转为协议数据。
落点与修复沉淀
- 只作用于续 chunk:组批时仅当
chunked_req非空才预测(scheduler.py:3130)——首 chunk 用 base,后续按 history 递减;prefill buffer 按 1.25×base 预留探测余量(server_args.py:5368)。 - 修复沉淀:#15372 采样 32→128 点、对齐下限 page→max(page, 64);#16140 chunk 下限 base/4——防长 history 下解出微小 chunk、固定开销反超收益;#17198 拟合丢首样本;#17339 DPA 画像参与 collective。平滑系数
SGLANG_DYNAMIC_CHUNKING_SMOOTH_FACTOR=0.75防尺寸骤降。 - centralized 方案再进一步:本地 plan-driven 分支里,chunk 尺寸随
extend_lens、chunked_rid编入PPPrefillPlan由 PP0 统一下发,其余 rankbuild_batch_from_plan镜像 chunked_req 指针(scheduler_pp_mixin.py:143/794)——从「全员同构地各自算」升级为「单点算、协议发」。
chunk 与缓存的交界不变:每片结束仍走 cache_unfinished_req 入树 + 锁移交(主 deck 第 8 / 22 页),动态的只是「下一片切多长」。
本页引用 — PR / issue:#15372 · #16140 · #17198 · #17339 · #27010 · #27285 | 源码:scheduler.py · scheduler_pp_mixin.py · server_args.py(行号基于 2026-08 基线,链接指向 main)
PD 分离 × Mooncake:KVPoll 五态与两个数据平面
与 HiCache 的联动即主 deck 第 20 页那条链:decode 端也可开 L3 prefetch(#26227),abort 时走 _clean_hicache_prefetch_resources;PP+PD 下 bootstrap/release 决策由共识统一(#31869 / 本地 centralized 方案)。
本页引用 — PR / issue:#26227 · #31869(行号基于 2026-08 基线,链接指向 main)
EAGLE:draft–verify 循环,与 target 共享 KV 池
req_to_token_pool 与 KV allocator(draft 只多出自己那几层的 KV);被接受路径的 KV slot 会被压实到 block 前端(_finalize_accept_tree_path,topk>1 时最微妙的一步)。(speculative/eagle_worker_v2.py:1074 双分支)- 与 radix cache 的交互:EAGLE 下
RadixKey全部转 bigram 视图——draft KV 绑定 (prev, cur) token 对,普通 unigram 前缀语义会错配(radix_cache.py:139)。 - 超额分配:每轮按预留量分配而非 +1(
kv_allocated_len先行,kv_committed_len只随接受推进);结束时pop_overallocated_kv_cache()释放被拒段。 - retract 限制:spec 下只能从队尾踢请求(filter API 限制,
schedule_batch.py:2511)。 - req_to_token 行宽为 draft 预留
4 + max_draft_tokens列(common.py:253)。
# prefill 分支顺带为 decode 备好 draft 起点(:1074) if batch.forward_mode.is_extend(): batch.capture_hidden_mode = FULL # target 吐全量 hidden out = target_worker.forward_batch_generation(batch) draft_worker._draft_extend_for_prefill(...) else: verify_input = draft_worker.draft(batch) out = self.verify(batch) # 含 eagle_sample draft_worker._draft_extend_for_decode(...)
本页引用 — 源码:common.py · radix_cache.py · schedule_batch.py(行号基于 2026-08 基线,链接指向 main)
PD + EAGLE:hidden states 的三跳旅程
decode 端的第一轮 draft 需要 target 模型在 prefill 时的 hidden states 和 top-k 分布——这些不走 KV 平面,而是搭 aux buffer 的便车。
bootstrap_room 会随 buffer 一起校验,防 aux slot 撞车。(prefill.py:658 / utils.py:395 / decode.py:1562 / eagle_disaggregation.py:17)cache_finished_req 入树、释放超额 draft KV。abort 在任何一跳发生,都走主 deck 第 18–19 页的路径。
本页引用 — 总纲 issue #22607 · 代码基线 sglang/srt(行号基于 2026-08 基线,链接指向 main)