Python 像这样在Numba中实现cuda gridsync()安全吗

Python 像这样在Numba中实现cuda gridsync()安全吗,python,cuda,synchronization,numba,Python,Cuda,Synchronization,Numba,Numba缺少cuda-C命令gridsync,因此没有一种固定的跨整个网格同步方法。只有块级同步可用 如果cudaKernal1的执行时间非常快,那么下面的代码的运行速度将提高1000倍 for i in range(10000): X = X + cudaKernel1[(100,100),(32,32)] (X) 通过将循环放入同一内核中,避免gpu内核安装时间。但是你不能,因为你需要在下一次迭代开始之前完成所有的网格,并且在Numba中没有gridsync命令 这里有一个在num

Numba缺少cuda-C命令gridsync,因此没有一种固定的跨整个网格同步方法。只有块级同步可用

如果cudaKernal1的执行时间非常快,那么下面的代码的运行速度将提高1000倍

for i in range(10000):
   X = X + cudaKernel1[(100,100),(32,32)] (X)
通过将循环放入同一内核中,避免gpu内核安装时间。但是你不能,因为你需要在下一次迭代开始之前完成所有的网格,并且在Numba中没有gridsync命令

这里有一个在numba中进行网格同步的明显方法,所以你可能会认为人们会使用这种方法,但我找不到任何这样的例子

然而,我发现很多关于stackoverflow的评论都没有解释——试图使用原子计数器跨网格同步块是毫无意义的、不安全的,或者在竞争条件下会死锁。相反,他们建议在这两个步骤之间退出内核。但是,如果每个步骤都非常快,那么调用内核所需的时间比执行内核所需的时间要长,因此,如果您可以循环执行这些步骤而不退出,那么调用内核所需的时间可以快1000倍

我不知道什么是不安全的,或者为什么会有一个比赛条件,这将是一个陷阱

下面这样的东西有什么问题

@numba.cuda.jit('void()')
def gpu_initGridSync():
    if ( cuda.threadIdx.x == 0): 
        Global_u[0] = 0
        Global_u[1] = 0

@numba.cuda.jit('void(int32)'device=True)
def gpu_fakeGridSync(i):
    ###wait till the the entire grid has finished doSomething()
    # in Cuda-C we'd call gridsync()
    # but lack that in Numba so do the following instead.

    #Syncthreads in current block
    numba.cuda.syncthreads()

    #increment global counter, once per block
    if ( cuda.threadIdx.x == 0 ):  numba.atomic.add( Global_u, 0, 1 )

    # idle in a loop
    while ( Global_u[0] < (i+1)*cuda.gridDim.x-1 ) ):  pass   #2

    #regroup the block threads after the slow global memory reads.
    numba.cuda.syncthreads()

    # now, to avoid a race condition of blocks re-entering the above while
    # loop before other blocks have exited we do this global sync a second time

     #increment global counter, once per block
    if ( cuda.threadIdx.x == 0 ):  numba.atomic.add( Global_u,1, 1 )

    # idle in a loop
    while ( Global_u[1] > (i+2)*cuda.gridDim.x ) ):  pass   #2

    #regroup the block threads after the slow global memory reads.
    numba.cuda.syncthreads()
在我看来,这在逻辑上是合理的。初始化全局计数器有一个微妙的步骤。这必须在它自己的内核调用中完成,以避免竞争条件。但在那之后,我可以自由调用fakeGridSync,而无需重新初始化它。我必须跟踪我调用它的循环迭代,从而将传入的参数传递给gridSync


我承认我可以看出有些努力是白费的,但这是交易杀手吗?例如,在语句2中,这个while循环意味着所有已完成块中的所有线程都在徒劳地旋转它们的轮子。我想这可能会稍微减慢仍在尝试执行doSomething的网格块的速度。然而,我不确定这种浪费的努力有多糟糕。对语句2的第二个挑剔是,所有线程都在争夺相同的全局内存,因此它们访问它的速度会很慢。如果这意味着调度器推迟了它们的执行,并让有用的线程更频繁地执行,那么这甚至可能是一件好事。如果冲突是一个问题,可以通过在每个块检查中只使用thread0来改进这种幼稚的代码。

我认为Robert Crovella的评论指出了此方法失败的正确原因

我错误地假设调度器会先发制人地执行多任务,这样所有的块都会得到一个时间片来运行

目前Nvidia GPU没有先发制人的多任务调度程序。作业一直运行到完成

因此,一旦有足够的块进入while循环等待,剩余的块就可能不会由调度器启动。因此,等待循环将永远等待

我看到有研究论文建议如何让Nvidia先发制人。 但很明显,目前情况并非如此

我一直在想cuda-C是如何实现gridSync命令的。如果可以在C语言中完成,那么必须有一些通用的方法来解决这些限制。这是一个谜,我希望有人在下面评论


把1000倍的加速比放在桌子上真是太遗憾了。

Robert,我修复了你指的小错误。但是你关于逻辑失败的地方的评论,我只是没有看到。也许问题是,下一个街区在第一个街区出口前无法通行?也就是说,我假设先发制人的多任务处理,这样所有的块最终都会得到一些时间片。如果它不是先发制人的话,那么我想我看到了一些区块在第一组完成后等待运行的问题。因此,后面的块永远不会运行,因为前面的集在while循环中。即,非抢占式多任务是问题所在?如果是的话,我们希望有一个收益率报表。非常感谢您的洞察力。我看过英伟达CUDA文档。但我发现有很多谜团。我已经在谷歌上搜索过了,但也许你可以推荐一些简单易懂的教程来涵盖像这样的场景。例如,cuda-C如何实现网格同步?这难道不是说即使我的方法失败也有可能做到吗?你找到合理的解决方案了吗?对于其他遇到这种情况的人来说,gridsync似乎正在走向Numba:你不必把1000倍的加速放在桌子上。使用CUDA C++。如果需要,可以将其连接到python代码。如果您只愿意使用python,那么这就是问题的一部分。关于合作团队,他们不仅仅是建立在现有能力的基础上。它们公开了新的硬件和软件功能,设计成新的GPU。CUDA不是一个静态实体。它在进化。硬件在发展,CUDA API和功能也在发展。
@numba.cuda.jit('void(float32[:])')):
def ReallyReallyFast(X):
    i = numba.cuda.grid(1)
    for h in range(1,40000,4):
        temp = calculateSomething(X)
        gpu_fakeGridSync(h)
        X[i] = X[i]+temp
        gpu_fakeGridSync(h+2)

gpu_initGridSync[(1,),(1,)]()
ReallyReallyFast[(1000,), (32,) ](X)


@numba.cuda.jit('float32(float32[:])',device=True):
def calculateSomething(X):  # A dummy example of a very fast kernel operation
    i = numba.cuda.grid(1)
    if (i>0):
        return (X[i]-X[i-1])/2.0
    return 0.0