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

背景概述

在 PyTorch 的 torch.compile(mode="reduce-overhead") 优化路径中,图捕获(Graph Capture)是实现性能提升的关键环节。当前 PyTorch 社区对图捕获的实现主要围绕 CUDA 设备展开,但随着 NPU 等其他加速器的接入需求日益增长,我们需要设计一套可扩展的图捕获架构,使得不同硬件后端能够以统一的方式接入这一优化路径。

本文介绍的是“使用 GraphImplInterface 契约、但不迁移 CUDA”的实现变体。该方案的核心思路是:NPU 及其他已注册后端通过 at::accelerator::Graph / NPUGraphImpl 走新的契约路径,而 CUDA 保留原有的 torch.cuda.CUDAGraph 老路径,通过 if device.type=="cuda" 分叉实现共存。这种设计在保证 CUDA 零回归的前提下,为 NPU 等后端提供了接入图捕获的能力。

架构设计

四层架构概览

整个图捕获系统分为四个层次,CUDA 不进入 L1 注册表:

L4  Inductor 消费层
    compile_fx.cudagraphify(device=...)
    cudagraph_trees.py: Manager/Node 经 DeviceInterface 取运行时
    图对象构造: if device.type=="cuda": torch.cuda.CUDAGraph()  ← 老路分叉
                else:                    torch.accelerator.Graph()
    cudagraph_utils.py 资格门: CUDA 特判 True; 其余查能力位

L3  Dynamo 设备契约层(Python 扩展点)
    DeviceInterface.is_graph_capture_supported()
      CUDA: 硬编码 True(不查注册表)
      NPU : torch._C._accelerator_hasGraphImpl("npu")
    DeviceInterface.GraphOps: pool 路由 / checkpoint / prepare ...
      CUDA 转发 torch._C._cuda_*(零变化);NPU 转发 torch_npu._C._npu_*

L2  Python 门面层
    torch.accelerator.Graph(已有)
    torch._C._accelerator_hasGraphImpl(新增绑定)

L1  C++ 契约层
    GraphImplInterface / GraphImplRegistry(已有)
    XPUGraphImpl(已有)    NPUGraphImpl(torch_npu 侧新增)
    ❌ 无 CUDAGraphImpl —— CUDA 不在注册表

与其它方案的对比

