Open
SAPP-ND 实测验证发现的问题(P1-P4) #272
lei_xxxAdd on  Jul 3
lei_xxx
Jul 3 Create

SAPP-ND 实测验证发现的问题(P1-P4)

PR #827(Issue #132)在 DeepSeek-V3 缩小版上做了 6 config 的 profiling 实测验证,发现了 4 个问题。其中 P2(EP/ZeRO-3 抵消)和 P4 的 EP_WAIT 单位问题是 EP 估算模块本身的 bug,P1(TP 运行时开销未建模)和 P3(PP 通信估算)是其他模块的问题,但在 EP 验证实验中一起暴露出来。

1. 背景

实验模型为 DeepSeek-V3 缩小版(~1.3B, 32 experts, 12 层, PP=2, 32 卡),覆盖 EP(2/4/8) × TP(1/2) 共 6 个配置。

Peak HBM 预估 vs 实测:

Config EP TP DP 实测 Peak(MB) 预估 Peak(MB) 预估/实测
A 2 1 16 10941 8255 0.75 (低估)
B 4 1 16 10056 7382 0.73 (低估)
C 8 1 16 9614 6945 0.72 (低估)
D 4 2 8 5850 6174 1.06
E 2 2 8 6735 7047 1.05
F 8 2 8 5408 5737 1.06

通信分量 Pearson r:

Wait Type Pearson r 状态
COMP 0.999 OK
MP_WAIT 0.890 OK
EP_WAIT 0.872 PASS
PP_WAIT -0.197 NEGATIVE (P3)
TOTAL -0.534 NEGATIVE (P4)

从上述数据中可以清楚看到两类问题:显存预估的 TP/EP 偏差,以及通信估算的排序反转。按优先级整理如下。


2. 已知问题

2.1 P1:TP 依赖的基线偏差(OOM 风险)

现象

TP=1 组系统性低估约 25%(0.72-0.75x),TP=2 组略高估约 5%(1.05-1.06x)。绝对偏差:TP=1 平均低估 ~2676 MB,TP=2 平均略高估 ~322 MB。

影响

TP=1 低估 25% 超过了 20% 的可接受阈值。更危险的是,这是 OOM 方向的错误:策略搜索认为某配置可以部署,实际运行却 OOM。

根因

TP=1 时有约 2676 MB 的运行时开销没有被模型计入,而 TP=2 时这些开销几乎消失——因为它们会被隐式分片。来源:

  • 输出层 activation ~2084 MB 未被 recompute 覆盖
  • 通信 double-buffering ~500-600 MB
  • Graph memory、框架开销
  • 安全余量仅 1 GB,远不足以覆盖上述开销

实测 TP1/TP2 = 1.63-1.78x,而预估只有 1.17-1.21x,说明模型严重低估了 TP 带来的实际节省。

关键点:上述运行时开销(activation buffer、通信 buffer、graph memory)是 TP-shardable 的——TP>1 时它们随 TP 线性缩小,但当前模型中的 Static 分量(param/OS/grad)已通过 shard_p_os_non_exp_partial 等因子正确缩放,Dynamic 分量(activation + comm)也通过 _inner_dynamic_mem 按 TP 缩放。问题在于这些额外的运行时开销项完全不在模型中,而 1 GB 的 safety buffer 既不够大也不随 TP 缩放。

相关代码

  • _backbone.py:660-662 — safety buffer 硬编码为 1 GB(flat,不随 TP 缩放),仅加到 Dynamic>0 的 stage
  • _backbone.py:651-658 — Dynamic = sum(activation + comm) + backward_overhead + safety_buffer
  • body.py:62 — non-expert param 使用 shard_p_os_non_exp_partial,当 has_op=True 时为 os_max_shard * cp(非简单 /t),当 has_op=False 时为 t * cp
  • body.py:88 — non-expert grad 使用 shard_grad_non_exp,当 has_grad_shard=False 时为 t(纯 TP 分片),当 has_grad_shard=True 时为 shard_p_os_non_exp

2.2 P2:EP 分片与 ZeRO-3 分片抵消

现象

6 个配置中,expert param 始终为 304 MB、OS 始终为 608 MB,EP 从 2 增到 8 完全没有变化。而实测中 expert 参数只做 EP 分片(不做 ZeRO-3 分片),应随 EP 线性缩放。

影响

这是一个 per-stage 的 bug。在当前 DeepSeek-V3 缩小版中,Stage 1 的 EP-constant 组件(Param+OS+Safety Buffer)占比约 34%,稀释了 EP-scalable 部分,所以 EP scaling 看起来很弱(1.09x~1.14x),抵消的影响也被稀释了。但对于 expert 占比更大的模型,策略搜索可能会因此选错 EP 值。

根因

stat_p_layer 中 expert param 公式:

routed_mem = routed_p / ep * bytes_p / shard_p_os_exp

shard_p_os_exp = d_exp * cp * t_exp(when has_op=True),而 d_exp 在所有分支中都包含 1/ep:

d_exp 分支 公式 代入后 shard_p_os_exp
etp 已设置 (line 131) d * t * cp / t_exp / ep d * t * cp² / ep
etp 未设置, d ≥ ep (line 135) d / ep d / ep * cp * t_exp
etp 未设置, d < ep (line 137) d * t / ep d * t / ep * cp * t_exp

