OpenCL内核可以处理某些数据类型,但不能处理其他数据类型

OpenCL内核可以处理某些数据类型,但不能处理其他数据类型,opencl,reduction,dot-product,Opencl,Reduction,Dot Product,我正在努力学习OpenCL,目前我正在练习制作不同的内核。在我尝试使用缩减方法制作点积时,遇到了一个我不理解的问题。当我使用int输入和输出运行代码时,它运行良好。当我将所有int类型更改为float类型(对于输入和输出)时,它会给出一个接近的结果,但稍微不接近。有人能找出这是为什么或是什么原因吗 这是主机代码 #include <stdio.h> #include <CL/cl.h> #include <string.h> #include <stdl

我正在努力学习OpenCL,目前我正在练习制作不同的内核。在我尝试使用缩减方法制作点积时,遇到了一个我不理解的问题。当我使用int输入和输出运行代码时,它运行良好。当我将所有int类型更改为float类型(对于输入和输出)时,它会给出一个接近的结果,但稍微不接近。有人能找出这是为什么或是什么原因吗

这是主机代码

#include <stdio.h>
#include <CL/cl.h>
#include <string.h>
#include <stdlib.h>

/*
*   IF ERROR, PRINT OUT OPENCL ERROR CODE
*/
void errorCheck(cl_int error){
    if(error < CL_SUCCESS){
        printf("Failed - OpenCL error #: %d\n", error);
        //exit(1);
    }
}

int main(){

cl_int err;
cl_uint numPlatforms = 0;
cl_uint numDevices = 0;
cl_platform_id * platformIDs = NULL;
cl_device_id * deviceIDs = NULL;
cl_context context = NULL;
cl_command_queue cmdQueue = NULL;
cl_program program = NULL;
cl_kernel kernel = NULL;

FILE *program_handle;
char *program_buffer;
size_t program_size;

cl_mem a_buff = NULL;
cl_mem b_buff = NULL;
cl_mem c_buff = NULL;
int * a = NULL;
int * b = NULL;
int * c = NULL;
int correct_dot = 0;
int ELEMENTS = 1024;

const int datasize = sizeof(int)*ELEMENTS;

a = (int *)malloc(datasize);
b = (int *)malloc(datasize);
c = (int *)malloc(datasize);

int i;
for(i = 0; i<ELEMENTS; i++){
    a[i] = i;
    b[i] = i;
    correct_dot += (int)i*i;
}

/*
**RETRIEVE NUMBER OF PLATFORMS
*/
err = clGetPlatformIDs(0, NULL, &numPlatforms);
errorCheck(err);

/*
**ALLOCATE SPACE FOR/RETRIEVE IDS OF PLATFORMS
*/
platformIDs = (cl_platform_id*)malloc(numPlatforms*sizeof(cl_platform_id));
err = clGetPlatformIDs(numPlatforms, platformIDs, NULL);
errorCheck(err);

/*
**RETRIEVE NUMBER OF DEVICES
*/
err = clGetDeviceIDs(platformIDs[0], CL_DEVICE_TYPE_GPU, 0, NULL, &numDevices);
errorCheck(err);

/*
**ALLOCATE SPACE FOR/RETRIEVE IDS OF DEVICES
*/
deviceIDs = (cl_device_id*)malloc(numDevices*sizeof(cl_device_id));
err = clGetDeviceIDs(platformIDs[0], CL_DEVICE_TYPE_GPU, numDevices, deviceIDs, NULL);
errorCheck(err);

/*
**GET INFO ON DEVICE
*/
cl_uint * info;
size_t info_size;

err = clGetDeviceInfo(deviceIDs[0], CL_DEVICE_MAX_WORK_GROUP_SIZE, 0, NULL, &info_size);
info = (cl_uint *) malloc(info_size);
err = clGetDeviceInfo(deviceIDs[0], CL_DEVICE_MAX_WORK_GROUP_SIZE, info_size, info, NULL);
errorCheck(err);


/*
**CREATE CONTEXT
*/
context = clCreateContext(NULL, numDevices, deviceIDs, NULL, NULL, &err);
errorCheck(err);

/*
**CREATE COMMAND QUEUE
*/
cmdQueue = clCreateCommandQueue(context, deviceIDs[0], 0, &err);
errorCheck(err);

/*
**CREATE BUFFERS
*/
a_buff = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, datasize, a, &err);
errorCheck(err);
b_buff = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, datasize, b, &err);
errorCheck(err);
c_buff = clCreateBuffer(context, CL_MEM_WRITE_ONLY | CL_MEM_COPY_HOST_PTR, datasize, c, &err);
errorCheck(err);

