MindSpore自定义算子报错ERROR: No kernel found for [MyCustomOp] in device GPU及解决

1 系统环境

硬件环境(Ascend/GPU/CPU): GPU/CPU
MindSpore版本: MindSpore=2.6.0
执行模式(PyNative/ Graph): 不限
Python版本: Python=3.8
操作系统平台: linux

2 报错信息

2.1 问题描述

按照 MindSpore 的规范,成功实现了一个自定义算子(一个名为MyCustomOp的 element-wise 操作),并且在 CPU 后端上能够正常编译和运行。

2.2 报错信息

然而当尝试切换到 GPU 后端,通过如下设置

context.set_context(device_target="GPU")

并重新编译运行时,程序会报错并退出,错误信息类似于:

ERROR: mindspore/ccsrc/backend/kernel_compiler/gpu/gpu_kernel_factory.cc:56] No kernel found for [MyCustomOp] in device GPU

3 根因分析

这个问题的核心是:MindSpore 的 GPU 后端无法找到为你的自定义算子MyCustomOp编写的 GPU 内核实现。

问题原因分析:这个错误的根本原因在于,MindSpore 的算子执行依赖于 “算子定义” 与 “硬件内核实现” 的绑定。

算子定义 (Operator Definition): 这部分通常是用 Python 编写的,继承自mindspore.ops.Op或mindspore.nn.Cell。它定义了算子的输入输出、属性(Attributes)以及在前端的计算逻辑(通常是构建一个Primitive对象)。这部分你已经完成了,所以 CPU 上能跑。

内核实现 (Kernel Implementation): 这部分是算子在特定硬件(如 CPU、GPU、Ascend)上的具体计算代码。

CPU 内核: 通常用 C++ 编写,遵循 MindSpore 的 CPU 内核接口。MindSpore 的构建系统会自动将这些 C++ 代码编译成动态链接库(.so 文件)。

GPU 内核: 这正是问题所在。 GPU 内核需要用CUDA C++来编写。仅仅有 C++ 代码是不够的,还需要一个专门的构建流程将其编译成CUDA 动态并行线程(PTX)代码或GPU 二进制代码(CUBIN)。

在 GPU 模式下运行时,MindSpore 的 GPU 执行器会根据算子的名称和输入类型,去查找对应的、已经编译好的 GPU 内核。如果找不到,就会抛出 “no kernel found” 的错误。

简单来说,只提供了 “说明书”(算子定义和 CPU 代码),但没有提供 “GPU 专用工具”(CUDA 内核),所以 GPU 不知道该如何执行这个操作。

4 解决方案

要解决这个问题,需要为你的自定义算子提供一个 GPU 内核实现,并将其正确地集成到 MindSpore 的编译系统中。

步骤一:编写 GPU 内核代码 (CUDA C++)

需要创建一个.cu文件(例如my_custom_op_gpu.cu),在其中实现算子的 CUDA 内核。

示例代码 (my_custom_op_gpu.cu):

假设你的MyCustomOp是一个简单的加法操作,输入是两个 Tensor x和y,输出是z = x + y。

#include "mindspore/ccsrc/backend/kernel_compiler/gpu/gpu_kernel.h"

#include "mindspore/ccsrc/backend/kernel_compiler/gpu/gpu_kernel_factory.h"

#include "mindspore/core/utils/ms_context.h"

// 定义CUDA核函数

template <typename T>

__global__ void MyCustomOpKernel(const T* x, const T* y, T* z, const int size) {

    int idx = blockIdx.x * blockDim.x + threadIdx.x;

    if (idx < size) {

        z[idx] = x[idx] + y[idx]; // 这里是你的核心计算逻辑

    }

}

// 实现MindSpore的GPUKernel基类

template <typename T>

class MyCustomOpGpuKernel : public mindspore::kernel::GPUKernel {

public:

    MyCustomOpGpuKernel() = default;

    ~MyCustomOpGpuKernel() override = default;

