Prhub

#31500 [AMD][DI][CI] 5/N Add DSV4 wide-EP16 4-node 2P1D nightly recipes

原始 PR 作者 Lzy17 合并时间 2026-08-04 13:08 文件变更 9 提交数 14 评论 10 代码增减 +951 / -0

执行摘要

DSV4 宽 EP16 4 节点 2P1D 夜间配方新增

PR body 将本 PR 定位为 "Phase-4 of the AMD disaggregation nightly":为 mi355x 集群新增 DSV4 Wide-EP16 腿(no-MTP + MTP),目标是在 4 节点上运行 2P1D EP16(2 个 prefill EP8 + 1 个 decode EP16)。拓扑采纳了 functionstackx 的建议——wide EP 只对 decode 有益("wideEP is mainly helpful on decode as long as the weights fill in a single prefill node");同时说明 prefill TP8 → decode TP16 的非对称 KV 传输对 DeepSeek MLA 天然布局兼容(KV 是按 token 复制的 latent,不做 head 分片)。此外,最初面向 spur 集群的方案因 "spur's ionic fabric could not cross-rail the MORI MoE all-to-all" 而放弃,整体迁移到 bnxt RoCE 的 mi355x 集群,并以 GSM8K 0.927 ≥ 0.91 作为正确性门禁验证。

值得精读,尤其适合关注 AMD 分离式部署与 RDMA/RoCE 调优的读者。重点学习:① 2P1D 拓扑中窄 prefill + 宽 decode 的设计取舍;② 硬件约束(ionic 4 GiB MR 上限、GID 作用域、RDMA ABI)如何转化为可复现的配方参数;③ 字节级非回归门控思路——所有新逻辑仅在 nodes-per-engine > 1 时生效,保证既有 EP8 配方逐字节不变。注意最终合并 diff 仅为配方与配置,launcher 能力需结合同系列其他 PR 阅读。

讨论亮点

核心讨论围绕拓扑设计与集群环境验证展开。① functionstackx 建议把 wide EP 只放在 decode(2 个单节点 prefill EP8 + 1 个跨 2 节点 decode EP16),HaiShaw 认可、Lzy17 据此把 8 个配方从 1P1D 重写为 2P1D。② bingxche 的纯合并验证发现全局 SLURM_EXCLUDE=mia1-p01-g20 导致 4 节点请求只剩 3 节点可用而无法启动;进一步验证表明 g20 的排除不能简单删除(EP8 路径在 g20 上遭遇 ionic ABI 不匹配),最终采用条件例外 + SLURM_DIST_TAIL 固定 g20 为 decode 非根 rank。③ 完整 8 腿验证 7/8 通过,唯一失败为 Flash FP8 base 的 MORI bootstrap 节点内竞态。

实现拆解

  1. 配方矩阵(8 个新 YAML):新增 scripts/ci/slurm/recipes/mi355x-{fp4,fp8}/dsv4{pro,flash}/1k1k/2p1d-ep16{-mtp}.yaml,声明 resources(prefill_workers: 2 / decode_workers: 1)、backend.sglang_config(prefill TP/EP/DP=8,decode TP/EP/DP=16)、runtime(attention_backend: dsv4、moe_a2a_backend / kv_transfer_backend: mori、ib_devices: rdma0..7、dist_socket_ifname: eno0、rocm700a: 0)。

  2. wide_ep 参数定标(硬件约束驱动):① decode KV 池硬上限——ionic NIC 拒绝超过 4 GiB 的单一 RDMA 内存区,mori 把每个 KV buffer 注册为单一 region(无 chunking),最大 buffer = max_total_num_tokens × 256 B,上限 16,777,216 tokens,MTP 配方统一定标为 7,000,000;② MTP decode 的 MORI dispatch 缓冲(512 / 2048 / 1024)——MTP 的 decode CUDA 图捕获会把 MoE dispatch 推到 cuda_graph_bs × draft_tokens tokens/rank,缓冲不足会在捕获阶段中止;③ 非对称 mem-fraction(prefill 0.8 / decode 0.85,pro-fp8 MTP prefill 因权重最重提到 0.85)。

  3. nightly leg 登记:scripts/ci/slurm/nightly-configs.yaml 新增 8 条 runner: mi355x 的 leg(multinode: true、disagg: true),与既有 dp8ep8 leg 并列;Pro MTP 两条腿在 search-space 排除 conc256,原因是分离式 decode 的 SWA hybrid pool retract→get_cpu_copy 为上游 NotImplementedError,会在并发 256 时崩溃。

  4. 正确性门禁:每个配方在 bench 段配置并发扫描(1..256)之前先跑完整 GSM8K(1319 题、8-shot、准确率阈值 0.91),防止“快但错”的结果混入夜间测试。

  5. 跨 PR 的配套加固:launcher(经同系列其他 PR 进入 main)按 nodes-per-engine = ceil(TP/8) 推导引擎节点数,跨节点 --nnodes / --node-rank / --dist-init-addr 仅对多节点引擎发射;MORI_IB_GID_INDEX=1 规避 link-local 与 global GID 不匹配;g20 节点先以条件例外 + SLURM_DIST_TAIL 作为 decode 非根 rank 使用,随后在共享 host ionic 挂载就绪后重新放开全局排除;MORI_BOOTSTRAP_TIMEOUT=300 缓解 bootstrap 竞态。所有门控保证 TP=8 既有配方生成参数逐字节不变。

注:本 PR 的 commit 历史包含 launcher 修改,但最终合并 diff 为 9 个 YAML 文件(951 行纯新增),launch_mi355x.sh 的变更在合并前已随系列其他 PR 落入 main。

文件 模块 状态 重要度
scripts/ci/slurm/recipes/mi355x-fp4/dsv4flash/1k1k/2p1d-ep16-mtp.yaml CI 配方 added 5.4
scripts/ci/slurm/recipes/mi355x-fp4/dsv4pro/1k1k/2p1d-ep16-mtp.yaml CI 配方 added 5.4
scripts/ci/slurm/recipes/mi355x-fp8/dsv4flash/1k1k/2p1d-ep16-mtp.yaml CI 配方 added 5.4
scripts/ci/slurm/recipes/mi355x-fp8/dsv4pro/1k1k/2p1d-ep16-mtp.yaml CI 配方 added 5.4
scripts/ci/slurm/recipes/mi355x-fp4/dsv4flash/1k1k/2p1d-ep16.yaml CI 配方 added 5.33
scripts/ci/slurm/recipes/mi355x-fp4/dsv4pro/1k1k/2p1d-ep16.yaml CI 配方 added 5.33
scripts/ci/slurm/recipes/mi355x-fp8/dsv4flash/1k1k/2p1d-ep16.yaml CI 配方 added 5.33
scripts/ci/slurm/recipes/mi355x-fp8/dsv4pro/1k1k/2p1d-ep16.yaml CI 配方 added 5.33
scripts/ci/slurm/nightly-configs.yaml CI 配置 modified 5.16

关键符号

launch_mi355x.sh nightly-configs.yaml 2p1d-ep16*.yaml 配方

关键源码片段

scripts/ci/slurm/recipes/mi355x-fp4/dsv4flash/1k1k/2p1d-ep16-mtp.yaml configuration

MTP 分支的宽 EP16 配方代表,集中体现硬件约束定标:decode KV 池上限(ionic 4 GiB MR 限制 → 7M token)、MTP 专用 MORI dispatch 缓冲(512/2048/1024)、非对称 mem-fraction,以及 GSM8K 正确性门禁。