维度本变体(GraphImplInterface + 不迁 CUDA)完整方案(GraphImplInterface + 迁 CUDA)最小方案(鸭子类型 GraphOps)
图对象层(非 CUDA)torch.accelerator.GraphNPUGraphImpl(需 C++)torch.accelerator.GraphNPUGraphImpl(需 C++)di.GraphOps.create_graph() → 复用 torch_npu.npu.NPUGraph(零 C++)
图对象层(CUDA)torch.cuda.CUDAGraph 老路径(if 分叉)torch.accelerator.GraphCUDAGraphImpldi.GraphOps.create_graph() → 转发 torch.cuda.CUDAGraph
CUDA 是否迁契约否(不写 CUDAGraphImpl
核心消费路径图对象层 if cuda 分叉;其余统一 di/GraphOps全统一 accelerator.Graph全统一 di.GraphOps
NPU 是否需 NPUGraphImpl需要需要不需要

整体调用链

torch.compile(mode="reduce-overhead")
│
└─ compile_fx_inner
   │
   ├─ check_multiple_devices_or_any_cpu_nodes
   │  单设备 d 合格 ⇔ (d.type=="cuda") or get_interface_for_device(d.type).is_graph_capture_supported()
   │
   ├─ check_caching_allocator_for_cudagraphs
   │  → di.GraphOps.caching_allocator_enabled()
   │
   └─ cudagraphify(签名加 device)
      │
      └─ cudagraph_trees.cudagraphify
         │
         └─ get_container(device).get_tree_manager()
            │
            └─ CUDAGraphTreeManager(= GraphTreeManager 别名)
               │  持有 di = get_interface_for_device(device.type)
               │
               ├─ 图对象构造 + 捕获(★唯一 CUDA 分叉点★)
               │  if device.type == "cuda":
               │      graph = torch.cuda.CUDAGraph()
               │      with torch.cuda.graph(graph, pool, stream, "thread_local"): ...
               │  else:
               │      graph = torch.accelerator.Graph(pool, "thread_local")
               │      with di.stream(stream), _graph_capture(graph): ...
               │
               ├─ 流/同步/设备上下文(所有后端统一)
               │  di.synchronize / di.Stream / di.current_stream / di.stream / di.device
               │
               └─ 内存池路由 + checkpoint + 捕获前清理(所有后端统一)
                  di.GraphOps.begin_allocate_current_thread_to_pool / end / release
                  di.GraphOps.get_checkpoint_state / set_checkpoint_pool_state /
                              check_pool_live_allocations / raw_delete
                  di.GraphOps.prepare_for_capture / graph_pool_handle / memory_snapshot

关键改动详解

PR-2:能力查询与 CUDA 特判

文件:torch/csrc/DeviceAccelerator.cpp

新增 _accelerator_hasGraphImpl 绑定:

m.def("_accelerator_hasGraphImpl", [](std::optional<std::string> device_type) {
  const auto dt = device_type.has_value()
      ? c10::Device(device_type.value()).type()
      : at::accelerator::getAccelerator(/*check=*/true);
  return at::has_graph_impl(dt);
}, py::arg("device_type") = std::nullopt);

文件:torch/_dynamo/device_interface.py

CUDA 不能查注册表(未注册),必须硬编码 True:

class DeviceInterface:
    @classmethod
    def is_graph_capture_supported(cls) -> bool:
        return False

class CudaInterface(DeviceInterface):
    @classmethod
    def is_graph_capture_supported(cls) -> bool:
        return True     # ★特判★ CUDA 走老路支持图捕获,但未入注册表

class XpuInterface(DeviceInterface):
    @classmethod
    def is_graph_capture_supported(cls) -> bool:
        return torch._C._accelerator_hasGraphImpl(cls.device.type)

PR-3:资格门设备无关化

文件:torch/_inductor/cudagraph_utils.py

# check_multiple_devices_or_any_cpu_nodes
- if (len(device_node_mapping) == 1
-         and next(iter(device_node_mapping.keys())).type == "cuda"):
-     return None
+ if len(device_node_mapping) == 1:
+     device = next(iter(device_node_mapping.keys()))
+     from torch._dynamo.device_interface import get_interface_for_device
+     if get_interface_for_device(device.type).is_graph_capture_supported():
+         return None

# check_caching_allocator_for_cudagraphs
- if torch.cuda.is_available() and not torch._C._cuda_cudaCachingAllocator_is_enabled():
+ di = get_interface_for_device(torch.accelerator.current_accelerator().type)
+ if di.is_available() and not di.GraphOps.caching_allocator_enabled():

PR-4:DeviceInterface.GraphOps 扩展点

文件:torch/_dynamo/device_interface.py

class CudaInterface(DeviceInterface):
    class GraphOps(DeviceInterface.GraphOps):
        graph_pool_handle = staticmethod(torch.cuda.graph_pool_handle)
        begin_allocate_current_thread_to_pool = staticmethod(torch._C._cuda_beginAllocateCurrentThreadToPool)
        end_allocate_to_pool = staticmethod(torch._C._cuda_endAllocateToPool)
        release_pool = staticmethod(torch._C._cuda_releasePool)
        get_checkpoint_state = staticmethod(torch._C._cuda_getCheckpointState)
        set_checkpoint_pool_state = staticmethod(torch._C._cuda_setCheckpointPoolState)
        check_pool_live_allocations = staticmethod(torch._C._cuda_checkPoolLiveAllocations)
        raw_delete = staticmethod(torch._C._cuda_cudaCachingAllocator_raw_delete)
        caching_allocator_enabled = staticmethod(torch._C._cuda_cudaCachingAllocator_is_enabled)
        @staticmethod
        def prepare_for_capture():
            ...  # 搬移 cudagraph_trees.py 的 clear_cublas_manager + disable_conv_cache_emptying

PR-5a:机械替换(流/同步/设备/allocator/checkpoint)

文件:torch/_inductor/cudagraph_trees.py

现调用替换为
torch.cuda.synchronize()di.synchronize()
torch.cuda.Stream() / current_stream()di.Stream() / di.current_stream()
torch.cuda.stream(...) / device(idx)di.stream(...) / di.device(idx)
_cuda_beginAllocateCurrentThreadToPooldi.GraphOps.*
_cuda_getCheckpointStatedi.GraphOps.*
clear_cublas_manager()+disable_conv_cache_emptying()di.GraphOps.prepare_for_capture()
torch.cuda.memory_snapshot()di.GraphOps.memory_snapshot()

PR-5b:图对象构造——CUDA 分叉

文件:torch/_inductor/cudagraph_trees.py

if device.type == "cuda":
    # CUDA 老路径,一行不改
    self.graph = torch.cuda.CUDAGraph()
    self.cuda_graphs_thread_pool = torch.cuda.graph_pool_handle()
    capture_cm = torch.cuda.graph(self.graph, pool=..., stream=self.stream,
                                  capture_error_mode="thread_local")
else:
    # 注册后端走 accelerator.Graph
    self.graph = torch.accelerator.Graph(pool=..., capture_error_mode="thread_local")
    self.cuda_graphs_thread_pool = di.GraphOps.graph_pool_handle()
    capture_cm = _graph_capture(self.graph)

with di.stream(self.stream), capture_cm:
    static_outputs = model(inputs)

NPU 侧接入方案

NPU 侧需要完成以下三步:

1. C++ NPUGraphImpl 实现

struct NPUGraphImpl final : public at::GraphImplInterface {
  explicit NPUGraphImpl(const at::GraphImplArgs& args = {}) {}
  void capture_begin(MempoolId_t pool, at::GraphCaptureMode mode) override {
    graph_.capture_begin(pool, toAclCaptureMode(mode));
  }
  void capture_end() override { graph_.capture_end(); }
  void instantiate()  override { /* CANN 在 capture_end 内已实例化 → no-op */ }
  void replay() override { graph_.replay(); }
  void reset()  override { graph_.reset(); }
  MempoolId_t pool() const override { return graph_.pool(); }
  void enable_debug_mode() override { graph_.enable_debug_mode(); }
  void debug_dump(const std::string& p) override { graph_.debug_dump(p); }
 private:
  c10_npu::NPUGraph graph_;
};
REGISTER_GRAPH_IMPL(NPU, NPUGraphImpl)

2. NpuInterface 实现

class NpuInterface(DeviceInterface):
    @classmethod
    def is_graph_capture_supported(cls):
        return torch._C._accelerator_hasGraphImpl("npu")
    class GraphOps(DeviceInterface.GraphOps):
        graph_pool_handle = staticmethod(torch_npu.npu.graphs.graph_pool_handle)
        begin_allocate_current_thread_to_pool = staticmethod(torch_npu._C._npu_beginAllocateCurrentThreadToPool)
        end_allocate_to_pool = staticmethod(torch_npu._C._npu_endAllocateToPool)
        release_pool = staticmethod(torch_npu._C._npu_releasePool)
        get_checkpoint_state = staticmethod(torch_npu._C._npu_getCheckpointState)
        set_checkpoint_pool_state = staticmethod(torch_npu._C._npu_setCheckpointPoolState)

3. 适配层注意事项

NPUGraphImpl 需要弥合三处签名差异:

  • capture_begin 的 mode 参数映射
  • capture_end 后 CANN 自动实例化,instantiate 为 no-op
  • keep_graph 参数无语义,设为 true 时 WARN

CUDA 零回归验证

逻辑现(直绑 CUDA)本变体
图对象构造/捕获/回放torch.cuda.CUDAGraph + torch.cuda.graph(...)完全不变(if cuda 分支保原路)
内存池路由 + checkpointtorch._C._cuda_*di.GraphOps.*(一行转发原符号)
流/同步/设备torch.cuda.*di.*(CUDA 实现即 torch.cuda.*
资格判断== "cuda" 字面量is_graph_capture_supported(),CUDA 仍硬编码 True

不迁移 CUDA 的代价

  1. 重新引入 == "cuda" 特判:核心路径出现 if device.type=="cuda" 分叉,与社区"拆掉 ==cuda"的方向相悖
  2. 能力位契约不对称:CUDA 硬编码 True、NPU 查注册表
  3. else 分支失去 in-tree 共验后端:NPU 成为新消费路径的唯一验证者
  4. instantiate/keep_graph 等语义边界只由 NPU no-op 路径探

PR 拆分与合入判据

PR内容合入门槛
PR-2_accelerator_hasGraphImpl + is_graph_capture_supported(CUDA 硬编码 True)能力查询贯通;CUDA 返回 True
PR-3资格门 + allocator 检查 + ir.py(CUDA 特判保留)假后端能力位 True 时放行
PR-4DeviceInterface.GraphOps + CUDA/XPU 转发逐符号 assertIs
PR-5a机械替换(di.* / GraphOps.*,含 CUDA)test_cudagraph_trees.py 零改动通过
PR-5b图对象 if cuda: 老路 else: accelerator.Graph非 CUDA 分支端到端(靠 NPU/XPU)
PR-6设备泛型测试NPU/XPU 单独验 else 分支

总结

本变体通过 GraphImplInterface 契约为 NPU 等后端提供了接入图捕获的标准化路径,同时通过 if cuda 分叉保留了 CUDA 的原有实现,实现了"零回归"的目标。虽然引入了 ==cuda 特判等代价,但相比完整方案省去了 CUDA 迁移的前置工作,适合作为渐进式改造的中间步骤。NPU 侧需要完成 NPUGraphImpl 的 C++ 实现和 NpuInterface 的 Python 扩展,即可接入 torch.compile(mode="reduce-overhead") 优化路径。# 背景概述

在 PyTorch 的 Inductor 图捕获(cudagraph)机制中,原有的实现高度耦合于 CUDA 设备,大量代码直接使用 torch.cuda.*torch._C._cuda_* 符号。随着 XPU、NPU 等其他加速器后端逐步接入 PyTorch 生态,这种硬编码的 CUDA 依赖成为设备无关化的主要障碍。

本文旨在描述如何通过引入设备接口抽象层(DeviceInterface)和 GraphOps 契约,将图捕获相关的资格判断、内存池管理、checkpoint 操作等核心逻辑从 CUDA 硬编码改造为设备无关的通用实现。改造的核心思路是:CUDA 保留原有行为不变(零回归),其他后端通过注册机制获得图捕获能力


1. torch/_dynamo/device_interface.py:设备接口抽象层

1.1 DeviceInterface 基类:能力位与 GraphOps 契约

目的:定义“设备是否支持 Inductor 图捕获”的共享能力位(供资格门与图对象分发消费),以及 cudagraph_trees 所需的 allocator/checkpoint 运行时扩展点 GraphOps(契约层)。默认实现使未适配的后端安全地“不支持图捕获”。default_generatorsir.py 的 GeneratorState 设备无关化。

1.2 CudaInterface:能力位硬编码 True + GraphOps 转发 + default_generators

修改前raise_if_triton_unavailable 之后直接是 get_mtia_stream

        elif "nvidia" not in triton.backends.backends:
            raise RuntimeError("triton not built with the 'nvidia' backend")

get_mtia_stream: Callable[[int], int] | None

修改后

        elif "nvidia" not in triton.backends.backends:
            raise RuntimeError("triton not built with the 'nvidia' backend")

@classmethod
    def is_graph_capture_supported(cls) -> bool:
        # 本变体不迁移 CUDA:CUDA 保留 torch.cuda.CUDAGraph 老路、未入 C++ 注册表,
        # 不能由 _accelerator_hasGraphImpl 推导 → 硬编码 True 保持 CUDA 既有资格。
        return True

@staticmethod
    def default_generators() -> Any:
        return torch.cuda.default_generators

class GraphOps(DeviceInterface.GraphOps):
        graph_pool_handle = staticmethod(torch.cuda.graph_pool_handle)
        # torch._C._cuda_* 仅 CUDA 构建存在 → 运行时包装懒绑定(对齐 get_cuda_stream 条件导入模式)
        @staticmethod
        def begin_allocate_current_thread_to_pool(device, pool) -> None:
            torch._C._cuda_beginAllocateCurrentThreadToPool(device, pool)
        @staticmethod
        def end_allocate_to_pool(device, pool) -> None:
            torch._C._cuda_endAllocateToPool(device, pool)
        @staticmethod
        def release_pool(device, pool) -> None:
            torch._C._cuda_releasePool(device, pool)
        @staticmethod
        def get_checkpoint_state(device, pool) -> Any:
            return torch._C._cuda_getCheckpointState(device, pool)
        @staticmethod
        def set_checkpoint_pool_state(device, state, stale_storages, storages_to_add_deleters_to) -> None:
            torch._C._cuda_setCheckpointPoolState(device, state, stale_storages, storages_to_add_deleters_to)
        @staticmethod
        def check_pool_live_allocations(device, pool, expected_live_allocations) -> bool:
            return torch._C._cuda_checkPoolLiveAllocations(device, pool, expected_live_allocations)
        @staticmethod
        def raw_delete(ptr) -> None:
            torch._C._cuda_cudaCachingAllocator_raw_delete(ptr)
        @staticmethod
        def caching_allocator_enabled() -> bool:
            return torch._C._cuda_cudaCachingAllocator_is_enabled()
        @staticmethod
        @contextlib.contextmanager
        def prepare_for_capture() -> Any:
            # 复刻 cudagraph_trees clear_cublas_manager + disable_conv_cache_emptying(同符号,零行为变化)
            torch._C._cuda_clearCublasWorkspaces()
            prev_conv = torch._C._cuda_get_conv_benchmark_empty_cache()
            torch._C._cudnn_set_conv_benchmark_empty_cache(False)
            try:
                yield
            finally:
                torch._C._cudnn_set_conv_benchmark_empty_cache(prev_conv)
                torch._C._cuda_clearCublasWorkspaces()
        memory_snapshot = staticmethod(torch.cuda.memory_snapshot)

get_mtia_stream: Callable[[int], int] | None

目的:给 CUDA 提供图捕获能力位与 GraphOps 内置实现。

  • 能力位硬编码 True:这是“不迁移 CUDA”变体的关键——CUDA 不在 GraphImplRegistry_accelerator_hasGraphImpl("cuda") 会返回 False,故不能查注册表,必须硬编码以保持 CUDA 既有资格。
  • GraphOps 一行转发原 torch._C._cuda_* 符号:保证消费层统一走 di.GraphOps 后,CUDA 调用的底层 C 符号不变(行为零变化)。用运行时包装而非 staticmethod(torch._C._cuda_...),避免 CPU-only 构建在类定义期因符号缺失而 AttributeError
  • prepare_for_capture 内联 cublas/conv 清理:避免 _dynamo 反向依赖 _inductor,且使用与原 cudagraph_trees 相同的符号。

1.3 XpuInterface:能力位查注册表

修改前raise_if_triton_unavailable 之后直接是 CpuDeviceProperties

        if "intel" not in triton.backends.backends:
            raise RuntimeError("triton not built with the 'intel' backend")

@dataclass
class CpuDeviceProperties:

修改后

        if "intel" not in triton.backends.backends:
            raise RuntimeError("triton not built with the 'intel' backend")

@classmethod
    def is_graph_capture_supported(cls) -> bool:
        # XPU 已迁入 GraphImplInterface 契约(XPUGraphImpl 已注册),注册表是唯一事实源。
        return torch._C._accelerator_hasGraphImpl(cls.device.type)

@dataclass
class CpuDeviceProperties:

目的:XPU 已按契约注册 XPUGraphImpl,其能力位以 C++ 注册表为准。与 CUDA 的硬编码形成对照,体现“注册后端查注册表、未迁 CUDA 特判”的不对称契约。

注:本变体未给 XpuInterfaceGraphOps(XPU 的 Inductor 图树启用属延后项,其 torch._C._xpu_* checkpoint 符号未必齐全);且该方法依赖 C++ 绑定,需重编译后才可调用。


2. torch/_inductor/cudagraph_utils.py

2.1 check_multiple_devices_or_any_cpu_nodes(资格门)

修改前:361

    if (
        len(device_node_mapping) == 1
        and next(iter(device_node_mapping.keys())).type == "cuda"
    ):
        return None

修改后

    if len(device_node_mapping) == 1:
        device = next(iter(device_node_mapping.keys()))
        from torch._dynamo.device_interface import get_interface_for_device

        # 设备无关资格门:单设备图合格 iff 该设备支持图捕获。
        # CUDA 经 CudaInterface 硬编码 is_graph_capture_supported 仍合格;
        # 注册后端(XPU/NPU)查注册表。
        if get_interface_for_device(device.type).is_graph_capture_supported():
            return None

目的:把资格判断从 == "cuda" 字面量改为查能力位,让 NPU(及任何支持图捕获的后端)能通过单设备资格门。CUDA 因硬编码 True 而行为不变。

2.2 check_caching_allocator_for_cudagraphs

修改前:371

    if (
        torch.cuda.is_available()
        # pyrefly: ignore [missing-attribute]
        and not torch._C._cuda_cudaCachingAllocator_is_enabled()
    ):
        return format_default_skip_message(
            "cudagraph capture requires the caching allocator; "
            "current allocator is uncached"
        )
    return None

修改后

    current_accelerator = torch.accelerator.current_accelerator()
    if current_accelerator is None:
        return None
    from torch._dynamo.device_interface import get_interface_for_device

    di = get_interface_for_device(current_accelerator.type)
    if di.is_available() and not di.GraphOps.caching_allocator_enabled():
        return format_default_skip_message(
            "cudagraph capture requires the caching allocator; "
            "current allocator is uncached"
        )
    return None

目的:把“caching allocator 是否开启”的检查从硬编码 CUDA 改为按当前 accelerator 经 GraphOps.caching_allocator_enabled() 分发。NPU 用自己的 allocator 状态判定,CUDA 经转发保持原判定。


3. torch/_inductor/ir.py(GeneratorState 断言设备无关化)

两处结构相同(args_flat 分支 :7172example_inputs 分支 :7242)。

3.1 :7172 附近

修改前

                    device_index = arg.device.index
                    if not (arg.device.type == "cuda" and device_index is not None):
                        raise AssertionError(
                            'Expected arg.device.type == "cuda" and device_index is not None'
                        )
                    real_non_tensor_args.append(
                        torch.cuda.default_generators[device_index].clone_state()
                    )

修改后

                    device_index = arg.device.index
                    if device_index is None:
                        raise AssertionError(
                            "Expected GeneratorState device_index is not None"
                        )
                    from torch._dynamo.device_interface import (
                        get_interface_for_device,
                    )

                    di = get_interface_for_device(arg.device.type)
                    real_non_tensor_args.append(
                        di.default_generators()[device_index].clone_state()
                    )

3.2 :7242 附近

修改前

                device_index = x.device.index
                if not (x.device.type == "cuda" and device_index is not None):
                    raise AssertionError(
                        'Expected x.device.type == "cuda" and device_index is not None'
                    )
                example_args.append(
                    torch.cuda.default_generators[device_index].clone_state()
                )

修改后

                device_index = x.device.index
                if device_index is None:
                    raise AssertionError(
                        "Expected GeneratorState device_index is not None"
                    )
                from torch._dynamo.device_interface import get_interface_for_device

                di = get_interface_for_device(x.device.type)
                example_args.append(
                    di.default_generators()[device_index].clone_state()
                )

目的:解除 GeneratorState(含 RNG 生成器占位符的 AOTI 场景)对 CUDA 的硬断言,改为经 di.default_generators() 取设备默认生成器。保留“device_index 不为 None”的真实前置断言。CUDA 经 CudaInterface.default_generators() 返回 torch.cuda.default_generators,行为不变;NPU 提供自己的实现即可走通。


4. torch/csrc/DeviceAccelerator.cpp(新增能力查询绑定,需重编译)

4.1 新增头文件

修改前

#include <c10/core/AllocatorConfig.h>
#include <torch/csrc/DeviceAccelerator.h>
#include <torch/csrc/Exceptions.h>
#include <torch/csrc/utils/device_lazy_init.h>

修改后

#include <ATen/core/GraphImplInterface.h>
#include <c10/core/AllocatorConfig.h>
#include <c10/core/Device.h>
#include <torch/csrc/DeviceAccelerator.h>
#include <torch/csrc/Exceptions.h>
#include <torch/csrc/utils/device_lazy_init.h>

目的:引入 at::has_graph_implGraphImplInterface.h)与 c10::Device(std::string) 构造(Device.h)。

4.2 新增 _accelerator_hasGraphImpl 绑定

修改前_accelerator_getDefaultGenerator 之后直接是 Accelerator Graph class binding)

  m.def("_accelerator_getDefaultGenerator", [](c10::DeviceIndex device_index) {
    const auto device_type = at::accelerator::getAccelerator(true).value();
    torch::utils::maybe_initialize_device(device_type);
    return at::accelerator::getDefaultGenerator(device_index);
  });

