Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
9013904
webgpu plugin pipeline
fs-eire Mar 24, 2026
23782b9
2
fs-eire Mar 24, 2026
0a8b240
linux
fs-eire Mar 24, 2026
f7c6c34
change npm feed
fs-eire Mar 25, 2026
4639345
fix linux build 2
fs-eire Mar 25, 2026
8cd1590
include version info
fs-eire Mar 25, 2026
794c6a3
fix build tool path
fs-eire Mar 25, 2026
1492f57
fix linux build 3
fs-eire Mar 25, 2026
ad6d885
exclude build tools from code sign
fs-eire Mar 25, 2026
781627e
dxc copy
fs-eire Mar 25, 2026
7334ea9
publish U package
fs-eire Mar 25, 2026
fe278cd
validate params
fs-eire Mar 25, 2026
e4f654d
use version string to detect ORT API version
fs-eire Mar 28, 2026
f6d38f5
update packaging pipeline
fs-eire Mar 28, 2026
113c823
fix build
fs-eire Mar 28, 2026
1b3cee6
resolve comments
fs-eire Mar 31, 2026
9037880
Merge remote-tracking branch 'origin/main' into fs-eire/webgpu-plugin…
fs-eire Mar 31, 2026
2a1ffff
fix build flag for arm64
fs-eire Apr 1, 2026
31c4f4c
apply fix according to comments
fs-eire Apr 1, 2026
4b97ea1
save current ort api version
fs-eire Apr 1, 2026
85eb070
resolve comments
fs-eire Apr 1, 2026
04278d9
fix build
fs-eire Apr 1, 2026
a81e76d
resolve comments
fs-eire Apr 1, 2026
4d8a762
Merge remote-tracking branch 'origin/main' into fs-eire/webgpu-plugin…
fs-eire Apr 2, 2026
ea3aa72
resolve comments
fs-eire Apr 2, 2026
92bfc30
fix build
fs-eire Apr 4, 2026
1214dd2
suppress MSVC ICE
fs-eire Apr 4, 2026
70174de
Address PR review feedback: null checks, binary verification, and nits
fs-eire Apr 4, 2026
1c90606
Add DXC hash verification and fail on RC versioning
fs-eire Apr 4, 2026
1b868f3
Update tools/ci_build/github/linux/build_webgpu_plugin_package.sh
fs-eire Apr 6, 2026
e0a4ce8
sign dxc dlls
fs-eire Apr 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions cmake/onnxruntime_providers_webgpu.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,14 @@
add_definitions("-DONNX_NAMESPACE=onnx")
add_definitions("-DONNX_USE_LITE_PROTO=1")

# Default plugin EP version to ORT_VERSION with "-dev" suffix if not explicitly provided.
if(NOT DEFINED onnxruntime_PLUGIN_EP_VERSION)
set(onnxruntime_PLUGIN_EP_VERSION "${ORT_VERSION}-dev")
endif()

# Set preprocessor definition for plugin EP version
target_compile_definitions(onnxruntime_providers_webgpu PRIVATE ORT_PLUGIN_EP_VERSION="${onnxruntime_PLUGIN_EP_VERSION}")
Comment thread
fs-eire marked this conversation as resolved.

# Set preprocessor definitions used in onnxruntime_providers_webgpu.rc
if(WIN32)
set(WEBGPU_DLL_FILE_DESCRIPTION "ONNX Runtime WebGPU Provider")
Expand Down
56 changes: 54 additions & 2 deletions include/onnxruntime/ep/api.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,35 @@

namespace detail {
inline std::optional<ApiPtrs> g_api_ptrs;

inline bool TryGetAPIVersionFromVersionString(const char* version_str, uint32_t& api_version) {
// A valid version string should always be in the format of "1.{API_VERSION}.*".
if (!version_str || version_str[0] != '1' || version_str[1] != '.') {
return false;
}
const char* p = version_str + 2;
if (*p < '0' || *p > '9') {
return false;
}
uint32_t version = 0;
constexpr uint32_t kMaxBeforeMultiply = UINT32_MAX / 10;
while (*p >= '0' && *p <= '9') {
uint32_t digit = static_cast<uint32_t>(*p - '0');
if (version > kMaxBeforeMultiply || (version == kMaxBeforeMultiply && digit > UINT32_MAX % 10)) {
return false;
}
version = version * 10 + digit;
++p;
}
Comment thread
fs-eire marked this conversation as resolved.
Outdated
if (*p != '.' && *p != '\0') {
return false;
}
api_version = version;
return true;
Comment thread
fs-eire marked this conversation as resolved.
}

} // namespace detail

