OnnxRuntime EP 上下文快取功能設計

目錄

背景

ONNX 執行時**執行提供程式 (EPs)** 使使用者能夠**在由後端 SDK(例如 QNN、OpenVINO、Vitis AI 等)提供支援的各種硬體加速器上執行 ONNX 模型**。
執行提供程式**將 ONNX 模型轉換為後端 SDK 所需的圖格式**,並**將其編譯為硬體所需的格式**。
在**NPU 領域**,這種**轉換和編譯過程**可能非常耗時,特別是對於**LLM 模型**,有時需要**數十分鐘**才能完成。這顯著影響了會話建立期間的**使用者體驗**。
為了**消除**模型轉換和編譯的重複開銷,大多數後端 SDK 都提供了一項功能,可以將**預編譯的模型轉儲到二進位制檔案**中。

  • **預編譯的模型**可以直接由後端 SDK 載入並在目標裝置上執行。
  • 這極大地**縮短了會話建立時間**並提高了整體效率。為了支援此最佳化,ONNX 執行時在**MS 域**中引入了一個**貢獻者運算子**,名為 EPContext

EPContext 操作模式

操作域:com.microsoft
節點輸入 & 輸出:可變

屬性表如下

屬性 資料型別 描述
main_context int64 **1(預設)**:此節點**引用包含與此節點關聯的圖的 EP 上下文內容**。
**0**:該節點**不引用任何 EP 上下文內容**。相反,它期望從另一個將此欄位設定為**1**的節點中檢索圖。
某些 EP 支援**包含多個圖的單個上下文**。
main_context = 1EPContext 節點指的是**主上下文**。
此上下文包含多個圖,這些圖可以被其他 main_context = 0 的節點**引用**。
ep_cache_context string 如果 embed_mode = 1,則為**EP 上下文的有效載荷**;如果 embed_mode = 0,則為**上下文檔案的路徑**。
該路徑**相對於 ONNX 模型檔案**,可以是**檔名**或**子資料夾/檔名**。
embed_mode int64 **1(預設)**:ep_cache_context 包含**上下文內容的有效載荷**。
**0**:ep_cache_context 包含**上下文二進位制檔案的檔案路徑**。
ep_sdk_version string 可選。用於**生成此節點**的 **SDK 版本**。
onnx_model_filename string 可選。原始 Onnx 模型檔名。
hardware_architecture string 可選。硬體架構。
partition_name string 可選。OnnxRuntime 分割槽圖名稱。
source string 可選。用於**生成此節點**的**源識別符號**。
這應該是一個**由 EP 定義的唯一鍵**,允許 ONNX 執行時支援使用不同 EP 執行的**多個 EPContext 節點**。
例如
**QNN EP** 只接受 source = QNNQnnExecutionProvider 的節點。
**OpenVINO EP** 只接受 source = OpenVINOExecutionProvider 的節點。
notes string 可選。特定 EP 所需的附加資訊。
max_size int64 可選。上下文中的**最大大小**,其用法**取決於 EP**。
預設為**0**。

EP Context node example

