我在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
没有分配预期值。
我的思路让我检查by
和y
之间是否存在差异,因为它们应始终具有相同的值;我添加了一行检查是否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 code和gist with example inputs and outputs。
非常感谢。
两位用户DarkZeros和sharpneli都对他们的假设是正确的:for循环内部的障碍没有达到适当的次数。特别是,有一个错误涉及每个本地组的第一个元素,使其运行一次迭代少于其余部分,从而引发一个未定义的行为。事后看来很痛苦。
谢谢大家的答案和时间。
答案 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循环运行多次才会出现问题。
你确定这种情况吗?