Python (Py)OpenCL同时修改来自所有线程的值

Python (Py)OpenCL同时修改来自所有线程的值,python,opencl,pyopencl,Python,Opencl,Pyopencl,很简单,我有下面的内核,它修改了C[0]的值,其中C是一个只有一个元素的数组 __kernel void sigma(__global float *A, __global float *B, __global float *C) { int i = get_global_id(0); printf("Adding %.2f + %.2f", A[i], B[i]); C[0] += A[i] + B[i]; } 问题是,最后C[0]有最后完成的线程

很简单,我有下面的内核,它修改了
C[0]
的值,其中
C
是一个只有一个元素的数组

__kernel void sigma(__global float *A, __global float *B, __global float *C) {
    int i = get_global_id(0);
    printf("Adding %.2f + %.2f", A[i], B[i]);
    C[0] += A[i] + B[i];
}
问题是,最后
C[0]
有最后完成的线程的值,特别是在本例中,我得到了以下结果

Adding 1.00 + 0.00
Adding 2.00 + 1.00
Adding 3.00 + 1.00
Adding 4.00 + 1.00
[5.]
最后,
C[0]
4.00+1.00
。我想要的是
C[0]
成为
(1.00+0.00)+(2.00+1.00)+(3.00+1.00)+(4.00+1.00)
。因此,我希望将每个线程的
A[I]
B[I]
添加到
C[0]

此外,我不只是寻找补充,我希望这是与任何功能或操作兼容

这可能是多余的,但在主机代码中,我只是尽可能地将数据传递到内核。这个问题与主机代码有关吗

import pyopencl as cl, numpy as np; 
ctx = cl.create_some_context(); queue = cl.CommandQueue(ctx); mf = cl.mem_flags
M = np.array([1, 2, 3, 4]).astype(np.float32) # A
V = np.array([0, 1, 1, 1]).astype(np.float32) # B
a = np.array([0]).astype(np.float32) # C
# Transfer data to GPU
A_GPU = cl.Buffer(ctx, mf.READ_ONLY | mf.COPY_HOST_PTR, hostbuf=M) 
B_GPU = cl.Buffer(ctx, mf.READ_ONLY | mf.COPY_HOST_PTR, hostbuf=V)
C_GPU = cl.Buffer(ctx, mf.WRITE_ONLY, a.nbytes)
c = np.zeros(shape=a.shape, dtype= np.float32) # array to copy the result
kernel.sigma(queue, M.shape, None, A_GPU, B_GPU, C_GPU)
cl.enqueue_copy(queue, c, C_GPU).wait()

内核的编写方式效率低下,在许多情况下会导致不确定的结果。通常,应该避免多个线程(工作项)同时写入同一地址。在你的例子中,
C[0]+=A[i]+B[i]
是一个读-修改-写序列,从并行访问的角度来看,这会使事情变得更糟

您需要使用的是并行归约模式。 此模式允许您将数组中的所有元素高效且确定地求和为单个值(或者在您的情况下为-2数组)。它还支持除加法之外的许多其他关联运算符


在线资源很多,您可以根据自己的情况进行调整。

正如@Elad Maimoni所说,在PyOpenCl中执行reduce的一种方法是使用
2.0版
中提供的功能
work\u group\u reduce\u add

在python中,它可以这样实现

  • 内核
  • 主机代码的其余部分
这将输出
[90]

kernel = cl.Program(ctx, """
__kernel void resum(__global float *A, __global float *B, __global float *a) {
    int i = get_global_id(0);
    a[0] = work_group_reduce_add(A[i] + B[i]);
}
""").build(options='-cl-std=CL2.0') # Build using cl 2.0
# Some example arrays
a = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9])
b = np.array([9, 8, 7, 6, 5, 4, 3, 2, 1])
c = np.array([0])
d = np.array([0])

# Create GPU data
a = cl.Buffer(ctx, mf.READ_ONLY | mf.COPY_HOST_PTR, hostbuf=a)
b = cl.Buffer(ctx, mf.READ_ONLY | mf.COPY_HOST_PTR, hostbuf=b)
c = cl.Buffer(ctx, mf.READ_ONLY | mf.COPY_HOST_PTR, hostbuf=c)

kernel.resum(queue, (10, ), None, a, b, c)
cl.enqueue_copy(queue, d, c)
print(d)