// Accelerator Graph class binding

修改后

  m.def("_accelerator_getDefaultGenerator", [](c10::DeviceIndex device_index) {
    const auto device_type = at::accelerator::getAccelerator(true).value();
    torch::utils::maybe_initialize_device(device_type);
    return at::accelerator::getDefaultGenerator(device_index);
  });

// Whether a backend-agnostic graph capture implementation (GraphImplInterface)
  // is registered for the given device type (default: current accelerator).
  // Consumed by DeviceInterface.is_graph_capture_supported for registered backends
  // (XPU/NPU). CUDA is intentionally not registered in the no-CUDA-migration
  // variant, so CudaInterface hardcodes its capability bit instead of using this.
  m.def(
      "_accelerator_hasGraphImpl",
      [](std::optional<std::string> device_type) {
        const auto dt = device_type.has_value()
            ? c10::Device(device_type.value()).type()
            : at::accelerator::getAccelerator(/*check=*/true).value();
        return at::has_graph_impl(dt);
      },
      py::arg("device_type") = std::nullopt);

// Accelerator Graph class binding

目的:暴露 Python 可查询的“某设备类型是否注册了 GraphImplInterface 实现”接口,供 XpuInterface/NpuInterfaceis_graph_capture_supported 消费。CUDA 因不入注册表而不走此查询(硬编码 True)。

