OpenCL的Python调用

曾伟 2024-07-17

简介:工作需要使用OpenCL,有必要对使用Python调用OpenCL进行一个学习。网上已经有不少资料,我只需要做一些笔记记录即可。

入门

问了下Kimi,用Python调用OpenCL主要使用PyOpenCL库。这个库提供了对OpenCL的完整访问权限,包括自动错误检查、对象清理和生命周期管理,以及速度优势,它的底层是用C++写的。

直接阅读官方文档即可。无意中还发现了一所大学的Introduction to Supercomputing (MCS 572)课程网页以及《Python并行编程 中文版》,先收藏着。

笔记

1.OpenCL通用流程

询问kimi,一般OpenCL通用流程为:

  1. 选择平台(Platform Selection): 使用clGetPlatformIDs函数获取系统中可用的OpenCL平台列表。平台通常与特定的硬件厂商相关联,例如NVIDIA、AMD或Intel。

  2. 获取平台信息(Platform Information Query): 使用clGetPlatformInfo函数查询有关平台的详细信息,如平台名称、版本、扩展等。

  3. 创建上下文(Context Creation): 使用clCreateContext或clCreateContextFromType函数创建一个OpenCL上下文。上下文是程序与OpenCL设备交互的接口,包含了一个或多个设备。

  4. 创建命令队列(Command Queue Creation): 对于每个要使用的设备,使用clCreateCommandQueue函数创建一个命令队列。命令队列用于管理设备上的命令执行,如内核执行和数据传输。

  5. 编写内核代码(Kernel Code Writing): 编写OpenCL内核代码,这是一种用OpenCL C编写的并行函数,将在设备上执行。

  6. 创建程序对象(Program Object Creation): 使用clCreateProgramWithSource函数创建一个OpenCL程序对象,并将内核代码作为源代码传递给它。

  7. 编译程序(Program Compilation): 使用clBuildProgram函数编译程序对象。编译过程可能需要指定编译选项和包含路径。

  8. 创建内核对象(Kernel Object Creation): 使用clCreateKernel函数从编译后的程序中创建一个或多个内核对象。

  9. 设置内核参数(Kernel Argument Setup): 使用clSetKernelArg函数为每个内核设置参数,这些参数可以是常量值、内存对象(如缓冲区或图像)或指向内存对象的指针。

  10. 创建内存对象(Memory Object Creation): 使用clCreateBuffer或clCreateImage函数在GPU上创建内存对象,用于存储数据。

  11. 数据传输(Data Transfer): 使用clEnqueueReadBuffer、clEnqueueWriteBuffer或clEnqueueCopyBuffer等函数在主机和设备之间传输数据。

  12. 执行内核(Kernel Execution): 使用clEnqueueNDRangeKernel函数将内核排队到命令队列中执行。需要指定全局工作尺寸和局部工作尺寸。

  13. 同步操作(Synchronization): 使用clFinish或clWaitForEvents函数确保命令队列中的所有命令完成。

  14. 读取结果(Reading Results): 如果需要,使用clEnqueueReadBuffer函数将结果从设备内存读取回主机内存。

  15. 错误检查(Error Checking): 在每个OpenCL API调用后,检查并处理可能发生的错误。

  16. 资源清理(Resource Cleanup): 使用clReleaseKernel、clReleaseProgram、clReleaseCommandQueue、clReleaseContext等函数释放所有分配的OpenCL资源。

  17. 调试和性能分析(Debugging and Profiling): 使用OpenCL的调试工具和性能分析工具来优化内核性能和调试程序。

PyOpenCL的一般流程:

  1. 导入 PyOpenCL 库: import pyopencl as cl。 首先,需要导入 PyOpenCL 模块以便使用其提供的功能。

  2. 创建 OpenCL 上下文(Context): ctx = cl.create_some_context()。 使用 create_some_context 函数自动选择一个可用的平台和设备,并创建一个 OpenCL 上下文。

  3. 创建命令队列(Command Queue): queue = cl.CommandQueue(ctx)。 基于上下文创建一个命令队列,用于在选定的设备上排队执行 OpenCL 命令。

  4. 编写 OpenCL 内核代码(Kernel Code): 内核代码是用 OpenCL C 编写的,需要定义在一个字符串中,然后传递给 PyOpenCL。

  5. 创建 OpenCL 程序(Program): prg = cl.Program(ctx, kernel_code).build()。 使用上下文和内核代码创建一个程序对象,并通过 build 方法编译内核代码。

  6. 创建缓冲区或图像内存(Buffers/Memories): a_g = cl.Buffer(ctx, cl.mem_flags.READ_ONLY | cl.mem_flags.COPY_HOST_PTR, hostbuf=a_np)。 根据需要创建缓冲区或图像,并将其与 Python 的 NumPy 数组关联起来。

  7. 分配设备内存并准备数据传输: 使用 Buffer 或 Image 创建 GPU 内存,并使用 COPY_HOST_PTR 标志将主机内存的数据复制到设备。

  8. 定义内核参数并执行内核(Execute Kernel):
     res_g = cl.Buffer(ctx, cl.mem_flags.WRITE_ONLY, a_np.nbytes) 
     knl = prg.sum
     knl(queue, a_np.shape, None, *args)
    

    定义内核的参数,并将内核排队到命令队列执行。*args 是传递给内核的参数列表。

  9. 同步设备和主机: cl.enqueue_copy(queue, res_np, res_g)。 使用同步命令,如 enqueue_copy,将计算结果从设备内存复制回主机内存。

  10. 读取和验证结果: 在主机端使用 NumPy 等工具对结果进行验证,确保 GPU 计算的正确性。

  11. 资源清理(Resource Cleanup): 显式或隐式地释放所有分配的资源,包括缓冲区、程序、内核等。

  12. 错误检查(Error Checking): 在每个 PyOpenCL API 调用后,检查返回的错误代码,确保每个操作都成功执行。

  13. 性能分析(Performance Profiling): 使用 PyOpenCL 的事件和性能计数器来分析内核执行的性能。

  14. 使用 PyOpenCL 扩展功能: 利用 PyOpenCL 提供的便利函数和类,例如 get_context, release, 或使用 LocalMemory 辅助类来简化代码。

2.使用env创建虚拟环境以安装PyOpenCL

sudo apt install python3.8-venv

python3 -m venv usePyCL

source usePyCL/bin/activate

使用env创建虚拟环境会在本地生成虚拟环境文件夹,这点跟conda不同。