GPU上的广义滑动窗口计算

Bri*_*ion 7 cuda gpu sliding-window dot-product

这是一些Python代码,它在两个3D矩阵X和Y上实现滑动窗口计算.

import numpy

def sliding_dot( X,Y ) :

    assert X.ndim == Y.ndim == 3
    iw,ih,id = X.shape
    fw,fh,fd = Y.shape

    assert id == fd
    assert fw < iw and fh < ih

    ow,oh = iw-fw+1,ih-fh+1
    out = numpy.zeros( [ow,oh] )

    for x in xrange(ow) :
        for y in xrange(oh) :
            window = X[x:x+fw,y:y+fh,:]
            out[x,y] = numpy.dot( window.flatten(),Y.flatten() )

    return out

#################    

A_dims = (640,480,32)
B_dims = (6,6,32)

A = numpy.random.rand(*A_dims)
B = numpy.random.rand(*B_dims)

sliding_dot(A,B)
Run Code Online (Sandbox Code Playgroud)

通常,Y沿第一维和第二维始终小于X,但它们在第三维中相等.

请注意,我们可以用Y和窗口的任何函数替换numpy.dot().这与卷积有点不同,因为Y只沿X的第一维和第二维滑动.我正在寻找一种有效的策略,使用CUDA有效地实现这种滑动窗口计算.有人想给我一些方向吗?干杯!

更新:在下面的答案中,您可以在其他用户的帮助下观看我的优化过程.

ala*_*and 1

好吧,这里有一些想法:

您执行约 640*480 次迭代numpy.dot,它本身处理 6*6*32 个元素。并行化点积几乎不值得:192 个并行线程对于 GPU 来说是不够的,而 CUDA 上的减少则是额外的麻烦。因此,IMO,并行化任务的最佳方法是将输出数组的一个元素分配给每个线程。

现在关于内存:输出数组将位于全局内存中,没有太多选择。对于输入数据,A纹理内存看起来相当不错,因为相邻线程访问相邻元素。或者,您可以手动将其“缓存”在共享内存中,但在这种情况下,它看起来比简单地使用纹理没有多大优势。对于B,共享内存不好,因为它会导致存储体冲突,因为当您计算点积时,half-warp中的所有线程都访问相同的 B 元素(您可以从不同线程中的不同元素开始求和,但这(再次) )看起来不太有希望)。所以选择要么是纹理,要么是常数。我投票支持常量,因为(a)常量内存适合设备上所有线程访问的数据,(b)您不会污染纹理缓存。

以上只是我的猜测,要真正获得良好的性能,您最好尝试不同的变体......

关于您的幼稚实施的更新

for (int Yi = 0; Yi < Ydims[0]; Yi++ )
Run Code Online (Sandbox Code Playgroud)

在这里,您可以在每次迭代时访问全局内存。这是一个巨大的性能杀手。由于您有 3 个维度,因此您最好将您替换int *Ydims为(与和int3 Ydims相同)。Xdimsoutdims

out[out_indx] += X[X_indx]*Y[Y_indx];
Run Code Online (Sandbox Code Playgroud)

这又是一个非常糟糕的主意。创建一个寄存器变量并用它执行所有操作。仅在内核末尾写入一次全局数组。

这些优化是您应该做的第一件事。第二件事是制作3DX纹理Y,因此对它们的访问将被缓存。我猜想,在此之后 CUDA 的性能将超越 CPU。

如需进一步优化,您最好阅读CUDA C 最佳实践指南。必须阅读它,您会更好地了解如何编写高效的 GPU 代码(现在您的实现太天真了)