news 2026/9/13 10:18:51

ONNX Runtime C 开发指南:CUDA 插件执行提供程序(CUDA Plugin EP)的命名与注册规范

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ONNX Runtime C 开发指南:CUDA 插件执行提供程序(CUDA Plugin EP)的命名与注册规范

ONNX Runtime C# 开发指南:CUDA 插件执行提供程序(CUDA Plugin EP)的命名与注册规范

【免费下载链接】onnxruntimeONNX Runtime: cross-platform, high performance ML inferencing and training accelerator项目地址: https://gitcode.com/GitHub_Trending/on/onnxruntime

本篇技术指南聚焦 ONNX Runtime 的 CUDA Plugin Execution Provider(CUDA 插件执行提供程序,以下简称 CUDA Plugin EP)在 C# 托管 API 侧的两条核心贡献规范:EP 名称(OrtEpDevice.EpName返回值)固定为CUDAExecutionProvider,而RegisterExecutionProviderLibrary的注册名称可由应用任意选择。文章将结合 csharp/CONTRIBUTING.md 的规范条目,深入仓库源码与测试用例,完整演示从注册插件库、枚举 EP 设备、挂载 SessionOptions 到运行推理的 C# 接入链路,并给出底层 P/Invoke 映射与 NuGet 打包的细节,帮助开发者正确、无歧义地集成 CUDA Plugin EP。

一、背景:什么是 CUDA Plugin EP 与 EP Plugin API

CUDA Plugin EP 是 ONNX Runtime 将 CUDA 执行提供程序从核心运行时中剥离、以独立插件库形式交付的一种构建形态。通过 cmake/onnxruntime_providers_cuda.cmake 中的onnxruntime_BUILD_CUDA_EP_AS_PLUGIN选项控制:置为ON时,onnxruntime_providers_cuda编译产物就是 CUDA 插件 EP;置为OFF(默认)时则是传统源码内置的 CUDA EP。详见 docs/cuda_plugin_ep/QUICK_START.md。

插件形态的核心价值在于运行时与 EP 解耦:插件库针对不同 CUDA 大版本(CUDA 12 / 13)分别交付,且可通过 EP Plugin API 动态注册到运行时。这套 API 在 C# 侧对应三个关键入口:

C# API作用
OrtEnv.RegisterExecutionProviderLibrary将插件原生库注册到进程级环境
OrtEnv.GetEpDevices枚举已注册 EP 可用的(EP,设备)组合
SessionOptions.AppendExecutionProvider(OrtEnv, IReadOnlyList<OrtEpDevice>, ...)将选中的 EP 设备挂载到会话

csharp/CONTRIBUTING.md 正是针对这套 API 的使用给出两条必须遵守的命名约定,防止不同集成方在注册名、EP 名上产生混乱。

二、规范一:EP 名称固定为CUDAExecutionProvider

The EP name for the CUDA Plugin EP (returned byOrtEpDevice.EpName) isCUDAExecutionProvider.

OrtEpDevice是 C# 侧对“执行提供程序 + 其可利用的硬件设备”这一组合的封装,定义于 csharp/src/Microsoft.ML.OnnxRuntime/OrtEpDevice.shared.cs。它的EpName属性直接调用原生OrtEpDevice_EpName接口取回 EP 名称字符串:

public string EpName { get { IntPtr namePtr = NativeMethods.OrtEpDevice_EpName(_handle); return NativeOnnxValueHelper.StringFromNativeUtf8(namePtr); } }

对 CUDA Plugin EP 而言,该属性的返回值恒为字符串CUDAExecutionProvider,与内置 CUDA EP 的 provider 名称完全一致。这一约定在多处得到印证:

  • 插件包自带的辅助类 plugin-ep-cuda/csharp/Microsoft.ML.OnnxRuntime.EP.Cuda/CudaEp.cs 中,GetEpName()直接返回硬编码的"CUDAExecutionProvider",并注释说明它“用于从OrtEnv.GetEpDevices()返回结果中筛选对应的OrtEpDevice”;
  • docs/cuda_plugin_ep/QUICK_START.md 明确说明“The plugin EP is registered under the nameCUDAExecutionProvider”,并且插件库刻意沿用与旧版 CUDA EP 相同的原生文件名(onnxruntime_providers_cuda.dll/libonnxruntime_providers_cuda.so);
  • 测试用例 csharp/test/Microsoft.ML.OnnxRuntime.Tests.Common/CudaPluginEpTests.cs 中以常量形式声明private const string CudaPluginEpName = "CUDAExecutionProvider";并在设备筛选、属性断言中反复使用。

