网上充斥着ONNX Runtime的简单科普,却没有一个系统介绍ONNX Runtime的博客,因此本博客旨在基于官方文档进行翻译与进一步的解释。ONNX runtime的官方文档:https://onnxruntime.ai/docs/
如果尚不熟悉ONNX格式,可以参照该博客专栏,本专栏对onnx 1.16文档进行翻译与进一步解释,
ONNX 1.16学习笔记专栏:https://blog.csdn.net/qq_33345365/category_12581965.html
如果觉得有收获,点赞收藏关注,目前仅在CSDN发布,本博客会分为多个章节,目前尚在连载中。
开始编辑时间:2024/3/11;最后编辑时间:2024/3/11
所有资料均来自书写时的最新官方文档内容。
本专栏链接如下所示,所有相关内容均会在此处收录。
https://blog.csdn.net/qq_33345365/category_12589378.html
介绍
参考:https://onnxruntime.ai/docs/get-started/with-python.html
本教程第一篇:介绍ONNX Runtime(ORT)的基本概念。
本教程第二篇:是一个快速指南,包括安装ONNX Runtime;安装ONNX进行模型输出;Pytorch, TensorFlow和SciKit的快速开始例子
本教程第三篇(本博客):CUDA Provider
CUDA Provider
延续上一篇教程,已经在data目录下有一个模型,我们使用Pytorch CV模型进行介绍:
在开始本教程前,需要在目录下有data/model.onnx
文件
本文使用的版本如下所示,更多版本适配问题,见https://onnxruntime.ai/docs/execution-providers/CUDA-ExecutionProvider.html
- onnx runtime 1.17 # 使用pip安装: pip install
- cudn 8.9.2.26 # 使用conda安装: conda install cudnn=8.9.2.26, 查看现有版本conda search cudnn
- CUDA 11.8 # 安装在系统上 见cuda安装
首先创建如下代码:
import torch
import onnxruntime as ort
from torchvision import datasets
from torchvision.transforms import ToTensor
onnx_model = "data/model.onnx"
test_data = datasets.FashionMNIST(
root="data", train=False, download=True, transform=ToTensor()
)
classes = [
"T-shirt/top",
"Trouser",
"Pullover",
"Dress",
"Coat",
"Sandal",
"Shirt",
"Sneaker",
"Bag",
"Ankle boot",
]
x, y = test_data[0][0], test_data[0][1]
session = ort.InferenceSession(onnx_model, None)
input_name = session.get_inputs()[0].name
output_name = session.get_outputs()[0].name
result = session.run([output_name], {input_name: x.numpy()})
predicted, actual = classes[result[0][0].argmax(0)], classes[y]
print(f'Predicted: "{predicted}", Actual: "{actual}"')
# GPU run
providers = [
(
"CUDAExecutionProvider",
{
"device_id": torch.cuda.current_device(),
},
)
]
sess_options = ort.SessionOptions()
session = ort.InferenceSession(
onnx_model, sess_options=sess_options, providers=providers
)
input_name = session.get_inputs()[0].name
output_name = session.get_outputs()[0].name
result = session.run([output_name], {input_name: x.numpy()})
predicted, actual = classes[result[0][0].argmax(0)], classes[y]
print(f'Predicted: "{predicted}", Actual: "{actual}"')
输出:
Predicted: "Ankle boot", Actual: "Ankle boot"
Predicted: "Ankle boot", Actual: "Ankle boot"
如果可以正确输出,则证明环境正确,输出第二行的代码是使用CUDA运行产生的。
示例
import onnxruntime as ort
model_path = '<path to model>'
providers = [
(
"CUDAExecutionProvider",
{
"device_id": torch.cuda.current_device(),
# "user_compute_stream": str(torch.cuda.current_stream().cuda_stream),
"do_copy_in_default_stream": True,
"use_ep_level_unified_stream": False,
"gpu_mem_limit": 2 * 1024 * 1024 * 1024,
"arena_extend_strategy": "kNextPowerOfTwo",
"cudnn_conv_algo_search": "EXHAUSTIVE",
"cudnn_conv_use_max_workspace": "1",
"cudnn_conv1d_pad_to_nc1d": "0",
"enable_cuda_graph": "0",
"enable_skip_layer_norm_strict_mode": "0",
# "use_tf32": 0,
"prefer_nhwc": "0",
},
)
]
session = ort.InferenceSession(model_path, providers=providers)
这些参数的含义如下所示。
更多语言的示例参照https://onnxruntime.ai/docs/execution-providers/CUDA-ExecutionProvider.html
配置选项
CUDA Execution provider提供的配置选项如下所示,以下所有参数的使用示例均体现在上面的代码中。
-
device_id
-
含义:GPU的设备id
-
默认值:0
-
-
user_compute_stream
- 含义:定义了推理运行的计算流。它隐含地设置了
has_user_compute_stream
选项。它不能通过UpdateCUDAProviderOptions
设置,而是要通过UpdateCUDAProviderOptionsWithValue
设置。这不能与外部分配器一起使用。为了充分利用用户计算流,建议使用I/O绑定将输入和输出绑定到设备中的张量。 - 默认值:未知
这个参数在本文环境中不可使用,或未找到正确使用方法
- 含义:定义了推理运行的计算流。它隐含地设置了
-
do_copy_in_default_stream
- 含义:是否在默认流中进行复制,还是使用单独的流。建议设置为 true。如果设置为 false,可能会出现竞争条件,也可能性能会更好。
- 默认值:true
-
use_ep_level_unified_stream
- 含义:所有CUDA EP(Execution Provider)线程都使用相同的CUDA流。这是通过
has_user_compute_stream
、enable_cuda_graph
或者使用外部分配器时隐式启用的。 - 默认值:false
- 含义:所有CUDA EP(Execution Provider)线程都使用相同的CUDA流。这是通过
-
gpu_mem_limit
- 设备内存区域的大小限制,以字节为单位。这个大小限制仅适用于执行提供程序的区域。总设备内存使用量可能会更高。s:C++ size_t 类型的最大值(实际上是无限制的)。会被
default_memory_arena_cfg
的内容覆盖(如果指定)。 - 默认值:不确定,可能不指定为无限大
- 设备内存区域的大小限制,以字节为单位。这个大小限制仅适用于执行提供程序的区域。总设备内存使用量可能会更高。s:C++ size_t 类型的最大值(实际上是无限制的)。会被
-
arena_extend_strategy
-
含义:扩展设备内存领域(memory arena)的策略。会被
default_memory_arena_cfg
的内容覆盖(如果指定)。 -
可用值
- kNextPowerOfTwo (0):乘以二的幂次来扩展
- kSameAsRequested (1):按照请求量扩展
-
默认值:kNextPowerOfTwo
-
-
cudnn_conv_algo_search
-
含义:对cuDNN卷积算法所做的搜索类型。
-
可用值:
- EXHAUSTIVE (0):使用cudnnFindConvolutionForwardAlgorithmEx进行昂贵的穷举基准测试
- HEURISTIC (1):基于cudnnGetConvolutionForwardAlgorithm_v7的轻量级启发式搜索
- DEFAULT (2):使用CUDNN_CONVOLUTION_FWD_ALGO_IMPLICIT_PRECOMP_GEMM的默认算法
-
默认值:EXHAUSTIVE
-
-
cudnn_conv_use_max_workspace
- 含义:在下面性能调优部分的卷积密集型模型中了解此标志的作用详情。当使用 C API 时,此标志仅在使用 V2 版本的提供程序选项结构时受支持。
- 默认值:1.14和更高的版本为1,之前的版本为0
-
cudnn_conv1d_pad_to_nc1d
- 含义:在下面性能调优部分的卷积输入填充中了解此标志的作用详情。当使用 C API 时,此标志仅在使用 V2 版本的提供程序选项结构时受支持。
- 默认值:0
-
enable_cuda_graph
- 含义:在下面性能调优部分的使用CUDA图中了解此标志的作用详情。当使用 C API 时,此标志仅在使用 V2 版本的提供程序选项结构时受支持。
- 默认值:0
-
enable_skip_layer_norm_strict_mode
- 含义:是否在SkipLayerNormalization的cuda实现中使用严格模式。默认和推荐的设置是false。如果启用,可以期望提高准确性但性能下降。仅当使用C API时,此标志才受V2版本的提供者选项结构支持。
- 默认值:0
-
use_tf32
- 含义:TF32 是自 Ampere 以来在 NVIDIA GPU 上可用的一种数学模式。它允许某些 float32 矩阵乘法和卷积在张量核上以 TensorFloat-32 降低精度运行得更快:float32 输入被四舍五入为 10 位尾数,并且结果使用 float32 精度累积。TensorFloat-32 默认已启用。从 ONNX Runtime 1.18 开始,您可以使用此标志禁用它以进行推理会话。
- 默认值:1
这个参数在本文环境中不可使用,或未找到正确使用方法
-
gpu_external_[alloc|free|empty_cache]
-
gpu_external_* 用于传递外部分配器。示例Python用法:
from onnxruntime.training.ortmodule.torch_cpp_extensions import torch_gpu_allocator provider_option_map["gpu_external_alloc"] = str(torch_gpu_allocator.gpu_caching_allocator_raw_alloc_address()) provider_option_map["gpu_external_free"] = str(torch_gpu_allocator.gpu_caching_allocator_raw_delete_address()) provider_option_map["gpu_external_empty_cache"] = str(torch_gpu_allocator.gpu_caching_allocator_empty_cache_address())
-
默认值:0
这个参数在本文环境中不可使用,包都找不到
-
-
prefer_nhwc
- 含义:该选项在默认构建中不可用!必须使用onnxruntime_USE_CUDA_NHWC_OPS=ON编译ONNX Runtime。如果启用了此选项,EP将优先使用NHWC运算符而不是NCHW。所需的转换将被添加到模型中。由于NVIDIA张量核只能在NHWC布局上工作,如果模型由许多支持的运算符组成并且不需要太多的新转置节点,则可以提高性能。未来计划扩展更广泛的运算符支持。此标志仅在使用C API时从V2版本的提供程序选项结构中受支持。可以使用CreateCUDAProviderOptions创建V2提供程序选项结构,并使用UpdateCUDAProviderOptions进行更新。
- 默认值:0
性能调优
I/O绑定特征应该被利用以避免由于输入和输出的复制而产生的开销。理想情况下,输入的上载和下载可以在推理之后隐藏起来。这可以通过在运行推断时进行异步复制来实现。这在这个PR中有所体现。
Ort::RunOptions run_options;
run_options.AddConfigEntry("disable_synchronize_execution_providers", "1");
session->Run(run_options, io_binding);
通过禁用推理中的同步,用户必须在执行后负责同步计算流。此功能只应与设备本地内存或在pinned memory中分配的ORT值一起使用,否则发出的下载将是阻塞的,并且不会表现出预期的行为。
卷积密集型模型(Convolution-heavy models)
ORT利用CuDNN进行卷积操作,这个过程的第一步是确定在每个Conv节点中执行给定输入配置(输入形状、过滤器形状等)的卷积操作时使用哪个“最佳”卷积算法。这个子步骤涉及向CuDNN查询“工作空间”内存大小,并分配这个内存,以便CuDNN在确定要使用的“最佳”卷积算法时可以使用这个辅助内存。
cudnn_conv_use_max_workspace
的默认值为1,对于版本1.14或更高版本,对于之前的版本,默认值为0。当其值为0时,ORT将工作空间大小限制为32 MB,这可能导致CuDNN选择一个次优的卷积算法。为了允许ORT分配由CuDNN确定的最大可能的工作空间,需要设置一个名为cudnn_conv_use_max_workspace
的提供者选项(如下所示)。
请注意,使用此标志可能会将峰值内存使用量增加数倍(有时几个GB),但这确实帮助CuDNN为给定的输入选择最佳的卷积算法。我们发现,在使用fp16模型时使用这个标志是很重要的,因为这允许CuDNN选择用于卷积操作的张量核算法(如果硬件支持张量核操作)。对于其他数据类型(float和double),该标志可能会或可能不会导致性能增益。
仅提供Python实现
providers = [("CUDAExecutionProvider", {"cudnn_conv_use_max_workspace": '1'})]
sess_options = ort.SessionOptions()
sess = ort.InferenceSession("my_conv_heavy_fp16_model.onnx", sess_options=sess_options, providers=providers)
卷积输入填充
ORT利用CuDNN进行卷积操作。虽然CuDNN仅接受4维或5维张量作为卷积操作的输入,但如果输入是3维张量,则需要进行维度填充。给定形状为[N,C,D]的输入张量,可以填充为[N,C,D,1]或[N,C,1,D]。虽然这两种填充方式产生相同的输出,但性能可能会有很大差异,因为会选择不同的卷积算法,特别是在某些设备上,如A100。默认情况下,输入被填充为[N,C,D,1]。如果更倾向于[N,C,1,D],则需要设置一个名为cudnn_conv1d_pad_to_nc1d
的提供者选项(如下所示)。
仅提供Python实现
providers = [("CUDAExecutionProvider", {"cudnn_conv1d_pad_to_nc1d": '1'})]
sess_options = ort.SessionOptions()
sess = ort.InferenceSession("my_conv_model.onnx", sess_options=sess_options, providers=providers)
使用CUDA图(预览) Using CUDA Graphs (Preview)
在使用CUDA EP时,ORT支持使用CUDA Graphs来消除与顺序启动CUDA内核相关的CPU开销。要启用CUDA Graph的使用,请使用以下示例中显示的提供程序选项。通过将用户指定的gpu_graph_id传递给运行选项,ORT支持多图形捕获功能。当会话使用一个CUDA Graph时,gpu_graph_id是可选的。如果未设置,则默认值为0。如果gpu_graph_id设置为-1,则在该运行中禁用CUDA Graph捕获/回放。
目前,使用CUDA Graph功能存在一些限制:
- 不支持具有控制流操作(即If、Loop和Scan操作)的模型。
- CUDA Graph的使用仅限于可以将所有模型操作(图节点)分区到CUDA EP的模型。
- 模型的输入/输出类型必须是张量。
- 对于相同的图注释ID,输入/输出的形状和地址在推断调用之间不能更改。用于回放的输入张量将被复制到图形捕获中使用的输入张量的地址。
- 在多图形捕获模式下,捕获的图形将保留在会话的生命周期中,并且当前不支持捕获的图形删除功能。
- 根据设计,CUDA Graph被设计为在图形重放步骤期间从/写入相同的CUDA虚拟内存地址读取/写入,正如它在图形捕获步骤期间所做的那样。由于这个要求,使用这个特性需要使用IOBinding来绑定将被用作CUDA Graph机制读取/写入输入/输出的内存(请参见下面的示例)。
- 在更新后续推断调用的输入时,需要将新的输入复制到绑定的OrtValue输入的相应CUDA内存位置(请参见下面的示例,了解如何实现此目标)。这是因为“图形重播”将需要从相同的CUDA虚拟内存地址读取输入。
- 目前不支持多线程使用,即在使用CUDA Graph时,不可以从多个线程在同一InferenceSession对象上调用Run()。
注意:第一个Run()在幕后执行各种任务,如进行CUDA内存分配、捕获模型的CUDA Graph,然后执行图形重放以确保图形运行。由于这个原因,与第一个Run()相关联的延迟必定会很高。后续的Run()只执行在第一个Run()中捕获和缓存的图形的图形重放。
仅提供Python代码
providers = [("CUDAExecutionProvider", {"enable_cuda_graph": '1'})]
sess_options = ort.SessionOptions()
sess = ort.InferenceSession("my_model.onnx", sess_options=sess_options, providers=providers)
providers = [("CUDAExecutionProvider", {'enable_cuda_graph': True})]
x = np.array([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]], dtype=np.float32)
y = np.array([[0.0], [0.0], [0.0]], dtype=np.float32)
x_ortvalue = onnxrt.OrtValue.ortvalue_from_numpy(x, 'cuda', 0)
y_ortvalue = onnxrt.OrtValue.ortvalue_from_numpy(y, 'cuda', 0)
session = onnxrt.InferenceSession("matmul_2.onnx", providers=providers)
io_binding = session.io_binding()
# Pass gpu_graph_id to RunOptions through RunConfigs
ro = onnxrt.RunOptions()
# gpu_graph_id is optional if the session uses only one cuda graph
ro.add_run_config_entry("gpu_graph_id", "1")
# Bind the input and output
io_binding.bind_ortvalue_input('X', x_ortvalue)
io_binding.bind_ortvalue_output('Y', y_ortvalue)
# One regular run for the necessary memory allocation and cuda graph capturing
session.run_with_iobinding(io_binding, ro)
expected_y = np.array([[5.0], [11.0], [17.0]], dtype=np.float32)
np.testing.assert_allclose(expected_y, y_ortvalue.numpy(), rtol=1e-05, atol=1e-05)
# After capturing, CUDA graph replay happens from this Run onwards
session.run_with_iobinding(io_binding, ro)
np.testing.assert_allclose(expected_y, y_ortvalue.numpy(), rtol=1e-05, atol=1e-05)
# Update input and then replay CUDA graph with the updated input
x_ortvalue.update_inplace(np.array([[10.0, 20.0], [30.0, 40.0], [50.0, 60.0]], dtype=np.float32))
session.run_with_iobinding(io_binding, ro)