Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
120 changes: 58 additions & 62 deletions sdk_v2/cpp/src/ep_detection/webgpu_ep_bootstrapper.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,22 @@
#include "ep_detection/webgpu_ep_bootstrapper.h"

#include "ep_detection/ep_utils.h"
#include "http/http_client.h"
#include "http/http_download.h"
#include "logger.h"
#include "util/file_lock.h"
#include "util/sha256.h"
#include "utils.h"
#include "util/zip_extract.h"

#include <fmt/format.h>
#include <nlohmann/json.hpp>

#include <algorithm>
#include <atomic>
#include <cctype>
#include <cstdlib>
#include <filesystem>
#include <stdexcept>
#include <string>
#include <string_view>
#include <unordered_map>

Comment thread
prathikr marked this conversation as resolved.
namespace {

Expand All @@ -29,11 +28,32 @@ constexpr const char* kStagingDirName = "webgpu-ep-staging";
constexpr const char* kUserAgent = "FoundryLocal";
constexpr int kMaxInstallAttempts = 5;

// Manifest URL — always uses prod.
constexpr const char* kManifestUrl =
"https://foundrypackages-ffhrdhbxb7gpdreh.b02.azurefd.net/webgpu_ep_prod.json";
struct WebGpuPackageMetadata {
const char* download_url;
const char* provider_sha256;
};

const std::unordered_map<std::string_view, WebGpuPackageMetadata> kPackageMetadataByPlatform = {
{"win-arm64",
{"https://foundrypackages-ffhrdhbxb7gpdreh.b02.azurefd.net/webgpu_ep_0.1.0_win-arm64.zip",
"C4A77911BDBFC6E2870D1895DA3F5BE476CE3398D772C02AFDDD2B2C49C66659"}
},
{"win-x64",
{"https://foundrypackages-ffhrdhbxb7gpdreh.b02.azurefd.net/webgpu_ep_0.1.0_win-x64.zip",
"591E286A211B133E3C4E5C833FEBF2D594B7B548433A2490407B11B906A9271B"}
},
{"macos-arm64",
{"https://foundrypackages-ffhrdhbxb7gpdreh.b02.azurefd.net/webgpu_ep_0.1.0_macos-arm64.zip",
"A08BCEBE097B555E23938FCC71A5FAAD461F586CAB0B63DC9D21E970F6CA4C87"}
},
{"linux-x64",
{"https://foundrypackages-ffhrdhbxb7gpdreh.b02.azurefd.net/webgpu_ep_0.1.0_linux-x64.zip",
"CBDFF74E6569E3CF66B46F0D194D87CD3CF49E83B7AA46552C39B0218D58B215"}
},
};

// Platform key used to look up this platform's package in the manifest.
// Platform-specific package metadata is baked into the binary to keep
// verification inputs fixed at build time.
#if defined(_WIN32) && defined(_M_ARM64)
constexpr const char* kPlatformKey = "win-arm64";
#elif defined(_WIN32)
Expand All @@ -44,6 +64,17 @@ constexpr const char* kPlatformKey = "macos-arm64";
constexpr const char* kPlatformKey = "linux-x64";
#endif

const WebGpuPackageMetadata& GetPackageMetadata() {
auto it = kPackageMetadataByPlatform.find(kPlatformKey);

if (it == kPackageMetadataByPlatform.end()) {
throw std::runtime_error(
fmt::format("WebGPU EP: no package metadata configured for platform '{}'", kPlatformKey));
}

return it->second;
}

// Platform-specific EP library filename.
#if defined(_WIN32)
constexpr const char* kWebGpuProviderLib = "onnxruntime_providers_webgpu.dll";
Expand All @@ -56,51 +87,6 @@ constexpr const char* kWebGpuProviderLib = "libonnxruntime_providers_webgpu.so";
constexpr const char* kRegistrationName = "Foundry.WebGPU";
constexpr const char* kWebGpuProviderOverrideEnv = "FOUNDRY_LOCAL_WEBGPU_EP_LIBRARY";

/// Parsed manifest entry for a single platform.
struct ManifestPackageInfo {
std::string url;
std::string sha256; // expected SHA256 hash of kWebGpuProviderLib
};

/// Fetch the manifest JSON from CDN and extract the package info for this platform.
ManifestPackageInfo FetchManifest(fl::ILogger& logger) {
logger.Log(fl::LogLevel::Debug, fmt::format("WebGPU EP: fetching manifest from {}", kManifestUrl));

fl::http::HttpRequestOptions options;
options.user_agent = kUserAgent;
auto body = fl::http::HttpGetWithRetry(kManifestUrl, logger, options);
auto manifest = nlohmann::json::parse(body);

if (!manifest.contains("packages") || !manifest["packages"].is_object()) {
throw std::runtime_error(
fmt::format("WebGPU EP: manifest is invalid — missing 'packages' field. "
"Raw content (first 200 chars): {}",
body.substr(0, 200)));
}

const auto& packages = manifest["packages"];
if (!packages.contains(kPlatformKey)) {
std::string available;
for (auto it = packages.begin(); it != packages.end(); ++it) {
if (!available.empty()) available += ", ";
available += it.key();
}
throw std::runtime_error(
fmt::format("WebGPU EP: manifest does not contain a package for platform '{}'. "
"Available platforms: {}",
kPlatformKey, available));
}

const auto& pkg = packages[kPlatformKey];
ManifestPackageInfo info;
info.url = pkg.at("url").get<std::string>();
info.sha256 = pkg.at("sha256").at(kWebGpuProviderLib).get<std::string>();

logger.Log(fl::LogLevel::Information,
fmt::format("WebGPU EP: manifest fetched for platform '{}'", kPlatformKey));
return info;
}

} // anonymous namespace