2.1 为什么 EP 名称必须固定

EP 名称是 ONNX Runtime 内部的稳定标识符,承担三重职责:

  1. 设备筛选键:应用调用OrtEnv.GetEpDevices()后,用d.EpName == "CUDAExecutionProvider"过滤出属于 CUDA 插件的设备(参见 OrtEnv.shared.cs);
  2. 与 provider 名称体系对齐:即使插件形态不同,上层业务代码仍可按CUDAExecutionProvider这个名字统一处理,无需感知插件与内置实现的差异;
  3. 兼容性信息检索的入参OrtEnv.GetCompatibilityInfoFromModel(modelPath, epType)等方法要求传入的epTypeOrtEpDevice.EpName的值(见 OrtEnv.shared.cs 的文档注释),名称不固定会导致预编译模型兼容性校验失效。

2.2 用 OrtEpDevice 读取设备画像

EP 名称只是OrtEpDevice暴露信息的冰山一角。从源码看,OrtEpDevice还提供以下能力,可用于应用侧的设备决策与诊断:

成员语义
EpNameEP 名称(CUDA 插件固定为CUDAExecutionProvider
EpVendorEP 所属厂商(测试断言其非空)
EpMetadataEP 元数据键值对
EpOptionsEP 选项键值对
HardwareDevice底层硬件设备(CUDA 插件为 GPU 类型,OrtHardwareDeviceType.GPU
GetMemoryInfo(OrtDeviceMemoryType)获取设备内存描述,可用于 IO 绑定与device_id提取
CreateSyncStream(...)为设备创建同步流,用于异步拷贝等场景

测试 CudaPluginEpTests.cs 的CudaPluginEp_DeviceProperties用例逐一验证了EpNameEpVendor非空以及HardwareDevice.Type == OrtHardwareDeviceType.GPU,可作为应用侧设备校验的模板。

三、规范二:注册名称由应用任意选择

The registration name passed toRegisterExecutionProviderLibraryis arbitrary and chosen by the application.

与固定不变的 EP 名称不同,RegisterExecutionProviderLibrary第一个参数(registration name)没有硬性约束,完全由调用方自定义。其方法签名定义于 csharp/src/Microsoft.ML.OnnxRuntime/OrtEnv.shared.cs:

public void RegisterExecutionProviderLibrary(string registrationName, string libraryPath) { var registrationNameUtf8 = NativeOnnxValueHelper.StringToZeroTerminatedUtf8(registrationName); var pathUtf8 = NativeOnnxValueHelper.GetPlatformSerializedString(libraryPath); NativeApiStatus.VerifySuccess( NativeMethods.OrtRegisterExecutionProviderLibrary(handle, registrationNameUtf8, pathUtf8)); }

3.1 registration name 的本质:注册表条目键

registration name 仅是应用为“本次加载的插件库”起的本地别名,用于在同一进程内区分多次注册、以及后续按名注销。它不会被写入 EP 名称,也不影响OrtEpDevice.EpName的返回值——后者由插件库内部决定。配套的注销 API 即按同名卸载:

public void UnregisterExecutionProviderLibrary(string registrationName)

因此,即使两次以不同 registration name 注册同一个 CUDA 插件库,枚举出的设备 EP 名称依然都是CUDAExecutionProvider

3.2 命名建议

虽然规范允许任意命名,仓库内已有实践可作为参考:

  • 测试代码为“省事”而让注册名与 EP 名保持一致,并在注释中明确说明这一选择(CudaPluginEpTests.cs:“EP name as returned byOrtEpDevice.EpName. Also used as the registration name for convenience.”);
  • NuGet 辅助类CudaEp.cs 建议以CudaEp.GetEpName()(即CUDAExecutionProvider)作为注册名传入RegisterExecutionProviderLibrary(),这样名字既能自解释,又与设备筛选逻辑天然一致。

如果应用同时需要注册多个不同版本的插件库(例如 CUDA 12 与 CUDA 13),则建议采用能区分版本的 registration name(如CUDAExecutionProvider-Cuda13),以方便后续精确注销与审计——这正是“任意选择”留给应用的灵活空间。

四、C# 完整接入链路(四步走)

综合 CudaPluginEpTests.cs 中的多个用例,CUDA Plugin EP 在 C# 中的标准接入流程可归纳为四步。

步骤 1:注册插件库

var ortEnv = OrtEnv.Instance(); const string CudaPluginEpName = "CUDAExecutionProvider"; // 注册名,可自定义 string libPath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? Path.Combine(Directory.GetCurrentDirectory(), "onnxruntime_providers_cuda.dll") : Path.Combine(Directory.GetCurrentDirectory(), "libonnxruntime_providers_cuda.so"); ortEnv.RegisterExecutionProviderLibrary(CudaPluginEpName, libPath);

插件库文件名与平台对应关系(见 docs/cuda_plugin_ep/QUICK_START.md 与 plugin-ep-cuda/csharp/README.md):

平台库文件名
Windows (x64 / arm64)onnxruntime_providers_cuda.dll
Linux (x64 / arm64)libonnxruntime_providers_cuda.so
macOSlibonnxruntime_providers_cuda.dylib

若通过Microsoft.ML.OnnxRuntime.EP.Cuda这类 NuGet 插件包引入,可借助CudaEp.GetLibraryPath()自动定位包内runtimes/<rid>/native/布局下的原生库,避免手写路径探测逻辑。

步骤 2:枚举并筛选 EP 设备

var epDevices = ortEnv.GetEpDevices(); var cudaPluginDevices = epDevices .Where(d => d.EpName == CudaPluginEpName) // 按固定 EP 名称筛选 .ToList(); if (cudaPluginDevices.Count == 0) throw new InvalidOperationException("No CUDA Plugin EP devices available (no GPU?).");

注意GetEpDevices()返回的是“EP × 设备”组合,同一 EP 可能对应多张 GPU,每张 GPU 对应一个OrtEpDevice条目。

步骤 3:挂载到 SessionOptions

using var sessionOptions = new SessionOptions(); sessionOptions.AppendExecutionProvider(ortEnv, cudaPluginDevices, null); // 可选:禁止任何节点回退到 CPU,用于验证整图都被 CUDA 插件接管 sessionOptions.AddSessionConfigEntry("session.disable_cpu_ep_fallback", "1");

AppendExecutionProvider(OrtEnv, IReadOnlyList<OrtEpDevice>, IReadOnlyDictionary<string, string>)定义于 csharp/src/Microsoft.ML.OnnxRuntime/SessionOptions.shared.cs,要点如下:

  • epDevices内所有设备必须属于同一个 EP(EpName 相同);
  • EP 的优先级由 Append 调用的先后顺序决定,越先挂载优先级越高;
  • epOptions为可选 provider 选项,例如指定device_id选择具体 GPU:
    var epOptions = new Dictionary<string, string> { { "device_id", "0" } }; sessionOptions.AppendExecutionProvider(ortEnv, new[] { firstDevice }.ToList(), epOptions);

    测试 CudaPluginEpTests.cs 中演示了从firstDevice.GetMemoryInfo(OrtDeviceMemoryType.DEFAULT).Id反查实际设备号、再原样传回device_id的稳健写法;

  • epOptions为 null 或空,内部直接以零长度的键值对调用原生OrtSessionOptionsAppendExecutionProvider_V2

步骤 4:创建会话并运行推理

using var session = new InferenceSession(modelBytes, sessionOptions); // 或 modelPath using var runOptions = new RunOptions(); using var inputOrtValue = OrtValue.CreateTensorValueFromMemory(inputData, inputShape); using var results = session.Run(runOptions, new[] { inputName }, new[] { inputOrtValue }, session.OutputNames);

测试CudaPluginEp_CreateSessionAndRunInference以 squeezenet 模型连续运行 3 次,断言输出张量形状{1, 1000, 1, 1}与基准输出一致,用于验证多次运行间无内存损坏——这一“重复运行 + 输出比对”的验证模式同样适用于生产环境的冒烟测试。

4.1 进阶:自动 EP 选择(Auto EP Selection)

若不显式指定设备,可改用策略化自动选择。测试 CudaPluginEpTests.cs 展示了其用法:

using var sessionOptions = new SessionOptions(); sessionOptions.SetEpSelectionPolicy(ExecutionProviderDevicePolicy.PREFER_GPU); sessionOptions.AddSessionConfigEntry("session.disable_cpu_ep_fallback", "1"); using var session = new InferenceSession(model, sessionOptions);

该策略会遍历已注册的 EP 设备,按“偏好 GPU”规则自动选中 CUDA 插件设备。

4.2 进阶:IO 绑定到 GPU 显存

OrtEnv.shared.cs 的CopyTensorsSession.CreateIoBinding()支持将输入输出直接绑定到 CUDA 设备内存,避免主机-设备间往返拷贝。测试CudaPluginEp_RunWithIoBinding(CudaPluginEpTests.cs)的完整模式为:

using var ioBinding = session.CreateIoBinding(); ioBinding.BindInput(inputName, inputOrtValue); // 将输出绑定到 CUDA 设备默认内存类型 var cudaMemInfo = cudaDevice.GetMemoryInfo(OrtDeviceMemoryType.DEFAULT); ioBinding.BindOutputToDevice(outputName, cudaMemInfo); ioBinding.SynchronizeBoundInputs(); using var results = session.RunWithBoundResults(runOptions, ioBinding); ioBinding.SynchronizeBoundOutputs(); // 校验输出确在 GPU 内存上 var memInfo = results.First().GetTensorMemoryInfo(); Assert.Equal(OrtDeviceMemoryType.DEFAULT, memInfo.GetDeviceMemoryType());

五、底层实现:C# P/Invoke 到原生 EP Plugin API

两条规范对应的原生调用最终落在 csharp/src/Microsoft.ML.OnnxRuntime/NativeMethods.shared.cs 的 P/Invoke 委托上,相关委托签名汇总如下:

C# 委托对应原生 API关键形参
DOrtRegisterExecutionProviderLibrary(L3078)OrtRegisterExecutionProviderLibraryenvregistration_name(UTF-8)、path(平台序列化路径)
DOrtUnregisterExecutionProviderLibrary(L3090)OrtUnregisterExecutionProviderLibraryenvregistration_name
DOrtGetEpDevices(L3103)OrtGetEpDevices输出ep_devices指针数组与数量
DOrtSessionOptionsAppendExecutionProvider_V2(L3137)OrtSessionOptionsAppendExecutionProvider_V2会话选项、env、设备指针数组、EP 选项键值

从这些签名可以推断出原生层的数据流:registration_name只用于注册/注销的按名匹配(印证了“任意字符串即可”的规范),而真正的 EP 身份信息由插件库在加载时通过CreateEpFactories()与运行时协商产生,最终体现为OrtGetEpDevices枚举结果中每个设备的EpName。名称的“固定”与“任意”由此在架构上天然解耦。

六、运行时约束与包体结构

  • 最小运行时版本:插件库编译自本仓库头文件,但可加载进更老的核心运行时,最低兼容版本声明于 plugin-ep-cuda/MIN_ONNXRUNTIME_VERSION,加载时由CreateEpFactories()做 API 版本协商,不满足则给出可读错误而非崩溃(见 docs/cuda_plugin_ep/QUICK_START.md);
  • NuGet 包布局Microsoft.ML.OnnxRuntime.EP.Cuda系列包按 CUDA 大版本 × RID 拆分(如Microsoft.ML.OnnxRuntime.EP.Cuda13.win-x64),包内托管 DLL 位于lib/netstandard2.0/,原生插件库位于runtimes/<rid>/native/,打包脚本为 plugin-ep-cuda/csharp/pack_nuget.py;
  • 平台限制:CUDA 插件当前支持 Windows 与 Linux 的 x64/arm64,测试代码通过#if !(ANDROID || IOS)排除移动平台(CudaPluginEpTests.cs),辅助类CudaEp.cs对不支持的平台直接抛出PlatformNotSupportedException

七、测试与验证

仓库为上述规范提供了可直接运行的验证用例,集中在 csharp/test/Microsoft.ML.OnnxRuntime.Tests.Common/CudaPluginEpTests.cs,六个用例覆盖规范的两个核心点:

用例验证内容对应规范
RegisterCudaPluginEp注册后能在GetEpDevices()中按EpName == "CUDAExecutionProvider"找到设备规范一
CudaPluginEp_CreateSessionAndRunInference注册、枚举、挂载、推理全链路,输出与基准一致规范一 + 四步流程
CudaPluginEp_DevicePropertiesEpName/EpVendor非空、硬件类型为 GPU规范一
CudaPluginEp_WithProviderOptionsdevice_id选项挂载指定设备规范二(设备维度)
CudaPluginEp_AutoEpSelectionPREFER_GPU策略自动选中 CUDA 插件规范一
CudaPluginEp_RunWithIoBindingIO 绑定到 GPU 显存并校验输出内存类型规范一

所有用例均以SkippableFact声明,在无插件库或无 GPU 环境下自动跳过;每个用例在finally中调用UnregisterExecutionProviderLibrary清理注册状态,避免测试间相互污染——这也再次说明 registration name 是唯一用于注销的句柄,应用侧务必妥善保存。

八、最佳实践小结

  1. EP 名称不可臆造:筛选设备、提取兼容性信息(GetCompatibilityInfoFromModelepType)、判断会话可用性时,一律以OrtEpDevice.EpName的返回值CUDAExecutionProvider为准;
  2. 注册名随意但要有管理:注册名仅作用于“按名注册/注销”,建议与CudaEp.GetEpName()保持一致或携带版本后缀,并在应用生命周期内集中登记,便于统一注销与排障;
  3. 先枚举再挂载AppendExecutionProvider要求传入的OrtEpDevice必须来自同一OrtEnvGetEpDevices(),且同一 EP 多设备可一次传入、多 EP 需多次调用(优先级按调用顺序);
  4. 善用禁用回退开关:验证整图由 CUDA 插件执行时,添加session.disable_cpu_ep_fallback=1配置,避免静默回退 CPU 造成性能误判;
  5. 清理注册状态:插件库在进程内注册后对所有会话生效,测试或短生命周期组件务必在结束时UnregisterExecutionProviderLibrary

参考文件索引

  • 规范原文:csharp/CONTRIBUTING.md
  • EP 设备封装:csharp/src/Microsoft.ML.OnnxRuntime/OrtEpDevice.shared.cs
  • 环境与注册 API:csharp/src/Microsoft.ML.OnnxRuntime/OrtEnv.shared.cs
  • 会话挂载 API:csharp/src/Microsoft.ML.OnnxRuntime/SessionOptions.shared.cs
  • P/Invoke 委托定义:csharp/src/Microsoft.ML.OnnxRuntime/NativeMethods.shared.cs
  • C# 集成测试:csharp/test/Microsoft.ML.OnnxRuntime.Tests.Common/CudaPluginEpTests.cs
  • 插件快速开始(构建与多语言用法):docs/cuda_plugin_ep/QUICK_START.md
  • NuGet 打包说明:plugin-ep-cuda/csharp/README.md
  • C# 辅助类:plugin-ep-cuda/csharp/Microsoft.ML.OnnxRuntime.EP.Cuda/CudaEp.cs
  • 最低运行时版本:plugin-ep-cuda/MIN_ONNXRUNTIME_VERSION

【免费下载链接】onnxruntimeONNX Runtime: cross-platform, high performance ML inferencing and training accelerator项目地址: https://gitcode.com/GitHub_Trending/on/onnxruntime

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 10:17:15

PostgreSQL重复数据处理:从检测到安全删除的实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 10:14:43

MMC变流器在电力质量调节中的Simulink仿真与应用

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 10:14:23

局部模糊C均值聚类在图像分割中的MATLAB实现与调参实践

简介&#xff1a;基于MATLAB实现的局部模糊c均值聚类&#xff08;FLICM&#xff09;代码包&#xff0c;面向图像分割、聚类分析领域的研究生、科研人员及工程开发者&#xff0c;用于解决传统FCM算法对噪声敏感、分割不稳定的问题。压缩包共7个文件、容量87KB&#xff0c;包含2个…

作者头像 李华
网站建设 2026/9/13 10:13:52

Java对象比较:==与equals()的深度解析与实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 10:09:34

C++ emplace_back与push_back性能差异深度解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华