NEP 53 — 为 NumPy 2.0 发展 NumPy C-API #
- 作者:
塞巴斯蒂安·伯格< sebastianb @ nvidia 。 com >
- 地位:
草稿
- 类型:
标准
- 创建:
2022-04-10
抽象的#
NumPy C-API 用于下游项目(通常通过 Cython)来扩展 NumPy 功能。支持这些包通常意味着我们的 C-API 的发展速度很慢,并且在正常的 NumPy 版本中不可能进行某些更改,因为 NumPy 必须保证向后兼容性:针对旧 NumPy 版本(例如 1.17)编译的下游包通常可以与新的 NumPy 版本(例如 1.25)。
NumPy 2.0 版本允许部分打破这一承诺:我们可以接受使用 NumPy 1.17 编译的 SciPy 版本(例如 SciPy 1.10)将无法与NumPy 2.0 一起使用。但是,创建与 NumPy 1.x 和 NumPy 2.0 兼容的单个 SciPy 二进制文件仍然必须很容易。
鉴于这些限制,本 NEP 概述了允许对 C-API 进行重大更改的前进道路。与针对 NumPy 2.0 提出的 Python API 更改类似,NEP 旨在允许进行一定程度的更改,预计大多数下游包不需要或只需要进行少量代码更改。
该新经济政策的实施包括两个步骤:
作为总体改进的一部分,从使用 NumPy 构建的 NumPy 1.25 开始,默认情况下将导出旧的 API 版本,以允许与最新可用的 NumPy 版本向后兼容的构建。 (除非选择加入,否则新 API 不可用。)
NumPy 2.0 将:
需要针对 NumPy 2.0 重新编译下游包以与 NumPy 2.0 兼容。
在 NumPy 1.x 上运行时需要 a
numpy2_compat
作为依赖项。需要更改一些下游代码以适应更改的 API。
动机和范围#
NumPy API 由 300 多个函数和大量宏组成。其中许多已经过时:有些仅在 NumPy 中使用,仅为了与 NumPy 的前身兼容而存在,或者没有或只有一个已知的下游用户(即 SciPy)。
此外,NumPy 使用的许多结构始终是公开的,因此不可能在主要版本之外更改它们。一些更改已计划多年,并且是NPY_NO_DEPRECATED_API
进一步弃用
的原因,
如C API 弃用中所述。
虽然我们可能没有什么理由改变数组结构(PyArrayObject_fields
)的布局,例如通过改变PyArray_Descr结构可以使数据类型的开发和改进变得更容易。
该 NEP 主要作为示例对我们的 C-API 提出了一些具体更改。然而,更多变更将根据具体情况进行处理,我们无意提供本 NEP 中变更的完整列表。
添加状态超出范围#
新的发展(例如 CPython 对子解释器和 HPy API 的支持)可能要求 NumPy C-API 以可能需要(或至少更喜欢)传入状态的方式发展。
截至目前,我们不打算在此进行更改。我们不能期望用户进行大量代码更新以将上下文传递HPy
给许多 NumPy 函数。
虽然我们可以在 NumPy 2.0 中为此目的引入第二个 API,但我们预计这是不必要的,并且此处引入的规定:
能够使用最新的 NumPy 版本进行编译,但与旧版本兼容,
以及更新包的可能性
numpy2_compat
。
应该允许在次要版本中添加这样的 API。
使用和影响#
向后兼容的版本#
向后兼容的版本将在文档中更详细地描述。简而言之,我们将允许用户使用如下定义:
#define NPY_TARGET_VERSION NPY_1_22_API_VERSION
选择他们希望编译的版本(兼容的最低版本)。默认情况下,向后兼容性将使得生成的二进制文件与支持相同版本的 Python 的最旧的 NumPy 版本兼容:NumPy 1.19.x 是第一个支持 Python 3.9 的版本,NumPy 1.25 支持 Python 3.9 或更高版本,因此 NumPy 1.25默认为 1.19 兼容性。因此,新API的用户可能需要添加定义,但是想要兼容旧版本的用户不需要做任何事情,除非他们希望具有特别长的兼容性。
过go几年添加的 API 非常有限,因此对于全球的一小部分用户来说,这样的更改最多是必要的。
这种机制与Python 受限 API非常相似,因为 NumPy 的 C-API 对 ABI 稳定性也有类似的需求。
打破 C-API 并更改 ABI #
NumPy 的函数太多,其中很多都是别名。以下列出了我们计划删除的内容示例,用户必须进行调整才能与 NumPy 2.0 兼容:
PyArray_Mean
和是与和PyArray_Std
类似的未经测试的实现 。我们计划删除它们,因为它们可以相对容易地用方法调用替换。arr.mean()
arr.std()
API 函数(和结构)集
MapIter
允许实现高级索引,例如下游语义。这个历史上有一个 已知的用户(theano),并且用例会以不同的方式更快、更容易地实现。 API 很复杂,需要深入 NumPy 才能发挥作用,而且它的暴露使得实现更加困难。除非发现新的重要用例,否则我们建议将其删除。
PyArray_Descr
ABI 更改的一个示例是更改(实例结构)的布局np.dtype
以允许更大的最大项目大小和新标志(对未来的自定义用户 DType 有用)。对于此特定更改,直接访问结构字段的用户将必须更改其代码。下游搜索发现这种情况应该不是很常见,主要影响是:
字段(和其他)的访问
descr->elsize
必须替换为类似的宏PyDataType_ITEMSIZE(descr)
(NumPy 可能在需要时包括版本检查)。用户定义的数据类型的实现者必须更改几行代码,幸运的是,这样的用户定义的数据类型很少。 (详细信息是我们将结构重命名
PyArray_DescrProto
为静态定义,并显式从 NumPy 获取实际实例。)
最后一个例子是NPY_MAXDIMS
增加到64
。
NPY_MAXDIMS
主要用于静态分配暂存空间:
func(PyArrayObject *arr) {
npy_intp shape[NPY_MAXDIMS];
/* Work with a shape or strides from the array */
}
NPY_MAXDIMS=32
如果 NumPy 在次要版本中将其更改为 64,则在编译代码时传入 40 维数组时,这将导致未定义的行为。但较大的值也是以前版本的 NumPy 的正确最大值,因此通常是安全的对于 NumPy 2.0 的更改。 (可以想象一下想要知道实际运行时值的代码。我们在实践中还没有看到这样的代码,但它需要调整。)
对 Cython 用户的影响#
Cython 用户可以通过.由于 Cython 开发的不确定性,对 Cython 用户的影响有两种情况。cimport numpy as cnp
如果可以依赖 Cython 3,Cython 用户受到的影响将小于C-API 用户,因为 Cython 3 允许我们隐藏结构布局更改(即对 的更改PyArray_Descr
)。如果情况并非如此,并且我们必须支持 Cython 0.29.x(这是 Cython 3 之前的历史分支),那么 Cython 用户还必须使用类似的函数/宏
PyDataType_ITEMSIZE()
(或使用 Python 对象)。不幸的是,这在 Cython 代码中不太常见,但也不太可能成为 dtype 结构字段/属性的常见模式。
进一步的影响是,一些未来的 API 添加(例如新类)可能需要放置在不同的.pyd
文件中,以避免 Cython 生成在旧 NumPy 版本上失败的代码。
最终用户和包装的影响#
以与 NumPy 2.0 兼容的方式进行打包将需要重新编译依赖于 NumPy C-API 的下游库。这可能需要一些时间,但希望该过程能够在 NumPy 2.0 本身发布之前开始。
此外,为了在 NumPy 2.0 中更轻松地进行更大的更改,我们希望创建一个numpy2_compat
包。当使用 NumPy 2.0 构建库但想要支持 NumPy 1.x 时,它必须依赖于numpy2_compat
.最终用户不需要了解这种依赖性,并且当模块丢失时可能会引发信息性错误。
一些新的 API 可以向后移植#
允许用户使用最新版本的 NumPy 进行编译的一大优势是,在某些情况下我们将能够向后移植新的 API。一些新的 API 函数可以根据旧的 API 函数编写或直接包含。
笔记
可以通过兼容numpy2_compat
包将 NumPy 1.x 中存在但私有的函数公开。
这意味着下游用户可以更快地使用一些新的 API 添加。他们需要新的 NumPy 版本进行 编译,但他们的轮子可以向后兼容早期版本。
执行#
实现的第一部分(允许构建早期的 API 版本)非常简单,因为 NumPy C-API 多年来发展缓慢。默认情况下,某些结构体字段将被隐藏,并且更新版本中引入的函数将被标记和隐藏,除非用户选择使用更新的 API 版本。可以在PR 23528中找到实现。
第二部分主要是确定和实现所需的更改,从而不会破坏向后兼容性,并且 API 中断对于下游库来说仍然是可管理的。我们所做的每一个改变都必须有一个简短的说明,说明如何适应API的改变(即替代功能)。
NumPy 2 兼容性和 API 表更改#
为了允许更改 API 表,NumPy 2.0 将提供与 NumPy 1.x 不同的表(表是函数和符号的列表)。
为了兼容性,我们需要将 1.x 表转换为 2.0 表。这仅在理论上可以在标头中完成,但这似乎很笨拙。因此我们建议添加一个numpy2_compat
包。该包的主要目的是在一个位置提供 1.x 表到 2.x 表的转换(填充任何必要的空白)。
引入此包解决了“过渡”问题,因为它允许用户:
安装与 2.0 和 1.x 兼容的 SciPy 版本
并继续使用 NumPy 1.x,因为他们使用的其他软件包尚不兼容。
的导入numpy2_compat
(以及缺少时的错误)将由 NumPy 读取器作为调用的一部分插入import_array()
。
备择方案#
总是有可能决定不进行某些更改(例如,由于下游用户注意到他们仍然需要它)。例如,
如果需要,PyArray_Mean
可以将该函数替换为调用的函数。array.mean()
NEP 提议通过引入兼容性包来允许对我们的 API 表进行更大的更改numpy2_compat
。我们可以在不引入这样的包的情况下进行许多更改。
默认 API 版本可以选择较旧的版本或当前版本。旧版本将针对那些想要比 NEP 29 建议的更大兼容性的库。选择当前选项将默认为不分发轮子的用户删除不必要的兼容性垫片。建议的默认选择有利于分发轮子并希望具有类似于 NEP 29 的兼容性范围的库。这是因为兼容性垫片应该是轻量级的,并且我们预计很少有库需要更长的兼容性。
向后兼容性#
如上所述,向后兼容性是通过以下方式实现的:
强制下游使用 NumPy 2.0 重新编译
提供
numpy2_compat
图书馆。
但依赖于用户适应更改后的 C-API,如“用法和影响”部分中所述。
讨论#
numpy/numpy#5888之前提到过,允许在标头中导出较旧的 API 版本会很有帮助。这从未实现,而是我们依赖oldest-support-numpy。
该提案的初稿已在 2023 年 4 月 3 日的 NumPy 2.0 规划会议上提交。
参考文献和脚注#
版权所有#
本文档已置于公共领域。[ 1 ]