⚠️ 此为 C++ 改动,需 pip install -e . -v --no-build-isolation 重新编译后才生效;未重编译前,XpuInterface/NpuInterface.is_graph_capture_supported 调用会 AttributeError


5. torch/_inductor/cudagraph_trees.py(PR-5a/5b,消费层主体)

5.0 总体方案:从当前 accelerator 解析 di

cudagraph_trees 本就是“进程单 accelerator”假设。为避免把 device_type 穿透进所有类/自由函数的签名,新增两个模块级 helper 从当前 accelerator 解析设备接口:

def _graph_device_type() -> str:
    acc = torch.accelerator.current_accelerator()
    return acc.type if acc is not None else "cuda"

def _graph_device_interface(device_type: str | None = None) -> Any:
    from torch._dynamo.device_interface import get_interface_for_device
    return get_interface_for_device(device_type or _graph_device_type())

并新增图捕获上下文分叉 helper(CUDA 走 torch.cuda.graph;注册后端走 torch.accelerator.Graph,其本身是“当前流上开始/结束捕获”的上下文管理器):

@contextlib.contextmanager
def _graph_capture_context(di, device_type, graph, stream, pool, capture_error_mode="thread_local"):
    if device_type == "cuda":
        with torch.cuda.graph(graph, stream=stream, pool=pool, capture_error_mode=capture_error_mode):
            yield
    else:
        with di.stream(stream), graph:  # accelerator.Graph 携带构造期 pool/mode
            yield