# MI355X DeepSeek-V4-Flash FP4 4 节点 2P1D 分离式配方(窄 prefill EP8 + 宽 decode EP16 + MTP)
# Oren 拓扑:wide EP 只对 decode 有收益;2 个单节点 prefill 引擎(EP8)承载路由分发,
# 1 个 decode 引擎跨 2 节点(EP16),共 4 节点,mori 同时承载 MoE all-to-all 与 P->D KV 传输。resources:
  prefill_workers: 2
  decode_workers: 1backend:
  sglang_config:
    # prefill 保持窄 EP8(节点内 XGMI all-to-all,已验证可靠);decode 放宽到 EP16。
    prefill:
      tensor-parallel-size: 8
      expert-parallel-size: 8
      data-parallel-size: 8
    decode:
      tensor-parallel-size: 16
      expert-parallel-size: 16
      data-parallel-size: 16runtime:
  image: lmsysorg/sglang-rocm:v0.5.15.post1-rocm720-mi35x-20260715
  attention_backend: dsv4
  # RoCE HCA(每节点 8 个)同时供 mori MoE all-to-all 与 P->D KV 传输使用。
  ib_devices: rdma0,rdma1,rdma2,rdma3,rdma4,rdma5,rdma6,rdma7
  moe_a2a_backend: mori
  kv_transfer_backend: mori
  dist_socket_ifname: eno0 # 宽 decode 引擎跨节点 torch-distributed 初始化的 NIC
  rocm700a: 0 # rocm720 0715 镜像需关闭 ROCm-7.0.0-alpha 路径(已验证)
  prefill_port: 30025
  decode_port: 30026
  prefill_bootstrap_port: 8998
  decode_bootstrap_port: 9001
  lb_port: 8000
  mem_fraction_static: 0.90
  page_size: 256
  max_running_requests: 256
  chunked_prefill_size: 131072
  swa_full_tokens_ratio: 0.1  # wide_ep 参数块:按角色拆分定标,覆盖单节点 EP<=8 配方默认的对称配置。
  wide_ep:
    kv_cache_dtype: fp8_e4m3
    prefill_mem_fraction_static: 0.8
    decode_mem_fraction_static: 0.85
    prefill_chunked_prefill_size: 131072
    prefill_max_running_requests: 1024
    decode_max_running_requests: 1024
    common_extra_flags: "--moe-dense-tp-size 1 --enable-dp-lm-head --decode-log-interval 100 --watchdog-timeout 3600 --load-balance-method round_robin"
    prefill_extra_flags: "--context-length 9217 --max-total-tokens 262144"
    # decode KV 池硬上限:ionic NIC 拒绝超过 4 GiB 的单一 RDMA 内存区,而 mori 把每个
    # KV buffer 注册为单一 region(无 chunking);最大 buffer = max_total_num_tokens * 256 B,
    # 故上限为 16,777,216 tokens。MTP 定标 7,000,000(已验证值,低于所有 MTP leg 的自然池)。
    # 下方 cuda-graph-bs 处为 1 到 128 的完整枚举(此处省略号仅为展示缩写)。
    decode_extra_flags: "--max-total-tokens 7000000 --cuda-graph-bs 1 2 3 ... 128 --prefill-round-robin-balance"
    prefill_extra_env:
      MORI_MAX_DISPATCH_TOKENS_PREFILL: 8192
      MORI_MAX_DISPATCH_TOKENS_DECODE: 256
      SGLANG_MORI_NUM_MAX_DISPATCH_TOKENS_PER_RANK: 16384
    # MTP 的 decode CUDA 图捕获会把 MoE dispatch 推到 cuda_graph_bs * draft_tokens
    # tokens/rank,缓冲不足会在捕获阶段中止;512/2048/1024 为 mi355x 实测值。
    decode_extra_env:
      MORI_MAX_DISPATCH_TOKENS_DECODE: 512
      MORI_MOE_MAX_INPUT_TOKENS_DECODE: 2048
      SGLANG_MORI_NUM_MAX_DISPATCH_TOKENS_PER_RANK: 1024# MTP / EAGLE 投机解码(NextN 头来自基础模型),prefill 与 decode 均启用。
mtp:
  enabled: true
  num_steps: 3
  eagle_topk: 1
  num_draft_tokens: 4bench:
  concurrencies: [1, 8, 16, 32, 64, 128, 256]
  num_prompts_factor: 4
  random_range_ratio: 1.0
  # 正确性门禁:性能扫描之前先走 PD 路径跑完整 GSM8K(1319 题 8-shot),
  # 准确率低于 0.91 即判失败,避免“快但错”的结果通过夜间测试。
  accuracy:
    enabled: true
    num_shots: 8
    num_questions: 1319
    threshold: 0.91

评论区精华

拓扑设计:从 1P1D 调整为 2P1D(宽 EP 只放 decode) 设计

functionstackx 建议:"have yall thought about doing 2P1D instead? where the wideEP (EP16) is only on decode? What we see is that wideEP is mainly helpful on decode as long as the weights fill in a single prefill node i.e. 2 prefill workers (each prefill worker is 1 node doing EP8 or something) and then 1 decode worker where the decode worker uses 2 nodes and does EP16 across the 2 nodes"。HaiShaw 回应 "your proposal looks good",Lzy17 据此重写全部 8 个配方。

