ONNX Runtime的执行提供者(Execution Providers):CPU、GPU与NPU的调度与切换机制

ONNX Runtime 执行提供者:CPU、GPU 与 NPU 的调度与切换机制

大家好,今天我们来深入探讨 ONNX Runtime 的核心概念之一:执行提供者(Execution Providers)。ONNX Runtime 的强大之处在于它能够利用不同的硬件加速器来执行 ONNX 模型,从而实现最佳的性能。这些硬件加速器,例如 CPU、GPU 和 NPU,通过执行提供者来集成到 ONNX Runtime 中。理解执行提供者的调度与切换机制对于充分利用硬件资源、优化模型性能至关重要。

1. 什么是执行提供者?

执行提供者是 ONNX Runtime 中用于执行模型计算的硬件加速器或软件库的抽象层。每个执行提供者都实现了 ONNX 算子的特定子集,并针对其底层硬件进行了优化。 当 ONNX Runtime 加载模型时,它会检查可用的执行提供者,并将模型图中的算子分配给最合适的执行提供者来执行。

简单来说,执行提供者就是 ONNX Runtime 与底层硬件之间的桥梁。它负责将 ONNX 模型的计算任务翻译成底层硬件能够理解并执行的指令。

2. 常见的执行提供者

ONNX Runtime 支持多种执行提供者,以下是几个最常见的:

  • CPUExecutionProvider (CPU EP): 这是默认的执行提供者,使用 CPU 执行模型计算。它适用于所有平台,并且不需要额外的配置。
  • CUDAExecutionProvider (CUDA EP): 使用 NVIDIA CUDA 平台上的 GPU 执行模型计算。它需要安装 NVIDIA 驱动程序和 CUDA 工具包。
  • ROCMExecutionProvider (ROCM EP): 使用 AMD ROCm 平台上的 GPU 执行模型计算。它需要安装 AMD 驱动程序和 ROCm 工具包。
  • OpenVINOExecutionProvider (OpenVINO EP): 使用 Intel OpenVINO 工具包加速模型计算。它可以在 CPU、GPU 和 VPU 上运行。
  • TensorrtExecutionProvider (TensorRT EP): 使用 NVIDIA TensorRT 推理引擎加速模型计算。它需要安装 NVIDIA 驱动程序和 TensorRT 工具包。
  • NNAPIExecutionProvider (NNAPI EP): 使用 Android Neural Networks API (NNAPI) 加速模型计算。它适用于 Android 设备。
  • CoreMLExecutionProvider (CoreML EP): 使用 Apple Core ML 框架加速模型计算。它适用于 macOS 和 iOS 设备。
  • ACL Execution Provider (ACL EP): 使用 Arm Compute Library (ACL) 加速模型计算。它适用于 Arm 架构的设备。
  • CPU DML Execution Provider (DirectML EP): 使用 Microsoft DirectML 加速模型计算. 它适用于 Windows 设备上的 DirectX 12 兼容的 GPU。

不同的执行提供者支持的算子和硬件平台不同,因此选择合适的执行提供者对于获得最佳性能至关重要。

3. 执行提供者的优先级和选择

ONNX Runtime 允许配置多个执行提供者,并根据优先级顺序选择合适的执行提供者来执行模型计算。优先级高的执行提供者会优先被考虑。

选择执行提供者的过程如下:

  1. 检查可用性: ONNX Runtime 首先检查配置中指定的执行提供者是否可用。如果指定的执行提供者未安装或配置不正确,则会被跳过。
  2. 算子支持: 对于模型中的每个算子,ONNX Runtime 会检查可用的执行提供者是否支持该算子。
  3. 优先级排序: 如果多个执行提供者都支持某个算子,ONNX Runtime 会根据优先级选择优先级最高的执行提供者。
  4. 图划分: ONNX Runtime 可能会将模型图划分为多个子图,并将不同的子图分配给不同的执行提供者。例如,如果模型中的某些算子只能在 CPU 上执行,而其他算子可以在 GPU 上执行,则 ONNX Runtime 会将模型图划分为 CPU 子图和 GPU 子图,并将它们分别分配给 CPUExecutionProvider 和 CUDAExecutionProvider。

