我有一个简单的例子:
from numba import cuda
import numpy as np
import math
@cuda.jit
def func(i, y, z):
start = cuda.grid(1)
stride = cuda.gridsize(1)
for j in range(start, y.shape[0], stride):
# Note that these aren't my real functions but they demo the point
if i < j:
y[j, 0] = i
z[j, 0] = i + j
if i == j:
y[j, 1] = i
z[j, 1] = i * j
if i > j:
y[j, 2] = i
z[j, 2] = j
if __name__ == '__main__':
n = 30
y = np.ones((n, 3))
z = np.ones((n, 3)) * -1
device_y = cuda.to_device(y)
device_z = cuda.to_device(z)
max_i = 5
threads_per_block = 10
blocks_per_grid = math.ceil(y.shape[0]/threads_per_block[1])
for i in range(max_i):
func[blocks_per_grid, threads_per_block](i, device_y, device_z)
out = device_y.copy_to_host()
print(out)
输出应该是这样的:
[[1. 0. 4.]
[0. 1. 4.]
[1. 2. 4.]
[2. 3. 4.]
[3. 4. 1.]
[4. 1. 1.]
[4. 1. 1.]
[4. 1. 1.]
[4. 1. 1.]
[4. 1. 1.]
[4. 1. 1.]
[4. 1. 1.]
[4. 1. 1.]
[4. 1. 1.]
[4. 1. 1.]
[4. 1. 1.]
[4. 1. 1.]
[4. 1. 1.]
[4. 1. 1.]
[4. 1. 1.]
[4. 1. 1.]
[4. 1. 1.]
[4. 1. 1.]
[4. 1. 1.]
[4. 1. 1.]
[4. 1. 1.]
[4. 1. 1.]
[4. 1. 1.]
[4. 1. 1.]
[4. 1. 1.]]
然而,当max_i
很大时,大部分时间都花费在调用CUDA内核上,我希望尽可能使这个内核更快。因此,我尝试着将max_i
的循环移动到内核中,但似乎会遇到竞争条件。这是我目前拥有的:
from numba import cuda
import numpy as np
import math
@cuda.jit
def func(max_i, y, z):
a, b = cuda.grid(2)
a_stride, b_stride = cuda.gridsize(2)
for i in range(a, max_i, a_stride):
for j in range(b, y.shape[0], b_stride):
if i < j:
y[j, 0] = i
z[j, 0] = i + j
if i == j:
y[j, 1] = i
z[j, 1] = i * j
if i > j:
y[j, 2] = i
z[j, 2] = j
if __name__ == '__main__':
n = 30
y = np.ones((n, 3))
z = np.ones((n, 3)) * -1
device_y = cuda.to_device(y)
device_z = cuda.to_device(z)
max_i = 5
threads_per_block = (1, 10)
blocks_per_grid = (max_i, math.ceil(y.shape[0]/threads_per_block[1]))
func[blocks_per_grid, threads_per_block](max_i, device_y, device_z)
out = device_y.copy_to_host()
print(out)
这个(不正确的)输出看起来像:
[[1. 0. 4.]
[0. 1. 4.]
[1. 2. 4.]
[1. 3. 4.] # Should be [2. 3. 4.]
[3. 4. 1.]
[4. 1. 1.]
[3. 1. 1.] # Should be [4. 1. 1.]
[3. 1. 1.] # Should be [4. 1. 1.]
[3. 1. 1.] # Should be [4. 1. 1.]
[3. 1. 1.] # Should be [4. 1. 1.]
[4. 1. 1.]
[4. 1. 1.]
[4. 1. 1.]
[4. 1. 1.]
[4. 1. 1.]
[4. 1. 1.]
[4. 1. 1.]
[4. 1. 1.]
[4. 1. 1.]
[4. 1. 1.]
[0. 1. 1.] # Should be [4. 1. 1.]
[0. 1. 1.] # Should be [4. 1. 1.]
[0. 1. 1.] # Should be [4. 1. 1.]
[0. 1. 1.] # Should be [4. 1. 1.]
[0. 1. 1.] # Should be [4. 1. 1.]
[0. 1. 1.] # Should be [4. 1. 1.]
[0. 1. 1.] # Should be [4. 1. 1.]
[0. 1. 1.] # Should be [4. 1. 1.]
[0. 1. 1.] # Should be [4. 1. 1.]
[0. 1. 1.]] # Should be [4. 1. 1.]
如上所述,如何使用单个内核获得正确的答案,同时使这个内核尽可能快(即避免原子操作)?
max_i
循环引入内核,您进行了两个更改:1.将网格增加了5倍,2.在内核中添加了一个循环。从概念上讲,您只需要其中一个更改。但更大的问题是,将该循环保持在内核之外意味着每次内核调用时都需要进行整个网格的同步,我认为这对于您的算法是必要的。内核中的循环并不能消除这种需求。原子操作也无法解决这个问题。您需要一个内核内的网格同步,而numba cuda没有这个功能。 - Robert Crovellai=1
开始执行(或同时执行)后,才能执行i=2
。然后,我会将i=1
的结果(以及所有的i=odd
)写入一个单独的y_odd
和z_odd
中,而i=2
(以及所有的i=even
)将写入一个y_even
和z_even
中。但是我意识到,如果总线程数大于2 * x.shape[0]
,那么这将是一个问题,因为对于i=3
来说,有足够的线程与i=1
同时执行,现在我有了相同的竞争条件,但是是针对奇数和偶数。我的选择是什么? - slawi
循环放在内核之外可以产生正确的答案,但会导致许多缓慢/昂贵的内核调用,有没有更好的方法从Pythoni
循环中多次调用内核?尝试将for循环推入内核的目的是为了加速处理速度,我感觉离成功已经很接近了,但又似乎遥不可及。 - slawmax_i
得到相同的时间结果),因为98-99%的时间实际上是在调用内核。感谢您抽出时间帮助我解决这个问题! - slaw