已开启
【RFC】声明式并行编程支持DFunciton,动态图自定义Function正反向执行及分布式逻辑 #91
hedongdong创建于 4月18日
4月18日 修改了issue 的描述
Hhedongdong
4月18日 关联了pull request:feat: add DistFunction base class for user-defined distributed autograd functions
4月18日 关联了pull request:feat: add DistFunction base class for user-defined distributed autograd functions
4月22日 修改了issue 的描述
4月22日 修改了issue 的描述
Hhedongdong
7月2日 修改标题为 “【RFC】声明式并行编程支持DFunciton,动态图自定义Function正反向执行及分布式逻辑”,原标题为“【RFC】声明式并行编程支持动态图自定义Function正反向执行及分布式逻辑”
7月2日 修改标题为 “【RFC】声明式并行编程支持DFunciton,动态图自定义Function正反向执行及分布式逻辑”,原标题为“【RFC】声明式并行编程支持动态图自定义Function正反向执行及分布式逻辑”
背景
在分布式训练场景中,存在若干需要用户自定义正反向计算逻辑的场景,使得现有算子级并行框架难以直接处理:
torch.autograd.Function或 MindSpore 等效类自定义正反向时,并行框架对正向插入的 layout 推导、DTensor 包装等额外操作对用户不透明,导致自定义反向无法感知并正确处理分布式张量。DistributedOp的 layout 推导和 DTensor 输入/输出包装流程,导致多卡场景下无法直接使用分布式张量作为输入输出。为解决上述问题,我们引入
DFunction机制,允许用户以 local tensor 视角编写自定义分布式 autograd 函数,框架自动完成 DTensor 的提取与包装。设计方案
DFunction是platform.Function的子类(在 PyTorch 上继承torch.autograd.Function,在 MindSpore 上继承对应的_Function)。用户子类实现forward/backward静态方法,操作的是 local tensor;当输入包含 DTensor 时,apply()自动路由到_OP_DISPATCHER.dispatch()完成 layout 推导和 DTensor 包装,用户感知不到多卡和单卡的区别。如图所示:
DFunction.apply(local_x, local_y)→super().apply()→ 平台 autograd 机制 →forward(ctx, local_x, local_y)DFunction.apply(dtensor_x, dtensor_y)→_OP_DISPATCHER.dispatch()→ 提取 local tensors →forward(ctx, local_x, local_y)→DTensor.from_local(output, mesh, placements)layout 推导逻辑由用户配套实现的
DistributedOp子类提供,通过_op_name字符串与DFunction子类关联。对外 API
class DFunction(platform.Function): _op_name: str = None # 与注册的 DistributedOp 的 op_name 对应 @staticmethod def forward(ctx, *args, **kwargs) -> Tensor: ... @staticmethod def backward(ctx, *grad_outputs) -> ...: ... @classmethod def apply(cls, *args, **kwargs) -> Tensor | DTensor: ...配套的
DistributedOp(已有接口,无需新增):class DistributedOp: def __init__(self, op_name: str): ... def preprocess(self, args: tuple, kwargs: dict) -> None | tuple: ... def infer_layout(self, cache_values) -> Layout | tuple: ... def get_expand_impl(self, func, infer_result, cache_values) -> None | Callable: ...使用样例:
from hyper_parallel import init_device_mesh, DFunction from hyper_parallel.core.dtensor.dtensor import distribute_tensor from hyper_parallel.core.dtensor.placement_types import Shard, Replicate from hyper_parallel.core.shard.ops.parallel_ops import DistributedOp # 步骤 1:注册 DistributedOp,描述 layout 推导逻辑 class MyAddDistOp(DistributedOp): def __init__(self): super().__init__("MyAdd") def infer_layout(self, layouts, extra_args=None): return layouts[0] # 元素级加法:输出 layout = 输入 layout MyAddDistOp() # 实例化即注册 # 步骤 2:实现 DFunction 子类(操作 local tensor) class MyAdd(DFunction): _op_name = "MyAdd" @staticmethod def forward(ctx, x, y): ctx.save_for_backward(x, y) return x + y @staticmethod def backward(ctx, grad): return grad, grad # 单卡调用(plain tensor) result = MyAdd.apply(x_local, y_local) # 多卡调用(DTensor,自动走分布式路径) mesh = init_device_mesh("npu", (2, 4), mesh_dim_names=("dp", "tp")) x_dist = distribute_tensor(x, mesh, (Shard(0), Replicate())) y_dist = distribute_tensor(y, mesh, (Shard(0), Replicate())) result_dist = MyAdd.apply(x_dist, y_dist) # 返回 DTensor使用约束
DFunction子类必须设置_op_name,且与注册的DistributedOp实例的op_name完全一致;当输入包含 DTensor 时,若未设置_op_name则抛出ValueError;forward和backward内部必须操作 local tensor,不得递归调用DFunction.apply;preprocess)中不会被传入forward,需改用 kwargs 或实现preprocess走新 dispatch 路径;get_expand_impl返回值为可调用对象且输出具有 partial 状态时,需在使用前调用result.reduce_partial();测试设计
普通用例(不涉及自定义反向)
DFunction.apply正向结果正确;涉及自定义反向的用例
ctx.save_for_backward/ctx.saved_tensors的自定义正反向;class LinearFunction(DFunction): _op_name = "TestLinear" @staticmethod def forward(ctx, x, weight): output = platform.matmul(x, weight) ctx.save_for_backward(x, weight) return output @staticmethod def backward(ctx, grad_output): x, weight = ctx.saved_tensors grad_input = platform.matmul(grad_output, weight.t()) grad_weight = platform.matmul(x.t(), grad_output) return grad_input, grad_weight mesh = init_device_mesh("npu", (1, 8), mesh_dim_names=("dp", "tp")) in_layouts = (layout("None", "None"), layout("None", "tp")) out_layout = (layout("None", "tp"),) # 单卡与多卡前向/反向结果完全对齐row-parallel 场景(含 get_expand_impl bias scaling)
bias / tp_size);