【发布时间】:2018-12-26 17:41:25
【问题描述】:
我在 C 中有一个包含结构数组的结构,我需要在 GPU 中复制它。为此,我正在编写一个函数,该函数使结构中的一些变量 cudaMalloc 和 cudaMemcpys 从主机到设备。
结构体的一个简单版本(真正的内部有各种结构体和变量/数组)是:
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(&outGraph->boundary, &d_auxboundary, sizeof(unsigned int *), cudaMemcpyDeviceToDevice));线上)出现“无效输入”错误,因此它无法修复代码的任何问题 -
该评论没有粗鲁之意——如果您专注于编写以最简单的方式实际工作的代码,这是一个建设性的建议。但是您引用
&d_auxboundary的内容是错误的。它应该只是d_auxboundary。就像我说的,如果你觉得这很难,请使用托管内存,这就是它的用途。 -
@talonmies 谢谢,误会请见谅。我需要对在 CPU 上生成的结构执行此操作,因此托管内存似乎不适合我。无论如何,我正在努力学习更好的 CUDA,所以这是一个好方法,我宁愿让它在这里工作。我会检查你的建议,看看我能不能解决它