为什么我的OpenCL内核在nVidia驱动程序上失败,而不是英特尔(可能的驱动程序错误)?

时间:2017-09-25 15:49:06

标签: opencl nvidia gpgpu

我最初写了一个OpenCL程序来计算非常大的厄米特矩阵,其中内核计算矩阵中的一对条目(上三角形部分及其下三角形补码)。

很早以前,我发现了一个非常奇怪的问题,如果我的内核大小正好是55,那么第27个内核线程将无法执行。使用nVidia驱动程序和GPU加速时,会出现此问题仅 。当我在CPU上使用Intel驱动程序运行它时,我发现第27个内核线程执行得很好。较大和较小的内核大小似乎没有出现问题。

认为它可能是我的代码中的内容,我将我的问题提炼到以下非常简单的内核:

__kernel void testIndex(__global float* outMatrix, unsigned int sizeN)
{
    //k is the linear kernel ID (related to but not exactly the linear index into the outMatrix)
    int k = get_global_id(0);
    //i'th index (Row or Y)
    int i = floor((2 * sizeN+1 - sqrt((float)((2 * sizeN + 1) * (2 * sizeN + 1) -8 * k) )) /2);

    //j'th index (Column or X)
    int j = k - sizeN * i + i * (i - 1) / 2;
    j += i;

    //Index bounds check... If we're greater than sizeN, we're an idle core.
    //(OpenCL will queue up a fixed block size of worker threads, some of them may be out of bounds)
    if(j >= sizeN || i >= sizeN)
    {
        return;
    }

    //Identity case. The original kernel did some special stuff here,
    //but I've just replaced it with the K index code.
    if(i == j)
    {
        outMatrix[i * sizeN +j] = k;
        return;
    }

    outMatrix[i * sizeN + j] = k;

    //Since we only have to calculate the upper triangle of our matrix,
    //(the lower triangle is just the complement of the upper),
    //this test sets the lower triangle to -9999 so it's easier to see
    //how the indexing plays out...

    outMatrix[j * sizeN + i] = -9999.0;

 }

outMatrix 是输出矩阵, sizeN 是一侧的方阵的大小(即矩阵是sizeN x sizeN)。

我使用以下主机代码计算并执行我的内核大小:

size_t kernelSize = elems * (elems + 1) / 2;
cl::NDRange globalRange(kernelSize);
cl::NDRange localRange(1);
cl::Event event;

clCommandQueue.enqueueNDRangeKernel(testKernel, cl::NullRange, globalRange, cl::NullRange, NULL, &event);
event.wait();

elems sizeN 相同(即矩阵大小的平方根)。在这种情况下,elems = 10(因此内核大小为55)。

如果我打印出我读回的矩阵,我会得到以下内容(使用boost ublas矩阵格式化):

[10,10] ((    0,     1,     2,     3,     4,     5,     6,     7,     8,    9),
        ((-9999,    10,    11,    12,    13,    14,    15,    16,    17,   18),
        ((-9999, -9999,    19,    20,    21,    22,    23,    24,    25,   26),
        ((-9999, -9999, -9999,  JUNK,    28,    29,    30,    31,    32,   33),
        ((-9999, -9999, -9999, -9999,    34,    35,    36,    37,    38,   39),
        ((-9999, -9999, -9999, -9999, -9999,    40,    41,    42,    43,   44), 
        ((-9999, -9999, -9999, -9999, -9999, -9999,    45,    46,    47,   48),
        ((-9999, -9999, -9999, -9999, -9999, -9999, -9999,    49,    50,   51),    
        ((-9999, -9999, -9999, -9999, -9999, -9999, -9999, -9999,    52,   53),   
        ((-9999, -9999, -9999, -9999, -9999, -9999, -9999, -9999, -9999,   54))

其中“JUNK”是基于当时在该存储器中发生的任何事情的随机值。这当然是可疑的,因为27基本上是内核中的中间点。

为了完整起见,使用以下代码回读矩阵结果:

boost::scoped_array<float> outMatrixReadback(new float[elems * elems]);
clCommandQueue.enqueueReadBuffer(clOutputMatrixBuffer, CL_TRUE, 0, elems * elems * sizeof(float), outMatrixReadback.get());

我正在做(也许是不正确的)假设,因为代码在Intel CPU上运行良好,代码本身没有一些基本的错误。

那么,在nVidia卡上编程OpenCL时,是否有一些问题我不知道,或者我不幸发现驱动程序错误?

硬件/操作系统规范

  • nVidia GTX 770

  • RHEL Server版本6.4(圣地亚哥)

  • 英特尔OpenCL 1.2 4.4.4.0.134 SDK标头

  • nVidia GeForce驱动程序384.69

  • Intel Xeon CPU E6520 @ 2.4 GHz

2 个答案:

答案 0 :(得分:0)

在与nVidia讨论后,技术代表确认这是可重复的和驱动程序错误。提交了一个错误报告,但遗憾的是我被告知nVidia没有专门的OpenCL开发团队,因此无法提供修复时间表。

修改 在终于从nVidia回复之后,解决方法显然是在CL内核中使用pow()而不是sqrt(),因为sqrt()显然是bug的来源。

答案 1 :(得分:0)

以下是NVIDIA的答复,一个正在解决,一个是解决方案。我们只是在我们的错误系统中发布,但没有得到您的答复,因此我们在此处发布解决方法/解决方案。谢谢!

1。解决方法: 我们对此问题进行了本地复制,并从我们的开发团队中提出了一种解决方法,请尝试以下修改,让我们知道它是否有效。谢谢您的耐心。

这是您可以尝试的解决方法。 在文件testIndex.cl中将sqrt()更改为使用pow(),请参见下面的代码段。

//i'th index (Row or Y)
// original version
// FAIL float sqrt
//int i = floor((2 * sizeN+1 - sqrt((float)((2 * sizeN + 1) * (2 * sizeN + 1) -8 * k) )) /2);
// PASS float pow
//int i = floor((2 * sizeN+1 - pow((float)((2 * sizeN + 1) * (2 * sizeN + 1) -8 * k), 0.5f)) /2);

2。解决方案: 今天有新的问题解决方法,请查看下面的描述和方法来解决问题,并让我们知道它是否有效。谢谢。

OpenCL 1.2规范第5.6.4.2节说: -cl-fp32-正确四舍五入的分隔sqrt。 clBuildProgram或clCompileProgram的-cl-fp32-corrected-rounded-divide-sqrt构建选项允许应用程序指定正确舍入程序源中使用的单精度浮点除法(x / y和1 / x)和sqrt 。如果未指定此构建选项,则单精度浮点除法和sqrt的最小数值精度如OpenCL规范的7.4节所定义。

在7.4节中,表格显示: sqrt <= 3 ulp

这两个值在这里产生: 根= 15.0000009537 根= 15.0000000000 标准仅允许1ULP的差异。有关ULP的介绍,请参见https://en.wikipedia.org/wiki/Unit_in_the_last_place。 通过指定program.build(devices,“ -cl-fp32-corrected-rounded-divide-sqrt”),您基本上需要在opencl编译时使用“ -cl-fp32-正确地舍入-divide-sqrt”选项;