mindspore-ai / mindspore-ai/hyper-parallel
【RFC】声明式并行编程支持DFunciton,动态图自定义Function正反向执行及分布式逻辑
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 53
- Forks
- 63
- Avg merge
- 23h 45m
- Merged PRs (30d)
- 63
Description
背景
在分布式训练场景中,存在若干需要用户自定义正反向计算逻辑的场景,使得现有算子级并行框架难以直接处理:
- 自定义 autograd 函数与分布式并行框架的割裂:用户通过
torch.autograd.Function或 MindSpore 等效类自定义正反向时,并行框架对正向插入的 layout 推导、DTensor 包装等额外操作对用户不透明,导致自定义反向无法感知并正确处理分布式张量。 - 缺少跨平台统一抽象:用户在 PyTorch 和 MindSpore 后端上编写的自定义 autograd 函数无法复用,需为不同后端分别适配。
- 无法与 DTensor dispatch 系统衔接:现有用户自定义函数无法接入
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;- 非 Tensor 位置参数在 legacy dispatch 路径(未实现
preprocess)中不会被传入forward,需改用 kwargs 或实现preprocess走新 dispatch 路径; - 当
get_expand_impl返回值为可调用对象且输出具有 partial 状态时,需在使用前调用result.reduce_partial(); - 当前仅支持动态图,暂不支持静态图;
测试设计
-
普通用例(不涉及自定义反向)
- 构造单卡用例,验证
DFunction.apply正向结果正确; - 构造并行用例(DTensor 输入),验证输出类型为 DTensor 且值与单卡对齐;
- 分别跑一个训练 step,观察正向 loss / 反向 grad 是否完全对齐;
- 构造单卡用例,验证
-
涉及自定义反向的用例
- 实现包含
ctx.save_for_backward/ctx.saved_tensors的自定义正反向; - 单卡 vs 多卡反向梯度精度对齐验证;
- 参考用例:
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)
- 构造输入在 TP 维度 Shard 的 row-parallel linear;
- 验证 bias 在每个 rank 上被正确缩放(
bias / tp_size); - 反向梯度与单卡参考值对齐;
schema_version: 1
source: gitcode
gitcode_repo: mindspore/hyper-parallel
gitcode_issue: 91
source_url: https://gitcode.com/mindspore/hyper-parallel/issues/91
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by locating the DFunction entry point, _OP_DISPATCHER.dispatch(), and the existing DistributedOp registration and dispatch paths. Use the specified single-card, DTensor, custom-backward, and row-parallel cases as acceptance checks; done means forward results, DTensor outputs, gradients, and bias scaling align with the single-card reference.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python, pytorch
- Domain
- distributed-systems, machine-learning
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100