我的OpenCL代码基于看似noop来改变输出

时间:2013-11-04 11:37:52

标签: c opencl nvidia intel

我在Intel CPU和NVIDIA GPU上运行相同的OpenCL内核代码,结果在第一个上是错误的,但在后者上是正确的;奇怪的是,如果我做了一些看似无关的改变,那么输出在两种情况下都会按预期工作。

该函数的目标是计算A(三角形)和B(常规)之间的矩阵乘法,其中操作中A的位置由变量left的值确定。该错误仅在left为真且for循环至少迭代两次时出现。

这是代码的一个片段,省略了一些不应该为了清晰起见而影响的位。

__kernel void blas_strmm(int left, int upper, int nota, int unit, int row, int dim, int m, int n,
                         float alpha, __global const float *a, __global const float *b, __global float *c) {

  /* [...] */
  int ty = get_local_id(1);
  int y = ty + BLOCK_SIZE * get_group_id(1);
  int by = y;
  __local float Bs[BLOCK_SIZE][BLOCK_SIZE];
  /* [...] */

  for(int i=start; i<end; i+=BLOCK_SIZE) {
    if(left) {
      ay = i+ty;
      bx = i+tx;
    }   
    else {
      ax = i+tx;
      by = i+ty;
    }   

    barrier(CLK_LOCAL_MEM_FENCE);
    /* [...] (Load As) */
    if(bx >= m || by >= n)
      Bs[tx][ty] = 0;
    else
      Bs[tx][ty] = b[bx*n+by];
    barrier(CLK_LOCAL_MEM_FENCE);

    /* [...] (Calculate Csub) */
  }

  if(y < n && x < (left ? row : m)) // In bounds
    c[x*n+y] = alpha*Csub;
}

现在变得很奇怪。

如您所见,如果by为真,则y始终等于left。我检查过(有一些printf,请注意)并且left始终为true,并且循环中else分支上的代码永远不会被执行。不过,如果我删除或注释掉那里的by = i+ty行,代码就可以了。为什么?我还不知道,但我可能会发现by没有分配预期值。

我的思路让我检查byy之间是否存在差异,因为它们应始终具有相同的值;我添加了一行检查是否by != y,但该比较总是返回false,如预期的那样。所以我接着改变了by y的外观,所以行

if(bx >= m || by >= n)

转化为

if(bx >= m || y >= n)

并且它再次起作用,即使我仍然在下面正确使用变量by三行。

我以开放的心态尝试了其他一些事情,如果我在循环中添加以下行,只要它位于初始if / else之后和之前的任何点,我就会认为代码有效。如果我之前提到的条件。

if(y >= n) left = 1;

里面的代码(left = 1)可以代替任何东西(printf,另一个无用的分配等),但条件有点限制。以下是一些使代码输出正确值的示例:

if(y >= n) left = 1;
if(y < n) left = 1;
if(y+1 < n+1) left = 1;
if(n > y) left = 1;

有些不起作用,请注意我正在测试的特定示例中的m = n

if(y >= n+1) left = 1;
if(y > n) left = 1;
if(y >= m) left = 1;
/* etc. */

这就是我现在的意义。我添加了一行不应该影响程序,但它使它工作。这个神奇的解决方案对我来说并不令人满意,我想知道我的CPU内部发生了什么以及为什么。

为了确保我没有忘记任何事情,这里是full function codegist with example inputs and outputs

非常感谢。


解决方案

两位用户DarkZeros和sharpneli都对他们的假设是正确的:for循环内部的障碍没有达到适当的次数。特别是,有一个错误涉及每个本地组的第一个元素,使其运行一次迭代少于其余部分,从而引发一个未定义的行为。事后看来很痛苦。

谢谢大家的答案和时间。

2 个答案:

答案 0 :(得分:2)

您是否检查过get_local_size始终返回正确的值?

你说“简而言之,矩阵的全长被划分为BLOCK_SIZE的本地块并且并行运行;”。请记住,OpenCL仅允许在工作组中进行任何并发。因此,如果您调用enqueueNDrange,其全局大小为[32,32],本地大小为[16,16],则第一个线程块可能从头到尾运行,然后是第二个,然后是第三个等。您无法在工作组。

您的EnqueueNDRange电话是什么?获取示例输出所需的调用示例将非常受欢迎(主要是对全局和本地大小参数感兴趣)。

(我在评论中提出这个问题,但我是新用户)。

E(如果有答案,经过验证没有,还需要更多信息): http://multicore.doc.ic.ac.uk/tools/GPUVerify/

通过使用,我得到了一个不均匀控制流可以达到屏障的抱怨。

这一切都取决于什么值dim,nota和upper get。你能提供一些例子吗?

我做了一些测试。假设left = 1. nota!= upper和dim = 32,row为16或32或whatnot,仍然有效并得到以下结果:

...
gid0: 2 gid1: 0 lid0: 14 lid1: 13 start:  0  end: 32
gid0: 2 gid1: 0 lid0: 14 lid1: 14 start:  0  end: 32
gid0: 2 gid1: 0 lid0: 14 lid1: 15 start:  0  end: 32
gid0: 2 gid1: 0 lid0: 15 lid1:  0 start:  0  end: 48
gid0: 2 gid1: 0 lid0: 15 lid1:  1 start:  0  end: 48
gid0: 2 gid1: 0 lid0: 15 lid1:  2 start:  0  end: 48
...

因此,如果我对变量值的假设甚至接近正确,那么你就会遇到障碍分歧问题。有些线程会遇到另一个线程永远不会遇到的障碍。我很惊讶它并没有陷入僵局。

答案 1 :(得分:1)

我看到的第一件事可能非常失败,就是你在for循环中使用了障碍。

如果所有线程都没有输入相同的for循环次数。然后结果完全未定义。并且您明确指出只有for循环运行多次才会出现问题。

你确定这种情况吗?