目的:一处解析、全局复用;CUDA 的 di.*/di.GraphOps.* 均转发原符号(零行为变化),仅图对象层 if 分叉。

5.1 三个类:__init__self.device_interface / self.device_type

CUDAWarmupNode / CUDAGraphNode / CUDAGraphTreeManager__init__ 各加:

self.device_type = _graph_device_type()
self.device_interface = _graph_device_interface(self.device_type)

目的:类方法内统一用 self.device_interface.* 取运行时,用 self.device_type 做 if 分叉。

5.2 机械替换映射(前 → 后)

现调用替换为所在
torch.cuda.synchronize()self.device_interface.synchronize() / di.synchronize()各类方法 / _use_cuda_memory_pool_manager / check_memory_pool
torch.cuda.Stream() / current_stream() / stream(...) / device(...)di.Stream() / di.current_stream() / di.stream(...) / di.device(...)同上
torch.cuda.graph_pool_handle()di.GraphOps.graph_pool_handle()manager __init__
torch._C._cuda_beginAllocateCurrentThreadToPool / _endAllocateToPool / _releasePooldi.GraphOps.begin_allocate_current_thread_to_pool_use_cuda_memory_pool_manager
torch._C._cuda_getCheckpointStateself.device_interface.GraphOps.get_checkpoint_stateCUDAGraphNode.record
torch._C._cuda_setCheckpointPoolStateself.device_interface.GraphOps.set_checkpoint_pool_statemanager
torch._C._cuda_checkPoolLiveAllocationsdi.GraphOps.check_pool_live_allocationscheck_memory_pool
torch._C._cuda_cudaCachingAllocator_raw_deleteself.device_interface.GraphOps.raw_deletemanager
torch.cuda.memory_snapshot()di.GraphOps.memory_snapshot()(None → 返回空段,跳过慢路径断言)get_cudagraph_segments

