PR #827(Issue #132)在 DeepSeek-V3 缩小版上做了 6 config 的 profiling 实测验证,发现了 4 个问题。其中 P2(EP/ZeRO-3 抵消)和 P4 的 EP_WAIT 单位问题是 EP 估算模块本身的 bug,P1(TP 运行时开销未建模)和 P3(PP 通信估算)是其他模块的问题,但在 EP 验证实验中一起暴露出来。
实验模型为 DeepSeek-V3 缩小版(~1.3B, 32 experts, 12 层, PP=2, 32 卡),覆盖 EP(2/4/8) × TP(1/2) 共 6 个配置。
Peak HBM 预估 vs 实测:
通信分量 Pearson r:
从上述数据中可以清楚看到两类问题:显存预估的 TP/EP 偏差,以及通信估算的排序反转。按优先级整理如下。
现象
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 时这些开销几乎消失——因为它们会被隐式分片。来源:
实测 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 缩放。
shard_p_os_non_exp_partial
_inner_dynamic_mem
相关代码
_backbone.py:660-662
_backbone.py:651-658
body.py:62
os_max_shard * cp
t * cp
body.py:88
shard_grad_non_exp
t
shard_p_os_non_exp
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 公式:
stat_p_layer
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:
shard_p_os_exp = d_exp * cp * t_exp
d_exp
1/ep
d * t * cp / t_exp / ep
d * t * cp² / ep
d / ep
d / ep * cp * t_exp
d * t / ep
d * t / ep * cp * t_exp
以 etp 分支为例,代入后分子的 /ep 和分母中的 1/ep 刚好抵消:routed_mem = routed_p * bytes_p / (d * t * cp²),EP 完全消失。其他分支同理,EP 均被抵消。
/ep
routed_mem = routed_p * bytes_p / (d * t * cp²)
同样的抵消影响 OS、Grad 和 Grad-clip(body.py:130)。
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()
_cost_model_parser.py:59-61
config_optimizer_shard()
shard_p_os_exp
_cost_model_parser.py:62-66
shard_grad_exp
body.py:58
body.py:72
stat_os_layer
body.py:84
stat_grad_layer
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
_cost_model_variables.py:88-116
_CostModVar
expert_zero3_sharding
PP_WAIT 的 Pearson r = -0.197,呈负相关——模型认为 PP_WAIT 更高的配置,实测反而更低,排序完全反转。
这个问题不影响显存估算,但会影响性能估算——策略搜索可能选错 PP 配置。同时 PP_WAIT 的负相关也拖累了 TOTAL 估算(见 P4)。
当前 PP_WAIT = BUBBLE + PP_COMM(debug.py:590-593),其中:
PP_WAIT = BUBBLE + PP_COMM
debug.py:590-593
pipeline_perf - time_sum
estimate.py:325-332
time_sum
MANUAL_P2P_RATIO = 0.002
estimate.py:42
⚠️ 数学推导表明原根因方向错误:对 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 的情况分析:
结论: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,但方向仍为"增大或不变"。
arch_hooks.py:218-231
hook_dense
ccfg.ep = 1
hook_moe
ccfg.ep = saved.ep
真正的根因是估算模型与现实的系统性偏差:估算模型将 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 建模。
可能的次级因素(需通过消融实验验证):
comm[Dim.DP] = 0
comm_time.py:397
apply_regression_coefficients()
此外,当前 pipeline schedule 仅区分 vp==1(1F1B)和 vp>1(interleaved),没有考虑 zero_bubble_v 等 schedule 的不同 bubble 比例。
estimate.py:238-333
estimate_pipeline()
estimate.py:295-313
estimate.py:336-369
estimate_p2p_comm()
estimate.py:467-499
debug.py:587-593
estimation_in_real_parts()
ccfg.ep=1
ccfg.ep=saved.ep
pipeline_builder.py:25-177
common.py:382-389
TOTAL 的 Pearson r = -0.534,整体负相关。另外 EP_WAIT 的绝对值与实测差了约 1e7 倍,是一个单位问题。
总时间估算不可靠,目前只有分项(COMP、EP_WAIT)有一定参考价值。
TOTAL 负相关由两个独立问题叠加导致:
comm.py:167-226
comm_time.py:366-374
estimate_comm_score()
[Dim.DP, Dim.TP, Dim.DP]
注意:即使 ttype == PerformanceType.TIME,EP 也不被转换。问题不是"仅在 TIME 模式下转换",而是"EP 从未被转换"。
ttype == PerformanceType.TIME
comm.py:167-181
ep_comm_layer_balanced()
comm.py:184-226
ep_comm_layer_imbalanced()
Dim.EP
Dim.CP
comm_time.py:489-507
comm_time.py:396-397
comm[Dim.TP] /= 2
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。
cp_comm_layer
shard_p_os_exp_partial
当前:_backbone.py:660-662 中 safety_buffer = 1024 * 1024 * 1024(1 GB 硬编码,flat 不随 TP 缩放)
safety_buffer = 1024 * 1024 * 1024
变更:将 safety_buffer 改为从 CostModelConfig 读取,支持用户配置,并随 TP 缩放:
CostModelConfig
safety_buffer_mb
计算公式:
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,与现有字节级累加逻辑一致。
_backbone.py
self.mb()
约束:
设计决策:Safety buffer 代表的运行时余量(graph memory、框架开销等)本质上也是 TP-shardable 的,与 tp_shardable_overhead 一致。将其改为 /t 缩放比新增独立的 TP-shardable 开销项更简洁,避免了两个参数叠加导致 TP=2 过度高估的问题。
新增 TP-shardable 运行时开销的显式建模,覆盖 safety buffer 之外的额外开销(输出层 activation、通信 double-buffer 等),当 TP > 1 时被 /t 分片:
/t
tp_shardable_overhead_mb
effective_overhead = tp_shardable_overhead_mb / t (单位 MB)
插入位置:在 _backbone.py 的 memory 汇总阶段,加到 Dynamic 分量上(与 safety_buffer 同级)。
为什么需要两个参数:safety_buffer_mb 是通用的安全余量(所有模型都需要),tp_shardable_overhead_mb 是 MoE/特定模型结构的额外开销建模。两者职责不同,分开配置更灵活。
添加位置:_cost_model_variables.py 的 _CostModVar dataclass,与 has_op、has_grad_shard 等布尔标志同组(line 115-116 附近)。
_cost_model_variables.py
has_op
has_grad_shard
当前(_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 抵消。
expert_zero3_sharding=False
shard_p_os_exp = cp * t_exp
注意:当 has_op=False 时,原公式已为 1 * cp * t_exp = cp * t_exp,此时 expert_zero3_sharding 的取值无影响——没有 ZeRO-3 就不存在抵消问题。
has_op=False
1 * cp * t_exp = cp * t_exp
expert_zero3_sharding=False 时受影响的公式:
routed_p / ep * bytes_p / shard_p_os_exp
routed_p / ep * 2 * bytes_os / shard_p_os_exp
routed_p / ep * bytes_grad / shard_grad_exp
不受影响的公式:
shard_grad_exp_partial
True
config_dp_tp_exp
cp * t_exp
在实施修复前,需先通过消融实验确认负相关的真正根因:
决策规则:
当前(estimate.py:295-332):
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 的重叠比例:
overlap_ratio
ep_bubble_overlap_ratio
效果:当 overlap_ratio=1.0 时,EP 通信不增加 effective_straggler,pipeline_perf 不随 EP 增大,而 time_sum 中的 EP_COMM 仍计为有用工作,BUBBLE = pipeline_perf - time_sum 随 EP 增大而减小,PP_WAIT 减小——与实测方向一致。
ep ≤ 1
当前(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 验证。
当前 pipeline schedule 仅区分 vp==1 和 vp>1,未来可扩展为按 schedule 类型使用不同 bubble 比例:
(pp-1) / (pp + mb - 1)
(pp-1) / (pp*vp + mb - 1)
本期暂不实现此变更(需同步修改 fast-tuner 的 pipeline conductor),仅预留接口。当 pipeline_scheduler 配置项可用时,bubble 比例公式按上表选择。
pipeline_scheduler
当前: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.py
变更:在 comm_time.py 的 estimate_from_mem_comm() 中,将 Dim.EP 加入 estimate_comm_score() 转换循环:
comm_time.py
estimate_from_mem_comm()
# 当前 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),后续可通过配置调整。
接口变更:
estimate_comm_score(comm[Dim.EP], Dim.EP, overlap=0.0)
debug.py
关于 ttype 条件:当前转换循环整体受 if param["ccfg"].ttype == PerformanceType.TIME: 条件保护。变更后,EP 转换仍在此条件内——仅当 ttype==TIME 时转换。这意味着当 ttype != TIME 时 EP 仍为字节数,但这与 DP/TP 的行为一致(DP/TP 也仅在 TIME 模式下转换)。若需要无条件转换,需单独讨论是否改变 ttype 的语义。
if param["ccfg"].ttype == PerformanceType.TIME:
tp_comm_layer
Dim.TP
dp_comm_layer
TO REMOVE
estimate_comm_score() 已有完整的带宽模型(comm_time.py:489-507),使用 device.level_bandwidth 和 device.level_assign() 按网络层级计算。无需新增参数,确保 EP 通信正确传入即可。
device.level_bandwidth
device.level_assign()
test_sapp_nd
test_memory_estimation
test_comm_estimation
ep_bubble_overlap_ratio=1.0
expert_zero3_sharding=True
建议修复顺序:P2 → P1 → P3(消融实验)→ P3(实施)→ P4。
exp_peak_hbm_validation.xlsx
exp_ep_wait_correlation.xlsx
body.py
_cost_model_parser.py
estimate.py
arch_hooks.py
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 实测:
通信分量 Pearson r:
从上述数据中可以清楚看到两类问题:显存预估的 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 时这些开销几乎消失——因为它们会被隐式分片。来源:
实测 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_bufferbody.py:62— non-expert param 使用shard_p_os_non_exp_partial,当 has_op=True 时为os_max_shard * cp(非简单 /t),当 has_op=False 时为t * cpbody.py:88— non-expert grad 使用shard_grad_non_exp,当 has_grad_shard=False 时为t(纯 TP 分片),当 has_grad_shard=True 时为shard_p_os_non_exp2.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 公式:shard_p_os_exp = d_exp * cp * t_exp(when has_op=True),而d_exp在所有分支中都包含1/ep:d * t * cp / t_exp / epd * t * cp² / epd / epd / ep * cp * t_expd * t / epd * 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—_CostModVardataclass 中目前无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),其中:pipeline_perf - time_sum(estimate.py:325-332),time_sum包含所有 PerfParts(含 EP_COMM)MANUAL_P2P_RATIO = 0.002(estimate.py:42),与 EP 无关⚠️ 数学推导表明原根因方向错误:对 vp=1 的 1F1B schedule(DeepSeek-V3 测试配置),形式化推导:
对 p=2, 添加 EP_COMM Δ 到各 stage 的情况分析:
结论: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 建模。
可能的次级因素(需通过消融实验验证):
comm[Dim.DP] = 0(comm_time.py:397的 TO REMOVE hack)已将 DP 通信清零,此机制在估算中被掩盖apply_regression_coefficients()对各 PerfParts 独立缩放,EP_COMM 和 COMPUTE 的系数不同可能导致 BUBBLE 被扭曲此外,当前 pipeline schedule 仅区分 vp==1(1F1B)和 vp>1(interleaved),没有考虑 zero_bubble_v 等 schedule 的不同 bubble 比例。
相关代码
estimate.py:238-333—estimate_pipeline()计算 BUBBLEestimate.py:295-313— time_sum 累加循环(含 EP_COMM)estimate.py:336-369—estimate_p2p_comm()计算 PP_COMMestimate.py:467-499—apply_regression_coefficients()独立缩放各 PerfPartsdebug.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 负相关由两个独立问题叠加导致:
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.CPcomm_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 目标
expert_zero3_sharding配置项,使 expert param/OS/grad/grad-clip 在不做 ZeRO-3 分片时随 EP 正确缩放(P2)3.2 非目标
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计算公式:
注意:
_backbone.py中 Dynamic 分量以字节累加(safety_buffer = 1024 * 1024 * 1024直接加到字节级的 Dynamic),最终通过self.mb()(÷ 1024²)转回 MB 输出。参数safety_buffer_mb以 MB 为单位输入,需在内部乘以 1024² 转为字节后再加入 Dynamic,与现有字节级累加逻辑一致。约束:
设计决策: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计算公式:
插入位置:在
_backbone.py的 memory 汇总阶段,加到 Dynamic 分量上(与 safety_buffer 同级)。约束:
为什么需要两个参数:
safety_buffer_mb是通用的安全余量(所有模型都需要),tp_shardable_overhead_mb是 MoE/特定模型结构的额外开销建模。两者职责不同,分开配置更灵活。4.2 P2:expert_zero3_sharding 配置项
4.2.1 新增配置项
expert_zero3_sharding添加位置:
_cost_model_variables.py的_CostModVardataclass,与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_layerrouted_p / ep * bytes_p / shard_p_os_expstat_os_layerrouted_p / ep * 2 * bytes_os / shard_p_os_expstat_grad_layerrouted_p / ep * bytes_grad / shard_grad_exprouted_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 专用)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 根因确认(消融实验,优先级最高)
在实施修复前,需先通过消融实验确认负相关的真正根因:
comm[Dim.DP] = 0hack,恢复 DP 通信,观察 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 为例):
其中
overlap_ratio为 EP 通信与 pipeline bubble 的重叠比例:ep_bubble_overlap_ratio效果:当 overlap_ratio=1.0 时,EP 通信不增加 effective_straggler,pipeline_perf 不随 EP 增大,而 time_sum 中的 EP_COMM 仍计为有用工作,BUBBLE = pipeline_perf - time_sum 随 EP 增大而减小,PP_WAIT 减小——与实测方向一致。
约束:
ep_bubble_overlap_ratio≤ 1.0ep ≤ 1时,ratio 不生效(无 EP 通信)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] /= 2hack(line 396),并用 UT 验证。4.3.4 Pipeline schedule bubble 比例公式(预留)
当前 pipeline schedule 仅区分 vp==1 和 vp>1,未来可扩展为按 schedule 类型使用不同 bubble 比例:
(pp-1) / (pp + mb - 1)(pp-1) / (pp*vp + mb - 1)本期暂不实现此变更(需同步修改 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()关于 ttype 条件:当前转换循环整体受
if param["ccfg"].ttype == PerformanceType.TIME:条件保护。变更后,EP 转换仍在此条件内——仅当 ttype==TIME 时转换。这意味着当 ttype != TIME 时 EP 仍为字节数,但这与 DP/TP 的行为一致(DP/TP 也仅在 TIME 模式下转换)。若需要无条件转换,需单独讨论是否改变 ttype 的语义。约束:
tp_comm_layer)当前也返回字节数,且已在转换循环中(Dim.TP),本期无需额外处理dp_comm_layer)返回 element count,在转换循环中被当作字节数传入estimate_comm_score()——这是一个潜在的单位不一致,但本期不处理cp_comm_layer)返回 element count,也从未被转换——标记为 follow-upcomm_time.py:396-397的TO REMOVEhack(与 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 测试
P2 测试
P3 测试
P4 测试
5.2 集成测试
5.3 回归测试
test_sapp_nd系列 UT 全部通过test_memory_estimation系列 UT 全部通过test_comm_estimation系列 UT 全部通过6. 风险与开放问题
tp_shardable_overhead_mb的默认值 2676 MB 仅基于 DeepSeek-V3 缩小版,不同模型的运行时开销比例不同expert_zero3_sharding默认 True 保持向后兼容,但大多数 MoE 模型可能都需要设为 Falseep_bubble_overlap_ratio=1.0假设 EP 通信完全在 bubble 内完成,这对不同集群拓扑不一定成立arch_hooks.py:218-231),overlap 修正需正确处理 dense stage(ep=1, 无 EP_COMM)和 MoE stage 的差异device.level_bandwidth的准确性,带宽配置错误会导致 EP_WAIT 绝对值偏差comm_time.py:396-397的TO REMOVEhack(comm[Dim.TP] /= 2和comm[Dim.DP] = 0)需与 P3 §4.3.3 联动清理7. 验收标准
expert_zero3_sharding=False时,expert param/OS/grad/grad-clip 随 EP 线性缩放(不再被抵消);expert_zero3_sharding=True时行为与现有完全一致8. 优先级与依赖关系
建议修复顺序:P2 → P1 → P3(消融实验)→ P3(实施)→ P4。
9. 参考
exp_peak_hbm_validation.xlsx、exp_ep_wait_correlation.xlsxbody.pystat_p_layer / stat_os_layer / stat_grad_layer(lines 54-89, 130)_cost_model_parser.pyshard_p_os_exp / d_exp 计算(lines 59-61, 131, 135, 137)_cost_model_variables.py_CostModVar dataclass(lines 34-240)comm.pyep_comm_layer_balanced / ep_comm_layer_imbalanced(lines 167-226)comm_time.pyestimate_from_mem_comm / estimate_comm_score(lines 314-425, 489-507)estimate.pyBUBBLE / PP_COMM / TOTAL 计算(lines 238-333, 336-369, 467-499)debug.pyPerfParts → RealParts 映射(lines 567-594)arch_hooks.pyhook_dense / hook_moe 的 ep 设置(lines 218-231)_backbone.pysafety buffer 硬编码(lines 660-662)