CuPy 与 NumPy 的区别#

CuPy 的接口设计遵循 NumPy。然而,也存在一些差异。

从浮点数到整数的类型转换行为#

从浮点数到整数的一些类型转换行为在 C++ 规范中没有定义。将负浮点数转换为无符号整数以及将无穷大转换为整数就是其中一些例子。NumPy 的行为取决于你的 CPU 架构。这是在 Intel CPU 上的结果

>>> np.array([-1], dtype=np.float32).astype(np.uint32)
array([4294967295], dtype=uint32)
>>> cupy.array([-1], dtype=np.float32).astype(np.uint32)
array([0], dtype=uint32)
>>> np.array([float('inf')], dtype=np.float32).astype(np.int32)
array([-2147483648], dtype=int32)
>>> cupy.array([float('inf')], dtype=np.float32).astype(np.int32)
array([2147483647], dtype=int32)

随机方法支持 dtype 参数#

NumPy 的随机值生成器不支持 dtype 参数,而是总是返回 float64 值。我们在 CuPy 中支持此选项,因为 CuPy 使用的 cuRAND 支持 float32float64

>>> np.random.randn(dtype=np.float32)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: randn() got an unexpected keyword argument 'dtype'
>>> cupy.random.randn(dtype=np.float32)    
array(0.10689262300729752, dtype=float32)

越界索引#

在使用整数数组索引时,CuPy 默认处理越界索引的方式与 NumPy 不同。NumPy 通过引发错误来处理它们,而 CuPy 会进行回绕(wrap around)。

>>> x = np.array([0, 1, 2])
>>> x[[1, 3]] = 10
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: index 3 is out of bounds for axis 1 with size 3
>>> x = cupy.array([0, 1, 2])
>>> x[[1, 3]] = 10
>>> x
array([10, 10,  2])

索引中的重复值#

当整数数组多次引用同一位置时,CuPy 的 __setitem__ 行为与 NumPy 不同。在这种情况下,实际存储的值是未定义的。这里有一个 CuPy 的例子。

>>> a = cupy.zeros((2,))
>>> i = cupy.arange(10000) % 2
>>> v = cupy.arange(10000).astype(np.float32)
>>> a[i] = v
>>> a  
array([ 9150.,  9151.])

NumPy 存储对应于引用重复位置的元素中的最后一个元素的值。

>>> a_cpu = np.zeros((2,))
>>> i_cpu = np.arange(10000) % 2
>>> v_cpu = np.arange(10000).astype(np.float32)
>>> a_cpu[i_cpu] = v_cpu
>>> a_cpu
array([9998., 9999.])

零维数组#

归约方法#

NumPy 的归约函数(例如 numpy.sum())返回标量值(例如 numpy.float32)。然而,CuPy 的对应函数返回零维 cupy.ndarray。这是因为 CuPy 标量值(例如 cupy.float32)是 NumPy 标量值的别名,并且分配在 CPU 内存中。如果返回这些类型,则需要在 GPU 和 CPU 之间进行同步。如果你想使用标量值,请显式地对返回的数组进行类型转换。

>>> type(np.sum(np.arange(3))) == np.int64
True
>>> type(cupy.sum(cupy.arange(3))) == cupy.ndarray
True

类型提升#

在包含两个或更多操作数的函数中,CuPy 会自动提升 cupy.ndarray 的 dtype,结果 dtype 由输入操作数的 dtype 决定。这与 NumPy 在类型提升方面的规则不同,当操作数包含零维数组时。零维 numpy.ndarray 在 NumPy 函数的操作数中出现时,会被视为标量值处理,这可能会影响其输出的 dtype,具体取决于“标量”输入的值。

>>> (np.array(3, dtype=np.int32) * np.array([1., 2.], dtype=np.float32)).dtype
dtype('float32')
>>> (np.array(300000, dtype=np.int32) * np.array([1., 2.], dtype=np.float32)).dtype
dtype('float64')
>>> (cupy.array(3, dtype=np.int32) * cupy.array([1., 2.], dtype=np.float32)).dtype
dtype('float64')

矩阵类型 (numpy.matrix)#

SciPy 在从稀疏矩阵计算密集矩阵时(例如,coo_matrix + ndarray)返回 numpy.matrixnumpy.ndarray 的子类)。然而,CuPy 对此类操作返回 cupy.ndarray

CuPy 目前没有提供 numpy.matrix 对应类型的计划。这是因为从 NumPy 1.15 开始,不再推荐使用 numpy.matrix

数据类型#

CuPy 数组的数据类型不能是非数值型,例如字符串或对象。详见概述

通用函数仅适用于 CuPy 数组或标量#

与 NumPy 不同,CuPy 中的通用函数仅适用于 CuPy 数组或标量。它们不接受其他对象(例如,列表或 numpy.ndarray)。

>>> np.power([np.arange(5)], 2)
array([[ 0,  1,  4,  9, 16]])
>>> cupy.power([cupy.arange(5)], 2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Unsupported type <class 'list'>

随机种子数组会被哈希为标量#

与 Numpy 一样,CuPy 的 RandomState 对象接受数字或完整的 numpy 数组作为种子。

>>> seed = np.array([1, 2, 3, 4, 5])
>>> rs = cupy.random.RandomState(seed=seed)

然而,与 Numpy 不同,数组种子将被哈希成一个单独的数字,因此可能无法向底层的随机数生成器传递尽可能多的熵(entropy)。

NaN (非数值) 处理#

默认情况下,CuPy 的归约函数(例如,cupy.sum())处理复数中的 NaNs 的方式与 NumPy 的对应函数不同

>>> a = [0.5 + 3.7j, complex(0.7, np.nan), complex(np.nan, -3.9), complex(np.nan, np.nan)]
>>>
>>> a_np = np.asarray(a)
>>> print(a_np.max(), a_np.min())
(0.7+nanj) (0.7+nanj)
>>>
>>> a_cp = cp.asarray(a_np)
>>> print(a_cp.max(), a_cp.min())
(nan-3.9j) (nan-3.9j)

原因是内部的归约是以跨步方式执行的,因此它不能确保正确的比较顺序,也无法遵循 NumPy 始终传播第一个遇到的 NaN 的规则。请注意,当启用 CUB 时(CuPy v11 及更高版本的默认设置),此差异不适用。

连续性 / 跨步#

为了提供最佳性能,生成的 ndarray 的连续性不保证与 NumPy 输出的连续性匹配。

>>> a = np.array([[1, 2], [3, 4]], order='F')
>>> print((a + a).flags.f_contiguous)
True
>>> a = cp.array([[1, 2], [3, 4]], order='F')
>>> print((a + a).flags.f_contiguous)
False