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(...);