Repository navigation
Ascend backend: decouple torch_npu so PrivateUse1 plugins can use it #1046
Description
Summary
The Ascend backend is coupled to torch_npu at the C++/link level, not just at the Python import level. This makes it unusable from PyTorch out-of-tree device plugins that claim the PrivateUse1 backend key themselves, because torch_npu claims PrivateUse1 on import and only one owner is possible per process.
I would like to use FlagTree's Ascend backend as the Triton layer under torch.compile for such a plugin, and currently cannot. triton-ascend works there precisely because it does not require torch_npu.
All references below are against triton_v3.5.x at d2063b0.
The coupling is not just the imports
The obvious reading is that this is three stray imports (driver.py:231, utils.py:48, backend_register.py:87) which a stub module could satisfy. That is not the case — the torch_npu backend policy also selects generated C++ and link flags:
| Location | Emits |
|---|---|
backend_register.py:227 |
-I{torch_npu}/include, -L{torch_npu}/lib, -ltorch_npu |
backend_register.py:293 |
#include <torch_npu/csrc/core/npu/NPUWorkspaceAllocator.h> |
backend_register.py:316 |
at_npu::native::allocate_workspace(...) |
backend_register.py:340 |
at_npu::native::OpCommand cmd; |
backend_register.py:305 |
allocates on at::kPrivateUse1 |
backend_register.py:270-278 |
torch_npu._C._npu_getCurrentRawStream[NoWait] (compiled extension) |
So the launcher .so genuinely needs libtorch_npu.so and its headers. A Python-level shim cannot work, and backend_register.py:305 targeting at::kPrivateUse1 is itself the conflict rather than a workaround.
Why this blocks plugin-based integrations
PyTorch allows exactly one out-of-tree backend to occupy PrivateUse1. A plugin that calls torch.utils.rename_privateuse1_backend("<name>") and registers its own kernels, allocator and streams cannot coexist with torch_npu in one process — whichever imports first wins, and the other fails to register.
Such a plugin already has everything the Ascend backend needs from torch_npu: current device, current stream as an aclrtStream, and a workspace allocator. What it cannot do is be torch_npu.
What I'd like to ask
The registry already anticipates more than one host runtime — mindspore is a full second policy alongside torch_npu, and BackendStrategyRegistry.register(category, method) accepts any category string. That seems like the right seam. Concretely:
-
Would you accept a third, non-
torch_npupolicy covering the 11 methodsdriver.pydispatches (get_current_device,set_current_device,get_current_stream,get_device_interface,get_empty_tensor,get_tensor_params_shape,header_file,get_cc_cmd,allocate_memory,allocate_sync_block_lock,pre_launch,async_launch)? A generictorch_pluginpolicy that parameterizes the device name and drops theat_npu::calls would serve any PrivateUse1 plugin, not just mine. -
Could
utils.get_backend_funcstop hard-coding the accepted policy names?utils.py:42-44whitelists onlytorch_npuandmindspore, so even a correctly registered third policy cannot be selected viaTRITON_BACKEND. Accepting any registered category, or at least consulting the registry, would make the extension point usable without patching. -
Could the two
at_npu::native::uses fall back to CANN APIs?allocate_sync_block_lockandasync_launchare the only places that need more than ATen. If the workspace could come from a plainat::emptyon the active device and the task-queue path were optional, the policy would need no vendor-specific C++ at all. -
Smaller, independent:
driver.py:229-232importstorch_npuonly sotorch.device("npu", ...)is constructible, and its one caller (compiler/hint_manager.py:95) just readsdevice.type. If the device name were taken from the policy, this import could go away on its own.
I'm happy to prepare a PR for (1) and (2) if that direction is acceptable — please say which shape you'd prefer before I start. Thanks for the work on this backend.
Activity