【问题标题】:Variable gets lost after allocating array of structs in cuda在 cuda 中分配结构数组后变量丢失
【发布时间】:2018-12-26 17:41:25
【问题描述】:

我在 C 中有一个包含结构数组的结构,我需要在 GPU 中复制它。为此,我正在编写一个函数,该函数使结构中的一些变量 cudaMalloccudaMemcpys 从主机到设备。

结构体的一个简单版本(真正的内部有各种结构体和变量/数组)是:

struct Node {

    float* position;

};

struct Graph{
    unsigned int nNode;
    Node* node;
    unsigned int nBoundary;
    unsigned int* boundary;
};

我的问题是我在结构的内存分配和复制中一定做错了什么。当我使用Graph 复制变量时,我可以看到它们被正确复制(通过在内核中访问它,如下例所示)。例如,我可以检查graph.nBoundary=3

但是,如果我不分配和复制Node *的内存,我只能看到这个。如果我这样做,我会得到-858993460 而不是3。有趣的是,Node * 没有被错误地分配,因为我可以检查说graph.node[0].pos[0] 的值并且它具有正确的值。

这只发生在graph.nBoundary 上。所有其他变量都保持正确的数值,但在运行Node*cudaMemcpy 时,这个变量会“出错”。

我做错了什么,为什么会这样?我该如何解决?

如果您需要更多信息,请告诉我。


MCVE:

#include <algorithm>
#include <cuda_runtime_api.h>
#include <cuda.h>

// A point, part of some elements
struct Node {

    float* position;

};

struct Graph{
    unsigned int nNode;
    Node* node;
    unsigned int nBoundary;
    unsigned int* boundary;
};
Graph* cudaGraphMalloc(const Graph* inGraph);
#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);
    }
}

__global__ void testKernel(Graph* graph,unsigned int * d_res){
    d_res[0] = graph->nBoundary;

};
int main()
{

    // Generate some fake data on the CPU
    Graph graph;
    graph.node = (Node*)malloc(2 * sizeof(Node));
    graph.boundary = (unsigned int*)malloc(3 * sizeof(unsigned int));
    for (int i = 0; i < 3; i++){
        graph.boundary[i] = i + 10;
    }
    graph.nBoundary = 3;
    graph.nNode = 2;
    for (int i = 0; i < 2; i++){
        // They can have different sizes in the original code
        graph.node[i].position = (float*)malloc(3 * sizeof(float));
        graph.node[i].position[0] = 45;
        graph.node[i].position[1] = 1;
        graph.node[i].position[2] = 2;
    }

    // allocate GPU memory
    Graph * d_graph = cudaGraphMalloc(&graph);
    // some dummy variables to test on GPU.
    unsigned int * d_res, *h_res;
    cudaMalloc((void **)&d_res, sizeof(unsigned int));
    h_res = (unsigned int*)malloc(sizeof(unsigned int));

    //Run kernel
    testKernel << <1, 1 >> >(d_graph, d_res);
    gpuErrchk(cudaPeekAtLastError());
    gpuErrchk(cudaMemcpy(h_res, d_res, sizeof(unsigned int), cudaMemcpyDeviceToHost));

    printf("%u\n", graph.nBoundary);
    printf("%d", h_res[0]);

    return 0;
}

