diff --git a/docs/benchmarks/qwen3-14b-pd-vs-mix-h200.md b/docs/benchmarks/qwen3-14b-pd-vs-mix-h200.md new file mode 100644 index 000000000..d0f17a36d --- /dev/null +++ b/docs/benchmarks/qwen3-14b-pd-vs-mix-h200.md @@ -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 --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:// \ + --model --tokenizer \ + --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 --multi-turn-concurrency \ + --extra-body '{"min_tokens":1}' --temperature 0 \ + --percentile-metrics ttft,tpot,itl,e2el --metric-percentiles 50,99 \ + --save-result --result-filename .json --result-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)。 +- **故障退化**:杀 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) diff --git a/docs/index.md b/docs/index.md index aedd8b821..a1a4e72a0 100644 --- a/docs/index.md +++ b/docs/index.md @@ -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 diff --git a/docs/models/qwen3/pd-disaggregation-m2.md b/docs/models/qwen3/pd-disaggregation-m2.md index 35a6352bc..b77c87dcb 100644 --- a/docs/models/qwen3/pd-disaggregation-m2.md +++ b/docs/models/qwen3/pd-disaggregation-m2.md @@ -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 @@ -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 与传输流水。