无法在cuda内核中使用我的模板类

时间:2019-11-29 09:42:15

标签: c++ cuda nvcc

我以为我知道如何编写一些简洁的cuda代码。直到我尝试制作一个简单的模板类并在简单的内核中使用它。 我几天来一直在麻烦射击。我访问过的每个线程都让我感到更加愚蠢。

为了进行错误检查,我使用了此

这是我的课。h:

#pragma once
template <typename T>
class MyArray
{
public:
    const int size;

    T *data;

    __host__ MyArray(int size); //gpuErrchk(cudaMalloc(&data, size * sizeof(T)));

    __device__ __host__ T GetValue(int); //return data[i]
    __device__ __host__ void SetValue(T, int); //data[i] = val;
    __device__ __host__ T& operator()(int); //return data[i];

    ~MyArray(); //gpuErrchk(cudaFree(data));
};

template class MyArray<double>;

class.cu的相关内容在注释中。如果您认为整件事都相关,那么很高兴添加它。

现在是主要班级:

__global__ void test(MyArray<double> array, double *data, int size)
{
    int j = threadIdx.x;
        //array.SetValue(1, j);  //doesn't work
        //array(j) = 1;  //doesn't work
        //array.data[j] = 1; //doesn't work
        data[j] = 1;   //This does work !
        printf("Reach this code\n");
    }
}
int main(int argc, char **argv)
{
    MyArray x(20);
    test<<<1, 20>>>(x, x.data, 20);

    gpuErrchk(cudaPeekAtLastError());
    gpuErrchk(cudaDeviceSynchronize());
}

当我说“不起作用”时,我的意思是程序在到达printf之前停在那里,而不输出任何错误。另外,我从cudaDeviceSynchronizecudaFree都收到了以下错误消息:

  

遇到非法的内存访问

我不明白的是,内存管理应该没有问题,因为将数组直接发送到内核可以正常工作。那么,为什么在我发送课程并尝试访问课程数据时它不起作用?为什么当我的代码明显遇到错误时,为什么我没有收到警告或错误消息?

这是nvcc --version

的输出
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2017 NVIDIA Corporation
Built on Fri_Nov__3_21:07:56_CDT_2017
Cuda compilation tools, release 9.1, V9.1.85

1 个答案:

答案 0 :(得分:1)

(编者注:关于此问题的评论中有相当多的虚假信息,因此我将其答案整理为社区Wiki条目。)

没有特殊原因不能将模板类作为参数传递给内核。在这样做之前,需要先了解一些限制:

    对于所有意图和目的,
  1. CUDA内核参数总是通过值传递。在极其有限的一组情况下支持按引用传递(有问题的参数必须存储在托管内存中)。这不适用于这里。
  2. 作为(1)的结果,POD参数可以正常工作,因为它们是可复制的并且不依赖任何特殊行为
  3. 类的不同之处在于,当您按值传递类时,您将隐式调用副本构造或移动构造语义。这意味着通过值作为内核参数传递的类必须是可复制构造的。在内核启动过程中,无法在设备上运行非平凡的副本构造函数。
  4. CUDA进一步要求类不包含虚拟成员
  5. 尽管<<< >>>内核启动语法看起来像一个简单的函数调用,但事实并非如此。您在主机代码中编写的内容与主机端工具链实际发出的内容之间有多层抽象样板和API调用。这意味着您的代码和GPU之间存在若干复制构造操作。如果执行诸如在析构函数中放置cudaFree调用之类的操作,则应假定它将作为函数调用序列的一部分被调用,该函数调用序列将在其中一个副本超出范围时启动内核。你不要那个。

您没有显示在这种情况下类成员函数是如何实际实现的,因此,除了将原始指针传递给内核之外,为什么要说代码注释所提示的许多排列之一为什么起作用或不起作用是不可能的之所以起作用,是因为它是一个几乎可复制的POD值,而该类几乎可以肯定不是。

这是一个简单的完整示例,展示了如何进行此工作:

$cat classy.cu
#include <vector>
#include <iostream>

#define gpuErrchk(ans) { gpuAssert((ans), __FILE__, __LINE__); }
inline void gpuAssert(cudaError_t code, const char *file, int line, bool abort=true)
{
    if (code != cudaSuccess)
    {
        fprintf(stderr,"GPUassert: %s %s %d\n", cudaGetErrorString(code), file, line);
        if (abort) exit(code);
    }
}

template <typename T>
class MyArray
{
    public:
        int len;
        T *data;

        __device__ __host__ void SetValue(T val, int i) { data[i] = val; };
        __device__ __host__ int size() { return sizeof(T) * len; };

        __host__ void DevAlloc(int N) {
            len = N;
            gpuErrchk(cudaMalloc(&data, size()));
        };

        __host__ void DevFree() {
            gpuErrchk(cudaFree(data));
            len = -1;
        };
};

__global__ void test(MyArray<double> array, double val)
{
    int j = threadIdx.x;
    if (j < array.len)
        array.SetValue(val, j);
}

int main(int argc, char **argv)
{
    const int N = 20;
    const double val = 5432.1;

    gpuErrchk(cudaSetDevice(0));
    gpuErrchk(cudaFree(0));

    MyArray<double> x;
    x.DevAlloc(N);

    test<<<1, 32>>>(x, val);
    gpuErrchk(cudaPeekAtLastError());
    gpuErrchk(cudaDeviceSynchronize());

    std::vector<double> y(N);
    gpuErrchk(cudaMemcpy(&y[0], x.data, x.size(), cudaMemcpyDeviceToHost));
    x.DevFree();

    for(int i=0; i<N; ++i) std::cout << i << " = " << y[i] << std::endl;

    return 0;
}

其编译和运行方式如下:

$ nvcc -std=c++11 -arch=sm_53 -o classy classy.cu
$ cuda-memcheck ./classy
========= CUDA-MEMCHECK
0 = 5432.1
1 = 5432.1
2 = 5432.1
3 = 5432.1
4 = 5432.1
5 = 5432.1
6 = 5432.1
7 = 5432.1
8 = 5432.1
9 = 5432.1
10 = 5432.1
11 = 5432.1
12 = 5432.1
13 = 5432.1
14 = 5432.1
15 = 5432.1
16 = 5432.1
17 = 5432.1
18 = 5432.1
19 = 5432.1
========= ERROR SUMMARY: 0 errors

(在Jetson Nano上为CUDA 10.2 / gcc 7.5)

请注意,我包括用于分配和释放的主机端函数,这些函数不与构造函数和析构函数交互。否则,该类与您的设计极为相似,并且具有相同的属性。