【问题标题】:Unified memory and struct with arrays带数组的统一内存和结构
【发布时间】:2018-12-24 04:57:28
【问题描述】:

我在 CUDA 上有一个大的结构数组结构,它是恒定的,并且对于我的应用程序来说是只读的。一个非常简单的例子是

struct Graph{
    Node * nodes;
    int nNode;
}
struct Node{
   int* pos;
   int nPos;
}

我的内核需要浏览这个图表并查询它。如您所知,使用cudaMalloccudaMemcpy 将此结构复制到GPU 内存只是大量代码,统一内存应该不需要。

在我的代码中,我在 CPU 中生成了图形,然后为了测试,我设计了以下内核

__global__ void testKernel(const Graph graph,int * d_res){
    d_res[0]=graph.nNode;


};

被称为:

// using malloc for testing to make sure I know what I am doing
int * d_res,* h_res;
cudaMalloc((void **)&d_res,sizeof(int));
h_res=(int*)malloc(sizeof(int));

testKernel<<<1,1>>>(graph,d_res);

gpuErrchk( cudaPeekAtLastError() );
gpuErrchk(cudaMemcpy(h_res,d_res,sizeof(int),cudaMemcpyDeviceToHost));

带有错误检查from here

当我使用如图所示的testKernel 时,它工作正常,但如果我将内核更改为:

__global__ void testKernel(const Graph graph,int * d_res){
    d_res[0]=graph.nodes[0].nPos;

};

我收到非法内存访问错误。

是不是因为统一内存没有正确处理这类数据? 有没有办法确保我可以避免将所有显式副本写入 GPU 内存?


完整的 MCVE:

#include <algorithm>
#include <cuda_runtime_api.h>
#include <cuda.h>
typedef struct node{
    int* pos;
    int nPos;
}Node;
typedef struct Graph{
    Node * nodes;
    int nNode;
}Graph;


#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(const Graph graph, int * d_res){
    d_res[0] = graph.nNode;
    // d_res[0]=graph.nodes[0].nPos; // Not working

};



int main(void){

    // fake data, this comes from another process
     Graph graph;
    graph.nodes = (Node*)malloc(2*sizeof(Node));
    graph.nNode = 2;
    for (int i = 0; i < 2; i++){


    // They can have different sizes in the original code
    graph.nodes[i].pos = (int*)malloc(3 * sizeof(int));
    graph.nodes[i].pos[0] = 0;
    graph.nodes[i].pos[1] = 1;
    graph.nodes[i].pos[2] = 2;

    graph.nodes[i].nPos = 3;

}



printf("%d\n", graph.nNode); // Change to the kernel variable for comparison
int * d_res, *h_res;
cudaMalloc((void **)&d_res, sizeof(int));
h_res = (int*)malloc(sizeof(int));
testKernel << <1, 1 >> >(graph, d_res);
gpuErrchk(cudaPeekAtLastError());
gpuErrchk(cudaMemcpy(h_res, d_res, sizeof(int), cudaMemcpyDeviceToHost));

printf("%d", h_res[0]);
return 0;
}

【问题讨论】:

  • 您应该提供一个minimal reproducible example 我的猜测是由于取消引用nodes 内的nodes 指针graph,但您没有提供您如何构建的描述graph。与其试图解决这一点,我建议您提供一个minimal reproducible example 这样的问题,请参阅第1 项here
  • @RobertCrovella 你是完全正确的。在这里。
  • 我没有看到在您的代码中使用统一(即托管)内存的任何证据。也许您对CUDA unified memory 是什么感到困惑。您还可以在cuda 标签上研究 CUDA UM 示例代码以及有关它的许多问题。撇开这一点不谈,您的代码试图取消引用设备代码中的主机指针(由malloc 提供)。这是基本的 CUDA 禁忌,也是您观察到的错误的近端原因。
  • @RobertCrovella 啊,我可能误解了这个例子(这里)[devblogs.nvidia.com/unified-memory-in-cuda-6/].我见过的大多数示例将内存创建为 UM(如链接文档 cudaMallocManaged 中所示),然后用东西填充它。我还没有找到一个示例,其中我已经有一个如图所示的主机结构(由另一个进程生成,在 C 上没有对其进行控制),我想将它传递给 CUDA。我不擅长谷歌或者我不能这样做?
  • in the docs (K.1.6)... 阅读整个 K.1.6 部分。到最后一句话/行。将主机分配器(例如malloc)直接用作“托管”分配器取决于 ATS 支持。 K.1.6 节第一段的最后一句指出“应用程序可以通过检查新的pageableMemoryAccessUsesHostPageTables 属性来查询设备是否支持通过 ATS 一致地访问可分页内存。”你检查过那个属性吗? (提示:您的系统不支持此功能)。

