【发布时间】:2018-03-06 15:54:35
【问题描述】:
我最初编写了一个 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 服务器版本 6.4(圣地亚哥)
英特尔 OpenCL 1.2 4.4.4.0.134 SDK 标头
nVidia GeForce 驱动程序 384.69
英特尔至强 CPU E6520 @ 2.4 GHz
【问题讨论】:
-
CPU 和 GPU 之间存在许多差异 - 主要是内存一致性和算术精度。这里没有明显的罪魁祸首,但是 - 疯狂的建议:这可能是平方根的精度问题吗?将
floor()更改为round()有什么不同吗? GPU 上的平方根是出了名的不精确,但我确实希望sqrt(225.0) = 15.0成立…… -
据我所知,对于 k=27,内核根本没有执行。如果我做一些像“if(k == 27) outMatrix[0] = 4242; return;”这样简单的事情,我没有得到任何迹象表明 K 曾经等于 27。
-
如果您注释掉内核中除
if (k == 27) printf("Thread reached\n");之外的所有其他代码。输出是什么? -
奇怪的是,that 可以工作,但是如果你有任何内核线程做任何其他操作,27 将无法执行。
-
您是否尝试过明确地将“自然”数量的工作项排入队列?例如。 64 而不是 55。奇怪的工作规模可能是实现中测试不佳的代码路径。