OpenCL内核的随机NaN和错误结果

OpenCL内核的随机NaN和错误结果,opencl,gpgpu,blas,Opencl,Gpgpu,Blas,我试图实现一个通用的矩阵乘法OpenCL内核,它符合C=α*a*B+β*C 内核 我在网上做了一些研究,决定从一个修改过的内核开始。我所做的主要修改是,作为工作空间的本地内存分配现在是动态的。下面是我编写的内核: __kernel void clkernel_gemm(const uint M, const uint N, const uint K, const float alpha, __global const float* A, __global c

我试图实现一个通用的矩阵乘法OpenCL内核,它符合
C=α*a*B+β*C

内核 我在网上做了一些研究,决定从一个修改过的内核开始。我所做的主要修改是,作为工作空间的本地内存分配现在是动态的。下面是我编写的内核:

__kernel
void clkernel_gemm(const uint M, const uint N, const uint K, const float alpha,
                   __global const float* A, __global const float* B, const float beta, 
                   __global float* C, __local float* Asub, __local float* Bsub) {

  const uint row = get_local_id(0);
  const uint col = get_local_id(1);
  const uint TS = get_local_size(0); // Tile size
  const uint globalRow = TS * get_group_id(0) + row; // Row ID of C (0..M)
  const uint globalCol = TS * get_group_id(1) + col; // Row ID of C (0..N)

  // Initialise the accumulation register
  float acc = 0.0f;

  // Loop over all tiles
  const int numtiles = K / TS;
  for (int t = 0; t < numtiles; t++) {
    const int tiledRow = TS * t + row;
    const int tiledCol = TS * t + col;
    Asub[col * TS + row] = A[tiledCol * M + globalRow];
    Bsub[col * TS + row] = B[globalCol * K + tiledRow];

    barrier(CLK_LOCAL_MEM_FENCE);

    for(int k = 0; k < TS; k++) {
      acc += Asub[k * TS + row] * Bsub[col * TS + k] * alpha;
    }

    barrier(CLK_LOCAL_MEM_FENCE);
  }

  C[globalCol * M + globalRow] = fma(beta, C[globalCol * M + globalRow], acc);
}
问题 我遇到的问题是,我编写的单元测试(用Google的gtest编写)将随机失败,但只针对这个特定的内核。(我在同一
.cl
源文件中还有20个其他内核,它们100%通过了测试)

我有一个测试,它将一个1x4浮点矩阵
{0.0,1.0,2.0,3.0}
与它自身的一个转置版本相乘
{0.0},{1.0},{2.0},{3.0}
。预期的输出是
{14.0}

然而,我可以得到这个正确的结果,可能只有75%的时间

有时,我可以获得23.0(GTX 970)、17.01(GTX 750)或仅
-nan
和0.0(全部3台设备)。奇怪的是,各自不正确的结果似乎是这些设备所独有的;例如,我似乎无法在Intel CPU或GTX 750上获得23.0

我很困惑,因为如果我犯了算法或数学错误,错误应该是一致的;相反,我只是随机得到错误的结果

我做错了什么?

我尝试过的事情
  • 我已经验证了进入内核的数据是正确的
  • 我尝试将两个
    \uu local
    内存都初始化为0.0,但这会导致所有结果都出错(但坦率地说,我不确定如何正确初始化)
  • 我已经编写了一个测试程序,它只执行这个内核,以排除与我的程序其余部分交互的任何竞争条件,但是错误仍然发生
其他需要注意的事项
    我使用的是从.< /LI]直接检索的C++包装器。
  • 为了使用包装器,我定义了
    CL\u HPP\u MINIMUM\u OPENCL\u VERSION 120
    CL\u HPP\u TARGET\u OPENCL\u VERSION 120
  • 我正在使用
    -cl std=CL1.2
    标志编译内核
  • 所有
    cl::Buffer
    s仅使用
    cl\u MEM\u READ\u WRITE
    标志创建
  • 我正在Ubuntu 16.04、Ubuntu 14.04和Debian 8上测试这个
  • 我已经在安装了的英特尔CPU上测试了这一点。运行时报告它最多支持OpenCL1.2
  • 我已经在Nvidia GTX 760和970上测试过了。Nvidia最多只支持OpenCL 1.2
  • 所有3个平台都表现出频率变化的相同问题

    • 这看起来很复杂。有几件事需要解决,它们不适合评论,所以我将把这一切作为一个答案发布,即使它不能解决你的问题(现在)


      我很困惑,因为如果我做了一个算法或数学模型 错误,错误应该是一致的;相反,我得到了 错误的结果只是随机的

      这种行为是种族状况的典型指标


      我尝试将两个本地内存都初始化为0.0,但这会导致 所有的结果都会变得错误(但坦率地说,我真的不知道该怎么做) 正确初始化它)

      其实这是一件好事。最后我们有了一些一致性


      初始化本地存储器 可以使用工作项初始化本地内存,例如,如果您有一个由16项组成的1D工作组,并且您的本地内存由16个浮点组成,只需执行以下操作:

      local float* ptr = ...          // your pointer to local memory
      int idx = get_local_id(0);      // get the index for the current work-item
      ptr[idx] = 0.f;                 // init with value 0
      barrier(CLK_LOCAL_MEM_FENCE);   // synchronize local memory access within workgroup
      
      如果您的本地内存较大,例如64个浮点,则必须使用一个循环,其中每个工作项初始化4个值,至少这是最有效的方法。但是,没有人会阻止您使用每个工作项初始化本地内存中的每个值,即使这完全是胡说八道,因为您实际上要多次初始化它


      你的变化 看起来它是专门为使用二次瓷砖而设计的

      __local float Asub[TS][TS];
      __local float Bsub[TS][TS];
      
      不仅如此,在他们的示例32x32中,本地内存的大小还与工作组的大小相匹配。 当我查看本地内存的内核参数时,我可以看到您使用的参数在原始算法中定义为M和N。这似乎不正确。

      更新1 由于您没有说明原始算法是否适用于您,因此您应该这样做来查找错误:

      • 创建一组测试数据。确保只使用原始算法实际支持的数据大小(例如最小大小、x的倍数等)。此外,请使用大型数据集,因为某些错误仅在调度多个工作组时才会显示
      • 对测试数据集使用原始的、未更改的算法,并验证结果
      • 更改算法时,只需使用动态本地内存大小,而不是固定大小的本地内存,但请确保其大小与固定大小方法相同。这是你尝试过的,但我认为它失败了,因为我在“你的改变”中描述了这一点
      __local float Asub[TS][TS];
      __local float Bsub[TS][TS];