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

问题描述

cuda.local.array()

与 numba.cuda.to_device() 相比,使用 numba.cuda.local.array() 对性能有何影响?简单快速排序算法的基准测试表明,使用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 级函数嵌套,跨越数十个函数和数百到数千行代码。这两种方法的替代方法是什么?

标签: pythonallocationnumba

解决方案


备择方案

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

  1. 分配一个串联的向量/矩阵(称为 eg local_args),它实际上代表了 15 个变量。这样做的缺点是需要不断地对其进行切片,并希望您不会意外使用来自不同“子变量”的索引,或者通过稍后添加新变量、更改大小等来破坏排序。
  2. 将操作拆分为按顺序调用的 Numba/CUDA 内核,或Numba cuda.jit()CuPy cupy.fuse()调用和/或其他 CUDA 实现的组合。例如,如果您对一组向量进行操作,否则这些操作将在成对距离矩阵计算中重复多次(昂贵且冗余)(例如 10,000 2而不是 10,000 次),则考虑预先执行这些操作并传递它们in 作为参数(可以与 1. 或 3. 结合使用)
  3. 我遇到的一个方便的替代方法是定义一个自定义 NumPydtype,尽管这可能会导致NVCC 编译器出现问题(希望永久修复)。GitHub问题有一个示例如下:
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.1and cudatoolkit 11.2.2,这表明“自定义 dtype”方法现在可能还可以。

为了防止将大量数据不必要地传递给堆栈跟踪中较低的函数,在这个 custom 中只传递参数的一个子集可能是合适的dtype,但我不知道如何做到这一点。

其他一般有用的例子

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

其中一些示例非常好,因为您可以看到原始的、低效的方法以及如何对其进行修改以变得更加高效,类似于Numba 文档:CUDA: Matrix Multiplication example 并查看其他人如何处理数组分配和在 Numba 中传递参数/CUDA。


推荐阅读