CuPy加速计算 —— 使用 Python 进行 GPU 加速计算的 NumPy/SciPy 兼容数组库

一、简介

CuPy:是一个基于 NVIDIA CUDA 的高效数组处理库,它提供了与 NumPy 类似的 API,但能够利用 GPU 的强大计算能力来加速数组计算。通过将数据加载到 GPU 中,CuPy 可以显著提升一些数值计算、线性代数、傅里叶变换等操作的速度。

核心优势:能够利用 GPU 进行并行计算,从而加速大规模的数值运算。例如,当数组的维度非常大时,CuPy 可以利用 NVIDIA CUDA 加速,而 NumPy 则只能依赖 CPU。

  • GPU 加速的数组运算:
  • CUDA 内核编程: CuPy 还允许用户编写自定义的 CUDA 内核来执行更加特定的操作。通过 RawKernel 和 ElementwiseKernel,你可以编写 GPU 上运行的高效并行代码。

【CuPy官网】https://cupy.dev/

  • CuPy 是一个使用 Python 进行 GPU 加速计算的开源数组库:利用 CUDA Toolkit 库(包括 cuBLAS、cuRAND、cuSOLVER、cuSPARSE、cuFFT、cuDNN 和 NCCL)充分利用 GPU 架构。
  • CuPy 的接口与 NumPy 和 SciPy 高度兼容在大多数情况下,它可以用作直接替换。需要做的只是在 Python 代码中将 numpy 和 scipy 替换为 cupycupyx.scipy

在这里插入图片描述
左图中显示了 CuPy 相对于 NumPy 的加速。CuPy 将某些操作的速度提高了 100 倍以上。

二、安装

(不同的 CUDA 版本需要不同的 CuPy 安装包)通过以下命令安装:

pip install cupy-cuda10x —— 用于安装 CUDA 10.x 版本对应的CuPy
pip install cupy-cuda11x —— 用于安装 CUDA 11.x 版本对应的CuPy
pip install cupy-cuda12x —— 用于安装 CUDA 12.x 版本对应的CuPy

三、项目实战

3.1、互相转换(CuPy 与 NumPy )

(1)将一个 CuPy 数组转换为 NumPy 数组,并在 CPU 上继续处理;
(2)将 NumPy 数组转换为 CuPy 数组以便在 GPU 上加速计算。

(1)CuPy 到 NumPy:cp.asnumpy() —— <class ‘numpy.ndarray’>

import cupy as cp

x = cp.array([1, 2, 3])
x_cpu = cp.asnumpy(x)  # 转换为 NumPy 数组
print(type(x_cpu))     # 打印 NumPy 数组的类型
"""<class 'numpy.ndarray'>"""

(2)NumPy 到 CuPy:cp.asarray() —— <class ‘cupy.ndarray’>

import cupy as cp
import numpy as np

x_np = np.array([1, 2, 3])
x_gpu = cp.asarray(x_np)  # 转换为 CuPy 数组
print(type(x_gpu))     # 打印 NumPy 数组的类型
"""<class 'cupy.ndarray'>"""

3.2、CuPy基础函数

CuPy:API Reference

import cupy as cp

x = cp.array([1, 2, 3])  # 在 GPU 上创建一个数组
y = cp.array([4, 5, 6])
z1 = x + y  # 计算两个数组的元素级加法
z2 = x[:2]  # 获取前两个元素
A = cp.array([[1, 2], [3, 4]])
B = cp.array([[5, 6], [7, 8]])
C = cp.dot(A, B)  # 矩阵乘法

cp.linalg.inv(A)  # 求矩阵的逆
cp.fft.fft(x)  # 傅里叶变换
cp.random.rand(3, 3)  # 随机数生成

3.3、CuPy自定义内核(速度更快)

如果想让代码运行得更快,可以轻松制作自定义 CUDA 内核,只需一小段 C++ 代码即可。CuPy 会自动包装并编译它以生成 CUDA 二进制文件。编译后的二进制文件会被缓存并在后续运行中重复使用。

CuPy 提供了几种方法来在 GPU 上定义和运行内核。通过编写 CUDA 内核代码,CuPy 能够直接执行并行计算任务,充分利用 GPU 的计算能力。

官网介绍:定义内核

  • (1)ElementwiseKernel(向量计算):是一种简化的 API,适用于逐元素操作。
    • 用户只需要提供一个简单的 CUDA 内核代码,系统将自动管理网格和线程块。
  • (2)RawKernel(矩阵计算):是一个更加底层的 API,适用于需要自定义并行计算任务(如矩阵乘法、卷积等)。
    • 用户需要直接编写 CUDA C++ 内核代码(通常是使用 __global____device__ 修饰符),且需要手动管理网格和线程块。
import cupy as cp
import time

N = 10000
x = cp.random.rand(N).astype(cp.float32)
y = cp.random.rand(N).astype(cp.float32)

kernel = '''
    if (x - 2 > y) {
      z = x * y;
    } else {
      z = x + y;
    }
    '''

kernel = cp.ElementwiseKernel('float32 x, float32 y', 'float32 z', kernel, 'my_kernel')

