| @@ -0,0 +1,170 @@ | |||
| 1 | +# 自定义算子直调并适配aclgraph | ||
| 2 | + | ||
| 3 | +## 概述 | ||
| 4 | + | ||
| 5 | +本样例展示了如何使用Pybind注册自定义算子,通过<<<>>>内核调用符调用核函数,并适配aclgraph使用该自定义算子,以简单的Add算子和三角函数计算的原地算子为例,实现aclgraph下自定义算子的调用。 | ||
| 6 | + | ||
| 7 | +## 支持的产品 | ||
| 8 | + | ||
| 9 | +- Atlas A3 训练系列产品/Atlas A3 推理系列产品 | ||
| 10 | +- Atlas A2 训练系列产品/Atlas A2 推理系列产品 | ||
| 11 | + | ||
| 12 | +## 目录结构介绍 | ||
| 13 | + | ||
| 14 | +``` | ||
| 15 | +├── README.md // 示例介绍 | ||
| 16 | +├── setup.py // setup文件 | ||
| 17 | +├── csrc | ||
| 18 | +│ ├── add_custom.asc // Add算子实现 & 自定义算子注册 | ||
| 19 | +│ └── trig_inplace_custom.asc // 原地三角函数算子实现 & 自定义算子注册 | ||
| 20 | +├── op_extension | ||
| 21 | +│ ├── __init__.py // python初始化文件 | ||
| 22 | +└── test | ||
| 23 | + ├── add_aclgraph_test.py // Add算子aclgraph测试demo | ||
| 24 | + └── trig_aclgraph_test.py // 原地三角函数aclgraph测试demo | ||
| 25 | +``` | ||
| 26 | + | ||
| 27 | +## 算子描述 | ||
| 28 | +### Add算子 | ||
| 29 | +- 算子功能: | ||
| 30 | + Add算子实现了两个数据相加,返回相加结果的功能。对应的算子原型为: | ||
| 31 | + | ||
| 32 | + ``` | ||
| 33 | + ascendc_add(Tensor x, Tensor y) -> Tensor | ||
| 34 | + ``` | ||
| 35 | +- 算子规格: | ||
| 36 | + | ||
| 37 | + <table> | ||
| 38 | + <tr><td rowspan="1" align="center">核函数名</td><td colspan="4" align="center">add_custom</td></tr> | ||
| 39 | + </tr> | ||
| 40 | + <tr><td rowspan="3" align="center">算子输入</td><td align="center">name</td><td align="center">shape</td><td align="center">data type</td><td align="center">format</td></tr> | ||
| 41 | + <tr><td align="center">x</td><td align="center">8 * 2048</td><td align="center">int</td><td align="center">ND</td></tr> | ||
Y | |||
| 42 | + <tr><td align="center">y</td><td align="center">8 * 2048</td><td align="center">int</td><td align="center">ND</td></tr> | ||
| 43 | + </tr> | ||
| 44 | + </tr> | ||
| 45 | + <tr><td rowspan="1" align="center">算子输出</td><td align="center">z</td><td align="center">8 * 2048</td><td align="center">int</td><td align="center">ND</td></tr> | ||
| 46 | + </tr> | ||
| 47 | + | ||
| 48 | + </table> | ||
| 49 | + | ||
| 50 | +### 原地三角函数算子 | ||
| 51 | +- 算子功能: | ||
| 52 | + 该算子入参为x, out_sin ,out_cos, 算子调用后,out_sin会被原地修改为sin(x)计算结果,out_cos会被原地修改为cos(x)计算结果,返回值tan(x)计算结果。对应的算子原型为: | ||
| 53 | + | ||
| 54 | + ``` | ||
| 55 | + ascendc_trig(Tensor x, Tensor(a!) out_sin, Tensor(b!) out_cos) -> Tensor | ||
| 56 | + ``` | ||
| 57 | +- 算子规格: | ||
| 58 | + | ||
| 59 | + <table> | ||
| 60 | + <tr><td rowspan="1" align="center">核函数名</td><td colspan="4" align="center">trig_inplace_custom</td></tr> | ||
| 61 | + </tr> | ||
| 62 | + <tr><td rowspan="4" align="center">算子输入</td><td align="center">name</td><td align="center">shape</td><td align="center">data type</td><td align="center">format</td></tr> | ||
| 63 | + <tr><td align="center">x</td><td align="center">8 * 2048</td><td align="center">float</td><td align="center">ND</td></tr> | ||
| 64 | + <tr><td align="center">out_sin</td><td align="center">8 * 2048</td><td align="center">float</td><td align="center">ND</td></tr> | ||
| 65 | + <tr><td align="center">out_cos</td><td align="center">8 * 2048</td><td align="center">float</td><td align="center">ND</td></tr> | ||
| 66 | + | ||
| 67 | + </tr> | ||
| 68 | + </tr> | ||
| 69 | + <tr><td rowspan="3" align="center">算子输出</td><td align="center">out_sin</td><td align="center">8 * 2048</td><td align="center">float</td><td align="center">ND</td></tr> | ||
| 70 | + <tr><td align="center">out_cos</td><td align="center">8 * 2048</td><td align="center">float</td><td align="center">ND</td></tr> | ||
| 71 | + <tr><td align="center">out_tan</td><td align="center">8 * 2048</td><td align="center">float</td><td align="center">ND</td></tr> | ||
| 72 | + </tr> | ||
| 73 | + | ||
| 74 | + </table> | ||
| 75 | + | ||
| 76 | +## 代码实现介绍 | ||
| 77 | + | ||
| 78 | +- 以Add算子为例,样例在*.asc文件中定义了一个名为ascendc_ops的命名空间,并在其中注册了ascendc_add函数。在ascendc_add函数中通过`c10_npu::getCurrentNPUStream()`函数获取当前NPU上的流,并通过内核调用符<<<>>>调用自定义的Kernel函数add_custom,在NPU上执行算子。 | ||
| 79 | + ```c++ | ||
| 80 | + add_custom<<<blockDim, nullptr, aclStream>>>(xGm, yGm, zGm, totalLength); | ||
| 81 | + ``` | ||
| 82 | + | ||
| 83 | +- 在pybind11.asc文件中使用了pybind11库来将C++代码封装成Python模块,在Python侧可以通过`import`方式进行调用。例如: | ||
| 84 | + | ||
| 85 | + ```c++ | ||
| 86 | + PYBIND11_MODULE(custom_ops, m) | ||
| 87 | + { | ||
| 88 | + m.def("run_add_custom", &ascendc_ops::run_add_custom, ""); | ||
| 89 | + m.def("run_trig_custom", &ascendc_ops::run_trig_custom, ""); | ||
| 90 | + } | ||
| 91 | + ``` | ||
| 92 | + | ||
| 93 | +- python侧通过`torch.library`将算子逻辑绑定到特定的DispatchKey(PyTorch设备调度标识)。针对NPU设备,需要将算子实现注册到PrivateUse1这一专属的DispatchKey上,例如: | ||
| 94 | + | ||
| 95 | + ```python | ||
| 96 | + ascendc_ops = library.Library("ascendc_ops", "DEF") | ||
| 97 | + | ||
| 98 | + ascendc_ops.define("ascendc_add(Tensor a, Tensor b) -> Tensor") | ||
| 99 | + | ||
| 100 | + @library.impl(ascendc_ops, "ascendc_add", "PrivateUse1") | ||
| 101 | + def add_custom_ops(a, b): | ||
| 102 | + return custom_ops.run_add_custom(a, b) | ||
| 103 | + ``` | ||
| 104 | + | ||
| 105 | +- 注册Meta函数: | ||
| 106 | + 注册Meta函数使faketensor流程正常工作,在使用fx, compile等功能涉及,本示例在add_aclgraph_test.py开头注册代码如下: | ||
| 107 | + | ||
| 108 | + ```python | ||
| 109 | + @library.impl(ascendc_ops, "ascendc_add", "Meta") | ||
| 110 | + def ascendc_add_meta(a, b): | ||
| 111 | + return torch.empty_like(a) | ||
| 112 | + ``` | ||
| 113 | + | ||
| 114 | +- aclgraph的调用: | ||
| 115 | + [示例代码](./test/add_aclgraph_test.py)中,展示了3种aclgraph的使能方式,通过对比NPU输出与CPU标准加法结果来验证自定义算子的数值正确性。 | ||
| 116 | + | ||
| 117 | +1. torch.npu.NPUGraph() | ||
| 118 | +2. torch.npu.make_graphed_callables | ||
| 119 | +3. backend="npugraph_ex" | ||
| 120 | + | ||
| 121 | +## 编译运行 | ||
| 122 | + | ||
| 123 | +在本样例根目录下执行如下步骤,编译并执行算子。 | ||
| 124 | + | ||
| 125 | +- 环境安装 | ||
| 126 | + | ||
| 127 | +1. 请参考与您当前使用的版本配套的[《Ascend Extension for PyTorch | ||
| 128 | + 软件安装指南》](https://www.hiascend.com/document/detail/zh/Pytorch/720/configandinstg/instg/insg_0001.html),获取PyTorch和torch_npu详细的安装步骤。 | ||
| 129 | + | ||
| 130 | + 本样例需torch2.6.0版本以上,支持`backend="npugraph_ex"`需7.3.0版本以上。 | ||
| 131 | +2. 根据实际环境安装CANN toolkit包,安装指导详见《[CANN 软件安装指南](https://www.hiascend.com/document/redirect/CannCommunityInstSoftware)》。 | ||
| 132 | +3. 根据实际环境安装CANN ops包。根据产品型号和环境架构,下载对应安装包,可参考[下载链接](https://ascend.devcloud.huaweicloud.com/cann/run/software/8.5.0-beta.1)并执行如下命令安装: | ||
| 133 | + | ||
| 134 | + ```bash | ||
| 135 | + # 确保安装包具有可执行权限 | ||
| 136 | + chmod +x Ascend-cann-${soc_name}-ops_${cann_version}_linux-${arch}.run | ||
| 137 | + # 安装命令 | ||
| 138 | + ./Ascend-cann-${soc_name}-ops_${cann_version}_linux-${arch}.run --install --quiet --install-path=${install_path} | ||
| 139 | + ``` | ||
| 140 | + | ||
| 141 | + - \$\{soc\_name\}:表示NPU型号名称,即\$\{soc\_version\}删除“ascend”后剩余的内容。 | ||
| 142 | + - \$\{install\_path\}:表示指定安装路径,需要与toolkit包安装在相同路径,默认安装在`/usr/local/Ascend`目录。 | ||
| 143 | + | ||
| 144 | +- 配置环境变量 | ||
| 145 | + | ||
| 146 | + 请根据当前环境上CANN开发套件包的安装位置,执行如下配置环境变量的命令。 | ||
| 147 | + | ||
| 148 | + ```bash | ||
| 149 | + source ${install_path}/ascend-toolkit/set_env.sh | ||
| 150 | + ``` | ||
| 151 | + | ||
| 152 | + | ||
| 153 | + | ||
| 154 | +- 样例执行 | ||
| 155 | + | ||
| 156 | + ```bash | ||
| 157 | + python setup.py bdist_wheel | ||
| 158 | + pip install dist/*.whl --force-reinstall | ||
| 159 | + cd test | ||
| 160 | + python ./add_aclgraph_test.py | ||
| 161 | + ``` | ||
| 162 | + | ||
| 163 | +执行结果如下,说明精度对比成功。 | ||
| 164 | + | ||
| 165 | +```bash | ||
| 166 | +Ran * test in **s. | ||
| 167 | +OK | ||
| 168 | +``` | ||
| 169 | + | ||
| 170 | + | ||
| @@ -0,0 +1,109 @@ | |||
| 1 | +// Copyright (c) 2026 Huawei Technologies Co., Ltd | ||
Y 文件头错误 ![]() ![]() | |||
| 2 | +// All rights reserved. | ||
| 3 | +// | ||
| 4 | +// Licensed under the BSD 3-Clause License (the "License"); | ||
| 5 | +// you may not use this file except in compliance with the License. | ||
| 6 | +// You may obtain a copy of the License at | ||
| 7 | +// | ||
| 8 | +// Unless required by applicable law or agreed to in writing, software | ||
| 9 | +// distributed under the License is distributed on an "AS IS" BASIS, | ||
| 10 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 11 | +// See the License for the specific language governing permissions and | ||
| 12 | +// limitations under the License. | ||
| 13 | +#include <pybind11/pybind11.h> | ||
| 14 | +#include <torch/extension.h> | ||
| 15 | + | ||
| 16 | +#include "torch_npu/csrc/core/npu/NPUStream.h" | ||
| 17 | +#include "kernel_operator.h" | ||
| 18 | + | ||
| 19 | +constexpr uint32_t BUFFER_NUM = 2; //tensor num for each queue | ||
| 20 | +class KernelAdd { | ||
| 21 | +public: | ||
| 22 | + __aicore__ inline KernelAdd() {} | ||
| 23 | + __aicore__ inline void Init(GM_ADDR x, GM_ADDR y, GM_ADDR z, uint32_t totalLength) | ||
| 24 | + { | ||
| 25 | + this->blockLength = totalLength / AscendC::GetBlockNum(); | ||
Y AscendC::GetBlockNum()未判断是否为0,可能导致除0 ![]() ![]() | |||
| 26 | + this->tileNum = 8; | ||
Y 需要说明含义,增加可读性 ![]() ![]() | |||
| 27 | + this->tileLength = this->blockLength / this->tileNum / BUFFER_NUM; | ||
| 28 | + xGm.SetGlobalBuffer((__gm__ int32_t *)x + this->blockLength * AscendC::GetBlockIdx(), this->blockLength); | ||
| 29 | + yGm.SetGlobalBuffer((__gm__ int32_t *)y + this->blockLength * AscendC::GetBlockIdx(), this->blockLength); | ||
| 30 | + zGm.SetGlobalBuffer((__gm__ int32_t *)z + this->blockLength * AscendC::GetBlockIdx(), this->blockLength); | ||
| 31 | + pipe.InitBuffer(inQueueX, BUFFER_NUM, this->tileLength * sizeof(int32_t)); | ||
| 32 | + pipe.InitBuffer(inQueueY, BUFFER_NUM, this->tileLength * sizeof(int32_t)); | ||
| 33 | + pipe.InitBuffer(outQueueZ, BUFFER_NUM, this->tileLength * sizeof(int32_t)); | ||
| 34 | + } | ||
| 35 | + __aicore__ inline void Process() | ||
| 36 | + { | ||
| 37 | + int32_t loopCount = this->tileNum * BUFFER_NUM; | ||
| 38 | + for (int32_t i = 0; i < loopCount; i++) { | ||
| 39 | + CopyIn(i); | ||
| 40 | + Compute(i); | ||
| 41 | + CopyOut(i); | ||
| 42 | + } | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | +private: | ||
| 46 | + __aicore__ inline void CopyIn(int32_t progress) | ||
| 47 | + { | ||
| 48 | + AscendC::LocalTensor<int32_t> xLocal = inQueueX.AllocTensor<int32_t>(); | ||
| 49 | + AscendC::LocalTensor<int32_t> yLocal = inQueueY.AllocTensor<int32_t>(); | ||
| 50 | + AscendC::DataCopy(xLocal, xGm[progress * this->tileLength], this->tileLength); | ||
| 51 | + AscendC::DataCopy(yLocal, yGm[progress * this->tileLength], this->tileLength); | ||
| 52 | + inQueueX.EnQue(xLocal); | ||
| 53 | + inQueueY.EnQue(yLocal); | ||
| 54 | + } | ||
| 55 | + __aicore__ inline void Compute(int32_t progress) | ||
| 56 | + { | ||
| 57 | + AscendC::LocalTensor<int32_t> xLocal = inQueueX.DeQue<int32_t>(); | ||
| 58 | + AscendC::LocalTensor<int32_t> yLocal = inQueueY.DeQue<int32_t>(); | ||
| 59 | + AscendC::LocalTensor<int32_t> zLocal = outQueueZ.AllocTensor<int32_t>(); | ||
| 60 | + AscendC::Add(zLocal, xLocal, yLocal, this->tileLength); | ||
| 61 | + outQueueZ.EnQue<int32_t>(zLocal); | ||
| 62 | + inQueueX.FreeTensor(xLocal); | ||
| 63 | + inQueueY.FreeTensor(yLocal); | ||
| 64 | + } | ||
| 65 | + __aicore__ inline void CopyOut(int32_t progress) | ||
| 66 | + { | ||
| 67 | + AscendC::LocalTensor<int32_t> zLocal = outQueueZ.DeQue<int32_t>(); | ||
| 68 | + AscendC::DataCopy(zGm[progress * this->tileLength], zLocal, this->tileLength); | ||
| 69 | + outQueueZ.FreeTensor(zLocal); | ||
| 70 | + } | ||
| 71 | + | ||
| 72 | +private: | ||
| 73 | + AscendC::TPipe pipe; | ||
| 74 | + AscendC::TQue<AscendC::TPosition::VECIN, BUFFER_NUM> inQueueX, inQueueY; | ||
| 75 | + AscendC::TQue<AscendC::TPosition::VECOUT, BUFFER_NUM> outQueueZ; | ||
| 76 | + AscendC::GlobalTensor<int32_t> xGm; | ||
| 77 | + AscendC::GlobalTensor<int32_t> yGm; | ||
| 78 | + AscendC::GlobalTensor<int32_t> zGm; | ||
| 79 | + uint32_t blockLength; | ||
| 80 | + uint32_t tileNum; | ||
| 81 | + uint32_t tileLength; | ||
| 82 | +}; | ||
| 83 | + | ||
| 84 | +__global__ __aicore__ void add_custom(GM_ADDR x, GM_ADDR y, GM_ADDR z, uint32_t totalLength) | ||
| 85 | +{ | ||
| 86 | + KernelAdd op; | ||
| 87 | + op.Init(x, y, z, totalLength); | ||
| 88 | + op.Process(); | ||
| 89 | +} | ||
| 90 | + | ||
| 91 | +namespace ascendc_ops { | ||
| 92 | +at::Tensor run_add_custom(const at::Tensor &x, const at::Tensor &y) | ||
| 93 | +{ | ||
| 94 | + auto acl_stream = c10_npu::getCurrentNPUStream().stream(true); | ||
Y acl_stream 与getCurrentNPUStream等风格不一致,需要统一为小驼峰或者其他规格 ![]() ![]() | |||
| 95 | + at::Tensor z = at::empty_like(x); | ||
| 96 | + uint32_t blockDim = 8; | ||
Y 需要以dtype相关sizeof计算替换数字,防止功能拓展时散弹式修改 ![]() ![]() | |||
| 97 | + uint32_t totalLength = 1; | ||
| 98 | + for (uint32_t size : x.sizes()) { | ||
| 99 | + totalLength *= size; | ||
| 100 | + } | ||
| 101 | + // Launch the custom kernel use <<<>>> | ||
| 102 | + auto xGm = static_cast<uint8_t *>(const_cast<void *>(x.storage().data())); | ||
| 103 | + auto yGm = static_cast<uint8_t *>(const_cast<void *>(y.storage().data())); | ||
| 104 | + auto zGm = static_cast<uint8_t *>(const_cast<void *>(z.storage().data())); | ||
| 105 | + add_custom<<<blockDim, nullptr, acl_stream>>>(xGm, yGm, zGm, totalLength); | ||
| 106 | + return z; | ||
| 107 | +} | ||
| 108 | + | ||
| 109 | +} // namespace ascendc_ops | ||
| @@ -0,0 +1,14 @@ | |||
| 1 | +#include <pybind11/pybind11.h> | ||
| 2 | +#include <torch/extension.h> | ||
| 3 | + | ||
| 4 | +namespace ascendc_ops { | ||
| 5 | +at::Tensor run_add_custom(const at::Tensor &x, const at::Tensor &y); | ||
Y 缺少空格 ![]() ![]() | |||
| 6 | +at::Tensor run_trig_custom(const at::Tensor &x, const at::Tensor &out_sin, const at::Tensor &out_cos); | ||
| 7 | +} | ||
| 8 | + | ||
| 9 | +// expose Ascend custom ops to Python | ||
| 10 | +PYBIND11_MODULE(custom_ops_lib, m) | ||
| 11 | +{ | ||
| 12 | + m.def("run_add_custom", &ascendc_ops::run_add_custom, ""); | ||
| 13 | + m.def("run_trig_custom", &ascendc_ops::run_trig_custom, ""); | ||
| 14 | +} | ||
| @@ -0,0 +1,126 @@ | |||
| 1 | +// Copyright (c) 2026 Huawei Technologies Co., Ltd | ||
| 2 | +// All rights reserved. | ||
| 3 | +// | ||
| 4 | +// Licensed under the BSD 3-Clause License (the "License"); | ||
Y 文件头错误,需要改为正确内容 ![]() ![]() | |||
| 5 | +// you may not use this file except in compliance with the License. | ||
| 6 | +// You may obtain a copy of the License at | ||
| 7 | +// | ||
| 8 | +// Unless required by applicable law or agreed to in writing, software | ||
| 9 | +// distributed under the License is distributed on an "AS IS" BASIS, | ||
| 10 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 11 | +// See the License for the specific language governing permissions and | ||
| 12 | +// limitations under the License. | ||
| 13 | +#include <pybind11/pybind11.h> | ||
| 14 | +#include <torch/extension.h> | ||
| 15 | + | ||
| 16 | +#include "torch_npu/csrc/core/npu/NPUStream.h" | ||
| 17 | +#include "kernel_operator.h" | ||
| 18 | + | ||
| 19 | +constexpr uint32_t BUFFER_NUM = 2; //tensor num for each queue | ||
| 20 | +class KernelTrig { | ||
| 21 | +public: | ||
| 22 | + __aicore__ inline KernelTrig() {} | ||
| 23 | + | ||
| 24 | + // Initialize the global memory and buffer queues | ||
| 25 | + __aicore__ inline void Init(GM_ADDR x, GM_ADDR out_sin, GM_ADDR out_cos, GM_ADDR out_tan, uint32_t totalLength) | ||
| 26 | + { | ||
| 27 | + this->blockLength = totalLength / AscendC::GetBlockNum(); | ||
Y 【致命】AscendC::GetBlockNum()未校验是否为0,可能导致除0 ![]() ![]() | |||
| 28 | + this->tileNum = 8; | ||
| 29 | + this->tileLength = this->blockLength / this->tileNum / BUFFER_NUM; | ||
Y 【致命】this->tileNum未校验是否为0,可能导致除0 ![]() ![]() | |||
| 30 | + xGm.SetGlobalBuffer((__gm__ float *)x + this->blockLength * AscendC::GetBlockIdx(), this->blockLength); | ||
| 31 | + sinGm.SetGlobalBuffer((__gm__ float *)out_sin + this->blockLength * AscendC::GetBlockIdx(), this->blockLength); | ||
| 32 | + cosGm.SetGlobalBuffer((__gm__ float *)out_cos + this->blockLength * AscendC::GetBlockIdx(), this->blockLength); | ||
| 33 | + tanGm.SetGlobalBuffer((__gm__ float *)out_tan + this->blockLength * AscendC::GetBlockIdx(), this->blockLength); | ||
| 34 | + pipe.InitBuffer(inQueueX, BUFFER_NUM, this->tileLength * sizeof(float)); | ||
| 35 | + pipe.InitBuffer(outQueueSin, BUFFER_NUM, this->tileLength * sizeof(float)); | ||
| 36 | + pipe.InitBuffer(outQueueCos, BUFFER_NUM, this->tileLength * sizeof(float)); | ||
| 37 | + pipe.InitBuffer(outQueueTan, BUFFER_NUM, this->tileLength * sizeof(float)); | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + __aicore__ inline void Process() | ||
| 41 | + { | ||
| 42 | + int32_t loopCount = this->tileNum * BUFFER_NUM; | ||
| 43 | + for (int32_t i = 0; i < loopCount; i++) { | ||
| 44 | + CopyIn(i); | ||
| 45 | + Compute(i); | ||
| 46 | + CopyOut(i); | ||
| 47 | + } | ||
| 48 | + } | ||
| 49 | + | ||
| 50 | +private: | ||
| 51 | + __aicore__ inline void CopyIn(int32_t progress) | ||
| 52 | + { | ||
| 53 | + AscendC::LocalTensor<float> xLocal = inQueueX.AllocTensor<float>(); | ||
| 54 | + AscendC::DataCopy(xLocal, xGm[progress * this->tileLength], this->tileLength); | ||
| 55 | + inQueueX.EnQue(xLocal); | ||
| 56 | + } | ||
| 57 | + __aicore__ inline void Compute(int32_t progress) | ||
| 58 | + { | ||
| 59 | + AscendC::LocalTensor<float> xLocal = inQueueX.DeQue<float>(); | ||
| 60 | + AscendC::LocalTensor<float> sinLocal = outQueueSin.AllocTensor<float>(); | ||
| 61 | + AscendC::LocalTensor<float> cosLocal = outQueueCos.AllocTensor<float>(); | ||
| 62 | + AscendC::LocalTensor<float> tanLocal = outQueueTan.AllocTensor<float>(); | ||
| 63 | + | ||
| 64 | + AscendC::Sin(sinLocal, xLocal, this->tileLength); | ||
| 65 | + AscendC::Cos(cosLocal, xLocal, this->tileLength); | ||
| 66 | + AscendC::Tan(tanLocal, xLocal, this->tileLength); | ||
| 67 | + | ||
| 68 | + outQueueSin.EnQue<float>(sinLocal); | ||
| 69 | + outQueueCos.EnQue<float>(cosLocal); | ||
| 70 | + outQueueTan.EnQue<float>(tanLocal); | ||
| 71 | + inQueueX.FreeTensor(xLocal); | ||
| 72 | + } | ||
| 73 | + __aicore__ inline void CopyOut(int32_t progress) | ||
| 74 | + { | ||
| 75 | + // Copy the sin, cos, and tan values from local memory to global memory (inplace modification) | ||
| 76 | + AscendC::LocalTensor<float> sinLocal = outQueueSin.DeQue<float>(); | ||
| 77 | + AscendC::LocalTensor<float> cosLocal = outQueueCos.DeQue<float>(); | ||
| 78 | + AscendC::LocalTensor<float> tanLocal = outQueueTan.DeQue<float>(); | ||
| 79 | + AscendC::DataCopy(sinGm[progress * this->tileLength], sinLocal, this->tileLength); | ||
| 80 | + AscendC::DataCopy(cosGm[progress * this->tileLength], cosLocal, this->tileLength); | ||
| 81 | + AscendC::DataCopy(tanGm[progress * this->tileLength], tanLocal, this->tileLength); | ||
| 82 | + outQueueSin.FreeTensor(sinLocal); | ||
| 83 | + outQueueCos.FreeTensor(cosLocal); | ||
| 84 | + outQueueTan.FreeTensor(tanLocal); | ||
| 85 | + } | ||
| 86 | + | ||
| 87 | +private: | ||
| 88 | + AscendC::TPipe pipe; | ||
| 89 | + AscendC::TQue<AscendC::TPosition::VECIN, BUFFER_NUM> inQueueX; | ||
| 90 | + AscendC::TQue<AscendC::TPosition::VECOUT, BUFFER_NUM> outQueueSin, outQueueCos, outQueueTan; | ||
| 91 | + AscendC::GlobalTensor<float> xGm; | ||
| 92 | + AscendC::GlobalTensor<float> sinGm; | ||
| 93 | + AscendC::GlobalTensor<float> cosGm; | ||
| 94 | + AscendC::GlobalTensor<float> tanGm; | ||
| 95 | + uint32_t blockLength; | ||
| 96 | + uint32_t tileNum; | ||
| 97 | + uint32_t tileLength; | ||
| 98 | +}; | ||
| 99 | + | ||
| 100 | +__global__ __aicore__ void trig_inplace_custom(GM_ADDR x, GM_ADDR out_sin, GM_ADDR out_cos, GM_ADDR out_tan, | ||
| 101 | + uint32_t totalLength) | ||
| 102 | +{ | ||
| 103 | + KernelTrig op; | ||
| 104 | + op.Init(x, out_sin, out_cos, out_tan, totalLength); | ||
| 105 | + op.Process(); | ||
| 106 | +} | ||
| 107 | + | ||
| 108 | +namespace ascendc_ops { | ||
| 109 | +at::Tensor run_trig_custom(const at::Tensor &x, const at::Tensor &out_sin, const at::Tensor &out_cos) | ||
| 110 | +{ | ||
| 111 | + auto acl_stream = c10_npu::getCurrentNPUStream().stream(true); | ||
| 112 | + at::Tensor out_tan = at::empty_like(x); | ||
| 113 | + uint32_t blockDim = 8; | ||
Y 数字8需要改为宏或者全局变量或加注释,防止功能拓展时散弹式修改 ![]() ![]() | |||
| 114 | + uint32_t totalLength = 1; | ||
| 115 | + for (uint32_t size : x.sizes()) { | ||
| 116 | + totalLength *= size; | ||
| 117 | + } | ||
| 118 | + auto xGm = static_cast<uint8_t *>(const_cast<void *>(x.storage().data())); | ||
| 119 | + auto sinGm = static_cast<uint8_t *>(const_cast<void *>(out_sin.storage().data())); | ||
| 120 | + auto cosGm = static_cast<uint8_t *>(const_cast<void *>(out_cos.storage().data())); | ||
| 121 | + auto tanGm = static_cast<uint8_t *>(const_cast<void *>(out_tan.storage().data())); | ||
| 122 | + // Launch the custom kernel using <<<>>> | ||
| 123 | + trig_inplace_custom<<<blockDim, nullptr, acl_stream>>>(xGm, sinGm, cosGm, tanGm, totalLength); | ||
| 124 | + return out_tan; | ||
| 125 | +} | ||
| 126 | +} // namespace ascendc_ops | ||
| @@ -0,0 +1,3 @@ | |||
| 1 | +from op_extension.custom_ops_lib import run_add_custom, run_trig_custom | ||
| 2 | + | ||
| 3 | +__all__ = ["run_add_custom", "run_trig_custom"] | ||
| @@ -0,0 +1,97 @@ | |||
| 1 | +import os | ||
Y 缺少文件头 ![]() ![]() | |||
| 2 | +import glob | ||
| 3 | +import sysconfig | ||
| 4 | +from distutils.errors import CompileError | ||
| 5 | +from distutils.spawn import find_executable | ||
| 6 | +import torch | ||
| 7 | +import torch_npu | ||
| 8 | +import torch.utils.cpp_extension as cpp_extension | ||
| 9 | +from setuptools import setup, Extension, find_packages | ||
| 10 | +from setuptools.command.build_ext import build_ext | ||
| 11 | + | ||
| 12 | +BASE_DIR = os.path.dirname(os.path.realpath(__file__)) | ||
| 13 | +source_files = glob.glob(os.path.join(BASE_DIR, "csrc", "*.asc"), recursive=True) | ||
| 14 | + | ||
| 15 | + | ||
| 16 | +def get_dependency_paths(): | ||
| 17 | + python_include = sysconfig.get_config_var("INCLUDEPY") | ||
| 18 | + python_lib = sysconfig.get_config_var("LIBDIR") | ||
| 19 | + | ||
| 20 | + torch_include_paths = cpp_extension.include_paths() | ||
| 21 | + torch_lib = os.path.join(os.path.dirname(torch.__file__), "lib") | ||
| 22 | + | ||
| 23 | + torch_npu_path = os.path.dirname(torch_npu.__file__) | ||
| 24 | + torch_npu_include = os.path.join(torch_npu_path, "include") | ||
| 25 | + torch_npu_lib = os.path.join(torch_npu_path, "lib") | ||
| 26 | + | ||
| 27 | + all_include_paths = list([ | ||
| 28 | + *torch_include_paths, | ||
| 29 | + python_include, | ||
| 30 | + torch_npu_include, | ||
| 31 | + ]) | ||
| 32 | + | ||
| 33 | + all_libs = list([ | ||
| 34 | + python_lib, | ||
| 35 | + torch_lib, | ||
| 36 | + torch_npu_lib, | ||
| 37 | + ]) | ||
| 38 | + | ||
| 39 | + return { | ||
| 40 | + "all_includes": all_include_paths, | ||
| 41 | + "all_libs": all_libs | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | + | ||
| 45 | +class AscendBuildExtension(build_ext): | ||
| 46 | + def __init__(self, *args, **kwargs): | ||
| 47 | + super().__init__(*args, **kwargs) | ||
| 48 | + | ||
| 49 | + def _check_bisheng_compiler(self): | ||
| 50 | + bisheng_compiler = find_executable('bisheng') | ||
| 51 | + if not bisheng_compiler: | ||
| 52 | + raise RuntimeError("bisheng command not found!") | ||
| 53 | + | ||
| 54 | + def build_extension(self, ext): | ||
| 55 | + self._check_bisheng_compiler() | ||
| 56 | + dep_paths = get_dependency_paths() | ||
| 57 | + | ||
| 58 | + ext_fullpath = self.get_ext_fullpath(ext.name) | ||
| 59 | + os.makedirs(os.path.dirname(ext_fullpath), exist_ok=True) | ||
| 60 | + | ||
| 61 | + compile_cmd = [ | ||
| 62 | + "bisheng", | ||
| 63 | + "-x", "asc", | ||
| 64 | + "--npu-arch=dav-2201", | ||
| 65 | + "-shared", | ||
| 66 | + "-fPIC", | ||
| 67 | + "-std=c++17", | ||
| 68 | + "-ltorch_npu", "-ltorch", "-lc10", | ||
| 69 | + *ext.sources, | ||
| 70 | + "-o", ext_fullpath, | ||
| 71 | + ] | ||
| 72 | + | ||
| 73 | + for include_dir in dep_paths["all_includes"]: | ||
| 74 | + compile_cmd.append(f"-I{include_dir}") | ||
| 75 | + | ||
| 76 | + for lib_dir in dep_paths["all_libs"]: | ||
| 77 | + compile_cmd.append(f"-L{lib_dir}") | ||
| 78 | + | ||
| 79 | + try: | ||
| 80 | + self.spawn(compile_cmd) | ||
| 81 | + except Exception as e: | ||
| 82 | + raise CompileError(f"{str(e)}") from e | ||
| 83 | + | ||
| 84 | + | ||
| 85 | +your_ext = Extension( | ||
| 86 | + name="op_extension.custom_ops_lib", | ||
| 87 | + sources=source_files, | ||
| 88 | + language="asc", | ||
| 89 | +) | ||
| 90 | + | ||
| 91 | +setup( | ||
| 92 | + name="op_extension", | ||
| 93 | + version="0.1", | ||
| 94 | + ext_modules=[your_ext], | ||
| 95 | + packages=find_packages(), | ||
| 96 | + cmdclass={"build_ext": AscendBuildExtension}, | ||
| 97 | +) | ||
| @@ -0,0 +1,92 @@ | |||
| 1 | +import sys | ||
| 2 | +import os | ||
| 3 | +import torch | ||
| 4 | +import torch_npu | ||
| 5 | +import torch.library as library | ||
| 6 | +from torch_npu.testing.testcase import TestCase, run_tests | ||
| 7 | +import op_extension | ||
| 8 | + | ||
| 9 | +# Define Ascend custom operator library | ||
| 10 | +ascendc_ops = library.Library("ascendc_ops", "DEF") # "DEF" means defining new operators | ||
| 11 | + | ||
| 12 | +# Define a new operator | ||
| 13 | +ascendc_ops.define("ascendc_add(Tensor a, Tensor b) -> Tensor") | ||
| 14 | + | ||
| 15 | + | ||
| 16 | +# Register a meta function | ||
| 17 | + | ||
| 18 | +def ascendc_add_meta(a, b): | ||
| 19 | + return torch.empty_like(a) | ||
| 20 | + | ||
| 21 | + | ||
| 22 | +# Register implementation for the "PrivateUse1" backend | ||
| 23 | + | ||
| 24 | +def add_custom_ops(a, b): | ||
| 25 | + return op_extension.run_add_custom(a, b) | ||
| 26 | + | ||
| 27 | + | ||
| 28 | +# Define a simple model using the custom operation | ||
| 29 | +class Model(torch.nn.Module): | ||
| 30 | + def forward(self, x, y): | ||
| 31 | + return torch.ops.ascendc_ops.ascendc_add(x, y) | ||
| 32 | + | ||
| 33 | + | ||
| 34 | +length = [8, 2048] | ||
Y 需要说明length属于哪个轴,并改用对应宏,避免散弹式修改 ![]() ![]() | |||
| 35 | + | ||
| 36 | + | ||
| 37 | +class TestCustomAdd(TestCase): | ||
| 38 | + | ||
| 39 | + def get_rand_input(self): | ||
| 40 | + x = torch.randint(low=1, high=100, size=length, device='npu', dtype=torch.int) | ||
Y high=100格式有问题,缺少空格,且需要改用宏,避免散弹式修改 ![]() ![]() | |||
| 41 | + y = torch.randint(low=1, high=100, size=length, device='npu', dtype=torch.int) | ||
Y low=1格式有问题,缺少空格,且需要改用宏,避免散弹式修改 ![]() ![]() | |||
| 42 | + return x, y | ||
| 43 | + | ||
| 44 | + # Test using torch.npu.NPUGraph | ||
| 45 | + def test_npugraph(self): | ||
| 46 | + static_x, static_y = self.get_rand_input() | ||
| 47 | + static_target = torch.randint(low=1, high=100, size=length, device='npu:0', dtype=torch.int) | ||
Y high=100格式有问题,缺少空格,且需要改用宏,避免散弹式修改 ![]() ![]() | |||
| 48 | + | ||
| 49 | + g = torch.npu.NPUGraph() | ||
| 50 | + model = Model() | ||
| 51 | + with torch.npu.graph(g): | ||
| 52 | + static_target = model(static_x, static_y) | ||
| 53 | + | ||
| 54 | + real_x, real_y = self.get_rand_input() | ||
| 55 | + static_x.copy_(real_x) | ||
| 56 | + static_y.copy_(real_y) | ||
| 57 | + # replay | ||
| 58 | + g.replay() | ||
| 59 | + cpuout = torch.add(real_x, real_y) | ||
| 60 | + self.assertEqual(static_target, cpuout) | ||
| 61 | + | ||
| 62 | + # Test using make_graphed_callables | ||
| 63 | + def test_make_graphed_callables(self): | ||
| 64 | + model = Model().npu() | ||
| 65 | + x, y = self.get_rand_input() | ||
| 66 | + model = torch.npu.make_graphed_callables(model, (x, y)) | ||
| 67 | + | ||
| 68 | + real_x = torch.randint_like(x, low=1, high=100) | ||
Y high=100格式有问题,缺少空格,且需要改用宏,避免散弹式修改 ![]() ![]() | |||
| 69 | + real_y = torch.randint_like(y, low=1, high=100) | ||
Y 格式有问题,缺少空格,且需要改用宏 ![]() ![]() | |||
| 70 | + output = model(real_x, real_y) | ||
| 71 | + cpuout = torch.add(real_x, real_y) | ||
| 72 | + self.assertEqual(output, cpuout) | ||
| 73 | + | ||
| 74 | + # Test using the npugraph_ex backend for model compilation | ||
| 75 | + def test_npugraph_ex_backend(self): | ||
| 76 | + model = Model().npu() | ||
| 77 | + compiled_model = torch.compile(model, backend="npugraph_ex", fullgraph=True, dynamic=True) | ||
Y fullgraph=True需要修正格式 ![]() ![]() | |||
| 78 | + x, y = self.get_rand_input() | ||
| 79 | + output = compiled_model(x, y) | ||
| 80 | + cpuout = torch.add(x, y) | ||
| 81 | + self.assertEqual(output, cpuout) | ||
| 82 | + | ||
| 83 | + # Test single custom operator call | ||
| 84 | + def test_add_custom_ops(self): | ||
| 85 | + x, y = self.get_rand_input() | ||
| 86 | + output = torch.ops.ascendc_ops.ascendc_add(x.npu(), y.npu()).cpu() | ||
| 87 | + cpuout = torch.add(x, y) | ||
| 88 | + self.assertEqual(output, cpuout) | ||
Y cpuout需要改为cpu_out ![]() ![]() | |||
| 89 | + | ||
| 90 | + | ||
| 91 | +if __name__ == "__main__": | ||
| 92 | + run_tests() | ||
| @@ -0,0 +1,98 @@ | |||
| 1 | +import torch | ||
Y 无文件头,开源需要文件头 ![]() ![]() | |||
| 2 | +import torch_npu | ||
| 3 | +from torch_npu.testing.testcase import TestCase, run_tests | ||
| 4 | +import torch.library as library | ||
| 5 | +import op_extension | ||
| 6 | + | ||
| 7 | +# Define Ascend custom operator library | ||
| 8 | +ascendc_ops = library.Library("ascendc_ops", "DEF") # "DEF" means defining new operators | ||
| 9 | + | ||
| 10 | +# Define a new operator | ||
| 11 | +ascendc_ops.define("ascendc_trig(Tensor x, Tensor(a!) out_sin, Tensor(b!) out_cos) -> Tensor") | ||
| 12 | + | ||
| 13 | + | ||
| 14 | +# Register a meta function | ||
| 15 | + | ||
| 16 | +def ascendc_trig_meta(x, out_sin, out_cos): | ||
| 17 | + return torch.empty_like(x) | ||
| 18 | + | ||
| 19 | + | ||
| 20 | +# Register implementation for the "PrivateUse1" backend | ||
| 21 | + | ||
Y ascendc_trig与PrivateUse1风格不统一 ![]() ![]() | |||
| 22 | +def trig_custom_ops(x, out_sin, out_cos): | ||
| 23 | + return op_extension.run_trig_custom(x, out_sin, out_cos) | ||
| 24 | + | ||
| 25 | + | ||
| 26 | +# Define a simple model using the custom operation | ||
| 27 | +class Model(torch.nn.Module): | ||
| 28 | + def forward(self, x, out_sin, out_cos): | ||
| 29 | + out_tan = torch.ops.ascendc_ops.ascendc_trig(x, out_sin, out_cos) | ||
| 30 | + return out_tan | ||
| 31 | + | ||
| 32 | + | ||
| 33 | +length = [8, 2048] | ||
Y 需要体现是哪个轴的长度 ![]() ![]() | |||
| 34 | + | ||
| 35 | + | ||
| 36 | +class TestCustomTrig(TestCase): | ||
| 37 | + | ||
| 38 | + def get_rand_input(self): | ||
| 39 | + x = torch.rand(length, device='npu', dtype=torch.float32) | ||
| 40 | + out_sin = torch.empty_like(x) | ||
| 41 | + out_cos = torch.empty_like(x) | ||
| 42 | + return x, out_sin, out_cos | ||
| 43 | + | ||
| 44 | + # Test using torch.npu.NPUGraph | ||
| 45 | + def test_npugraph(self): | ||
| 46 | + static_x, static_out_sin, static_out_cos = self.get_rand_input() | ||
| 47 | + static_out_tan = torch.rand(length, device='npu', dtype=torch.float32) | ||
| 48 | + | ||
| 49 | + g = torch.npu.NPUGraph() | ||
| 50 | + model = Model() | ||
| 51 | + with torch.npu.graph(g): | ||
| 52 | + static_out_tan = model(static_x, static_out_sin, static_out_cos) | ||
| 53 | + | ||
| 54 | + real_x, real_out_sin, real_out_cos = self.get_rand_input() | ||
| 55 | + | ||
| 56 | + static_x.copy_(real_x) | ||
| 57 | + static_out_sin.copy_(real_out_sin) | ||
| 58 | + static_out_cos.copy_(real_out_cos) | ||
| 59 | + # replay | ||
| 60 | + g.replay() | ||
| 61 | + self.check_res(real_x, static_out_sin, static_out_cos, static_out_tan) | ||
| 62 | + | ||
| 63 | + # Test using torch.npu.NPUGraph | ||
| 64 | + def test_make_graphed_callables(self): | ||
| 65 | + model = Model().npu() | ||
| 66 | + x, out_sin, out_cos = self.get_rand_input() | ||
| 67 | + model = torch.npu.make_graphed_callables(model, (x, out_sin, out_cos)) | ||
| 68 | + | ||
| 69 | + real_x = torch.rand_like(x) | ||
| 70 | + real_out_tan = model(real_x, out_sin, out_cos) | ||
| 71 | + self.check_res(real_x, out_sin, out_cos, real_out_tan) | ||
| 72 | + | ||
| 73 | + # Test using make_graphed_callables | ||
| 74 | + def test_npugraph_ex_backend(self): | ||
| 75 | + model = Model().npu() | ||
| 76 | + compiled_model = torch.compile(model, backend="npugraph_ex", fullgraph=True, dynamic=True) | ||
Y fullgraph=True的格式需要修正 ![]() ![]() | |||
| 77 | + x, out_sin, out_cos = self.get_rand_input() | ||
| 78 | + out_tan = compiled_model(x, out_sin, out_cos) | ||
| 79 | + self.check_res(x, out_sin, out_cos, out_tan) | ||
| 80 | + | ||
| 81 | + # Test single custom operator call | ||
| 82 | + def test_trig_inplace_ops(self): | ||
| 83 | + x, out_sin, out_cos = self.get_rand_input() | ||
| 84 | + out_tan = torch.ops.ascendc_ops.ascendc_trig(x, out_sin, out_cos) | ||
| 85 | + self.check_res(x, out_sin, out_cos, out_tan) | ||
| 86 | + | ||
| 87 | + # Test using the npugraph_ex backend for model compilation | ||
| 88 | + def check_res(self, x, out_sin, out_cos, out_tan): | ||
| 89 | + cpu_out_sin = torch.sin(x) | ||
| 90 | + cpu_out_cos = torch.cos(x) | ||
| 91 | + cpu_out_tan = torch.tan(x) | ||
| 92 | + self.assertRtolEqual(out_sin, cpu_out_sin) | ||
| 93 | + self.assertRtolEqual(out_cos, cpu_out_cos) | ||
| 94 | + self.assertRtolEqual(out_tan, cpu_out_tan) | ||
| 95 | + | ||
| 96 | + | ||
| 97 | +if __name__ == "__main__": | ||
| 98 | + run_tests() | ||
| @@ -0,0 +1,169 @@ | |||
| 1 | +# 自定义算子直调并适配aclgraph | ||
| 2 | + | ||
| 3 | +## 概述 | ||
| 4 | + | ||
| 5 | +本样例展示了如何使用PyTorch的torch.library注册自定义算子,通过<<<>>>内核调用符调用核函数,并适配aclgraph使用该自定义算子,以简单的Add算子和三角函数计算的原地算子为例,实现aclgraph下自定义算子的调用。 | ||
| 6 | + | ||
| 7 | +## 支持的产品 | ||
| 8 | + | ||
| 9 | +- Atlas A3 训练系列产品/Atlas A3 推理系列产品 | ||
| 10 | +- Atlas A2 训练系列产品/Atlas A2 推理系列产品 | ||
| 11 | + | ||
| 12 | +## 目录结构介绍 | ||
| 13 | + | ||
| 14 | +``` | ||
| 15 | +├── README.md // 示例介绍 | ||
| 16 | +├── setup.py // setup文件 | ||
| 17 | +├── csrc | ||
| 18 | +│ ├── add_custom.asc // Add算子实现 & 自定义算子注册 | ||
| 19 | +│ └── trig_inplace_custom.asc // 原地三角函数算子实现 & 自定义算子注册 | ||
| 20 | +├── op_extension | ||
| 21 | +│ ├── __init__.py // python初始化文件 | ||
| 22 | +│ └── _load.py // 加载模块 | ||
| 23 | +└── test | ||
| 24 | + ├── add_aclgraph_test.py // Add算子aclgraph测试demo | ||
| 25 | + └── trig_aclgraph_test.py // 原地三角函数aclgraph测试demo | ||
| 26 | +``` | ||
| 27 | + | ||
| 28 | +## 算子描述 | ||
| 29 | +### Add算子 | ||
| 30 | +- 算子功能: | ||
| 31 | + Add算子实现了两个数据相加,返回相加结果的功能。对应的算子原型为: | ||
| 32 | + | ||
| 33 | + ``` | ||
| 34 | + ascendc_add(Tensor x, Tensor y) -> Tensor | ||
| 35 | + ``` | ||
| 36 | +- 算子规格: | ||
| 37 | + | ||
| 38 | + <table> | ||
| 39 | + <tr><td rowspan="1" align="center">核函数名</td><td colspan="4" align="center">add_custom</td></tr> | ||
| 40 | + </tr> | ||
| 41 | + <tr><td rowspan="3" align="center">算子输入</td><td align="center">name</td><td align="center">shape</td><td align="center">data type</td><td align="center">format</td></tr> | ||
| 42 | + <tr><td align="center">x</td><td align="center">8 * 2048</td><td align="center">int</td><td align="center">ND</td></tr> | ||
| 43 | + <tr><td align="center">y</td><td align="center">8 * 2048</td><td align="center">int</td><td align="center">ND</td></tr> | ||
| 44 | + </tr> | ||
| 45 | + </tr> | ||
| 46 | + <tr><td rowspan="1" align="center">算子输出</td><td align="center">z</td><td align="center">8 * 2048</td><td align="center">int</td><td align="center">ND</td></tr> | ||
| 47 | + </tr> | ||
| 48 | + | ||
| 49 | + </table> | ||
| 50 | + | ||
| 51 | +### 原地三角函数算子 | ||
| 52 | +- 算子功能: | ||
| 53 | + 该算子入参为x, out_sin ,out_cos, 算子调用后,out_sin会被原地修改为sin(x)计算结果,out_cos会被原地修改为cos(x)计算结果,返回值tan(x)计算结果。对应的算子原型为: | ||
| 54 | + | ||
| 55 | + ``` | ||
| 56 | + ascendc_trig(Tensor x, Tensor(a!) out_sin, Tensor(b!) out_cos) -> Tensor | ||
| 57 | + ``` | ||
| 58 | +- 算子规格: | ||
| 59 | + | ||
| 60 | + <table> | ||
| 61 | + <tr><td rowspan="1" align="center">核函数名</td><td colspan="4" align="center">trig_inplace_custom</td></tr> | ||
| 62 | + </tr> | ||
| 63 | + <tr><td rowspan="4" align="center">算子输入</td><td align="center">name</td><td align="center">shape</td><td align="center">data type</td><td align="center">format</td></tr> | ||
| 64 | + <tr><td align="center">x</td><td align="center">8 * 2048</td><td align="center">float</td><td align="center">ND</td></tr> | ||
| 65 | + <tr><td align="center">out_sin</td><td align="center">8 * 2048</td><td align="center">float</td><td align="center">ND</td></tr> | ||
| 66 | + <tr><td align="center">out_cos</td><td align="center">8 * 2048</td><td align="center">float</td><td align="center">ND</td></tr> | ||
| 67 | + | ||
| 68 | + </tr> | ||
| 69 | + </tr> | ||
| 70 | + <tr><td rowspan="3" align="center">算子输出</td><td align="center">out_sin</td><td align="center">8 * 2048</td><td align="center">float</td><td align="center">ND</td></tr> | ||
| 71 | + <tr><td align="center">out_cos</td><td align="center">8 * 2048</td><td align="center">float</td><td align="center">ND</td></tr> | ||
| 72 | + <tr><td align="center">out_tan</td><td align="center">8 * 2048</td><td align="center">float</td><td align="center">ND</td></tr> | ||
| 73 | + </tr> | ||
| 74 | + | ||
| 75 | + </table> | ||
| 76 | + | ||
| 77 | +## 代码实现介绍 | ||
| 78 | + | ||
| 79 | + - 以Add算子为例,样例在*.asc文件中定义了一个名为ascendc_ops的命名空间,并在其中注册了ascendc_add函数。在ascendc_add函数中通过`c10_npu::getCurrentNPUStream()`函数获取当前NPU上的流,并通过内核调用符<<<>>>调用自定义的Kernel函数add_custom,在NPU上执行算子。 | ||
| 80 | + ```c++ | ||
| 81 | + add_custom<<<blockDim, nullptr, aclStream>>>(xGm, yGm, zGm, totalLength); | ||
| 82 | + ``` | ||
| 83 | + | ||
| 84 | + - PyTorch提供`TORCH_LIBRARY_FRAGMENT`宏作为自定义算子注册的核心接口,用于创建并初始化自定义算子库,注册后在Python侧可以通过`torch.ops.namespace.op_name`方式进行调用,例如: | ||
| 85 | + | ||
| 86 | + ```c++ | ||
| 87 | + TORCH_LIBRARY_FRAGMENT(ascendc_ops, m) | ||
| 88 | + { | ||
| 89 | + m.def(ascendc_add"(Tensor x, Tensor y) -> Tensor"); | ||
| 90 | + } | ||
| 91 | + ``` | ||
| 92 | + | ||
| 93 | + - `TORCH_LIBRARY_IMPL`用于将算子逻辑绑定到特定的DispatchKey(PyTorch设备调度标识)。针对NPU设备,需要将算子实现注册到PrivateUse1这一专属的DispatchKey上,例如: | ||
| 94 | + | ||
| 95 | + ```c++ | ||
| 96 | + TORCH_LIBRARY_IMPL(ascendc_ops, PrivateUse1, m) | ||
| 97 | + { | ||
| 98 | + m.impl("ascendc_add", TORCH_FN(ascendc_ops::ascendc_add)); | ||
| 99 | + } | ||
| 100 | + ``` | ||
| 101 | + | ||
| 102 | +- 注册Meta函数: | ||
| 103 | + | ||
| 104 | + 注册Meta函数使faketensor流程正常工作,在使用fx, compile等功能涉及,注册代码如下: | ||
| 105 | + | ||
| 106 | + ```c++ | ||
| 107 | + TORCH_LIBRARY_IMPL(ascendc_ops, Meta, m) | ||
| 108 | + { | ||
| 109 | + m.impl("ascendc_add", &add_impl_meta); | ||
| 110 | + } | ||
| 111 | + ``` | ||
| 112 | + | ||
| 113 | +- aclgraph的调用: | ||
| 114 | + [示例代码](./test/add_aclgraph_test.py)中,通过`torch.ops.load_library`加载生成的自定义算子库,并展示了3种aclgraph的使能方式,通过对比NPU输出与CPU标准加法结果来验证自定义算子的数值正确性。 | ||
| 115 | + | ||
| 116 | +1. torch.npu.NPUGraph() | ||
| 117 | +2. torch.npu.make_graphed_callables | ||
| 118 | +3. backend="npugraph_ex" | ||
| 119 | + | ||
| 120 | +## 编译运行 | ||
| 121 | + | ||
| 122 | +在本样例根目录下执行如下步骤,编译并执行算子。 | ||
| 123 | + | ||
| 124 | +- 环境安装 | ||
| 125 | + | ||
| 126 | +1. 请参考与您当前使用的版本配套的[《Ascend Extension for PyTorch | ||
| 127 | + 软件安装指南》](https://www.hiascend.com/document/detail/zh/Pytorch/720/configandinstg/instg/insg_0001.html),获取PyTorch和torch_npu详细的安装步骤。 | ||
| 128 | + | ||
| 129 | + 本样例需torch2.6.0版本以上,支持`backend="npugraph_ex"`需7.3.0版本以上。 | ||
| 130 | +2. 根据实际环境安装CANN toolkit包,安装指导详见《[CANN 软件安装指南](https://www.hiascend.com/document/redirect/CannCommunityInstSoftware)》。 | ||
| 131 | +3. 根据实际环境安装CANN ops包。根据产品型号和环境架构,下载对应安装包,可参考[下载链接](https://ascend.devcloud.huaweicloud.com/cann/run/software/8.5.0-beta.1)并执行如下命令安装: | ||
| 132 | + | ||
| 133 | + ```bash | ||
| 134 | + # 确保安装包具有可执行权限 | ||
| 135 | + chmod +x Ascend-cann-${soc_name}-ops_${cann_version}_linux-${arch}.run | ||
| 136 | + # 安装命令 | ||
| 137 | + ./Ascend-cann-${soc_name}-ops_${cann_version}_linux-${arch}.run --install --quiet --install-path=${install_path} | ||
| 138 | + ``` | ||
| 139 | + | ||
| 140 | + - \$\{soc\_name\}:表示NPU型号名称,即\$\{soc\_version\}删除“ascend”后剩余的内容。 | ||
| 141 | + - \$\{install\_path\}:表示指定安装路径,需要与toolkit包安装在相同路径,默认安装在`/usr/local/Ascend`目录。 | ||
| 142 | + | ||
| 143 | +- 配置环境变量 | ||
| 144 | + | ||
| 145 | + 请根据当前环境上CANN开发套件包的安装位置,执行如下配置环境变量的命令。 | ||
| 146 | + | ||
| 147 | + ```bash | ||
| 148 | + source ${install_path}/ascend-toolkit/set_env.sh | ||
| 149 | + ``` | ||
| 150 | + | ||
| 151 | + | ||
| 152 | + | ||
| 153 | +- 样例执行 | ||
| 154 | + | ||
| 155 | + ```bash | ||
| 156 | + python setup.py bdist_wheel | ||
| 157 | + pip install dist/*.whl --force-reinstall | ||
| 158 | + cd test | ||
| 159 | + python ./add_aclgraph_test.py | ||
| 160 | + ``` | ||
| 161 | + | ||
| 162 | +执行结果如下,说明精度对比成功。 | ||
| 163 | + | ||
| 164 | +```bash | ||
| 165 | +Ran * test in **s. | ||
| 166 | +OK | ||
| 167 | +``` | ||
| 168 | + | ||
| 169 | + | ||
| @@ -0,0 +1,129 @@ | |||
| 1 | +// Copyright (c) 2026 Huawei Technologies Co., Ltd | ||
| 2 | +// All rights reserved. | ||
Y 文件头内容需要修正 ![]() ![]() | |||
| 3 | +// | ||
| 4 | +// Licensed under the BSD 3-Clause License (the "License"); | ||
| 5 | +// you may not use this file except in compliance with the License. | ||
| 6 | +// You may obtain a copy of the License at | ||
| 7 | +// | ||
| 8 | +// Unless required by applicable law or agreed to in writing, software | ||
| 9 | +// distributed under the License is distributed on an "AS IS" BASIS, | ||
| 10 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 11 | +// See the License for the specific language governing permissions and | ||
| 12 | +// limitations under the License. | ||
| 13 | + | ||
| 14 | +#include <torch/extension.h> | ||
| 15 | +#include "torch_npu/csrc/core/npu/NPUStream.h" | ||
| 16 | +#include "kernel_operator.h" | ||
| 17 | + | ||
| 18 | +constexpr uint32_t BUFFER_NUM = 2; //tensor num for each queue | ||
| 19 | +class KernelAdd { | ||
| 20 | +public: | ||
| 21 | + __aicore__ inline KernelAdd() {} | ||
| 22 | + __aicore__ inline void Init(GM_ADDR x, GM_ADDR y, GM_ADDR z, uint32_t totalLength) | ||
| 23 | + { | ||
| 24 | + this->blockLength = totalLength / AscendC::GetBlockNum(); | ||
Y 【致命】没有校验除数是否为0,可能导致除0 ![]() ![]() | |||
| 25 | + this->tileNum = 8; | ||
| 26 | + this->tileLength = this->blockLength / this->tileNum / BUFFER_NUM; | ||
Y 【致命】没有校验tileNum是否为0,可能导致除0 ![]() ![]() | |||
| 27 | + xGm.SetGlobalBuffer((__gm__ int32_t *)x + this->blockLength * AscendC::GetBlockIdx(), this->blockLength); | ||
| 28 | + yGm.SetGlobalBuffer((__gm__ int32_t *)y + this->blockLength * AscendC::GetBlockIdx(), this->blockLength); | ||
| 29 | + zGm.SetGlobalBuffer((__gm__ int32_t *)z + this->blockLength * AscendC::GetBlockIdx(), this->blockLength); | ||
| 30 | + pipe.InitBuffer(inQueueX, BUFFER_NUM, this->tileLength * sizeof(int32_t)); | ||
| 31 | + pipe.InitBuffer(inQueueY, BUFFER_NUM, this->tileLength * sizeof(int32_t)); | ||
| 32 | + pipe.InitBuffer(outQueueZ, BUFFER_NUM, this->tileLength * sizeof(int32_t)); | ||
| 33 | + } | ||
| 34 | + __aicore__ inline void Process() | ||
| 35 | + { | ||
| 36 | + int32_t loopCount = this->tileNum * BUFFER_NUM; | ||
| 37 | + for (int32_t i = 0; i < loopCount; i++) { | ||
| 38 | + CopyIn(i); | ||
| 39 | + Compute(i); | ||
| 40 | + CopyOut(i); | ||
| 41 | + } | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | +private: | ||
| 45 | + __aicore__ inline void CopyIn(int32_t progress) | ||
| 46 | + { | ||
| 47 | + AscendC::LocalTensor<int32_t> xLocal = inQueueX.AllocTensor<int32_t>(); | ||
| 48 | + AscendC::LocalTensor<int32_t> yLocal = inQueueY.AllocTensor<int32_t>(); | ||
| 49 | + AscendC::DataCopy(xLocal, xGm[progress * this->tileLength], this->tileLength); | ||
| 50 | + AscendC::DataCopy(yLocal, yGm[progress * this->tileLength], this->tileLength); | ||
| 51 | + inQueueX.EnQue(xLocal); | ||
| 52 | + inQueueY.EnQue(yLocal); | ||
| 53 | + } | ||
| 54 | + __aicore__ inline void Compute(int32_t progress) | ||
| 55 | + { | ||
| 56 | + AscendC::LocalTensor<int32_t> xLocal = inQueueX.DeQue<int32_t>(); | ||
| 57 | + AscendC::LocalTensor<int32_t> yLocal = inQueueY.DeQue<int32_t>(); | ||
| 58 | + AscendC::LocalTensor<int32_t> zLocal = outQueueZ.AllocTensor<int32_t>(); | ||
| 59 | + AscendC::Add(zLocal, xLocal, yLocal, this->tileLength); | ||
| 60 | + outQueueZ.EnQue<int32_t>(zLocal); | ||
| 61 | + inQueueX.FreeTensor(xLocal); | ||
| 62 | + inQueueY.FreeTensor(yLocal); | ||
| 63 | + } | ||
| 64 | + __aicore__ inline void CopyOut(int32_t progress) | ||
| 65 | + { | ||
| 66 | + AscendC::LocalTensor<int32_t> zLocal = outQueueZ.DeQue<int32_t>(); | ||
| 67 | + AscendC::DataCopy(zGm[progress * this->tileLength], zLocal, this->tileLength); | ||
| 68 | + outQueueZ.FreeTensor(zLocal); | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | +private: | ||
| 72 | + AscendC::TPipe pipe; | ||
| 73 | + AscendC::TQue<AscendC::TPosition::VECIN, BUFFER_NUM> inQueueX, inQueueY; | ||
| 74 | + AscendC::TQue<AscendC::TPosition::VECOUT, BUFFER_NUM> outQueueZ; | ||
| 75 | + AscendC::GlobalTensor<int32_t> xGm; | ||
| 76 | + AscendC::GlobalTensor<int32_t> yGm; | ||
| 77 | + AscendC::GlobalTensor<int32_t> zGm; | ||
| 78 | + uint32_t blockLength; | ||
| 79 | + uint32_t tileNum; | ||
| 80 | + uint32_t tileLength; | ||
| 81 | +}; | ||
| 82 | + | ||
| 83 | +__global__ __aicore__ void add_custom(GM_ADDR x, GM_ADDR y, GM_ADDR z, uint32_t totalLength) | ||
| 84 | +{ | ||
| 85 | + KernelAdd op; | ||
| 86 | + op.Init(x, y, z, totalLength); | ||
| 87 | + op.Process(); | ||
| 88 | +} | ||
| 89 | + | ||
| 90 | +namespace ascendc_ops { | ||
| 91 | +at::Tensor ascendc_add(const at::Tensor &x, const at::Tensor &y) | ||
| 92 | +{ | ||
| 93 | + auto acl_stream = c10_npu::getCurrentNPUStream().stream(true); | ||
Y acl_stream 与下面的blockDim 风格不一致 ![]() ![]() | |||
| 94 | + at::Tensor z = at::empty_like(x); | ||
| 95 | + uint32_t blockDim = 8; | ||
Y 需要以dtype相关sizeof计算替换8,防止功能拓展时散弹式修改 ![]() ![]() | |||
| 96 | + uint32_t totalLength = 1; | ||
| 97 | + for (uint32_t size : x.sizes()) { | ||
| 98 | + totalLength *= size; | ||
| 99 | + } | ||
| 100 | + // Launch the custom kernel use <<<>>> | ||
| 101 | + add_custom<<<blockDim, nullptr, acl_stream>>>((uint8_t *)(x.mutable_data_ptr()), (uint8_t *)(y.mutable_data_ptr()), | ||
| 102 | + (uint8_t *)(z.mutable_data_ptr()), totalLength); | ||
| 103 | + return z; | ||
| 104 | +} | ||
| 105 | + | ||
| 106 | +} // namespace ascendc_ops | ||
| 107 | + | ||
| 108 | +at::Tensor add_impl_meta(const at::Tensor& x, const at::Tensor& y) | ||
| 109 | +{ | ||
| 110 | + return at::empty_like(x); | ||
| 111 | +} | ||
| 112 | + | ||
| 113 | +// Define a new operator | ||
| 114 | +TORCH_LIBRARY_FRAGMENT(ascendc_ops, m) | ||
| 115 | +{ | ||
| 116 | + m.def("ascendc_add(Tensor x, Tensor y) -> Tensor"); | ||
| 117 | +} | ||
| 118 | + | ||
| 119 | +// Register implementation for the "PrivateUse1" backend | ||
| 120 | +TORCH_LIBRARY_IMPL(ascendc_ops, PrivateUse1, m) | ||
Y PrivateUse1与ascendc_ops风格不一致 ![]() ![]() | |||
| 121 | +{ | ||
| 122 | + m.impl("ascendc_add", TORCH_FN(ascendc_ops::ascendc_add)); | ||
| 123 | +} | ||
| 124 | + | ||
| 125 | +// Define a simple model using the custom operation | ||
| 126 | +TORCH_LIBRARY_IMPL(ascendc_ops, Meta, m) | ||
Y Meta与ascendc_ops风格不一致 ![]() ![]() | |||
| 127 | +{ | ||
| 128 | + m.impl("ascendc_add", &add_impl_meta); | ||
| 129 | +} | ||
| @@ -0,0 +1,147 @@ | |||
| 1 | +// Copyright (c) 2026 Huawei Technologies Co., Ltd | ||
| 2 | +// All rights reserved. | ||
| 3 | +// | ||
| 4 | +// Licensed under the BSD 3-Clause License (the "License"); | ||
Y 文件头内容需要修正 ![]() ![]() | |||
| 5 | +// you may not use this file except in compliance with the License. | ||
| 6 | +// You may obtain a copy of the License at | ||
| 7 | +// | ||
| 8 | +// Unless required by applicable law or agreed to in writing, software | ||
| 9 | +// distributed under the License is distributed on an "AS IS" BASIS, | ||
| 10 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 11 | +// See the License for the specific language governing permissions and | ||
| 12 | +// limitations under the License. | ||
| 13 | + | ||
| 14 | +#include <torch/extension.h> | ||
| 15 | +#include "torch_npu/csrc/core/npu/NPUStream.h" | ||
| 16 | +#include "kernel_operator.h" | ||
| 17 | + | ||
| 18 | +constexpr uint32_t BUFFER_NUM = 2; //tensor num for each queue | ||
| 19 | +class KernelTrig { | ||
| 20 | +public: | ||
| 21 | + __aicore__ inline KernelTrig() {} | ||
| 22 | + | ||
| 23 | + // Initialize the global memory and buffer queues | ||
| 24 | + __aicore__ inline void Init(GM_ADDR x, GM_ADDR out_sin, GM_ADDR out_cos, GM_ADDR out_tan, uint32_t totalLength) | ||
| 25 | + { | ||
| 26 | + this->blockLength = totalLength / AscendC::GetBlockNum(); | ||
Y 【致命】需要校验GetBlockNum 是否为0,防止除0 ![]() ![]() | |||
| 27 | + this->tileNum = 8; | ||
| 28 | + this->tileLength = this->blockLength / this->tileNum / BUFFER_NUM; | ||
Y 【致命】需要校验tileNum 是否为0,防止除0 ![]() ![]() | |||
| 29 | + xGm.SetGlobalBuffer((__gm__ float *)x + this->blockLength * AscendC::GetBlockIdx(), this->blockLength); | ||
| 30 | + sinGm.SetGlobalBuffer((__gm__ float *)out_sin + this->blockLength * AscendC::GetBlockIdx(), this->blockLength); | ||
| 31 | + cosGm.SetGlobalBuffer((__gm__ float *)out_cos + this->blockLength * AscendC::GetBlockIdx(), this->blockLength); | ||
| 32 | + tanGm.SetGlobalBuffer((__gm__ float *)out_tan + this->blockLength * AscendC::GetBlockIdx(), this->blockLength); | ||
| 33 | + pipe.InitBuffer(inQueueX, BUFFER_NUM, this->tileLength * sizeof(float)); | ||
| 34 | + pipe.InitBuffer(outQueueSin, BUFFER_NUM, this->tileLength * sizeof(float)); | ||
| 35 | + pipe.InitBuffer(outQueueCos, BUFFER_NUM, this->tileLength * sizeof(float)); | ||
| 36 | + pipe.InitBuffer(outQueueTan, BUFFER_NUM, this->tileLength * sizeof(float)); | ||
| 37 | + } | ||
| 38 | + | ||
| 39 | + __aicore__ inline void Process() | ||
| 40 | + { | ||
| 41 | + int32_t loopCount = this->tileNum * BUFFER_NUM; | ||
| 42 | + for (int32_t i = 0; i < loopCount; i++) { | ||
| 43 | + CopyIn(i); | ||
| 44 | + Compute(i); | ||
| 45 | + CopyOut(i); | ||
| 46 | + } | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | +private: | ||
| 50 | + __aicore__ inline void CopyIn(int32_t progress) | ||
| 51 | + { | ||
| 52 | + AscendC::LocalTensor<float> xLocal = inQueueX.AllocTensor<float>(); | ||
| 53 | + AscendC::DataCopy(xLocal, xGm[progress * this->tileLength], this->tileLength); | ||
| 54 | + inQueueX.EnQue(xLocal); | ||
| 55 | + } | ||
| 56 | + __aicore__ inline void Compute(int32_t progress) | ||
| 57 | + { | ||
| 58 | + AscendC::LocalTensor<float> xLocal = inQueueX.DeQue<float>(); | ||
| 59 | + AscendC::LocalTensor<float> sinLocal = outQueueSin.AllocTensor<float>(); | ||
| 60 | + AscendC::LocalTensor<float> cosLocal = outQueueCos.AllocTensor<float>(); | ||
| 61 | + AscendC::LocalTensor<float> tanLocal = outQueueTan.AllocTensor<float>(); | ||
| 62 | + | ||
| 63 | + AscendC::Sin(sinLocal, xLocal, this->tileLength); | ||
| 64 | + AscendC::Cos(cosLocal, xLocal, this->tileLength); | ||
| 65 | + AscendC::Tan(tanLocal, xLocal, this->tileLength); | ||
| 66 | + | ||
| 67 | + outQueueSin.EnQue<float>(sinLocal); | ||
| 68 | + outQueueCos.EnQue<float>(cosLocal); | ||
| 69 | + outQueueTan.EnQue<float>(tanLocal); | ||
| 70 | + inQueueX.FreeTensor(xLocal); | ||
| 71 | + } | ||
| 72 | + __aicore__ inline void CopyOut(int32_t progress) | ||
| 73 | + { | ||
| 74 | + // Copy the sin, cos, and tan values from local memory to global memory (inplace modification) | ||
| 75 | + AscendC::LocalTensor<float> sinLocal = outQueueSin.DeQue<float>(); | ||
| 76 | + AscendC::LocalTensor<float> cosLocal = outQueueCos.DeQue<float>(); | ||
| 77 | + AscendC::LocalTensor<float> tanLocal = outQueueTan.DeQue<float>(); | ||
| 78 | + AscendC::DataCopy(sinGm[progress * this->tileLength], sinLocal, this->tileLength); | ||
| 79 | + AscendC::DataCopy(cosGm[progress * this->tileLength], cosLocal, this->tileLength); | ||
| 80 | + AscendC::DataCopy(tanGm[progress * this->tileLength], tanLocal, this->tileLength); | ||
| 81 | + outQueueSin.FreeTensor(sinLocal); | ||
| 82 | + outQueueCos.FreeTensor(cosLocal); | ||
| 83 | + outQueueTan.FreeTensor(tanLocal); | ||
| 84 | + } | ||
| 85 | + | ||
| 86 | +private: | ||
| 87 | + AscendC::TPipe pipe; | ||
| 88 | + AscendC::TQue<AscendC::TPosition::VECIN, BUFFER_NUM> inQueueX; | ||
| 89 | + AscendC::TQue<AscendC::TPosition::VECOUT, BUFFER_NUM> outQueueSin, outQueueCos, outQueueTan; | ||
| 90 | + AscendC::GlobalTensor<float> xGm; | ||
| 91 | + AscendC::GlobalTensor<float> sinGm; | ||
| 92 | + AscendC::GlobalTensor<float> cosGm; | ||
| 93 | + AscendC::GlobalTensor<float> tanGm; | ||
| 94 | + uint32_t blockLength; | ||
| 95 | + uint32_t tileNum; | ||
| 96 | + uint32_t tileLength; | ||
| 97 | +}; | ||
| 98 | + | ||
| 99 | +__global__ __aicore__ void trig_inplace_custom(GM_ADDR x, GM_ADDR out_sin, GM_ADDR out_cos, GM_ADDR out_tan, | ||
| 100 | + uint32_t totalLength) | ||
| 101 | +{ | ||
| 102 | + KernelTrig op; | ||
| 103 | + op.Init(x, out_sin, out_cos, out_tan, totalLength); | ||
| 104 | + op.Process(); | ||
| 105 | +} | ||
| 106 | + | ||
| 107 | +namespace ascendc_ops { | ||
| 108 | +at::Tensor ascendc_trig(const at::Tensor &x, const at::Tensor &out_sin, const at::Tensor &out_cos) | ||
| 109 | +{ | ||
| 110 | + auto acl_stream = c10_npu::getCurrentNPUStream().stream(true); | ||
| 111 | + at::Tensor out_tan = at::empty_like(x); | ||
| 112 | + uint32_t blockDim = 8; | ||
Y 需要以dtype相关sizeof计算替换8与1,防止功能拓展时散弹式修改 ![]() ![]() | |||
| 113 | + uint32_t totalLength = 1; | ||
| 114 | + for (uint32_t size : x.sizes()) { | ||
| 115 | + totalLength *= size; | ||
| 116 | + } | ||
| 117 | + // Launch the custom kernel using <<<>>> | ||
| 118 | + trig_inplace_custom<<<blockDim, nullptr, acl_stream>>>( | ||
| 119 | + (uint8_t *)(x.mutable_data_ptr()), (uint8_t *)(out_sin.mutable_data_ptr()), | ||
| 120 | + (uint8_t *)(out_cos.mutable_data_ptr()), (uint8_t *)(out_tan.mutable_data_ptr()), totalLength); | ||
| 121 | + return out_tan; | ||
| 122 | +} | ||
| 123 | + | ||
| 124 | +} // namespace ascendc_ops | ||
| 125 | + | ||
| 126 | +at::Tensor trig_impl_meta(const at::Tensor& x, const at::Tensor& out_sin, const at::Tensor& out_cos) | ||
| 127 | +{ | ||
| 128 | + return at::empty_like(x); | ||
| 129 | +} | ||
| 130 | + | ||
| 131 | +// Define a new operator | ||
| 132 | +TORCH_LIBRARY_FRAGMENT(ascendc_ops, m) | ||
| 133 | +{ | ||
| 134 | + m.def("ascendc_trig(Tensor x, Tensor(a!) out_sin, Tensor(b!) out_cos) -> Tensor"); | ||
| 135 | +} | ||
| 136 | + | ||
| 137 | +// Register implementation for the "PrivateUse1" backend | ||
| 138 | +TORCH_LIBRARY_IMPL(ascendc_ops, PrivateUse1, m) | ||
Y PrivateUse1与ascendc_ops风格不一致 ![]() ![]() | |||
| 139 | +{ | ||
| 140 | + m.impl("ascendc_trig", TORCH_FN(ascendc_ops::ascendc_trig)); | ||
| 141 | +} | ||
| 142 | + | ||
| 143 | +// Define a simple model using the custom operation | ||
| 144 | +TORCH_LIBRARY_IMPL(ascendc_ops, Meta, m) | ||
Y Meta与ascendc_ops风格不一致 ![]() ![]() | |||
| 145 | +{ | ||
| 146 | + m.impl("ascendc_trig", &trig_impl_meta); | ||
| 147 | +} | ||
| @@ -0,0 +1,3 @@ | |||
| 1 | +from ._load import _load_opextension_so | ||
Y 缺少文件头,开源后需要文件头 ![]() ![]() | |||
| 2 | + | ||
| 3 | +_load_opextension_so() | ||
| @@ -0,0 +1,14 @@ | |||
| 1 | +import pathlib | ||
| 2 | +import torch | ||
| 3 | + | ||
| 4 | + | ||
| 5 | +# Load the custom operator library | ||
| 6 | +def _load_opextension_so(): | ||
| 7 | + so_dir = pathlib.Path(__file__).parents[0] | ||
| 8 | + so_files = list(so_dir.glob('custom_ops_lib*.so')) | ||
| 9 | + | ||
| 10 | + if not so_files: | ||
| 11 | + raise FileNotFoundError(f"not find custom_ops_lib*.so in {so_dir}") | ||
| 12 | + | ||
| 13 | + atb_so_path = str(so_files[0]) | ||
| 14 | + torch.ops.load_library(atb_so_path) | ||
| @@ -0,0 +1,97 @@ | |||
| 1 | +import os | ||
| 2 | +import glob | ||
| 3 | +import sysconfig | ||
| 4 | +from distutils.errors import CompileError | ||
| 5 | +from distutils.spawn import find_executable | ||
| 6 | +import torch | ||
| 7 | +import torch_npu | ||
| 8 | +import torch.utils.cpp_extension as cpp_extension | ||
| 9 | +from setuptools import setup, Extension, find_packages | ||
| 10 | +from setuptools.command.build_ext import build_ext | ||
| 11 | + | ||
| 12 | +BASE_DIR = os.path.dirname(os.path.realpath(__file__)) | ||
| 13 | +source_files = glob.glob(os.path.join(BASE_DIR, "csrc", "*.asc"), recursive=True) | ||
| 14 | + | ||
| 15 | + | ||
| 16 | +def get_dependency_paths(): | ||
| 17 | + python_include = sysconfig.get_config_var("INCLUDEPY") | ||
| 18 | + python_lib = sysconfig.get_config_var("LIBDIR") | ||
| 19 | + | ||
| 20 | + torch_include_paths = cpp_extension.include_paths() | ||
| 21 | + torch_lib = os.path.join(os.path.dirname(torch.__file__), "lib") | ||
| 22 | + | ||
| 23 | + torch_npu_path = os.path.dirname(torch_npu.__file__) | ||
| 24 | + torch_npu_include = os.path.join(torch_npu_path, "include") | ||
| 25 | + torch_npu_lib = os.path.join(torch_npu_path, "lib") | ||
| 26 | + | ||
| 27 | + all_include_paths = list([ | ||
| 28 | + *torch_include_paths, | ||
| 29 | + python_include, | ||
| 30 | + torch_npu_include, | ||
| 31 | + ]) | ||
| 32 | + | ||
| 33 | + all_libs = list([ | ||
| 34 | + python_lib, | ||
| 35 | + torch_lib, | ||
| 36 | + torch_npu_lib, | ||
| 37 | + ]) | ||
| 38 | + | ||
| 39 | + return { | ||
| 40 | + "all_includes": all_include_paths, | ||
| 41 | + "all_libs": all_libs | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | + | ||
| 45 | +class AscendBuildExtension(build_ext): | ||
| 46 | + def __init__(self, *args, **kwargs): | ||
| 47 | + super().__init__(*args, **kwargs) | ||
| 48 | + | ||
| 49 | + def _check_bisheng_compiler(self): | ||
| 50 | + bisheng_compiler = find_executable('bisheng') | ||
| 51 | + if not bisheng_compiler: | ||
| 52 | + raise RuntimeError("bisheng command not found!") | ||
| 53 | + | ||
| 54 | + def build_extension(self, ext): | ||
| 55 | + self._check_bisheng_compiler() | ||
| 56 | + dep_paths = get_dependency_paths() | ||
| 57 | + | ||
| 58 | + ext_fullpath = self.get_ext_fullpath(ext.name) | ||
| 59 | + os.makedirs(os.path.dirname(ext_fullpath), exist_ok=True) | ||
| 60 | + | ||
| 61 | + compile_cmd = [ | ||
| 62 | + "bisheng", | ||
| 63 | + "-x", "asc", | ||
| 64 | + "--npu-arch=dav-2201", | ||
| 65 | + "-shared", | ||
| 66 | + "-fPIC", | ||
| 67 | + "-std=c++17", | ||
| 68 | + "-ltorch_npu", "-ltorch", "-lc10", | ||
| 69 | + *ext.sources, | ||
| 70 | + "-o", ext_fullpath, | ||
| 71 | + ] | ||
| 72 | + | ||
| 73 | + for include_dir in dep_paths["all_includes"]: | ||
| 74 | + compile_cmd.append(f"-I{include_dir}") | ||
| 75 | + | ||
| 76 | + for lib_dir in dep_paths["all_libs"]: | ||
| 77 | + compile_cmd.append(f"-L{lib_dir}") | ||
| 78 | + | ||
| 79 | + try: | ||
| 80 | + self.spawn(compile_cmd) | ||
| 81 | + except Exception as e: | ||
| 82 | + raise CompileError(f"{str(e)}") from e | ||
| 83 | + | ||
| 84 | + | ||
| 85 | +your_ext = Extension( | ||
| 86 | + name="op_extension.custom_ops_lib", | ||
| 87 | + sources=source_files, | ||
| 88 | + language="asc", | ||
| 89 | +) | ||
| 90 | + | ||
| 91 | +setup( | ||
| 92 | + name="op_extension", | ||
| 93 | + version="0.1", | ||
| 94 | + ext_modules=[your_ext], | ||
| 95 | + packages=find_packages(), | ||
| 96 | + cmdclass={"build_ext": AscendBuildExtension}, | ||
| 97 | +) | ||
| @@ -0,0 +1,73 @@ | |||
| 1 | +import torch | ||
Y 缺少文件头 ![]() ![]() | |||
| 2 | +import torch_npu | ||
| 3 | +import op_extension | ||
| 4 | +from torch_npu.testing.testcase import TestCase, run_tests | ||
| 5 | +from torch.library import Library | ||
| 6 | + | ||
| 7 | + | ||
| 8 | +# Define a simple model using the custom operation | ||
| 9 | +class Model(torch.nn.Module): | ||
| 10 | + def forward(self, x, y): | ||
| 11 | + return torch.ops.ascendc_ops.ascendc_add(x, y) | ||
| 12 | + | ||
| 13 | + | ||
| 14 | +length = [8, 2048] | ||
Y 数字8需要改为宏或者全局变量或加注释,防止功能拓展时散弹式修改 ![]() ![]() | |||
| 15 | + | ||
| 16 | + | ||
| 17 | +class TestCustomAdd(TestCase): | ||
| 18 | + | ||
| 19 | + def get_rand_input(self): | ||
| 20 | + x = torch.randint(low=1, high=100, size=length, device='npu', dtype=torch.int) | ||
Y high=100格式有问题,缺少空格,且需要改用宏,避免散弹式修改 ![]() ![]() | |||
| 21 | + y = torch.randint(low=1, high=100, size=length, device='npu', dtype=torch.int) | ||
Y low=1格式有问题,缺少空格,且需要改用宏,避免散弹式修改 ![]() ![]() | |||
| 22 | + return x, y | ||
| 23 | + | ||
| 24 | + # Test using torch.npu.NPUGraph | ||
| 25 | + def test_npugraph(self): | ||
| 26 | + static_x, static_y = self.get_rand_input() | ||
| 27 | + static_target = torch.randint(low=1, high=100, size=length, device='npu:0', dtype=torch.int) | ||
Y high=100格式有问题,缺少空格,且需要改用宏,避免散弹式修改 ![]() ![]() | |||
| 28 | + | ||
| 29 | + g = torch.npu.NPUGraph() | ||
| 30 | + model = Model() | ||
| 31 | + with torch.npu.graph(g): | ||
| 32 | + static_target = model(static_x, static_y) | ||
| 33 | + | ||
| 34 | + real_x, real_y = self.get_rand_input() | ||
| 35 | + static_x.copy_(real_x) | ||
| 36 | + static_y.copy_(real_y) | ||
| 37 | + # replay | ||
| 38 | + g.replay() | ||
| 39 | + cpuout = torch.add(real_x, real_y) | ||
| 40 | + self.assertEqual(static_target, cpuout) | ||
| 41 | + | ||
| 42 | + # Test using make_graphed_callables | ||
| 43 | + def test_make_graphed_callables(self): | ||
| 44 | + model = Model().npu() | ||
| 45 | + x, y = self.get_rand_input() | ||
| 46 | + model = torch.npu.make_graphed_callables(model, (x, y)) | ||
| 47 | + | ||
| 48 | + real_x = torch.randint_like(x, low=1, high=100) | ||
Y low=1格式有问题,缺少空格,且需要改用宏,避免散弹式修改 ![]() ![]() | |||
| 49 | + real_y = torch.randint_like(y, low=1, high=100) | ||
| 50 | + output = model(real_x, real_y) | ||
| 51 | + cpuout = torch.add(real_x, real_y) | ||
| 52 | + self.assertEqual(output, cpuout) | ||
Y cpuout需要改成cpu_out ![]() ![]() | |||
| 53 | + | ||
| 54 | + # Test using the npugraph_ex backend for model compilation | ||
| 55 | + def test_npugraph_ex_backend(self): | ||
| 56 | + model = Model().npu() | ||
| 57 | + compiled_model = torch.compile(model, backend="npugraph_ex", fullgraph=True, dynamic=True) | ||
| 58 | + x, y = self.get_rand_input() | ||
| 59 | + output = compiled_model(x, y) | ||
| 60 | + cpuout = torch.add(x, y) | ||
| 61 | + self.assertEqual(output, cpuout) | ||
| 62 | + | ||
| 63 | + # Test single custom operator call | ||
| 64 | + def test_add_custom_ops(self): | ||
| 65 | + x, y = self.get_rand_input() | ||
| 66 | + output = torch.ops.ascendc_ops.ascendc_add(x.npu(), y.npu()).cpu() | ||
| 67 | + cpuout = torch.add(x, y) | ||
| 68 | + self.assertEqual(output, cpuout) | ||
| 69 | + | ||
| 70 | + | ||
| 71 | + | ||
| 72 | +if __name__ == "__main__": | ||
| 73 | + run_tests() | ||
| @@ -0,0 +1,79 @@ | |||
| 1 | +import torch | ||
| 2 | +import torch_npu | ||
Y 缺少文件头,开源后需要文件头 ![]() ![]() | |||
| 3 | +import op_extension | ||
| 4 | +from torch_npu.testing.testcase import TestCase, run_tests | ||
| 5 | + | ||
| 6 | + | ||
| 7 | +# Define a simple model using the custom operation | ||
| 8 | +class Model(torch.nn.Module): | ||
| 9 | + def forward(self, x, out_sin, out_cos): | ||
| 10 | + out_tan = torch.ops.ascendc_ops.ascendc_trig(x, out_sin, out_cos) | ||
| 11 | + return out_tan | ||
| 12 | + | ||
| 13 | + | ||
| 14 | +length = [8, 2048] | ||
Y 需要以dtype相关sizeof计算替换2048,防止功能拓展时散弹式修改 ![]() ![]() | |||
| 15 | + | ||
| 16 | + | ||
| 17 | +class TestCustomTrig(TestCase): | ||
| 18 | + | ||
| 19 | + def get_rand_input(self): | ||
| 20 | + x = torch.rand(length, device='npu', dtype=torch.float32) | ||
| 21 | + out_sin = torch.empty_like(x) | ||
| 22 | + out_cos = torch.empty_like(x) | ||
| 23 | + return x, out_sin, out_cos | ||
| 24 | + | ||
| 25 | + # Test using torch.npu.NPUGraph | ||
| 26 | + def test_npugraph(self): | ||
| 27 | + static_x, static_out_sin, static_out_cos = self.get_rand_input() | ||
| 28 | + static_out_tan = torch.rand(length, device='npu', dtype=torch.float32) | ||
| 29 | + | ||
| 30 | + g = torch.npu.NPUGraph() | ||
| 31 | + model = Model() | ||
| 32 | + with torch.npu.graph(g): | ||
| 33 | + static_out_tan = model(static_x, static_out_sin, static_out_cos) | ||
| 34 | + | ||
| 35 | + real_x, real_out_sin, real_out_cos = self.get_rand_input() | ||
| 36 | + | ||
| 37 | + static_x.copy_(real_x) | ||
| 38 | + static_out_sin.copy_(real_out_sin) | ||
| 39 | + static_out_cos.copy_(real_out_cos) | ||
| 40 | + # replay | ||
| 41 | + g.replay() | ||
| 42 | + self.check_res(real_x, static_out_sin, static_out_cos, static_out_tan) | ||
| 43 | + | ||
| 44 | + # Test using torch.npu.NPUGraph | ||
| 45 | + def test_make_graphed_callables(self): | ||
| 46 | + model = Model().npu() | ||
| 47 | + x, out_sin, out_cos = self.get_rand_input() | ||
| 48 | + model = torch.npu.make_graphed_callables(model, (x, out_sin, out_cos)) | ||
| 49 | + | ||
| 50 | + real_x = torch.rand_like(x) | ||
| 51 | + real_out_tan = model(real_x, out_sin, out_cos) | ||
| 52 | + self.check_res(real_x, out_sin, out_cos, real_out_tan) | ||
| 53 | + | ||
| 54 | + # Test using make_graphed_callables | ||
| 55 | + def test_npugraph_ex_backend(self): | ||
| 56 | + model = Model().npu() | ||
| 57 | + compiled_model = torch.compile(model, backend="npugraph_ex", fullgraph=True, dynamic=True) | ||
Y fullgrap需要与out_tan风格一致 ![]() ![]() | |||
| 58 | + x, out_sin, out_cos = self.get_rand_input() | ||
| 59 | + out_tan = compiled_model(x, out_sin, out_cos) | ||
| 60 | + self.check_res(x, out_sin, out_cos, out_tan) | ||
| 61 | + | ||
| 62 | + # Test single custom operator call | ||
| 63 | + def test_trig_inplace_ops(self): | ||
| 64 | + x, out_sin, out_cos = self.get_rand_input() | ||
| 65 | + out_tan = torch.ops.ascendc_ops.ascendc_trig(x, out_sin, out_cos) | ||
| 66 | + self.check_res(x, out_sin, out_cos, out_tan) | ||
| 67 | + | ||
| 68 | + # Test using the npugraph_ex backend for model compilation | ||
| 69 | + def check_res(self, x, out_sin, out_cos, out_tan): | ||
| 70 | + cpu_out_sin = torch.sin(x) | ||
| 71 | + cpu_out_cos = torch.cos(x) | ||
| 72 | + cpu_out_tan = torch.tan(x) | ||
| 73 | + self.assertRtolEqual(out_sin, cpu_out_sin) | ||
| 74 | + self.assertRtolEqual(out_cos, cpu_out_cos) | ||
| 75 | + self.assertRtolEqual(out_tan, cpu_out_tan) | ||
| 76 | + | ||
| 77 | + | ||
| 78 | +if __name__ == "__main__": | ||
| 79 | + run_tests() | ||


需要注释说明2048的含义,增加可读性