OpenCL学习


文章目录

  • OpenCL学习
  • 相关知识
  • 一、OpenCL简介
  • 1.1 平台模型
  • 1.2 执行模型
  • 二、OpenCL使用
  • 2.1 平台信息查询
  • 2.2 设备信息查询
  • 2.3 创建上下文
  • 2.4 创建命令队列
  • 2.5 创建程序对象
  • 2.6 编译程序对象
  • 2.7 创建buffer
  • 2.8 读写buffer
  • 2.9 创建内核
  • 2.10 设置内核参数
  • 2.11 排队执行内核
  • 2.12 OpenCL错误码说明
  • 三、参考资料



相关知识

  • 异构多核处理器
一般指在CPU里集成了CPU与其他模块(GPU/DSP/FPGA)一起同步计算,因为是属于不同的设备但要一同工作,所以需要设计可以让他们协同工作。海思Hi3559A芯片架构如下:

openeuler修改成centos8数据源_CL

一、OpenCL简介

1.1 平台模型

  • Opencl的平台模型是由一个主机和若干个设备组成,也就是一个Host+多个Device的组织形式。这些设备可以是CPU、GPU、DSP、FPGA等。这种多种处理器混合的结构,就组成了异构并行计算平台。在这些Device中又包含了一个或者多个计算单元(Computing Units, CU),每个计算单元中可以包括若干个处理元件(Processing Elements, PE),内核程序(kernel)最终就在各个PE上并行运行。

1.2 执行模型

  • 理解执行模型对于理解OpenCl的程序至关重要,所谓执行模型是指OpenCL程序的运行方式,OpenCL的程序包括Host程序和Kernel程序两部分。Host程序运行在主机上,Kernel程序的执行依赖于Host程序中定义的上下文。OpenCL的执行模型的核心理念就是通过主机管理运行在Kernel上的程序(一般OpenCL代码中包含多个kernel程序)。
  • 由于OpenCL是数据并行的,同一时刻会在PE上同时执行同一条kernel函数,只是数据不同而已。相同时刻同时执行一条kernel,怎么区分哪个PE上跑的是什么数据?OpenCL会为每个PE执行的kernel分配index, 用于区分相同函数不同的数据,为OpenCL抽象的最小可执行单位被称为工作节点(Work Item),每个kernel执行时都会分配一个全局的工作节点 index。
  • 每个Kernel程序运行在一个工作节点上(Work Item),同时有一个索引与之对应,通过这个索引建立了Kernel程序和它所要处理的数据之间的关系。程序申请的所有处理节点构成的组合就称之为工作空间。换句话说,工作空间就是当前程序所需要的所有处理节点的集合,它可以是一维、二维和三维的,每一个work item都有一个全局的ID,称为global ID。对应的每一个运行在处理节点上的kernel程序都可以通过一个全局索引访问。
  • 除了全局的工作空间外,opencl还提供了将工作空间进一步划分为工作组空间(work group)的结构,这就是工作组的由来。工作组的维度必须和工作空间的维度相同,并且共组的每个维度都可以整除工作空间的每个对应的维度。这样一来,每个工作组都有一个work group ID,每个工作组内,有多个work item,每个work item又对应有一个局部的ID,称为local ID。
  • Opencl 将相同的kernel下的work-item,以相同数量的划分为work-group。也就是相同kernel下的work-group之间的work-item是相同的。opencl能够保证同一条 work-group下的work-item是并行的,但是不同的work-group下的不同work-item并不能保证并行,所以opencl只能够支持同一work-group的work-item的同步。其次work-item大小的划分是和硬件相关的,每个芯片不一样。work-item和work-group可以和硬件很好的映射起来,这是opencl抽象模型的优势之一, work-item可以映射为GPU的PE, 每个GPU有多个CU,在同一个CU下面有多个PE。在CU里面有多个调度器,可以保证在该CU下的PE能够并行运行,故可以将work-item映射为PE, work-group映射为CU
  • 注意:
    1.每个GPU都有固定的处理单元,如何调度这些单元工作是程序优化的关键;
    2.工作空间和工作组空间每个维度上的整除关系一定要注意。

二、OpenCL使用

#include <CL/cl.h>
#include <CL/cl_ext.h>

void main()
{
	clGetPlatformIDs();  //查询平台
	clGetDeviceIDs();  //查询平台上的设备
	clCreateContext();  //创建上下文
	clCreateCommandQueue();  //创建命令队列
	clCreateProgramWithSource();  //创建CL程序
	clBuildProgram();  //编译CL程序
	clCreateKernel();  //创建CL内核
	clCreateBuffer();  //创建内存
	clSetKernelArg();  //设置CL程序参数
	clEnqueueNDRangeKernel();  //将命令队列在设备上排队执行内核
	clFinish();  //阻塞host上的执行线程,直到命令队列上的所有命令执行完毕
}

2.1 平台信息查询

/* Platform API */
extern CL_API_ENTRY cl_int CL_API_CALL
clGetPlatformIDs(cl_uint          num_entries,
                 cl_platform_id * platforms,
                 cl_uint *        num_platforms) CL_API_SUFFIX__VERSION_1_0;

extern CL_API_ENTRY cl_int CL_API_CALL
clGetPlatformInfo(cl_platform_id   platform,
                  cl_platform_info param_name,
                  size_t           param_value_size,
                  void *           param_value,
                  size_t *         param_value_size_ret) CL_API_SUFFIX__VERSION_1_0;

2.2 设备信息查询

/* Device APIs */
extern CL_API_ENTRY cl_int CL_API_CALL
clGetDeviceIDs(cl_platform_id   platform,
               cl_device_type   device_type,
               cl_uint          num_entries,
               cl_device_id *   devices,
               cl_uint *        num_devices) CL_API_SUFFIX__VERSION_1_0;

extern CL_API_ENTRY cl_int CL_API_CALL
clGetDeviceInfo(cl_device_id    device,
                cl_device_info  param_name,
                size_t          param_value_size,
                void *          param_value,
                size_t *        param_value_size_ret) CL_API_SUFFIX__VERSION_1_0;

2.3 创建上下文

extern CL_API_ENTRY cl_context CL_API_CALL
clCreateContext(const cl_context_properties * properties,
                cl_uint              num_devices,
                const cl_device_id * devices,
                void (CL_CALLBACK * pfn_notify)(const char * errinfo,
                                                const void * private_info,
                                                size_t       cb,
                                                void *       user_data),
                void *               user_data,
                cl_int *             errcode_ret) CL_API_SUFFIX__VERSION_1_0;

// 示例 
ptOpenclHdl->context = clCreateContext(properties, 1, &ptOpenclHdl->device, NULL, NULL, &s32Ret);
if (s32Ret != CL_SUCCESS) {
     DPRINT_ERROR("Error creatting context, the error code is: %d", s32Ret);
     return s32Ret;
}
  • 创建OpenCL上下文。OpenCL上下文是用一个或多个设备创建的。OpenCL运行时使用上下文来管理命令队列、内存、程序和内核对象等对象,以及在上下文中指定的一个或多个设备上执行内核。
  • 参数说明:
  • properties 指定上下文属性名称及其相应值的列表。每个属性名后面紧跟着相应的所需值,属性可以为空,下面是实现在*.cl中进行打印的属性配置。
void printf_callback(const char *buffer, size_t len, size_t complete, void *user_data)
	{
	    printf("%.*s", len, buffer);
	}
	cl_context_properties properties[] =
    {
        /* Enable a Osp_printf callback function for this context. */
        CL_PRINTF_CALLBACK_ARM, (cl_context_properties)printf_callback,

        /* Request a minimum Osp_printf buffer size of 4MiB for devices in the
		context that support this extension. */
        CL_PRINTF_BUFFERSIZE_ARM, (cl_context_properties)0x100000,

        CL_CONTEXT_PLATFORM, (cl_context_properties)ptOpenclHdl->platforms,
        0 };
  • num_devices 是在devices参数中指定的设备数;
  • devices是一个指针,指向clGetDeviceIDs()为平台返回的唯一设备列表;
  • pfn_notify 是一个可以由应用程序注册的回调函数。OpenCL实现将使用这个回调函数来报告在这个上下文中发生的错误的信息。这个回调函数可以由OpenCL实现异步调用。应用程序负责确保回调函数是线程安全的。此回调函数的参数为:

参数

类型

描述

errinfo

const char *

指向错误字符串的指针

private_info

const void *

表示指向OpenCL实现返回的二进制数据的指针,该数据可用于记录有助于调试错误的附加信息

user_data

void*

指向用户提供的数据的指针

如果pfn_notify为空,则不注册回调函数,否则,user_data将作为user_data参数传递。用户数据可以为空。

  • errcode_ret 将返回错误码

2.4 创建命令队列

extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_2_DEPRECATED cl_command_queue CL_API_CALL
clCreateCommandQueue(cl_context                     context,
                     cl_device_id                   device,
                     cl_command_queue_properties    properties,
                     cl_int *                       errcode_ret) CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED;

// 示例 
//创建命令队列 (CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE:无序执行)
ptOpenclHdl->queue = clCreateCommandQueue(ptOpenclHdl->context, ptOpenclHdl->device, 0, &s32Ret);
if (s32Ret < 0){
     DPRINT_ERROR("Coudn't create the command queue %d\n", s32Ret);
     return s32Ret;
}
  • 创建命令队列。OpenCL对象(如内存、程序和内核对象)是使用上下文创建的,而对这些对象的操作是使用命令队列执行的。命令队列可用于按顺序对一组操作(称为命令)进行排队。
  • properties指定命令队列的属性列表,指定命令队列中排队的命令是按顺序执行还是无序执行。默认按照顺序执行命令。

2.5 创建程序对象

extern CL_API_ENTRY cl_program CL_API_CALL
clCreateProgramWithSource(cl_context        context,
                          cl_uint           count,
                          const char **     strings,
                          const size_t *    lengths,
                          cl_int *          errcode_ret) CL_API_SUFFIX__VERSION_1_0;
                          
// 示例 
// 从program buffer 写入到context
ptOpenclHdl->program = clCreateProgramWithSource(ptOpenclHdl->context, 1, 
					(const char **)&program_buffer, (const size_t *)&program_size, &s32Ret); 
if (s32Ret < 0){
     DPRINT_ERROR("Couldn't create the program!");
     return -1;
 }
  • 为上下文创建程序对象,并将字符串数组中strings指定的源代码加载到程序对象中。

2.6 编译程序对象

extern CL_API_ENTRY cl_int CL_API_CALL
clBuildProgram(cl_program           program,
               cl_uint              num_devices,
               const cl_device_id * device_list,
               const char *         options,
               void (CL_CALLBACK *  pfn_notify)(cl_program program,
                                                void * user_data),
               void *               user_data) CL_API_SUFFIX__VERSION_1_0;

// 示例
s32Ret = clBuildProgram(ptOpenclHdl->program, 1, &ptOpenclHdl->device, NULL, NULL, NULL);
if (s32Ret < 0){
	DPRINT_ERROR("Error building program, the error code is: %d", s32Ret);
    return s32Ret;
 }

2.7 创建buffer

extern CL_API_ENTRY cl_mem CL_API_CALL
clCreateBuffer(cl_context   context,
               cl_mem_flags flags,
               size_t       size,
               void *       host_ptr,
               cl_int *     errcode_ret) CL_API_SUFFIX__VERSION_1_0;
               
// 示例               
ptOpenclHdl->clmLutBlend = clCreateBuffer(ptOpenclHdl->context, CL_MEM_READ_WRITE | CL_MEM_ALLOC_HOST_PTR, MAX_FACE_NUM * MAX_FACE_WIDTH * MAX_FACE_HEIGHT * 3 * sizeof(unsigned char), NULL, &s32Ret);
if (ptOpenclHdl->clmLutBlend == (cl_mem)0)
{
	DPRINT_ERROR("Creat clmLutBlend failed!\n");
	return -1;
}
  • flags是一个位字段,用于指定分配和使用信息,例如应该用于分配缓冲区对象的内存区域以及如何使用它。表5.3描述了标志的可能值:

cl_mem_flags

描述

CL_MEM_READ_WRITE

指定内存对象将由内核读写。默认值

CL_MEM_WRITE_ONLY

指定内存对象将被内核写入但不被读取。

CL_MEM_READ_ONLY

指定内存对象在内核中使用时是只读内存对象。

CL_MEM_USE_HOST_PTR

仅在host_ptr 不为空时有效。采用在主机端分配内存空间,让设备端有访问权限(如果硬件不支持这种方式的话,则在设备端开辟一块缓存,存储host_ptr里的内容),特点是主机和设备端都可以访问。类似于将主机上的内存块映射到设备上供设备使用,虽然是同一块内存,但是有可能在设备上对该内存操作了,在主机上访问的结果不正确,因此有可能需要对数据进行同步。

CL_MEM_ALLOC_HOST_PTR

仅在host_ptr 为空时有效。在主机端可访问的存储空间给设备端分配内存空间。

CL_MEM_ALLOC_HOST_PTR和CL_MEM_USE_HOST_PTR是互斥的。

CL_MEM_COPY_HOST_PTR

仅在host_ptr 不为空时有效。在设备端分配内存,并将hsot_ptr指针指向的内存,拷贝size大小的数据到设备内存上去。 由于是直接拷贝,因此在设备端对数据进行修改后,主机端不能够获取相应数据。

CL_MEM_COPY_HOST_PTR和CL_MEM_USE_HOST_PTR是互斥的。

CL_MEM_COPY_HOST_PTR可与CL_MEM_ALLOC_HOST_PTR一起使用,以初始化使用主机可访问(e.g. PCIe)内存分配的cl_mem对象的内容。

  • size 是待分配的缓冲区内存对象的大小。
  • host_ptr 是指向可能已由应用程序分配的缓冲区数据的指针。host_ptr 指向的缓冲区大小必须大于等于size 字节。

2.8 读写buffer

  • 使用内存映射方式
// 映射
extern CL_API_ENTRY void * CL_API_CALL
clEnqueueMapBuffer(cl_command_queue command_queue,
                   cl_mem           buffer,
                   cl_bool          blocking_map,
                   cl_map_flags     map_flags,
                   size_t           offset,
                   size_t           size,
                   cl_uint          num_events_in_wait_list,
                   const cl_event * event_wait_list,
                   cl_event *       event,
                   cl_int *         errcode_ret) CL_API_SUFFIX__VERSION_1_0;
// 解映射
extern CL_API_ENTRY cl_int CL_API_CALL
clEnqueueUnmapMemObject(cl_command_queue command_queue,
                        cl_mem           memobj,
                        void *           mapped_ptr,
                        cl_uint          num_events_in_wait_list,
                        const cl_event * event_wait_list,
                        cl_event *       event) CL_API_SUFFIX__VERSION_1_0;

内存映射可以将设备上的内存对象映射到主机上的内存区域,一旦我们映射了对象,我们就可以对主机上的内存对象进行读取/写入操作。但需要注意的是可能存在数据同步问题,即在主机上的内存区域写入数据后设备内存对象不能及时获取完整数据,导致数据不一致。目前实验分析海思3559A平台不存在此问题,但RK3588平台存在数据不同步问题。

  • 使用读/写buffer方式
// 写buffer
extern CL_API_ENTRY cl_int CL_API_CALL
clEnqueueWriteBuffer(cl_command_queue   command_queue,
                     cl_mem             buffer,
                     cl_bool            blocking_write,
                     size_t             offset,
                     size_t             size,
                     const void *       ptr,
                     cl_uint            num_events_in_wait_list,
                     const cl_event *   event_wait_list,
                     cl_event *         event) CL_API_SUFFIX__VERSION_1_0;
// 读buffer
extern CL_API_ENTRY cl_int CL_API_CALL
clEnqueueReadBuffer(cl_command_queue    command_queue,
                    cl_mem              buffer,
                    cl_bool             blocking_read,
                    size_t              offset,
                    size_t              size,
                    void *              ptr,
                    cl_uint             num_events_in_wait_list,
                    const cl_event *    event_wait_list,
                    cl_event *          event) CL_API_SUFFIX__VERSION_1_0;

读/写方式可以将设备上的内存对象读入主机内存或者将主机内存写入设备上的内存对象,此方式不存在数据不同步问题。

  • 与常规读/写相比,在不影响结果的基础上,内存映射可以显着提高性能;

2.9 创建内核

extern CL_API_ENTRY cl_kernel CL_API_CALL
clCreateKernel(cl_program      program,
               const char *    kernel_name,
               cl_int *        errcode_ret) CL_API_SUFFIX__VERSION_1_0;

// 示例               
ptOpenclHdl->clkLutKernel = clCreateKernel(ptOpenclHdl->program, "trilinear_b", &s32Ret); // trilinear_b batch
if (s32Ret != CL_SUCCESS) {
    DPRINT_ERROR("Error creatting clkLutKernel, the error code is: %d", s32Ret);
    return -1;
}
  • program 是一个具有成功构建的可执行文件的程序对象。
  • kernel_name是程序中用__kernel限定符声明的函数名。

2.10 设置内核参数

extern CL_API_ENTRY cl_int CL_API_CALL
clSetKernelArg(cl_kernel    kernel,
               cl_uint      arg_index,
               size_t       arg_size,
               const void * arg_value) CL_API_SUFFIX__VERSION_1_0;
               
// 示例  
s32Ret = clSetKernelArg(ptOpenclHdl->clkLutKernel, 6, sizeof(cl_mem), &ptOpenclHdl->clmLutBlend);
  • clSetKernelArg()用于为内核的特定参数设置参数值。
  • kernel 是一个有效的内核对象。
  • arg_index 是参数索引。
  • arg_value 是指向数据的指针

2.11 排队执行内核

extern CL_API_ENTRY cl_int CL_API_CALL
clEnqueueNDRangeKernel(cl_command_queue command_queue,
                       cl_kernel        kernel,
                       cl_uint          work_dim,
                       const size_t *   global_work_offset,
                       const size_t *   global_work_size,
                       const size_t *   local_work_size,
                       cl_uint          num_events_in_wait_list,
                       const cl_event * event_wait_list,
                       cl_event *       event) CL_API_SUFFIX__VERSION_1_0;
                       
// 示例 
size_t globalThreads[3] = { (size_t)stRoi.u32RoiW, (size_t)stRoi.u32RoiH, (size_t)u32FaceNum};
s32Ret = clEnqueueNDRangeKernel(ptOpenclHdl->queue, ptOpenclHdl->clkLutKernel, 3, NULL, globalThreads, NULL, 0, NULL, NULL);
if (s32Ret != CL_SUCCESS){
	DPRINT_ERROR("[Tril]Error %d clkBlendKernel clEnqueueNDRangeKernel!\n", s32Ret);
	return s32Ret;
}
  • clEnqueueNDRangeKernel()将命令排队以在设备上执行内核。
  • command_queue是有效的命令队列。内核将排队等待在与command_queue关联的设备上执行。
  • kernel 是一个有效的内核对象。与kernel 和command_queue 关联的OpenCL上下文必须相同。
  • work_dim 是用于指定全局工作项和工作组中工作项的维度数。work_dim 必须大于零且小于或等于3。
  • global_work_offset 当前必须为空值。在OpenCL的未来版本中,global_work_offset 可用于指定work_dim unsigned值的数组,这些值描述用于计算工作项的全局ID的偏移量,而不是让全局ID始终以偏移量(0,0,…0)开始。
  • global_work_size 指向一个work_dim 无符号值数组,这些值描述将执行内核函数的work_dim 维度中的全局工作项的数量。全局工作项的总数按global_work_size[0]*…*global_work_size[work_dim–1]计算。
  • local_work_size指向一个work_dim unsigned值数组,该数组描述组成工作组(也称为工作组的大小)的工作项数,该工作组将执行kernel指定的内核。工作组中工作项的总数按local_work_size[0]…local_work_size[work_dim–1]计算。显式指定的local_work_size大小将用于确定如何将由global_work_size大小指定的全局工作项分解为适当的工作组实例。如果指定了local_work_size,则在global_work_size[0],…global_work_size[work_dim - 1]中指定的值必须可以被local_work_size[0],… local_work_size[work_dim–1]中指定的相应值整除。
  • 用于内核的工作组大小也可以在程序源代码中使用__attribute__((reqd_work_group_size(X,Y,Z))限定符指定。在这种情况下,由local_work_size 指定的工作组大小必须与reqd_work_group_size属性限定符指定的值匹配。local_work_size 大小也可以是空值,在这种情况下,OpenCL实现将决定如何将全局工作项分解为适当的工作组实例。这些工作组实例在多个计算单元之间并行执行,或者在同一计算单元上并发执行。
  • 每个工作项由全局标识符唯一标识。可以在内核内部读取的全局ID是使用global_work_size和global_work_offset指定的值来计算的。在opencl1.0中,起始全局ID总是(0,0,…0)。此外,工作项还通过一个唯一的本地ID在工作组中标识。本地ID也可以被内核读取,它是使用local_work_size 给定的值来计算的。起始本地ID始终是(0,0,…0)。
  • event_wait_list 和num_events_in_wait_list 指定执行此特定命令之前需要完成的事件。如果event_wait_list 为空,则此特定命令不会等待任何事件完成。如果event_wait_list 为空,则num_events_in_wait_list 必须为0。如果event_wait_list 不为空,则event_wait_list 所指向的event_wait_list 必须有效,且num_events_in_wait_list 必须大于0。事件等待列表中指定的事件充当同步点。与event_wait_list和command_queue中的事件关联的上下文必须相同。event返回标识此特定内核执行实例的事件对象。事件对象是唯一的,可以在以后用于标识特定的内核执行实例。如果event 为NULL,则不会为此内核执行实例创建任何事件,因此应用程序将无法查询或排队等待此特定的内核执行实例。
extern CL_API_ENTRY cl_int CL_API_CALL
clFinish(cl_command_queue command_queue) CL_API_SUFFIX__VERSION_1_0;

// 示例 
s32Ret = clFinish(ptOpenclHdl->queue);
if (s32Ret != CL_SUCCESS){
	DPRINT_ERROR("[Tril]Error %d clFinish clFinish!\n", s32Ret);
	return s32Ret;
}
  • clFinish()主要用于命令之间的同步;
  • clFinish()和clFlush()都用于命令之间的同步,但两者之间还是存在区别。

同步命令

描述

clFinish()

阻塞host上的执行线程,直到命令队列上的所有命令执行完毕

clFlush()

阻塞host上的执行线程,直到命令队列上的命令都从队列上移出

2.12 OpenCL错误码说明

#define CL_SUCCESS                                   0  // 命令成功执行,没有出现错误
#define CL_DEVICE_NOT_FOUND                         -1  // 未发现与条件匹配的OpenCL设备
#define CL_DEVICE_NOT_AVAILABLE                     -2  // OpenCL设备目前不可用
#define CL_COMPILER_NOT_AVAILABLE                   -3  // 程序由源代码创建,不过没有可用的OpenCL C编译器
#define CL_MEM_OBJECT_ALLOCATION_FAILURE            -4  // 无法为内存对象或图像对象分配内存
#define CL_OUT_OF_RESOURCES                         -5  // 没有足够的资源执行命令
#define CL_OUT_OF_HOST_MEMORY                       -6  // 宿主机上没有足够的内存执行命令
#define CL_PROFILING_INFO_NOT_AVAILABLE             -7  // 无法得到事件的性能评测信息或者命令队列不支持性能评测
#define CL_MEM_COPY_OVERLAP                         -8  // 两个缓冲区在同一个内存区域重叠
#define CL_IMAGE_FORMAT_MISMATCH                    -9  // 图像未采用相同的图像格式
#define CL_IMAGE_FORMAT_NOT_SUPPORTED               -10  // 不支持指定的图像格式
#define CL_BUILD_PROGRAM_FAILURE                    -11  // 无法为程序构建可执行代码(此处纠错需要查看源码语法)
#define CL_MAP_FAILURE                              -12  // 内存区域无法映射到宿主机内存
#define CL_MISALIGNED_SUB_BUFFER_OFFSET             -13
#define CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST -14
#define CL_COMPILE_PROGRAM_FAILURE                  -15
#define CL_LINKER_NOT_AVAILABLE                     -16
#define CL_LINK_PROGRAM_FAILURE                     -17
#define CL_DEVICE_PARTITION_FAILED                  -18
#define CL_KERNEL_ARG_INFO_NOT_AVAILABLE            -19
#define CL_INVALID_VALUE                            -30  // 命令的一个或多个参数指定了非法值
#define CL_INVALID_DEVICE_TYPE                      -31  // 传入的设备类型不是合法值
#define CL_INVALID_PLATFORM                         -32  // 传入的平台不是合法值
#define CL_INVALID_DEVICE                           -33  // 传入的设备不是合法值
#define CL_INVALID_CONTEXT                          -34  // 传入的上下文不是合法值
#define CL_INVALID_QUEUE_PROPERTIES                 -35  // 设备不支持命令队列属性
#define CL_INVALID_COMMAND_QUEUE                    -36  // 传入的命令队列不是合法值
#define CL_INVALID_HOST_PTR                         -37  // 宿主机指针不合法
#define CL_INVALID_MEM_OBJECT                       -38  // 传入的内存对象不是合法值
#define CL_INVALID_IMAGE_FORMAT_DESCRIPTOR          -39  // 传入的图像格式描述符不是合法值
#define CL_INVALID_IMAGE_SIZE                       -40  // 设备不支持这个图像大小
#define CL_INVALID_SAMPLER                          -41  // 传入的采样工具不是合法值
#define CL_INVALID_BINARY                           -42  // 传入了非法的二进制程序
#define CL_INVALID_BUILD_OPTIONS                    -43  // 一个或多个构建选项不合法
#define CL_INVALID_PROGRAM                          -44  // 传入的程序不是合法值
#define CL_INVALID_PROGRAM_EXECUTABLE               -45  // 程序未能成功地构建命令队列关联设备上的一个可执行程序
#define CL_INVALID_KERNEL_NAME                      -46  // 程序中不存在指定的内核
#define CL_INVALID_KERNEL_DEFINITION                -47  // 程序源代码中定义的内核不合法
#define CL_INVALID_KERNEL                           -48  // 传入的内核不是合法值
#define CL_INVALID_ARG_INDEX                        -49  // 参数索引指示的参数对于内核不合法
#define CL_INVALID_ARG_VALUE                        -50	 // 对于一个非局部参数,内核参数值为NULL;或者对于一个局部参数,内核参数值为非NULL
#define CL_INVALID_ARG_SIZE                         -51  // 参数大小与内核参数不一致
#define CL_INVALID_KERNEL_ARGS                      -52	 // 一个或多个内核参数未赋值
#define CL_INVALID_WORK_DIMENSION                   -53	 // 工作维度值不是介于1~3的一个值
#define CL_INVALID_WORK_GROUP_SIZE                  -54	 // 局部或全局工作组大小不合法
#define CL_INVALID_WORK_ITEM_SIZE                   -55	 // 一个或多个工作项大小超出了设备支持的最大大小
#define CL_INVALID_GLOBAL_OFFSET                    -56	 // 全局偏移量超出了所支持的界限
#define CL_INVALID_EVENT_WAIT_LIST                  -57	 // 提供的等待列表大小不合法或者其中包含非事件
#define CL_INVALID_EVENT                            -58	 // 传入的事件不是一个合法值
#define CL_INVALID_OPERATION                        -59	 // 执行命令导致出现一个不合法的操作
#define CL_INVALID_GL_OBJECT                        -60	 // OpenGL引用的对象存在问题
#define CL_INVALID_BUFFER_SIZE                      -61	 // 指定的缓冲区大小越界
#define CL_INVALID_MIP_LEVEL                        -62	 // 为OpenGL纹理指定的mipmap级别对于OpenGL对象不合法
#define CL_INVALID_GLOBAL_WORK_SIZE                 -63	 // 传入的全局工作大小不合法,可能为0或者超出了设备支持的大小
#define CL_INVALID_PROPERTY                         -64
#define CL_INVALID_IMAGE_DESCRIPTOR                 -65
#define CL_INVALID_COMPILER_OPTIONS                 -66
#define CL_INVALID_LINKER_OPTIONS                   -67
#define CL_INVALID_DEVICE_PARTITION_COUNT           -68