已关闭
[需求] mHC dispatcher 算子需要为 torch.compile 提供官方 Autograd 注册 #4289
xuyujun创建于  8月12日关闭于  8月31日
xuyujun成员
8月12日 创建

背景

DeepSeek-V4 mHC 需要在训练的 forward/backward 中调用以下 dispatcher 算子:

  • cann_ops_transformer::mhc_pre_sinkhorn
  • cann_ops_transformer::mhc_post

当前 cann_ops_transformer Python wrapper 在输入需要梯度时,会切换到 Python torch.autograd.Function,并在其 forward 内调用 OpBuilder.load() 加载后的 pybind 扩展。该调用路径无法被 PyTorch Dynamo/AOTAutograd 作为图节点追踪,开启 torch.compile(backend="inductor") 时会失败,典型报错为:

Dynamo does not know how to trace the builtin
mhc_pre_sinkhorn.pybind11_detail_function_record...

我们已验证直接调用 dispatcher 算子可以被 Inductor 入图;但当前 forward 算子没有官方的 dispatcher-level Autograd 注册,因此业务侧需要额外编写 torch.autograd.Function bridge,手动保存 forward 中间量并调用 backward 算子。

环境信息

  • 硬件:Ascend910_9362(A3)
  • CANN:9.2.0,安装自 2026-08-05 软件包
  • torch:2.14.0.dev20260719+cpu
  • torch_npu:2.14.0.dev20260719+git5f31dff
  • cann_ops_transformer:/data/x00617812/CANN/cann-9.2.0/python/site-packages/cann_ops_transformer

重现步骤

下面的探针使用公开的 dispatcher schema,且不会预先加载任何 OpBuilder:

import torch
import torch_npu
import cann_ops_transformer

def fn(x, phi, alpha, bias):
    outputs = torch.ops.cann_ops_transformer.mhc_pre_sinkhorn(
        x, phi, alpha, bias, 4, 20, 1e-6, 1e-6, True
    )
    h_in, h_post, h_res = outputs[:3]
    return torch.ops.cann_ops_transformer.mhc_post(
        x, h_res.reshape(1, 128, 4, 4), h_in, h_post
    )

compiled = torch.compile(fn, backend="inductor")
compiled(x, phi, alpha, bias)

在当前软件包中,直接 forward 路径可以注册并执行,但 AOTAutograd 没有这些 forward dispatcher 算子的导数规则,因此模型需要使用自定义 bridge。

交付需求

参考同仓 cann_ops_transformer/ops/compressor.py 的官方交付范式(torch.library.custom_op + register_fake + register_autograd(setup_context=...) + 公开 wrapper 统一走 dispatcher),为 mhc_pre_sinkhorn 和 mhc_post 补齐以下三项交付:

一、官方 Autograd 注册(对齐 compressor.py:189-226)

  1. 为 mhc_pre_sinkhorn 注册 autograd:
    • setup_context 保存前向输入 x, phi, alpha, bias 以及 out_flag=True 返回的 5 个中间量(h_pre, hc_before_norm, inv_rms, sum_out, norm_out,即 forward 8 元组输出的后 5 个),并保存 hc_eps;
    • backward 调用 torch.ops.cann_ops_transformer.mhc_pre_sinkhorn_backward,对 9 个 forward 输入返回 4 个张量梯度 + 5 个 None。
  2. 为 mhc_post 注册 autograd:
    • save_for_backward(x, h_res, h_out, h_post);
    • backward 调用 torch.ops.cann_ops_transformer.mhc_post_backward 返回 4 个梯度。

注册完成后,直接调用 torch.ops.cann_ops_transformer.mhc_pre_sinkhorn / mhc_post 即可参与 eager Autograd,并可通过 torch.compile(backend="inductor") 追踪,不再需要业务侧 bridge。

二、公开 wrapper 去掉双轨分支(对齐 compressor.py:248-264)

mhc_pre_sinkhorn() / mhc_post() 公开接口删除 needs_grad 分支中切换到 Python torch.autograd.Function(MhcPreSinkhornFunction.apply / MhcPostFunction.apply)+ OpBuilder.load() pybind 直调的路径,改为统一调用 dispatcher:

def mhc_pre_sinkhorn(x, phi, alpha, bias, hc_mult, num_iters, hc_eps=1e-6, norm_eps=1e-6):
    out_flag = x.requires_grad or phi.requires_grad or alpha.requires_grad or bias.requires_grad
    return torch.ops.cann_ops_transformer.mhc_pre_sinkhorn(
        x, phi, alpha, bias, hc_mult, num_iters, hc_eps, norm_eps, out_flag)[:3]

由 wrapper 计算 out_flag 传入 forward 算子,始终走 torch.ops.cann_ops_transformer.*。编译场景下不再存在不可追踪的 pybind autograd.Function 路径。

三、补齐导出(对齐 ops/__init__.py 中其它算子全量导出)

在 ops/__init__.py 中导出 mhc_post_backward:

from .mhc_post_backward import mhc_post_backward

与 mhc_pre_sinkhorn_backward 等 backward 算子保持一致,业务侧无需再显式 import 该模块。

关联 issue

  • #4028:请求为 mhc_post_backward 提供 TorchAir/torch-extension 支持。
  • #3698:跟踪 mHC pre forward/backward 的 GE 图支持。

验证记录