以 etp 分支为例,代入后分子的 /ep 和分母中的 1/ep 刚好抵消:routed_mem = routed_p * bytes_p / (d * t * cp²),EP 完全消失。其他分支同理,EP 均被抵消。

同样的抵消影响 OS、Grad 和 Grad-clip(body.py:130)。

为什么不能直接去掉 d_exp 中的 1/ep

有些 MoE 模型确实对 expert 参数做 ZeRO-3 分片,此时 d_exp 包含 1/ep 是正确的——抵消反映的是真实行为。DeepSeek-V3 不对 expert 参数做 ZeRO-3 分片,抵消就是 bug。所以不能一刀切,需要配置项来区分这两种场景。

相关代码

  • _cost_model_parser.py:131,135,137 — config_dp_tp_exp() 计算 d_exp(三个分支均含 /ep)
  • _cost_model_parser.py:59-61 — config_optimizer_shard() 计算 shard_p_os_exp
  • _cost_model_parser.py:62-66 — shard_grad_exp(当 has_grad_shard 时等于 shard_p_os_exp)
  • body.py:58 — stat_p_layer 中 routed_mem 公式
  • body.py:72 — stat_os_layer 中 routed_mem 公式
  • body.py:84 — stat_grad_layer 中 routed_mem 公式
  • body.py:130 — gradient clipping buffer 也使用 routed_p / ccfg.ep * ccfg.bytes_os / ccfg.shard_p_os_exp,同样受消去影响。注意完整公式为 (routed_p / ep * bytes_os / shard_p_os_exp + shared_p * bytes_os / shard_p_os_exp_partial) * bytes_os / shard_p_os_non_exp_partial * int(has_clip),其中外层还乘以 bytes_os / shard_p_os_non_exp_partial,但内层 routed_p / ep * bytes_os / shard_p_os_exp 的 EP 消去行为与 param/OS/grad 一致
  • _cost_model_variables.py:88-116 — _CostModVar dataclass 中目前无 expert_zero3_sharding 字段

2.3 P3:PP_WAIT 负相关

现象

PP_WAIT 的 Pearson r = -0.197,呈负相关——模型认为 PP_WAIT 更高的配置,实测反而更低,排序完全反转。

影响

这个问题不影响显存估算,但会影响性能估算——策略搜索可能选错 PP 配置。同时 PP_WAIT 的负相关也拖累了 TOTAL 估算(见 P4)。

根因

当前 PP_WAIT = BUBBLE + PP_COMM(debug.py:590-593),其中:

  1. BUBBLE = pipeline_perf - time_sum(estimate.py:325-332),time_sum 包含所有 PerfParts(含 EP_COMM)
  2. PP_COMM 使用硬编码的 MANUAL_P2P_RATIO = 0.002(estimate.py:42),与 EP 无关

⚠️ 数学推导表明原根因方向错误:对 vp=1 的 1F1B schedule(DeepSeek-V3 测试配置),形式化推导:

pipeline_perf = sum(s_i) + (m-1) * max(s_i)    # estimate.py:255
time_sum      = m * max(s_i)                     # estimate.py:315-317 sanity check
BUBBLE        = pipeline_perf - time_sum
              = sum(s_i) - max(s_i)              # = min(s_i) for p=2

对 p=2, 添加 EP_COMM Δ 到各 stage 的情况分析:

场景 EP_COMM 分布 BUBBLE 变化
等量增加 Δ 加到两个 stage BUBBLE' = min(s₁,s₂) + Δ → 增大 Δ
仅加到 straggler Δ 只加到 max(s₁,s₂) BUBBLE' = min(s₁,s₂) → 不变
仅加到非 straggler Δ 只加到 min(s₁,s₂) BUBBLE' = min(s₁,s₂) + Δ → 增大 Δ(若不切换 straggler)

结论:BUBBLE 随 EP_COMM 增大而增大或不变,绝不会减小。原分析"EP_COMM 填入 time_sum 导致 BUBBLE 减小"在数学上不成立。

DeepSeek-V3 的特殊结构:arch_hooks.py:218-231 中 hook_dense 设置 ccfg.ep = 1(零 EP_COMM),hook_moe 设置 ccfg.ep = saved.ep(完整 EP_COMM)。因此 EP_COMM 在 pipeline stage 间不均匀——dense 层所在的 stage 不受 EP_COMM 影响,MoE 层所在的 stage 承受全部 EP_COMM。这使得 BUBBLE 的变化取决于哪个 stage 是 straggler,但方向仍为"增大或不变"。

真正的根因是估算模型与现实的系统性偏差:估算模型将 EP_COMM 视为串行工作,增加 stage 执行时间 → BUBBLE 增大。而实际运行中 EP all-to-all 可以与 pipeline bubble 重叠(stage 等待其他 stage 时仍可执行 EP 通信),EP_COMM 不增加实际 stage 时间 → PP_WAIT 随 EP 减小。估算的 BUBBLE 增大方向与实测的 PP_WAIT 减小方向矛盾,根因是估算缺少 pipeline-EP overlap 建模。

