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 by
OrtEpDevice.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 name
CUDAExecutionProvider”,并且插件库刻意沿用与旧版 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 内部的稳定标识符,承担三重职责:
- 设备筛选键:应用调用
OrtEnv.GetEpDevices()后,用d.EpName == "CUDAExecutionProvider"过滤出属于 CUDA 插件的设备(参见 OrtEnv.shared.cs); - 与 provider 名称体系对齐:即使插件形态不同,上层业务代码仍可按
CUDAExecutionProvider这个名字统一处理,无需感知插件与内置实现的差异; - 兼容性信息检索的入参:
OrtEnv.GetCompatibilityInfoFromModel(modelPath, epType)等方法要求传入的epType即OrtEpDevice.EpName的值(见 OrtEnv.shared.cs 的文档注释),名称不固定会导致预编译模型兼容性校验失效。
2.2 用 OrtEpDevice 读取设备画像
EP 名称只是OrtEpDevice暴露信息的冰山一角。从源码看,OrtEpDevice还提供以下能力,可用于应用侧的设备决策与诊断:
| 成员 | 语义 |
|---|---|
EpName | EP 名称(CUDA 插件固定为CUDAExecutionProvider) |
EpVendor | EP 所属厂商(测试断言其非空) |
EpMetadata | EP 元数据键值对 |
EpOptions | EP 选项键值对 |
HardwareDevice | 底层硬件设备(CUDA 插件为 GPU 类型,OrtHardwareDeviceType.GPU) |
GetMemoryInfo(OrtDeviceMemoryType) | 获取设备内存描述,可用于 IO 绑定与device_id提取 |
CreateSyncStream(...) | 为设备创建同步流,用于异步拷贝等场景 |
测试 CudaPluginEpTests.cs 的CudaPluginEp_DeviceProperties用例逐一验证了EpName、EpVendor非空以及HardwareDevice.Type == OrtHardwareDeviceType.GPU,可作为应用侧设备校验的模板。
三、规范二:注册名称由应用任意选择
The registration name passed to
RegisterExecutionProviderLibraryis 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 by
OrtEpDevice.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 |
| macOS | libonnxruntime_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 的CopyTensors与Session.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) | OrtRegisterExecutionProviderLibrary | env、registration_name(UTF-8)、path(平台序列化路径) |
DOrtUnregisterExecutionProviderLibrary(L3090) | OrtUnregisterExecutionProviderLibrary | env、registration_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_DeviceProperties | EpName/EpVendor非空、硬件类型为 GPU | 规范一 |
CudaPluginEp_WithProviderOptions | 以device_id选项挂载指定设备 | 规范二(设备维度) |
CudaPluginEp_AutoEpSelection | PREFER_GPU策略自动选中 CUDA 插件 | 规范一 |
CudaPluginEp_RunWithIoBinding | IO 绑定到 GPU 显存并校验输出内存类型 | 规范一 |
所有用例均以SkippableFact声明,在无插件库或无 GPU 环境下自动跳过;每个用例在finally中调用UnregisterExecutionProviderLibrary清理注册状态,避免测试间相互污染——这也再次说明 registration name 是唯一用于注销的句柄,应用侧务必妥善保存。
八、最佳实践小结
- EP 名称不可臆造:筛选设备、提取兼容性信息(
GetCompatibilityInfoFromModel的epType)、判断会话可用性时,一律以OrtEpDevice.EpName的返回值CUDAExecutionProvider为准; - 注册名随意但要有管理:注册名仅作用于“按名注册/注销”,建议与
CudaEp.GetEpName()保持一致或携带版本后缀,并在应用生命周期内集中登记,便于统一注销与排障; - 先枚举再挂载:
AppendExecutionProvider要求传入的OrtEpDevice必须来自同一OrtEnv的GetEpDevices(),且同一 EP 多设备可一次传入、多 EP 需多次调用(优先级按调用顺序); - 善用禁用回退开关:验证整图由 CUDA 插件执行时,添加
session.disable_cpu_ep_fallback=1配置,避免静默回退 CPU 造成性能误判; - 清理注册状态:插件库在进程内注册后对所有会话生效,测试或短生命周期组件务必在结束时
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),仅供参考