MindSpore导入CUDA算子的解决方案

 更新时间:2024年05月09日 09:24:37   作者:DECHIN  
本文介绍了在MindSpore标准格式下进行CUDA算子开发的方法和流程,可以让开发者在现有的AI框架下仍然可以调用基于CUDA实现的高性能的算子,感兴趣的朋友跟随小编一起看看吧

本文介绍了在MindSpore标准格式下进行CUDA算子开发的方法和流程,可以让开发者在现有的AI框架下仍然可以调用基于CUDA实现的高性能的算子。并且,除了常规的数值计算之外,在MindSpore框架下,我们还可以通过实现一个bprop函数,使得我们手写的这个CUDA算子也可以使用MindSpore框架本身自带的自动微分-端到端微分技术。

技术背景

当今众多的基于Python的AI框架(如MindSpore、PyTorch等)给了开发者非常便利的编程的条件,我们可以用Python的简单的语法写代码,然后由框架在后端自动编译成可以在GPU上高效计算的程序。而对于一些定制化比较高的算法,MindSpore也支持了相关的接口,允许开发者自己开发相应的CUDA算子(需要统一接口),然后编译成.so动态链接库,再用MindSpore内置的函数加载为本地算子。本文针对这种方案写一个简单的示例。

程序结构

本地自己手写一个CUDA算子,一般至少需要两个文件和一个nvcc的环境,最好是在安装完成MindSpore的GPU版本之后,再尝试CUDA算子的引入。具体MindSpore的安装方法,可以参考MindSpore官网,这里不做赘述。我这里使用的环境是10.1版本的nvcc:

$ nvcc -V
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2019 NVIDIA Corporation
Built on Sun_Jul_28_19:07:16_PDT_2019
Cuda compilation tools, release 10.1, V10.1.243
$ python3 -m pip show mindspore
Name: mindspore
Version: 2.1.0
Summary: MindSpore is a new open source deep learning training/inference framework that could be used for mobile, edge and cloud scenarios.
Home-page: https://www.mindspore.cn
Author: The MindSpore Authors
Author-email: contact@mindspore.cn
License: Apache 2.0
Location: /home/dechin/anaconda3/envs/mindspore-latest/lib/python3.7/site-packages
Requires: numpy, protobuf, pillow, astunparse, scipy, psutil, asttokens, packaging
Required-by: 

需要准备的两个文件,一个是CUDA算子本身的.cu文件,另一个是用来调用CUDA算子的.py文件。操作流程是:先按照自己的需求写好CUDA算子,然后用nvcc进行编译,编译输出为.so的动态链接库,然后在python脚本中使用mindspore.ops.Custom生成相应的算子。在MindSpore2.1之后的版本中,对于本地CUDA算子的调用定义了统一的接口,其格式为:

extern "C" int CustomFunc(int nparam, void **params, int *ndims, int64_t **shapes, const char **dtypes, void *stream, void *extra);

具体方法可以参考官网的这一篇文档说明。这样的话,在.cu文件中至少有两个函数,一个是原本用于计算的Kernel函数,另一个是用于统一标准接口的CustomFunc函数。需要说明的是,旧版本的MindSpore是没有这样的规范的,所以旧版本的算子没有CustomFunc函数也能够用nvcc编译,但是无法在新版本的MindSpore中调用。

一维张量求和

我们用一个一维的张量求和的示例来演示一下如何在本地写一个可以用MindSpore来调用的CUDA算子,一维张量求和的算法是比较简单的:

那么对应的CUDA算子的代码如下所示:

