已开启
【RFC】基于 DFunction 接入自定义算子的端到端流程 #99
hedongdong创建于 4月23日
4月23日 修改了issue 的描述
4月23日 修改了issue 的描述
Hhedongdong
8月22日 修改标题为 “【RFC】基于 DFunction 接入自定义算子的端到端流程”,原标题为“【RFC】基于 DFunction 接入自定义算子的端到端流程——以 npu_dense_lightning_indexer_softmax_lse 为例”
8月22日 修改标题为 “【RFC】基于 DFunction 接入自定义算子的端到端流程”,原标题为“【RFC】基于 DFunction 接入自定义算子的端到端流程——以 npu_dense_lightning_indexer_softmax_lse 为例”
Hhedongdong
8月22日 关联了pull request:feat: add DFunction base class for user-defined distributed autograd functions
8月22日 关联了pull request:feat: add DFunction base class for user-defined distributed autograd functions
背景
在分布式训练场景中,存在若干需要用户自定义正反向计算逻辑的场景,使得现有算子级并行框架难以直接处理:
torch.autograd.Function或 MindSpore 等效类自定义正反向时,并行框架对正向插入的 layout 推导、DTensor 包装等额外操作对用户不透明,导致自定义反向无法感知并正确处理分布式张量。DistributedOp的 layout 推导和 DTensor 输入/输出包装流程,导致多卡场景下无法直接使用分布式张量作为输入输出。为解决上述问题,我们引入
DFunction机制,允许用户以 local tensor 视角编写自定义分布式 autograd 函数,框架自动完成 DTensor 的提取与包装。本次提交基于
DFunction机制,成功接入了npu_dense_lightning_indexer_softmax_lse接口,打通了从用户 API 到底层 CustomOpBuilder 的完整链路。设计方案
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子类关联。用户接口到底层 CustomOpBuilder 的实现链路
本章节详细梳理
npu_dense_lightning_indexer_softmax_lse接口从用户调用到底层 C++ kernel 的完整实现链路。目录结构
类与接口设计
1. 用户接口层 (
hyper_parallel/custom_ops/__init__.py)def npu_dense_lightning_indexer_softmax_lse( query_index, key_index, weights, *, actual_seq_qlen=None, actual_seq_klen=None, layout='BSND', sparse_mode=3, ...) -> Tuple: """平台无关的公开 API,委托给 platform.custom_ops""" return _platform.custom_ops.npu_dense_lightning_indexer_softmax_lse(...)职责:提供统一的用户入口,参数校验,文档字符串。
2. Platform 抽象层
基类 (
hyper_parallel/platform/platform.py):class Platform(ABC): @property @abstractmethod def custom_ops(self): """子类必须实现,返回平台特定的 custom ops 类""" raise NotImplementedErrorMindSpore 实现 (
hyper_parallel/platform/mindspore/platform.py):class MindSporePlatform(Platform): Function = _Function # MindSpore autograd 基类 _custom_ops_cls = None @property def custom_ops(self): if MindSporePlatform._custom_ops_cls is None: from hyper_parallel.platform.mindspore.custom_ops.custom_ops import MindSporeCustomOps MindSporePlatform._custom_ops_cls = MindSporeCustomOps return MindSporePlatform._custom_ops_clsPyTorch 实现 (
hyper_parallel/platform/torch/platform.py):class TorchPlatform(Platform): Function = torch.autograd.Function # PyTorch autograd 基类 _custom_ops_cls = None @property def custom_ops(self): if self._custom_ops_cls is None: from hyper_parallel.platform.torch.custom_ops.custom_ops import TorchCustomOps self._custom_ops_cls = TorchCustomOps return self._custom_ops_cls3. 平台特定 CustomOps 类
MindSpore (
hyper_parallel/platform/mindspore/custom_ops/custom_ops.py):class MindSporeCustomOps: @staticmethod def npu_dense_lightning_indexer_softmax_lse(...): from hyper_parallel.platform.mindspore.custom_ops.custom_op_impl import ( NpuDenseLightningIndexerSoftmaxLseDFunction, ) return NpuDenseLightningIndexerSoftmaxLseDFunction.apply(...)PyTorch (
hyper_parallel/platform/torch/custom_ops/custom_ops.py):class TorchCustomOps: @staticmethod def npu_dense_lightning_indexer_softmax_lse(*args, **kwargs): raise RuntimeError( "npu_dense_lightning_indexer_softmax_lse is not supported via HyperParallel " "on the PyTorch platform. Please call torch_npu.npu_dense_lightning_indexer_softmax_lse directly." )4. DFunction 子类 (
hyper_parallel/platform/mindspore/custom_ops/custom_op_impl.py)# 1. 加载 C++ 自定义算子 _custom_ops = ms.ops.CustomOpBuilder( "custom_ops", [ "module.cc", "dense_lightning_indexer_softmax_lse.cc", ... ], backend="Ascend", ).load() # 2. DFunction 子类 class NpuDenseLightningIndexerSoftmaxLseDFunction(DFunction): _op_name = "npu_dense_lightning_indexer_softmax_lse" @staticmethod def forward(ctx, query_index, key_index, weights, ...): return _custom_ops.npu_dense_lightning_indexer_softmax_lse(...) @staticmethod def backward(ctx, *grad_outputs): return (None,) * 9 # 无梯度需求5. DFunction 基类 (
hyper_parallel/core/shard/dfunction.py)class DFunction(platform.Function): _op_name: str = None @classmethod def apply(cls, *args, **kwargs): has_dtensor = any(isinstance(a, DTensor) for a in args) if has_dtensor: if cls._op_name is None: raise ValueError("DTensor inputs require '_op_name'") return _OP_DISPATCHER.dispatch(cls._get_local_callable(), args, kwargs) return super().apply(*args, **kwargs) @classmethod def _get_local_callable(cls) -> _LocalCallable: """返回带 op_name 的 callable,用于 dispatcher 查找 DistributedOp""" ...6. OpDispatcher (
hyper_parallel/core/shard/_op_dispatch.py)class OpDispatcher: def dispatch(self, op_call: callable, args: tuple, kwargs: dict) -> object: op_name = platform.get_op_name(op_call) # 自动注册通过 DistributedOp 程序化注册的算子 if op_name not in self.layout_infer_ops and get_distributed_op(op_name) is not None: self.layout_infer_ops[op_name] = {} return self._dispatch_layout_infer(op_name, op_call, args, kwargs)7. DistributedOp 子类 (
hyper_parallel/core/shard/ops/parallel_npu_dense_lightning_indexer_softmax_lse.py)class NpuDenseLightningIndexerSoftmaxLseDistributedOp(DistributedOp): def __init__(self): super().__init__("npu_dense_lightning_indexer_softmax_lse") def preprocess(self, args, kwargs) -> tuple: """提取 local tensors,构建 cache_values""" ... def infer_layout(self, cache_values) -> Tuple[tuple, None]: """推导输出 layout:BSND → (B, N2index, S1),TND → (N2index, T1)""" ... def get_expand_impl(self, func, infer_result, cache_values, ...) -> Optional[Callable]: """Context Parallel 场景下的 key 切片 / seq_len 调整""" ...8. YAML 配置 (
hyper_parallel/core/shard/ops/yaml/npu_dense_lightning_indexer_softmax_lse_ops.yaml)npu_dense_lightning_indexer_softmax_lse: dist_op_name: _npu_dense_lightning_indexer_softmax_lse_dist_op distributed_op_class: NpuDenseLightningIndexerSoftmaxLseDistributedOp distributed_op_file: parallel_npu_dense_lightning_indexer_softmax_lse9. C++ Kernel 封装 (
hyper_parallel/platform/mindspore/custom_ops/dense_lightning_indexer_softmax_lse.cc)class DenseLightningIndexerSoftmaxLseRunner : public ms::pynative::PyboostRunner { void RunAclnnDirect() { // 调用 Ascend ACLNN API: aclnnDenseLightningIndexerSoftmaxLse ... } };调用链路图
对外 API
from hyper_parallel import custom_ops # 单卡调用 (plain tensor) softmax_max, softmax_sum = custom_ops.npu_dense_lightning_indexer_softmax_lse( query_index, key_index, weights, layout='BSND') # 多卡调用 (DTensor) from hyper_parallel import init_device_mesh from hyper_parallel.core.dtensor.dtensor import distribute_tensor from hyper_parallel.core.dtensor.placement_types import Shard, Replicate mesh = init_device_mesh("npu", (2, 4), mesh_dim_names=("dp", "tp")) query_dist = distribute_tensor(query, mesh, (Shard(0), Replicate())) key_dist = distribute_tensor(key, mesh, (Replicate(), Replicate())) weights_dist = distribute_tensor(weights, mesh, (Shard(0), Replicate())) softmax_max_dist, softmax_sum_dist = custom_ops.npu_dense_lightning_indexer_softmax_lse( query_dist, key_dist, weights_dist, layout='BSND')使用约束
DFunction子类必须设置_op_name,且与注册的DistributedOp实例的op_name完全一致;forward和backward内部必须操作 local tensor,不得递归调用DFunction.apply;preprocess方法处理,或改用 kwargs 传递;torch_npu.npu_dense_lightning_indexer_softmax_lse,DTensor dispatch 通过 YAML 注册的 DistributedOp 自动处理;测试设计
测试文件:
tests/mindspore/st/custom_ops/test_npu_dense_lightning_indexer_softmax_lse.py