CUDA 集成#

Arrow 不限于 CPU 缓冲区(位于计算机的主内存中,也称为“主机内存”)。它还提供访问位于 CUDA 支持的 GPU 设备(在“设备内存”中)的缓冲区的规定。

注意

此功能是可选的,必须在构建时启用。如果您的包管理器没有这样做,您可能需要自己构建 Arrow。

CUDA 上下文#

CUDA 上下文表示对特定 CUDA 支持的设备的访问。例如,这是创建访问 CUDA 设备编号 0 的 CUDA 上下文

>>> from pyarrow import cuda
>>> ctx = cuda.Context(0)
>>>

CUDA 缓冲区#

CUDA 缓冲区可以通过使用 Context.buffer_from_data() 方法将数据从主机内存复制到 CUDA 设备的内存来创建。源数据可以是任何 Python 缓冲区类对象,包括 Arrow 缓冲区

>>> import numpy as np
>>> arr = np.arange(4, dtype=np.int32)
>>> arr.nbytes
16
>>> cuda_buf = ctx.buffer_from_data(arr)
>>> type(cuda_buf)
pyarrow._cuda.CudaBuffer
>>> cuda_buf.size     # The buffer's size in bytes
16
>>> cuda_buf.address  # The buffer's address in device memory
30088364544
>>> cuda_buf.context.device_number
0

相反,您可以将 CUDA 缓冲区复制回设备内存,获取一个常规的 CPU 缓冲区

>>> buf = cuda_buf.copy_to_host()
>>> type(buf)
pyarrow.lib.Buffer
>>> np.frombuffer(buf, dtype=np.int32)
array([0, 1, 2, 3], dtype=int32)

警告

许多 Arrow 函数期望一个 CPU 缓冲区,但不会检查缓冲区的实际类型。如果您将 CUDA 缓冲区传递给此类函数,您将遇到崩溃

>>> pa.py_buffer(b"x" * 16).equals(cuda_buf)
Segmentation fault

Numba 集成#

虽然你无法直接在 Python 中对 Arrow CUDA 缓冲区进行太多操作,但它们支持与 Numba 交互,Numba 是一种 JIT 编译器,可以将 Python 代码转换为优化的 CUDA 内核。

Arrow 到 Numba#

首先,让我们定义一个在 int32 数组上运行的 Numba CUDA 内核。在这里,我们将简单地递增每个数组元素(假设数组是可写的)。

import numba.cuda

@numba.cuda.jit
def increment_by_one(an_array):
    pos = numba.cuda.grid(1)
    if pos < an_array.size:
        an_array[pos] += 1

然后,我们需要将我们的 CUDA 缓冲区包装到一个具有正确数组元数据(形状、步长和数据类型)的 Numba “设备数组” 中。这是必要的,以便 Numba 可以识别数组的特征并使用适当的类型声明编译内核。

在这种情况下,元数据可以简单地从原始 NumPy 数组中获取。请注意,GPU 数据没有被复制,只是被指向。

>>> from numba.cuda.cudadrv.devicearray import DeviceNDArray
>>> device_arr = DeviceNDArray(arr.shape, arr.strides, arr.dtype, gpu_data=cuda_buf.to_numba())

(理想情况下,我们可以在 CPU 内存中定义一个 Arrow 数组,将其复制到 CUDA 内存中而不会丢失类型信息,然后在它上面调用 Numba 内核,而无需手动构建 DeviceNDArray;这目前还不可行)

最后,我们可以使用 Numba 设备数组运行 Numba CUDA 内核(这里使用 16x16 网格大小)。

>>> increment_by_one[16, 16](device_arr)

然后可以通过将 CUDA 缓冲区复制回 CPU 内存来检查结果。

>>> np.frombuffer(cuda_buf.copy_to_host(), dtype=np.int32)
array([1, 2, 3, 4], dtype=int32)

Numba 到 Arrow#

相反,可以使用 CudaBuffer.from_numba() 工厂方法将 Numba 创建的设备数组视为 Arrow CUDA 缓冲区。

为了举例说明,让我们首先创建一个 Numba 设备数组。

>>> arr = np.arange(10, 14, dtype=np.int32)
>>> arr
array([10, 11, 12, 13], dtype=int32)
>>> device_arr = numba.cuda.to_device(arr)

然后,我们可以创建一个指向设备数组内存的 CUDA 缓冲区。这次我们不需要显式传递 CUDA 上下文:适当的 CUDA 上下文将自动从 Numba 对象中检索和适配。

>>> cuda_buf = cuda.CudaBuffer.from_numba(device_arr.gpu_data)
>>> cuda_buf.size
16
>>> cuda_buf.address
30088364032
>>> cuda_buf.context.device_number
0

当然,我们可以将 CUDA 缓冲区复制回主机内存。

>>> np.frombuffer(cuda_buf.copy_to_host(), dtype=np.int32)
array([10, 11, 12, 13], dtype=int32)

另请参阅

Numba 的 CUDA 支持 文档。