可能的次级因素(需通过消融实验验证):

  • DP 减少效应:EP 增大 → DP 减小(总卡数固定)→ DP 通信量减少 → stage 执行更快。但当前 comm[Dim.DP] = 0(comm_time.py:397 的 TO REMOVE hack)已将 DP 通信清零,此机制在估算中被掩盖
  • Regression 系数失真:apply_regression_coefficients() 对各 PerfParts 独立缩放,EP_COMM 和 COMPUTE 的系数不同可能导致 BUBBLE 被扭曲
  • Straggler 切换:EP 变化可能导致两个 stage 的相对执行时间反转,但如上表分析,即使切换 straggler,BUBBLE 也不会减小

此外,当前 pipeline schedule 仅区分 vp==1(1F1B)和 vp>1(interleaved),没有考虑 zero_bubble_v 等 schedule 的不同 bubble 比例。

相关代码

  • estimate.py:238-333 — estimate_pipeline() 计算 BUBBLE
  • estimate.py:295-313 — time_sum 累加循环(含 EP_COMM)
  • estimate.py:336-369 — estimate_p2p_comm() 计算 PP_COMM
  • estimate.py:467-499 — apply_regression_coefficients() 独立缩放各 PerfParts
  • debug.py:587-593 — estimation_in_real_parts() 中 EP_WAIT = EP_COMM、PP_WAIT = BUBBLE + PP_COMM 的映射
  • arch_hooks.py:218-231 — hook_dense 设置 ccfg.ep=1、hook_moe 设置 ccfg.ep=saved.ep,导致 EP_COMM 在 stage 间不均匀
  • comm_time.py:397 — comm[Dim.DP] = 0(TO REMOVE hack,将 DP 通信清零)
  • pipeline_builder.py:25-177 — schedule 构建器(1f1b / vpp / vpp2)
  • common.py:382-389 — zero_bubble_v schedule 检测

2.4 P4:TOTAL 负相关与 EP_WAIT 绝对值单位问题

现象

TOTAL 的 Pearson r = -0.534,整体负相关。另外 EP_WAIT 的绝对值与实测差了约 1e7 倍,是一个单位问题。

影响

总时间估算不可靠,目前只有分项(COMP、EP_WAIT)有一定参考价值。

根因

TOTAL 负相关由两个独立问题叠加导致:

  1. PP_WAIT 负相关(P3) 直接把 TOTAL 排序带偏
  2. EP_WAIT 绝对值单位错误:EP 通信公式(comm.py:167-226)输出 byte volume(字节数),而非通信时间。在 comm_time.py:366-374 中,estimate_comm_score() 的转换循环仅覆盖 [Dim.DP, Dim.TP, Dim.DP]——EP 和 CP 从未被传入转换,无论 ttype 是否为 TIME。因此 EP_WAIT 始终为字节数,量级偏大 ~1e7 倍,淹没 COMP、DP_WAIT 等正确分量。

注意:即使 ttype == PerformanceType.TIME,EP 也不被转换。问题不是"仅在 TIME 模式下转换",而是"EP 从未被转换"。

相关代码

  • comm.py:167-181 — ep_comm_layer_balanced() 返回字节数,docstring 明确标注 "Result is in bytes"
  • comm.py:184-226 — ep_comm_layer_imbalanced() 同样返回字节数
  • comm_time.py:366-374 — 关键:estimate_comm_score() 转换循环为 [Dim.DP, Dim.TP, Dim.DP],不含 Dim.EP 或 Dim.CP
  • comm_time.py:489-507 — estimate_comm_score() 将字节除以各网络层级带宽得到时间
  • comm_time.py:396-397 — comm[Dim.TP] /= 2 和 comm[Dim.DP] = 0(TO REMOVE hack)
  • estimate.py:492-499 — TOTAL = COMP + DP_WAIT + MP_WAIT + EP_WAIT + CP_WAIT + PP_WAIT 的累加
  • debug.py:587-589 — EP_WAIT = EP_COMM 的直接映射

额外发现:CP 通信(cp_comm_layer)返回 element count,也从未被传入 estimate_comm_score() 转换。本期不处理 CP 单位问题,但需记录为 follow-up。


3. 目标与非目标

3.1 目标

  • 修正 TP=1 时显存低估问题,预估/实测比提升至 0.85-1.10(P1)
  • 新增 expert_zero3_sharding 配置项,使 expert param/OS/grad/grad-clip 在不做 ZeRO-3 分片时随 EP 正确缩放(P2)
  • 修正 PP_WAIT 排序反转,使 Pearson r > 0.5(P3)
  • 统一 EP 通信的单位,使 EP_WAIT 输出时间(秒)而非字节数,TOTAL Pearson r > 0.5(P4)

3.2 非目标

  • 不引入新的显存估算模型(如基于实测的回归模型),仅修正现有公式的参数和逻辑
  • 不修改 TP 通信估算逻辑(TP comm 当前返回字节数,与 EP 相同的问题,但 TP comm 的量级影响较小,本期不处理)
  • 不修改 pipeline schedule 构建器本身(1f1b / vpp / vpp2 的调度逻辑不变),仅修正 bubble 估算公式
  • 不修改 fast-tuner 中的 pipeline conductor 或 OR-Tools 优化器
  • 不处理 CP 通信的单位问题(CP comm 返回 element count 且从未被 estimate_comm_score 转换,本期不涉及,标记为 follow-up)
  • expert_zero3_sharding 不影响 shared expert 的分片逻辑(shared expert 不参与 EP 分片,始终走 shard_p_os_exp_partial 路径)