在上述环境中,使用围绕 dispatcher 算子编写的本地 torch.autograd.Function bridge 进行的最小 NPU 探针,已通过 eager 和编译模式下的 forward/backward 验证。4 个可训练输入均成功获得梯度。该 bridge 只是针对当前缺少官方 Autograd 注册问题的临时下游规避方案。

likedislike
Xxuyujun成员
8月12日 关联了pull request:[fix] Add graph-safe mHC autograd bridge
Xxuyujun成员
8月12日 修改标题为 “中文测试”,原标题为“[Requirement] mHC dispatcher operators need official Autograd registration for torch.compile”
CANN-robotCANN-robot成员
8月12日 将 xuyujun 设为负责人
Xxuyujun成员
8月12日 修改标题为 “[需求] mHC dispatcher 算子需要为 torch.compile 提供官方 Autograd 注册”,原标题为“中文测试”
Xxuyujun成员
8月12日 修改了issue 的描述
Xxuyujun成员
8月13日 移除了负责人 xuyujun
Xxuyujun成员
8月13日 删除了关联的pull request:[fix] Add graph-safe mHC autograd bridge
weihao18成员
8月13日 评论:

/assign @xuejinghui

likedislike
CANN-robotCANN-robot成员
8月13日 将 xuejinghui 设为负责人
Xxuyujun成员
8月19日 修改了issue 的描述
xuyujun成员
8月20日 评论:

torch.autograd.Function 与 custom_op + register_autograd 的机制差异

核心结论

这不是 Torch 版本依赖问题,而是两种机制在 torch.compile 下的本质差异:

torch.autograd.Function 的 forward 函数体会被 Dynamo 追踪执行;custom_op + register_autograd 的 forward 会被当作不透明的图节点,Dynamo 不会深入其实现函数体。

因此,autograd.Function 在 eager 模式下可以正常运行,并不意味着它在 torch.compile 下也一定可用。只要 forward 或 backward 内部包含 Dynamo 无法理解的 Python/C++ 扩展调用,就可能在编译阶段失败。

eager 与 torch.compile 的对比

机制 eager torch.compile(inductor)
torch.autograd.Function forward / backward 直接执行,通常可以正常运行。 Dynamo 遇到 apply() 时通过高阶算子(HOP)处理,并重新追踪 forward 和 backward 的函数体。因此,函数体内的代码必须全部能够被 Dynamo 理解。
custom_op + register_autograd 通过 dispatcher 执行,通常可以正常运行。 forward 是带有 schema 和 fake/meta 实现的 dispatcher 节点。Dynamo 将其作为图节点处理,不深入追踪实现函数体;backward 同样可以调用已注册的 dispatcher op,形成黑盒节点。

为什么 mHC 开发者的 autograd.Function 在 compile 下不可用

相关实现的 forward 大致如下:

class MhcPreSinkhornFunction(torch.autograd.Function):
    @staticmethod
    def forward(ctx, x, phi, alpha, bias, ...):
        op_module = mhc_pre_sinkhorn_op_builder.load()
        hin, h_post, h_res, ... = op_module.mhc_pre_sinkhorn(...)
        ...

在 eager 模式下,这段代码没有问题:OpBuilder.load() 加载扩展模块,然后通过 pybind 直接调用算子。

但在 torch.compile 下,Dynamo 会重新追踪 MhcPreSinkhornFunction.forward 的函数体。当它遇到下面这个 pybind 调用时:

op_module.mhc_pre_sinkhorn(...)

Dynamo 不知道这个 builtin 函数如何追踪,于是报错:

Dynamo does not know how to trace the builtin
mhc_pre_sinkhorn.pybind11_detail_function_record...

这正是 issue #4289 所体现的问题。根因不是 autograd.Function 在 eager 下不能工作,而是其函数体在 compile 下会被追踪,而函数体中存在无法被 Dynamo 解释的 pybind 直调。

为什么 compressor 的实现方式可以工作

compressor 将 pybind 调用藏在 custom_op 的实现函数内部:

@torch.library.custom_op(
    "cann_ops_transformer::_compressor_forward",
    ...,
)
def _compressor_forward(x, wkv, wgate, ...):
    op_module = compressor_op_builder.load()
    ... = op_module.compressor(...)

同时为该 op 注册 fake/meta 实现,用于编译阶段的形状推导。

对 Dynamo 来说,模型侧调用的是:

torch.ops.cann_ops_transformer._compressor_forward(...)

它是一个已经注册的 dispatcher op,具有明确的 schema 和 fake 实现。因此:

  1. Dynamo 将它作为图节点记录下来;
  2. 编译阶段使用 fake/meta 实现进行形状推导;
  3. Dynamo 不会进入 custom_op 实现函数体,也不会看到其中的 OpBuilder.load() 或 pybind 调用;
  4. 编译后的运行时通过 dispatcher 调用 PrivateUse1 实现,再由实现层调用 pybind 算子。

调用链可以概括为:

模型代码
  -> torch.ops.cann_ops_transformer._compressor_forward
  -> dispatcher 图节点
  -> PrivateUse1 实现
  -> pybind 扩展

这里的关键是 forward 被黑盒化为 dispatcher 节点,而不是 pybind 调用本身有什么特殊之处。

likedislike
CANN-robotCANN-robot成员
8月31日 关闭了 issue
CANN-robotCANN-robot成员
8月31日 添加了label:resolved