在 cuda 上运行 python 代码 — cuPy 是一个支持 cuda 的 python

1,016 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第8天,点击查看活动详情

我们熟悉的 numpy 是运行在 CPU 上用于操作数组的库。而我们都知道 GPU 更适合进行大规模的平行计算,所以适合处理一些矩阵运算。虽然今天不会深入介绍 cuda 编程模型,但是有一点还是需要大家清楚的,就是 GPU 是为了数据并行计算而设计,如果我们运行满足每一个计算单元都是独立的,相互并不依赖而且每一个计算单元是相同,并且这样的计算是大量的,我们就可以考虑将这样计算移动 GPU 上,今天深度学习中关于矩阵变换和计算就满足以上条件,所以 GPU 出现加速了深度学习向前发展

导入 cuPy

import cupy as cp
import numpy as np

创建数组

random.randint 来创建是一个 2000 x 2000 大小二维矩阵

arr_cpu = np.random.randint(0,255,size=(2000,2000))

然后我们输出一下这个数组内容

array([[ 60, 222, 168, ..., 155, 195, 116], 
        [146, 83, 213, ..., 130, 253, 48], 
        [ 58, 111, 241, ..., 186, 203, 82], 
        ..., 
        [212, 157, 101, ..., 207, 0, 10], 
        [ 18, 238, 128, ..., 182, 176, 114], 
        [ 69, 14, 31, ..., 248, 111, 134]])

我们可以通过下面方式来查看刚刚创建好的 arr_cpu 所占的内存数量

arr_cpu.nbytes/1e6 #32.0

可以通过 numpy 的 nbytes 来查看 numpy 数组所占用的内存数量

x = np.array([100, 12, 32])
x.nbytes #24

然后可以使用 cp.asarray 将 cpu 上数组转移到 gpu 上

%%timeit
arr_gpu = cp.asarray(arr_cpu)

将 cpu 上数组移动到 gpu 上是需要消耗一定成本的

100 loops, best of 5: 7.54 ms per loop

如果将更大的数组从 cpu 上移动到 gpu 上我们需要耗费更多时间,所以这个也是我们在设计程序时候需要考虑到的。

arr_cpu = np.random.randint(0,255,size=(4000,4000))
%%timeit
arr_gpu = cp.asarray(arr_cpu)
%%timeit
arr_gpu = cp.asarray(arr_cpu_2)
100 loops, best of 5: 17.7 ms per loop

傅里叶变换

from scipy import fft
%%timeit
fft.fftn(arr_cpu)
1 loop, best of 5: 430 ms per loop

接下来我们尝试用 scipy 提供傅里叶变换的方法 fft.fftn 来对 arr_gpu 进行操作

fft.fftn(arr_gpu)

这是因为现在 arr_gpu 并不是位于内存上的 Numpy array 而是一个 cupy 数组,scipy 不知道应该如何处理这个数据

TypeError: Implicit conversion to a NumPy array is not allowed. Please use `.get()` to construct a NumPy array explicitly.
from cupyx.scipy import fft as fft_gpu
%%timeit
fft_gpu.fftn(arr_gpu)
The slowest run took 4229.40 times longer than the fastest. This could mean that an intermediate result is being cached. 1 loop, best of 5: 344 µs per loop

这个操作大约耗时 344 微秒,相比上面在 cpu 上运行,这次在 gpu 上运行速度要远远超过在 cpu 上运算速度。The slowest run took 4229.40 times longer than the fastest 这是什么原因,这是因为首先需要将函数加载到 gpu kernel 上,并且需要进行编译到 gpu kernel,所以第一次时间

fft_cpu = fft.fftn(arr_cpu)
fft_from_gpu = cp.asnumpy(fft_gpu.fftn(arr_gpu))
np.allclose(fft_cpu,fft_from_gpu)
True

对于 scipy 的 fftn 方法无法操作 gpu 上数组,不过也有一些方法,例如下面 np.max 可以操作 gpu 上数组,下面我们可以运行一下。

np.max(arr_gpu)
type(np.max(arr_gpu)) #cupy._core.core.ndarray

我们通常需要显式地将数组在 cpu 和 gpu 之间传递,例如上面我们先在 cpu 上创建一个 arr_cpu 然后用 cp.asarray 方法将创建好的 cpu 上数组转移到 gpu 上,其实这不同我们直接用 cp.random.randint 来直接在 gpu 上创建数组。