通过PyTorch框架进行模型的训练、推理时,会调用很多算子进行计算。开发者开发的自定义算子如果需要集成部署到PyTorch框架,有如下几种方式:

本节主要提供通过torch.library与Pybind注册自定义算子并实现PyTorch框架调用算子Kernel程序的指导。
Pybind适用于快速将C++函数暴露给Python,实现高效接口绑定。但其生成的算子无法被PyTorch的算子系统识别,不具备schema定义与图追踪能力,因此不支持torch.compile优化。相比之下,torch.library提供了与PyTorch核心算子系统深度集成的机制,支持算子注册、schema定义和图追踪能力,是支持torch.compile的必要条件。开发者可根据需求选择对应方式。
下面代码以add_custom(Add自定义算子为例)算子为例,介绍通过torch.library如何调用算子Kernel程序,文档中仅介绍核心步骤,完整样例请参考torch.library样例。
包括算子Kernel实现,并使用<<<>>>接口调用算子核函数完成指定的运算。样例中的c10_npu::getCurrentNPUStream接口用于获取当前npu流,返回值类型NPUStream,使用方式请参考《Ascend Extension for PyTorch 自定义API参考》中的“(beta)c10_npu::getCurrentNPUStream”章节。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | namespace ascendc_ops { at::Tensor ascendc_add(const at::Tensor& x, const at::Tensor& y) { // 运行资源申请,通过c10_npu::getCurrentNPUStream()的函数获取当前NPU上的流 auto aclStream = c10_npu::getCurrentNPUStream().stream(false); // 分配Device侧输出内存 at::Tensor z = at::empty_like(x); uint32_t numBlocks = 8; uint32_t totalLength = 1; for (uint32_t size : x.sizes()) { totalLength *= size; } // 用<<<>>>接口调用核函数完成指定的运算 add_custom<<<numBlocks, nullptr, aclStream>>>((uint8_t*)(x.mutable_data_ptr()), (uint8_t*)(y.mutable_data_ptr()), (uint8_t*)(z.mutable_data_ptr()), totalLength); // 将Device上的运算结果拷贝回Host并释放申请的资源 return z; } } // namespace ascendc_ops |
1 2 3 4 5 6 7 8 9 10 11 | // 注册算子到torch.library TORCH_LIBRARY(ascendc_ops, m) { m.def("ascendc_add(Tensor x, Tensor y) -> Tensor"); } // 注册PrivateUse1实现,NPU设备 TORCH_LIBRARY_IMPL(ascendc_ops, PrivateUse1, m) { m.impl("ascendc_add", TORCH_FN(ascendc_ops::ascendc_add)); } |
在add_custom_test.py中,首先通过torch.ops.load_library加载生成的自定义算子库,调用注册的ascendc_add函数,并通过对比NPU输出与CPU标准加法结果来验证自定义算子的数值正确性。
下面代码以add_custom算子为例,介绍通过Pybind方式实现Pytorch脚本中调用自定义算子的流程。文档中仅介绍核心步骤,完整样例请参考Pybind样例。
包括算子Kernel实现,并使用<<<>>>接口调用算子核函数完成指定的运算。样例中的c10_npu::getCurrentNPUStream接口用于获取当前npu流,返回值类型NPUStream,使用方式请参考《Ascend Extension for PyTorch 自定义API参考》中的“(beta)c10_npu::getCurrentNPUStream”章节。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | // Pybind和PyTorch调用所需的头文件 #include <pybind11/pybind11.h> #include <torch/extension.h> #include "torch_npu/csrc/core/npu/NPUStream.h" // Kernel侧实现需要的头文件 #include "kernel_operator.h" ... namespace ascendc_ops { at::Tensor ascendc_add(const at::Tensor& x, const at::Tensor& y) { // 运行资源申请,通过c10_npu::getCurrentNPUStream()的函数获取当前NPU上的流 auto aclStream = c10_npu::getCurrentNPUStream().stream(false); // 分配Device侧输出内存 at::Tensor z = at::empty_like(x); uint32_t numBlocks = 8; uint32_t totalLength = 1; for (uint32_t size : x.sizes()) { totalLength *= size; } // 用<<<>>>接口调用核函数完成指定的运算 add_custom<<<numBlocks, nullptr, aclStream>>>((uint8_t*)(x.mutable_data_ptr()), (uint8_t*)(y.mutable_data_ptr()), (uint8_t*)(z.mutable_data_ptr()), totalLength); // 将Device上的运算结果拷贝回Host并释放申请的资源 return z; } } // namespace ascendc_ops |
1 2 3 4 5 | PYBIND11_MODULE(ascendc_ops, m)// 模块名ascendc_ops,模块对象m { m.doc() = "add_custom pybind11 interfaces";// optional module docstring m.def("ascendc_add", &ascendc_ops::ascendc_add, "");// 将函数ascendc_add与Pybind模块进行绑定 } |