err = clEnqueueWriteBuffer(cmdQueue, a_buff, CL_TRUE, 0, datasize, a, 0, NULL, NULL);
errorCheck(err);
err = clEnqueueWriteBuffer(cmdQueue, b_buff, CL_TRUE, 0, datasize, b, 0, NULL, NULL);
errorCheck(err);

/*
**READ PROGRAM FROM FILE
*/
program_handle = fopen("kernels/mvdot.cl", "r");
if(program_handle == NULL) {
    perror("Couldn't find the program file");
    exit(1);
}
fseek(program_handle, 0, SEEK_END);
program_size = ftell(program_handle);
rewind(program_handle);
program_buffer = (char*)malloc(program_size + 1);
program_buffer[program_size] = '\0';
fread(program_buffer, sizeof(char), program_size, program_handle);
fclose(program_handle);

/*
**CREATE PROGRAM
*/
program = clCreateProgramWithSource(context, 1, (const char**)&program_buffer, &program_size, &err);
errorCheck(err);

/*
**BUILD PROGRAM
*/
err = clBuildProgram(program, 1, deviceIDs, NULL, NULL, NULL);
errorCheck(err);
if (err == CL_BUILD_PROGRAM_FAILURE) {
    // Determine the size of the log
    size_t log_size;
    clGetProgramBuildInfo(program, deviceIDs[0], CL_PROGRAM_BUILD_LOG, 0, NULL, &log_size);
    // Allocate memory for the log
    char *log = (char *) malloc(log_size);
    // Get the log
    clGetProgramBuildInfo(program, deviceIDs[0], CL_PROGRAM_BUILD_LOG, log_size, log, NULL);
    // Print the log
    printf("%s\n", log);
}

/*
**CREATE KERNEL
*/
kernel = clCreateKernel(program, "mvdot", &err);
errorCheck(err);

/*
**SET UP KERNEL ARGS
*/
err = clSetKernelArg(kernel, 0, sizeof(cl_mem), (void *) &a_buff);  //input A
err |= clSetKernelArg(kernel, 1, sizeof(cl_mem), (void *) &b_buff); //input B
err |= clSetKernelArg(kernel, 2, datasize, NULL);           //local scratch 
err |= clSetKernelArg(kernel, 3, sizeof(cl_mem), (void *) &c_buff); //output C
err |= clSetKernelArg(kernel, 4, sizeof(cl_int), (void *) &ELEMENTS);   //size
errorCheck(err);

/*
**ENQUEUE KERNEL FOR EXECUTION
*/
const size_t global_work_size = ELEMENTS;
const size_t local_work_size = info[0];
err = clEnqueueNDRangeKernel(cmdQueue, kernel, 1, NULL, &global_work_size, &local_work_size, 0, NULL, NULL);
errorCheck(err);


/*
**READ OUTPUT
*/
err = clEnqueueReadBuffer(cmdQueue, c_buff, CL_TRUE, 0, datasize, c, 0, NULL, NULL);
errorCheck(err);

