NEP 53 — 發展 NumPy 2.0 的 NumPy C-API#
- 作者:
Sebastian Berg <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 旨在允許擴展變更,使得大多數下游套件預計不需要或只需要進行少量程式碼變更。
此 NEP 的實作將包含兩個步驟
作為一般改進的一部分,從 NumPy 1.25 開始,使用 NumPy 建置時,預設會匯出較舊的 API 版本,以允許與最新可用的 NumPy 版本進行向後相容的建置。(除非選擇加入,否則新 API 無法使用。)
NumPy 2.0 將
需要針對 NumPy 2.0 重新編譯下游套件,才能與 NumPy 2.0 相容。
在 NumPy 1.x 上執行時,需要
numpy2_compat
作為依賴項。需要一些下游程式碼變更以適應已變更的 API。
動機與範疇#
NumPy API 包含超過 300 個函式和眾多巨集。其中許多已過時:有些僅在 NumPy 內部使用,僅為了與 NumPy 的前身相容而存在,或者沒有或只有單一已知的下游使用者 (即 SciPy)。
此外,NumPy 使用的許多結構一直都是公開的,因此無法在主要版本之外變更它們。某些變更已計畫多年,並且是 NPY_NO_DEPRECATED_API
和進一步棄用的原因,如C API 棄用中所述。
雖然我們可能沒有太多理由變更陣列結構 (PyArrayObject_fields
) 的佈局,例如,透過變更 PyArray_Descr 結構,可以更輕鬆地開發和改進 dtype。
此 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 的使用者可能需要新增定義,但想要與舊版本相容的使用者無需執行任何操作,除非他們希望具有異常長的相容性。
過去幾年的 API 新增非常有限,因此這種變更最多只需要全球少數使用者即可。
此機制與 Python 有限 API 非常相似,因為 NumPy 的 C-API 對 ABI 穩定性有類似的需求。
打破 C-API 並變更 ABI#
NumPy 有太多函式,其中許多是別名。以下列出我們計畫移除的範例,使用者必須調整才能與 NumPy 2.0 相容
PyArray_Mean
和PyArray_Std
是未經測試的實作,類似於arr.mean()
和arr.std()
。我們計畫移除這些,因為它們可以相對容易地用方法呼叫取代。MapIter
API 函式 (和結構) 允許下游實作類似進階索引的語意。有一個歷史已知的使用者 (theano),並且用不同的方式實作使用案例會更快更容易。API 很複雜,需要深入 NumPy 才能有用,並且其公開使得實作更加困難。除非找到新的重要使用案例,否則我們建議移除它。
ABI 變更的一個範例是變更 PyArray_Descr
( np.dtype
實例的結構) 的佈局,以允許更大的最大項目大小和新的標誌 (對於未來的自訂使用者 DType 很有用)。對於此特定變更,直接存取結構欄位的使用者必須變更其程式碼。下游搜尋顯示這應該不是很常見,主要影響是
存取
descr->elsize
欄位 (和其他欄位) 必須替換為類似PyDataType_ITEMSIZE(descr)
的巨集 (NumPy 可能在需要時包含版本檢查)。使用者定義 dtype 的實作者將必須變更幾行程式碼,幸運的是,此類使用者定義 dtype 非常少。(詳細資訊是我們將結構重新命名為
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 */
}
如果 NumPy 在次要版本中將其變更為 64,如果程式碼使用 NPY_MAXDIMS=32
編譯,但傳入 40 維陣列,則會導致未定義的行為。但是,較大的值也是先前 NumPy 版本上的正確最大值,使其通常對於 NumPy 2.0 變更而言是安全的。(可以想像程式碼想要知道實際執行時間值。我們在實務中尚未看到此類程式碼,但它需要調整。)
對 Cython 使用者的影響#
Cython 使用者可能會透過 cimport numpy as cnp
使用 NumPy C-API。由於 Cython 開發的不確定性,對 Cython 使用者有兩種影響情境。
如果可以依賴 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 函式可以使用舊函式編寫或直接包含在內。
注意
可能可以透過相容的 numpy2_compat
套件,公開在 NumPy 1.x 中存在但為私有的函式。
這表示在某些情況下,新的 API 新增可以更快地提供給下游使用者。它們將需要新的 NumPy 版本進行編譯,但它們的 wheel 可以與早期版本向後相容。
實作#
實作的第一部分 (允許針對早期 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()
呼叫的一部分插入。
替代方案#
始終有可能決定不進行某些變更 (例如,由於下游使用者指出他們仍然需要它)。例如,如果需要,可以使用一個函式呼叫 array.mean()
來取代函式 PyArray_Mean
。
NEP 建議透過引入相容性套件 numpy2_compat
來允許對我們的 API 表格進行更大的變更。我們可以進行許多變更,而無需引入此類套件。
預設 API 版本可以選擇為較舊的版本或目前的版本。較舊的版本旨在為想要比 NEP 29 建議的相容性更大的程式庫提供支援。選擇目前的版本將預設為移除不必要的相容性 shim,以供不發佈 wheel 的使用者使用。建議的預設選擇傾向於發佈 wheel 並希望相容性範圍與 NEP 29 相似的程式庫。這是因為相容性 shim 應該是輕量級的,並且我們預期很少有程式庫需要更長的相容性。
向後相容性#
如上所述,向後相容性是透過以下方式實現的
強制下游使用 NumPy 2.0 重新編譯
提供
numpy2_compat
程式庫。
但依賴使用者適應用法與影響章節中描述的已變更 C-API。
討論#
numpy/numpy#5888 先前提出,允許在我們的標頭中匯出較舊的 API 版本會很有幫助。這從未實作,相反地,我們依賴 oldest-support-numpy。
此提案的初稿已在 2023-04-03 的 NumPy 2.0 規劃會議上提出。
參考文獻與註腳#
版權#
本文檔已放置在公共領域。[1]