會話選項 描述
ep.context_enable **僅用於 EP 上下文模型生成**。
**1**:啟用 ONNX 執行時**轉儲上下文快取模型**。
**0(預設)**:**停用**上下文模型轉儲。
ep.context_file_path 指定**轉儲模型**的**檔案路徑**。
**預設:** 對於**上下文模型生成**,為 original_file_name_ctx.onnx
對於**模型推理**
如果使用者從**記憶體緩衝區**載入模型,並且**EP 上下文二進位制檔案**位於 ONNX 模型之外,則必須設定此選項。
ONNX 執行時 EP 使用此路徑來**確定資料夾位置**,並將其與 ep_cache_context(指向**上下文二進位制路徑**)結合起來,構建上下文二進位制檔案的**絕對路徑**。
ep.context_embed_mode **僅用於上下文模型生成**。
**1**:將**EP 上下文內容直接轉儲到 ONNX 模型中**,儲存在 ep_cache_context 節點屬性內。
**0(預設)**:將**EP 上下文內容轉儲到單獨的檔案中**,並將**檔名**儲存在 ONNX 模型中。
**檔案路徑**在 ep_cache_context 節點屬性中進行跟蹤。
ep.context_node_name_prefix **僅用於上下文模型生成**。
指定 EPContext 節點名稱的**字首**(也用作 partition_name 屬性和內部圖名稱)。
當多個 EPContext 節點合併到**單個模型**中時,確保**節點之間的唯一性**,防止命名衝突。
EP 還可以將此字首應用於轉換後的 EP 上下文二進位制檔案內的**ep_graph 名稱**。
session.model_external_initializers_file_folder_path 這並非**EPContext**設計所特有。通常,對於包含外部資料的模型,當從**記憶體緩衝區**載入模型時,會話會丟失模型的名稱和路徑,導致無法定位外部資料檔案。使用此配置可指定外部資料檔案的**資料夾路徑**。
所有外部資料檔案應放置在**同一資料夾**中。
ep.context_model_external_initializers_file_name **僅用於上下文模型生成**。
此配置在某些節點被分割槽到**CPU EP**且這些節點具有**外部初始化器**時使用。生成**EP 上下文模型**時,新模型**不應依賴於源 ONNX 模型使用的舊外部資料檔案**。
在**轉儲 EP 上下文模型**並帶有外部初始化器檔案時使用此設定。
如果指定,所有初始化器都將放置在**外部資料檔案**中。
否則,所有初始化器都將嵌入到**生成的 ONNX 檔案**中。
預設情況下,此選項**未設定**,這意味著所有初始化器都將包含在 ONNX 檔案中。

EP 上下文快取模型生成工作流

用於生成 EP 上下文快取模型的 EP 介面 GetEpContextNodes()

直接在執行提供程式 (EP) 程式碼中生成**分割槽圖**具有挑戰性,因為 EP 缺乏對整個分割槽圖的完整檢視。為解決此問題,ONNX Runtime 引入了一個新的**執行提供程式介面**:GetEpContextNodes()

virtual const InlinedVector<const Node*> GetEpContextNodes() const {
  return InlinedVector<const Node*>();
}
  • 此 API 返回一個指向 EPContext 節點的**指標陣列**。
  • 如果執行提供程式需要**生成上下文快取模型**,則應實現此介面。否則,可以不實現。
  • **EP 有責任**建立 EPContext 節點及其依賴項(例如,如果 embed_mode = 0,則為上下文二進位制檔案)。
  • **ONNX 執行時 GraphPartitioner** 使用此介面檢索 EPContext 節點並生成**分割槽 ONNX 模型**。EP 上下文模型生成程式碼詳情在此

EP 上下文快取模型生成指南