Graph* cudaGraphMalloc(const Graph* inGraph){
    Graph* outGraph;
    gpuErrchk(cudaMalloc((void**)&outGraph, sizeof(Graph)));

    //copy constants
    gpuErrchk(cudaMemcpy(&outGraph->nNode, &inGraph->nNode, sizeof(unsigned int), cudaMemcpyHostToDevice));
    gpuErrchk(cudaMemcpy(&outGraph->nBoundary, &inGraph->nBoundary, sizeof(unsigned int), cudaMemcpyHostToDevice));


    // copy boundary
    unsigned int * d_auxboundary, *h_auxboundary;
    h_auxboundary = inGraph->boundary;
    gpuErrchk(cudaMalloc((void**)&d_auxboundary, inGraph->nBoundary*sizeof(unsigned int)));
    gpuErrchk(cudaMemcpy(d_auxboundary, h_auxboundary, inGraph->nBoundary*sizeof(unsigned int), cudaMemcpyHostToDevice));
    gpuErrchk(cudaMemcpy(&outGraph->boundary, d_auxboundary, sizeof(unsigned int *), cudaMemcpyDeviceToDevice));


    //Create nodes 
    Node * auxnode;
    gpuErrchk(cudaMalloc((void**)&auxnode, inGraph->nNode*sizeof(Node)));

    // Crate auxiliary pointers to grab them from host and pass them to device
    float ** d_position, ** h_position;
    d_position = static_cast<float **>(malloc(inGraph->nNode*sizeof(float*)));
    h_position = static_cast<float **>(malloc(inGraph->nNode*sizeof(float*)));

    for (int i = 0; i < inGraph->nNode; i++){

        // Positions
        h_position[i] = inGraph->node[i].position;
        gpuErrchk(cudaMalloc((void**)&d_position[i], 3 * sizeof(float)));
        gpuErrchk(cudaMemcpy(d_position[i], h_position[i], 3 * sizeof(float), cudaMemcpyHostToDevice));
        gpuErrchk(cudaMemcpy(&auxnode[i].position, d_position[i], sizeof(float *), cudaMemcpyDeviceToDevice));

    }
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////// If I comment the following section, nBoundary can be read by the kernel
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////

    gpuErrchk(cudaMemcpy(&outGraph->node, auxnode, inGraph->nNode*sizeof(Node *), cudaMemcpyDeviceToDevice));



    return outGraph;
}

【问题讨论】:

  • 安装程序结束时的复制循环完全被破坏了。为什么要在设备指针之间执行 hostToDevice 副本? (并且神奇地获取这些指针的主机地址来修复运行时错误仅意味着您正在从主机堆栈复制随机垃圾)。如果您无法跟踪什么是主机指针和什么是设备指针,则最好使用最初询问的托管内存。
  • 事实上,整个 cudaGraphMalloc 都充满了同样的错误
  • @talonmies 啊,我太专注于指针,以至于我的思绪滑落。简单的错误,无需粗鲁。如果我理解正确,您建议在我从设备指针复制到设备指针时将标志更改为cudaMemcpyDeviceToDevice,这当然是有道理的。但是,当我这样做时(在gpuErrchk(cudaMemcpy(&amp;outGraph-&gt;boundary, &amp;d_auxboundary, sizeof(unsigned int *), cudaMemcpyDeviceToDevice)); 线上)出现“无效输入”错误,因此它无法修复代码的任何问题
  • 该评论没有粗鲁之意——如果您专注于编写以最简单的方式实际工作的代码,这是一个建设性的建议。但是您引用&amp;d_auxboundary 的内容是错误的。它应该只是d_auxboundary。就像我说的,如果你觉得这很难,请使用托管内存,这就是它的用途。
  • @talonmies 谢谢,误会请见谅。我需要对在 CPU 上生成的结构执行此操作,因此托管内存似乎不适合我。无论如何,我正在努力学习更好的 CUDA,所以这是一个好方法,我宁愿让它在这里工作。我会检查你的建议,看看我能不能解决它

标签: c++ cuda


【解决方案1】:

问题出在函数cudaGraphMalloc 中,您试图将设备内存分配给已在设备上分配的outGraph 的成员。在这样做的过程中,您正在取消引用主机上的设备指针,这是非法的。

要将设备内存分配给设备上存在的struct类型变量的成员,我们首先必须创建一个struct类型的临时宿主变量,然后将设备内存分配给它的成员,然后将其复制到设备上存在的结构。

我已经回答了一个类似的问题here。请看一下。

固定的代码可能如下所示:

#include <algorithm>
#include <cuda_runtime.h>
#include <cuda.h>

// A point, part of some elements
struct Node {

    float* position;

};

struct Graph {
    unsigned int nNode;
    Node* node;
    unsigned int nBoundary;
    unsigned int* boundary;
};
Graph* cudaGraphMalloc(const Graph* inGraph);
#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);
    }
}