4. 接口与契约

4.1 P1:TP 运行时开销建模

4.1.1 Safety buffer 参数化

当前:_backbone.py:660-662 中 safety_buffer = 1024 * 1024 * 1024(1 GB 硬编码,flat 不随 TP 缩放)

变更:将 safety_buffer 改为从 CostModelConfig 读取,支持用户配置,并随 TP 缩放:

参数 类型 默认值 说明
safety_buffer_mb float 1024 Safety buffer 大小(MB),默认 1 GB(保持向后兼容)

计算公式:

effective_safety_buffer = safety_buffer_mb / t   (单位 MB)

注意:_backbone.py 中 Dynamic 分量以字节累加(safety_buffer = 1024 * 1024 * 1024 直接加到字节级的 Dynamic),最终通过 self.mb()(÷ 1024²)转回 MB 输出。参数 safety_buffer_mb 以 MB 为单位输入,需在内部乘以 1024² 转为字节后再加入 Dynamic,与现有字节级累加逻辑一致。

约束:

  • 值 ≥ 0;0 表示不添加 safety buffer
  • 仅加到 Dynamic > 0 的 stage(保持现有行为)
  • 默认值 1024 MB 保持向后兼容:TP=1 时仍为 1 GB,TP=2 时为 512 MB(与原行为 1 GB 相比有变化,但 TP=2 原本已高估 5%,减小 safety buffer 可缓解高估)

设计决策:Safety buffer 代表的运行时余量(graph memory、框架开销等)本质上也是 TP-shardable 的,与 tp_shardable_overhead 一致。将其改为 /t 缩放比新增独立的 TP-shardable 开销项更简洁,避免了两个参数叠加导致 TP=2 过度高估的问题。

4.1.2 TP-shardable 运行时开销项

新增 TP-shardable 运行时开销的显式建模,覆盖 safety buffer 之外的额外开销(输出层 activation、通信 double-buffer 等),当 TP > 1 时被 /t 分片:

参数 类型 默认值 说明
tp_shardable_overhead_mb float 2676 TP-shardable 运行时开销(MB),包括输出层 activation、通信 double-buffer、graph memory 等

计算公式:

effective_overhead = tp_shardable_overhead_mb / t   (单位 MB)

插入位置:在 _backbone.py 的 memory 汇总阶段,加到 Dynamic 分量上(与 safety_buffer 同级)。

约束:

  • 值 ≥ 0;0 表示不添加额外开销项
  • 对于 DeepSeek-V3 缩小版,默认值 2676 MB 为实测偏差均值
  • 后续可通过 fast-tuner 校准自动设置

为什么需要两个参数:safety_buffer_mb 是通用的安全余量(所有模型都需要),tp_shardable_overhead_mb 是 MoE/特定模型结构的额外开销建模。两者职责不同,分开配置更灵活。

4.2 P2:expert_zero3_sharding 配置项

4.2.1 新增配置项

参数 类型 默认值 说明
expert_zero3_sharding bool True expert 参数是否参与 ZeRO-3 分片。True=参与(当前行为,抵消是正确的);False=不参与(DeepSeek-V3 场景)

添加位置:_cost_model_variables.py 的 _CostModVar dataclass,与 has_op、has_grad_shard 等布尔标志同组(line 115-116 附近)。

4.2.2 sharding factor 计算变更

当前(_cost_model_parser.py:59-61):

shard_p_os_exp = (d_exp if has_op else 1) * cp * t_exp

变更:

if expert_zero3_sharding:
    shard_p_os_exp = (d_exp if has_op else 1) * cp * t_exp
else:
    # expert 参数不做 ZeRO-3 分片,仅 CP+TP 分片,不含 d_exp
    shard_p_os_exp = cp * t_exp

即 expert_zero3_sharding=False 时,shard_p_os_exp = cp * t_exp,不含 d_exp,EP 分片不再被 ZeRO-3 抵消。

注意:当 has_op=False 时,原公式已为 1 * cp * t_exp = cp * t_exp,此时 expert_zero3_sharding 的取值无影响——没有 ZeRO-3 就不存在抵消问题。

4.2.3 影响范围

expert_zero3_sharding=False 时受影响的公式:

函数 行号 公式项 变更
stat_p_layer 58 routed_p / ep * bytes_p / shard_p_os_exp shard_p_os_exp 不含 d_exp,分母变小,routed_mem 变大
stat_os_layer 72 routed_p / ep * 2 * bytes_os / shard_p_os_exp 同上
stat_grad_layer 84 routed_p / ep * bytes_grad / shard_grad_exp shard_grad_exp = shard_p_os_exp(当 has_grad_shard),同样不含 d_exp
gradient clipping 130 routed_p / ep * bytes_os / shard_p_os_exp 同样受消去影响