    bool Launch(const mindspore::kernel::KernelLaunchInfo& launch_info) override {

        // 从launch_info中获取输入输出张量的设备指针

        auto x = launch_info.GetInputAddr<T>(0);

        auto y = launch_info.GetInputAddr<T>(1);

        auto z = launch_info.GetOutputAddr<T>(0);

        // 获取数据大小

        auto size = launch_info.GetInputSize(0) / sizeof(T);

        // 定义CUDA网格和块大小

        dim3 block_size(256);

        dim3 grid_size((size + block_size.x - 1) / block_size.x);

        // 启动CUDA内核

        MyCustomOpKernel<<<grid_size, block_size, 0, stream_>>>(x, y, z, size);

        return true;

    }

    // 其他必要的虚函数实现...

    bool Init(const mindspore::CNodePtr& cnode) override { return true; }

    void InitKernel(const mindspore::CNodePtr& cnode) override {}

    mindspore::kernel::KernelAttr GetKernelAttr() const override {

        return mindspore::kernel::KernelAttr().AddInputAttr(mindspore::kNumberTypeFloat32).AddOutputAttr(mindspore::kNumberTypeFloat32);

    }

};

// 注册GPU内核到MindSpore的内核工厂

MS_REG_GPU_KERNEL(MyCustomOp, MyCustomOpGpuKernel<float>)

步骤二:修改构建脚本 (CMakeLists.txt)

你需要告诉 MindSpore 的构建系统(基于 CMake)去编译这个新的.cu文件。

在你的算子所在的目录下找到CMakeLists.txt文件。

添加你的.cu源文件到 GPU 内核的编译目标中。通常会有一个类似mindspore_add_pkg_kernel或add_library的指令。

示例 CMakeLists.txt 修改:

# ... 其他原有配置 ...

# 定义GPU内核源文件

set(DEVICE_GPU_SRC

    my_custom_op_gpu.cu

    # ... 其他GPU算子的.cu文件

)

# 将源文件添加到名为 "my_ops_gpu" 的库中进行编译

if(MS_BUILD_GPU)

    mindspore_add_pkg_kernel(my_ops_gpu

        SHARED

        ${DEVICE_GPU_SRC}

    )

    target_link_libraries(my_ops_gpu PRIVATE ${CUDA_LIBRARIES} mindspore_gpu_kernel)

endif()

# ... 其他原有配置 ...

具体的 CMake 指令可能因你的项目结构和 MindSpore 版本略有不同,但核心思想是将你的.cu文件加入到 GPU 相关的编译目标中,并链接 CUDA 库。

步骤三:重新编译 MindSpore

完成上述修改后,你需要按照 MindSpore 的编译指南,从源码重新编译 MindSpore。

bash

cd mindspore_root_directory

mkdir build && cd build

cmake .. -DDEVICE_ID=GPU -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_CUDA_COMPILER=nvcc

make -j8

编译成功后,会生成包含你新算子 GPU 内核的动态链接库(例如在build/mindspore/_c_dataengine.so或类似路径下)。

步骤四:验证

将重新编译好的 MindSpore 安装到你的 Python 环境中(pip install output/)。

再次运行你的测试代码,并确保context.set_context(device_target=“GPU”)已设置。

此时,MindSpore 应该能够成功找到并加载你的 GPU 内核,算子可以在 GPU 上正常执行。

总结与扩展排查

检查算子名称: 确保 Python 中定义的算子名称(_name_属性)与 CUDA 代码中MS_REG_GPU_KERNEL宏的第一个参数完全一致。

检查数据类型: 确保你注册的内核支持你在 Python 中使用的数据类型。例如,上面的例子只注册了float类型,如果你的输入是double,同样会找不到内核。你需要为double也注册一个版本:MS_REG_GPU_KERNEL(MyCustomOp, MyCustomOpGpuKernel)。

检查 Context 设置: 确认你的代码在执行前正确设置了device_target为 “GPU”。

查看编译日志: 如果编译失败,仔细查看 CMake 和 make 的输出日志,定位是 CUDA 代码语法错误还是编译配置问题。

参考官方文档和样例: MindSpore 官方文档中有关于自定义 GPU 算子的详细教程和示例,是解决此类问题的最佳参考。

总而言之,“no kernel found” 问题的核心就是缺少对应硬件的内核实现。只要按照规范补全 GPU 内核代码并正确编译,问题就能迎刃而解。