1. CuPy入门:CuPy是什么、与NumPy的关系、安装配置、GPU环境检查

1.1 CuPy是什么?

简单来说,CuPy就是NumPy的GPU版本。我最初接触时,第一反应是“这不就是把数组操作搬到显卡上吗?”没错,它的核心就是让你用几乎一样的API,在GPU上做并行计算。你写过的np.array()、np.dot(),换成cp.array()、cp.dot(),就能自动跑在CUDA核心上。底层靠的是CUDA和cuDNN这些库,但CuPy帮你把调用细节全封装好了。

我在项目中遇到过几次:数据量一大,NumPy跑个聚类要等半天。换成CuPy后,同样是K-Means,训练时间从十几分钟压缩到几十秒。当然,不是所有场景都适合——小数据或频繁CPU-GPU传输时反而更慢。这个后面会细讲。

1.2 与NumPy的关系

CuPy和NumPy就像双胞胎。API长得几乎一样,但一个在CPU上跑,一个在GPU上跑。下面的表格可以帮你快速对比:

维度 NumPy CuPy
运行设备 CPU (内存) GPU (显存)
数据类型 ndarray cupy.ndarray
核心库 BLAS, LAPACK 等 CUDA, cuBLAS, cuDNN
并行能力 多线程 (CPU核心) 数千个CUDA核心
适用数据量 中小规模 (GB级以内) 大规模 (显存GB/TB级)
API兼容性 参考基准 约90%接口一致

你想想看,如果你已经会用NumPy,上手CuPy基本就是改个import的事。但有几个细节要留意:

  • 显存有限:一个float32的1亿元素数组占用约400MB显存,别把整机内存都往里塞。
  • 数据传输延迟:每次cupy.asnumpy()都要从显存拷贝回内存,频繁操作会拖慢速度。
  • 部分函数未实现:比如某些复杂的排序算法,CuPy还没覆盖全。碰到时可以用cp.asnumpy()回退到NumPy处理。
我个人习惯:先在NumPy上验证小数据,再切到CuPy跑全量。这样既能保正逻辑正确,又能享受加速红利。

1.3 安装配置

安装CuPy并不复杂,但关键的一步是选对CUDA版本对应的包。我之前踩过一个坑:机器上装的CUDA 11.7,我直接pip install cupy,结果装了个cupy-cuda12x的版本,跑起来报错说找不到CUDA运行时。

正确做法是先查你的CUDA版本:

nvidia-smi | grep "CUDA Version"

比如看到CUDA Version: 11.7,那就装cupy-cuda11x系列:

pip install cupy-cuda11x

如果你用的是CUDA 12.x,就换:

pip install cupy-cuda12x

另外,官方还提供cupy-cuda101、cupy-cuda110等更细分的版本,但日常用最常见的版本号就好。我建议用虚拟环境安装,避免污染全局Python。比如:

python -m venv cupy_env
source cupy_env/bin/activate   # Linux/Mac
cupy_env\Scripts\activate      # Windows
pip install cupy-cuda11x

如果你没有NVIDIA显卡,或者CUDA版本特别老(比如CUDA 10.x),可以考虑用cupy-rocm(AMD ROCm)或者纯CPU模式跑CuPy做测试——但那就没有加速意义了。

小技巧: 装好后用pip show cupy-cuda11x确认版本。如果看到类似Requires: fastrlock, numpy,说明依赖正确。

1.4 GPU环境检查

装好之后,咱们得确认CuPy真的能调用到GPU。先跑个基本的硬件检查:

nvidia-smi

你会看到GPU型号、显存、温度、进程等信息。如果没输出,说明驱动没装好或者没有NVIDIA显卡——CuPy就没法用了。

然后进Python验证:

import cupy as cp
print(cp.__version__)
print(cp.cuda.runtime.getDeviceCount())   # GPU数量
print(cp.cuda.runtime.getDeviceProperties(0)['name'])  # 第一块显卡名字

输出类似:

12.0.0
1
'NVIDIA GeForce RTX 3090'

如果getDeviceCount()返回0,要么驱动没装好,要么CuPy链接的CUDA版本不对。解决办法:卸载当前CuPy,重新装匹配CUDA的版本。

我还习惯跑一个简单的性能测试,看看加速效果:

import numpy as np
import cupy as cp
import time

n = 10000
a_np = np.random.randn(n, n).astype(np.float32)
b_np = np.random.randn(n, n).astype(np.float32)

a_cp = cp.asarray(a_np)
b_cp = cp.asarray(b_np)

# CPU 矩阵乘法
t0 = time.time()
c_np = np.dot(a_np, b_np)
t1 = time.time()
print(f"NumPy: {t1-t0:.4f} s")

# GPU 矩阵乘法
t0 = time.time()
c_cp = cp.dot(a_cp, b_cp)
cp.cuda.Stream.null.synchronize()  # 等待GPU完成
t1 = time.time()
print(f"CuPy:  {t1-t0:.4f} s")

在我机器上,NumPy大约2.1秒,CuPy只要0.08秒——快了25倍。这个差距会随矩阵增大而更明显。

注意: CuPy默认是异步执行的。上面例子中我调用了cp.cuda.Stream.null.synchronize(),否则时间测量会不准确。这块儿容易出错,我建议每次测速都用同步。
CuPy 加速流程 输入NumPy数组 转换为CuPy数组 数据传输到GPU GPU并行计算 结果传输回CPU 输出CuPy数组 说明:整个流程中,最关键的是第4步GPU并行计算——数据量大时速度提升最明显。 第3步和第5步的数据传输是瓶颈,应尽量在GPU内完成所有运算。

好了,到此你已经掌握了CuPy的基本概念、安装方法和环境验证。其实说白了,就是让NumPy代码“坐火箭”——把任务扔给GPU的几千个核心并行处理。接下来你就可以开始用CuPy写聚类和训练脚本了。不过别急,记得先跑通上面的测试代码,确保你的环境没问题。