5.3 图对象 if 分叉(PR-5b)——两处构造 + 两处捕获

构造CUDAGraphNode._recordCUDAGraphTreeManager.__init__):

if self.device_type == "cuda":
    self.graph = torch.cuda.CUDAGraph()
else:
    self.graph = torch.accelerator.Graph(pool=<pool>, capture_error_mode="thread_local")

manager __init__先取 pool 再建图(accelerator.Graph 构造期需要 pool),CUDA 无影响。

捕获torch.cuda.graph(self.graph, stream=..., pool=..., capture_error_mode=...)_graph_capture_context(self.device_interface, self.device_type, self.graph, self.stream, <pool>, "thread_local")

5.4 CUDA 专属清理的处理:守卫而非合并

关键决策clear_cublas_manager / disable_conv_cache_emptying / enable_history_recordingget_history_recording未改为 GraphOps.prepare_for_capture,而是在函数顶部加 if _graph_device_type() != "cuda": yield/nullcontext; return 守卫

目的/原因:warmup 路径用了两个清理(conv + cublas),而 record 路径只用了 cublas 一个;若统一替换为“合并二者”的 prepare_for_capture,会给 record 路径新增 conv 清理 → 改变 CUDA 行为。守卫方案让 CUDA 调用点字节不变、NPU 安全 no-op(NPU 无 cublas/cudnn 清理需求),且这些函数内的 torch._C._cuda_* 永不在非 CUDA 路径执行。

