如何使用 PyOpenCL 将带有数组和变量的 C 结构传递给 OpenCL 内核

How to pass a C struct with arrays and variables to OpenCL kernel using PyOpenCL

因此,我必须使用 PyOpenCL 或使用 Python 的一些解决方法将一些数据传递给 OpenCL 内核。数据在内核端作为结构读取,我无法更改内核,因为它工作正常,并且是我的代码必须使用的更大项目的一部分。

内核看起来像这样:

typedef struct VglClStrEl{ 
    float data[VGL_ARR_CLSTREL_SIZE];
    int ndim;
    int shape[VGL_ARR_SHAPE_SIZE];
    int offset[VGL_ARR_SHAPE_SIZE];
    int size;
} VglClStrEl;

typedef struct VglClShape{ 
    int ndim;
    int shape[VGL_ARR_SHAPE_SIZE];
    int offset[VGL_ARR_SHAPE_SIZE];
    int size;
} VglClShape;

__kernel void kernel(__global unsigned char* img_input, 
                     __global unsigned char* img_output,  
                     __constant VglClShape* img_shape,
                     __constant VglClStrEl* window)
{

    // do what is needed

}

因此,如您所见,VglClShape 和 VglClStrEl 结构具有不同的类型数组和静态位大小变量。

[1] 变通方法支持只有一种类型数组的结构(或者不幸的是我没有找到一种方法来处理多种数组类型)。

[2] 解决方法是 PyOpenCL 文档参考,其中介绍了如何将 Python 数据传递到 OpenCL 内核结构。这种方法根本不支持数组。

那么,我如何传递 OpenCL 内核可以读取的 python 数据?我已经拥有 Python 端的所有数据,我只需要知道如何将它从 Python 传递到内核。

在你问之前:我正在使用 Python 3 并且 我无法更改内核

是的,数组大小是静态的。你可以这样假设:

VGL_ARR_CLSTREL_SIZE=256;
VGL_ARR_SHAPE_SIZE=20;

[1] Passing struct with pointer members to OpenCL kernel using PyOpenCL

[2]https://documen.tician.de/pyopencl/howto.html#how-to-use-struct-types-with-pyopencl

有一种骇人听闻的方法可以做到这一点,需要进行一些乏味的字节整理。大概您可以部署一个小型 OpenCL 探测内核? (PyOpenCL 在任何情况下都会在某些操作的后台执行此操作)

基本思路是:

  • 了解 OpenCL 设备如何通过 运行 单个实例内核
  • 对齐结构的所有元素
  • 创建一个 numpy 字节数组以匹配 OpenCL 结构的大小
  • 按字节将 Python 结构的每个元素复制到此数组中
  • 调用不可更改的 OpenCL 内核时,通过一袋字节缓冲区传递此数组

以下内核完成这项工作:

__kernel void get_struct_sizes( __global uint *struct_sizes )
{
    const uint global_id = get_global_id(0u)+get_global_id(1u)*get_global_size(0u);
    VglClStrEl vgclstrel;
    VglClShape vgclshape;
    uint offset;

    printf("In GPU (probing):\n Kernel instance = %d\n", global_id);

    if (global_id==0) {
        offset = (uint)&(vgclstrel.data);
        struct_sizes[0] = (uint)sizeof(vgclstrel);
        struct_sizes[1] = (uint)&(vgclstrel.ndim)-offset;
        struct_sizes[2] = (uint)&(vgclstrel.shape)-offset;
        struct_sizes[3] = (uint)&(vgclstrel.offset)-offset;
        struct_sizes[4] = (uint)&(vgclstrel.size)-offset;
        offset = (uint)&(vgclshape.ndim);
        struct_sizes[5] = (uint)sizeof(vgclshape);
        struct_sizes[6] = (uint)&(vgclshape.shape)-offset;
        struct_sizes[7] = (uint)&(vgclshape.offset)-offset;
        struct_sizes[8] = (uint)&(vgclshape.size)-offset;
    }
    return;
}

执行这个内核,return struct_sizes进入vgclshape_sizes,创建这个数组:

img_shape  = np.zeros((vgclshape_sizes[0]), dtype=np.uint8)

然后把你需要的复制进去:

def copy_into_byte_array(value, byte_array, offset):
        for i,b in enumerate(np.ndarray.tobytes(value)):
            byte_array[i+offset] = b
copy_into_byte_array(ndim,   img_shape, 0) 
copy_into_byte_array(shape,  img_shape, vgclshape_sizes[1]) 
copy_into_byte_array(offset, img_shape, vgclshape_sizes[2]) 
copy_into_byte_array(size,   img_shape, vgclshape_sizes[3]) 

我在这里跳过了一些步骤;填写它们你会发现这种方法有效。我能够将演示结构传递给您的纯正内核的虚拟副本。

我很想知道是否有更优雅的方法来执行这些步骤中的 any/all。我还希望在字节顺序等方面会出现问题,否则这些问题将是透明的。运气好的话,您可以解决这些问题。