ONNX Runtime WebGPU EP ABI 适配器:插件式动态执行提供者的设计与实现解析
【免费下载链接】onnxruntimeONNX Runtime: cross-platform, high performance ML inferencing and training accelerator项目地址: https://gitcode.com/GitHub_Trending/on/onnxruntime
本文以 onnxruntime/core/providers/webgpu/ep/README.md 为核心,深入剖析 ONNX Runtime WebGPU 执行提供者(EP)在“EP ABI 适配器”架构下的设计取舍与源码实现:静态库构建如何保持零改动、动态库构建如何仅依赖 EP ABI 并通过桥接类连接内部实现、导出符号与版本脚本如何收敛 DLL 接口,以及构建系统中版本号的注入方式。读完本文,你将掌握 WebGPU EP 插件化(Plugin EP)的完整机制,包括设备枚举、内存分配器、数据布局协商、图捕获(Graph Capture)等关键 API 的实现路径,并能理解 README 中列出的“缺失部分”当前在源码中的落地状态。
一、定位:ep文件夹是 WebGPU 的 EP ABI 适配层
onnxruntime/core/providers/webgpu/ep/ 目录下的 README 开门见山地说明了该目录的职责:
The current folder contains the implementation of EP ABI adapter for WebGPU.
也就是说,整个 WebGPU EP 的推理内核(program_manager、buffer_manager、wgsl 着色器模板等,见 onnxruntime/core/providers/webgpu/)在静态库构建下直接链接进onnxruntime主库;而在插件化场景下,同一套内核被封装为一个独立的动态库(Windows 下为onnxruntime_providers_webgpu.dll),通过 ONNX Runtime 定义的EP ABI(Execution Provider ABI,即OrtEpFactory/OrtEp函数表结构)与主运行时解耦通信。
目录结构如下,各文件职责清晰:
| 文件 | 职责 |
|---|---|
| ep.h / ep.cc | Ep桥接类,实现OrtEp函数表,连接 EP ABI 与内部WebGpuExecutionProvider |
| factory.h / factory.cc | Factory类,实现OrtEpFactory函数表,负责设备枚举、创建 EP 实例、共享分配器与数据搬运 |
| api.cc | 动态库仅有的两个导出符号CreateEpFactories/ReleaseEpFactory的实现 |
| symbols.def | Windows 导出定义文件(.def) |
| version_script.lds | Linux/Unix 链接器版本脚本,限定导出符号 |
| versioninfo.rc | Windows DLL 资源版本信息 |
二、README 的核心设计约束:静态库零改动,动态库“只用 EP ABI”
README 的 “Design considerations” 一节给出了三条设计决策,这些决策直接决定了源码的组织方式:
- 静态库构建不做任何改动(“No changes to static library build. It should still work as before.”)。WebGPU 在静态构建下仍是内置 EP,原有的
GetProvider路径继续工作。 - 动态库只使用、且仅使用 EP ABI(“use and only use the EP ABI. (no support for
GetProvider)”)。插件库不再依赖主库的内部 C++ 符号发现机制,避免 ABI 耦合。 - 动态库仍然依赖 onnxruntime 各构建目标(“still depends on onnxruntime targets”),并通过桥接类(bridge)把 EP ABI 的内部类连接起来。
这第 3 点在 cmake/onnxruntime_providers_webgpu.cmake 中有直接印证:动态库目标onnxruntime_providers_webgpu链接了onnxruntime_optimizer、onnxruntime_providers、onnxruntime_lora、onnxruntime_framework、onnxruntime_graph、onnxruntime_util、onnxruntime_common、onnxruntime_flatbuffers等内部目标——插件 EP 复用了大量主库代码,只是对外接口收敛到 EP ABI。
构建方式上还有两条硬约束(cmake/onnxruntime_providers_webgpu.cmake):共享库构建不支持 build cache,且Emscripten 平台不支持共享库构建(WASM 场景请使用静态库),否则 CMake 会直接报错终止。
三、导出面:整个动态库只有两个 C 符号
无论 Windows 还是 Linux,插件库的导出符号都被严格限定为两个:
- symbols.def(Windows):
LIBRARY "onnxruntime_providers_webgpu.dll" EXPORTS CreateEpFactories @1 ReleaseEpFactory @2- version_script.lds(Linux):
VERS_1.0.0 { global: CreateEpFactories; ReleaseEpFactory; local: *; };这两个符号正是 README 所说“use and only use the EP ABI”的落地点。cmake/onnxruntime_providers_webgpu.cmake 中按平台分别挂上链接选项:Linux 使用--version-script=.../ep/version_script.lds、--gc-sections与-rpath=$ORIGIN;Windows 使用-DEF:.../ep/symbols.def;macOS 则仅附加-dead_strip(符号导出依赖 api.cc 中的EXPORT_SYMBOLvisibility 属性)。
3.1CreateEpFactories:插件入口
api.cc 中的CreateEpFactories由 ORT 主库在加载插件动态库时调用,其执行流程为:
- 手动初始化 C++ API 层:以
ORT_API_MANUAL_INIT模式包含onnxruntime_cxx_api.h,并调用onnxruntime::ep::ApiInit(ort_api_base, ORT_PLUGIN_EP_MIN_ORT_VERSION)。这里传入的最小 ORT 版本宏在初始化失败时(如宿主 ORT 版本过低)会经由一个保守的report_errorlambda 创建OrtStatus——由于此时OrtApi尚未完成初始化,代码用static_assert断言OrtApi::CreateStatus在 v1 API 中的偏移为 0,保证即使只能拿到 v1 表也能安全创建错误对象。 - 读取环境变量决定 Factory 行为(见 factory.h 的
Config结构):- 环境配置项
kOrtEnvAllowVirtualDevices(值为"1"时)允许注册一个虚拟 GPU 设备,用于“无设备、仅编译”(device-free compile-only)会话,典型场景是 Win32k-lockdown 沙箱等枚举不到 GPU 的主机; - 环境变量
ORT_WEBGPU_EP_ALLOW_SOFTWARE_ADAPTER设为"1"时,允许在没有 GPU 硬件设备的情况下把 WebGPU EP 挂到CPU 硬件设备上,使 EP 设备变得可选(Dawn 独立选择软件适配器),开启时还会打印 WARNING 日志。
- 环境配置项
- 初始化全局默认 logger(
LoggingManager::CreateDefaultLogger),并构造唯一的Factory实例返回给宿主。
3.2ReleaseEpFactory:清理顺序即“缺失部分”的答案
README 的 “Missing parts” 第一条提到“需要一种方式做 WebGPU 清理(OrtEnv::~OrtEnv()目前在静态库构建下调用webgpu::CleanupWebGpuContexts())”。从当前源码看,动态库构建中这个职责已经落在 api.cc 的ReleaseEpFactory中,其清理链条为固定五步:
delete掉Factory实例(其析构函数会释放虚拟硬件设备);webgpu::CleanupKernelRegistries()清理缓存的 kernel 注册表;webgpu::CleanupWebGpuContexts()清理 WebGPU 上下文;LoggingManager::DestroyDefaultLogger()销毁默认日志包装;google::protobuf::ShutdownProtobufLibrary()关闭 protobuf 库。
这与静态库构建下OrtEnv析构时的清理形成了互补:静态库由OrtEnv生命周期驱动,动态库由插件工厂生命周期驱动。
四、Factory:设备枚举、EP 实例与共享分配器
Factory继承自OrtEpFactory(见 factory.h),构造函数(factory.cc)完成三件事:初始化OrtEpFactory函数表、预构造两组OrtMemoryInfo(default_memory_info_为OrtDeviceAllocator,readonly_memory_info_为OrtReadOnlyAllocator,allocator name 均为WEBGPU_BUFFER)、在allow_virtual_devices开启时通过Api().ep.CreateHardwareDevice注册带IsVirtual=1元数据的虚拟 GPU 硬件设备。工厂自报身份为:名字kWebGpuExecutionProvider、厂商"Microsoft"、vendor id0、版本ORT_PLUGIN_EP_VERSION。
4.1GetSupportedDevices:三层设备暴露策略
factory.cc 按顺序做三件事:
- 真实 GPU:遍历宿主传入的
OrtHardwareDevice数组,凡类型为OrtHardwareDeviceType_GPU的设备都包装成一个OrtEpDevice,并挂上默认与只读两组 allocator info; - 软件适配器回退(
allow_software_adapter开启且没有任何 GPU 设备时):找到 CPU 硬件设备,把 WebGPU EP 暴露在其上,使 EP 在纯 CPU 主机上仍可选(实际由 Dawn 选择软件适配器); - 虚拟设备(
allow_virtual_devices开启时):额外暴露虚拟 GPU EP 设备,且不携带 allocator info——源码注释解释了原因:虚拟设备仅支撑“停止在会话最终化之前的 compile-only 会话”,从不分配内存,若设置内存信息反而会让 ORT 尝试创建没有底层设备的共享 WebGPU 分配器。
4.2CreateEp:把内部WebGpuExecutionProvider包进Ep
factory.cc 的CreateEpImpl是桥接发生的地方:
- 目前只支持一次一个设备(
num_devices != 1直接报ORT_INVALID_ARGUMENT); - 从
OrtSessionOptions取出全部 session config entries 并灌入ConfigOptions,再经WebGpuProviderFactoryCreator::Create(config_options)+CreateProvider构造出真正的内部 EPWebGpuExecutionProvider——可以看到插件路径下,session 配置项(如webgpu_*选项)的解析逻辑与静态构建完全复用; - 虚拟设备 + 非 compile-only 组合会被前置拒绝:若选中的设备元数据标记为虚拟但
session.compile_only != 1,直接返回清晰的错误信息,避免 Dawn 后续创建设备时的晦涩失败; - 分配器策略:若上下文
HasDevice()为 false(即无设备的 compile-only 会话),CreateWebGpuAllocator会构造一个 no-op 分配器;否则构造真正的GpuBufferAllocator。最终Ep::Config持有三个分配器:CPU 分配器(CPUAllocator::DefaultInstance())、设备分配器、以及 initializer 只读设备分配器(指向InitializerBufferManager)。
4.3 共享分配器与数据搬运
CreateAllocatorImpl(factory.cc)只接受allocator_type == OrtDeviceAllocator、device_id == 0、allocator name 为WEBGPU_BUFFER的OrtMemoryInfo,底层包装成webgpu::GpuBufferAllocator(绑定到默认 WebGPU 上下文的BufferManager);CreateDataTransferImpl(factory.cc)直接调用OrtWebGpuCreateDataTransfer(),复用 WebGPU EP 的 host↔device 数据搬运实现;IsStreamAware恒为false,CreateSyncStreamForDevice返回ORT_NOT_IMPLEMENTED——WebGPU EP 不是 stream-aware 的 EP。
五、Ep桥接类:EP ABI 函数表的逐项映射
Ep继承自onnxruntime::ep::adapter::Ep(见 ep.h),构造函数(ep.cc)把OrtEp的每个函数表槽位绑定到对应的静态实现,并显式地把不适用的槽位置空:
Compile = nullptr、ReleaseNodeComputeInfos = nullptr:per-kernel 型 EP 不使用图级编译;SetDynamicOptions = nullptr:未实现;CreateSyncStreamForDevice = nullptr:非 stream-aware;GetCompiledModelCompatibilityInfo = nullptr:不是 compiled EP。
有实现的槽位逐一映射到内部WebGpuExecutionProvider的方法,形成“EP ABI → 内部类”的一对一桥接:
| EP ABI 函数 | 桥接目标 | 行为说明 |
|---|---|---|
GetName | Factory::GetName | 返回kWebGpuExecutionProvider |
GetCapability | 见 5.1 节 | 决定哪些节点交给 WebGPU |
GetKernelRegistry | WebGpuExecutionProvider::GetKernelRegistryImpl() | 委托内部注册表(ep.cc) |
GetPreferredDataLayout/ShouldConvertDataLayoutForOp | GetPreferredLayout()/ShouldConvertDataLayoutForOp() | 内部DataLayout枚举与OrtEpDataLayout一一对应(NCHW=0,NHWC=1),未知 op 返回-1表示未表态 |
OnRunStart/OnRunEnd | WebGpuExecutionProvider::OnRunStart/OnRunEnd | OnRunStart目前只透传kOrtRunOptionsConfigCudaGraphAnnotation(gpu_graph_id)这一条 run option(ep.cc) |
IsConcurrentRunSupported | 恒false | 不支持并发运行 |
IsGraphCaptureEnabled/IsGraphCaptured/ReplayGraph/ReleaseCapturedGraph/GetGraphCaptureNodeAssignmentPolicy | 同名内部方法 | 直通内部 WebGPU 图捕获实现(ep.cc) |
CreateAllocator | adapter::Allocator包装 | OrtReadOnlyAllocator映射到 initializer 分配器,其余映射到设备分配器(ep.cc) |
5.1GetCapability:节点分配与 CPU 回退逻辑
GetCapabilityImpl(ep.cc)是插件路径下的图分区入口,其判定流程为:
- 取图中全部节点,EP 名已标注为 WebGPU 的节点直接进入候选集;
- 已标注为其他非 CPU EP的节点跳过(拒绝重分配);
- 对未标注或标注 CPU 的节点,先用
EpGraphSupportInfo_LookUpKernel查询 kernel 注册表——查不到 kernel 即回退 CPU,并打 INFO 日志(webgpu kernel not found in registries for Op type: ...); - 命中
ForceCpuNodeNames强制 CPU 名单的节点回退 CPU; - 针对
com.microsoft域的Attention算子做细粒度能力检查:不支持mask_index(input[3])、past(input[4])、past_seq_len(input[6])输入,不支持present(output[1])输出,且past_present_share_buffer属性非 0 时一律回退 CPU(借助FALLBACK_TO_CPU_IF_EXIST_INPUT/OUTPUT宏实现,ep.cc); - 最后调用
onnxruntime::ep::GetCpuPreferredNodes(头文件ep/get_capability_utils.h)做 CPU 偏好节点判定,只有不在cpu_preferred_nodes中的候选节点才通过EpGraphSupportInfo_AddSingleNode正式划归 WebGPU。
这套逻辑保证了插件路径与静态路径的节点分配语义一致:注册表查不到 kernel 或能力不满足时,节点自然留在 CPU 上执行,而不是让整个会话失败。
六、构建与版本治理:版本号如何进入插件 DLL
插件 EP 的版本信息有两条注入链,均在 cmake/onnxruntime_providers_webgpu.cmake 中完成:
- 插件自身版本:若未显式定义
onnxruntime_PLUGIN_EP_VERSION,默认取${ORT_VERSION}-dev,编译为ORT_PLUGIN_EP_VERSION宏,供Factory::GetVersionImpl返回; - 最小兼容 ORT 版本:以 plugin-ep-webgpu/MIN_ONNXRUNTIME_VERSION 文件(当前内容为
1.24.4,格式要求严格的MAJOR.MINOR.PATCH)为唯一事实来源,读入并编译为ORT_PLUGIN_EP_MIN_ORT_VERSION宏——该宏在CreateEpFactories中传给ApiInit,从而在插件加载期就强制校验宿主 ORT 版本是否足够新;文件缺失或为空时 CMake 直接FATAL_ERROR。
这与 plugin-ep-webgpu/ 目录下的打包工程(VERSION_NUMBER、MIN_ONNXRUNTIME_VERSION、Python/C# 插件包)构成同一套版本治理体系:DLL 内自报的版本和最低宿主版本,与发布物元数据保持一致。
七、README “Missing parts” 的现状盘点
README 最后列出了两项尚未完成的能力,结合当前源码可以做如下对照:
- WebGPU 清理的入口:静态库下依赖
OrtEnv::~OrtEnv()调用webgpu::CleanupWebGpuContexts();动态库下该职责已由ReleaseEpFactory的五步清理链承接(见 3.2 节)。从源码结构看,动态库路径的清理问题已基本闭环,README 该条目反映的是插件化早期状态。 - WebGPU “默认配置”的设置途径:README 希望在 ORT C API 上提供类似
SetCurrentGpuDeviceId的全局状态入口,并进一步泛化为:
ORT_API2_STATUS(SetEpDefaultConfig, _In_ const char* ep_name, _In_ const char* key, _In_ const char* value);该 API 截至目前未在仓库中出现。当前的替代手段是:通过 session options 配置项(在CreateEpImpl中从OrtSessionOptions读取并灌入ConfigOptions)以及环境变量ORT_WEBGPU_EP_ALLOW_SOFTWARE_ADAPTER、环境配置项allow_virtual_devices来控制插件行为。换言之,面向“进程级默认配置”的通用 API 仍是 WebGPU EP ABI 适配层待补齐的一块。
八、小结
onnxruntime/core/providers/webgpu/ep/ 这套 EP ABI 适配器用极小的对外接口面(CreateEpFactories/ReleaseEpFactory两个符号 + ep.cc、factory.cc 两层桥接类)实现了 WebGPU EP 的插件化:静态库构建零改动继续走内置 EP 路径;动态库构建下,设备枚举(含软件适配器回退与虚拟设备)、内存分配、数据布局协商、运行生命周期与图捕获全部经由 EP ABI 函数表转发到既有的WebGuExecutionProvider内部实现(WebGpuExecutionProvider)。版本兼容性由MIN_ONNXRUNTIME_VERSION单一事实来源 + 编译期宏注入在加载期强制校验。对需要自行构建或排查 WebGPU 插件行为的开发者,建议按“README 设计约束 →api.cc入口 →factory.cc设备/分配器 →ep.cc函数表映射”的顺序阅读源码,即可完整还原插件从加载到执行的全链路。
【免费下载链接】onnxruntimeONNX Runtime: cross-platform, high performance ML inferencing and training accelerator项目地址: https://gitcode.com/GitHub_Trending/on/onnxruntime
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考