5.5 类型注解放宽

self.graph: torch.cuda.CUDAGraph | None / existing_cuda_graph: torch.cuda.CUDAGraph | None / stream: torch.cuda.StreamAny | None / AnyTreeManagerContainerCUDAWarmupNodeCUDAGraphNode)。


6. torch/_inductor/compile_fx.py(PR-5a/5b,旧非 trees 路径)

cudagraphify_implconfig.triton.cudagraph_trees=False 时的回退路径)的 warmup + record 段:

修改前

    torch.cuda.synchronize()
    stream = torch.cuda.Stream()
    stream.wait_stream(torch.cuda.current_stream())
    with torch.cuda.stream(stream):
        model(list(static_inputs))
    stream.synchronize()
    torch.cuda.current_stream().wait_stream(stream)
    torch.cuda.synchronize()
    # record
    graph = torch.cuda.CUDAGraph()
    with torch.cuda.graph(graph, stream=stream, capture_error_mode="thread_local"):
        static_outputs = model(list(static_inputs))

修改后

    from torch._dynamo.device_interface import get_interface_for_device
    _acc = torch.accelerator.current_accelerator()
    device_type = _acc.type if _acc is not None else "cuda"
    di = get_interface_for_device(device_type)
    # warmup
    di.synchronize()
    stream = di.Stream()
    stream.wait_stream(di.current_stream())
    with di.stream(stream):
        model(list(static_inputs))
    stream.synchronize()
    di.current_stream().wait_stream(stream)
    di.synchronize()
    # record
    if device_type == "cuda":
        graph = torch.cuda.CUDAGraph()
        with torch.cuda.graph(graph, stream=stream, capture_error_mode="thread_local"):
            static_outputs = model(list(static_inputs))
    else:
        graph = torch.accelerator.Graph(capture_error_mode="thread_local")
        with di.stream(stream), graph:
            static_outputs = model(list(static_inputs))

目的:让旧回退路径也设备无关。graph.replay() 对两种图对象通用,无需改动。

get_cuda_device_context(compile 期设备上下文)属设备守卫范畴,非 cudagraph 捕获点,未改。

Logo

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

更多推荐