4. 配置执行提供者

可以通过编程方式或配置文件来配置 ONNX Runtime 的执行提供者。

4.1 编程方式配置

以下是使用 Python API 配置执行提供者的示例代码:

import onnxruntime

# 创建 InferenceSessionOptions 对象
so = onnxruntime.SessionOptions()

# 设置执行提供者列表
providers = ['CUDAExecutionProvider', 'CPUExecutionProvider']  # CUDA 优先,其次 CPU
so.providers = providers

#  如果CUDA可用,则使用CUDA,否则使用CPU
try:
    sess = onnxruntime.InferenceSession("model.onnx", so, providers=providers)
except Exception as e:
    print(f"Error during CUDA initialization: {e}. Falling back to CPU.")
    sess = onnxruntime.InferenceSession("model.onnx", None, providers=['CPUExecutionProvider'])

# 获取可用的执行提供者
available_providers = onnxruntime.get_available_providers()
print("Available Execution Providers:", available_providers)

# 获取实际使用的执行提供者
actual_providers = sess.get_providers()
print("Actual Execution Providers:", actual_providers)

# 运行模型
input_name = sess.get_inputs()[0].name
output_name = sess.get_outputs()[0].name
input_data = ... # 你的输入数据
output = sess.run([output_name], {input_name: input_data})

在这个例子中,我们创建了一个 InferenceSessionOptions 对象,并将其 providers 属性设置为一个包含 CUDAExecutionProviderCPUExecutionProvider 的列表。这意味着 ONNX Runtime 会首先尝试使用 CUDA 执行提供者,如果 CUDA 不可用,则会回退到 CPU 执行提供者。get_available_providers() 函数可以用于获取当前系统上可用的执行提供者列表。sess.get_providers() 函数可以获取session实际使用的provider。

4.2 配置文件配置

也可以使用配置文件来配置执行提供者。配置文件的格式可以是 JSON 或 YAML。以下是一个 JSON 配置文件的示例:

{
  "session_options": {
    "providers": [
      "CUDAExecutionProvider",
      "CPUExecutionProvider"
    ],
    "graph_optimization_level": "ORT_ENABLE_ALL"
  }
}

要使用配置文件,需要在创建 InferenceSession 对象时指定配置文件的路径:

import onnxruntime

# 从配置文件加载会话选项
sess = onnxruntime.InferenceSession("model.onnx", sess_options=None, config_file="config.json")

# 运行模型
input_name = sess.get_inputs()[0].name
output_name = sess.get_outputs()[0].name
input_data = ... # 你的输入数据
output = sess.run([output_name], {input_name: input_data})

4.3 配置CUDA Execution Provider

对于CUDA Execution Provider,可以配置额外的参数,例如CUDA设备的ID,内存分配策略等。

import onnxruntime

# 创建 CUDAExecutionProvider 选项
cuda_options = {
    'device_id': 0,  # 指定使用的 GPU 设备 ID
    'cudnn_conv_algo_search': 'EXHAUSTIVE', # 可以是 EXHAUSTIVE, HEURISTIC, DEFAULT
    'gpu_mem_limit': 4 * 1024 * 1024 * 1024 # 限制 GPU 内存使用量 (4GB)
}

# 创建 InferenceSessionOptions 对象
so = onnxruntime.SessionOptions()

# 设置执行提供者列表,并传入 CUDA 选项
providers = [('CUDAExecutionProvider', cuda_options), 'CPUExecutionProvider']
so.providers = providers

# 创建 InferenceSession 对象
sess = onnxruntime.InferenceSession("model.onnx", so)

# 运行模型
input_name = sess.get_inputs()[0].name
output_name = sess.get_outputs()[0].name
input_data = ... # 你的输入数据
output = sess.run([output_name], {input_name: input_data})

4.4 配置 TensorRT Execution Provider

TensorRT EP 的配置允许用户指定 TensorRT 的构建参数,比如最大工作空间大小,精度模式等。

