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 会环绕处理。

>>> 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

矩阵类型 (numpy.matrix)#

当从稀疏矩阵计算密集矩阵时(例如,coo_matrix + ndarray),SciPy 返回 numpy.matrix`(:class:`numpy.ndarray 的子类)。然而,CuPy 在这种情况下返回 cupy.ndarray

没有计划在 CuPy 中提供与 numpy.matrix 等效的功能。这是因为自 NumPy 1.15 以来,不再推荐使用 numpy.matrix

数据类型#

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

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

与 NumPy 不同,CuPy 中的通用函数(Universal Functions)只能使用 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 不同,数组种子将被哈希为一个单一的数字,因此可能不会向底层的随机数生成器传递那么多的熵。

NaN(非数字)处理#

默认情况下,CuPy 的归约函数(例如,cupy.sum())在处理复数中的 NaN 时与 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