// custom_add.cu
// nvcc --shared -Xcompiler -fPIC -o custom_add.so custom_add.cu
// 常量,一般可以放在.cuh头文件中
constexpr int THREADS = 1024;
// 用于CUDA计算的Kernel函数
__global__ void CustomAddKernel(float *input1, float *input2, float *output, size_t size) {
    auto idx = blockIdx.x * THREADS + threadIdx.x;
    if (idx < size) {
        // 逐元素操作,CUDA算子的基本写法
        output[idx] = input1[idx] + input2[idx];
    }
}
// 标准算子接口
extern "C" int CustomAdd(int nparam, void **params, int *ndims, int64_t **shapes, const char **dtypes, void *stream,
                         void *extra) {
    cudaStream_t custream = static_cast<cudaStream_t>(stream);
    // 输出变量的位置
    constexpr int OUTPUT_INDEX = 2;
    // 传入的参数都是指针形式
    float *input1 = static_cast<float *>(params[0]);
    float *input2 = static_cast<float *>(params[1]);
    float *output = static_cast<float *>(params[2]);
    // 获取输入张量的大小
    int size = shapes[OUTPUT_INDEX][0];
    // GPU运算中的block和thread,一般要求block*thread大于或者等于size即可
    int blocks = ceil(size / THREADS) + 1;
    // 调用Kernel函数
    CustomAddKernel<<<blocks, THREADS, 0, custream>>>(input1, input2, output, size);
    return 0;
}

值得注意的是,上述CustomAdd函数中的params有3个输入,但是实际上其中一个是返回值,这也是MindSpore对于标准接口的设定,不需要我们额外传入一个变量。保存好上述的CUDA算子代码之后,可以用如下指令直接编译成python可以调用的动态链接库:

$ nvcc --shared -Xcompiler -fPIC -o custom_add.so custom_add.cu

编译完成后,会在当前目录下生成一个新的.so文件,然后就可以在python代码中进行调用:

# test_custom_ops.py
# python3 test_custom_ops.py
import mindspore as ms
from mindspore import ops, Tensor, context
ms.set_context(device_target="GPU", mode=context.GRAPH_MODE)
t1 = Tensor([1., 2., 3.], ms.float32)
t2 = Tensor([3., 2., 1.], ms.float32)
CustomAdd = ops.Custom("./custom_add.so:CustomAdd",
                       out_shape=[t1.shape[0]],
                       out_dtype=ms.float32,
                       func_type="aot"
                       )
res = CustomAdd(t1, t2)
ops.print_(res)

上述的CustomAdd就是我们导入的基于CUDA算子来写的本地MindSpore算子,并且这种算子还可以使用MindSpore进行注册,这样就不需要每次使用都去加载这个动态链接库,感兴趣的童鞋可以自己研究一下算子注册的方法。上述Python代码的运行结果如下:

$ python3 test_custom_ops.py 
Tensor(shape=[3], dtype=Float32, value= [ 4.00000000e+00,  4.00000000e+00,  4.00000000e+00])

可见跟我们预期的结果是一致的,那么就完成了一个本地CUDA算子的实现和调用。

自定义算子反向传播

在前面的章节里面我们已经实现了一个本地的一维张量求和算子,但是这还不是一个完整的算子实现,因为我们在AI框架中一般要求算子可自动微分,光实现一个算子是不完整的,因此这里我们再通过bprop函数,来实现一下自定义算子的反向传播:

# test_custom_ops.py
# python3 test_custom_ops.py
import mindspore as ms
from mindspore import ops, Tensor, context
from mindspore import nn, grad
ms.set_context(device_target="GPU", mode=context.GRAPH_MODE)
t1 = Tensor([1., 2., 3.], ms.float32)
t2 = Tensor([3., 2., 1.], ms.float32)
CustomAdd = ops.Custom("./custom_add.so:CustomAdd",
                       out_shape=[t1.shape[0]],
                       out_dtype=ms.float32,
                       func_type="aot"
                       )
为了自动微分,我们需要定义一个Cell类来封装我们的自定义算子:
class Add(nn.Cell):
    # 反向传播函数
    def bprop(self, x, y, out, dout):
        return (y, )
    # 计算函数
    def construct(self, x, y):
        return CustomAdd(x, y)
# 把Cell类加载为custom_add函数
custom_add = Add()
# 计算求和结果
res = custom_add(t1, t2)
# 计算自动微分结果
res_g = grad(custom_add, grad_position=(0, ))(t1, t2)
print(res)
print(res_g)

在这个代码中,主要就是增加了一个Cell类和两个新的函数bpropconstruct,分别用于计算函数的反向传播和正向值,代码运行的结果如下:

