已开启
训练栈:examples/ 三个 train.yaml 在最新 master 上报错(dp_shard leaf/grad + vl-moe get_input_embeddings/AC 回退) #230
xuxinglei创建于 6月17日
6月17日 修改标题为 “训练栈:examples/ 三个 train.yaml 在最新 master 上报错(dp_shard leaf/grad + vl-moe get_input_embeddings/AC 回退)”,原标题为“训练栈:examples/ 三个 train.yaml 在最新 master 上全部跑不起来(dp_shard leaf/grad + vl-moe get_input_embeddings/AC 回退)”
6月22日 关联了pull request:fix(fsdp): detach narrowed shard in reset_sharded_param to avoid is_leaf error
6月22日 关联了pull request:fix(fsdp): detach narrowed shard in reset_sharded_param to avoid is_leaf error
6月24日 关联了pull request:fix: restore reset_sharded_param .detach() and Qwen3VLMoe get_input_embeddings
6月24日 关联了pull request:fix: restore reset_sharded_param .detach() and Qwen3VLMoe get_input_embeddings
该问题是怎么引起的?
PR #599(训练栈 + qwen3.5 dense/moe/vl-moe 对齐,描述中显式依赖 PR #633 的
dp_shard==1修复)合入时,examples/下三个train.yaml是验证过能跑通 + 1c↔FSDP4 对齐的。此后 master 上的若干提交把四处回退/移除了,导致现在直接跑这三个 example 在 init / parallelize 阶段全部崩溃,无法开始训练。下面逐文件说明每一处为什么是 bug。1.
hyper_parallel/platform/torch/fully_shard/param.py(TorchHSDPParamV2)—— sharded_local_tensor丢了.detach()这段代码做什么:meta-init(
to_empty)+ FSDP 分片后,要把"切出来的本地分片"绑到 DTensor 参数的_local_tensor上。当前 master:if updated_local_tensor: with torch.no_grad(): local_view = local_tensor.narrow(dim=shard_dim, start=0, length=length) # ← 缺 .detach() set_requires_grad_if_needed(self.sharded_param, local_view) self.sharded_param._local_tensor = local_view为什么是 bug:
Tensor.narrow(...)返回的是 base(local_tensor)的 autograd 视图(view),仍挂在它的求导图上;这步在to_empty期间、torch.no_grad()下执行 → 是"no_grad 下创建的视图"。之后权重加载 / 混精 upcast 在 grad 模式下原地改写它/它的 base。等构造torch.optim.AdamW时,add_param_group读param.is_leaf(DTensor 的is_leaf→self._local_tensor.is_leaf),PyTorch autograd 检测到"no_grad 下建的视图、其 base 又在 grad 模式下被原地改"这种自相矛盾状态,直接抛 RuntimeError。为什么这样改能修:
.narrow(...).detach()返回共享 storage 但脱离求导图的张量(干净 leaf),不是local_tensor的 autograd 视图——is_leaf读取不再触发该检查。PR #633 原本就有这个.detach(),master 上某次fully_shard重构把它丢了。本质是恢复 PR #633。2.
hyper_parallel/platform/torch/fully_shard/state.py(TorchHSDPStateV2.lazy_init)——dp_shard==1时漏 reset这段代码做什么:
lazy_init在 meta 物化后把每个 sharded 参数reset_sharded_param()重新包回正确的 DTensornn.Parameter,让优化器和 forward 拿到对的对象。当前 master:def lazy_init(self): if self.is_shard and not self._reset_sharded_params: # ← 多了 self.is_shard for hsdp_param in self.hsdp_params: hsdp_param.reset_sharded_param() self._reset_sharded_params = True为什么是 bug:
self.is_shard在dp_shard==1(world_size==1、实际不分片)时为 False,于是整段 reset 被跳过——单卡路径下 param 没被重新包/重置,留在 meta(HSDP params still on meta device),且第 1 点的 leaf 不变量也建立不起来。PR #599(380840b)时这里是if not self._reset_sharded_params:,没有self.is_shard。world_size==1 时同样要 reset/re-wrap(unshard/shard hook 要把模块属性换回普通nn.Parameter,避免 DTensor 漏进 forward),故去掉守卫。本文件 #2 与上面 #1 合起来就是 PR #633 的"dp_shard==1leaf/grad"修复,缺一不可。3.
hyper_parallel/models/qwen3_vl_moe/model.py(Qwen3VLMoeModel)——get_input_embeddings被误删这段代码做什么:
Qwen3VLMoeModel.forward在inputs_embeds is None时按 HF 标准做法取文本词嵌入:def forward(self, input_ids, ..., inputs_embeds=None, pixel_values=None, ...): if inputs_embeds is None: inputs_embeds = self.get_input_embeddings()(input_ids) # ← 调用 get_input_embeddings ... # 之后再把图像特征 merge 进 inputs_embeds为什么是 bug:
Qwen3VLMoeModel.get_input_embeddings(返回self.language_model.embed_tokens)被d464b132 fix(codecheck): add missing docstrings for C0116 warnings(一次性动了 66 个文件的 codecheck/docstring 清理)连带删掉了,但forward仍在调它 → vl-moe 第一步 forward 就崩:AttributeError: 'Qwen3VLMoeModel' object has no attribute 'get_input_embeddings'。为什么这样改能修:把
get_input_embeddings(+ 对称的set_input_embeddings)补回Qwen3VLMoeModel,指向self.language_model.embed_tokens——纯属恢复 PR #599 就有的方法。4.
hyper_parallel/platform/torch/activation_checkpoint/activation_swap.py(_check_and_mark_wrapped)—— 不容忍跨层共享的 rotary这段代码做什么:激活重计算逐层
checkpoint_wrapper(layer)时,_check_and_mark_wrapped遍历该层子模块:先"重叠保护"(任一子模块已_is_wrapped就报错),再把所有子模块标记为已包裹。为什么是 bug:Qwen3-VL-MoE 的所有 text decoder 层共享同一个
MultiModalRotaryEmbedding实例(有意设计:位置编码算一次、各层共用)。包第 0 层时该 rotary 被标记_is_wrapped;包第 1 层时,同一个实例被判"已包裹" →ValueError: Submodule 'MultiModalRotaryEmbedding' of 'Qwen3VLMoeTextDecoder' is already wrapped。这个 overlap 守卫是 PR #599 之后才加的(PR #599 没有它),加时没考虑"合法的跨层共享子模块"。vl-moe example 用activation_checkpoint: full必然走到这里(dense/moe 用none不触发)。为什么这样改能修:被共享的 rotary 是无状态的(只算位置编码、没有要 checkpoint 的参数),包一次(随第 0 层)、后续层跳过它是正确的,不存在真正的"重叠区域"风险。所以把"子模块已包裹就
raise"改成"continue(跳过)"。重现步骤
环境:8×Ascend 910B3,CANN 9.0.0,torch 2.7.1 + torch_npu 2.7.1,最新
upstream/master(eebcca6)。填好三个 example 的weights_path(dense/moe 另需preset_ptbatch 文件),确定性变量HCCL_DETERMINISTIC=true LCCL_DETERMINISTIC=1 ASCEND_LAUNCH_BLOCKING=1 HCCL_OP_BASE_FFTS_MODE_ENABLE=false,分别跑:三个全部崩溃,无一能开始训练。
报错信息
dense / moe —— optimizer 构造(文件 #1+#2):
vl-moe —— parallelize 的 AC 包裹(文件 #4):
vl-moe —— AC 修复后 forward(文件 #3):
修复(恢复 #1
.detach()+ #2 去self.is_shard守卫 + #3 补回get_input_embeddings+ #4 容忍共享子模块)后,三个 example 各 20 步 Training completed,1c↔FSDP4 自洽通过(dense/moe ULP 级 ~1e-6,vl-moe 逐位)。修复见关联 PR。