2013-12-08 46 views
0

我想转换一个程序,我做了OpenCL,但我还不够熟悉。尽管如此,我仍然遇到了我的(三)内核之一。它基本上是一个复杂的矩阵向量乘法,但我正在写它以便更好地满足我的需求。OpenCL内核似乎并没有得到全球ID“全球”

问题是,我无法让内核在GPU上工作。我已经将它简化为最多(2行),在CPU上进行调试,并且它在CPU上完全正常工作。但是当谈到GPU时,一切都会消失。我正在研究MacBook Pro,在NVIDIA GeForce 650M上我得到了一个结果,而在集成的英特尔HD 4000上,我得到了另一个结果。内核是

__kernel void Chmv_(__global float2 *H, const float alpha, __global float2 *vec, 
       const int off/*in number of elements*/, 
       __local float2 *vw, 
       __global float2 *vout) 
{ 
int gidx=get_global_id(0); 
int gidy=get_global_id(1); 
int gs=get_global_size(0); 

    vout[gidx].x += alpha*(H[gidx+gidy*gs].x*vec[gidy].x-H[gidx+gidy*gs].y*vec[gidy].y); 
    vout[gidx].y += alpha*(H[gidx+gidy*gs].y*vec[gidy].x+H[gidx+gidy*gs].x*vec[gidy].y); 

} 

对于试验,我让矩阵H是4×4矩阵,填充有(1.0F,0.0F),而输入矢量vec是具有x组分(0.0,1.0,2.0,3.0) ,y组件0. alpha设置为2.0f。所以,我应该有(12,12,12,12)作为x输出,如果我使用CPU,我也应该这样做。 NVIDIA给我6.0,而Intel给我4.0。

现在,更仔细的检查表明,如果输入矢量是(0,1,2,0),NVIDIA给我0作为答案,如果它是(0,1,0,3),则Intel给出0以及。顺便说一下,更改vec[gidy]vec[gidx]给我的矢量加倍。从这些角度来看,在我看来,内核只在一维中执行得很好,x只有一个值为get_global_id(1),这显然是不好的。

我将添加调用此内核检查的测试函数。现在,任何人都知道会发生什么?

void _test_(){ 
cl_mem mat,vec, out; 
size_t gs[2]={4,4}; 
size_t ls[2]={1,4}; 
size_t cpuws[2]={1,1}; 
cl_float2 *A=(cl_float2*)calloc(gs[0]*gs[0], sizeof(cl_float2)); 
cl_float2 *v=(cl_float2*)calloc(gs[0], sizeof(cl_float2)); 
cl_float2 *w=(cl_float2*)calloc(gs[0], sizeof(cl_float2)); 
int i; 

for (i=0; i<gs[0]; i++) { 
    A[i*gs[0]].x=1.0; 
    A[i*gs[0]+1].x= 1.0;//(i<ls-1)? 1.0f:0.0f; 
    A[i*gs[0]+2].x=1.0; 
    A[i*gs[0]+3].x=1.0; 
    v[i].x= (float)i; 
    printf("%d %f %f %f %f\n%v2f\n",i, A[i*gs[0]].x, A[i*gs[0]+1].x, A[i*gs[0]+2].x, A[i*gs[0]+3].x, v[i]); 
} 
v[2].x=0.0f; //<--- set individually for debug 

mat = clCreateBuffer(context, CL_MEM_READ_WRITE, gs[0]*gs[0]*sizeof(cl_float2), NULL, NULL); 
vec = clCreateBuffer(context, CL_MEM_READ_WRITE, gs[0]*sizeof(cl_float2), NULL, NULL); 
out = clCreateBuffer(context, CL_MEM_READ_WRITE, gs[0]*sizeof(cl_float2), NULL, NULL); 

error = clEnqueueWriteBuffer(queue, mat, CL_TRUE, 0, gs[0]*gs[0]*sizeof(cl_float2), A, 0, NULL, NULL); 
error = clEnqueueWriteBuffer(queue, vec, CL_TRUE, 0, gs[0]*sizeof(cl_float2), v, 0, NULL, NULL); 
error = clEnqueueWriteBuffer(queue, out, CL_TRUE, 0, gs[0]*sizeof(cl_float2), w, 0, NULL, NULL); 

int offset=0; 
float alpha=2.0; 
error = clSetKernelArg(Chmv_, 0, sizeof(cl_mem),&mat); 
error |= clSetKernelArg(Chmv_, 1, sizeof(float), &alpha); 
error |= clSetKernelArg(Chmv_, 2, sizeof(cl_mem),&vec); 
error |= clSetKernelArg(Chmv_, 3, sizeof(int), &offset); 
error |= clSetKernelArg(Chmv_, 4, gs[0]*sizeof(cl_float2), NULL); 
error |= clSetKernelArg(Chmv_, 5, sizeof(cl_mem), &out); 
assert(error == CL_SUCCESS); 

error = clEnqueueNDRangeKernel(queue, Chmv_, 2, NULL, gs, NULL, 0, NULL, &event); 

error = clEnqueueReadBuffer(queue, out, CL_TRUE, 0, gs[0]*sizeof(cl_float2), w, 0, NULL, NULL); 
clFinish(queue); 

for (i=0; i<gs[0]; i++) { 
    printf("%f %f\n", w[i].x, w[i].y); 

} 

clReleaseMemObject(mat); 
clReleaseMemObject(vec); 
clReleaseMemObject(out); 
} 

回答

1

您遇到了一个多线程不安全访问公共内存区的典型问题。 (vout

你必须认为所有的工作项目将同时运行。这意味着,他们将以任何顺序读写内存。

在CPU中执行时,由于执行由硬件连续完成,因此不会显示问题。 但是,在GPU中,一些工作项目读取vout的内存,将其增加并写入。但其他人在新值由前面的工作项目写入之前也会读取vout的内存。

由于内核大小很小,可能所有的工作项都是并行运行的,这就是为什么你只看到其中一个增加了最终结果。

这是一个典型的并行压缩问题。你可以谷歌它的更多细节。你需要实现的是在访问vout时,通过atomic_add()(缓慢)或正确缩小(难以编码)来同步所有线程。你可以查看本指南,它是针对CUDA的,但是或多或少具有相同的基本思想:Reduction Guide

+0

我明白了。我认为这只会导致内存银行冲突,开始时会放缓内核,但是GPU似乎工作方式不同,所以我认为,所有线程/ work_items都会读取相同的“旧值”并在稍后编写它们。以前从未使用过MPI或OpenMP(当然,它们在很多方面都比较容易处理)。事实上,减少是我从一开始就想要的,你的联系从现在开始帮助我完成整个工作。非常感谢! – Meligordman