**OnnxRuntime EPs** 應遵循以下準則來建立**EP 上下文快取模型**並維護統一的使用者介面

  • 所有權
    • **執行提供程式 (EP)** 負責**建立 EPContext 節點**及其依賴項。
    • **ONNX 執行時框架**負責使用 EP 提供的 EPContext 節點列表**生成 EP 上下文 ONNX 模型**。
  • 生命週期
    • EPContext 節點的生命週期至少在 EP 呼叫編譯時開始,並在 EP 銷燬時結束。
  • ep.context_enable
    • 如果 ep.context_enable = 1,ONNX 執行時會建立 EP 上下文快取模型。
    • 否則,如果 ep.context_enable = 0(預設),ONNX 執行時遵循標準工作流,不生成快取模型。
  • ep.context_file_path
    • 如果未提供 ep.context_file_path,ONNX 執行時將透過將原始輸入模型檔名中的 .onnx 替換為 _ctx.onnx 來生成輸出模型檔名。
    • 如果指定了 ep.context_file_path,ONNX 執行時將使用提供的檔案路徑。當 ep.context_embed_mode = 0 時,EP 也應使用此路徑來確定轉儲編譯後的 EP 上下文二進位制檔案的資料夾位置。
    • **注意:** 當從**記憶體緩衝區**載入模型時,需要 ep.context_file_path,因為在此場景中 ONNX 執行時無法檢索原始模型檔案路徑。
  • ep.context_embed_mode
    • 1:將 EP 上下文內容直接嵌入到 ONNX 模型中。
    • 0(預設):將 EP 上下文內容轉儲到**單獨的檔案**中(EP 上下文二進位制檔案)。
      • 即使存在多個分割槽子圖,也應該只有一個 EP 上下文二進位制檔案。如果 EP 短期內無法實現此目標,請在 EP 網頁上註明。在這種情況下,使用者將需要透過遍歷所有主要 EPContext 節點(embed_mode=1 的節點)並從**節點屬性** ep_cache_context 中提取檔案路徑來確定生產部署所需的檔案。
      • EP 上下文二進位制檔名應為 [model_name]_[ep].bin
      • EP 將上下文二進位制檔名記錄在**EPContext 節點屬性** ep_cache_context 中。
      • 上下文二進位制檔案必須與轉儲的 ONNX 模型檔案位於**同一目錄**中。
      • EPContext 節點中記錄的檔案路徑是 ONNX 模型檔案的**相對路徑**。
      • **注意:** 允許使用子資料夾。
  • ep.context_node_name_prefix
    • 如果使用者想為 EPContext 節點名稱新增**自定義字首**(也應用於 partition_name 屬性和圖名稱),EP 應在生成 EPContext 節點時提供此功能。
    • 這在將來自不同模型的多個 EPContext 節點合併到**單個模型**中時非常有用,因為存在跨模型**節點名稱或圖名稱衝突**的風險。
    • EP 應支援單個模型中的多個 EP 上下文,使使用者能夠**合併和連線**從不同模型生成的 EPContext 節點。
  • 帶有外部資料的源模型
    當源模型依賴外部資料檔案時,ONNX 使用相對路徑來定位該檔案。因此,外部資料檔案必須與源模型位於同一目錄中。然而,新生成的模型**不應依賴**任何原始原始檔。這種方法是由以下幾個考慮因素驅動的
    • 所有新生成的檔案都應該位於同一目錄中。
    • 無法保證輸出檔案會生成在與原始檔相同的目錄中。
    • EPContext 設計允許模型由多個 EP 分割槽,每個 EP 編譯自己的 EPContext 節點。統一和標準化的流程有助於避免資料重複。
    • 某些 EP 可能需要將權重從源複製到其上下文二進位制檔案中,以滿足特定的資料佈局要求。
    • 對於回退到 ONNX 執行時 CPU EP 的子圖,所有權重資料將預設直接嵌入到新生成的 [model_name]_ctx.onnx 模型中。如果設定了 ep.context_model_external_initializers_file_name,則所有權重資料將改為儲存到指定的外部初始化器檔案中。

使用場景程式碼示例

透過從模型路徑建立會話來生成 EPContext 模型

    Ort::SessionOptions so;

    // Enable EPContext ONNX model dumping
    so.AddConfigEntry(kOrtSessionOptionEpContextEnable, "1");

    // Add the execution provider (using QNN as an example)
    so.AppendExecutionProvider("QNN", provider_options);

    // Create the session to dump the `_ctx.onnx` model
    Ort::Session session1(env, "./model1.onnx", so);

透過從記憶體緩衝區中的模型建立會話來生成 EPContext 模型
與 C API CreateSessionFromArray 類似,以下示例從儲存在記憶體陣列中的模型建立 ONNX 執行時會話,導致會話無法跟蹤模型的名稱和路徑。要生成 EPContext 模型,必須使用 ep.context_file_path 指定檔案路徑。

    // Read model file into buffer array
    std::vector<char> buffer;
    ReadFileToBuffer("./model1.onnx", buffer);

    Ort::SessionOptions so;

    // Enable EPContext ONNX model dumping
    so.AddConfigEntry(kOrtSessionOptionEpContextEnable, "1");

    // Specify the generated EPContext model file path using option ep.context_file_path
    so.AddConfigEntry(kOrtSessionOptionEpContextFilePath, "./model_ctx.onnx");

    // Add the execution provider (using QNN as an example)
    so.AppendExecutionProvider("QNN", provider_options);


    // Create the session to dump the `_ctx.onnx` model
    Ort::Session session1(env, buffer.data(), buffer.size(), so);

