Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions docs/benchmarks/qwen3-14b-pd-vs-mix-h200.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Qwen3-14B P/D 分离 vs mixed vs vLLM(8×H200,多轮长输入负载)

> **TL;DR**: Qwen3-14B bf16 多轮长输入(首轮 8k + 每轮 2k)A/B(2026-07-17):等卡数下 P/D 全面胜出——2 卡 1P+1D 比 mixed×2 吞吐 +17%(354 vs 303 tok/s)、TPOT p99 -42%(22.8 vs 39.4ms)、**ITL p99 23.5 vs 105ms(-78%)**;对 vLLM 0.25.1×2,vLLM 吞吐/decode 内核更快(453 tok/s、TPOT p50 16.7ms),但 ITL p99 141ms 是 P/D 的 6 倍。重负载(60 会话,KV 1.9× 超 D 的 HBM)下 200GiB hugepage host 池 vs 16GiB 小池:小池丢 decode 纯净性(ITL p99 23.6→84.6ms)+吞吐 -9%。弹性 2P+2D 重负载(openinfer [#705](https://github.com/openinfer-project/openinfer/pull/705) 修复后、每配置冷启动重测):router round-robin ITL p99 81ms → **前缀亲和 22.8ms + ~270 tok/s**(pegaflow [#405](https://github.com/novitalabs/pegaflow/pull/405)),达到 1P+1D 的隔离度和 1.4× 其吞吐;裸吞吐低于 2 卡 mixed 的 359——重负载下 P/D 买的是 decode 纯净性(ITL p99 22.8 vs 107.5),不是每卡吞吐。⚠️ 首版 ladder 数据(437/465/495 tok/s)因同栈固定 seed 复跑全量前缀命中而虚高,已作废(见 §6)。

环境:单机 8×NVIDIA H200,每 GPU 1×400G IB NIC(P2P KV 走单边 RDMA READ),Qwen3-14B bf16(40L/40H/8KV/head 128,KV 160 KiB/token;单卡 HBM KV 容量 626k tokens),openinfer main `c116077`(§4 复测用 [#705](https://github.com/openinfer-project/openinfer/pull/705) 分支 `a3b3ecac`),pegaflow `111ea34`(0.23.4)+ 亲和 router 补丁(#405),vLLM 0.25.1。每实例 `--kv-offload --kv-offload-host-gib <N> --kv-offload-hugepages`(机器预留 1.5 TB 2MiB hugepages,200 GiB 池 NUMA 感知 2×100GiB,分配 ~5s/池)。

## 1. 负载与压测方法

vllm-bench 多轮对话,输入比 [Qwen3-8B 那轮](qwen3-8b-pd-vs-mix-h200.md) 拉长一倍:

```bash
vllm-bench \
--backend openai-chat --base-url http://<endpoint> \
--model <model-path> --tokenizer <model-path> \
--dataset-name random \
--multi-turn --multi-turn-num-turns 5 \
--random-input-len 8192 --per-turn-input-len 2048 --random-output-len 128 \
--num-prompts <N> --multi-turn-concurrency <C> \
--extra-body '{"min_tokens":1}' --temperature 0 \
--percentile-metrics ttft,tpot,itl,e2el --metric-percentiles 50,99 \
--save-result --result-filename <name>.json --result-dir <dir>
```

- **标准负载**:20 会话、并发 10(会话终态 ~17k tokens ≈ 2.7 GiB KV,总量 ~53 GiB < 单 D HBM)。
- **重负载**:60 会话、并发 12(总 KV ~1.02M tokens ≈ 160 GiB,1.6× 单 D 的 626k HBM 容量——host 池被真实使用)。
- 每组配置测前重启整栈冷起(清 HBM 前缀缓存 + host tier + metaserver 目录)。
- mixed 多实例前面挂会话亲和 LB(首条消息 hash 定实例);vLLM ×2 同法。
- `max_completion_tokens` 坑(openai-chat 用它不用 `max_tokens`)已由 pegaflow router 修复覆盖,无需 workaround。

## 2. 标准负载(20 会话 × 5 轮,并发 10)

| 配置 | 卡数 | out tok/s | TTFT p50/p99 (ms) | TPOT p50/p99 (ms) | ITL p99 (ms) |
|---|---|---|---|---|---|
| mixed ×1 | 1 | 264 | 239 / 5007 | 31.9 / 46.6 | 107.4 |
| mixed ×2 | 2 | 303 | 560 / 3970 | 25.3 / 39.4 | 105.0 |
| **P/D 1P+1D** | 2 | **354** | 493 / 4653 | **19.7 / 22.8** | **23.5** |
| vLLM 0.25.1 ×2 | 2 | 453 | 577 / 3205 | 16.7 / 36.0 | 141.2 |
| mixed ×4 | 4 | 356 | 397 / 2839 | 22.3 / 33.0 | 96.7 |
| **P/D 2P+2D** | 4 | **409** | **312 / 2665** | **19.3 / 22.5** | **22.8** |

分轮(p50 TTFT / p99 ITL,ms):

| Turn | mixed×2 | P/D 1P+1D | vLLM×2 | mixed×4 | P/D 2P+2D |
|---|---|---|---|---|---|
| 1(冷 8k) | 1001 / 82 | 2734 / 18 | 833 / **387** | 889 / 79 | 840 / 22 |
| 2 | 242 / 88 | 492 / 20 | 431 / 117 | 248 / 87 | 265 / 20 |
| 3 | 351 / 100 | 336 / 27 | 579 / 136 | 271 / 95 | 289 / 21 |
| 4 | 628 / 106 | 302 / 26 | 546 / 141 | 318 / 102 | 312 / 23 |
| 5 | 749 / 109 | 499 / 24 | 488 / 17 | 333 / 107 | 336 / 23 |

读法:

- **输入拉长一倍后,P/D 从"吞吐持平"变成"吞吐也赢"**:8B 4k 输入时 P/D vs mixed×2 吞吐持平;14B 8k 输入下 prefill 干扰变重,mixed 的 unified step 被长 suffix prefill 反复打断,P/D +17%。
- **ITL p99 是分水岭指标**:mixed/vLLM 全轮 80–140ms(decode 被并发 prefill 冻结),P/D 全轮 <27ms。TPOT p99 会平均掉冻结,ITL 不会。
- **vLLM 0.25.1 的 14B decode 内核比我们快**(TPOT p50 16.7 vs 19.7ms,吞吐 453 vs 354)——这是 qwen3 line 在 14B 上的内核差距(历史调优都在 4B/8B/RTX 5090),与 P/D 架构无关;vLLM 的 ITL p99 141ms 同样输给 decode 隔离。
- **P/D 冷 turn1 代价**:1P+1D 2734ms vs mixed×2 1001ms——10 并发 8k prefill 全压在 1 个 P 上排队 + P→D 交接。2P+2D 把它压回 840ms(低于 mixed×4 的 889ms)。M3 layer-wise push 进一步优化交接部分。

## 3. 重负载:hugepage host 池的价值(60 会话,并发 12,1P+1D)

总 KV ~160 GiB,单 D HBM 只装得下 ~95 GiB——host 池装不装得下工作集直接决定 decode 纯净性:

| host 池 | out tok/s | TTFT p50/p99 (ms) | TPOT p99 (ms) | ITL p99 (ms) |
|---|---|---|---|---|
| **200 GiB hugepage ×2** | **194** | 3695 / 11607 | **22.4** | **23.6** |
| 16 GiB ×2(对照) | 177 | 4128 / 11795 | 42.9 | 84.6 |

- 大池:P 的 host tier 保住全部会话前缀,D 每轮只 RDMA 拉新增 suffix,300/300 请求全程 ITL p99 <24ms。
- 小池:P 侧 evict → D metaserver 查询 miss → **D 被迫本地 prefill**(decode 节点被 prefill 污染),turn2/3 ITL p99 飙到 84–97ms,吞吐 -9%。
- 这就是 KV cache 分层与 P/D 一体的意义:**同一个 pegaflow 池既是容量层又是传输基底**——host 容量买到的不只是 prefix cache 命中,还有 decode 节点的纯净性。
- 注意两组 TTFT p50 都是秒级且逐轮爬升(turn5 p50 ~11s):重负载下 1 个 P 的 prefill 算力饱和,与池大小无关——解法是加 P(见 §4),不是加内存。

## 4. 弹性 xP+yD:router 亲和是必要条件(60 会话,并发 12,2P+2D)

pegaflow-router 原生支持 `--prefill/--decode` 各传多个端点(round-robin)。直接上 2P+2D 重负载暴露了 round-robin 的问题:同一会话的不同 turn 落到不同 P/D,前缀局部性被打碎,退化成跨节点全量 KV 搬运——P 从别的 P(甚至从 D!内容寻址 mesh 是全向的)拉 2.2 GiB 前缀(300 请求 ~80 次),D 每轮全量重拉而非增量。修复 = 前缀亲和选路(pegaflow [#405](https://github.com/novitalabs/pegaflow/pull/405))。下表为**每配置冷启动**、openinfer [#705](https://github.com/openinfer-project/openinfer/pull/705)(`a3b3ecac`)复测;亲和吞吐/TTFT 有 ±5-10% 轮间波动(三轮 267/278/338,取中值口径 ~270),TPOT/ITL p99 轮间完全稳定:

| 选路策略 | out tok/s | TTFT p50/p99 (ms) | TPOT p99 (ms) | ITL p99 (ms) |
|---|---|---|---|---|
| round-robin | 248 | 1277 / 14701 | 33.5 | 80.6 |
| **前缀亲和(P+D)** | **~270** | 1720 / 18011 | **22.5** | **22.8** |

- 亲和后 ITL p99 回到 1P+1D 的 23ms 隔离度,吞吐 1.4×(~270 vs 194),TTFT p50 从 1P+1D 的 3695ms 降到 ~1.7s(双 P 分摊 prefill 洪峰)。round-robin 的 TTFT p50 更低(会话摊到两个 P),但代价是决定性的:跨节点搬运把 decode 平滑度打回 81ms。
- 对照 2 卡重负载 mixed×2(359 tok/s、TPOT p99 47ms、ITL p99 107.5ms):4 卡 P/D 亲和栈裸吞吐仍低 ~22%——重负载下 prefill 算力是吞吐瓶颈,P/D 买到的是 TPOT/ITL 纯净性(22.5/22.8 vs 47/107.5),适合 SLO 约束的服务而非吞吐最大化。
- 任意 D 发现任意 P 已实测(2P+2D 下 D0/D1 均从两个 P 拉过块);亲和只是把"能跑"变成"跑得好"。
- 剩余的 round-robin ITL 81ms 不再是 bug:restore 后的 suffix prefill 与 decode 共享 unified step,step 被真实计算拉长(nsys:GPU 占空比 ~95% 无空洞)。亲和从源头消掉搬运,故 22.8ms。

### 4.1 restore 冻结 decode 的机制(nsys + A/B 实锤,openinfer [#704](https://github.com/openinfer-project/openinfer/issues/704) → [#705](https://github.com/openinfer-project/openinfer/pull/705) 已修复)

单独探测(8 路 decode + 4 个 16k 冷 restore 共存)复现出全部流**同一毫秒**同步 hiccup。nsys 排除了直觉解释:GPU kernel 占空比全程 ~95%、无 >10ms gap——不是 HBM 带宽也不是 SM 争抢(DMA 上限 64GB/s 仅为 HBM 4.8TB/s 的 ~1.3%)。真实机制是 **scheduler 线程被 CPU 侧簿记卡死**,两层:

1. **主因**:`commit_loaded_blocks` 在 scheduler 线程上逐块注册 restore 的块,~70µs/块(registry radix 插入 + event hook + 频率统计 + store 锁)。1000+ 块的大 restore = ~70ms 内所有流的 token 交付冻结。
2. **次因**:greedy token 回读用 pageable D2H(同步拷贝语义),被并发大批量拷贝排队,从亚毫秒平顶到 23.6ms/步。

修复(#705,单 PR):追根后发现 70µs/块中真实注册只有 ~0.8µs,其余是 `PositionalRadixTree` 内层 DashMap 的按需分配——分片数随核数缩放(192 核 = 1024 分片/新 position)而并发度结构性不可达(两个入口都返回外层 entry 独占写锁);内层改平 HashMap 后 1024 块 commit 36.4ms → 1.67ms(22×,1.6µs/块),1000+ 块 inline 提交 ~2ms、远小于一个 decode step,因此不需要任何分期/pacing 机制,scheduler 侧零改动。次因用 pinned host buffer 修 token 回读。中途试过并放弃的方案:每 tick 64 块分期(根因修掉后是死复杂度)、off-thread 注册线程(store 锁争抢)、Kernel 拷贝后端(抢 SM)。修后微探测(8 路 decode + 4×16k 冷 restore)尖峰从每次 restore 必现降到 **0**。

## 5. 正确性与故障门(14B 复验)

- **逐字节一致(限定条件)**:3 档 prompt(<1 page / 跨页 / ~600 tok),temp=0,router P/D vs 直连 D baseline。大传输档(33 块 82.5 MiB RDMA)与 <1 page 档稳定 IDENTICAL;跨页短 prompt 档在 #705 复验中暴露一个**非 P/D 缺陷**的近平局翻转——P/D 路径的 D 侧做 restore 后 suffix prefill,与本地整段 prefill 的 chunk 边界/GEMM shape 不同,Tuned 数值策略下 bf16 近平局 token 可各自合法翻转(换回修复前二进制冷跑同样复现,两个变体都连贯正确)。这与单机 prefix-cache 命中 vs 冷 prefill 的差异同类;engine 也因此拒绝 `--batch-invariant` + `--kv-offload` 组合(前缀命中会把 chunk 边界移出 request-local 网格)。字节级门只对数值路径一致的档位成立;传输无损性由大传输档 + logits 级 golden gate 保证。
- **P2P 实证**:~600 tok prompt D 侧 `RDMA fetch summary: blocks=33/33 bytes_mib=82.5`,连接复用后 rdma_wait 1.80ms(30.4 GiB/s)。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Correct the reported RDMA bandwidth or wait time

The stated transfer figures do not yield 30.4 GiB/s: transferring 82.5 MiB in 1.80 ms is approximately 44.8 GiB/s, while 30.4 GiB/s corresponds to about 2.65 ms. Since this is the P2P performance evidence, clarify whether 1.80 ms is RDMA-only and 30.4 GiB/s uses a larger end-to-end duration, or correct whichever value is inaccurate.

Useful? React with 👍 / 👎.

- **故障退化**:杀 metaserver → router 请求 WARN 后本地 prefill 正常完成;杀 P → D 冷请求正常完成。无 crash 无 hang。
- 全部压测 0 失败请求(含 300 请求重负载 ×5 组)。

## 6. 工具坑

- vllm-bench `accept-dist-20260709` 之后的私有构建在 multi-turn synthetic 模式下生成完对话即 **segfault**("timeout: the monitored command dumped core",exit 0 且无输出,极易误判为静默无请求);用 `before-accept-dist-20260709` 构建正常。
- vLLM 0.25.1 起服务需 `ninja` 在 PATH(venv 里 pip 装的 ninja 要把 venv/bin export 进 PATH),且 `--disable-log-requests` 已移除。
- **同栈复跑 = warm 污染**:vllm-bench synthetic 模式 seed 固定,同一栈上按顺序测多个配置时,后面的配置全量命中前面留下的前缀(host 池 + GPU cache),吞吐可虚高 ~70%(本文首版 2P+2D ladder 437/465/495 即此坑,冷启动复测实为 248/~270)。协议必须是**每配置重启栈**(清 GPU/host 池 + metaserver),或换 seed。warm 复跑只可用于刻意的 restore 压力测试,并须标注。

## 7. 关联

- P/D 架构与 M2 验收:`../models/qwen3/pd-disaggregation-m2.md`
- Qwen3-8B 首轮 A/B(短输入、吞吐持平结论的出处):`qwen3-8b-pd-vs-mix-h200.md`
- router 亲和补丁:pegaflow [#405](https://github.com/novitalabs/pegaflow/pull/405)
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ Organized by domain (model line / subsystem / playbook / lesson) instead of by l
| `benchmarks/mixed-load-itl.md` | Qwen3-4B + Qwen3.5 mixed-load ITL (#244, #375): chunking-off sweeps (qps × prompt × prefix) via `bench_serving mixed`. Both lines freeze every active decode for the full prefill in the unified step (8k≈0.9–1.2s, 12k≈1.4–2.2s). Qwen3 p99 blows up with prompt (8k 1161, 12k 3270ms at qps≥0.5); Qwen3.5's freeze lands just under the 1% p99 knee (shows in `max`, not p99 — measurement artifact of the 1024-tok background, **not** immunity). Prefix reuse defeats it. #375 chunked prefill caps the per-step freeze. |
| `benchmarks/accuracy-eval-results.md` | Phase 1 GSM8K: Qwen3-4B PASS (openinfer 85.37% vs HF 85.82%, delta -0.45 pp). Qwen3.5-4B historical FAIL recovered by #250 (strict 79.38%, flexible 79.30% vs HF 79.45%). |
| `benchmarks/qwen3-8b-pd-vs-mix-h200.md` | Qwen3-8B 多轮负载三方 A/B(2×H200):P/D 1P+1D vs mixed×2(会话亲和 LB)vs mixed×1。吞吐持平(47.8k vs 47.0k tok/s),P/D 赢在 decode 稳定性(TPOT p99 10.08 vs 12.77ms,turn2+ TTFT 恒定 ~107ms vs 爬升 71→132ms),冷 turn1 多付 ~200ms(M3 目标)。含 vllm-bench 命令与 `max_completion_tokens` 坑。 |
| `benchmarks/qwen3-14b-pd-vs-mix-h200.md` | Qwen3-14B 多轮长输入(8k+2k/轮)P/D 战役(8×H200):等卡 P/D 吞吐反超 mixed +17% 且 ITL p99 23.5 vs 105ms;vLLM 0.25.1 吞吐/内核更快但 ITL p99 141ms;重负载下 200GiB hugepage host 池 vs 16GiB 决定 decode 纯净性(84.6→23.6ms);2P+2D 弹性对比证明 router 前缀亲和是必要条件(round-robin ITL p99 86 → 亲和 22.8ms,pegaflow #405);restore 冻结 decode 的机制与修复(openinfer #705);同栈固定 seed 复跑的 warm 污染坑。 |

## conventions

Expand Down
7 changes: 5 additions & 2 deletions docs/models/qwen3/pd-disaggregation-m2.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# P/D 分离 M2:pegaflow metaserver P2P 数据面

> **TL;DR**: Qwen3-8B 1P+1D 双 openinfer 实例 P/D 分离**已在单机 2×H200(每卡 1 块 400G IB NIC)端到端验证**:KV 经 pegaflow 内容寻址 P2P 从 P 流向 D(metaserver 发现 + 单边 RDMA READ + H2D restore),greedy 输出与单实例 baseline 逐 token 一致(3 档 prompt 长度),33/33 块 74.2 MiB 拉取 rdma_wait 仅 2.6ms,杀 metaserver / 杀 P 均优雅退化为本地 prefill。无 handle 协议——D 从同一 prompt 推出同一组 kvbm lineage hash 直接查询。**多轮并发压测已过**:turn2+ TTFT 恒定 ~107ms、TPOT p99 全轮 <10.1ms,与 mixed 部署的完整 A/B 见 `../../benchmarks/qwen3-8b-pd-vs-mix-h200.md`;先修掉 §4 的 `max_completion_tokens` 坑。openinfer 分支 `feat/pd-pegaflow-p2p`,pegaflow 侧 PR [#381](https://github.com/novitalabs/pegaflow/pull/381)
> **TL;DR**: Qwen3-8B 1P+1D 双 openinfer 实例 P/D 分离**已在单机 2×H200(每卡 1 块 400G IB NIC)端到端验证**:KV 经 pegaflow 内容寻址 P2P 从 P 流向 D(metaserver 发现 + 单边 RDMA READ + H2D restore),greedy 输出与单实例 baseline 逐 token 一致(3 档 prompt 长度),33/33 块 74.2 MiB 拉取 rdma_wait 仅 2.6ms,杀 metaserver / 杀 P 均优雅退化为本地 prefill。无 handle 协议——D 从同一 prompt 推出同一组 kvbm lineage hash 直接查询。**多轮并发压测已过**:turn2+ TTFT 恒定 ~107ms、TPOT p99 全轮 <10.1ms,与 mixed 部署的完整 A/B 见 `../../benchmarks/qwen3-8b-pd-vs-mix-h200.md`。**2026-07-17 用 Qwen3-14B 在 8×H200 全量复验并扩展**(正确性/故障门 + hugepage 大池 + 弹性 2P+2D + router 前缀亲和 pegaflow [#405](https://github.com/novitalabs/pegaflow/pull/405)),完整数据见 `../../benchmarks/qwen3-14b-pd-vs-mix-h200.md`——长输入下 P/D 等卡吞吐也反超 mixed(+17%),ITL p99 差距 4.5×。M2 代码已随 #522 合入 main
>
> Last touched: 2026-07

Expand Down Expand Up @@ -47,7 +47,10 @@ pegaflow #381 已合入 master(squash 为 `d46fd16`,含 router `max_completi
- **P2P/RDMA 依赖未做 feature gate**(openinfer #523):`rdma` feature 无条件开,默认构建也拉 pegaflow-transfer + vendored rdma-core;运行时无影响(不带 `--kv-p2p-*` 不激活),是打包卫生欠账。
- P 侧冷 prompt 多付一轮 RemoteFetch 往返(本地全 miss 先 `Loading` 再空手 prefill)——设计使然。
- 单机验证 ≠ 跨机:跨机需确认 dma-buf/GID/路由;目标集群 GPU↔NIC 同构(8×400G 1:1 PIX)预期直接成立。
- 多 P 多 D 纯 router 事务(内容寻址保证任意 D 发现任意 P 的 KV),M2 架构无障碍。
- 多 P 多 D 纯 router 事务(内容寻址保证任意 D 发现任意 P 的 KV)——**14B 战役已实测 2P+2D**:任意 D↔任意 P 拉取成立,甚至 P 会从 D 拉前缀(mesh 全向);但 router round-robin 会打碎前缀局部性,重负载 ITL p99 从 23→85ms,**必须配前缀亲和选路**(pegaflow #405,P+D 都要亲和)。
- bulk restore 的块注册曾在 scheduler 线程冻结全部流 ~70ms(openinfer [#704](https://github.com/openinfer-project/openinfer/issues/704)):~97% 成本是 registry PRT 内层分片表按核数分配(192 核 = 1024 分片/新 position),[#705](https://github.com/openinfer-project/openinfer/pull/705) 内层改平 HashMap(~1.6µs/块,1000 块 inline ~2ms)+ pinned token 回读修复,无需分期机制。诊断教训:GPU 占空比正常但 token 停 = 查 scheduler 线程,别猜带宽;第三方并发容器的 or_default 可能藏着随核数缩放的分配。
- 字节一致门有边界:P/D 的 restore+suffix-prefill 与本地整段 prefill 的 chunk 边界不同,Tuned 策略下近平局 token 可合法翻转(非缺陷,单机 prefix-cache 命中同理;详见 14B 战役文档 §5)。传输无损由大传输档 + logits golden gate 保证。
- host 池大小是 decode 纯净性的一部分:池装不下工作集时 P 侧 evict → D 查询 miss → D 本地 prefill 兜底污染 decode(14B 重负载实测 ITL p99 23.6→84.6ms)。大池用 `--kv-offload-hugepages`(2MiB hugepages,200GiB NUMA 感知池 ~5s/池分配)。
- prefill-only 请求模式(省掉 max_tokens=1 的一步 decode):`PendingEffect::EmitAndFinish` 缝上加,未做。
- M3(延后):pd-rdma-push 逐层 GPU→GPU WRITE Rust 化,P prefill 与传输流水。

Expand Down
Loading