Closed
【RFC】 支持典型网络(Qwen/DS)算子级并行与自定义算子 #124
hedongdongAdd on May 12Closed at Jun 23
May 26 Associated pull request
May 28 Associated pull request
May 29 Associated pull request
May 30 Associated pull request
May 30 Associated pull request
May 31 Associated pull request
Jun 1 Associated pull request
Jun 4 Associated pull request
Jun 10 Associated pull request
Jun 23 changed custom state from TODO to DONE
Jun 23 Close issue
MindSpore-Bot
Jun 23 Comments:
Jun 23 Comments:
Jun 25 Associated pull request
Jul 7 Associated pull request


RFC: 支持典型网络(Qwen/DS)算子级并行与自定义算子
需求背景 & 价值
大模型训练需要对算子的输入/输出张量进行切分,支持 Qwen、DeepSeek 等典型网络涉及的分布式算子。Qwen/DS 等典型网络中涉及一些专门算子(如 mhc / dsa / gdn / engram),这些算子在单卡上能正常运行,但在分布式训练中需要框架自动完成输入/输出张量的切分与重组,保证切分后计算结果与单卡数学等价。
同时,对于框架尚未内置实现的分布式算子,需要提供用户自定义注册机制,降低接入门槛——用户只需描述算子的 layout 推导逻辑,框架自动完成 DTensor 的提取与包装。
核心价值:
功能描述
1. 算子级并行基础设施
提供三层架构支持算子级分布式并行:
shard_module):用户描述模块的输入/输出/参数布局,框架自动注册 forward hooks 完成 DTensor 输入→本地计算→DTensor 输出的全流程包装;custom_shard):将任意原生函数包装为 DTensor 感知函数,适用于快速原型和简单自定义逻辑;DFunction):允许用户以 local tensor 视角编写正反向计算,框架自动处理 DTensor 提取与包装,适用于需要自定义反向传播逻辑的场景。2. 分布式算子实现体系
基于
DistributedOp抽象基类,为框架内建算子和用户自定义算子提供统一的开发范式(三阶段模式):preprocess(args, kwargs):参数规范化 + to_local + 构建 cache_values;infer_layout(cache_values):基于输入布局推导输出布局,所有校验集中在此;get_expand_impl(func, infer_result, cache_values):返回可选的扩展实现(闭包),用于注入额外分布式逻辑(如 bias scaling、partial 归约)。3. 双通道算子注册
core/shard/ops/yaml/*.yaml配置文件将算子名称映射到DistributedOp子类,OpDispatcher启动时自动加载并实例化;DistributedOp.__init__自动调用register_distributed_op(),无需任何配置文件,适用于自定义算子快速接入;设计方案
1. 整体架构
2. DTensor 分发流程
框架通过 DTensor 机制实现算子级并行的透明分发:
分发结果通过
LayoutCacheManager按op_name + LayoutCacheKey缓存,避免重复的 layout 推导。3. DistributedOp 三阶段模式
每个分布式算子的
DistributedOp子类推荐按三阶段实现(参考 Issue #94):阶段 1 — preprocess:参数规范化 + to_local + 构建 cache_values。仅做参数处理,不含校验逻辑。
def preprocess(self, args, kwargs): args, _ = _normalize_xxx_args(*args, **kwargs) local_args = (args[0].to_local(), args[1].to_local()) local_kwargs = {} cache_values = [args[0].layout, args[1].layout] return local_args, local_kwargs, cache_values阶段 2 — infer_layout:基于 cache_values 推导输出 layout。所有校验(类型、范围、分片约束)集中在此完成,必须手动调用
_check_partial_inputs()。def infer_layout(self, cache_values): layout = cache_values[0] self._check_partial_inputs([layout]) # ... 校验 & 推导 ... return ((output_layout,), None)阶段 3 — get_expand_impl:返回扩展实现闭包或 None。None 表示使用原始函数,不需要扩展。
4. YAML 注册
# core/shard/ops/yaml/matmul_ops.yaml MatMul: dist_op_name: _matmul_dist_op distributed_op_class: MatMulDistributedOp distributed_op_file: parallel_matmulOpDispatcher启动时自动加载所有 YAML 文件并实例化对应DistributedOp子类,实例化过程中触发register_distributed_op()完成注册。5. DFunction — 用户自定义分布式正反向
DFunction允许用户以 local tensor 视角编写自定义分布式 autograd 函数(参考 Issue #91):from hyper_parallel import DFunction 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): return x + y @staticmethod def backward(ctx, grad): return grad, grad # 自动路由: result = MyAdd.apply(x, y) # 纯 Tensor → 单设备路径 result = MyAdd.apply(dtensor_x, dtensor_y) # DTensor → 分布式路径分发机制:
super().apply()→ 平台 autograd 单设备路径;_OP_DISPATCHER.dispatch()→ 提取 local tensors → 调用infer_layout()→ 调用本地函数 → 包装为 DTensor。6. custom_shard — 函数级轻量包装
对于不需要自定义反向的简单场景,提供更轻量的接入方式(参考 Issue #4):
from hyper_parallel import custom_shard @custom_shard( out_layouts=(out_layout,), in_layouts=(x_layout, y_layout), redistribute_inputs=True, ) def my_distributed_func(x, y): out = ops.matmul(x, y) out = all_reduce(out, "mp") return out框架自动完成:
DTensor → redistribute(如需要)→ to_local → 调用函数 → from_local 包装为 DTensor。对外 API
shard_module
from hyper_parallel import shard_module, Layout layout = Layout((dp, tp), ("dp", "tp")) sharding_plan = { "forward": { "input": (layout("dp", "tp"),), "output": (layout("dp", "None"),), }, "parameter": {"weight": layout("None", "tp")}, } model = shard_module(model, sharding_plan)custom_shard
from hyper_parallel import custom_shard @custom_shard(out_layouts=(Layout(...),), in_layouts=(Layout(...),)) def my_func(x, y): return some_operation(x, y)DFunction
from hyper_parallel import DFunction class MyCustomFunc(DFunction): _op_name = "MyCustomOp" @staticmethod def forward(ctx, x, y): return custom_compute(x, y) @staticmethod def backward(ctx, grad): return compute_grad(grad)编程式注册
from hyper_parallel.core.shard.ops.parallel_ops_register import register_distributed_op class MyOpDistributedOp(DistributedOp): def infer_layout(self, layouts, extra_args=None): return (layouts[0],) register_distributed_op("MyOp", MyOpDistributedOp("MyOp"))YAML 注册
# custom_ops.yaml(放入 core/shard/ops/yaml/ 目录) MyOp: dist_op_name: _my_op_dist distributed_op_class: MyOpDistributedOp distributed_op_file: parallel_my_op使用约束
算子各输入的切分形态需匹配:如 add 算子,在不涉及广播的情况下,两个输入的 DTensor 状态需一致(即相同 mesh_shape 和 placements);
训练场景需结合 fully_shard 特性:反向的权重梯度通信都由
fully_shard处理,若未启用fully_shard可能出现精度问题;建议在shard_module之后再调用fully_shard;Partial 状态消除:切分中若涉及 Partial 状态(如 MatMul contract dim 被分片后的 partial sum),需通过 cell 的配置触发重排来消除,或手动调用
result.reduce_partial();DFunction 约束:
_op_name,且与注册的DistributedOp的op_name一致;forward和backward内部必须操作 local tensor,不得递归调用DFunction.apply;custom_shard 约束:
算子分发兼容性:所有内置算子的
DistributedOp实现需保证旧分发流(_with_layout_infer)和新分发流(_dispatch_new)的行为一致,逐步迁移至新模式。测试设计
单元测试
ValueErrorpreprocess路由正确性:新旧分发流等价_dispatch_new与_with_layout_infer输出一致ctx.save_for_backward的 DFunction 正反向自定义算子注册测试
get_distributed_op("Xxx")能获取回归测试
tests/ut/core/shard/ops/)无回归;规格 & 约束
参考