【pytorch扩展】CUDA自定义pytorch算子(简单demo入手)

news/2024/7/7 19:38:45 标签: pytorch, python, CUDA

Pytorch作为一款优秀的AI开发平台,提供了完备的自定义算子的规范。我们用torch开发时,经常会因为现有算子的不足限制我们idea的迸发。于是,CUDA/C++自定义pytorch算子是不得不磕了。

今天通过一个小实验来梳理自定义pytorch算子都需要做哪些准备。比如,我们做一个张量加法。
vim test_add.py

from add import sum_double_op
import torch
import time

class Timer:
    def __init__(self, op_name):
        self.begin_time = 0
        self.end_time = 0
        self.op_name = op_name

    def __enter__(self):
        torch.cuda.synchronize()
        self.begin_time = time.time()

    def __exit__(self, exc_type, exc_val, exc_tb):
        torch.cuda.synchronize()
        self.end_time = time.time()
        print(f"Average time cost of {self.op_name} is {(self.end_time - self.begin_time) * 1000:.4f} ms")


if __name__ == '__main__':
    n = 1000000
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    tensor1 = torch.ones(n, dtype=torch.float32, device=device, requires_grad=True)
    tensor2 = torch.ones(n, dtype=torch.float32, device=device, requires_grad=True)
    with Timer("sum_double"):
        ans = sum_double_op(tensor1, tensor2)

这里的"sum_double_op"就是我们用CUDA写的算子。那这个可以直接调用,并且可以传递梯度的算子,需要怎么做呢?


众所周知,CUDA/C++都是编译性语言,编译以后再调用会比python这种解释性语言更快。所以,我们需要对CUDA有一个编译过程。这个编译过程用setuptools来实现(可以pip安装)。
先vim setup.py

from setuptools import find_packages, setup
from torch.utils.cpp_extension import BuildExtension, CUDAExtension

setup(
    name='myAdd',
    packages=find_packages(),
    version='0.1.0',
    author='muzhan',
    ext_modules=[
        CUDAExtension(
            'sum_double',
            ['./add/add.cpp',
             './add/add_cuda.cu',]
        ),
    ],
    cmdclass={
        'build_ext': BuildExtension
    }
)

直接“python setup.py install”即可完成cuda算子的编译和安装。等等,你的add.cpp和add_cuda.cu还没呢?
vim add_cuda.cu

#include <cstdio>
#define THREADS_PER_BLOCK 256
#define WARP_SIZE 32
#define DIVUP(m, n) ((m + n - 1) / n)


__global__ void two_sum_kernel(const float* a, const float* b, float * c, int n){
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    if (idx < n){
        c[idx] = a[idx] + b[idx];
    }
}


void two_sum_launcher(const float* a, const float* b, float* c, int n){
    dim3 blockSize(DIVUP(n, THREADS_PER_BLOCK));
    dim3 threadSize(THREADS_PER_BLOCK);
    two_sum_kernel<<<blockSize, threadSize>>>(a, b, c, n);
}

vim add.cpp

#include <torch/extension.h>
#include <torch/serialize/tensor.h>