不受影响的公式:

  • shard_p_os_exp_partial(shared expert 专用,与 EP 无关)
  • shard_p_os_non_exp_partial / shard_p_os_non_exp(non-expert 专用)
  • shard_grad_exp_partial(shared expert gradient 专用)
  • shard_grad_non_exp(non-expert gradient 专用)
  • 所有 non-expert 和 shared expert 的内存估算

4.2.4 向后兼容

  • 默认值 True:现有行为完全不变,shard_p_os_exp 仍含 d_exp,所有现有 UT 无需修改
  • d_exp 的计算逻辑(config_dp_tp_exp)不变——d_exp 仍按原公式计算,仅 shard_p_os_exp 不再使用它
  • 当 has_op=False 时,True 和 False 的结果相同(均为 cp * t_exp),不存在兼容性问题

4.3 P3:PP_WAIT 修正

4.3.1 根因确认(消融实验,优先级最高)

在实施修复前,需先通过消融实验确认负相关的真正根因:

实验 方法 目的
EXP-1 将 EP_COMM 从 time_sum 中移除(同时从 pipeline_perf 中移除),观察 BUBBLE 变化方向 确认 overlap 缺失是否为 PP_WAIT 负相关的主因(BUBBLE 增大方向已由数学证明)
EXP-2 移除 comm[Dim.DP] = 0 hack,恢复 DP 通信,观察 BUBBLE 变化 验证 DP 减少效应是否被 hack 掩盖
EXP-3 固定 EP,仅改变 DP(用不同总卡数),观察 PP_WAIT 变化 隔离 DP 对 PP_WAIT 的影响
EXP-4 打印各 config 的 straggler stage id,检查是否随 EP 变化 验证 straggler 切换假设

决策规则:

  • 数学推导已确认:当前 BUBBLE 随 EP 增大而增大(min(stage_perfs) 增加),与实测的 PP_WAIT 随 EP 减小方向相反。EXP-1 的目的是确认 overlap 缺失是否是主因(而非验证 BUBBLE 增大方向——这已由数学证明)。若 EXP-1 显示移除 EP_COMM 后 BUBBLE 变化仍然很小(说明 EP_COMM 对 BUBBLE 的影响被其他因素淹没),则需重新评估 §4.3.2 的优先级
  • 若 EXP-2 显示 DP 通信是主因,则优先修复 DP hack(§4.3.3),P3 修法完全不同
  • 若 EXP-3 或 EXP-4 显示 straggler 切换是主因,则需引入 stage-aware BUBBLE 计算

4.3.2 BUBBLE 计算中 EP 通信 overlap 修正(待 EXP-1 确认后实施)

当前(estimate.py:295-332):

time_sum 包含 EP_COMM,使得 BUBBLE = pipeline_perf - time_sum。

变更(仅当 EXP-1 确认 overlap 缺失是主因时):

引入 EP 通信与 pipeline bubble 的 overlap 修正。数学推导表明,当前 BUBBLE = min(stage_perfs)(对 vp=1, p=2),EP_COMM 作为串行工作加入 stage 后 BUBBLE 增大,导致 PP_WAIT 随 EP 增大——与实测方向相反。修正核心:让 EP 通信不增加 pipeline_perf 中的 straggler_time(对应现实中 EP 通信与 bubble 重叠),从而使 BUBBLE 随 EP 增大而减小。

# 当前:straggler_time 包含 EP_COMM(串行),pipeline_perf 随 EP 增大,BUBBLE 增大
# 变更:计算 straggler_time 时,扣除与 bubble 可重叠的 EP 通信部分
effective_straggler = straggler_time - ep_comm_per_stage * overlap_ratio

推导验证(以 p=2, overlap_ratio=1.0 为例):

修改前: pipeline_perf = sum(s_i) + (m-1)*max(s_i),  BUBBLE = min(s_i)           → 随 EP 增大
修改后: pipeline_perf = sum(s_i') + (m-1)*max(s_i'), s_i' = s_i - ep_comm*1.0
        time_sum 不变(仍含 EP_COMM)
        BUBBLE = min(s_i - ep_comm) < min(s_i)       → 随 EP 增大而减小 ✓

其中 overlap_ratio 为 EP 通信与 pipeline bubble 的重叠比例:

参数 类型 默认值 说明
ep_bubble_overlap_ratio float 1.0 EP 通信与 pipeline bubble 的重叠比例。1.0=完全重叠(EP 通信在 bubble 时间内完成,不增加 straggler),0.0=无重叠(当前行为,BUBBLE 随 EP 增大)

效果:当 overlap_ratio=1.0 时,EP 通信不增加 effective_straggler,pipeline_perf 不随 EP 增大,而 time_sum 中的 EP_COMM 仍计为有用工作,BUBBLE = pipeline_perf - time_sum 随 EP 增大而减小,PP_WAIT 减小——与实测方向一致。

约束:

  • 0 ≤ ep_bubble_overlap_ratio ≤ 1.0
  • 当 ep ≤ 1 时,ratio 不生效(无 EP 通信)
  • DeepSeek-V3 实测表明 overlap 接近 1.0(EP 通信几乎全在 bubble 内完成)
  • 此方案需 EXP-1 确认后才能实施,否则可能引入新的偏差

