NEP 54 — SIMD 基礎架構演進:在轉向 C++ 時採用 Google Highway?#
- 作者:
Sayed Adel, Jan Wassenberg, Matti Picus, Ralf Gommers, Chris Sidebottom
- 狀態:
草稿
- 類型:
標準追蹤
- 建立日期:
2023-07-06
- 決議:
待辦事項
摘要#
我們正在將 SIMD 內部函數架構 Universal Intrinsics 從 C 移至 C++。我們也已轉向 Meson 作為建置系統。Google Highway 內部函數專案建議我們使用 Highway 取代我們的 Universal Intrinsics,如 NEP 38 中所述。這是一個複雜且多面向的決策 — 此 NEP 旨在描述所涉及的權衡以及需要完成的工作。
動機與範疇#
我們希望將基於 C 的 Universal Intrinsics(請參閱 NEP 38)重構為 C++。這項工作已進行一段時間,而 Google 的 Highway 被建議作為替代方案,它已經以 C++ 編寫,並且支援可擴展的 SVE 和其他可重複使用的元件(例如 VQSort)。
從 C 移至 C++ 的動機是 (a) 程式碼可讀性和易於開發,(b) 需要新增對無大小 SIMD 指令(例如,ARM 的 SVE、RISC-V 的 RVV)的支援。
以下是我們目前 C 語言通用內部函數架構中典型的 C 程式碼範例行
// The @name@ is the numpy-specific templating in .c.src files
npyv_@sfx@ a5 = npyv_load_@sfx@(src1 + npyv_nlanes_@sfx@ * 4);
這將變更為(如 PR gh-21057 中實作的那樣)
auto a5 = Load(src1 + nlanes * 4);
如果上述 C++ 程式碼要在底層使用 Highway,它看起來會非常相似,它使用與個別可攜式內部函數 Load
類似的可理解名稱。
上述 C 版本中的 @sfx
是類型識別碼的範本變數,例如:#sfx = u8, s8, u16, s16, u32, s32, u64, s64, f32, f64#
。像這樣明確使用位元大小編碼類型不適用於無大小 SIMD 指令集。使用 C++ 可以更輕鬆地處理此問題;PR gh-21057 顯示了如何操作,並包含更完整的 C++ 程式碼外觀範例。
此 NEP 的範疇包括討論採用 Google Highway 以取代我們目前 Universal Intrinsics 架構的最相關方面,包括但不限於
可維護性、領域專業知識可用性、新貢獻者易於上手以及其他社會方面,
可能影響 NumPy 內部設計或效能的關鍵技術差異和限制,
與建置系統相關的方面,
與發佈時程相關的方面。
不在範疇內(至少目前如此)是重新審視我們目前 SIMD 支援策略的其他方面
在為函數新增 SIMD 支援時,準確性與效能之間的權衡
SVML 和 x86-simd-sort 的使用(以及可能適用於 aarch64 的對等項目)
提取 Highway 的個別位元或演算法(如 gh-24018 中所示)或 SLEEF(在同一個 PR 中討論)
用法與影響#
不適用 - 不會對使用者可見的變更造成重大影響。
向後相容性#
使用者面向的 Python 或 C API 不會發生變更:所有控制編譯和執行階段 CPU 功能選擇的方法都應保留,儘管由於轉向 C++,可能會有一些變更,而與 Highway/Universal Intrinsics 選擇無關。
Highway 中 CPU 功能的命名與 Universal Intrinsics 的命名不同(請參閱下方的「支援的功能/目標」)
在 Windows 上,可能必須避免使用 MSVC,因為 Highway 使用 MSVC 支援較差的 pragma。這表示我們可能必須使用 clang-cl 或 Mingw-w64 建置我們的 wheels。這兩者都應可運作 — 我們稍早合併了 clang-cl 支援(請參閱 gh-20866),而 SciPy 是使用 Mingw-w64 建置的。然而,這可能會影響從 Windows 來源建置的其他重新發佈者或終端使用者。
為了回應先前圍繞此 NEP 的討論,Highway 現在已獲得雙重授權,即 Apache 2 / BSD-3。
高階考量#
注意
目前本節嘗試分別涵蓋每個主題,並比較未來使用 NumPy 特定 C++ 實作與使用 Google Highway 以及我們自己的數值常式。它(尚未)假設已做出決策或擬議的決策。因此,此 NEP 不是「這是擬議的」,在「替代方案」章節中有另一個選項,而是並排比較。
開發工作與長期可維護性#
轉向 Highway 可能是一項重大的開發工作。從長遠來看,希望 Highway 本身能有更多的維護人員頻寬來處理編譯器支援中的持續問題並新增新平台,這將有所抵消。
Highway 被其他專案使用,例如 Chromium 和 JPEG XL(請參閱 Highway 文件中的 這個更完整的清單),這確實暗示了更廣泛的測試和錯誤報告/修正的好處。
一個疑慮是可能必須新增新的指令,而這通常最好在開發需要該指令的數值核心時完成。如果指令位於 NumPy 儲存庫內的 git 子模組 Highway 中,這會有點笨拙 — 需要先實作臨時/通用版本,然後在上傳新的內部函數後更新子模組。
在文件方面,Highway 將明顯勝出。與 Highway 文件 相比,NumPy 的 CPU/SIMD 最佳化 文件相當稀疏。
移轉策略 — 可以逐步進行嗎?#
這是關於兩個半部分的故事。轉向 Highway 的靜態調度內部函數可以逐步完成,如 PR gh-24018 中已見。然而,採用 Highway 執行階段調度的方式必須一次完成 — 我們不能(或不應該)有兩種執行方式。
Highway 編譯器和平台支援政策#
在新增新指令時,Highway 的政策是它們的實作方式必須在 CPU 架構之間相當平衡。
關於支援狀態以及所有目前支援的架構是否將保持支援,Jan 表示 Highway 可以承諾以下事項
如果它可以透過 Clang 交叉編譯並透過標準 QEMU 進行測試,則可以進入 Highway 的 CI。
如果它可以透過 clang/gcc 交叉編譯並可以使用新的 QEMU(可能帶有額外標幟)進行測試,那麼它可以透過在每次 Highway 發佈之前進行手動測試來支援。
只要現有目標在 QEMU 中編譯/執行,它們將保持支援。
Highway 不受 Google 的「不再支援」策略約束(或如其 README 中所寫,這不是 Google 官方支援的產品)。這並不是一件壞事;這表示由於 Google 關於專案的業務決策而使其不再受支援的可能性較低。GitHub org google
下的許多知名開放原始碼專案都聲明了這一點,例如 JAX 和 tcmalloc。
支援的功能/目標#
這兩個架構都支援大量的平台和 SIMD 指令集,以及通用純量/後備版本。目前的主要差異在於
NumPy 支援 IBM Z 系統 (s390x, VX/VXE/VXE2),而 Highway 支援 Z14、Z15。
Highway 支援 ARM SVE/SVE2 和 RISC-V RVV(無大小指令),而 NumPy 不支援。
NumPy 中對無大小 SIMD 支援的基礎工作已在 gh-21057 中完成,但是 SVE/SVE2 和 RISC-V 尚未在那裡實作。
指令集群組的粒度也存在差異:NumPy 支援比 Highway 更精細的架構集。請參閱 Highway 的目標清單 此處(大致按 CPU 系列劃分)和 NumPy 的目標清單 此處(大致按 SIMD 指令集劃分)。因此,使用 Highway 我們會失去一些粒度 — 但這可能沒問題,我們真的不需要這種程度的粒度,也沒有太多證據表明使用者會明確地使用它來為自己的 CPU 擠出最後一點效能。
多個目標和執行階段調度的編譯策略#
Highway 編譯一次,同時使用預處理技巧在同一個編譯單元內為每個 CPU 功能產生多個節,(請參閱 foreach_target.h
用法和動態調度文件,了解這是如何完成的)。Universal Intrinsics 產生多個編譯單元,每個 CPU 功能群組一個,並編譯多次,將它們全部(以不同的名稱)連結在一起以進行執行階段調度。Highway 技術可能無法在 MSVC 上可靠地運作,Universal Intrinsic 技術可以在 MSVC 上運作。
哪一個更穩健?專家意見分歧。Jan 認為 Highway 方法更穩健,尤其可以避免連結器將具有太新指令的函數拉入最終二進位檔案中。Sayed 認為目前的 NumPy 方法(OpenCV 也使用)更穩健,尤其是不太可能遇到編譯器特定的錯誤或更早地捕獲它們。兩者都同意 meson 建置系統允許指定物件連結順序,這會產生更一致的建置。然而,這確實將 NumPy 與 meson 綁定。
Matti 和 Ralf 認為目前的建置策略對於 NumPy 運作良好,並且變更建置和執行階段調度的優點,以及可能未知的穩定性,超過了採用 Highway 動態調度可能帶來的好處。
我們過去四年的經驗表明,「無效指令」類型崩潰的錯誤總是歸因於功能偵測問題 — 最常見的原因是用戶在模擬下執行,有時是因為我們的 CPU 功能偵測程式碼存在實際問題。我們幾乎沒有發現連結器拉入為不同架構編譯多次的函數並選擇具有不受支援指令的函數的證據。為了確保避免此問題,建議將數值核心保留在原始碼內,並避免在可快取的物件內定義非內聯函數。
C++ 重構考量#
我們希望從 C 移至 C++,這自然會涉及大量的重構,主要有兩個原因
擺脫 NumPy 特定的範本語言,改用更具表現力的 C++
這將使使用無大小內部函數(例如 SVE 的內部函數)更容易。
此外,我們看到以下考量
如果我們使用 Highway,我們需要將 C++ 包裝函式從通用內部函數切換到 Highway。另一方面,轉向 C++ 的工作尚未完成。
如果我們使用 Highway,我們需要使用 Highway 內部函數重寫現有的核心。但是,同樣地,轉向 C++ 仍然需要接觸所有這些核心。
關於 Highway 的一個疑慮是,是否可以取得架構特定函數的函數指標,而不是直接呼叫該函數。這樣我們就可以確定,對於單個 Python API 調用多次呼叫一維內部迴圈不會多次產生調度開銷。這已進行調查:這也可以使用 Highway 完成。
第二個疑慮是,Highway 是否有可能允許使用者在執行階段選擇或停用調度到某些指令集。這是可能的。
在 Highway 的 C++ 實作中使用標籤減少了程式碼重複,但新增的範本使得 C 級測試和追蹤更加複雜。
_simd
單元測試模組#
將 _simd testing
模組重寫為使用 C++ 最近在 PR gh-24069 中完成。它取決於轉向 C++ 的主要 PR gh-21057。它允許人們幾乎使用相同的簽章從 Python 存取 C++ 內部函數。這不僅是測試的好方法,也是設計新的 SIMD 核心的好方法。
可以將類似的測試和原型設計功能新增至 Highway(它使用普通的 googletest
),但是目前 NumPy 的方式更好一些。
數學常式#
數學或數值常式是以比通用內部函數更高的抽象層次編寫的,而通用內部函數是此 NEP 的主要重點。Highway 只有有限數量的數學常式,並且它們的精度不足以滿足 NumPy 的需求。因此,無論如何,NumPy 現有的常式(使用通用內部函數)都將保留,如果我們採用 Highway 路線,它們只需在內部使用 Highway 原始碼即可。我們仍然可以使用 Highway 排序常式。如果我們接受較低精度的常式(透過使用者提供的選項,即擴展 errstate
以允許精度選項),我們可以改用 Highway 原生常式。
可能有其他程式庫具有可以在 NumPy 中重複使用的數值常式(例如,來自 SLEEF,或可能來自 JPEG XL 或其他使用 Highway 的程式庫)。這裡可能有一些小的好處,但可能不太重要。
支援和遺失的內部函數#
NumPy 需要的一些特定內部函數可能在 Highway 中遺失。同樣地,NumPy 需要實作常式的一些內部函數已在 Highway 中實作,但在 NumPy 中遺失。
Highway 具有比 NumPy 通用內部函數更多的指令,因此 NumPy 核心的一些未來需求可能已經在那裡得到滿足。
無論如何,我們始終必須在任一解決方案中實作內部函數。
實作#
待辦事項
替代方案#
使用 Google Highway 進行動態調度。其他替代方案包括:不執行任何操作並保留 C 語言通用內部函數,使用 Xsimd 作為 SIMD 架構(不如 Highway 全面 — 例如,不支援 SVE 或 PowerPC),或使用/供應 SLEEF(一個良好的程式庫,但維護不一致)。這些替代方案似乎都不具吸引力。
討論#
參考資料與註腳#
著作權#
本文檔已置於公有領域。[1]