host 8×910B3 / torch_npu 2.7.1,做 PP(2)×DP-FSDP(2)×CP(2)×TP(1)(mesh=(pp,dp,cp,tp)=(2,2,2,1),4 层 DenseFFN)与单卡逐参数梯度对齐时失配。定位出 4 项,每项给出根因与如何修改(hp 库侧 / 用户脚本+PP 侧)。文末给出最小可跑通集与优化项。①② 偏库侧防呆+文档,③ 示例工具,④ 真实库 bug。
① [库侧防呆 / 文档] ContextParallel 对 BNSD 的 head_dim 误配无早期校验
[B, N, S, D]
ContextParallel(seq_dim=2, head_dim=3)
head_dim
core/context_parallel/context_parallel.py:449
_scatter_seq_to_head
shape[head_dim] = 64
:394
64 % 2 == 0
q @ kᵀ
Partial:['sum']
div
SkipDTensorDispatch
apply
head_dim != seq_dim
parallel_ops.py
_check_partial_inputs
cp_plan = ContextParallel(seq_dim=2, head_dim=1, ulysses_degree=cp_size)
② [文档 / 防呆] Ulysses 要求按 seq 分片喂,库无显式校验
_to_cp_dtensor
Shard(seq_dim)
S·cp
[., ., S·cp, S·cp]
[S, S]
SDPACore
tests/torch/context_parallel/_test_context_parallel.py:498-500
seq_dim / head_dim
schedule.run
ls = seq // cp_size; x = x[:, cp_rank*ls:(cp_rank+1)*ls]
tokens_local = global_tokens[:, cp_slice]
sdpa_core
[., ., S, S]
S/cp
fully_shard
(dp, cp)
optimizer.step()
examples/torch/llama3/pp_fsdp_tp_cp_sp_example.py
③ [示例改进] PP stage 层名重映射应按 pp_rank 偏移
key.replace("layers.0.", f"layers.{rank // dp_size}.")
layers.0.
import re layers_per_stage = (num_layers + pp_size - 1) // pp_size offset = pp_rank * layers_per_stage # 只按 pp_rank,与 dp / cp 无关 def remap(key): return re.sub(r"layers\.(\d+)\.", lambda m: f"layers.{offset + int(m.group(1))}.", key) # 权重同步: s_key = remap(key) # 梯度对比: s_name = remap(name2)
④ [库 bug] PP-stage-root fully_shard + GPipe 多 microbatch 下 root 级参数梯度偏差
fully_shard(chunk_model)
final_norm.weight
lm_head.weight
fully_shard(block)
!790 [feature] torch backend pp support FSDP metastep
platform/torch/fully_shard/scheduler.py
platform/torch/pipeline_parallel/stage.py
fully_shard(chunk_model, mesh=dp_mesh)
final_norm
lm_head
_shard_blocks
✅ 最小可跑通集(改这些 → demo 与单卡梯度对齐)
pp_rank * layers_per_stage
all_gather
(pp, dp, tp)
ContextParallel
head_dim=1
🔧 优化项(分开,非跑通必需)
环境:host 8×910B3,torch_npu 2.7.1,hyper_parallel editable 安装。
torchrun --nproc_per_node=2
rank // dp_size
fully_shard(layers[0])
CP 正向 2×2 消融实测(与单卡 maxdiff):
head_dim=3 + 整seq : 3.481 MISMATCH ← 现配置 head_dim=3 + seq分片 : 1.770 MISMATCH head_dim=1 + 整seq : 3.524 MISMATCH head_dim=1 + seq分片 : 0.000000 MATCH ← 唯一与单卡逐位相等
real DTensor 路径下 head_dim=3 直接崩(收缩轴被切 → 部分和):
ValueError: For div, input 0 with Layout Configuration: Mesh shape: (2,) Alias Names: ('cp',) Partial: ['sum'] Tensor Map: ('None', 'None', 'None', 'None') Rank List: (0, 1) has Partial status which is not allowed.
④ root-FSDP:final_norm.weight / lm_head.weight 梯度稀疏偏差,~0.4% 元素超 (rtol = atol = 0.002),个别元素差约 28×;各 block 参数梯度全对。
该问题是怎么引起的?
host 8×910B3 / torch_npu 2.7.1,做 PP(2)×DP-FSDP(2)×CP(2)×TP(1)(mesh=(pp,dp,cp,tp)=(2,2,2,1),4 层 DenseFFN)与单卡逐参数梯度对齐时失配。定位出 4 项,每项给出根因与如何修改(hp 库侧 / 用户脚本+PP 侧)。文末给出最小可跑通集与优化项。①② 偏库侧防呆+文档,③ 示例工具,④ 真实库 bug。
① [库侧防呆 / 文档] ContextParallel 对 BNSD 的 head_dim 误配无早期校验
[B, N, S, D]的 attention core 用ContextParallel(seq_dim=2, head_dim=3)(误指每头特征维 D);head_dim应是 num_heads 轴(BNSD = 1,core/context_parallel/context_parallel.py:449)。_scatter_seq_to_head的 divisibility 校验用shape[head_dim] = 64(:394),64 % 2 == 0蒙混过关,all-to-all 切掉收缩轴 →q @ kᵀ退化为Partial:['sum']→div崩;SkipDTensorDispatch下静默算错。apply/ pre-hook 增加head_dim != seq_dim校验,divisibility 针对真正 head 轴,parallel_ops.py的_check_partial_inputs报错补「Partial 多因 ContextParallel head_dim 指到注意力收缩维」提示。cp_plan = ContextParallel(seq_dim=2, head_dim=1, ulysses_degree=cp_size)。② [文档 / 防呆] Ulysses 要求按 seq 分片喂,库无显式校验
_to_cp_dtensor把整 seq 当Shard(seq_dim)拼成S·cp重复伪全序列;head_dim 修对后 scores 变[., ., S·cp, S·cp],与[S, S]的 mask 不匹配,因果 mask 被SDPACore门控静默丢。契约见tests/torch/context_parallel/_test_context_parallel.py:498-500(先 slice 再喂)。seq_dim / head_dim对照表 + 「每 rank 喂 seq 分片」醒目说明;可选在 pre-hook 支持传入全局 seq 做一致性校验。schedule.run前):ls = seq // cp_size; x = x[:, cp_rank*ls:(cp_rank+1)*ls],labels 同样切(参照官方 example 第 242 行tokens_local = global_tokens[:, cp_slice])。sdpa_core内部 all-to-all 把 local seq 重组成整 seq 算注意力、再切回;mask 传整[S, S](buffer 已是整 seq,all-to-all 后 scores =[., ., S, S]匹配)。S/cp个 token,对同一参数的梯度需 cp 维 all-reduce(sum);loss 同理需 cp 维规约。建议fully_shard的 mesh 纳入 cp 维((dp, cp)),或在optimizer.step()前对参数梯度在 cp 维 all-reduce。官方 exampleexamples/torch/llama3/pp_fsdp_tp_cp_sp_example.py只验证「能跑 + loss 有限」、未做与单卡的逐参数梯度对齐,也无显式 cp 维梯度规约 —— 此对齐需在 demo 里额外处理并实测。③ [示例改进] PP stage 层名重映射应按 pp_rank 偏移
key.replace("layers.0.", f"layers.{rank // dp_size}."),层偏移混了 dp / pp 且只替换字面layers.0.。8-rank 验证:rank 0/1 对,2/3 第 0 层错,4/5 第 1 层错,6/7 两层全错 → 多数 rank 装错初始权重、配错梯度层。import re layers_per_stage = (num_layers + pp_size - 1) // pp_size offset = pp_rank * layers_per_stage # 只按 pp_rank,与 dp / cp 无关 def remap(key): return re.sub(r"layers\.(\d+)\.", lambda m: f"layers.{offset + int(m.group(1))}.", key) # 权重同步: s_key = remap(key) # 梯度对比: s_name = remap(name2)④ [库 bug] PP-stage-root fully_shard + GPipe 多 microbatch 下 root 级参数梯度偏差
fully_shard(chunk_model)(分片 stage root)+ GPipe 多 microbatch 时,root-FSDP 单元里的非 block 参数(末 stage 的final_norm.weight/lm_head.weight)梯度算偏(稀疏 ~0.4% 元素超 0.002 容差,个别 ~28×);各 block 单独fully_shard(block)全对。与 CP 无关(cp=1 仍失配)、与 mb 无关(mb=1 仍失配);自!790 [feature] torch backend pp support FSDP metastep上线即在,非回归。怀疑platform/torch/fully_shard/scheduler.py+platform/torch/pipeline_parallel/stage.py。fully_sharddocstring 明确 warn「PP stage root 不要 fully_shard」。fully_shard(chunk_model, mesh=dp_mesh);改为对每个子模块(每个 block +final_norm+lm_head)逐个fully_shard、不分片 stage root(参照 example_shard_blocks的 "not the stage root")。✅ 最小可跑通集(改这些 → demo 与单卡梯度对齐)
pp_rank * layers_per_stage偏移 + 正则覆盖所有本地层(必须,否则权重 / 梯度配错层)。fully_shard(chunk_model),改为逐子模块fully_shard(每个 block +final_norm+lm_head)、不 shard stage root(必须:规避库 bug,且让all_gather还原 full grad 的对比逻辑成立)。(pp, dp, tp)、不 applyContextParallel。head_dim=1+ 数据按 cp 切 seq + cp 维参数梯度规约(见 ②,需实测)。🔧 优化项(分开,非跑通必需)
fully_sharddocstring warn。重现步骤
环境:host 8×910B3,torch_npu 2.7.1,hyper_parallel editable 安装。
SkipDTensorDispatch下对照单卡 SDPA):head_dim ∈ {3, 1} × 喂法 ∈ {整 seq, seq 分片} 共 4 组,torchrun --nproc_per_node=2比对与单卡 local 切片的 maxdiff。rank // dp_size与pp_rank * layers_per_stage计算层映射,对照是否命中正确全局层。fully_shard(layers[0])+fully_shard(chunk_model),GPipe 多 microbatch 训练后 all_gather 比对final_norm/lm_head梯度与单卡。报错信息
CP 正向 2×2 消融实测(与单卡 maxdiff):
real DTensor 路径下 head_dim=3 直接崩(收缩轴被切 → 部分和):
④ root-FSDP:
final_norm.weight/lm_head.weight梯度稀疏偏差,~0.4% 元素超 (rtol = atol = 0.002),个别元素差约 28×;各 block 参数梯度全对。