标签: cuda unified-memory


【解决方案1】:

您的代码未使用 CUDA 统一内存。 UM 无论如何都不是“自动的”。它需要特定的编程步骤才能利用它,并且它具有特定的系统要求。

UM section of the programming guide 涵盖了所有这些内容。

有没有办法确保我可以避免将所有显式副本写入 GPU 内存?

正确使用 UM 应该可以做到这一点。这是一个完整的示例。我唯一做的就是将主机代码中的malloc 操作机械地转换为等效的cudaMallocManaged 操作。

$ cat t1389.cu
#include <algorithm>
#include <stdio.h>

typedef struct node{
    int* pos;
    int nPos;
}Node;
typedef struct Graph{
    Node * nodes;
    int nNode;
}Graph;


#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(const Graph graph, int * d_res){
    d_res[0] = graph.nNode;
     d_res[0]=graph.nodes[0].nPos; // Not working

};



int main(void){

    // fake data, this comes from another process
     Graph graph;
    cudaMallocManaged(&(graph.nodes), 2*sizeof(Node));
    graph.nNode = 2;
    for (int i = 0; i < 2; i++){


    // They can have different sizes in the original code
    cudaMallocManaged(&(graph.nodes[i].pos), 3 * sizeof(int));
    graph.nodes[i].pos[0] = 0;
    graph.nodes[i].pos[1] = 1;
    graph.nodes[i].pos[2] = 2;

    graph.nodes[i].nPos = 3;

}



printf("%d\n", graph.nNode); // Change to the kernel variable for comparison
int * d_res, *h_res;
cudaMalloc((void **)&d_res, sizeof(int));
h_res = (int*)malloc(sizeof(int));
testKernel << <1, 1 >> >(graph, d_res);
gpuErrchk(cudaPeekAtLastError());
gpuErrchk(cudaMemcpy(h_res, d_res, sizeof(int), cudaMemcpyDeviceToHost));

printf("%d", h_res[0]);
return 0;
}
$ nvcc t1389.cu -o t1389
$ cuda-memcheck ./t1389
========= CUDA-MEMCHECK
2
3========= ERROR SUMMARY: 0 errors
$

UM 有许多记录在案的系统要求。我不打算在这里全部背诵。主要你需要一个 cc3.0 或更高版本的 GPU。您的 MCVE 没有包含任何标准错误检查,我也没有尝试添加它。但是,如果您对此代码仍有问题,请务必使用正确的 CUDA 错误检查并使用cuda-memcheck 运行它。

如果您的整个数据结构(包括嵌入式指针)是使用普通主机分配器分配的,而您无法控制它,那么您将无法在 UM 机制中直接使用它,而无需进行某种相关操作复制。如上述链接编程指南部分的第 K.1.6 节所述,此处的例外情况是在 IBM Power9 系统上。

在尝试将主机分配器(例如 malloc)与 UM 一起使用之前,您应该首先测试 pageableMemoryAccessUsesHostPageTables 属性,如该部分所述。

除了正确配置的 IBM Power9 系统外,目前不会在任何系统上设置该属性。当前没有 x86 系统设置/可用此属性。

【讨论】:

  • 谢谢,非常感谢。这只是我快速完成的一个非常小的 MCVE,所以它并不花哨。但是,正如问题的最后一条评论中所提到的,UM 文档说(docs.nvidia.com/cuda/cuda-c-programming-guide/…“在上面的示例中,数据可以由第三方 CPU 库初始化,然后由 GPU 内核直接访问。 " 这是我想要达到的目标。我在 GTX1070 上运行,所以我应该可以使用 UM。
  • 数据确实可以由第三方 CPU 库初始化,因为托管分配的工作方式与主机代码的普通分配一样。但是,底层的分配必须是由 UM 系统创建的。
  • 我猜你指的是第 K.1.6 节。阅读我在上面关于您的问题的 cmets 下发表的评论。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-26
  • 2015-06-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多