import onnxruntime

trt_options = {
    'device_id': 0,
    'trt_max_workspace_size': 1 * 1024 * 1024 * 1024, # 1GB
    'trt_fp16_enable': True, # 启用 FP16 精度
    'trt_int8_enable': False, # 启用 INT8 精度 (需要校准)
    'trt_int8_calibration_table_name': 'calibration.cache', # 校准表文件名
}

so = onnxruntime.SessionOptions()
providers = [('TensorrtExecutionProvider', trt_options), 'CUDAExecutionProvider', 'CPUExecutionProvider'] # TensorRT 优先
so.providers = providers

sess = onnxruntime.InferenceSession("model.onnx", so)

input_name = sess.get_inputs()[0].name
output_name = sess.get_outputs()[0].name
input_data = ... # 你的输入数据
output = sess.run([output_name], {input_name: input_data})

5. 执行提供者的切换机制

ONNX Runtime 的执行提供者切换机制是动态的,它会根据模型的结构、算子的支持情况以及硬件的可用性来自动选择最佳的执行提供者。

  • 模型加载时: 在模型加载时,ONNX Runtime 会对模型进行分析,并确定每个算子应该由哪个执行提供者来执行。这个过程称为图划分
  • 运行时: 在运行时,ONNX Runtime 会根据图划分的结果,将模型的计算任务分配给相应的执行提供者。如果某个执行提供者在运行时出现故障,ONNX Runtime 会自动切换到优先级较低的执行提供者。

这种动态的切换机制使得 ONNX Runtime 能够充分利用各种硬件资源,并保证模型的稳定运行。

6. NPU 执行提供者

随着人工智能技术的不断发展,越来越多的设备配备了专门的神经网络处理器 (NPU)。NPU 具有高度并行的计算能力,可以有效地加速深度学习模型的推理。

ONNX Runtime 也支持 NPU 执行提供者,例如:

  • NNAPIExecutionProvider (Android): 使用 Android Neural Networks API (NNAPI) 加速模型计算。
  • CoreMLExecutionProvider (Apple): 使用 Apple Core ML 框架加速模型计算。

使用 NPU 执行提供者可以显著提高模型的推理速度,并降低功耗。

6.1 NNAPI Execution Provider

NNAPI 允许 ONNX Runtime 利用 Android 设备上的专用硬件加速器(例如 NPU)进行模型推理。

import onnxruntime

# 创建 InferenceSessionOptions 对象
so = onnxruntime.SessionOptions()

# 设置执行提供者列表,NNAPI 优先
providers = ['NNAPIExecutionProvider', 'CPUExecutionProvider']
so.providers = providers

# 创建 InferenceSession 对象
try:
    sess = onnxruntime.InferenceSession("model.onnx", so)
except Exception as e:
    print(f"Error during NNAPI initialization: {e}. Falling back to CPU.")
    sess = onnxruntime.InferenceSession("model.onnx", None, providers=['CPUExecutionProvider'])

# 运行模型
input_name = sess.get_inputs()[0].name
output_name = sess.get_outputs()[0].name
input_data = ... # 你的输入数据
output = sess.run([output_name], {input_name: input_data})

在使用 NNAPI EP 时,需要注意以下几点:

  • 确保 Android 设备支持 NNAPI。
  • 模型必须与 NNAPI 兼容。某些算子可能不受 NNAPI 支持,需要回退到 CPU 执行。
  • 不同的 Android 设备上的 NNAPI 实现可能存在差异,性能表现也会有所不同。

6.2 CoreML Execution Provider

CoreML EP 允许 ONNX Runtime 利用 Apple 设备上的专用硬件加速器(例如 Neural Engine)进行模型推理。

import onnxruntime

# 创建 InferenceSessionOptions 对象
so = onnxruntime.SessionOptions()

# 设置执行提供者列表,CoreML 优先
providers = ['CoreMLExecutionProvider', 'CPUExecutionProvider']
so.providers = providers

# 创建 InferenceSession 对象
try:
    sess = onnxruntime.InferenceSession("model.onnx", so)
