Prhub

#6950 [trainer] fix: spmd_types and activation checkpointing composability bug

原始 PR 作者 acisseJZhong 合并时间 2026-07-07 10:52 文件变更 1 提交数 2 评论 2 代码增减 +10 / -4

执行摘要

修复 spmd_types 与选择性激活检查点兼容性崩溃

spmd_backend=spmd_typesactivation_checkpoint=selective 组合,且 use_torch_compile=False 时,训练崩溃,错误为 spmd_types.types.SpmdTypeError: assert_type(...) requires an active mesh, but no current mesh is set.。根本原因是 spmd.assert_type 需要从 train_context 获取线程本地 SPMD mesh,但由于选择性激活检查点在 loss.backward() 期间重新运行前向传播,且 verl 在 Ray 非主线程上执行 step,导致 autograd 工作线程丢失了 mesh。编译开启时避免了此问题(因为编译会内联重计算),因此该 bug 仅在编译关闭时出现。

该 PR 值得阅读,特别是对 TorchTitan 引擎或分布式训练中线程本地 mesh 传播机制感兴趣的开发者。设计决策(禁用 autograd 多线程)参考了 TorchTitan 上游实践,思路清晰。注意兼容性风险,建议环境确认 PyTorch 版本 >=2.5.0。

讨论亮点

Review 中 gemini-code-assist[bot] 指出,torch.autograd.set_multithreading_enabled 在 PyTorch 2.5.0 中引入,而 veRL 仍支持 PyTorch 2.4.0(如 Docker 镜像 vemlp-th2.4.0-cu124-...),直接调用可能导致 AttributeError。建议通过 getattr(torch.autograd, "set_multithreading_enabled", None) 进行版本兼容性处理,并在 API 不可用时回退到 nullcontext()。然而,该 PR 最终并未采纳此建议,而是直接调用该 API。这可能是因为 veRL 实际上已升级最低 PyTorch 版本要求,或该问题在目标环境中不存在。wuxibin89 已批准该 PR。

实现拆解

本 PR 仅修改 verl/workers/engine/torchtitan/transformer_impl.py 一个文件,主要包含三处变更:

  1. _init_device_mesh 中禁用 autograd 多线程(第 290-293 行):在构建设备网格后,调用 torch.autograd.set_multithreading_enabled(False)。这确保了反向传播和激活检查点重计算在调用线程上执行,从而可以访问线程本地的 SPMD mesh。该变更参考了 TorchTitan 的 init_distributed 实现。

  2. forward_backward_batch 中包裹 train_context(第 359-365 行):将 micro-batch 循环置于 self.trainer.train_context() 上下文管理器中,使得 SPMD mesh 在前后向传播中持续生效。选择性激活检查点重新运行前向时,仍能获取正确的 mesh。

  3. model_forward_step 中移除多余的 train_context(第 392-396 行):由于 forward_backward_batch 已经提供了 train_context,此处的嵌套包装变得冗余,因此移除,以保持清晰。

文件 模块 状态 重要度
verl/workers/engine/torchtitan/transformer_impl.py 训练引擎 modified 6.06

关键源码片段

verl/workers/engine/torchtitan/transformer_impl.py core-logic

核心变更文件,包含了所有修复逻辑:禁用 autograd 多线程、包裹 train_context、移除冗余上下文。

# verl/workers/engine/torchtitan/transformer_impl.pydef _init_device_mesh(self):
    """Initialize the device mesh for TorchTitan style parallelism."""
    world_size = torch.distributed.get_world_size()
    self.parallel_dims = ParallelDims(
        dp_shard=self.engine_config.data_parallel_shard_size,
        dp_replicate=self.engine_config.data_parallel_replicate_size,
        cp=self.engine_config.context_parallel_size,
        tp=self.engine_config.tensor_parallel_size,
        pp=self.engine_config.pipeline_parallel_size,
        ep=self.engine_config.expert_parallel_size,
        world_size=world_size,
    )
    self.device_mesh = self.parallel_dims.build_mesh()
​
    # Mirror torchtitan 's init_distributed (which verl bypasses): disable autograd
    # multithreading so backward - thread activation - checkpoint recompute can access the
    # thread - local SPMD mesh / process groups (e .g . current_spmd_mesh ().get_group (...)).
    torch.autograd.set_multithreading_enabled(False)def forward_backward_batch(self, data: TensorDict, loss_function: Callable, forward_only=False):
    """Perform forward and optionally backward pass on a batch."""
    # ... setup code omitted ...
​
    ctx = torch.no_grad() if forward_only else nullcontext()
​
    # train_context activates the (thread - local) SPMD mesh required by spmd_types; it must
    # span backward too, since activation - checkpoint recompute re - runs the forward there.
    for micro_batch in micro_batches:
        with self.trainer.train_context(), ctx:
            loss, output = self.forward_step(micro_batch, loss_function=loss_function, forward_only=forward_only)
            if not forward_only:
                loss.backward()
        output_lst.append(output)
​
    return postprocess_batch_func(output_lst=output_lst, indices=indices, data=data)def model_forward_step(self, *, inputs, extra_inputs=None, extra_kwargs=None):
    # ... non - PP path:
    assert len(model_parts) == 1
    # train_context (SPMD mesh) is set by the caller (forward_backward_batch),
    # so we remove the redundant wrapper here.
    pred = model_parts[0](inputs, **extra_inputs, **extra_kwargs)
    if isinstance(pred, DTensor):
        pred = pred.full_tensor()

评论区精华

PyTorch 版本兼容性:set_multithreading_enabled 在 2.5.0 中引入 正确性

gemini-code-assist[bot] 指出直接调用 torch.autograd.set_multithreading_enabled 在 PyTorch 2.4.0 上会引发 AttributeError,建议通过 getattr 进行版本兼容性处理并提供回退方案。

结论:未采纳,PR 中直接使用了该 API。可能由于 veRL 已实际要求 PyTorch >=2.5.0。 · 未解决

风险与影响

主要风险在于兼容性:torch.autograd.set_multithreading_enabled 仅在 PyTorch 2.5.0+ 中可用。如果用户仍使用 PyTorch 2.4.0,将导致 AttributeError。虽然 veRL 的部分 Docker 镜像标记为 2.4.0,但可能实际运行时已升级至 2.5.0,需确认最低 PyTorch 版本要求。此外,禁用 autograd 多线程可能对多线程数据加载或其他依赖 autograd 多线程的特性产生性能影响,但影响范围有限(仅影响 TorchTitan 引擎,且仅在初始化时设置一次)。

直接影响:修复了 TorchTitan 引擎中使用 spmd_types + 选择性激活检查点 + 关闭编译时的崩溃问题,使得该配置组合能够正常工作。影响范围:仅影响 verl/workers/engine/torchtitan/transformer_impl.py 中的 TorchTitan 引擎路径,不影响其他引擎(如 FSDP、Megatron)或默认配置。对用户:使用 TorchTitan 引擎且采用上述配置的用户将受益。

缺少测试覆盖 兼容性风险(PyTorch 版本依赖)

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论