什么是 numba.cuda.local.array() 的有效替代方案,它们不像通过 to_device() 传递许多参数那么麻烦?

Posted

技术标签:

【中文标题】什么是 numba.cuda.local.array() 的有效替代方案,它们不像通过 to_device() 传递许多参数那么麻烦?【英文标题】:What are efficient alternatives to numba.cuda.local.array() that aren't as cumbersome as passing many arguments via to_device()? 【发布时间】:2021-10-22 11:44:20 【问题描述】:

cuda.local.array()

在 How is performance affected by using numba.cuda.local.array() compared with numba.cuda.to_device()? 中,简单快速排序算法的基准测试表明,使用 to_device 传递预分配数组的效率可以提高约 2 倍,但这需要更多内存。

单独排序 2,000,000 行每行包含 100 个元素的基准测试结果如下:

2000000
Elapsed (local: after compilation) = 4.839058876037598
Elapsed (device: after compilation) = 2.2948694229125977
out is sorted
Elapsed (NumPy) = 4.541851282119751

使用to_device() 的虚拟示例

如果您有一个复杂的程序,它有许多 cuda.local.array() 调用,等效的 to_device 版本可能会开始看起来像这样并且变得相当麻烦:

def foo2(var1, var2, var3, var4, var5, var6, var7, var8, var9, var10, out):
    for i in range(len(var1)):
        out[i] = foo(var1, var2, var3, var4, var5, var6, var7, var8, var9, var10, out)

def foo3(var1, var2, var3, var4, var5, var6, var7, var8, var9, var10, out):
    idx = cuda.grid(1)
    foo(var1, var2, var3, var4, var5, var6, var7, var8, var9, var10, out[idx])

在真实的代码库中,可能有 3-4 级函数嵌套,跨越数十个函数和数百到数千行代码。 这两种方法的替代方法是什么?

【问题讨论】:

【参考方案1】:

替代方案

以下是cuda.local.array() 和通过cuda.to_device() 单独传递参数的一些替代方案:

    分配一个串联的向量/矩阵(例如local_args),它实际上代表了 15 个变量。这样做的缺点是需要不断对其进行切片,并希望您不会意外使用来自不同“子变量”的索引或通过稍后添加新变量、更改大小等来破坏排序。 将操作拆分为顺序调用的 Numba/CUDA 内核,或 Numbacuda.jit()、CuPycupy.fuse() 调用和/或其他 CUDA 实现的组合。例如,如果您对一组向量进行操作,否则这些操作将在成对距离矩阵计算中(例如 10,0002 而不是 10,000 次)重复多次(昂贵且冗余),然后考虑预先执行这些操作并将它们作为参数传入(可以与 1. 或 3. 结合使用) 我遇到的一个方便的替代方法是define a custom NumPy dtype,尽管这可能会导致issues with the NVCC compiler(希望永久修复)。 GitHub issue 有一个例子如下:
import numpy as np
np_int = np.int32
np_float = np.float32
cuda_const_arrays_type = np.dtype([
('a1', (np_int,(7776, 13))),
('a2', (np_int,(7776, 2, 5))),
('a3', (np_int,(16494592))),
('a4', (np_int,13)),
('a5', (np_float,(22528, 64))),
('a6', (np_int,(522523, 64))),
('a7', (np_int,(32,5))),
('a8', (np_int,(66667))),
('a9', (np_int,(252, 64, 3, 2, 2, 2, 2, 2, 2, 13))),
('a10', (np_int,(7776)))
])
cuda_const_arrays = np.zeros(1, dtype=cuda_const_arrays_type)
for txt in cuda_const_arrays_type.names: # i.e. ("a1", "a2", ...)
    cuda_const_arrays[0][txt] = np.loadtxt(open(txt+".csv", "rb"), delimiter=",", skiprows=1)
gpu_const_arrays = cuda.to_device(cuda_const_arrays[0])

@cuda.jit(device=True)
def cuda_doSomething(gpu_const_arrays,...):
    gpu_const_arrays.a1

可以在Gitlab 上找到来自同一用户的示例(可以删除import keras as ks 行)。虽然这会导致以前的 Numba 版本出现零星错误,但它在 numba 0.53.1cudatoolkit 11.2.2 上运行良好,表明“自定义 dtype”方法可能是 OK now。

为了防止不必要地将大量数据传递给堆栈跟踪中较低的函数,在此自定义 dtype 中仅传递参数的子集可能是合适的,但我不知道该怎么做这个。

其他一般有用的例子

虽然我们正在等待 CuPy 或 NumPy 支持 Numba/CUDA 7 9 10 11,但以下是我发现在编写 Numba/CUDA 脚本的工作流程中相关/有用的示例.

Why numba cuda is running slow after recalling it several times? accelerated FFT to be invoked from Python Numba CUDA kernel Numba Discourse: Optimizing Code Further, CUDA Jit?(Graham Markall 提供了很好的建议和示例) Cuda Optimize Jaro Distance(Graham Markall 的实施示例和解释) Numba convolutions 和 user's implementations in NumPy, CuPy, and Numba How to generalize fast matrix multiplication on GPU using numba(扩展/更正 Numba Docs matmul 示例)

其中一些示例非常好,因为您可以看到原始的低效方法以及如何对其进行修改以提高效率,类似于 Numba Docs: CUDA: Matrix Multiplication 示例,并了解其他人如何处理数组分配和 Numba 中的参数传递/ CUDA。

【讨论】:

以上是关于什么是 numba.cuda.local.array() 的有效替代方案,它们不像通过 to_device() 传递许多参数那么麻烦?的主要内容,如果未能解决你的问题,请参考以下文章

时间是什么?时间同步是什么?GPS北斗卫星授时又是什么?

什么是拉电流,什么是灌电流?什么是吸收电流 ?

在java中,OOA是什么?OOD是什么?OOP是什么?

什么是DIV,全称是什么?

什么是抢占/什么是可抢占内核?到底有什么好处呢?

什么是 JNDI?它的基本用途是什么?什么时候使用?