#define CHECK_CUDA(x) \
  TORCH_CHECK(x.type().is_cuda(), #x, " must be a CUDAtensor ")
#define CHECK_CONTIGUOUS(x) \
  TORCH_CHECK(x.is_contiguous(), #x, " must be contiguous ")
#define CHECK_INPUT(x) \
  CHECK_CUDA(x);       \
  CHECK_CONTIGUOUS(x)


void two_sum_launcher(const float* a, const float* b, float* c, int n);


void two_sum_gpu(at::Tensor a_tensor, at::Tensor b_tensor, at::Tensor c_tensor){
    CHECK_INPUT(a_tensor);
    CHECK_INPUT(b_tensor);
    CHECK_INPUT(c_tensor);

    const float* a = a_tensor.data_ptr<float>();
    const float* b = b_tensor.data_ptr<float>();
    float* c = c_tensor.data_ptr<float>();
    int n = a_tensor.size(0);
    two_sum_launcher(a, b, c, n);
}


PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
  m.def("forward", &two_sum_gpu, "sum two arrays (CUDA)");
}

我们看一下文件结构:

.
├── add
│   ├── add.cpp
│   ├── add_cuda.cu
│   ├── __init__.py
│   └── sum.py
├── README.md
├── setup.py
└── test_add.py

有了add.cpp和add_cuda.cu以后,我们就可以用"python setup.py install"来进行编译和安装了。编译和安装以后,我们需要用python类封装一下:

vim __init__.py
from .sum import *

vim sum.py

from torch.autograd import Function
import sum_double


class SumDouble(Function):

    @staticmethod
    def forward(ctx, array1, array2):
        """sum_double function forward.
        Args:
            array1 (torch.Tensor): [n,]
            array2 (torch.Tensor): [n,]
        
        Returns:
            ans (torch.Tensor): [n,]
        """
        array1 = array1.float()
        array2 = array2.float()
        ans = array1.new_zeros(array1.shape)
        sum_double.forward(array1.contiguous(), array2.contiguous(), ans)

        # ctx.mark_non_differentiable(ans) # if the function is no need for backpropogation

        return ans

    @staticmethod
    def backward(ctx, g_out):
        # return None, None   # if the function is no need for backpropogation

        g_in1 = g_out.clone()
        g_in2 = g_out.clone()
        return g_in1, g_in2


sum_double_op = SumDouble.apply

最后,直接

python test_add.py

http://www.niftyadmin.cn/n/5535045.html

相关文章

jvm常见调优

FullGC的STW停顿时间长 单体应用一台硬件上的jvm的部署策略 单独的jvm管理堆内存 对于用户停顿时间敏感的系统&#xff0c;并不是必须使用Shenandoah或者ZGC这些明确以控制延迟为目标的垃圾回收器才能解决问题&#xff08;当然&#xff0c;这是最好的方法&#xff09;&#…

数组与 ArrayList 的区别是什么?

在Java中&#xff0c;数组和ArrayList都是非常常见的数据结构&#xff0c;但它们在使用场景、特点和功能上各有千秋。 理解它们的不同&#xff0c;对于初级Java工程师来说&#xff0c;是提升编程技能的一个重要环节。 下面&#xff0c;我将以一种简单明了的方式&#xff0c;对…

JAVA声明数组

一、声明并初始化数组 直接初始化&#xff1a;在声明数组的同时为其分配空间并初始化元素。 int[] numbers {1, 2, 3, 4, 5}; 动态初始化&#xff1a;先声明数组&#xff0c;再为每个元素分配初始值。 double[] decimals;decimals new double[5]; // 分配空间&#xff0c;但…

理解神经网络的通道数

理解神经网络的通道数 1. 神经网络的通道数2. 输出的宽度和长度3. 理解神经网络的通道数3.1 都是错误的图片惹的祸3.1.1 没错但是看不懂的图3.1.2 开玩笑的错图3.1.3 给人误解的图 3.2 我或许理解对的通道数3.2.1 动图演示 1. 神经网络的通道数 半路出嫁到算法岗&#xff0c;额…

Kotlin/Android中执行HTTP请求

如何在Kotlin/Android中执行简单的HTTP请求 okhttp官网 okhttp3 github地址 打开build.gradle.kts文件加入依赖 dependencies {implementation("com.squareup.okhttp3:okhttp:4.9.0") }在IDEA的Gradle面板点击reload按钮便会自动下载jar 为了方便使用可以对okhttp…

计算机专业课面试常见问题-计算机网络篇

目录 1. 计算机网络分为哪 5 层&#xff1f; 2. TCP 协议简述&#xff1f; 3. TCP 和 UDP 的区别&#xff1f;->不同的应用场景&#xff1f; 4. 从浏览器输入网址到显示页…

bug,属性注入时为null

因为在使用拦截器时使用的是new的这个类放容器的 解决方法&#xff1a; 使用有参构造器&#xff0c;在new对象时传入值

Element中的选择器组件Select (一级选择组件el-select)

简述&#xff1a;在 Element UI 中&#xff0c;ElSelect&#xff08;或简称为 Select&#xff09;是一个非常常用的选择器组件&#xff0c;它提供了丰富的功能来帮助用户从一组预定义的选项中选择一个或多个值。这里来简单记录一下 一. 组件和属性配置 <el-selectv-model&q…