随机NaN和OpenCL内核

时间:2016-07-20 14:47:03

标签: opencl gpgpu blas

我正在尝试实现一般的矩阵 - 矩阵乘法OpenCL内核,符合C = α*A*B + β*C

内核

我在网上进行了一些研究,并决定使用this website中的修改内核作为起点。我所做的主要修改是将本地内存分配为工作空间现在是动态的。以下是我写的内核:

__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);
}

平铺大小(TS)现在是调用代码中定义的值,如下所示:

  // A, B and C are 2D matrices, their cl::Buffers have already been set up
  // and values appropriately set.

  kernel.setArg(0, (cl_int)nrowA);
  kernel.setArg(1, (cl_int)ncolB);
  kernel.setArg(2, (cl_int)ncolA);
  kernel.setArg(3, alpha);
  kernel.setArg(4, A_buffer);
  kernel.setArg(5, B_buffer);
  kernel.setArg(6, beta);
  kernel.setArg(7, C_buffer);
  kernel.setArg(8, cl::Local(sizeof(float) * nrowA * ncolB));
  kernel.setArg(9, cl::Local(sizeof(float) * nrowA * ncolB));

  cl::NDRange global(nrowA, ncolB);
  cl::NDRange local(nrowA, ncolB);

  status = cmdq.enqueueNDRangeKernel(kernel, cl::NDRange(0), global, local);

问题

我遇到的问题是,我编写的单元测试(用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。

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

我在这里做错了什么?

我尝试过的事情

  • 我已经确认进入内核的数据是正确的。
  • 我试图将__local内存初始化为0.0,但这会导致所有结果都出错(但坦率地说,我不确定如何正确初始化它)
  • 我编写了一个测试程序,它只执行这个内核来排除与我程序的其余部分交互的任何竞争条件,但是bug仍然会发生。

其他注意事项

  • 我正在使用直接从the Github page检索的C ++包装器。
  • 要使用包装器,我已定义CL_HPP_MINIMUM_OPENCL_VERSION 120CL_HPP_TARGET_OPENCL_VERSION 120
  • 我正在使用-cl-std=CL1.2标志编译内核。
  • 所有cl::Buffer仅使用 CL_MEM_READ_WRITE标志创建。
  • 我在Ubuntu 16.04,Ubuntu 14.04和Debian 8上测试它。
  • 我已经在装有Intel OpenCL Runtime 16.1 for Ubuntu的Intel CPU上测试了这个。运行时报告它最多支持OpenCL 1.2
  • 我在Nvidia GTX 760和970上都测试了这个.Nvidia只支持OpenCL 1.2。
  • 所有3个平台在不同频率下都会出现同样的问题。

1 个答案:

答案 0 :(得分:1)

这看起来很复杂。有几件事需要解决,它们不适合评论,所以我会将所有这些作为答案发布,即使它没有解决你的问题(还)。

  

我感到困惑,因为如果我做了算法或数学   错误,错误应该是一致的;相反,我得到了   不正确的结果只是随机的。

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

  

我试图将__local内存初始化为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个值,至少这是最有效的方式。但是,没有人会阻止您使用每个工作项来初始化本地内存中的每个值,即使这是完全无意义的,因为您实际上是多次初始化它。

您的更改

original algorithm似乎特别设计为使用二次切片。

__local float Asub[TS][TS];
__local float Bsub[TS][TS];

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

更新1

由于您尚未描述原始算法是否适合您,因此您应该采取以下措施来查找错误:

  • 创建一组testdata。确保仅使用原始算法实际支持的数据大小(例如,最小大小,x的多重等)。此外,使用大型数据集,因为某些错误仅显示是否分派了多个工作组。
  • 将原始的,未经改动的算法与testdata集一起使用并验证结果。
  • 仅更改算法,而不是使用固定大小的本地内存,使用动态本地内存大小,但请确保其大小与固定大小方法相同。这是你尝试过的,但我认为由于我在“你的改变”中描述的内容而失败了。