作者​:昇腾实战派
知识地图​:https://blog.csdn.net/Lumos_Lovegood/article/details/161601003

0. 背景与动机

问题描述

在使用 torch.compile 时,Inductor 后端需要编译 C++ kernel,这一过程需要获取设备相关的编译选项,包括 include 路径、链接库和宏定义等。当前,这部分逻辑由 torch/_inductor/cpp_builder.py 中的 get_cpp_torch_device_options() 函数提供。

然而,该函数目前仅硬编码支持三种设备:

设备判定条件提供内容
CUDAdevice_type == "cuda"USE_CUDA / USE_ROCMlibcuda.so
XPUdevice_type == "xpu"USE_XPUze_loadersycl
MPSdevice_type == "mps"USE_MPS

对于像 NPU(Ascend)这样的 PrivateUse1 设备,并不在支持范围内。当调用 get_cpp_torch_device_options("npu") 时,三个 if 分支全部跳过,返回空的设备编译选项,导致 C++ kernel 编译失败。

当前临时方案

目前,torch_npu 通过 monkey-patch 在运行时替换整个函数:

# torch_npu/_inductor/cpp_builder.py
def patch_get_cpp_torch_device_options():
    torch._inductor.cpp_builder.get_cpp_torch_device_options = get_cpp_torch_device_options

这种方式存在以下问题:

  • 非正规 API,多个后端同时 patch 会互相覆盖
  • 上游函数签名变化时会导致静默崩溃
  • 无法被上游社区接受

为什么不能直接将 NPU 硬编码进上游

NPU 依赖的外部资源均不在 PyTorch 仓库内:

NPU 所需资源维护方PyTorch 仓库中是否存在
/usr/local/Ascend/includeCANN 工具包(华为)
libascendcl.soCANN 工具包(华为闭源)
libtorch_npu.sotorch_npu 项目
Ascend 910B 测试硬件华为PyTorch CI 中不存在

若硬编码 elif device_type == "npu",PyTorch CI 会因找不到这些路径和库而构建失败。

1. 方案设计

核心思路

上游提供注册 API,NPU 在 torch_npu 初始化时登记回调。上游不感知 NPU 的具体细节。

                    PyTorch 上游                          torch_npu
                   ┌──────────────────────┐         ┌──────────────────┐
                   │ _registry = {}        │         │ def npu_options():│
                   │                      │  登记    │   return [        │
                   │ register_cpp_device  │←────────│     "USE_NPU",    │
                   │   _options("npu",fn) │         │     "/Ascend/...",│
                   │                      │         │     "ascendcl"    │
                   │ get_cpp_torch_device │         │   ]               │
                   │   _options("npu")    │         └──────────────────┘
                   │   ├─ cuda? no        │
                   │   ├─ xpu?  no        │
                   │   ├─ mps?  no        │
                   │   └─ 查注册表 → 命中  │
                   │       → 调用 npu回调  │
                   │       → 返回 NPU选项  │
                   └──────────────────────┘

与已有注册模式的对照

PyTorch Inductor 已有多个设备注册机制,本实践补齐最后一块缺失的拼图:

注册机制文件模式
register_backend_for_devicecodegen/common.py注册表 dict
register_interface_for_device_dynamo/device_interface.py注册表 dict
register_device_op_overridescodegen/common.py注册表 dict
register_cpp_device_options(本次新增)cpp_builder.py注册表 dict

2. 改动内容

文件:torch/_inductor/cpp_builder.py(+38 行,-1 行)

2.1 import 行

修改前:

from collections.abc import Sequence

修改后:

from collections.abc import Callable, Sequence

目的:注册表类型标注 dict[str, Callable] 需要 Callable

2.2 新增注册表与注册函数(插在 get_cpp_torch_device_options 定义之前)

修改前(_transform_rocm_paths 之后直接是函数定义):

def _transform_rocm_paths(lpaths: list[str]) -> None:
    if config.is_fbcode():
        sdk_lib = build_paths.sdk_lib
        if os.path.isdir(sdk_lib) and sdk_lib not in lpaths:
            lpaths.append(sdk_lib)

