| Files | Last commit | Last update |
|---|---|---|
| 10 months ago | ||
| 6 months ago | ||
| 4 months ago | ||
| 7 months ago | ||
| 6 months ago | ||
| 4 months ago | ||
| 6 days ago | ||
| 10 months ago | ||
| 6 months ago | ||
| 6 months ago |
Fast Kernel Launch
English | 简体中文
Overview
This document demonstrates how to develop custom NPU operators using Ascend C and the PyTorch Extension capabilities.
Core advantages:
-
Single deliverable: One file completes both operator development and PyTorch framework adaptation.
-
Efficient invocation: The kernel function is launched using the
<<<>>>syntax, and the process is simple and efficient.
Prerequisites
- Refer to Environment Deployment first to complete the basic environment setup
- gcc 9.4.0+
- python 3.8+
- torch>=2.6.0
- TorchNPU of the corresponding version
Installation Steps
-
Go to the
examples/fast_kernel_launch_exampledirectory. -
Install Dependencies:
python3 -m pip install -r requirements.txt -
Build the Wheel:
# -n: non-isolated build (uses existing environment) python3 -m build --wheel -nBy default,
dav-2201is used as the NPU compilation architecture. You can specify a supported architecture through theNPU_ARCHenvironment variable:NPU_ARCH=dav-2201 python3 -m build --wheel -nThe currently supported values of
NPU_ARCHaredav-2201anddav-3510.After the build is complete, the artifacts are in the
distfolder in the current directory. The artifact name isascend_ops-1.0.0-${python_version}-abi3-${arch}.whl, where${python_version}indicates the python version in the current environment (python3.8.3 corresponds to cp38), and${arch}indicates the CPU architecture. -
Install the Wheel Package:
python3 -m pip install dist/*.whl --force-reinstall --no-deps -
(Optional) Before building again, it is recommended that you run the following command to clear the compilation cache
python setup.py clean
Quick Start
After the installation is complete, you can use NPU operators in the same way as ordinary PyTorch operations. The invocation of the add operator is used as an example.
import torch
import torch_npu
import ascend_ops # Python package built
# Initialize data on NPU
x = torch.randn(10, 32, dtype=torch.float32).npu()
y = torch.randn(10, 32, dtype=torch.float32).npu()
# Call the custom NPU operator
npu_result = torch.ops.ascend_ops.add(x, y) # PyTorch Custom Operator Dispatch mechanism: torch.ops.<library_name>.<operator_name>
# Verify against CPU ATen implementation
cpu_x = x.cpu()
cpu_y = y.cpu()
cpu_result = cpu_x + cpu_y
assert torch.allclose(cpu_result, npu_result.cpu(), rtol=1e-6)
print("Verification successful!")
Developer Guide: Adding a New Operator
To implement a new operator (such as add), you only need to provide an .asc source file implementation.
-
First, create a folder named after the operator name
addin thecsrcdirectory, and create a subfolder named after the targetNPU_ARCH(for example,dav-2201) in this folder. -
Create a
CMakeLists.txtfile in theNPU_ARCHdirectoryascend_ops_add_current_op(OP_TARGET)Here,
NPU_ARCHis passed to the--npu-archparameter of the compiler. Currently,dav-2201anddav-3510are supported. For how to obtain the value, refer to NpuArch Description and Usage Guide. If the operator requires additional dependencies, you can explicitly add them for${OP_TARGET}in the sameCMakeLists.txt. -
Create an
add.ascfile in theNPU_ARCHdirectory (it is recommended that you use the operator name as the file name). This file contains all modules required for developing an AI Core operator.- Operator schema registration
- Operator Meta Function implementation and registration
- Operator kernel implementation (Ascend C)
- Operator NPU invocation implementation and registration
#include <ATen/Operators.h> #include <torch/all.h> #include <torch/library.h> #include "torch_npu/csrc/core/npu/NPUStream.h" #include "torch_npu/csrc/framework/OpCommand.h" #include "kernel_operator.h" #include "platform/platform_ascendc.h" #include <type_traits> namespace ascend_ops { // The current project is a namespace namespace Add { // It is recommended that each operator have its own independent namespace to prevent global variable pollution /** * Registers the operator schema with the PyTorch framework * The framework knows that this operator exists */ // Register the operator's schema TORCH_LIBRARY_FRAGMENT(EXTENSION_MODULE_NAME, m) { m.def("add(Tensor x, Tensor y) -> Tensor"); } /** * Implements the Meta function of the operator, that is, InferShape+InferDtype * Deduces what the output of this operator looks like and how much space is needed based on the input, without actually computing this operator */ // Meta function implementation of Add torch::Tensor add_meta(const torch::Tensor &x, const torch::Tensor &y) { TORCH_CHECK(x.sizes() == y.sizes(), "The shapes of x and y must be the same."); auto z = torch::empty_like(x); return z; } /** * Registers the Meta function of the operator with the framework * The framework can call this Meta function to know how much space is needed before actually executing the operator computation * Later, graph acceleration such as torch.compile/AutoGrad/AclGraph can be supported */ // Register the Meta implementation TORCH_LIBRARY_IMPL(EXTENSION_MODULE_NAME, Meta, m) { m.impl("add", add_meta); } /** * Implements the kernel of the NPU operator using the AscendC API, written for the current soc */ template <typename T> __global__ __aicore__ void add_kernel(GM_ADDR x, GM_ADDR y, GM_ADDR z, int64_t totalLength, int64_t blockLength, uint32_t tileSize) { // kernel implementation } /** * Implements the operator invocation API * In this API, the invocation of the NPU kernel needs to be completed * 1. Compute the number/shape/dtype of the output tensor (can be implemented by calling the Meta function or implemented directly) * 2. Compute the tiling: determine how to split the computation based on the shape * 3. Invoke the NPU kernel * */ torch::Tensor add_npu(const torch::Tensor &x, const torch::Tensor &y) { // OptionalDeviceGuard ensures that subsequent operations are executed in the correct device context // It records the current device state and automatically restores it after the scoped code is executed const c10::OptionalDeviceGuard guard(x.device()); auto z = add_meta(x, y); auto stream = c10_npu::getCurrentNPUStream().stream(false); int64_t totalLength, numBlocks, blockLength, tileSize; totalLength = x.numel(); std::tie(numBlocks, blockLength, tileSize) = calc_tiling_params(totalLength); auto x_ptr = (GM_ADDR)x.data_ptr(); auto y_ptr = (GM_ADDR)y.data_ptr(); auto z_ptr = (GM_ADDR)z.data_ptr(); auto acl_call = [=]() -> int { AT_DISPATCH_SWITCH( x.scalar_type(), "add_npu", // Calls different NPU kernels based on different data types AT_DISPATCH_CASE(torch::kFloat32, [&] { using scalar_t = float; add_kernel<scalar_t><<<numBlocks, nullptr, stream>>>(x_ptr, y_ptr, z_ptr, totalLength, blockLength, tileSize); }) AT_DISPATCH_CASE(torch::kFloat16, [&] { using scalar_t = half; add_kernel<scalar_t><<<numBlocks, nullptr, stream>>>(x_ptr, y_ptr, z_ptr, totalLength, blockLength, tileSize); }) AT_DISPATCH_CASE(torch::kInt32, [&] { using scalar_t = int32_t; add_kernel<scalar_t><<<numBlocks, nullptr, stream>>>(x_ptr, y_ptr, z_ptr, totalLength, blockLength, tileSize); }) ); return 0; }; // The RunOpApi/RunOpApiV2 APIs must be used for the invocation to ensure that the timing is consistent with that when TorchNPU invokes the aclnn API. at_npu::native::OpCommand::RunOpApi("Add", acl_call); return z; } /** * Registers the invocation function of the operator with the framework, where Device is PrivateUse1 * The framework knows that when the inputs are all on the NPU device, the dispatch goes to this operator implementation */ // Register the NPU implementation TORCH_LIBRARY_IMPL(EXTENSION_MODULE_NAME, PrivateUse1, m) { m.impl("add", add_npu); } } // namespace Add } // namespace ascend_ops -
Rebuild and install the wheel package by referring to the Installation Steps section.
-
For testing the operator API based on pytest, refer to the implementation of test_add.py.