4.3.3 DP 通信 hack 清理(待 EXP-2 确认后实施)

当前(comm_time.py:397):comm[Dim.DP] = 0(TO REMOVE hack,将 DP 通信清零)

变更:移除此 hack,恢复 DP 通信计算。若 EXP-2 确认 DP 通信是 PP_WAIT 负相关的主因,则此修复可能单独解决 P3。

风险:移除 DP=0 hack 后,DP 通信重新参与 TOTAL 累加,可能影响 TOTAL 的 Pearson r。需同步移除 comm[Dim.TP] /= 2 hack(line 396),并用 UT 验证。

4.3.4 Pipeline schedule bubble 比例公式(预留)

当前 pipeline schedule 仅区分 vp==1 和 vp>1,未来可扩展为按 schedule 类型使用不同 bubble 比例:

Schedule Bubble 比例公式
1F1B (vp=1) (pp-1) / (pp + mb - 1)
Interleaved (vp>1) (pp-1) / (pp*vp + mb - 1)
Zero-Bubble-V 待定(需实测校准)

本期暂不实现此变更(需同步修改 fast-tuner 的 pipeline conductor),仅预留接口。当 pipeline_scheduler 配置项可用时,bubble 比例公式按上表选择。

4.4 P4:EP 通信单位统一

4.4.1 EP 通信强制转换为时间

当前:comm.py 中 ep_comm_layer_balanced() 和 ep_comm_layer_imbalanced() 返回字节数。comm_time.py:366-374 中 estimate_comm_score() 的转换循环为 [Dim.DP, Dim.TP, Dim.DP],不含 Dim.EP——无论 ttype 是否为 TIME,EP 从未被转换。

变更:在 comm_time.py 的 estimate_from_mem_comm() 中,将 Dim.EP 加入 estimate_comm_score() 转换循环:

# 当前
for dim, ov in zip([Dim.DP, Dim.TP, Dim.DP], [0.9, 0, 0.0]):

# 变更
for dim, ov in zip([Dim.DP, Dim.TP, Dim.DP, Dim.EP], [0.9, 0, 0.0, 0.0]):

EP 通信的 overlap 默认为 0.0(无 overlap),后续可通过配置调整。

接口变更:

位置 变更
comm.py — ep_comm_layer_balanced() 保持返回字节数(不改函数签名)
comm.py — ep_comm_layer_imbalanced() 保持返回字节数(不改函数签名)
comm_time.py:366-374 — 转换循环 将 Dim.EP 加入循环,调用 estimate_comm_score(comm[Dim.EP], Dim.EP, overlap=0.0) 转换为时间
debug.py — estimation_in_real_parts() EP_WAIT 映射不变,但值从字节数变为秒

关于 ttype 条件:当前转换循环整体受 if param["ccfg"].ttype == PerformanceType.TIME: 条件保护。变更后,EP 转换仍在此条件内——仅当 ttype==TIME 时转换。这意味着当 ttype != TIME 时 EP 仍为字节数,但这与 DP/TP 的行为一致(DP/TP 也仅在 TIME 模式下转换)。若需要无条件转换,需单独讨论是否改变 ttype 的语义。

约束:

  • TP 通信(tp_comm_layer)当前也返回字节数,且已在转换循环中(Dim.TP),本期无需额外处理
  • DP 通信(dp_comm_layer)返回 element count,在转换循环中被当作字节数传入 estimate_comm_score()——这是一个潜在的单位不一致,但本期不处理
  • CP 通信(cp_comm_layer)返回 element count,也从未被转换——标记为 follow-up
  • 需同步移除 comm_time.py:396-397 的 TO REMOVE hack(与 P3 §4.3.3 联动)

4.4.2 带宽参数

estimate_comm_score() 已有完整的带宽模型(comm_time.py:489-507),使用 device.level_bandwidth 和 device.level_assign() 按网络层级计算。无需新增参数,确保 EP 通信正确传入即可。


5. 测试设计

5.1 单元测试

P1 测试

用例 ID 描述 期望
P1-01 safety_buffer_mb=1024(默认)、TP=1 时,effective_safety_buffer = 1024 MB,行为与修改前完全一致 Dynamic 分量与修改前相同
P1-02 safety_buffer_mb=1024、TP=2 时,effective_safety_buffer = 512 MB Dynamic 分量比 TP=1 少 512 MB(safety buffer 差异)
P1-03 tp_shardable_overhead_mb=2676、TP=1 时,effective_overhead = 2676 MB,预估 Peak 增加 2676 MB TP=1 偏差从 ~2676 MB 缩小到接近 0
P1-04 tp_shardable_overhead_mb=2676、TP=2 时,effective_overhead = 1338 MB TP=2 预估/实测比保持在合理范围
P1-05 safety_buffer_mb=0 且 tp_shardable_overhead_mb=0 时,Dynamic 分量不含任何额外开销 回归:现有 UT 通过(但需注意 TP=2 时 safety_buffer 从 1 GB 降为 0,有行为变化)
P1-06 safety_buffer_mb=3072、tp_shardable_overhead_mb=0、TP=1 时,effective = 3072 MB 偏差缩小约 2 GB
P1-07 safety_buffer_mb=3072、tp_shardable_overhead_mb=2676、TP=1 时,两项均计入 总增量 = 3072 + 2676 = 5748 MB(需确认不致过度高估)