透過從記憶體緩衝區中的模型建立會話來生成 EPContext 模型,且模型具有外部權重
從記憶體陣列建立會話,且模型依賴外部資料。會話需要 session.model_external_initializers_file_folder_path 來確定外部資料位置,與之前的示例相同,需要 ep.context_file_path 來設定生成的 EPContext 模型的檔案路徑。

    // Read model file into buffer array
    std::vector<char> buffer;
    ReadFileToBuffer("./model_folder/model1.onnx", buffer);

    Ort::SessionOptions so;

    // Enable EPContext ONNX model dumping
    so.AddConfigEntry(kOrtSessionOptionEpContextEnable, "1");

    // Specify the generated EPContext model file path using option ep.context_file_path
    so.AddConfigEntry(kOrtSessionOptionEpContextFilePath, "./model_folder/model_ctx.onnx");

    // Specify the external data folder path using option session.model_external_initializers_file_folder_path
    so.AddConfigEntry(kOrtSessionOptionsModelExternalInitializersFileFolderPath, "./external_data_folder/");

    // Add the execution provider (using QNN as an example)
    so.AppendExecutionProvider("QNN", provider_options);


    // Create the session to dump the `_ctx.onnx` model
    Ort::Session session1(env, buffer.data(), buffer.size(), so);

注意:如果**CPU EP** 上有**子圖回退**依賴外部資料,生成的 EPContext 模型**不應依賴於基礎模型使用的原始外部資料檔案**。預設情況下,EPContext 模型**將所有外部資料**直接**嵌入**到生成的 ONNX 檔案中。如果需要將權重儲存在外部檔案中,請設定 ep.context_model_external_initializers_file_name。此選項強制所有初始化器儲存到指定的外部檔案中。

EP 上下文快取模型的推理工作流

支援載入帶有 EPContext 節點的 ONNX 執行時 EP 應遵循以下工作流和規則進行模型推理

  • 模型識別
    • EP 應首先確定模型是否包含 EPContext 節點。
      • 如果不存在 EPContext 節點,EP 遵循其正常的推理工作流。
      • 如果模型包含 EPContext 節點
        • EP 應檢查所有 EPContext 節點的 source 節點屬性,以驗證其中是否有任何節點是為當前 EP 準備的(即 source 屬性與 EP 預期的鍵匹配)。
        • EP 應該只分區 source 屬性與 EP 所需鍵匹配的 EPContext 節點。
        • EP 從匹配的 EPContext 節點載入快取的上下文。
  • **處理外部上下文二進位制檔案 (embed_mode = 0)** 當 EPContext 快取模型以 embed_mode = 0 生成時,上下文二進位制檔案作為單獨的檔案與 ONNX 模型一起儲存在同一資料夾中。
    • ONNX 執行時從 EPContext 節點的 ep_cache_context 屬性中檢索上下文二進位制檔案的相對路徑。
    • 對於從檔案路徑載入的模型
      • EP 應確定輸入模型檔案的資料夾路徑,並將其與相對路徑結合起來,構建到上下文二進位制檔案的完整路徑。
    • 對於從記憶體緩衝區載入的模型
      • 由於 EP 無法推匯出模型的資料夾路徑,使用者必須指定會話選項 ep.context_file_path
      • EP 使用 ep.context_file_path 來確定資料夾路徑,並將其與相對路徑結合起來,構建到上下文二進位制檔案的完整路徑。
  • 支援多個主 EPContext 節點 (main_context = 1)
    • EP 應支援多個主 EPContext 節點,沒有任何限制。
    • EP 必須能夠載入 EPContext 節點的 ep_cache_context 屬性中指定的所有 EP 上下文二進位制緩衝區/檔案,對其進行反序列化,管理 ep_graphs,並選擇合適的進行執行。
  • EP 上下文二進位制載入期間的錯誤處理

    EP 或其後端 SDK 應該能夠檢測常見的故障場景(包括但不限於以下情況)。在這種情況下,EP 應返回狀態碼為 INVALID_GRAPH 的狀態

    • 檢測驅動版本與 EP 上下文二進位制檔案所需版本之間的不匹配;如果不相容則返回錯誤。
    • 檢測執行時 SDK 版本與生成 EP 上下文二進位制檔案所用版本之間的不匹配;如果不相容則返回錯誤。
    • 如果載入 EP 上下文二進位制檔案因任何原因失敗,則返回錯誤。

EP Context nodes with different EPs

使用場景程式碼示例

從預編譯的 EPContext 模型建立推理會話
從模型檔案路徑建立會話。如果存在外部 EP 上下文二進位制檔案,會話可以從模型檔案路徑中找出二進位制檔案路徑。

    Ort::SessionOptions so;

    // Add EP, take QNN for example
    so.AppendExecutionProvider("QNN", provider_options);

    // Create sessions to load from the _ctx.onnx model
    Ort::Session session1(env, "model1_ctx.onnx", so);

    session1.run(...);