def get_cpp_torch_device_options(

修改后:

def _transform_rocm_paths(lpaths: list[str]) -> None:
    if config.is_fbcode():
        sdk_lib = build_paths.sdk_lib
        if os.path.isdir(sdk_lib) and sdk_lib not in lpaths:
            lpaths.append(sdk_lib)

# Registry for out-of-tree device backends to provide C++ compile options.
# Maps device_type (e.g. "npu") to a callback with the same signature as
# get_cpp_torch_device_options.
_cpp_device_options_registry: dict[str, Callable] = {}

def register_cpp_device_options(
    device_type: str,
    callback: Callable,
) -> None:
    """Register C++ build options for a device type.

Allows out-of-tree device backends (e.g., PrivateUse1 devices like NPU)
    to inject their own compile options into Inductor's C++ compilation
    pipeline without monkey-patching.

Args:
        device_type: The device type string (e.g. "npu").
        callback: A callable with the same signature as
            :func:`get_cpp_torch_device_options`:
            ``(device_type, aot_mode, compile_only) ->
            tuple[definitions, include_dirs, cflags, ldflags,
                  libraries_dirs, libraries, passthrough_args]``
    """
    if device_type in _cpp_device_options_registry:
        warnings.warn(
            f"Device options for '{device_type}' is already registered, overwriting."
        )
    _cpp_device_options_registry[device_type] = callback

def get_cpp_torch_device_options(

目的:提供模块级注册表 _cpp_device_options_registry 与公开 API register_cpp_device_options()。重复注册时发出警告并覆盖,与 PyTorch 其他注册机制一致。

2.3 函数内新增查表逻辑(插在 mps 分支之后、fbcode 分支之前)

修改前:

    if device_type == "mps":
        definitions.append(" USE_MPS")

if config.is_fbcode():

修改后:

    if device_type == "mps":
        definitions.append(" USE_MPS")

# Check registered device backends (e.g., PrivateUse1 devices like NPU)
    if device_type in _cpp_device_options_registry:
        return _cpp_device_options_registry[device_type](
            device_type, aot_mode, compile_only
        )

if config.is_fbcode():

目的:cuda/xpu/mps 均不命中时查注册表。命中则直接委托给注册的回调并返回,跳过后续公共路径。未命中则继续原逻辑(返回空设备选项)。

文件:torch_npu/_inductor/cpp_builder.py(下游适配)

修改前:

def patch_get_cpp_torch_device_options():
    torch._inductor.cpp_builder.get_cpp_torch_device_options = get_cpp_torch_device_options

修改后:

def patch_get_cpp_torch_device_options():
    """Register NPU device options to PyTorch Inductor C++ builder.

Uses the official registration API when available (PyTorch >= 2.7),
    falls back to monkey-patching for older versions.
    """
    if hasattr(torch._inductor.cpp_builder, 'register_cpp_device_options'):
        # New path: proper API registration
        torch._inductor.cpp_builder.register_cpp_device_options(
            "npu", get_cpp_torch_device_options
        )
    else:
        # Legacy path: monkey-patch for older PyTorch versions
        torch._inductor.cpp_builder.get_cpp_torch_device_options = \
            get_cpp_torch_device_options

目的:优先使用上游注册 API;未合入此 PR 的旧版 PyTorch 回退 monkey-patch。get_cpp_torch_device_options 回调本身无需改动,签名已与上游一致。

3. 调用链路

完整时序

1. torch_npu 加载
   → __init__.py → patch_torch_for_aoti()
   → patch_get_cpp_torch_device_options()
   → register_cpp_device_options("npu", npu_get_options)
   → _registry = {"npu": npu_get_options}            ← 登记完成

2. 用户代码
   @torch.compile(backend="inductor")
   def model(x): ...

3. Inductor 生成 C++ kernel → 需要编译
   → CppBuilder.build()
   → CppTorchDeviceOptions(device_type="npu")
   → get_cpp_torch_device_options("npu")
     ├─ device_type == "cuda"?  → no
     ├─ device_type == "xpu"?   → no
     ├─ device_type == "mps"?   → no
     ├─ "npu" in _registry?     → yes
     └─ return _registry["npu"]("npu", aot_mode, compile_only)
        → 返回 (["USE_NPU"], ["/usr/local/Ascend/include", ...],
                 [], [], ["/usr/lib64", ...], ["ascendcl", "torch_npu"],
                 ["-DBUILD_LIBTORCH=ON"])

4. g++ 命令组装
   g++ kernel.cpp
     -DUSE_NPU
     -I/usr/local/Ascend/include
     -L/usr/lib64
     -lascendcl -ltorch_npu
     -DBUILD_LIBTORCH=ON
     -o kernel.so

关键不变量

  • CUDA/XPU/MPS 的处理逻辑一行不改
  • 未注册的设备类型行为不变(返回空设备选项)
  • torch_npu 在旧版 PyTorch 上兼容(回退 monkey-patch)

4. PR 信息

项目内容
PR 编号#190614
仓库pytorch/pytorch
分支lyx0517:inductor-npu-device-optionspytorch:main
改动量1 file, +38 / -1
RFChttps://gitcode.com/suhaibo/pytorch/wiki

5. 兼容性与风险

策略
旧版 PyTorch 无注册 APItorch_npu 保留 monkey-patch fallback
两个后端注册同名设备warn + overwrite(与 register_backend_for_device 一致)
注册回调抛异常异常直接传播,不吞
已有 cuda/xpu/mps逻辑一字不改

6. 验证

在 CANN 9.0.1 + PyTorch 2.13.0(安装版,piped)容器内完成功能验证:

#测试项结果
1mock 设备注册 + 查询
2重复注册触发 warning
3未知设备不污染 CUDA 定义
4未知设备不污染 XPU 定义
5未知设备不污染 MPS 定义
6未知设备不泄露 mock 定义
7NPU 回调注册后 USE_NPU 注入
8NPU include 路径正确
9NPU 链接库正确(ascendcl, torch_npu)
10NPU passthrough 参数正确
Logo

鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。

更多推荐