【问题标题】:PyOpenCL - Different results on Intel and NVidiaPyOpenCL - Intel 和 NVidia 上的不同结果
【发布时间】:2016-06-23 08:29:39
【问题描述】:

我正在尝试运行基于高斯脉冲传播的模拟。我正在使用 i5 4590 和 GTX 970(最新驱动程序)的 Windows 桌面和 2015 年初的 macbook air 之间进行交叉开发。

运行我的主代码时,我无法在我的桌面上获得任何体面的结果(计算结果不同),但在我的 Mac 上,结果似乎还可以。

为了进一步调查这个问题,我尝试运行一个简单的高斯传播。在 macbook 上的结果或多或少还可以,而在桌面上则完全是一团糟。

我在两台机器上运行相同的代码,并且都具有相同的 python (2.7.10) 和相应模块的分布。

这是我的代码

import scipy as sp
import pyopencl as cl
import matplotlib.pyplot as plot

ctx = cl.create_some_context()
queue = cl.CommandQueue(ctx)
MF = cl.mem_flags

dx = 0.01
X = sp.arange(0.0, 100, dx)
N = len(X)

A_h = (sp.exp(-(X-50)**2/10.)*sp.exp(-1j*1000*X)).astype(sp.complex64)
A_d = cl.Buffer(ctx, MF.READ_WRITE | MF.COPY_HOST_PTR, hostbuf=A_h)

plot.figure()
plot.plot(X, abs(A_h) ** 2 / (abs(A_h) ** 2).max())

Source = """
    #define complex_ctr(x, y) (float2)(x, y)
    #define complex_mul(a, b) complex_ctr(mad(-(a).y, (b).y, (a).x * (b).x), mad((a).y, (b).x, (a).x * (b).y))
    #define complex_unit (float2)(0, 1)

    __kernel void propagate(__global float2 *A){
        const int gid_x = get_global_id(0);
        float EPS = 0.1f;
        A[gid_x] = A[gid_x] + EPS*complex_mul((A[gid_x+1] + A[gid_x-1]), complex_unit);
    }
"""
prg = cl.Program(ctx, Source).build()
for i in range(3000):
    print i
    event = prg.propagate(queue, (N,), None, A_d)
    event.wait()
cl.enqueue_copy(queue, A_h, A_d)

plot.plot(X, abs(A_h) ** 2 / (abs(A_h) ** 2).max())

plot.show()

这是结果

桌面结果:

Mac 结果:

绿线对应传播后的高斯,蓝线是初始高斯

什么可能导致 NVidia 端出现此问题?我认为我错过了防​​止这种情况发生的关键步骤,并且由于运气,它在某种程度上可以在 mac 上运行

编辑

这是我根据用户建议运行的最终代码

import scipy as sp
import pyopencl as cl
import matplotlib.pyplot as plot

ctx = cl.create_some_context()
queue = cl.CommandQueue(ctx)
MF = cl.mem_flags

dx = sp.float32(0.001)
X = sp.arange(0.0, 100, dx).astype(sp.float32)
N = len(X)

A_h = (sp.exp(-(X-50)**2/10.)*sp.exp(1j*1000*X)).astype(sp.complex64)
A_d = cl.Buffer(ctx, MF.READ_WRITE | MF.COPY_HOST_PTR, hostbuf=A_h)
B_d = cl.Buffer(ctx, MF.READ_WRITE | MF.COPY_HOST_PTR, hostbuf=A_h)

plot.figure()
plot.plot(X, abs(A_h) ** 2 / (abs(A_h) ** 2).max())

Source = """
    #define complex_ctr(x, y) (float2)(x, y)
    #define complex_mul(a, b) complex_ctr((a).x * (b).x - (a).y * (b).y, (a).x * (b).y + (a).y * (b).x)
    #define complex_unit (float2)(0, 1)

    __kernel void propagate(__global float2 *A){
        const int gid_x = get_global_id(0);
        float EPS = 0.1f;
        float2 a1, a2;
        a1 = A[gid_x-1];
        a2 = A[gid_x+1];
        barrier(CLK_GLOBAL_MEM_FENCE);
        A[gid_x] += EPS*complex_mul((a1 + a2), complex_unit);
    }
"""

prg = cl.Program(ctx, Source).build()
for i in range(12000):
    print i
    evolve = prg.propagate(queue, (N,), None, A_d)
    evolve.wait()