/*
**CHECK OUTPUT
*/
int our_dot = 0;
printf("C = [");
//elements/info[0] gives # of work groups
for(i = 0; i<ELEMENTS; i++){
    printf(" %d ", c[i]);
    our_dot += c[i];
}
printf("]\n");

printf("Our dot = %d\n", our_dot);
printf("Correct dot = %d\n", correct_dot);

if(our_dot == correct_dot){
    printf("PASSED\n");
} else {
    printf("FAILED\n");
}

/*
**FREE RESOURCES
*/
clReleaseKernel(kernel);
clReleaseProgram(program);
clReleaseCommandQueue(cmdQueue);
clReleaseContext(context);

clReleaseMemObject(a_buff);
clReleaseMemObject(b_buff);
clReleaseMemObject(c_buff);

free(platformIDs);
free(deviceIDs);
free(program_buffer);

free(a);
free(b);
free(c);

}
#包括
#包括
#包括
#包括
/*
*如果出现错误,请打印出OPENCL错误代码
*/
无效错误检查(cl_int错误){
如果(错误对于(i=0;i而言,由于不同的工作项在连续的循环迭代中读取和写入同一位置,因此在本地内存缩减中存在竞争条件。在每次迭代中都需要设置障碍,并且在不执行任何工作时也应避免重写值(因为这将与尝试读取位置的其他工作项冲突)

编写这种工作组缩减的典型方法如下所示:

//Use local memory to compute the sum of the products
for (int offset = local_size/2; offset > 0; offset/=2) {
    barrier(CLK_LOCAL_MEM_FENCE);
    if (l_id < offset)
      scratch[l_id] = scratch[l_id] + scratch[l_id + offset];
}
barrier(CLK_LOCAL_MEM_FENCE);
//使用本地内存计算乘积之和
对于(int offset=local_size/2;offset>0;offset/=2){
屏障(CLK_本地_MEM_围栏);
if(l_id<偏移量)
划痕[l_id]=划痕[l_id]+划痕[l_id+偏移];
}
屏障(CLK_本地_MEM_围栏);
如果您知道正在执行的硬件的SIMD宽度,则可以展开循环的最后几次迭代并无障碍地执行它们(如果工作项在锁定步骤中执行,则不再需要同步)



正如@DarkZeros在评论中指出的,这里的罪魁祸首是浮点的精度(尽管上面的竞争条件仍然需要处理)。您可以通过将数据类型更改为
double
—这样做会使您的代码通过我的GPU上的测试来验证这一点。

您是否考虑到浮点类型不精确?它们中始终存在一些十进制错误。我得到的差异比十进制错误大得多,但与输出相比仍然很小。我非常高兴rry我忘了写下它们,但这有点像主机计算了300000000.00的值,内核计算了300000400.00。这还可能归因于十进制错误吗?(正常)浮点值的精度(因为异常表示具有不同的精度),大约是浮点值本身的1/2^24或=~0.00000012。因此,对于3e8输出,错误在+-35.7范围内。但是,由于求和循环,错误可能会堆积起来。我同意你的答案,但我认为他没有遇到这种情况。如果是这种情况,则在整数情况下也会失败,但事实并非如此。我的理论是工作组中的线程在进入循环之前同步,循环中的迭代次数对于所有WI都是恒定的。线程在循环中一路同步运行(无需障碍)。但是,是的,它内部应该是一个障碍。假设这种行为是不安全的,特别是在CPU上。@DarkZeros-我同意。我的第一个想法是整数和FP算术指令有不同的延迟,所以争用可能是一个问题,而不是另一个问题。但是在实际运行代码时,很明显,精度是个问题莱姆,不是数据竞赛。
//Use local memory to compute the sum of the products
for (int offset = local_size/2; offset > 0; offset/=2) {
    barrier(CLK_LOCAL_MEM_FENCE);
    if (l_id < offset)
      scratch[l_id] = scratch[l_id] + scratch[l_id + offset];
}
barrier(CLK_LOCAL_MEM_FENCE);