except Exception as e:
    print(f"Error during CoreML initialization: {e}. Falling back to CPU.")
    sess = onnxruntime.InferenceSession("model.onnx", None, providers=['CPUExecutionProvider'])

# 运行模型
input_name = sess.get_inputs()[0].name
output_name = sess.get_outputs()[0].name
input_data = ... # 你的输入数据
output = sess.run([output_name], {input_name: input_data})

在使用 CoreML EP 时,需要注意以下几点:

  • 确保 macOS 或 iOS 设备支持 Core ML。
  • 模型必须与 Core ML 兼容。某些算子可能不受 Core ML 支持,需要回退到 CPU 执行。
  • 不同的 Apple 设备上的 Core ML 实现可能存在差异,性能表现也会有所不同。

7. 性能调优

选择合适的执行提供者和配置参数对于获得最佳性能至关重要。以下是一些性能调优的建议:

  • 选择合适的执行提供者: 根据模型的特点和硬件平台选择最合适的执行提供者。例如,对于计算密集型的模型,可以使用 GPU 执行提供者;对于需要在移动设备上运行的模型,可以使用 NPU 执行提供者。
  • 配置执行提供者参数: 调整执行提供者的参数,例如 CUDA 设备的 ID、内存分配策略、TensorRT 的最大工作空间大小等,以获得最佳性能。
  • 使用 ONNX Runtime 的性能分析工具: ONNX Runtime 提供了性能分析工具,可以帮助你识别模型中的瓶颈,并优化模型的性能。例如, 可以使用onnxruntime.profiling.profile_model函数来分析模型在各个执行提供者上的执行时间。
  • 量化: 将模型量化为 INT8 或 FP16 精度可以显著提高模型的推理速度,并降低内存占用。但是,量化可能会导致精度损失,因此需要在性能和精度之间进行权衡。
  • 模型优化: 使用 ONNX Runtime 提供的模型优化工具,例如 ONNX Optimizer,可以简化模型结构,消除冗余算子,从而提高模型的推理速度。
  • 图优化: ONNX Runtime 提供多种图优化选项,例如常量折叠、算子融合等,可以减少计算量,提高模型的推理速度。可以通过so.graph_optimization_level = onnxruntime.GraphOptimizationLevel.ORT_ENABLE_ALL来启用所有图优化。

8. 调试和故障排除

在使用执行提供者时,可能会遇到各种问题。以下是一些调试和故障排除的建议:

  • 检查执行提供者是否安装和配置正确: 确保已安装所需的驱动程序和工具包,并正确配置执行提供者。
  • 查看 ONNX Runtime 的日志: ONNX Runtime 的日志可以提供有关执行提供者选择、算子分配和错误信息的详细信息。
  • 使用 ONNX Runtime 的调试工具: ONNX Runtime 提供了调试工具,可以帮助你跟踪模型的执行过程,并识别问题所在。
  • 简化模型: 如果模型过于复杂,可以尝试简化模型结构,减少算子数量,以便更容易地调试。
  • 使用最小化示例: 创建一个最小化的示例,只包含导致问题的算子和执行提供者,以便更容易地隔离和解决问题。

9. 总结与展望

我们讨论了 ONNX Runtime 执行提供者的概念、类型、优先级、配置、切换机制以及 NPU 执行提供者。理解并合理配置执行提供者是优化 ONNX 模型性能的关键。未来,随着硬件加速技术的不断发展,ONNX Runtime 将会支持更多的执行提供者,并提供更强大的性能优化工具,从而更好地满足各种应用场景的需求。

硬件加速的基石

执行提供者是 ONNX Runtime 连接硬件加速的关键组件,理解它的工作原理对优化模型性能至关重要。

灵活配置,性能至上

通过编程或配置文件,可以灵活地配置执行提供者,选择最适合的硬件加速方式,实现最佳性能。

持续优化,拥抱未来

随着硬件技术的进步,ONNX Runtime 将不断支持新的执行提供者,并提供更强大的性能优化工具。

更多IT精英技术系列讲座,到智猿学院

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注