$ python3 test_custom_ops.py 
[4. 4. 4.]
[3. 2. 1.]

当然,这里我们没有再额外写一个用于返回反向传播值的CUDA算子,但是原则上对于较为复杂的函数,是需要自己手动写一个用于求微分数值的CUDA算子的。

总结概要

本文介绍了在MindSpore标准格式下进行CUDA算子开发的方法和流程,可以让开发者在现有的AI框架下仍然可以调用基于CUDA实现的高性能的算子。并且,除了常规的数值计算之外,在MindSpore框架下,我们还可以通过实现一个bprop函数,使得我们手写的这个CUDA算子也可以使用MindSpore框架本身自带的自动微分-端到端微分技术。

版权声明

本文首发链接为:https://www.cnblogs.com/dechinphy/p/mindspore-cuda.html

作者ID:DechinPhy

更多原著文章:https://www.cnblogs.com/dechinphy/

请博主喝咖啡:https://www.cnblogs.com/dechinphy/gallery/image/379634.html

到此这篇关于MindSpore导入CUDA算子的文章就介绍到这了,更多相关MindSpore导入CUDA内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:

相关文章

  • Python的Flask框架开发验证码登录的实现

    Python的Flask框架开发验证码登录的实现

    在本文我们介绍了如何使用Python的Flask框架开发一个简单的验证码登录功能,将涵盖生成验证码、处理用户输入、验证验证码以及实现安全的用户认证等方面,感兴趣的可以了解一下
    2023-11-11
  • 面向新手解析python Beautiful Soup基本用法

    面向新手解析python Beautiful Soup基本用法

    这篇文章主要介绍了面向新手解析python Beautiful Soup基本用法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-07-07
  • python 详解如何写flask文件下载接口

    python 详解如何写flask文件下载接口

    Flask是一个使用 Python 编写的轻量级 Web 应用框架。其 WSGI 工具箱采用 Werkzeug ,模板引擎则使用 Jinja2 。Flask使用 BSD 授权。Flask也被称为 "microframework" ,因为它使用简单的核心,用 extension 增加其他功能。Flask没有默认使用的数据库、窗体验证工具
    2021-10-10
  • 详解django+django-celery+celery的整合实战

    详解django+django-celery+celery的整合实战

    这篇文章主要介绍了详解django+django-celery+celery的整合实战,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-03-03
  • Python+微信接口实现运维报警

    Python+微信接口实现运维报警

    这篇文章主要介绍了Python+微信接口实现运维报警的相关资料,需要的朋友可以参考下
    2016-08-08
  • python使用xlrd模块读取excel的方法实例

    python使用xlrd模块读取excel的方法实例

    Python读取Excel表格,相比xlwt来说,xlrd提供的接口比较多,下面这篇文章主要给大家介绍了关于python使用xlrd模块读取excel的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-03-03
  • Python使用Windows API创建窗口示例【基于win32gui模块】

    Python使用Windows API创建窗口示例【基于win32gui模块】

    这篇文章主要介绍了Python使用Windows API创建窗口操作,结合实例形式分析了Python基于win32gui模块调用Windows API创建窗口具体操作步骤与相关实现技巧,需要的朋友可以参考下
    2018-05-05
  • python基于SMTP发送QQ邮件

    python基于SMTP发送QQ邮件

    这篇文章主要为大家详细介绍了python基于SMTP发送QQ邮件,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-03-03
  • 深度学习环境搭建anaconda+pycharm+pytorch的方法步骤

    深度学习环境搭建anaconda+pycharm+pytorch的方法步骤

    本文主要介绍了深度学习环境搭建anaconda+pycharm+pytorch的方法步骤,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-09-09
  • python使用WMI检测windows系统信息、硬盘信息、网卡信息的方法

    python使用WMI检测windows系统信息、硬盘信息、网卡信息的方法

    这篇文章主要介绍了python使用WMI检测windows系统信息、硬盘信息、网卡信息的方法,涉及Python针对系统信息的相关操作技巧,需要的朋友可以参考下
    2015-05-05

最新评论