【问题标题】:Can't use my template class in cuda kernel无法在 cuda 内核中使用我的模板类
【发布时间】:2020-03-24 21:42:18
【问题描述】:

我以为我知道如何编写一些干净的 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 的相关内容在 cmets 中。如果您认为整个事情是相关的,我很乐意添加它。

现在是主要课程:

__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

【问题讨论】:

  • 我现在无法测试它,但我建议不要将 MyArray 作为参考,而是按值。看到这个问题:stackoverflow.com/questions/8302506/parameters-to-cuda-kernels
  • 此用例不支持通过引用传递
  • x 在主机内存中,您无法从 GPU 访问主机内存中的对象。请注意,data 成员不能在主机上使用,因为底层内存是在设备上分配的。我会推荐更多关于主机和设备内存的阅读。
  • @DevonCornwall:这种情况下传值也会失败
  • 您需要在主机上创建 x,在构造函数或其他函数中分配 GPU 上的 T* 数据。然后使用 cudaMemcpy() 将 x 从主机复制到 GPU,然后将其发送到内核以使其工作。您遇到的错误与作为模板的类无关。

标签: c++ cuda nvcc


【解决方案1】:

(编者按:关于这个问题的 cmets 中有相当多的虚假信息,因此我将答案汇总为社区 wiki 条目。)

模板类不能作为参数传递给内核并没有什么特别的原因。在这样做之前需要清楚地了解一些限制:

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

你没有展示在这种情况下类成员函数是如何实际实现的,所以除了将原始指针传递给内核之外,解释为什么你的代码 cmets 暗示的许多排列中的一个起作用或不起作用是不可能的,这之所以有效,是因为它是一个可轻松复制的 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)

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-10-24
    • 2013-08-28
    • 1970-01-01
    • 1970-01-01
    • 2021-10-16
    • 2012-08-23
    • 1970-01-01
    • 2018-05-29
    相关资源
    最近更新 更多