/// <summary>
/// Get the global instance of ApiPtrs.
/// </summary>
Expand All @@ -45,10 +72,35 @@
inline void ApiInit(const OrtApiBase* ort_api_base) {
static std::once_flag init_flag;
std::call_once(init_flag, [&]() {
// Manual init for the C++ API
const OrtApi* ort_api = ort_api_base->GetApi(ORT_API_VERSION);
// The following initialization process is composed of 3 steps:
// 1) Get the ORT API version string
// 2) Try to parse the ORT API version from the version string. If parsing fails, we assume the version is 24.
// 3) Get the ORT API for the parsed version and initialize the global API instance with it.
constexpr uint32_t ORT_BASE_API_VERSION = 24;
const char* version_str = ort_api_base->GetVersionString();
if (!version_str) {
version_str = "unknown";
}
uint32_t current_ort_version = 0;
Comment thread
fs-eire marked this conversation as resolved.
if (!detail::TryGetAPIVersionFromVersionString(version_str, current_ort_version)) {
// If we fail to parse the version string, we can still try to get the API for the base version and hope it works.
current_ort_version = ORT_BASE_API_VERSION;
}
if (current_ort_version < ORT_BASE_API_VERSION) {
throw std::runtime_error("Failed to initialize EP API: the minimum required ORT API version is " + std::to_string(ORT_BASE_API_VERSION) +
", but the current version is \"" + version_str +
"\" (parsed API version: " + std::to_string(current_ort_version) + ").");
}

const OrtApi* ort_api = ort_api_base->GetApi(current_ort_version);
if (!ort_api) {
throw std::runtime_error("Failed to initialize EP API: the current ORT version is \"" + std::string(version_str) +

Check warning on line 97 in include/onnxruntime/ep/api.h

View workflow job for this annotation

GitHub Actions / Optional Lint C++

[cpplint] reported by reviewdog 🐶 Add #include <string> for string [build/include_what_you_use] [4] Raw Output: include/onnxruntime/ep/api.h:97: Add #include <string> for string [build/include_what_you_use] [4]
"\" but it does not support the parsed API version " + std::to_string(current_ort_version) + ".");
}
const OrtEpApi* ep_api = ort_api->GetEpApi();
const OrtModelEditorApi* model_editor_api = ort_api->GetModelEditorApi();
Comment thread
fs-eire marked this conversation as resolved.

// Manual init for the C++ API
Ort::InitApi(ort_api);

// Initialize the global API instance
Expand Down
2 changes: 1 addition & 1 deletion onnxruntime/core/providers/webgpu/ep/factory.cc
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ uint32_t ORT_API_CALL Factory::GetVendorIdImpl(const OrtEpFactory* /*this_ptr*/)
}

const char* ORT_API_CALL Factory::GetVersionImpl(const OrtEpFactory* /*this_ptr*/) noexcept {
return "0.1.0";
return ORT_PLUGIN_EP_VERSION;
}

OrtStatus* ORT_API_CALL Factory::GetSupportedDevicesImpl(
Expand Down
30 changes: 30 additions & 0 deletions onnxruntime/core/session/onnxruntime_c_api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4829,7 +4829,37 @@ ORT_API(const OrtApi*, OrtApis::GetApi, uint32_t version) {
return nullptr; // Unsupported version
}

namespace {
consteval bool IsOrtVersionValid() {
// This is a consteval function to validate the format of ORT_VERSION at compile time.
// It should be in the format "X.Y.Z" where X == 1, Y and Z are non-negative integers.
std::string_view version(ORT_VERSION);
size_t first_dot = version.find('.');
if (first_dot == std::string_view::npos || first_dot == 0 || first_dot == version.size() - 1) {
return false; // Must have two dots and cannot start or end with a dot
}
size_t second_dot = version.find('.', first_dot + 1);
if (second_dot == std::string_view::npos || second_dot == first_dot + 1 || second_dot == version.size() - 1) {
return false; // Must have two dots and cannot be adjacent or end with a dot
}
std::string_view major = version.substr(0, first_dot);
std::string_view minor = version.substr(first_dot + 1, second_dot - first_dot - 1);
std::string_view patch = version.substr(second_dot + 1);
if (major != "1") {
return false; // Major version must be 1
}
auto is_non_negative_integer = [](std::string_view str) {
return !str.empty() && std::all_of(str.begin(), str.end(), [](char c) { return c >= '0' && c <= '9'; });
Comment thread
fs-eire marked this conversation as resolved.
Outdated
};
if (!is_non_negative_integer(minor) || !is_non_negative_integer(patch)) {
return false; // Minor and patch versions must be non-negative integers
}
return true;
}
} // namespace

ORT_API(const char*, OrtApis::GetVersionString) {
static_assert(IsOrtVersionValid(), "ORT_VERSION must be in the format '1.Y.Z' where Y and Z are non-negative integers");
Comment thread
edgchen1 marked this conversation as resolved.
Outdated
return ORT_VERSION;
}

Expand Down
117 changes: 117 additions & 0 deletions tools/ci_build/github/azure-pipelines/plugin-webgpu-pipeline.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
trigger: none

resources:
repositories:
- repository: 1esPipelines
type: git
name: 1ESPipelineTemplates/1ESPipelineTemplates
ref: refs/tags/release

parameters:
- name: build_windows_x64
displayName: 'Build Windows x64'
type: boolean
default: true

- name: build_windows_arm64
displayName: 'Build Windows ARM64'
type: boolean
default: false

- name: build_linux_x64
displayName: 'Build Linux x64'
type: boolean
default: false

- name: build_macos_arm64
displayName: 'Build macOS ARM64'
type: boolean
default: false

- name: package_version
displayName: 'Package Version'
type: string
values:
- release
- RC
- dev
default: dev

- name: cmake_build_type
type: string
default: 'Release'
values:
- Debug
- Release
- RelWithDebInfo
- MinSizeRel

variables:
# Windows ARM64 build requires Windows x64 build to be enabled (ARM64 cross-compilation depends on x64 build artifacts)
- name: invalidARM64Config
value: ${{ and(eq(parameters.build_windows_arm64, true), eq(parameters.build_windows_x64, false)) }}
# Non-dev package versions (release, RC) must use Release build type
- name: invalidBuildTypeConfig
value: ${{ and(ne(parameters.package_version, 'dev'), ne(parameters.cmake_build_type, 'Release')) }}

extends:
template: v1/1ES.Official.PipelineTemplate.yml@1esPipelines
parameters:
settings:
networkIsolationPolicy: Permissive
sdl:
componentgovernance:
ignoreDirectories: '$(Build.Repository.LocalPath)/cmake/external/emsdk/upstream/emscripten/tests,$(Build.Repository.LocalPath)/cmake/external/onnx/third_party/benchmark,$(Build.Repository.LocalPath)/cmake/external/onnx/third_party/pybind11,$(Build.Repository.LocalPath)/cmake/external/onnx/third_party/pybind11/tests,$(Build.Repository.LocalPath)/cmake/external/onnxruntime-extensions,$(Build.Repository.LocalPath)/js/react_native/e2e/node_modules,$(Build.Repository.LocalPath)/js/node_modules,$(Build.Repository.LocalPath)/onnxruntime-inference-examples,$(Build.SourcesDirectory)/cmake/external/emsdk/upstream/emscripten/tests,$(Build.SourcesDirectory)/cmake/external/onnx/third_party/benchmark,$(Build.SourcesDirectory)/cmake/external/onnx/third_party/pybind11,$(Build.SourcesDirectory)/cmake/external/onnx/third_party/pybind11/tests,$(Build.SourcesDirectory)/cmake/external/onnxruntime-extensions,$(Build.SourcesDirectory)/js/react_native/e2e/node_modules,$(Build.SourcesDirectory)/js/node_modules,$(Build.SourcesDirectory)/onnxruntime-inference-examples,$(Build.BinariesDirectory)'
alertWarningLevel: High
failOnAlert: false
verbosity: Normal
timeout: 3600
tsa:
enabled: true
codeSignValidation:
enabled: true
break: true
policheck:
enabled: true
exclusionsFile: '$(Build.SourcesDirectory)\tools\ci_build\policheck_exclusions.xml'
codeql:
compiled:
enabled: false
justificationForDisabling: 'CodeQL is taking nearly 6 hours resulting in timeouts in our production pipelines'
pool:
name: 'onnxruntime-Win-CPU-VS2022-Latest'
os: windows

stages:
# Validate parameter combinations
- ${{ if or(eq(variables['invalidARM64Config'], 'True'), eq(variables['invalidBuildTypeConfig'], 'True')) }}:
- stage: Validate_Parameters
displayName: 'Validate Parameters'
dependsOn: []
jobs:
- job: Validate
displayName: 'Validate parameter combinations'
pool:
name: 'onnxruntime-Win-CPU-VS2022-Latest'
os: windows
steps:
- checkout: none
- ${{ if eq(variables['invalidARM64Config'], 'True') }}:
- script: |
echo "##vso[task.logissue type=error]Windows ARM64 build requires Windows x64 build to be enabled."
exit 1
displayName: 'ERROR: Windows ARM64 requires Windows x64'
- ${{ if eq(variables['invalidBuildTypeConfig'], 'True') }}:
- script: |
echo "##vso[task.logissue type=error]Non-dev package version requires Release build type."
exit 1
displayName: 'ERROR: Non-dev package version requires Release build type'
- ${{ else }}:
- template: stages/plugin-webgpu-packaging-stage.yml
parameters:
build_windows_x64: ${{ parameters.build_windows_x64 }}
build_windows_arm64: ${{ parameters.build_windows_arm64 }}
build_linux_x64: ${{ parameters.build_linux_x64 }}
build_macos_arm64: ${{ parameters.build_macos_arm64 }}
package_version: ${{ parameters.package_version }}
cmake_build_type: ${{ parameters.cmake_build_type }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
parameters:
- name: machine_pool
type: string
default: 'onnxruntime-Ubuntu2404-AMD-CPU'

- name: package_version
type: string
default: dev

- name: cmake_build_type
type: string
default: 'Release'
values:
- Debug
- Release
- RelWithDebInfo
- MinSizeRel

- name: docker_base_image
type: string
default: 'onnxruntimebuildcache.azurecr.io/internal/azureml/onnxruntime/build/cuda12_x64_almalinux8_gcc14:20251017.1'

stages:
- stage: Linux_plugin_webgpu_x64_Build
dependsOn: []
jobs:
- job: Linux_plugin_webgpu_x64_Build
timeoutInMinutes: 240
workspace:
clean: all
pool:
name: ${{ parameters.machine_pool }}
os: linux
templateContext:
outputs:
- output: pipelineArtifact
targetPath: $(Build.ArtifactStagingDirectory)
artifactName: webgpu_plugin_linux_x64
variables:
- template: ../templates/common-variables.yml
steps:
- checkout: self
clean: true
submodules: recursive

- template: ../templates/set-nightly-build-option-variable-step.yml

- template: ../templates/set-plugin-build-variables-step.yml
parameters:
package_version: ${{ parameters.package_version }}

- template: ../templates/get-docker-image-steps.yml
parameters:
Dockerfile: tools/ci_build/github/linux/docker/inference/x86_64/python/cuda/Dockerfile
Context: tools/ci_build/github/linux/docker/inference/x86_64/python/cuda
DockerBuildArgs: "--build-arg BASEIMAGE=${{ parameters.docker_base_image }} --build-arg BUILD_UID=$( id -u )"
Repository: onnxruntimewebgpuplugin

- script: $(Build.SourcesDirectory)/tools/ci_build/github/linux/build_webgpu_plugin_package.sh -i onnxruntimewebgpuplugin -c ${{ parameters.cmake_build_type }}
workingDirectory: $(Build.SourcesDirectory)
displayName: 'Build WebGPU Plugin'
env:
EXTRA_CMAKE_DEFINES: $(PluginEpVersionDefine)

- script: |
set -e -x
mkdir -p $(Build.ArtifactStagingDirectory)/bin
plugin_path="$(Build.BinariesDirectory)/${{ parameters.cmake_build_type }}/libonnxruntime_providers_webgpu.so"
if [ ! -f "$plugin_path" ]; then
echo "Error: Expected plugin binary not found at '$plugin_path'. Failing build to avoid publishing an invalid package."
exit 1
fi
cp "$plugin_path" "$(Build.ArtifactStagingDirectory)/bin/"
displayName: 'Copy plugin binaries'

- script: |
set -e -x
mkdir -p "$(Build.ArtifactStagingDirectory)/version"
touch "$(Build.ArtifactStagingDirectory)/version/$(PluginPackageVersion)"
displayName: 'Create version marker file'

- script: |
set -e -x
mkdir -p "$(Build.BinariesDirectory)/universal_package"
cp -R "$(Build.ArtifactStagingDirectory)/bin/"* "$(Build.BinariesDirectory)/universal_package/"
displayName: 'Stage binaries for universal package'

- task: UniversalPackages@0
displayName: 'Publish universal package'
inputs:
command: publish
publishDirectory: '$(Build.BinariesDirectory)/universal_package'
vstsFeedPublish: 'PublicPackages/ORT-Nightly'
vstsFeedPackagePublish: 'onnxruntime-plugin-ep-webgpu-linux-x64'
versionOption: custom
versionPublish: '$(PluginUniversalPackageVersion)'
Loading
Loading