從記憶體緩衝區中的預編譯 EPContext 模型建立推理會話
從模型的記憶體緩衝區建立會話會導致會話丟失模型名稱和路徑的跟蹤。為解決此問題,必須設定:ep.context_file_path

  • 會話使用此路徑來識別資料夾位置。
  • 利用 EPContext 節點中的 EP 上下文二進位制檔名,會話構建到最終 EP 上下文二進位制檔案的完整路徑。
      // Read model file into buffer array
      std::vector<char> buffer;
      ReadFileToBuffer("./model_folder/model_ctx.onnx", buffer);
    
      Ort::SessionOptions so;
    
      // Specify the EPContext model file path using option ep.context_file_path
      so.AddConfigEntry(kOrtSessionOptionEpContextFilePath, "./model_path/model_ctx.onnx");
    
      // Add EP, take QNN for example
      so.AppendExecutionProvider("QNN", provider_options);
    
      // Create sessions to load from the buffer
      Ort::Session session1(env, buffer.data(), buffer.size(), so);
    
      session1.run(...);
    

帶有權重共享的 EPContext

Onnx 域中的權重共享

在 ONNX 中,權重共享指的是多個帶有外部權重的 ONNX 模型指向同一個外部權重檔案。這些模型使用相同的張量名稱,允許它們引用相同的張量資料。

Weight sharing across Onnx models

帶有 EPContext 的 EP 域中的權重共享

EP 權重共享透過預生成的 EP 上下文二進位制/blob 啟用。為此,使用者必須**離線生成上下文二進位制檔案**(提前)。

  • 某些 EP 需要特定的平臺,例如**Linux x86_64** 和/或**Windows x86_64**。詳細資訊請參閱特定 EP 頁面。
  • EP 上下文二進位制檔案包含**多個共享相同張量的圖**。

Weight sharing in EP context binary

EP 或後端 SDK 應該能夠如上所述轉換和編譯圖。

  • EP 或 SDK 應該從先前編譯的圖生成的現有 EP 上下文中識別相同的權重。
  • 當新圖編譯到 EP 上下文時,如果它們被識別為相同,它們應該重用現有權重。例如,在 [model_name]_[ep].bin 中,來自 ep_graph1tensor1_1 和來自 ep_graph2tensor2_1 是相同的,並且都指向相同的資料偏移 tensor_data1

帶有權重共享的 EPContext 模型生成工作流

Weight sharing workflow

每個 ONNX 執行時會話都與一個 ONNX 模型關聯。共享權重的模型被分組為模型組,而具有共同屬性的 ONNX 執行時會話被組織成會話組。ONNX 執行時引入了兩個會話選項:ep.share_ep_contextsep.stop_share_ep_contexts 來促進會話分組。

  • 會話組中的所有 ONNX 執行時會話都應啟用 ep.share_ep_contexts
  • 最終的 ONNX 執行時會話使用 ep.stop_share_ep_contexts 來指示它是組中的最後一個會話。注意:單個 ONNX 模型可能包含多個 EPContext 節點,具體取決於圖分割槽結果。但是,為簡單起見,此處每個模型只顯示一個 EPcontext 節點。

帶有權重共享的 EPContext 模型生成實現指南

  • 共享工作區建立
    第一個會話建立一個共享工作區(例如 EP Singleton),以與其他會話共享資源。
  • EP 上下文二進位制檔案命名
    EP 上下文二進位制檔名由第一個會話確定並存儲在共享工作區(例如 EP Singleton)中,供所有會話組使用。
    EP 上下文二進位制檔名應為 [model1_name]_[ep].bin
  • 圖編譯
    會話組中的所有會話將其圖編譯到共享資源中。
  • EPContext 模型生成
    會話組中的每個會話都建立一個 EPContext ONNX 模型。EP 生成一個引用 EP 上下文二進位制檔名的 EPContext 節點。然後 ONNX 執行時框架轉儲 EPContext ONNX 模型。
  • 最終 EP 上下文二進位制檔案生成
    會話組中最後一個會話(啟用了 ep.stop_share_ep_contexts 的會話)使用儲存在共享工作區中的名稱生成最終 EP 上下文二進位制檔案。
  • 共享工作區清理
    最後一個會話清除共享工作區。空的共享工作區表示下一個要執行的會話是第一個會話。
  • 生成的檔案數量
    對於 N 個共享權重的源模型,總共應生成 N+1 個檔案。
    生成的檔案是 model1_ctx.onnx, ..., modeln_ctx.onnx, [model1_name]_[ep].bin

