4

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

该函数的目标是计算 A(三角形)和 B(正则)之间的矩阵乘法,其中 A 在运算中的位置由变量 的值决定left。该错误仅在left为 true 并且 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始终等于。我检查了(用一些s,请注意)并且总是正确的,并且循环内 else 分支上的代码永远不会执行。不过,如果我删除或注释掉那里的行,代码就可以工作。为什么?我还不知道,但我认为这可能与没有分配预期值有关。yleftprintfleftby = i+tyby

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

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

转化成

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

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

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

if(y >= n) left = 1;

( ) 中的代码left = 1可以替换任何东西(a 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 内部发生了什么以及为什么。

只是为了确保我没有忘记任何东西,这里是完整的功能代码带有示例输入和输出的要点

非常感谢。


解决方案

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

谢谢大家的回答和时间。

4

2 回答 2

2

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

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

您的 EnqueueNDRange 调用是什么?获取示例输出所需的调用示例将不胜感激(主要对全局和本地大小参数感兴趣)。

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

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

通过使用它,我得到了一个抱怨,即非均匀控制流可能会达到障碍。

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

我做了一些测试。假设 left = 1. nota != upper 和 dim = 32, row as 16 or 32 or 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
...

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

于 2013-11-04T13:40:19.657 回答
1

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

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

你能保证这个条件吗?

于 2013-11-04T12:12:00.213 回答