结论:采纳 2P1D 拓扑:2 个单节点 prefill EP8 + 1 个跨 2 节点 decode EP16;配方文件更名 1p1d-ep16* → 2p1d-ep16*。 · 已解决

全局 SLURM_EXCLUDE=mia1-p01-g20 导致 4 节点请求失败 正确性

bingxche 纯合并验证发现 EP16 腿在服务器启动前失败:"TOTAL_NODES=4 salloc -N4 --exclude=mia1-p01-g20 ... salloc: error: Requested node configuration is not available"。amd-sglang 分区只有 4 个节点,全局排除 g20 后只剩 3 个可申请。

结论:EP16 腿需要条件例外并加 SLURM_DIST_TAIL=mia1-p01-g20,让 g20 作为 decode 非根 rank 参与四节点请求。 · 已解决

g20 的 ionic ABI 不匹配导致排除不能简单删除 正确性

bingxche 跟进验证:既有 EP8 路径在 g20 上 init_disaggregation 报 "libibverbs: Driver ionic does not support the kernel ABI of 1 (supports 4 to 4)"、"RuntimeError: no active RDMA device on this host",因此全局排除不能直接删除。

结论:最终方案:共享 host ionic 挂载修复 ABI 后重新放开 g20(保留 wide-EP 根节点顺序),相关 commit 由 bingxche 完成(e9085598 / d7a79633 / 0d0dcbd5)。 · 已解决

完整 8 腿验证 7/8 通过,Flash FP8 base 遭遇 MORI bootstrap 竞态 性能

bingxche 最终验证(Run 30807112587)覆盖全部 8 个 Pro/Flash × FP4/FP8 × base/MTP 配置,7/8 通过。唯一失败 Flash FP8 base:g29 prefill 引擎命中节点内 MORI bootstrap 竞态("Rank 2/4/6 failed to connect to root after 50 retries",root 端 "AcceptSocket timed out after 30000 ms")。

结论:PR 设置 MORI_BOOTSTRAP_TIMEOUT=300 放大握手窗口,竞态本质未根除,属已知不稳定点。 · 待处理

风险与影响

① MORI bootstrap 竞态未根除:最终验证 7/8 中失败的正是节点内握手竞态(Rank 2/4/6 连接失败、AcceptSocket 超时),MORI_BOOTSTRAP_TIMEOUT=300 仅放大握手窗口,竞态本质仍在,Flash FP8 base 腿存在偶发失败风险。② KV 池上限的长期影响:ionic 4 GiB MR 限制把 decode max-total-tokens 压到 16.78M(MTP 7M),长上下文或高并发下可能成为吞吐瓶颈,且依赖 mori 是否支持 buffer chunking。③ 多节点路径覆盖有限:跨节点 EP16 仅在 mi355x 集群 8 条夜间腿中回归,g20 节点排除/固定逻辑与环境强耦合,集群拓扑变化时易回归。④ MTP 依赖上游缺陷:Pro MTP 排除 conc256(SWA get_cpu_copy NotImplementedError)、overlap-plan-stream 需对 MTP 关闭(DSV4 MLA 后端未实现 update_verify_buffers_to_fill_after_draft),上游修复前覆盖不完整。⑤ 对生产代码零影响:合并 diff 全为 CI 配置,风险集中在夜间测试有效性层面。

为 AMD MI355X 集群新增 8 条 4 节点 2P1D 分布式夜间覆盖,首次把跨节点宽 EP16(MoE all-to-all + KV 传输双通道走 mori)纳入常规回归;对 AMD DI 团队而言是系列化推进的关键里程碑,沉淀了硬件约束(RoCE GID、ionic MR 上限、RDMA ABI)驱动的配方定标方法论。对用户与生产服务无任何运行时影响(纯 CI 基础设施变更)。

MORI bootstrap 竞态未根除 4 GiB RDMA MR 上限约束 KV 池 节点排除 / 固定逻辑与环境强耦合 MTP 依赖上游未实现 API 多节点路径仅 CI 验证

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论