for i in range(10):
    start = time.time()
    c = kernel(x, y)
    print(f"i = {i}, 时耗: {time.time() - start:.8f}秒")

"""
i = 0, 时耗: 0.00099659秒
i = 1, 时耗: 0.00000000秒
i = 2, 时耗: 0.00000000秒
i = 3, 时耗: 0.00000000秒
i = 4, 时耗: 0.00000000秒
i = 5, 时耗: 0.00000000秒
i = 6, 时耗: 0.00000000秒
i = 7, 时耗: 0.00000000秒
i = 8, 时耗: 0.00000000秒
i = 9, 时耗: 0.00000000秒
"""

3.4、速度测试(矩阵计算)

(1)【NumPy】np.dot(A, B) —— 时耗 5.6 秒

import numpy as np
import time

A = np.random.rand(10000, 10000)
B = np.random.rand(10000, 10000)

for i in range(10):
    start = time.time()
    C = np.dot(A, B)  # 在 CPU 上执行矩阵乘法
    print(f"i = {i}, 时耗: {time.time() - start:.8f}秒")
"""
i = 0, 时耗: 5.59134936秒
i = 1, 时耗: 5.66079187秒
i = 2, 时耗: 5.63619781秒
i = 3, 时耗: 5.62660122秒
i = 4, 时耗: 5.64722443秒
i = 5, 时耗: 5.57355118秒
i = 6, 时耗: 5.59616947秒
i = 7, 时耗: 5.63257861秒
i = 8, 时耗: 5.80971384秒
i = 9, 时耗: 5.62141252秒
"""

(2)【CuPy】cp.dot(A, B) —— 时耗 0.058 秒

import cupy as cp
import time

A = cp.random.rand(10000, 10000)
B = cp.random.rand(10000, 10000)

for i in range(10):
    start = time.time()
    C = cp.dot(A, B)  # 在 GPU 上执行矩阵乘法
    print(f"i = {i}, 时耗: {time.time() - start:.8f}秒")
"""
i = 0, 时耗: 0.05804682秒
i = 1, 时耗: 0.00100160秒
i = 2, 时耗: 0.00000000秒
i = 3, 时耗: 0.00000000秒
i = 4, 时耗: 0.00000000秒
i = 5, 时耗: 0.00000000秒
i = 6, 时耗: 0.00000000秒
i = 7, 时耗: 0.00000000秒
i = 8, 时耗: 0.00000000秒
i = 9, 时耗: 0.00000000秒
"""

(3)【CuPy】自定义内核 —— 时耗 0.039 秒

######################################################################################################
# extern "C":  修饰符用于告诉编译器函数按照 C 语言的调用约定进行编译,而不是 C++ 的默认调用约定。
#               C 语言的调用约定使得生成的符号(函数名)具有 C 风格的符号名称,避免 C++ 编译器引入名字重载和名称修饰。
# __global__:  这是 CUDA 编程中的关键字,用来定义一个 CUDA 内核函数(即 GPU 上执行的代码)。
#               带有 __global__ 的函数将会在 GPU 上执行,并且这些函数必须是从主机(CPU)代码中调用的。
#               它定义了 GPU 的并行线程模型,__global__ 函数可以通过线程块和网格进行并行化执行。
######################################################################################################
import cupy as cp
import time

A = cp.random.rand(10000, 10000)
B = cp.random.rand(10000, 10000)

for i in range(10):
    # (1)定义内核:(矩阵乘法)将每个元素的计算作为一个线程
    kernel = """
    extern "C" __global__ void matmul_kernel(const float* A, const float* B, float* C, int N) 
    {
        int row = blockIdx.y * blockDim.y + threadIdx.y;
        int col = blockIdx.x * blockDim.x + threadIdx.x;

        if (row < N && col < N) 
        {
            float value = 0.0f;
            for (int k = 0; k < N; ++k) 
            {
                value += A[row * N + k] * B[k * N + col];
            }
            C[row * N + col] = value;
        }
    }
    """
    # (2)编译内核,并创建 RawKernel 对象
    matmul_kernel = cp.RawKernel(kernel, 'matmul_kernel')

    # (3)运行内核
    #########################################################################
    # 设置网格和块的大小
    N = A.shape[0]
    block_size = (32, 32)  # 每个 block 中的线程数
    grid_size = (N // block_size[0] + 1, N // block_size[1] + 1)  # 网格大小

    # 创建输出矩阵
    C = cp.zeros_like(A)

    start = time.time()
    matmul_kernel((grid_size), (block_size), (A, B, C, N))
    print(f"i = {i}, 时耗: {time.time() - start:.8f}秒")
    #########################################################################

"""
i = 0, 时耗: 0.03830218秒
i = 1, 时耗: 0.00000000秒
i = 2, 时耗: 0.00000000秒
i = 3, 时耗: 0.00000000秒
i = 4, 时耗: 0.00000000秒
i = 5, 时耗: 0.00000000秒
i = 6, 时耗: 0.00000000秒
i = 7, 时耗: 0.00000000秒
i = 8, 时耗: 0.00000000秒
i = 9, 时耗: 0.00000000秒
"""
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

胖墩会武术

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值