P2 测试

用例 ID 描述 期望
P2-01 expert_zero3_sharding=True、EP=2→8 时,routed expert param 不随 EP 变化 与当前行为完全一致(抵消)
P2-02 expert_zero3_sharding=False、EP=2→4→8 时,routed expert param 按 1/EP 线性缩放 EP=4 时 param = EP=2 时的 50%;EP=8 时 = 25%
P2-03 expert_zero3_sharding=False 时,OS 和 Grad 也随 EP 线性缩放 OS 和 Grad 的缩放比例与 param 相同
P2-04 expert_zero3_sharding=False 时,gradient clipping buffer(body.py:130)也随 EP 线性缩放 Grad-clip 的缩放比例与 param 相同
P2-05 expert_zero3_sharding 不影响 shared expert 的内存估算 shared expert 的 param/OS/grad 在两种设置下完全相同
P2-06 expert_zero3_sharding 不影响 non-expert 的内存估算 non-expert 的 param/OS/grad 在两种设置下完全相同
P2-07 has_op=False 时,expert_zero3_sharding=True 和 False 的结果相同 两种设置的 shard_p_os_exp 均为 cp * t_exp
P2-08 默认值 expert_zero3_sharding=True 时,所有现有 UT 无回归 全部通过

P3 测试

用例 ID 描述 期望
P3-E1 消融实验 EXP-1:从 time_sum 移除 EP_COMM,6 config 的 BUBBLE 变化方向 记录方向,用于决定 §4.3.2 的修法
P3-E2 消融实验 EXP-2:移除 DP=0 hack,6 config 的 BUBBLE 变化方向 记录方向,判断 DP 是否为主因
P3-E3 消融实验 EXP-3:固定 EP=4,仅改变 DP(4/8/16),PP_WAIT 变化 隔离 DP 对 PP_WAIT 的影响
P3-E4 消融实验 EXP-4:6 config 的 straggler stage id 检查是否随 EP 变化
P3-01 ep_bubble_overlap_ratio=1.0 时,PP_WAIT 随 EP 增大而减小 PP_WAIT 排序与实测一致(待 EXP-1 确认方向后更新期望)
P3-02 ep_bubble_overlap_ratio=0.0 时,行为与修改前一致 PP_WAIT 值与修改前相同
P3-03 ep=1(无 EP 通信)时,overlap_ratio 不生效 PP_WAIT 值与修改前相同
P3-04 移除 DP=0 hack 后,DP 通信正确参与计算 comm[Dim.DP] > 0,TOTAL 包含 DP 通信分量

P4 测试

用例 ID 描述 期望
P4-01 EP 通信经过 estimate_comm_score 转换后,EP_WAIT 单位为秒 EP_WAIT 绝对值与实测差距从 ~1e7 倍缩小到 10 倍以内
P4-02 EP_WAIT 不再淹没 TOTAL 的其他分量 EP_WAIT 占 TOTAL 的比例 < 50%
P4-03 P3+P4 修完后,TOTAL Pearson r > 0.5 6 config 的 TOTAL 排序与实测基本一致
P4-04 无 EP(ep=1)时,TOTAL 行为与修改前一致 回归:现有 UT 通过
P4-05 ttype != TIME 时,EP 通信不经过 estimate_comm_score 转换 EP 仍为字节数(与 DP/TP 行为一致)

5.2 集成测试

用例 ID 描述 期望
INT-01 DeepSeek-V3 缩小版 6 config 全量回归:P1+P2 修完后,Peak HBM 预估/实测比均在 0.85-1.10 TP=1 组从 0.72-0.75 提升到目标范围
INT-02 DeepSeek-V3 缩小版 6 config 全量回归:P3+P4 修完后,PP_WAIT Pearson r > 0.5,TOTAL Pearson r > 0.5 排序正确,无负相关
INT-03 非 MoE 模型(n_exp=1)回归:所有变更对 dense 模型无影响 预估结果与修改前完全一致
INT-04 P1 两个参数交互:safety_buffer_mb=3072 + tp_shardable_overhead_mb=2676,TP=1 和 TP=2 的预估均不过度高估 预估/实测比均在 0.85-1.10

5.3 回归测试

  • 现有 test_sapp_nd 系列 UT 全部通过
  • 现有 test_memory_estimation 系列 UT 全部通过
  • 现有 test_comm_estimation 系列 UT 全部通过
  • 原有输出 shape 与 dtype 保持不变

6. 风险与开放问题