cl.enqueue_copy(queue, A_h, A_d)

plot.plot(X, abs(A_h) ** 2)

plot.show()

【问题讨论】:

  • 你的内核完全坏了。在A 的计算中存在内存竞争。更改内核,这样计算就不会在原地执行,您可能会有更多的运气
  • @talonmies 抱歉,我不确定我是否理解您所说的。你说的记忆竞赛是指我应该使用原子?
  • 您有不同的线程尝试同时读取和写入A 中的相同内存位置。这是一种先读后写的危险或竞赛。
  • 当我回复时,您的评论中没有任何关于原子的内容,它只是被切断了。但是不,你不需要原子。在内核中使用两个数组,一个作为输入,一个作为输出,并在 python 代码中的迭代之间切换它们(在这种情况下切换并不意味着执行复制)。
  • 内存栅栏完全靠运气。这也不安全。 CLK_GLOBAL_MEM_FENCE 不会跨工作组传播。您也有错误,但它们只是很小(工作组的边界)并且您没有注意到它们。

标签: python numpy opencl nvidia pyopencl


【解决方案1】:

编辑:哦,看看@talonmies 的评论,和这个是一样的解决方案。

此代码在 OpenCL 中不安全,存在数据竞争问题:

A[gid_x] = A[gid_x] + EPS*complex_mul((A[gid_x+1] + A[gid_x-1]), complex_unit);

每个工作项x 使用x+1x-1。根据工作项目的日程安排,结果会有所不同。

改为使用 2 个缓冲区,从 A 读取,写入 B,很简单:

import scipy as sp
import pyopencl as cl
import matplotlib.pyplot as plot

ctx = cl.create_some_context()
queue = cl.CommandQueue(ctx)
MF = cl.mem_flags

dx = 0.01
X = sp.arange(0.0, 100, dx)
N = len(X)

A_h = (sp.exp(-(X-50)**2/10.)*sp.exp(-1j*1000*X)).astype(sp.complex64)
A_d = cl.Buffer(ctx, MF.READ_WRITE | MF.COPY_HOST_PTR, hostbuf=A_h)
B_d = cl.Buffer(ctx, MF.READ_WRITE)

plot.figure()
plot.plot(X, abs(A_h) ** 2 / (abs(A_h) ** 2).max())

Source = """
    #define complex_ctr(x, y) (float2)(x, y)
    #define complex_mul(a, b) complex_ctr(mad(-(a).y, (b).y, (a).x * (b).x), mad((a).y, (b).x, (a).x * (b).y))
    #define complex_unit (float2)(0, 1)

    __kernel void propagate(__global float2 *A, __global float2 *B){
        const int gid_x = get_global_id(0);
        float EPS = 0.1f;
        B[gid_x] = A[gid_x] + EPS*complex_mul((A[gid_x+1] + A[gid_x-1]), complex_unit);
    }
"""
prg = cl.Program(ctx, Source).build()
for i in range(3000):
    print i
    event = prg.propagate(queue, (N,), None, A_d, B_d)
    A_d, B_d = B_d, A_d #Swap buffers, so A always has results
    #event.wait() #You don't need this, this is slowing terribly the execution, enqueue_copy already waits
cl.enqueue_copy(queue, A_h, A_d)

plot.plot(X, abs(A_h) ** 2 / (abs(A_h) ** 2).max())

plot.show()

【讨论】:

  • 这行得通。我还了解了内存栅栏,这似乎也有效。
  • 不,您不能在您发布的案例中使用内存栅栏。甚至不是原子。事实上,没有任何方法可以解决这个问题,因为您从同一个内存位置写入和读取,并且您的算法取决于工作项按顺序工作。唯一的解决方案是创建另一个内存区域。
  • 哦,好的。也许这解释了为什么当我尝试传播更多迭代时,错误再次出现在内存围栏中。创建不同的内存区域是什么意思?是你建议的方法吗?我不熟悉这些术语,抱歉。
  • 在我发布的代码中,我创建了一个内存 B,其中是每次迭代的输出。然后我在每次迭代中将 A 与 B 交换(仅指针)。使用 2 个内存区域可以避免读取和写入相同位置的问题。
猜你喜欢
  • 1970-01-01
  • 2018-07-21
  • 1970-01-01
  • 1970-01-01
  • 2014-07-02
  • 2013-01-06
  • 1970-01-01
  • 1970-01-01
  • 2013-12-01
相关资源
最近更新 更多