__global__ void testKernel(Graph* graph, unsigned int * d_res) {
    d_res[0] = graph->nBoundary;

};
int main()
{

    // Generate some fake data on the CPU
    Graph graph;
    graph.node = (Node*)malloc(2 * sizeof(Node));
    graph.boundary = (unsigned int*)malloc(3 * sizeof(unsigned int));
    for (int i = 0; i < 3; i++) {
        graph.boundary[i] = i + 10;
    }
    graph.nBoundary = 3;
    graph.nNode = 2;
    for (int i = 0; i < 2; i++) {
        // They can have different sizes in the original code
        graph.node[i].position = (float*)malloc(3 * sizeof(float));
        graph.node[i].position[0] = 45;
        graph.node[i].position[1] = 1;
        graph.node[i].position[2] = 2;
    }

    // allocate GPU memory
    Graph * d_graph = cudaGraphMalloc(&graph);
    // some dummy variables to test on GPU.
    unsigned int * d_res, *h_res;
    cudaMalloc((void **)&d_res, sizeof(unsigned int));
    h_res = (unsigned int*)malloc(sizeof(unsigned int));

    //Run kernel
    testKernel << <1, 1 >> >(d_graph, d_res);
    gpuErrchk(cudaPeekAtLastError());
    gpuErrchk(cudaMemcpy(h_res, d_res, sizeof(unsigned int), cudaMemcpyDeviceToHost));

    printf("%u\n", graph.nBoundary);
    printf("%u\n", h_res[0]);

    return 0;
}

Graph* cudaGraphMalloc(const Graph* inGraph) 
{
    //Create auxiliary Graph variable on host
    Graph temp;

    //copy constants
    temp.nNode = inGraph->nNode;
    temp.nBoundary = inGraph->nBoundary;

    // copy boundary
    gpuErrchk(cudaMalloc((void**)&(temp.boundary), inGraph->nBoundary * sizeof(unsigned int)));
    gpuErrchk(cudaMemcpy(temp.boundary, inGraph->boundary, inGraph->nBoundary * sizeof(unsigned int), cudaMemcpyHostToDevice));


    //Create nodes 
    size_t nodeBytesTotal = temp.nNode * sizeof(Node);
    gpuErrchk(cudaMalloc((void**)&(temp.node), nodeBytesTotal));

    for (int i = 0; i < temp.nNode; i++)
    {
        //Create auxiliary node on host
        Node auxNodeHost;

        //Allocate device memory to position member of auxillary node
        size_t nodeBytes = 3 * sizeof(float);
        gpuErrchk(cudaMalloc((void**)&(auxNodeHost.position), nodeBytes));
        gpuErrchk(cudaMemcpy(auxNodeHost.position, inGraph->node[i].position, nodeBytes, cudaMemcpyHostToDevice));

        //Copy auxillary host node to device
        Node* dPtr = temp.node + i;
        gpuErrchk(cudaMemcpy(dPtr, &auxNodeHost, sizeof(Node), cudaMemcpyHostToDevice));
    }


    Graph* outGraph;
    gpuErrchk(cudaMalloc((void**)&outGraph, sizeof(Graph)));
    gpuErrchk(cudaMemcpy(outGraph, &temp, sizeof(Graph), cudaMemcpyHostToDevice));

    return outGraph;
}

请注意,您必须保留内部设备指针的主机副本(即辅助主机变量)。这是因为您稍后必须释放设备内存,并且由于您在主代码中只有Graph 的设备副本,因此您将无法从主机访问其成员以在它们上调用cudaFree .在这种情况下,变量Node auxNodeHost(在每次迭代中创建)和Graph temp就是这些变量。

上面的代码并没有这样做,只是为了演示目的。

在 Windows 10、Visual Studio 2015、CUDA 9.2、NVIDIA 驱动程序 397.44 上测试。

【讨论】:

  • 如果我没有理解错,auxNodeHost.positionoutGraph-&gt;node[0].position(假设nNode==1)是同一个内存地址。为什么我需要将tempauxNodeHost 存储到cudaFree?我可以不cudaFreeoutGraph的成员吗?
  • @AnderBiguri... 是的,如果nNode==1 是正确的。两者都指向设备上的相同内存地址。对于cudaFree,您需要tempauxNodeHost,因为您无法从主机取消引用设备变量。这意味着做cudaFree(outGraph-&gt;boundary); 是非法的。正确的方法是cudaFree(temp.boundary);
  • 啊,我明白了。它的outGraph-&gt;boundary 是非法的,而不是释放。谢谢!这个错误是我误解的最大原因。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-11-24
  • 2018-11-05
  • 2019-02-20
  • 2020-09-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多