OpenCL 中的本地和全局内存栅栏有什么区别?

Whats the difference between local and global memory fence in OpenCL?

我正在尝试使用 PyOpenCL 来减少总和,类似于示例:https://dournac.org/info/gpu_sum_reduction。我正在尝试对所有值为 1 的向量求和。结果在第一个元素中应该是 16384。 然而,似乎只收集了一些要点。是否需要本地索引?是否有任何竞争条件(当我 运行 两次结果不一样时)?以下代码有什么问题?

import numpy as np
import pyopencl as cl

def readKernel(kernelFile):
    with open(kernelFile, 'r') as f:
        data=f.read()
    return data

a_np = np.random.rand(128*128).astype(np.float32)
a_np=a_np.reshape((128,128))
print(a_np.shape)

device = cl.get_platforms()[0].get_devices(cl.device_type.GPU)[0]
print(device)
ctx=cl.Context(devices=[device])
#ctx = cl.create_some_context() #ask which context to use 
queue = cl.CommandQueue(ctx)
mf = cl.mem_flags

a_g = cl.Buffer(ctx, mf.READ_WRITE | mf.COPY_HOST_PTR, hostbuf=a_np)

prg = cl.Program(ctx,readKernel("kernel2.cl")).build()

prg.test(queue, a_np.shape, None, a_g)

cl.enqueue_copy(queue, a_np, a_g).wait()
np.savetxt("teste2.txt",a_np,fmt="%i")

内核是:

__kernel void test(__global float *count){
    int id = get_global_id(0)+get_global_id(1)*get_global_size(0);
    int nelements = get_global_size(0)*get_global_size(1);

    count[id] = 1;
    barrier(CLK_GLOBAL_MEM_FENCE); 

    for (int stride = nelements/2; stride>0; stride = stride/2){
        barrier(CLK_GLOBAL_MEM_FENCE); //wait everyone update
        if (id < stride){
            int s1 = count[id];
            int s2 = count[id+stride];
            count[id] = s1+s2;
        }
    }
    barrier(CLK_GLOBAL_MEM_FENCE); //wait everyone update
}

问题是您的内核实现为在一个工作组内进行缩减,并且隐含地安排了许多工作组。

根据 GPU,每个工作组的最大工作项数量不同。 Nvidia 为 1024,AMD 和 Intel 为 256(旧 GPU 中的 Intel 为 512)。

让我们假设在这个例子中,GPU 上每个工作组的最大工作项数是 256。在这种情况下,最大 2d 工作组大小可以是 16x16,因此如果您使用该大小的矩阵,您的内核将 return 正确的结果。使用原始大小 128x128 并且在调度内核时未指定局部大小,实现会为您计算出全局大小 128x128 和局部大小(很可能)16x16,这意味着正在调度 8 个工作组。 在当前内核中,每个工作组都从不同的 id 开始计算,但索引减少到 0,因此您有竞争条件,因此每个 运行.

的结果不同

您有 2 个选项可以解决此问题:

  1. 重写您的内核以计算一个工作组中的所有内容,并使用全局、局部大小安排它:(16x16)、(16,16) 或每个工作组设备的最大工作项目数
  2. 使用全局、局部大小:(128x128),(16x16),每个工作组将计算其结果,然后在 cpu 端必须对每个工作组求和以获得最终结果。

对于 128x128,第一个选项将是首选,因为它应该执行得更快并且应该更容易实现。