使用者程式碼示例

    Ort::SessionOptions so;

    // Enable EPContext ONNX model dumping
    so.AddConfigEntry(kOrtSessionOptionEpContextEnable, "1");

    // Enable EP context sharing across sessions
    so.AddConfigEntry(kOrtSessionOptionShareEpContexts, "1");

    // Add the execution provider (using QNN as an example)
    so.AppendExecutionProvider("QNN", provider_options);

    // Create the first session to dump the model1_ctx.onnx file
    Ort::Session session1(env, "model1.onnx", so);

    // Mark the last session by enabling ep.stop_share_ep_contexts
    so.AddConfigEntry(kOrtSessionOptionStopShareEpContexts, "1");

    // Create the last session to dump the model2_ctx.onnx file and generate the [model1_name]_[ep].bin
    Ort::Session session2(env, "model2.onnx", so);

帶有權重共享的 EPContext 模型生成通用工具

OnnxRuntime 提供了 ep_weight_sharing_ctx_gen 工具來自動化權重共享工作流。此工具處理整個過程。此工具專為**權重共享**場景設計,簡化了 EPContext 模型生成過程。命令列示例

./ep_weight_sharing_ctx_gen -e qnn -i "soc_model|60 htp_graph_finalization_optimization_mode|3" ./model1.onnx,./model2.onnx

它建立兩個 Onnx 模型 (model1_ctx.onnx, model2_ctx.onnx) 和一個 QNN 上下文二進位制檔案 ([model1_name]_[ep].bin)。

從帶有權重共享的 EPContext 模型進行推理會話

要使用已轉儲的、啟用權重共享的 EPContext 模型,ONNX 執行時推理會話必須啟用**資源共享**。這透過設定會話選項來實現

    ep.share_ep_contexts = 1

從帶有權重共享的 EPContext 模型進行推理的實現指南

  • 建立第一個 OnnxRuntime 推理會話
    • 設定會話選項:ep.share_ep_contexts=1
    • 載入 model1_ctx.onnx 模型。
    • 共享工作區最初為空。
    • EP 載入 [model1_name]_[ep].bin 並反序列化二進位制檔案以檢索所有圖(例如 ep_graph1ep_graph2)。
    • model1_ctx.onnx 中的 EPContext 節點指定使用 ep_graph1
    • 會話使用 ep_graph1 進行推理。
    • 剩餘的圖 (ep_graph2) 被放置到共享工作區中,供未來的會話使用。
  • 建立第二個 ONNX 執行時推理會話
    • 設定會話選項:ep.share_ep_contexts=1
    • 載入 model2_ctx.onnx 模型。
    • model2_ctx.onnx 中的 EPContext 節點指定使用 ep_graph2
    • 共享工作區已包含 ep_graph2
    • EP **跳過載入** [model1_name]_[ep].bin,因為所需的圖已在共享工作區中可用。
    • 會話**將 ep_graph2 從共享工作區移動到當前會話**,使其**不再可從共享工作區訪問**。
  • 會話清理最佳實踐
    • 為避免併發執行期間出現問題,建議**以相反的順序銷燬會話**(即,在銷燬第一個會話之前銷燬第二個會話)。
    • 這確保了正確的資源管理並防止了與共享資源的潛在衝突。

使用者程式碼示例

    Ort::SessionOptions so;
    // enable ep.share_ep_contexts
    so.AddConfigEntry(kOrtSessionOptionShareEpContexts, "1");

    // Add EP, take QNN for example
    so.AppendExecutionProvider("QNN", provider_options);

    // Create sessions to load from the _ctx.onnx models with resource sharing enabled
    Ort::Session session1(env, "model1_ctx.onnx", so);	
    Ort::Session session2(env, "model2_ctx.onnx", so);

    session1.run(...);
    session2.run(...);