我正在尝试实现一般的矩阵 - 矩阵乘法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,但这会导致所有结果都出错(但坦率地说,我不确定如何正确初始化它)CL_HPP_MINIMUM_OPENCL_VERSION 120
和CL_HPP_TARGET_OPENCL_VERSION 120
。-cl-std=CL1.2
标志编译内核。cl::Buffer
仅使用 CL_MEM_READ_WRITE
标志创建。答案 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的参数。这似乎不正确。
由于您尚未描述原始算法是否适合您,因此您应该采取以下措施来查找错误: