在 ONNX 中新增運算子或函式

或將現有運算子更新至新的 Opset 版本。

目錄

在 ONNX 中提議及提交新的運算子或函式

運算子是用來定義 ONNX 模型的基本建構區塊。透過豐富的運算子集,ONNX 可以描述來自各種框架的大部分 DNN 和 ML 模型。函式可以利用更基本運算子表達複雜的運算子。ONNX 規格包含一組可啟用許多模型的核心運算子。新增所有可能的運算子並非目標,但是會視需要新增更多運算子,以涵蓋不斷發展的需求。

在這份文件中,我們說明接受新提議運算子的流程,以及如何正確提交新的運算子做為 ONNX 標準的一部分。目標是根據我們從社群收集的經驗、學習和意見反應來改進我們目前所擁有的。

新增運算子的 4 個步驟

  1. 決定要提議的項目

  2. 為新的運算子/函式提交 PR

  3. 由運算子 SIG 審查 PR

  4. 合併 PR 並納入下一個 ONNX 版本

步驟 1:提議新的運算子/函式

為了提議新的運算子/函式,需要以下項目

  1. 如果運算子可以用其他 ONNX 運算子表示,則應為函式而非運算子 (我們在 ONNX 中有一個函式:MeanVarianceNormalization)。

  2. 如果運算子可以拆分為新的基本運算,則應提議這些基本運算,並將運算子做為函式。

  3. 根據模型。這會幫助我們了解其用途,以及其是否可以解決實際問題。如果模型是私有或 IP 且無法共用,則運算子不屬於標準,應實作為自訂 OP。

  4. 運算子必須至少由一個 (知名的) 框架實作。這有助於我們了解運算子的實際行為及其用法。

  5. 運算子簽章和行為

    1. 如果運算子在 numpy 中可用,請優先使用 numpy 語意。

    2. 如果運算子在多個框架中可用,請確保您的設計是通用的,並且涵蓋這些框架。

  6. 優先使用屬性而非輸入。

  7. 運算子不應比使用案例所需更複雜。然而,運算子應盡可能通用,只要不會使實作更複雜。這需要仔細平衡通用性和複雜性。例如,對於某些運算子來說,從 3 維張量泛化到 N 維張量是簡單的 (就實作而言),但對於其他運算子來說則很複雜。在這種情況下,將根據這種泛化的複雜性來做出選擇。

步驟 2:提交 PR

一旦滿足提議新的運算子/函式的條件,您就需要為新的運算子/函式提交 PR。以下是 PR 應包含的內容的預期。審查者應在簽核前驗證 PR 的完整性。

  1. 描述

    1. 撰寫關於運算子及其預期行為的詳細描述。幾乎描述應清楚到足以避免實作者之間的混淆。

    2. 在描述中新增範例,以說明用法。

    3. 在描述中新增對應框架中運算子來源的參考 (如果可能的話)。

    4. 在描述中撰寫數學公式或虛擬碼。核心演算法需要非常清楚。

  2. 以 Python 撰寫參考實作,此參考實作應涵蓋運算子的所有預期行為。只有在極其罕見的情況下,我們才會免除此要求。

  3. 運算子版本:請參閱我們的 版本控制文件

  4. 撰寫涵蓋主要用法和邊緣案例的單元測試。

    1. 測試範例將會擷取至文件中。

    2. 我們也會為其產生二進位資料。

    3. 範例:onnx/backend/test/case/node/abs.py

  5. 撰寫升級和降級測試

    1. 請在 onnx/test/automatic_upgrade_test.py 中使用 _test_op_upgrade 為您的運算子新增至少一個自動升級測試。這些測試會在指定的 opset 版本 (通常是運算子引入的版本) 建立給定的運算子,並測試版本轉換器是否能夠將其轉換為最高可用版本。因此,對於新的運算子 _test_op_upgrade 不會測試任何內容,但是一旦運算子在未來的 opset 中更新,測試將會自動變得不簡單。

    2. 同樣地,請在 onnx/test/automatic_downgrade_test.py 中使用 _test_op_downgrade 為您的運算子新增至少一個自動降級測試。指定目前的版本,以便在運算子於較高的 opset 版本更新後,測試會確保向下轉換已驗證。

  6. 更新文件並產生測試資料。

    1. 執行 指令碼。如果您的檔案位於 onnx/backend/test/data/node 下,且無法由 onnx/backend/test/case/node 中的指令碼產生,請進一步使用 python onnx/backend/test/cmd_tools.py generate-data --clean 來清除目錄並僅保留所需的測試資料。以更新文件並產生測試資料。

  7. 形狀推斷函式

    1. 如果形狀推斷有意義且適用,請提供形狀推斷函式。

    2. 如果無法進行形狀推斷,則至少必須具有執行等級推斷的邏輯 (在輸出形狀中新增正確的維度數量)

    3. 形狀推斷函式必須隨附單元測試 (onnx/test/shape_inference_test.py)。

    4. 在實作您自己的函式時,您可以參考 TopK 運算子的形狀推斷函式(onnx/defs/math/defs.cc)。

範例參考

PR 1959 是一個很好的參考範例。

步驟 3:由運算子 SIG 進行 PR 審查

運算子 SIG 負責 ONNX 規格中的運算子/函式。SIG 會定期舉行會議並審查 PR。

簽核

至少需要兩位運算子 SIG 貢獻者的簽核。

步驟 4:ONNX 發佈

一旦 PR 通過審查並獲得運算子 SIG 的簽核,它將會被合併。您新的運算子/函式將會成為主分支的一部分,並可供任何從原始碼建置的人使用。這些並非正式發佈版本。ONNX 會定期發佈正式的新版本,這些版本是主分支的快照。您新的運算子/函式將會包含在該發佈版本中。

更新現有運算子

當需要支援新的情境或輸入類型時,可能需要更新現有運算子的定義。此過程與建立新運算子的過程大致相似。

檢查清單

更新現有運算子時,請使用此檢查清單:https://github.com/onnx/onnx/wiki/Checklist-for-updating-an-existing-operator

移除運算子或函式

移除現有 ONNX 運算子或函式的原因有很多,例如被不同的運算子取代,或者可以由一組其他運算子分解。本文檔描述從標準中移除現有 ONNX 運算子的標準。

移除運算子

ONNX 中的任何運算子都是因為模型和/或框架需要而添加的。為了棄用這樣的運算子,我們需要執行以下操作。

  • 除非有替代方案,否則運算子不能被棄用。

    • 替代方案可以是取代舊運算子的更通用的運算子。

    • 或者是一組原始運算子,它們共同可以實現已棄用運算子(函式)的相同功能和行為。

  • 如果已棄用的運算子可以由現有運算子分解,則必須將其轉換為函式。

  • 如果替代方案尚未在 ONNX 標準中,則先添加替代運算子或一組運算子。

  • 新增一個版本轉換器,將運算子轉換為版本轉換器的替代方案。範例:onnx/version_converter/adapters/upsample_9_10.h

  • 棄用的運算子不需要寬限期。

移除函式

根據定義,函式是由 ONNX 原始運算組成;但是,函式可能會被支援 ONNX 的框架或執行時加速。因此,不建議移除函式,除非添加另一個取代其功能的單一函式。

記錄移除運算子或函式

為了確保每個人都了解棄用,需要執行以下操作

  • 從 ONNX 中移除的任何運算子或函式都需要在發佈說明中提及。

  • 它們的舊文件需要更新,以顯示新的替代方案以及從舊到新的對應關係。

    • 只需要移除 def.ccold.cc 將保留。

    • old.cc 需要更新為與替代方案的對應關係。

  • 需要更新 ONNX 檢查器,以產生帶有適當訊息的錯誤。

  • 所有移除的運算子都需要附加在 operator.md 檔案的末尾。