namespace fl {
Expand Down Expand Up @@ -137,6 +123,7 @@ bool WebGpuEpBootstrapper::DownloadAndRegister(bool force,
auto parent_dir = ep_dir.parent_path();

try {
const auto& package_metadata = GetPackageMetadata();
auto override_path = Utils::GetEnv(kWebGpuProviderOverrideEnv);
if (override_path.has_value() && !override_path->empty()) {
std::filesystem::path provider_path(*override_path);
Expand Down Expand Up @@ -175,12 +162,13 @@ bool WebGpuEpBootstrapper::DownloadAndRegister(bool force,
return true;
}

// Fetch manifest before acquiring lock (avoid holding lock during network I/O)
auto manifest = FetchManifest(logger);

// Check if package already exists and is valid
if (!force && VerifyEpPackage(ep_dir, {{kWebGpuProviderLib, manifest.sha256}}, "WebGPU EP", logger)) {
logger.Log(LogLevel::Debug, "WebGPU EP: local binaries match manifest, skipping download");
if (!force && VerifyEpPackage(
ep_dir,
{{kWebGpuProviderLib, package_metadata.provider_sha256}},
"WebGPU EP",
logger)) {
logger.Log(LogLevel::Debug, "WebGPU EP: local binaries match expected hashes, skipping download");
} else {
// Ensure parent directory exists for the lock file
std::filesystem::create_directories(parent_dir);
Expand All @@ -190,7 +178,11 @@ bool WebGpuEpBootstrapper::DownloadAndRegister(bool force,
FileLock lock(lock_path);

// Re-check after acquiring lock (another process may have completed the update)
if (!force && VerifyEpPackage(ep_dir, {{kWebGpuProviderLib, manifest.sha256}}, "WebGPU EP", logger)) {
if (!force && VerifyEpPackage(
ep_dir,
{{kWebGpuProviderLib, package_metadata.provider_sha256}},
"WebGPU EP",
logger)) {
logger.Log(LogLevel::Debug, "WebGPU EP: another process already completed the update");
} else {
// Download and extract to staging directory for atomic swap
Expand All @@ -206,7 +198,7 @@ bool WebGpuEpBootstrapper::DownloadAndRegister(bool force,
logger.Log(LogLevel::Information,
fmt::format("WebGPU EP: downloading for {} from CDN", kPlatformKey));
logger.Log(LogLevel::Debug,
fmt::format("WebGPU EP: download URL is {}", manifest.url));
fmt::format("WebGPU EP: download URL is {}", package_metadata.download_url));

std::atomic<bool> cancel_flag{false};
auto download_progress = [&](float pct) {
Expand All @@ -218,7 +210,7 @@ bool WebGpuEpBootstrapper::DownloadAndRegister(bool force,
}
};

if (!HttpDownloadFile(manifest.url, zip_path, kUserAgent,
if (!HttpDownloadFile(package_metadata.download_url, zip_path, kUserAgent,
&cancel_flag, download_progress, logger)) {
logger.Log(LogLevel::Warning, "WebGPU EP: download failed (see prior log for details)");
return false;
Expand All @@ -238,7 +230,11 @@ bool WebGpuEpBootstrapper::DownloadAndRegister(bool force,
std::filesystem::remove(zip_path);

// Verify staging
if (!VerifyEpPackage(staging_dir, {{kWebGpuProviderLib, manifest.sha256}}, "WebGPU EP", logger)) {
if (!VerifyEpPackage(
staging_dir,
{{kWebGpuProviderLib, package_metadata.provider_sha256}},
"WebGPU EP",
logger)) {
logger.Log(LogLevel::Warning,
fmt::format("WebGPU EP: verification failed after extraction (attempt {})",
attempts_));
Expand Down
6 changes: 2 additions & 4 deletions sdk_v2/cpp/src/ep_detection/webgpu_ep_bootstrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@ class ILogger;

/// Bootstrapper for the WebGPU execution provider.
///
/// Fetches a manifest from Azure CDN to discover the current WebGPU EP
/// package URL and expected SHA-256 hash, downloads the binary, verifies
/// integrity, then registers with ORT. The manifest-driven approach allows
/// updating WebGPU EP binaries without shipping a new Foundry Local release.
/// Uses platform-specific package metadata (download URL and SHA-256 hash),
/// downloads the binary, verifies integrity, then registers with ORT.
///
/// Supports Windows x64/ARM64, Linux x64, and macOS ARM64.
class WebGpuEpBootstrapper : public IEpBootstrapper {
Expand Down
19 changes: 19 additions & 0 deletions sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,16 @@ void CALLBACK EnsureReadyProgressThunk(WinMLAsyncBlock* async, double progress)
}
}

constexpr const char* kWinMLWebGpuProviderName = "WebGpuExecutionProvider";

bool IsWinMLWebGpuProvider(const WinMLEpInfo* info) {
if (!info || !info->name) {
return false;
}

return info->name == std::string{kWinMLWebGpuProviderName};
}

} // namespace

WinMLEpBootstrapper::WinMLEpBootstrapper(std::string name, EpRegistrationCallback register_ep,
Expand Down Expand Up @@ -286,6 +296,15 @@ std::vector<std::unique_ptr<WinMLEpBootstrapper>> WinMLEpBootstrapper::DiscoverP
[](WinMLEpHandle ep, const WinMLEpInfo* info, void* context) -> BOOL {
auto* ctx = static_cast<EnumContext*>(context);

if (IsWinMLWebGpuProvider(info)) {
std::string provider_name = (info && info->name) ? info->name : "";
ctx->logger->Log(
LogLevel::Information,
fmt::format("WinML EP filtered out: {} (reason: prefer Foundry Local WebGPU EP)",
provider_name));
return TRUE; // continue enumeration
}

std::string provider_name = info->name ? info->name : "";

ctx->logger->Log(
Expand Down