风险 / 开放问题 缓解 / 决策
P1:tp_shardable_overhead_mb 的默认值 2676 MB 仅基于 DeepSeek-V3 缩小版,不同模型的运行时开销比例不同 优先通过 fast-tuner 校准自动设置;短期文档化推荐值的适用范围
P1:safety_buffer 改为 /t 缩放后,TP=2 时从 1 GB 降为 512 MB,可能轻微减少安全余量 TP=2 原本已高估 5%,减小 safety buffer 可缓解高估;若担心可设 safety_buffer_mb=2048 使 TP=2 时仍有 1 GB
P1:safety_buffer_mb 和 tp_shardable_overhead_mb 同时设置时可能过度高估 需用 INT-04 验证;推荐用法是二选一或微调
P2:expert_zero3_sharding 默认 True 保持向后兼容,但大多数 MoE 模型可能都需要设为 False 收集更多 MoE 模型的 ZeRO-3 行为后,考虑在未来版本调整默认值
P3:根因方向已修正——BUBBLE 随 EP 增大(而非减小),但 overlap 修法需消融实验验证 §4.3.1 的消融实验优先级最高;数学推导确认了 BUBBLE 增大方向,但 overlap_ratio 修法的效果需实测验证;若 EXP-1 显示 DP 减少效应是主因,则需改修 DP hack(§4.3.3)
P3:ep_bubble_overlap_ratio=1.0 假设 EP 通信完全在 bubble 内完成,这对不同集群拓扑不一定成立 提供配置项让用户调整;长期可通过 fast-tuner 自动校准
P3:DeepSeek-V3 的 dense/MoE 混合结构导致 EP_COMM 在 stage 间不均匀(arch_hooks.py:218-231),overlap 修正需正确处理 dense stage(ep=1, 无 EP_COMM)和 MoE stage 的差异 overlap 扣除仅对 ep>1 的 stage 生效,dense stage 的 straggler_time 不变
P3:Pipeline schedule bubble 比例公式本期仅预留接口,不实现 标记为 follow-up issue,待 fast-tuner pipeline conductor 同步修改后实现
P4:EP 通信转时间依赖 device.level_bandwidth 的准确性,带宽配置错误会导致 EP_WAIT 绝对值偏差 文档化带宽配置要求;fast-tuner 可用实测 EP_WAIT 校准带宽参数
P4:DP 通信返回 element count 但被 estimate_comm_score 当作字节数处理,存在单位不一致 本期不处理,标记为 follow-up;需确认 DP comm 的 bytes 转换是否在其他位置已完成
P4:CP 通信也从未被 estimate_comm_score 转换 本期不处理,标记为 follow-up issue
P4:comm_time.py:396-397 的 TO REMOVE hack(comm[Dim.TP] /= 2 和 comm[Dim.DP] = 0)需与 P3 §4.3.3 联动清理 移除前需消融实验确认影响;移除后用 UT 验证无回归
P3→P4 依赖:TOTAL 的 Pearson r 改善依赖 P3 先修完 修复顺序:P2 → P1 → P3(消融实验)→ P3(实施)→ P4

7. 验收标准

  • P1:TP=1 配置的预估/实测比从 0.72-0.75 提升到 0.85-1.10,TP=2 配置保持在 1.05-1.10
  • P2:expert_zero3_sharding=False 时,expert param/OS/grad/grad-clip 随 EP 线性缩放(不再被抵消);expert_zero3_sharding=True 时行为与现有完全一致
  • P3:PP_WAIT Pearson r 从 -0.197 提升到 > 0.5,6 config 排序与实测一致;EP_WAIT Pearson r 不退化(保持 > 0.85)
  • P4:EP_WAIT 绝对值与实测差距从 ~1e7 倍缩小到 10 倍以内;TOTAL Pearson r 从 -0.534 提升到 > 0.5(P3 修完后评估);各分量对 TOTAL 的贡献比例合理,不出现单一分量 > 90%
  • 全部现有 UT 无回归
  • 非 MoE 模型(n_exp=1)的预估结果与修改前完全一致

8. 优先级与依赖关系

优先级 问题 影响 依赖 修复难度
P1 TP 基线偏差 OOM 误判风险 无 中(需建模开销项)
P2 EP/ZeRO-3 抵消 EP 策略筛选失真 无 低(只需加配置项)
P3 PP_WAIT 负相关 PP 性能估算反转 消融实验结果 高(需确认根因后再建模)
P4 TOTAL 负相关 总时间不可靠 P3 中(带宽转换 + 依赖 P3)

建议修复顺序:P2 → P1 → P3(消融实验)→ P3(实施)→ P4。


9. 参考

  • PR #827 实测验证数据:exp_peak_hbm_validation.xlsx、exp_ep_wait_correlation.xlsx
  • QA.md Q9:HBM 预估偏差拆解分析
  • body.py stat_p_layer / stat_os_layer / stat_grad_layer(lines 54-89, 130)
  • _cost_model_parser.py shard_p_os_exp / d_exp 计算(lines 59-61, 131, 135, 137)
  • _cost_model_variables.py _CostModVar dataclass(lines 34-240)
  • comm.py ep_comm_layer_balanced / ep_comm_layer_imbalanced(lines 167-226)
  • comm_time.py estimate_from_mem_comm / estimate_comm_score(lines 314-425, 489-507)
  • estimate.py BUBBLE / PP_COMM / TOTAL 计算(lines 238-333, 336-369, 467-499)
  • debug.py PerfParts → RealParts 映射(lines 567-594)
  • arch_hooks.py hook_dense / hook_moe 的 ep 设置(lines 218-231)
  • _backbone.py safety buffer 硬编码(lines 660-662)
likedislike