From 86a6c573e4259f51efdd2513b2dd4911226260ec Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Thu, 4 Jun 2026 00:06:08 -0500 Subject: [PATCH 01/35] Migrate WinML EP downloads to WinML 2.x (reg-free runtime) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switch from `Microsoft.WindowsAppSDK.ML` 1.8.2192 (WinML 1.x, WinAppSDK bootstrap required, Win11 24H2+) to `Microsoft.Windows.AI.MachineLearning` 2.1.6 (WinML 2.x, registration-free, Win10 19H1+). This brings Foundry-Local in line with the same migration neutron-server completed. Highlights: * Remove WinAppSDK Bootstrap plumbing across all SDKs. The `Bootstrap` `additionalSettings` key — which used to flip on WinAppSDK init in the native layer — is gone in every binding (C++, C#, JS, Python). It was an internal init knob; no public surface signaled it as a stable contract. - C++: delete winml_bootstrap.{h,cc}, the WinAppSdkBootstrap link target, the Bootstrap.dll post-build copy, and the additional_options key handling in Manager::Create / Destroy. - C#: drop the `IS_WINML` default that injected Bootstrap=true. Lower TFM from net9.0-windows10.0.26100.0 to net9.0-windows10.0.18362.0. - JS: delete applyBootstrapAutoDetect and its test; drop Bootstrap.dll from copy-native script. - Python: drop `Bootstrap=false` examples from README and integration test. * Unify ORT to 1.25.1 / OnnxRuntimeGenAI to 0.13.2 for both the WinML and non-WinML flavors. Delete sdk_v2/deps_versions_winml.json and the `FOUNDRY_LOCAL_USE_WINML` branch in FindOnnxRuntime.cmake / FindOnnxRuntimeGenAI.cmake / the python build backend. * Collapse cuda_ep_bootstrapper.cc to the single ORT-1.25.1 URL / binary set (previously branched on WinML for the older 1.23.2 build). * Drop the WebGPU-EP skip on WinML builds (both flavors now satisfy ORT API >= 24). * Drop the IsWindows11_24H2OrLater() runtime guard in winml_ep_bootstrapper.cc; LoadLibraryW is a sufficient probe. * Gate `find_package(WinMLEpCatalog)` behind `FOUNDRY_LOCAL_USE_WINML` so non-WinML builds don't silently link the catalog DLL. * CMake/cmake-modules: update DLL path `runtimes-framework//native/` -> `runtimes//native/` and bump default `WINML_EP_CATALOG_VERSION` to 2.1.6. * Pipelines: bump `cppWinmlVersion` to 2.1.6, drop `cppOrtVersionWinml` and the entire `Microsoft.WindowsAppSDK.Foundation` resolution + Bootstrap.dll staging in steps-prefetch-nuget.yml / steps-build-windows.yml. * Nuget pack: replace `Microsoft.WindowsAppRuntime.Bootstrap.dll` with `Microsoft.Windows.AI.MachineLearning.dll` in OPTIONAL_SIBLINGS. * Bump gtest `DISCOVERY_TIMEOUT` to 60 for foundry_local_tests and cache_only_tests; WinML's delay-loaded DLL resolution + static initializers can exceed the 5 s default during test discovery. Verified: * `python sdk_v2/cpp/build.py --config RelWithDebInfo --skip_examples` (non-WinML) builds clean; 820 unit/cache tests pass. * `python sdk_v2/cpp/build.py --config RelWithDebInfo --skip_examples --use_winml` builds clean; FindWinMLEpCatalog.cmake downloads Microsoft.Windows.AI.MachineLearning 2.1.6 from nuget.org; Microsoft.Windows.AI.MachineLearning.dll is co-located with foundry_local.dll in the build output. * Targeted ctest run on the WinML build: `ctest -R "EpDetector|WinML|ManagerWebServiceTest|CacheOnlyTest"` -> 18/18 pass. Out of scope (deferred): WebGPU manifest-based granular updates; adopting WinML's bundled onnxruntime.dll. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .pipelines/foundry-local-packaging.yml | 12 +- .pipelines/v2/sdk_v2-pipeline-plan.md | 41 ++-- .../v2/templates/stages-build-native.yml | 8 +- .pipelines/v2/templates/stages-sdk-v2.yml | 3 - .../v2/templates/steps-build-windows.yml | 27 +-- .../v2/templates/steps-prefetch-nuget.yml | 46 ++-- sdk_v2/cpp/CMakeLists.txt | 39 +--- sdk_v2/cpp/build.py | 8 +- sdk_v2/cpp/cmake/FindOnnxRuntime.cmake | 21 +- sdk_v2/cpp/cmake/FindOnnxRuntimeGenAI.cmake | 26 +-- sdk_v2/cpp/cmake/FindWinMLEpCatalog.cmake | 209 ++++++------------ sdk_v2/cpp/docs/CppPortGuide.md | 2 +- sdk_v2/cpp/docs/EpDetectionPlan.md | 37 ++-- sdk_v2/cpp/docs/MigrationPlan_20260410.md | 3 +- sdk_v2/cpp/nuget/pack.py | 25 ++- .../src/ep_detection/cuda_ep_bootstrapper.cc | 22 +- .../src/ep_detection/winml_ep_bootstrapper.cc | 47 ---- sdk_v2/cpp/src/manager.cc | 35 +-- sdk_v2/cpp/src/winml_bootstrap.cc | 76 ------- sdk_v2/cpp/src/winml_bootstrap.h | 37 ---- sdk_v2/cpp/test/CMakeLists.txt | 6 + sdk_v2/cpp/test/sdk_api/ep_detection_test.cc | 2 +- sdk_v2/cs/src/FoundryLocalManager.cs | 12 +- .../cs/src/Microsoft.AI.Foundry.Local.csproj | 3 +- sdk_v2/deps_versions_winml.json | 5 - sdk_v2/js/script/copy-native.mjs | 3 +- sdk_v2/js/src/foundryLocalManager.ts | 35 +-- sdk_v2/js/test/bootstrap-autodetect.test.ts | 75 ------- sdk_v2/python/README.md | 1 - sdk_v2/python/_build_backend/__init__.py | 13 +- sdk_v2/python/pyproject.toml | 8 +- .../integration/test_configuration_native.py | 2 +- 32 files changed, 212 insertions(+), 677 deletions(-) delete mode 100644 sdk_v2/cpp/src/winml_bootstrap.cc delete mode 100644 sdk_v2/cpp/src/winml_bootstrap.h delete mode 100644 sdk_v2/deps_versions_winml.json delete mode 100644 sdk_v2/js/test/bootstrap-autodetect.test.ts diff --git a/.pipelines/foundry-local-packaging.yml b/.pipelines/foundry-local-packaging.yml index e67dc61c6..decdbb837 100644 --- a/.pipelines/foundry-local-packaging.yml +++ b/.pipelines/foundry-local-packaging.yml @@ -54,17 +54,16 @@ parameters: variables: - group: FoundryLocal-ESRP-Signing # C++ SDK (sdk_v2/cpp) native dependency versions. Must match cmake defaults -# in sdk_v2/deps_versions.json and sdk_v2/deps_versions_winml.json. +# in sdk_v2/deps_versions.json. WinML and non-WinML builds now share a single +# ORT line (WinML 2.x is reg-free and uses the standard ORT package). - name: cppOrtVersion value: '1.25.1' -- name: cppOrtVersionWinml - # Pinned to the WinML-aligned ORT line so foundry_local.dll's ORT ABI matches - # the WinML EP catalog plugins it loads. See FindOnnxRuntime.cmake. - value: '1.23.2.3' - name: cppGenaiVersion value: '0.13.2' - name: cppWinmlVersion - value: '1.8.2192' + # Microsoft.Windows.AI.MachineLearning (WinML 2.x). Reg-free, no Windows App + # SDK bootstrap, supports Windows 10 19H1 (build 18362) and later. + value: '2.1.6' - name: cppBuildConfig value: 'RelWithDebInfo' @@ -269,6 +268,5 @@ extends: parameters: buildConfig: $(cppBuildConfig) ortVersion: $(cppOrtVersion) - ortVersionWinml: $(cppOrtVersionWinml) genaiVersion: $(cppGenaiVersion) winmlVersion: $(cppWinmlVersion) diff --git a/.pipelines/v2/sdk_v2-pipeline-plan.md b/.pipelines/v2/sdk_v2-pipeline-plan.md index 68e920d9f..d52ff16af 100644 --- a/.pipelines/v2/sdk_v2-pipeline-plan.md +++ b/.pipelines/v2/sdk_v2-pipeline-plan.md @@ -68,9 +68,10 @@ are gated separately via `.pipelines/v1/templates/stages-sdk-v1.yml`. `foundry-local-sdk-winml` when `FL_PYTHON_PACKAGE_NAME` is set in the environment. The same backend also handles ORT pin rewriting (decision 8). 8. **Single source of truth for ORT/GenAI versions.** ORT and GenAI versions - live in `sdk_v2/deps_versions.json` (standard) and - `sdk_v2/deps_versions_winml.json` (WinML). Both files have shape + live in `sdk_v2/deps_versions.json`. The file shape is `{ "onnxruntime": { "version": "..." }, "onnxruntime-genai": { "version": "..." } }`. + WinML and non-WinML builds share the same ORT pin (WinML 2.x is reg-free + and uses the standard ORT package). Consumers: - **C++ build:** `sdk_v2/cpp/cmake/Find{OnnxRuntime,OnnxRuntimeGenAI}.cmake` read versions via `file(READ)` + `string(JSON ... GET ...)`. The @@ -175,15 +176,13 @@ compute_version * All build stages are independent (`dependsOn: [compute_version]`) and run in parallel. * Both pack stages run on every build (PR and `main`). -* The WinML build stages link against ORT 1.23.x (pinned via - `ortVersionWinml`) so the binary's ORT ABI matches the WinML EP catalog - plugins it loads. The base stages link against ORT 1.25.x. +* WinML and non-WinML build stages link against the same ORT version + (`ortVersion`, currently 1.25.x). WinML 2.x is reg-free and uses the + standard ORT package, so a separate WinML-aligned ORT pin is no longer + required. * Tests run on `cpp_build_win_x64`, `cpp_build_win_x64_winml`, `cpp_build_linux_x64`, and `cpp_build_osx_arm64`. The two ARM64 Windows - stages cross-compile from an x64 host and skip tests. The WinML x64 stage - runs the full suite — the C++ `VisionFixture` and the C# `VisionTests` - self-skip on the WinML-aligned ORT (older than the cataloged vision - models require), so the rest of the suite still exercises that configuration. + stages cross-compile from an x64 host and skip tests. ## Per-stage artifacts @@ -245,35 +244,31 @@ purposes: 1. **Version pinning** — the `KEY=PATH` pairs are passed via `--cmake_extra_defines` (`ORT_FETCH_URL`, `GENAI_FETCH_URL`, - `WINML_EP_CATALOG_FETCH_URL`, `WINAPPSDK_FOUNDATION_FETCH_URL`, - `ORT_GPU_LINUX_FETCH_URL`) so the cmake defaults in - `FindOnnxRuntime.cmake` / `FindOnnxRuntimeGenAI.cmake` are never - silently substituted. + `WINML_EP_CATALOG_FETCH_URL`, `ORT_GPU_LINUX_FETCH_URL`) so the cmake + defaults in `FindOnnxRuntime.cmake` / `FindOnnxRuntimeGenAI.cmake` are + never silently substituted. 2. **Stage isolation** — the build step no longer needs network access to the package feed once prefetch has completed. Versions are pipeline-level variables, currently: * `ortVersion` `1.25.1` (`Microsoft.ML.OnnxRuntime.Foundry`) -* `ortVersionWinml` `1.23.2.3` (WinML-aligned ORT line, used by the WinML build stages) * `genaiVersion` `0.13.2` (`Microsoft.ML.OnnxRuntimeGenAI.Foundry`) -* `winmlVersion` `1.8.2192` (`Microsoft.WindowsAppSDK.ML`) +* `winmlVersion` `2.1.6` (`Microsoft.Windows.AI.MachineLearning`, WinML 2.x reg-free) These must be kept in sync with the cmake defaults and with -`sdk_v2/deps_versions[_winml].json` (decision 8). When bumping, update -both places in the same PR. +`sdk_v2/deps_versions.json` (decision 8). When bumping, update both places +in the same PR. The shared download logic is in `steps-prefetch-nuget.yml` and exposes both a PowerShell (Windows) and a bash (Linux/macOS) implementation behind a `shell` parameter. It emits a single pipeline variable `cmakeFetchDefines` containing the quoted `KEY=PATH` pairs to splice into the build command. -WinML is special-cased: `Microsoft.WindowsAppSDK.ML` has a transitive -dependency on `Microsoft.WindowsAppSDK.Foundation` whose exact min version -is often unpublished, so the pwsh branch shells out to `nuget install -... -DependencyVersion Lowest` to let the resolver pick a satisfying -version, then emits fetch URLs for both `.nupkg`s. The bash branch fails -fast if `includeWinml=true` is ever passed — WinML is Windows-only. +WinML downloads `Microsoft.Windows.AI.MachineLearning` directly from +nuget.org as a single self-contained reg-free package — no transitive +WinAppSDK Foundation resolution needed. The bash branch fails fast if +`includeWinml=true` is ever passed — WinML is Windows-only. ## Test data diff --git a/.pipelines/v2/templates/stages-build-native.yml b/.pipelines/v2/templates/stages-build-native.yml index 295b9a934..09a75c33e 100644 --- a/.pipelines/v2/templates/stages-build-native.yml +++ b/.pipelines/v2/templates/stages-build-native.yml @@ -14,8 +14,6 @@ parameters: type: string - name: ortVersion type: string -- name: ortVersionWinml - type: string - name: genaiVersion type: string - name: winmlVersion @@ -181,7 +179,7 @@ stages: parameters: arch: x64 buildConfig: ${{ parameters.buildConfig }} - ortVersion: ${{ parameters.ortVersionWinml }} + ortVersion: ${{ parameters.ortVersion }} genaiVersion: ${{ parameters.genaiVersion }} winmlVersion: ${{ parameters.winmlVersion }} useWinml: true @@ -214,7 +212,7 @@ stages: parameters: arch: arm64 buildConfig: ${{ parameters.buildConfig }} - ortVersion: ${{ parameters.ortVersionWinml }} + ortVersion: ${{ parameters.ortVersion }} genaiVersion: ${{ parameters.genaiVersion }} winmlVersion: ${{ parameters.winmlVersion }} useWinml: true @@ -297,6 +295,6 @@ stages: steps: - template: steps-pack-nuget.yml parameters: - ortVersion: ${{ parameters.ortVersionWinml }} + ortVersion: ${{ parameters.ortVersion }} genaiVersion: ${{ parameters.genaiVersion }} variant: winml diff --git a/.pipelines/v2/templates/stages-sdk-v2.yml b/.pipelines/v2/templates/stages-sdk-v2.yml index c2cae47d1..5a1c59bab 100644 --- a/.pipelines/v2/templates/stages-sdk-v2.yml +++ b/.pipelines/v2/templates/stages-sdk-v2.yml @@ -16,8 +16,6 @@ parameters: default: 'RelWithDebInfo' - name: ortVersion type: string -- name: ortVersionWinml - type: string - name: genaiVersion type: string - name: winmlVersion @@ -30,7 +28,6 @@ stages: parameters: buildConfig: ${{ parameters.buildConfig }} ortVersion: ${{ parameters.ortVersion }} - ortVersionWinml: ${{ parameters.ortVersionWinml }} genaiVersion: ${{ parameters.genaiVersion }} winmlVersion: ${{ parameters.winmlVersion }} diff --git a/.pipelines/v2/templates/steps-build-windows.yml b/.pipelines/v2/templates/steps-build-windows.yml index ba2d82d79..0b2f6ecc2 100644 --- a/.pipelines/v2/templates/steps-build-windows.yml +++ b/.pipelines/v2/templates/steps-build-windows.yml @@ -9,9 +9,9 @@ # buildConfig – CMake config (Debug, Release, RelWithDebInfo, MinSizeRel) # ortVersion – Microsoft.ML.OnnxRuntime.Foundry version # genaiVersion – Microsoft.ML.OnnxRuntimeGenAI.Foundry version -# winmlVersion – Microsoft.WindowsAppSDK.ML version -# useWinml – Build the WinML variant (--use_winml). Caller is responsible -# for passing the WinML-aligned ortVersion (e.g. 1.23.2.3). +# winmlVersion – Microsoft.Windows.AI.MachineLearning version (WinML 2.x reg-free) +# useWinml – Build the WinML variant (--use_winml). WinML and non-WinML +# builds now share the same ORT version. # runTests – Whether to run tests # stageHeaders – Whether to stage public headers as a separate artifact @@ -151,17 +151,14 @@ steps: # libcurl/libssl/zlib/brotli* into foundry_local.dll (see # sdk_v2/cpp/triplets/x64-windows.cmake), so the only runtime payload is # foundry_local.dll itself. -# - WinML build: foundry_local.dll picks up one extra static import — -# Microsoft.WindowsAppRuntime.Bootstrap.dll — which is the entry point -# that calls MddBootstrapInitialize2 to register the system-installed -# Windows App Runtime. Bootstrap.dll must travel with the wheel because -# end-user / CI machines don't have it on PATH otherwise. Everything -# downstream of Bootstrap (Microsoft.Windows.AI.MachineLearning.dll, -# DirectML, the WinML-flavored ORT, etc.) is supplied by the system -# App Runtime that Bootstrap registers — Microsoft.Windows.AI.MachineLearning -# is delay-loaded (see /DELAYLOAD in CMakeLists.txt) so it is NOT needed at -# foundry_local.dll load time. ORT/GenAI come from the onnxruntime-core / -# onnxruntime-genai-core pip deps. +# - WinML build: foundry_local.dll picks up one extra runtime dependency, +# Microsoft.Windows.AI.MachineLearning.dll. WinML 2.x is reg-free, so this +# single self-contained native DLL replaces the previous WinAppSDK bootstrap +# plumbing — no MddBootstrapInitialize2 and no separate Bootstrap.dll. The +# WinML DLL is delay-loaded (see /DELAYLOAD in CMakeLists.txt) so it is +# NOT needed at foundry_local.dll load time, but the cmake post-build copy +# stages it next to foundry_local.dll for runtime EP discovery. ORT/GenAI +# come from the onnxruntime-core / onnxruntime-genai-core pip deps. - task: PowerShell@2 displayName: 'Stage native artifacts' inputs: @@ -179,7 +176,7 @@ steps: (Join-Path $linkDir 'foundry_local.lib') ) if ($${{ parameters.useWinml }}) { - $sources += (Join-Path $binDir 'Microsoft.WindowsAppRuntime.Bootstrap.dll') + $sources += (Join-Path $binDir 'Microsoft.Windows.AI.MachineLearning.dll') } foreach ($s in $sources) { diff --git a/.pipelines/v2/templates/steps-prefetch-nuget.yml b/.pipelines/v2/templates/steps-prefetch-nuget.yml index 70a83f7c2..4bc608d82 100644 --- a/.pipelines/v2/templates/steps-prefetch-nuget.yml +++ b/.pipelines/v2/templates/steps-prefetch-nuget.yml @@ -1,5 +1,5 @@ -# Pre-download ORT / GenAI / (optionally) WinML NuGet packages from the -# aiinfra ADO feed and emit cmake_extra_defines pointing at them. +# Pre-download ORT / GenAI / (optionally) WinML NuGet packages from +# nuget.org and emit cmake_extra_defines pointing at them. # # Sets the pipeline variable `cmakeFetchDefines` containing the space-separated # `KEY=PATH` pairs to pass to build.py --cmake_extra_defines. @@ -7,7 +7,7 @@ # Parameters: # ortVersion – Microsoft.ML.OnnxRuntime.Foundry version # genaiVersion – Microsoft.ML.OnnxRuntimeGenAI.Foundry version -# winmlVersion – Microsoft.WindowsAppSDK.ML version (Windows only) +# winmlVersion – Microsoft.Windows.AI.MachineLearning version (Windows only, e.g. 2.1.6) # includeWinml – Download WinML and emit WINML_EP_CATALOG_FETCH_URL # includeOrtGpuLinux – Also download Microsoft.ML.OnnxRuntime.Gpu.Linux (Linux only) # shell – 'pwsh' (Windows/macOS) or 'bash' (Linux) @@ -74,34 +74,16 @@ steps: } if ($${{ parameters.includeWinml }}) { - # WinML's bootstrap dep (Microsoft.WindowsAppSDK.Foundation) is a transitive dep of - # Microsoft.WindowsAppSDK.ML, and nuget min-version semantics mean the exact min - # version often isn't published — only later patches are. Defer to `nuget install` - # so the real resolver picks a satisfying version. - $winmlDir = "$cacheDir/winml-resolved" - if (Test-Path $winmlDir) { Remove-Item -Recurse -Force $winmlDir } - New-Item -ItemType Directory -Force -Path $winmlDir | Out-Null - Write-Host "Resolving Microsoft.WindowsAppSDK.ML ${{ parameters.winmlVersion }} via nuget install" - nuget install Microsoft.WindowsAppSDK.ML ` - -Version '${{ parameters.winmlVersion }}' ` - -OutputDirectory $winmlDir ` - -Source 'https://api.nuget.org/v3/index.json' ` - -DependencyVersion Lowest ` - -PackageSaveMode nupkg ` - -DirectDownload ` - -Verbosity quiet - if ($LASTEXITCODE -ne 0) { throw "nuget install Microsoft.WindowsAppSDK.ML failed (exit $LASTEXITCODE)" } - - $mlNupkg = Get-ChildItem $winmlDir -Recurse -Filter 'Microsoft.WindowsAppSDK.ML.*.nupkg' | - Select-Object -First 1 - $foundationNupkg = Get-ChildItem $winmlDir -Recurse -Filter 'Microsoft.WindowsAppSDK.Foundation.*.nupkg' | - Select-Object -First 1 - if (-not $mlNupkg) { throw "Microsoft.WindowsAppSDK.ML .nupkg not found under $winmlDir after nuget install" } - if (-not $foundationNupkg) { throw "Microsoft.WindowsAppSDK.Foundation .nupkg not found under $winmlDir after nuget install" } - Write-Host " -> $($mlNupkg.FullName) ($($mlNupkg.Length) bytes)" - Write-Host " -> $($foundationNupkg.FullName) ($($foundationNupkg.Length) bytes)" - $defines += "WINML_EP_CATALOG_FETCH_URL=$($mlNupkg.FullName)" - $defines += "WINAPPSDK_FOUNDATION_FETCH_URL=$($foundationNupkg.FullName)" + # WinML 2.x (Microsoft.Windows.AI.MachineLearning) is reg-free — a single self-contained + # native package with no transitive Windows App SDK Foundation dependency to resolve. + # Direct download from nuget.org is sufficient. + $winmlUrl = "$feed/Microsoft.Windows.AI.MachineLearning/${{ parameters.winmlVersion }}" + $winmlOut = "$cacheDir/winml.nupkg" + Write-Host "Downloading Microsoft.Windows.AI.MachineLearning ${{ parameters.winmlVersion }}" + Invoke-WebRequest -Uri $winmlUrl -OutFile $winmlOut -UseBasicParsing + if (-not (Test-Path $winmlOut)) { throw "WinML download failed" } + Write-Host " -> $winmlOut ($((Get-Item $winmlOut).Length) bytes)" + $defines += "WINML_EP_CATALOG_FETCH_URL=$winmlOut" } $joined = ($defines | ForEach-Object { "`"$_`"" }) -join ' ' @@ -126,8 +108,6 @@ steps: ) if [ "${{ parameters.includeWinml }}" = "True" ]; then # WinML is Windows-only; the bash branch should never receive includeWinml=true. - # If this fires, the prefetch needs to mirror the pwsh logic that resolves the - # Microsoft.WindowsAppSDK.Foundation version from the .ML nuspec dependency list. echo "ERROR: includeWinml=true is not supported on the bash prefetch branch (WinML is Windows-only)." >&2 exit 1 fi diff --git a/sdk_v2/cpp/CMakeLists.txt b/sdk_v2/cpp/CMakeLists.txt index e9b046e76..9987a8f90 100644 --- a/sdk_v2/cpp/CMakeLists.txt +++ b/sdk_v2/cpp/CMakeLists.txt @@ -44,7 +44,7 @@ option(FOUNDRY_LOCAL_BUILD_TESTS "Build unit tests" ON) option(FOUNDRY_LOCAL_BUILD_EXAMPLES "Build example programs" ON) option(FOUNDRY_LOCAL_BUILD_TOOLS "Build internal build-time tools (catalog_snapshot, ...)" ON) option(FOUNDRY_LOCAL_BUILD_SERVICE "Build web service support (requires oat++)" ON) -option(FOUNDRY_LOCAL_USE_WINML "Use WinML/WindowsAppSDK.ML for OnnxRuntime instead of standalone ORT" OFF) +option(FOUNDRY_LOCAL_USE_WINML "Enable the WinML 2.x EP catalog (Microsoft.Windows.AI.MachineLearning) for hardware EP discovery" OFF) option(FOUNDRY_LOCAL_ENABLE_ASAN "Enable AddressSanitizer + UndefinedBehaviorSanitizer (Linux only)" OFF) # Android: interactive examples and host tools don't run on device @@ -78,14 +78,17 @@ if(FOUNDRY_LOCAL_BUILD_TESTS) enable_testing() endif() -# ORT and ORT GenAI — acquired via FetchContent from nuget.org (or WinML SDK when FOUNDRY_LOCAL_USE_WINML=ON) +# ORT and ORT GenAI — acquired via FetchContent from nuget.org. WinML and non-WinML +# builds share the same ORT package and version. list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") find_package(OnnxRuntimeGenAI REQUIRED) find_package(OnnxRuntime REQUIRED) -# WinML EP Catalog — Windows-only, for EP discovery and download. -# Not REQUIRED: builds without it fall back to CPU-only EP detection. -find_package(WinMLEpCatalog) +# WinML EP Catalog — Windows-only, for EP discovery and download. Only fetched +# when the WinML SKU is requested so non-WinML builds don't pull the catalog DLL. +if(FOUNDRY_LOCAL_USE_WINML) + find_package(WinMLEpCatalog) +endif() # -------------------------------------------------------------------------- # Library target @@ -177,7 +180,6 @@ set(FOUNDRY_LOCAL_SOURCES src/inferencing/predictive/inference_session.cc src/inferencing/predictive/tensor.cc src/manager.cc - src/winml_bootstrap.cc src/contracts/chat_completions.cc src/contracts/chat_completions_converter.cc src/contracts/audio_transcriptions.cc @@ -275,17 +277,6 @@ function(foundry_local_configure_target TARGET LINK_SCOPE) else() target_compile_definitions(${TARGET} ${LINK_SCOPE} FOUNDRY_LOCAL_HAS_EP_CATALOG=0) endif() - - # WinAppSDK Bootstrap (MddBootstrapInitialize2). Only linked in WinML builds, since - # WinML by definition depends on the WindowsAppSDK runtime; non-WinML builds skip it - # and the bootstrap call site in Manager::Create is compiled out via the same macro. - if(FOUNDRY_LOCAL_USE_WINML) - if(NOT TARGET WinAppSdkBootstrap::WinAppSdkBootstrap) - message(FATAL_ERROR "FOUNDRY_LOCAL_USE_WINML=ON but the WinAppSDK Bootstrap import " - "library was not found. See FindWinMLEpCatalog.cmake.") - endif() - target_link_libraries(${TARGET} ${LINK_SCOPE} WinAppSdkBootstrap::WinAppSdkBootstrap) - endif() endfunction() # -------------------------------------------------------------------------- @@ -412,7 +403,9 @@ if(TARGET OnnxRuntime::OnnxRuntime) ) endif() - # Copy WinML EP Catalog DLL for runtime EP discovery + # Copy WinML EP Catalog DLL for runtime EP discovery. WinML 2.x is reg-free — + # the DLL loads directly with no Windows App SDK bootstrap, so co-locating it + # is the only runtime requirement. if(WinMLEpCatalog_FOUND AND EXISTS "${WINML_EP_CATALOG_DLL_DIR}/Microsoft.Windows.AI.MachineLearning.dll") add_custom_command(TARGET foundry_local POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different @@ -420,16 +413,6 @@ if(TARGET OnnxRuntime::OnnxRuntime) $ ) endif() - - # Co-locate the WinAppSDK bootstrap DLL with foundry_local.dll. Required at runtime - # because it's an import-library dependency of the WinML build, not a delay-loaded one. - if(FOUNDRY_LOCAL_USE_WINML AND WINAPPSDK_BOOTSTRAP_DLL AND EXISTS "${WINAPPSDK_BOOTSTRAP_DLL}") - add_custom_command(TARGET foundry_local POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different - "${WINAPPSDK_BOOTSTRAP_DLL}" - $ - ) - endif() elseif(APPLE) # macOS: copy dylibs so consumers that only link libfoundry_local.dylib (e.g. cache_only_tests) find the correct # ORT version instead of any system-installed ORT, which would cause an Ort::InitApi() version mismatch. diff --git a/sdk_v2/cpp/build.py b/sdk_v2/cpp/build.py index f4e2a6a39..82acbff4f 100644 --- a/sdk_v2/cpp/build.py +++ b/sdk_v2/cpp/build.py @@ -162,13 +162,13 @@ class HelpFormatter(argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescript ) parser.add_argument( "--use_winml", action="store_true", - help="Enable the WinML EP catalog (Microsoft.WindowsAppSDK.ML) for hardware EP " - "discovery. ORT itself still comes from Microsoft.ML.OnnxRuntime.Foundry; " + help="Enable the WinML EP catalog (Microsoft.Windows.AI.MachineLearning, WinML 2.x reg-free " + "runtime) for hardware EP discovery. ORT itself still comes from Microsoft.ML.OnnxRuntime.Foundry; " "this flag only adds the WinML EP catalog client.", ) parser.add_argument( "--winml_sdk_version", default=None, type=str, - help="Version of Microsoft.WindowsAppSDK.ML NuGet package (used for the WinML EP " + help="Version of Microsoft.Windows.AI.MachineLearning NuGet package (used for the WinML EP " "catalog when --use_winml is set; defaults to the version pinned in " "FindWinMLEpCatalog.cmake).", ) @@ -465,7 +465,7 @@ def configure(args: argparse.Namespace) -> None: if args.use_winml: command += ["-DFOUNDRY_LOCAL_USE_WINML=ON"] if args.winml_sdk_version: - command += [f"-DWINML_SDK_VERSION={args.winml_sdk_version}"] + command += [f"-DWINML_EP_CATALOG_VERSION={args.winml_sdk_version}"] else: # Pass explicitly so a re-configure without the flag clears any cached ON value. command += ["-DFOUNDRY_LOCAL_USE_WINML=OFF"] diff --git a/sdk_v2/cpp/cmake/FindOnnxRuntime.cmake b/sdk_v2/cpp/cmake/FindOnnxRuntime.cmake index 2ec92011f..98d01373d 100644 --- a/sdk_v2/cpp/cmake/FindOnnxRuntime.cmake +++ b/sdk_v2/cpp/cmake/FindOnnxRuntime.cmake @@ -4,9 +4,10 @@ # ORT is always sourced from Microsoft.ML.OnnxRuntime.Foundry (or # Microsoft.ML.OnnxRuntime on Android) via FetchContent — nuget.org for releases, # the ORT-Nightly ADO feed for -dev- versions. The FOUNDRY_LOCAL_USE_WINML flag -# does NOT change the ORT package source; it only: -# - selects a WinML-compatible ORT version (see version branch below), and -# - opts in to the WinML EP catalog (handled by FindWinMLEpCatalog.cmake). +# does NOT change the ORT package source or version — it only opts in to the +# WinML EP catalog (handled by FindWinMLEpCatalog.cmake). WinML and non-WinML +# builds share a single ORT pin from sdk_v2/deps_versions.json since both +# flavors are built against the same ORT line now. # # Creates an IMPORTED target: OnnxRuntime::OnnxRuntime @@ -80,14 +81,10 @@ else() # Standard path: FetchContent from nuget.org (releases) or ORT-Nightly ADO feed (dev builds) # ----------------------------------------------------------------------- if(NOT ORT_VERSION) - # Single source of truth: sdk_v2/deps_versions[_winml].json. The Python - # SDK build backend reads the same files so wheel deps and native ABI + # Single source of truth: sdk_v2/deps_versions.json. The Python SDK + # build backend reads the same file so wheel deps and native ABI # always agree. Override at the cmake command line with -DORT_VERSION=... - if(FOUNDRY_LOCAL_USE_WINML) - set(_DEPS_FILE "${CMAKE_CURRENT_LIST_DIR}/../../deps_versions_winml.json") - else() - set(_DEPS_FILE "${CMAKE_CURRENT_LIST_DIR}/../../deps_versions.json") - endif() + set(_DEPS_FILE "${CMAKE_CURRENT_LIST_DIR}/../../deps_versions.json") if(NOT EXISTS "${_DEPS_FILE}") message(FATAL_ERROR "Required versions file not found: ${_DEPS_FILE}") endif() @@ -245,8 +242,8 @@ else() set_target_properties(OnnxRuntime::OnnxRuntime PROPERTIES IMPORTED_IMPLIB "${_ORT_LIB_DIR}/onnxruntime.lib" ) - # On Windows the runtime DLL usually sits next to the import lib; WinML SDK is the - # exception (DLL lives under runtimes-framework/), so honour _ORT_DLL_DIR if set. + # On Windows the runtime DLL sits next to the import lib for both flavors — + # the historical WinML SDK layout (runtimes-framework/) is no longer used. if(NOT _ORT_DLL_DIR) set(_ORT_DLL_DIR "${_ORT_LIB_DIR}") endif() diff --git a/sdk_v2/cpp/cmake/FindOnnxRuntimeGenAI.cmake b/sdk_v2/cpp/cmake/FindOnnxRuntimeGenAI.cmake index 4906c254b..3163b6f11 100644 --- a/sdk_v2/cpp/cmake/FindOnnxRuntimeGenAI.cmake +++ b/sdk_v2/cpp/cmake/FindOnnxRuntimeGenAI.cmake @@ -1,10 +1,10 @@ # Copyright (c) Microsoft. All rights reserved. # Find/acquire ONNX Runtime GenAI. # -# Windows + FOUNDRY_LOCAL_USE_WINML=ON: Microsoft.ML.OnnxRuntimeGenAI.WinML -# Windows + FOUNDRY_LOCAL_USE_WINML=OFF: Microsoft.ML.OnnxRuntimeGenAI.Foundry -# Linux: Microsoft.ML.OnnxRuntimeGenAI.Foundry -# macOS: Microsoft.ML.OnnxRuntimeGenAI.Foundry +# All platforms / flavors: Microsoft.ML.OnnxRuntimeGenAI.Foundry +# +# WinML and non-WinML builds share the same GenAI package and version, pinned +# in sdk_v2/deps_versions.json. # # When ORT_GENAI_HOME is set, uses the local ORT GenAI build instead of NuGet. # Otherwise uses FetchContent from nuget.org. @@ -104,21 +104,13 @@ else() message(FATAL_ERROR "Unsupported platform for OnnxRuntimeGenAI: ${CMAKE_GENERATOR_PLATFORM} on ${CMAKE_SYSTEM_NAME}") endif() -if(FOUNDRY_LOCAL_USE_WINML) - set(_GENAI_PACKAGE_NAME "Microsoft.ML.OnnxRuntimeGenAI.WinML") -else() - set(_GENAI_PACKAGE_NAME "Microsoft.ML.OnnxRuntimeGenAI.Foundry") -endif() +set(_GENAI_PACKAGE_NAME "Microsoft.ML.OnnxRuntimeGenAI.Foundry") if(NOT ORT_GENAI_VERSION) - # Single source of truth: sdk_v2/deps_versions[_winml].json. The Python - # SDK build backend reads the same files. Override at the cmake command - # line with -DORT_GENAI_VERSION=... - if(FOUNDRY_LOCAL_USE_WINML) - set(_GENAI_DEPS_FILE "${CMAKE_CURRENT_LIST_DIR}/../../deps_versions_winml.json") - else() - set(_GENAI_DEPS_FILE "${CMAKE_CURRENT_LIST_DIR}/../../deps_versions.json") - endif() + # Single source of truth: sdk_v2/deps_versions.json. The Python SDK build + # backend reads the same file. Override at the cmake command line with + # -DORT_GENAI_VERSION=... + set(_GENAI_DEPS_FILE "${CMAKE_CURRENT_LIST_DIR}/../../deps_versions.json") if(NOT EXISTS "${_GENAI_DEPS_FILE}") message(FATAL_ERROR "Required versions file not found: ${_GENAI_DEPS_FILE}") endif() diff --git a/sdk_v2/cpp/cmake/FindWinMLEpCatalog.cmake b/sdk_v2/cpp/cmake/FindWinMLEpCatalog.cmake index 2f83cf836..e08691753 100644 --- a/sdk_v2/cpp/cmake/FindWinMLEpCatalog.cmake +++ b/sdk_v2/cpp/cmake/FindWinMLEpCatalog.cmake @@ -1,15 +1,21 @@ # Copyright (c) Microsoft. All rights reserved. -# Find/acquire the WinML EP Catalog C API from Microsoft.WindowsAppSDK.ML. +# Find/acquire the WinML EP Catalog C API from Microsoft.Windows.AI.MachineLearning. # -# Downloads the NuGet package if needed, then creates an IMPORTED target for -# the EP catalog library (Microsoft.Windows.AI.MachineLearning.dll/.lib). +# Downloads the NuGet package if needed, then defers to the package's first-party +# CMake config (build/cmake/microsoft.windows.ai.machinelearning-config.cmake) for +# target discovery. The config defines WindowsML::Api (EP catalog) and +# WindowsML::OnnxRuntime; we link only WindowsML::Api here so that ORT remains +# sourced from FindOnnxRuntime.cmake (Microsoft.ML.OnnxRuntime, same version for +# WinML and non-WinML builds). # -# This is separate from ORT — we use the EP catalog to discover and download -# hardware-specific execution providers at runtime. ORT itself comes from -# FindOnnxRuntime.cmake (either WinML SDK or ORT-Nightly feed). +# Reg-free runtime: WinML 2.x (Microsoft.Windows.AI.MachineLearning) does not +# require the Windows App SDK runtime bootstrap. The package ships a self-contained +# native DLL that can be loaded directly from any unpackaged app on Windows 10 +# 19H1 (build 18362) or newer. No MddBootstrapInitialize2 plumbing is needed. # -# Creates an IMPORTED target: WinMLEpCatalog::WinMLEpCatalog -# Sets: WINML_EP_CATALOG_HEADER_DIR, WINML_EP_CATALOG_DLL_DIR +# Re-exports an ALIAS target: WinMLEpCatalog::WinMLEpCatalog -> WindowsML::Api +# Sets: WINML_EP_CATALOG_HEADER_DIR (= WINML_INCLUDE_DIR), +# WINML_EP_CATALOG_DLL_DIR (= WINML_BINARY_DIR) if(WinMLEpCatalog_FOUND) return() @@ -22,29 +28,40 @@ if(NOT CMAKE_SYSTEM_NAME STREQUAL "Windows") return() endif() -# Latest stable Microsoft.WindowsAppSDK.ML 1.8.x on nuget.org. Anything older -# than 1.8.2141 silently disables EP detection (no WinMLEpCatalog.h). -set(_WINML_EP_CATALOG_MIN_VERSION "1.8.2192") +# Latest GA Microsoft.Windows.AI.MachineLearning on nuget.org. Bump as new GA +# releases ship; the WinMLEpCatalog.h C ABI is stable across 2.0.x and 2.1.x. +set(_WINML_EP_CATALOG_MIN_VERSION "2.1.6") -# WINML_EP_CATALOG_VERSION may be set explicitly; otherwise pick the minimum -# known-good version. We deliberately do NOT inherit WINML_SDK_VERSION here: -# the WinML SDK and the EP catalog package have independent compatibility -# requirements (the EP catalog ships only in newer WindowsAppSDK.ML packages, -# and our build no longer uses the WinML-bundled ORT regardless). if(NOT WINML_EP_CATALOG_VERSION) set(WINML_EP_CATALOG_VERSION "${_WINML_EP_CATALOG_MIN_VERSION}") endif() +# The package's own CMake config FATAL_ERRORs on architectures it doesn't ship +# binaries for (anything other than x64/ARM64). Pre-check so we degrade to a soft +# disable instead of halting configuration when someone builds e.g. ARM64EC with +# FOUNDRY_LOCAL_USE_WINML=ON. +if(CMAKE_GENERATOR_PLATFORM) + string(TOUPPER "${CMAKE_GENERATOR_PLATFORM}" _WINML_PLATFORM_UPPER) +elseif(CMAKE_VS_PLATFORM_NAME) + string(TOUPPER "${CMAKE_VS_PLATFORM_NAME}" _WINML_PLATFORM_UPPER) +elseif(CMAKE_SYSTEM_PROCESSOR) + string(TOUPPER "${CMAKE_SYSTEM_PROCESSOR}" _WINML_PLATFORM_UPPER) +else() + set(_WINML_PLATFORM_UPPER "X64") +endif() + +if(NOT _WINML_PLATFORM_UPPER MATCHES "^(AMD64|X64|X86_64|ARM64|AARCH64)$") + message(WARNING "WinML EP Catalog: unsupported architecture '${_WINML_PLATFORM_UPPER}'. " + "Package ships x64/ARM64 only. EP detection will be disabled.") + set(WinMLEpCatalog_FOUND FALSE) + return() +endif() + include(cmake/nuget.cmake) # WINML_EP_CATALOG_FETCH_URL can be set externally (e.g. for CI where nuget.org is blocked). set(WINML_EP_CATALOG_FETCH_URL "" CACHE STRING "Override URL or local path for the WinML EP Catalog NuGet package") -# Microsoft.WindowsAppSDK.Foundation ships the bootstrap (MddBootstrapInitialize2) ABI. When using -# FetchContent (offline CI) it must be supplied alongside the .ML package because nuget transitive -# resolution doesn't run on a raw .nupkg fetch. -set(WINAPPSDK_FOUNDATION_FETCH_URL "" CACHE STRING "Override URL or local path for the Microsoft.WindowsAppSDK.Foundation NuGet package") - if(WINML_EP_CATALOG_FETCH_URL) # Use FetchContent to download/extract the pre-downloaded package include(FetchContent) @@ -61,143 +78,45 @@ if(WINML_EP_CATALOG_FETCH_URL) set(_WINML_EP_ROOT "${winml_ep_catalog_SOURCE_DIR}") message(STATUS "WinML EP Catalog via FetchContent: ${_WINML_EP_ROOT}") else() - install_nuget_package(Microsoft.WindowsAppSDK.ML ${WINML_EP_CATALOG_VERSION} _WINML_EP_ROOT + install_nuget_package(Microsoft.Windows.AI.MachineLearning ${WINML_EP_CATALOG_VERSION} _WINML_EP_ROOT SOURCE https://api.nuget.org/v3/index.json) endif() -# Determine platform for lib/native path -if(CMAKE_GENERATOR_PLATFORM STREQUAL "ARM64" OR CMAKE_GENERATOR_PLATFORM STREQUAL "arm64") - set(_WINML_EP_ARCH "arm64") - set(_WINML_EP_RUNTIME_PLATFORM "win-arm64") -elseif(CMAKE_GENERATOR_PLATFORM STREQUAL "ARM64EC" OR CMAKE_GENERATOR_PLATFORM STREQUAL "arm64EC") - set(_WINML_EP_ARCH "arm64ec") - set(_WINML_EP_RUNTIME_PLATFORM "win-arm64ec") -else() - set(_WINML_EP_ARCH "x64") - set(_WINML_EP_RUNTIME_PLATFORM "win-x64") -endif() - -set(_WINML_EP_HEADER_DIR "${_WINML_EP_ROOT}/include") -set(_WINML_EP_LIB_DIR "${_WINML_EP_ROOT}/lib/native/${_WINML_EP_ARCH}") -set(_WINML_EP_DLL_DIR "${_WINML_EP_ROOT}/runtimes-framework/${_WINML_EP_RUNTIME_PLATFORM}/native") - -# Validate headers -if(NOT EXISTS "${_WINML_EP_HEADER_DIR}/WinMLEpCatalog.h") - message(WARNING "WinML EP Catalog: WinMLEpCatalog.h not found at ${_WINML_EP_HEADER_DIR}. " - "EP detection will be disabled. Package version may be too old (need ≥1.8.2141).") +# Defer to the package's first-party CMake config for target discovery and layout +# resolution. The config lives at build/cmake/-config.cmake +# and defines WindowsML::Api / WindowsML::OnnxRuntime / WindowsML::DirectML. +set(_WINML_EP_CONFIG_DIR "${_WINML_EP_ROOT}/build/cmake") +if(NOT EXISTS "${_WINML_EP_CONFIG_DIR}/microsoft.windows.ai.machinelearning-config.cmake") + message(WARNING "WinML EP Catalog: package CMake config not found at ${_WINML_EP_CONFIG_DIR}. " + "Package version may be too old or layout has changed. EP detection will be disabled.") set(WinMLEpCatalog_FOUND FALSE) return() endif() -# Validate import lib -set(_WINML_EP_IMPORT_LIB "${_WINML_EP_LIB_DIR}/Microsoft.Windows.AI.MachineLearning.lib") -if(NOT EXISTS "${_WINML_EP_IMPORT_LIB}") - message(WARNING "WinML EP Catalog: import lib not found at ${_WINML_EP_IMPORT_LIB}. " +set(microsoft.windows.ai.machinelearning_DIR "${_WINML_EP_CONFIG_DIR}" CACHE PATH + "Path to the Microsoft.Windows.AI.MachineLearning CMake config" FORCE) +find_package(microsoft.windows.ai.machinelearning CONFIG REQUIRED) + +if(NOT TARGET WindowsML::Api) + message(WARNING "WinML EP Catalog: WindowsML::Api target not defined after find_package. " "EP detection will be disabled.") set(WinMLEpCatalog_FOUND FALSE) return() endif() -# Create imported target -add_library(WinMLEpCatalog::WinMLEpCatalog SHARED IMPORTED) -set_target_properties(WinMLEpCatalog::WinMLEpCatalog PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${_WINML_EP_HEADER_DIR}" - IMPORTED_IMPLIB "${_WINML_EP_IMPORT_LIB}" -) - -# Set the DLL location if it exists (for post-build copy) -set(_WINML_EP_DLL "${_WINML_EP_DLL_DIR}/Microsoft.Windows.AI.MachineLearning.dll") -if(EXISTS "${_WINML_EP_DLL}") - set_target_properties(WinMLEpCatalog::WinMLEpCatalog PROPERTIES - IMPORTED_LOCATION "${_WINML_EP_DLL}" - ) -endif() +# Re-export under our existing name so consumers don't need to change. Promote +# WindowsML::Api to GLOBAL so the alias is visible in any subdirectory that may +# link foundry_local transitively. +set_target_properties(WindowsML::Api PROPERTIES IMPORTED_GLOBAL TRUE) +add_library(WinMLEpCatalog::WinMLEpCatalog ALIAS WindowsML::Api) -# Export paths for downstream use -set(WINML_EP_CATALOG_HEADER_DIR "${_WINML_EP_HEADER_DIR}" CACHE PATH "WinML EP Catalog include directory" FORCE) -set(WINML_EP_CATALOG_DLL_DIR "${_WINML_EP_DLL_DIR}" CACHE PATH "WinML EP Catalog native DLL directory" FORCE) +# Export paths set by the official config (WINML_INCLUDE_DIR/_BINARY_DIR) under +# our existing variable names for the post-build DLL-copy step. +set(WINML_EP_CATALOG_HEADER_DIR "${WINML_INCLUDE_DIR}" CACHE PATH "WinML EP Catalog include directory" FORCE) +set(WINML_EP_CATALOG_DLL_DIR "${WINML_BINARY_DIR}" CACHE PATH "WinML EP Catalog native DLL directory" FORCE) set(WinMLEpCatalog_FOUND TRUE) message(STATUS "WinML EP Catalog: ${_WINML_EP_ROOT}") -message(STATUS " Headers: ${_WINML_EP_HEADER_DIR}") -message(STATUS " Import lib: ${_WINML_EP_IMPORT_LIB}") -message(STATUS " DLL dir: ${_WINML_EP_DLL_DIR}") - -# -------------------------------------------------------------------------- -# WinAppSDK Bootstrap (MddBootstrapInitialize2) -# -------------------------------------------------------------------------- -# The bootstrap C ABI ships in Microsoft.WindowsAppSDK.Foundation. There are two acquisition paths: -# 1. nuget install (default) — pulled in transitively as a dependency of Microsoft.WindowsAppSDK.ML. -# 2. FetchContent (offline CI) — caller must supply WINAPPSDK_FOUNDATION_FETCH_URL pointing at the -# Foundation .nupkg, since FetchContent doesn't resolve nuget transitive deps. -# -# Either path resolves to a directory layout matching the NuGet package and exposes: -# - WinAppSdkBootstrap::WinAppSdkBootstrap (IMPORTED SHARED) — link to get -# Microsoft.WindowsAppRuntime.Bootstrap.lib + the MddBootstrap.h include path. -# - WINAPPSDK_BOOTSTRAP_DLL — full path to Microsoft.WindowsAppRuntime.Bootstrap.dll -# for post-build co-location next to the consuming binary. -set(_WINAPPSDK_FOUNDATION_ROOT "") - -if(WINAPPSDK_FOUNDATION_FETCH_URL) - include(FetchContent) - string(REPLACE "\\" "/" WINAPPSDK_FOUNDATION_FETCH_URL "${WINAPPSDK_FOUNDATION_FETCH_URL}") - if(WINAPPSDK_FOUNDATION_FETCH_URL MATCHES "\\.nupkg$" AND NOT WINAPPSDK_FOUNDATION_FETCH_URL MATCHES "^https?://") - set(_WINAPPSDK_FOUNDATION_ZIP_PATH "${CMAKE_BINARY_DIR}/_deps/winappsdk_foundation-download/winappsdk_foundation.zip") - get_filename_component(_WINAPPSDK_FOUNDATION_ZIP_DIR "${_WINAPPSDK_FOUNDATION_ZIP_PATH}" DIRECTORY) - file(MAKE_DIRECTORY "${_WINAPPSDK_FOUNDATION_ZIP_DIR}") - file(COPY_FILE "${WINAPPSDK_FOUNDATION_FETCH_URL}" "${_WINAPPSDK_FOUNDATION_ZIP_PATH}") - set(WINAPPSDK_FOUNDATION_FETCH_URL "${_WINAPPSDK_FOUNDATION_ZIP_PATH}") - endif() - FetchContent_Declare(winappsdk_foundation URL ${WINAPPSDK_FOUNDATION_FETCH_URL} - DOWNLOAD_EXTRACT_TIMESTAMP TRUE DOWNLOAD_NAME winappsdk_foundation.zip) - FetchContent_MakeAvailable(winappsdk_foundation) - set(_WINAPPSDK_FOUNDATION_ROOT "${winappsdk_foundation_SOURCE_DIR}") - message(STATUS "WinAppSDK Foundation via FetchContent: ${_WINAPPSDK_FOUNDATION_ROOT}") -elseif(NOT WINML_EP_CATALOG_FETCH_URL) - if(NOT NUGET_PACKAGE_ROOT_PATH) - set(NUGET_PACKAGE_ROOT_PATH "${CMAKE_BINARY_DIR}/__nuget") - endif() - file(GLOB _WINAPPSDK_FOUNDATION_DIRS LIST_DIRECTORIES TRUE - "${NUGET_PACKAGE_ROOT_PATH}/Microsoft.WindowsAppSDK.Foundation.*") - if(_WINAPPSDK_FOUNDATION_DIRS) - list(SORT _WINAPPSDK_FOUNDATION_DIRS COMPARE NATURAL ORDER DESCENDING) - list(GET _WINAPPSDK_FOUNDATION_DIRS 0 _WINAPPSDK_FOUNDATION_ROOT) - endif() -endif() - -if(_WINAPPSDK_FOUNDATION_ROOT) - set(_WINAPPSDK_BOOTSTRAP_HEADER_DIR "${_WINAPPSDK_FOUNDATION_ROOT}/include") - set(_WINAPPSDK_BOOTSTRAP_LIB - "${_WINAPPSDK_FOUNDATION_ROOT}/lib/native/${_WINML_EP_ARCH}/Microsoft.WindowsAppRuntime.Bootstrap.lib") - set(_WINAPPSDK_BOOTSTRAP_DLL - "${_WINAPPSDK_FOUNDATION_ROOT}/runtimes/${_WINML_EP_RUNTIME_PLATFORM}/native/Microsoft.WindowsAppRuntime.Bootstrap.dll") - - if(EXISTS "${_WINAPPSDK_BOOTSTRAP_HEADER_DIR}/MddBootstrap.h" AND EXISTS "${_WINAPPSDK_BOOTSTRAP_LIB}") - add_library(WinAppSdkBootstrap::WinAppSdkBootstrap SHARED IMPORTED) - set_target_properties(WinAppSdkBootstrap::WinAppSdkBootstrap PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${_WINAPPSDK_BOOTSTRAP_HEADER_DIR}" - IMPORTED_IMPLIB "${_WINAPPSDK_BOOTSTRAP_LIB}" - ) - if(EXISTS "${_WINAPPSDK_BOOTSTRAP_DLL}") - set_target_properties(WinAppSdkBootstrap::WinAppSdkBootstrap PROPERTIES - IMPORTED_LOCATION "${_WINAPPSDK_BOOTSTRAP_DLL}" - ) - set(WINAPPSDK_BOOTSTRAP_DLL "${_WINAPPSDK_BOOTSTRAP_DLL}" - CACHE FILEPATH "Microsoft.WindowsAppRuntime.Bootstrap.dll for post-build copy" FORCE) - endif() - set(WinAppSdkBootstrap_FOUND TRUE) - message(STATUS "WinAppSDK Bootstrap: ${_WINAPPSDK_FOUNDATION_ROOT}") - message(STATUS " Import lib: ${_WINAPPSDK_BOOTSTRAP_LIB}") - message(STATUS " DLL: ${_WINAPPSDK_BOOTSTRAP_DLL}") - else() - message(WARNING "WinAppSDK Bootstrap: header or import lib missing under " - "${_WINAPPSDK_FOUNDATION_ROOT}. Bootstrap.Initialize() will be a no-op.") - endif() -elseif(WINML_EP_CATALOG_FETCH_URL) - message(WARNING "WinAppSDK Bootstrap: WINML_EP_CATALOG_FETCH_URL is set but " - "WINAPPSDK_FOUNDATION_FETCH_URL is not. WinML builds in this configuration " - "will fail to link the bootstrap.") -else() - message(WARNING "WinAppSDK Bootstrap: Microsoft.WindowsAppSDK.Foundation NuGet not found " - "under ${NUGET_PACKAGE_ROOT_PATH}. Bootstrap.Initialize() will be a no-op.") -endif() +message(STATUS " Target: WinMLEpCatalog::WinMLEpCatalog -> WindowsML::Api") +message(STATUS " Headers: ${WINML_EP_CATALOG_HEADER_DIR}") +message(STATUS " DLL dir: ${WINML_EP_CATALOG_DLL_DIR}") diff --git a/sdk_v2/cpp/docs/CppPortGuide.md b/sdk_v2/cpp/docs/CppPortGuide.md index 8fc3772f7..5eaac27af 100644 --- a/sdk_v2/cpp/docs/CppPortGuide.md +++ b/sdk_v2/cpp/docs/CppPortGuide.md @@ -399,7 +399,7 @@ HuggingFace, NIM, and WCR providers were not ported. | `EpDetector` | `EpDetector` (implements `IEpDetector`) | Real detection via ORT `GetAvailableProviders()` + `GetEpDevices()` | | `IEpBootstrapper` | `IEpBootstrapper` (interface) | Bootstrapping interface for EP package download/registration | | `CudaEpBootstrapper` | `CudaEpBootstrapper` | Downloads CUDA EP zip, extracts, prepends to PATH, registers with ORT | -| `WinMLEpBootstrapper` | `WinMLEpBootstrapper` | Discovers WinML EPs via `Microsoft.Windows.AI.MachineLearning.dll` catalog API. Windows 11 24H2+ (build 26100). | +| `WinMLEpBootstrapper` | `WinMLEpBootstrapper` | Discovers WinML EPs via `Microsoft.Windows.AI.MachineLearning.dll` catalog API. WinML 2.x reg-free runtime, Windows 10 19H1+ (build 18362). | --- diff --git a/sdk_v2/cpp/docs/EpDetectionPlan.md b/sdk_v2/cpp/docs/EpDetectionPlan.md index 1dbd98857..2e4c46ad8 100644 --- a/sdk_v2/cpp/docs/EpDetectionPlan.md +++ b/sdk_v2/cpp/docs/EpDetectionPlan.md @@ -22,7 +22,7 @@ bootstrapping. C ABI (foundry_local_c.h) └── IEpDetector interface (expanded) ├── EpDetector (orchestrator — replaces stub) - │ ├── WinMLEpBootstrapper (Windows 11 24H2+, WinML package present) + │ ├── WinMLEpBootstrapper (Windows 10 19H1+ reg-free, WinML 2.x package present) │ ├── CudaEpBootstrapper (Windows x64 + NVIDIA, Linux x64) │ └── [future bootstrappers as needed] └── StubEpDetector (fallback — CPU only, used in tests) @@ -43,9 +43,13 @@ C ABI (foundry_local_c.h) ## Dependencies -### Microsoft.WindowsAppSDK.ML NuGet (via vcpkg) +### Microsoft.Windows.AI.MachineLearning NuGet (WinML 2.x) -**Package:** `Microsoft.WindowsAppSDK.ML` v1.8.2141+ +**Package:** `Microsoft.Windows.AI.MachineLearning` 2.1.6 (or newer GA) + +WinML 2.x is reg-free: the package ships a single self-contained native DLL +that loads directly on Windows 10 19H1 (build 18362) and later. There is no +Windows App SDK bootstrap step. **What we need from the package:** @@ -54,7 +58,7 @@ C ABI (foundry_local_c.h) | `WinMLEpCatalog.h` | `include/` | C API for EP catalog — enumerate, query, download, register | | `WinMLAsync.h` | `include/` | Async block + progress callback infrastructure | | `Microsoft.Windows.AI.MachineLearning.lib` | `lib/native/x64/` | Import library (delay-loaded) | -| `Microsoft.Windows.AI.MachineLearning.dll` | `runtimes-framework/win-x64/native/` | Runtime DLL (deploy alongside) | +| `Microsoft.Windows.AI.MachineLearning.dll` | `runtimes/win-x64/native/` | Runtime DLL (deploy alongside) | **What we do NOT use from the package:** @@ -119,31 +123,32 @@ STDAPI WinMLEpEnsureReadyAsync(WinMLEpHandle ep, WinMLAsyncBlock* async); > which can't be safely exercised via integration tests. Discovery and query paths are > tested through the full stack in `test/sdk_api/ep_detection_test.cc`. -### Phase 1: vcpkg Port for Microsoft.WindowsAppSDK.ML ✅ +### Phase 1: NuGet Acquisition for Microsoft.Windows.AI.MachineLearning ✅ **Goal:** Make the WinML headers and import lib available to CMake. -**Approach:** Custom vcpkg port that extracts from the NuGet package. +**Approach:** FetchContent-based download of the WinML 2.x NuGet package, no vcpkg port needed. -**What the port provides:** -- `WinMLEpCatalog.h`, `WinMLAsync.h` → vcpkg include path -- `Microsoft.Windows.AI.MachineLearning.lib` → vcpkg lib path (x64, arm64) +**What we use from the package:** +- `WinMLEpCatalog.h`, `WinMLAsync.h` → include path +- `Microsoft.Windows.AI.MachineLearning.lib` → import library (x64, arm64) - `Microsoft.Windows.AI.MachineLearning.dll` → copied to output dir for deployment -**What the port excludes:** -- All ORT files (`onnxruntime.dll`, `onnxruntime.lib`, etc.) +**What we exclude:** +- All ORT files (`onnxruntime.dll`, `onnxruntime.lib`, etc.) — Foundry-Local ships its own ORT - `DirectML.dll` (loaded by EPs themselves) - Auto-initializer `.cpp` files - Model catalog headers (we have our own) **CMake integration:** -- `find_package(WinMLEpCatalog)` or direct target -- Windows-only. Guarded by `if(WIN32)`. +- `find_package(WinMLEpCatalog)` — gated behind `FOUNDRY_LOCAL_USE_WINML` +- Windows-only. - Linked with `/DELAYLOAD:Microsoft.Windows.AI.MachineLearning.dll` — the DLL may not be present on older systems **Validation:** Write a minimal test that calls `WinMLEpCatalogCreate()` + -`WinMLEpCatalogEnumProviders()` and prints discovered EPs. Run on Windows 11 24H2+. +`WinMLEpCatalogEnumProviders()` and prints discovered EPs. Run on Windows 10 19H1+ +(WinML 2.x is reg-free; no Windows App SDK bootstrap required). --- @@ -390,9 +395,7 @@ static const std::map kModelIdToRequiredEP = { | Risk | Mitigation | |------|------------| -| vcpkg port for `Microsoft.WindowsAppSDK.ML` doesn't exist | Write custom port extracting from NuGet | -| `Microsoft.Windows.AI.MachineLearning.dll` not on all Windows | Delay-load + graceful fallback + OS version check (26100+) | -| WinAppSDK bootstrapper init for unpackaged apps | Test if `MddBootstrapInitialize` is needed for EP catalog access | +| `Microsoft.Windows.AI.MachineLearning.dll` not present on every Windows install | Delay-load + graceful fallback in `WinMLEpBootstrapper::DiscoverProviders` | | CUDA download URLs / hashes change per release | Hardcode per release, mirroring C# pattern | | ORT `GetEpDevices()` not available in our ORT build | Check ORT version/API availability. Fallback to CPU-only device map | | Cross-platform CUDA EP registration | Windows: download from CDN. Linux: register from co-located `.so` | diff --git a/sdk_v2/cpp/docs/MigrationPlan_20260410.md b/sdk_v2/cpp/docs/MigrationPlan_20260410.md index ae7e904ad..9b78dd335 100644 --- a/sdk_v2/cpp/docs/MigrationPlan_20260410.md +++ b/sdk_v2/cpp/docs/MigrationPlan_20260410.md @@ -195,7 +195,8 @@ Refactored to use Azure SDK native features instead of a custom retry loop: Full implementation plan in [docs/EpDetectionPlan.md](EpDetectionPlan.md). Summary: - Replace stub `EpDetector` with real hardware detection via WinML EP catalog C API - (`WinMLEpCatalog.h` from `Microsoft.WindowsAppSDK.ML` NuGet, acquired via vcpkg). + (`WinMLEpCatalog.h` from `Microsoft.Windows.AI.MachineLearning` NuGet, fetched directly + from nuget.org — WinML 2.x is reg-free, no Windows App SDK bootstrap needed). - `WinMLEpBootstrapper` wraps the WinML C API for EP enumeration + download + registration. - `CudaEpBootstrapper` handles manual CUDA EP download from Azure CDN + registration. - `EpDetector` orchestrator manages bootstrappers, coordinates download, and invalidates diff --git a/sdk_v2/cpp/nuget/pack.py b/sdk_v2/cpp/nuget/pack.py index bcda45e8f..205d6d7fb 100644 --- a/sdk_v2/cpp/nuget/pack.py +++ b/sdk_v2/cpp/nuget/pack.py @@ -43,12 +43,12 @@ # primary library (plus, on Windows, .pdb and .lib companions consumed by the # Python wheel build). We copy that one file into runtimes//native/. # -# WinML builds add one extra non-delay-loaded sibling DLL — -# Microsoft.WindowsAppRuntime.Bootstrap.dll — which is the entry point used to -# register the system Windows App Runtime before any delay-loaded WinML symbol -# resolves. The pipeline only stages it for WinML builds, so we forward any -# entry from OPTIONAL_SIBLINGS that happens to be present in the upstream -# artifact dir; absent files are silently skipped. +# WinML builds add one extra sibling DLL — Microsoft.Windows.AI.MachineLearning.dll +# — that ships the reg-free WinML 2.x runtime. The pipeline only stages it for +# WinML builds (the cmake post-build copy in sdk_v2/cpp/CMakeLists.txt drops it +# next to foundry_local.dll), so we forward any entry from OPTIONAL_SIBLINGS that +# happens to be present in the upstream artifact dir; absent files are silently +# skipped. RIDS: dict[str, tuple[str, str]] = { "win_x64": ("win-x64", "foundry_local.dll"), "win_arm64": ("win-arm64", "foundry_local.dll"), @@ -57,10 +57,11 @@ } # Sibling files copied into runtimes//native/ when present in the upstream -# artifact. WinML builds drop Bootstrap.dll alongside foundry_local.dll; -# standard builds don't, and that's the only signal we need to differentiate. +# artifact. WinML builds drop Microsoft.Windows.AI.MachineLearning.dll alongside +# foundry_local.dll; standard builds don't, and that's the only signal we need +# to differentiate. OPTIONAL_SIBLINGS: tuple[str, ...] = ( - "Microsoft.WindowsAppRuntime.Bootstrap.dll", + "Microsoft.Windows.AI.MachineLearning.dll", ) log = logging.getLogger("pack") @@ -78,8 +79,8 @@ def _parse_args() -> argparse.Namespace: help="Minimum Microsoft.ML.OnnxRuntimeGenAI.Foundry version.") parser.add_argument("--package_id", default="Microsoft.AI.Foundry.Local.Runtime", help="NuGet package id. Use Microsoft.AI.Foundry.Local.Runtime.WinML " - "for the WinML variant (Windows-only RIDs, ORT linked against the " - "WinML-aligned 1.23.x line).") + "for the WinML variant (Windows-only RIDs, ships the WinML 2.x " + "reg-free runtime alongside foundry_local).") for arg_name, (rid, lib) in RIDS.items(): parser.add_argument(f"--{arg_name}", type=Path, default=None, @@ -153,7 +154,7 @@ def stage(args: argparse.Namespace, staging: Path) -> int: # The upstream artifact for each RID is the primary library plus, on # Windows, .pdb / .lib companions used by the Python wheel build, and - # — for the WinML variant — Microsoft.WindowsAppRuntime.Bootstrap.dll. + # — for the WinML variant — Microsoft.Windows.AI.MachineLearning.dll. # We forward the primary library plus any present OPTIONAL_SIBLINGS; # everything else (.pdb, .lib) stays out of the NuGet runtime payload. shutil.copy2(lib_path, native_dir) diff --git a/sdk_v2/cpp/src/ep_detection/cuda_ep_bootstrapper.cc b/sdk_v2/cpp/src/ep_detection/cuda_ep_bootstrapper.cc index 6f50de93d..c2ec408e5 100644 --- a/sdk_v2/cpp/src/ep_detection/cuda_ep_bootstrapper.cc +++ b/sdk_v2/cpp/src/ep_detection/cuda_ep_bootstrapper.cc @@ -28,35 +28,23 @@ constexpr const char* kLockFileName = "cuda-ep.lock"; constexpr const char* kUserAgent = "FoundryLocal"; constexpr int kMaxInstallAttempts = 5; -// CUDA EP package is built against the ONNX Runtime version we link against, so -// WinML and non-WinML builds need separate downloads. Hashes mirror the C# core -// (see neutron.main/src/Service/Providers/Detector/CudaEpBootstrapper.cs). -// WinML build -> ORT 1.23.2 (cuda-ep-20260501-182408.zip) -// Non-WinML -> ORT 1.25.1 (cuda-ep-20260501-062935.zip) -#if defined(FOUNDRY_LOCAL_USE_WINML) && FOUNDRY_LOCAL_USE_WINML -constexpr const char* kDownloadUrl = - "https://foundrypackages-ffhrdhbxb7gpdreh.b02.azurefd.net/cuda-ep-20260501-182408.zip"; -#else +// CUDA EP package is built against the ONNX Runtime version we link against. +// WinML and non-WinML builds now both link ORT 1.25.1, so a single download +// + hash set covers both. The historical WinML-specific ORT 1.23.2 pin (and +// its companion cuda-ep-20260501-182408.zip) was retired when WinML 2.x +// adopted the unified ORT line. constexpr const char* kDownloadUrl = "https://foundrypackages-ffhrdhbxb7gpdreh.b02.azurefd.net/cuda-ep-20260501-062935.zip"; -#endif struct ExpectedBinary { const char* filename; const char* sha256; }; -#if defined(FOUNDRY_LOCAL_USE_WINML) && FOUNDRY_LOCAL_USE_WINML -constexpr ExpectedBinary kExpectedBinaries[] = { - {"onnxruntime_providers_cuda.dll", "4CEF18654878CEFCFCF8488E9C3A705EB5327AA9B5556155C319C9CBB2D98FCF"}, - {"onnxruntime-genai-cuda.dll", "BC953F8E2AAFC6219B2D723B65AB8F1A9426A6B7724D6A01ED756FAE8C3DE6AE"}, -}; -#else constexpr ExpectedBinary kExpectedBinaries[] = { {"onnxruntime_providers_cuda.dll", "DD540FCFECFBC68B4675C9ADF09C2858CF6B054563859D79598AA2524406A76F"}, {"onnxruntime-genai-cuda.dll", "BC953F8E2AAFC6219B2D723B65AB8F1A9426A6B7724D6A01ED756FAE8C3DE6AE"}, }; -#endif constexpr const char* kRegistrationName = "Foundry.CUDA"; constexpr const char* kCudaProviderDll = "onnxruntime_providers_cuda.dll"; diff --git a/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc b/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc index 4172ab395..4a161a79d 100644 --- a/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc +++ b/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc @@ -19,47 +19,6 @@ #define WIN32_LEAN_AND_MEAN #include -namespace { - -/// Checks for Windows 10 build 26100+ (Windows 11 24H2), the minimum OS -/// version that ships the WinML EP catalog. -/// -/// Uses RtlGetVersion (ntdll) because VerifyVersionInfoW lies without a -/// compatibility manifest — it caps the reported version at 6.2 (Win 8.1) -/// unless the app declares support for newer Windows versions. -bool IsWindows11_24H2OrLater() { - // RtlGetVersion is always available and always returns the true OS version. - using RtlGetVersionFn = LONG(WINAPI*)(OSVERSIONINFOW*); - auto* ntdll = GetModuleHandleW(L"ntdll.dll"); - if (!ntdll) { - return false; - } - - auto rtl_get_version = reinterpret_cast( - GetProcAddress(ntdll, "RtlGetVersion")); - if (!rtl_get_version) { - return false; - } - - OSVERSIONINFOW osvi = {}; - osvi.dwOSVersionInfoSize = sizeof(osvi); - if (rtl_get_version(&osvi) != 0) { - return false; - } - - // Windows 11 24H2 = build 26100+ - if (osvi.dwMajorVersion > 10) { - return true; - } - if (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0) { - return osvi.dwBuildNumber >= 26100; - } - - return false; -} - -} // anonymous namespace - namespace fl { #if FOUNDRY_LOCAL_HAS_EP_CATALOG @@ -155,12 +114,6 @@ std::vector> WinMLEpBootstrapper::DiscoverP (void)logger; return {}; #else - if (!IsWindows11_24H2OrLater()) { - logger.Log(LogLevel::Information, - "WinML EP catalog: requires Windows 11 24H2+ (build 26100)"); - return {}; - } - // Pre-check that the WinML DLL is loadable. The DLL is delay-loaded, so // calling WinML functions without it present would cause a structured // exception. Loading it explicitly is cleaner than SEH. diff --git a/sdk_v2/cpp/src/manager.cc b/sdk_v2/cpp/src/manager.cc index 6ec8dbed5..110374ef9 100644 --- a/sdk_v2/cpp/src/manager.cc +++ b/sdk_v2/cpp/src/manager.cc @@ -26,7 +26,6 @@ #include "telemetry/telemetry_action_tracker.h" #include "telemetry/telemetry_logger.h" #include "utils.h" -#include "winml_bootstrap.h" #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN @@ -239,7 +238,7 @@ Manager::Manager(const Configuration& config) std::vector> bootstrappers; #ifdef _WIN32 - // WinML EPs — enumerate from the OS EP catalog (Windows 11 24H2+) + // WinML EPs — enumerate from the OS EP catalog (Windows 10 19H1+ reg-free runtime). auto winml_providers = WinMLEpBootstrapper::DiscoverProviders(register_ep, *logger_); for (auto& p : winml_providers) { bootstrappers.push_back(std::move(p)); @@ -355,30 +354,6 @@ Manager& Manager::Create(const Configuration& config) { "Manager already created. Call Destroy() first."); } - // Optional Windows App SDK bootstrap. When the caller passes Bootstrap=true in - // additional_options we initialize the WinAppSDK framework package for this process. This - // must run before the Manager constructor so that WinML EP discovery (inside - // Manager::Manager) can resolve Microsoft.Windows.AI.MachineLearning.dll. We use a - // temporary stderr logger here because the Manager-owned logger doesn't exist yet; - // bootstrap output is low-volume (one line on success, one warning on failure). Mirrors - // the C# FoundryLocalCore IS_WINML path. Only meaningful in WinML builds; outside that - // configuration TryInitializeWindowsAppSdk is a no-op stub. -#if defined(FOUNDRY_LOCAL_USE_WINML) && FOUNDRY_LOCAL_USE_WINML - { - auto it = config.additional_options.find("Bootstrap"); - constexpr std::string_view kTrue = "true"; - if (it != config.additional_options.end() && it->second.size() == kTrue.size() && - std::equal(it->second.begin(), it->second.end(), kTrue.begin(), - [](char a, char b) { - return std::tolower(static_cast(a)) == - std::tolower(static_cast(b)); - })) { - StderrLogger bootstrap_logger; - TryInitializeWindowsAppSdk(bootstrap_logger); - } - } -#endif - // Construct into a local unique_ptr so a throw between construction and the post-init // telemetry/log calls cleans up the partially-initialized Manager instead of leaking it. // The constructor validates and resolves defaults; if it throws, no Manager exists. @@ -414,14 +389,6 @@ Manager& Manager::Instance() { void Manager::Destroy() { std::lock_guard lock(s_mutex_); s_instance_.reset(); - - // Pair WinAppSDK bootstrap shutdown with Manager teardown. No-op if the bootstrap was - // never initialized for this process. Use a temporary logger for the same reason as in - // Create — the Manager-owned logger has been destroyed by this point. -#if defined(FOUNDRY_LOCAL_USE_WINML) && FOUNDRY_LOCAL_USE_WINML - StderrLogger bootstrap_logger; - ShutdownWindowsAppSdk(bootstrap_logger); -#endif } ICatalog& Manager::GetCatalog() { diff --git a/sdk_v2/cpp/src/winml_bootstrap.cc b/sdk_v2/cpp/src/winml_bootstrap.cc deleted file mode 100644 index 5999a6d54..000000000 --- a/sdk_v2/cpp/src/winml_bootstrap.cc +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -#include "winml_bootstrap.h" - -// Entire translation unit is empty outside WinML builds — see winml_bootstrap.h. Callers -// guard their use sites with the same FOUNDRY_LOCAL_USE_WINML macro, so there are no -// undefined references to no-op stubs. -#if defined(FOUNDRY_LOCAL_USE_WINML) && FOUNDRY_LOCAL_USE_WINML - -#include "logger.h" - -#define WIN32_LEAN_AND_MEAN -#include -#include - -#include -#include -#include - -namespace { - -// Windows App SDK 1.8 — matches the C# FoundryLocalCore reference (majorMinorVersion= -// 0x00010008, minVersion={1,8,1,0}). Update in lockstep with the WinML EP catalog NuGet. -constexpr UINT32 kMajorMinorVersion = 0x00010008; -constexpr UINT16 kMinMajor = 1; -constexpr UINT16 kMinMinor = 8; -constexpr UINT16 kMinBuild = 1; -constexpr UINT16 kMinRevision = 0; - -std::atomic g_initialized{false}; - -} // namespace - -namespace fl { - -bool TryInitializeWindowsAppSdk(ILogger& logger) { - if (g_initialized.load(std::memory_order_acquire)) { - return true; - } - - PACKAGE_VERSION min_version{}; - min_version.Major = kMinMajor; - min_version.Minor = kMinMinor; - min_version.Build = kMinBuild; - min_version.Revision = kMinRevision; - - HRESULT hr = ::MddBootstrapInitialize2( - kMajorMinorVersion, nullptr, min_version, - MddBootstrapInitializeOptions_OnNoMatch_ShowUI); - if (FAILED(hr)) { - char buf[16]; - std::snprintf(buf, sizeof(buf), "%08lX", static_cast(hr)); - logger.Log(LogLevel::Warning, - std::string("WindowsAppSdk bootstrap: MddBootstrapInitialize2 failed (HRESULT=0x") + - buf + "). WinML EP discovery may find no providers."); - return false; - } - - g_initialized.store(true, std::memory_order_release); - logger.Log(LogLevel::Information, - "WindowsAppSdk bootstrap: initialized successfully (WinAppSDK >= 1.8.1.0)."); - return true; -} - -void ShutdownWindowsAppSdk(ILogger& logger) { - if (!g_initialized.exchange(false, std::memory_order_acq_rel)) { - return; - } - - ::MddBootstrapShutdown(); - logger.Log(LogLevel::Information, "WindowsAppSdk bootstrap: shutdown complete."); -} - -} // namespace fl - -#endif // FOUNDRY_LOCAL_USE_WINML diff --git a/sdk_v2/cpp/src/winml_bootstrap.h b/sdk_v2/cpp/src/winml_bootstrap.h deleted file mode 100644 index 4a068c83a..000000000 --- a/sdk_v2/cpp/src/winml_bootstrap.h +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -// -// Optional Windows App SDK bootstrap helper for non-packaged consumer processes (e.g. the -// JavaScript binding loaded into Node, where the host process has no built-in WinAppSDK -// activation). When opted in via additional_options["Bootstrap"]="true", initializes the -// Windows App Runtime framework package so that APIs depending on it — notably the WinML EP -// catalog DLL `Microsoft.Windows.AI.MachineLearning.dll` consumed by `WinMLEpBootstrapper` — -// can resolve at runtime. Defaults to off; matches the C# FoundryLocalCore behavior. -// -// Compiled only in WinML builds (FOUNDRY_LOCAL_USE_WINML=ON), which already take a hard -// dependency on the WindowsAppSDK NuGet. Outside that configuration the header is empty and -// callers must guard their use sites with the same FOUNDRY_LOCAL_USE_WINML macro. -#pragma once - -#if defined(FOUNDRY_LOCAL_USE_WINML) && FOUNDRY_LOCAL_USE_WINML - -namespace fl { - -class ILogger; - -/// Initialize the Windows App Runtime framework package for this process by calling -/// MddBootstrapInitialize2 with WinAppSDK 1.8 minimum. Idempotent — subsequent calls are -/// no-ops once initialized. -/// -/// Returns true if bootstrap succeeded (or was already initialized). Returns false on -/// failure; the reason is logged but the process continues — WinML EP discovery will simply -/// find no providers. -bool TryInitializeWindowsAppSdk(ILogger& logger); - -/// Reverse the effects of TryInitializeWindowsAppSdk(). Safe to call even if init never -/// succeeded. After this call the WinAppSDK framework package should not be used. -void ShutdownWindowsAppSdk(ILogger& logger); - -} // namespace fl - -#endif // FOUNDRY_LOCAL_USE_WINML diff --git a/sdk_v2/cpp/test/CMakeLists.txt b/sdk_v2/cpp/test/CMakeLists.txt index e14a9fc4d..343b3ca25 100644 --- a/sdk_v2/cpp/test/CMakeLists.txt +++ b/sdk_v2/cpp/test/CMakeLists.txt @@ -116,9 +116,14 @@ else() endif() include(GoogleTest) +# DISCOVERY_TIMEOUT defaults to 5s, which is not enough for the WinML build: +# foundry_local.dll links against Microsoft.Windows.AI.MachineLearning.dll +# (delay-loaded) plus standalone ORT, and process startup + gtest enumeration +# can exceed 5s on cold caches or slow CI agents. Bump to 60s for headroom. gtest_discover_tests(foundry_local_tests WORKING_DIRECTORY $ DISCOVERY_MODE PRE_TEST + DISCOVERY_TIMEOUT 60 ) # ========================================================================== @@ -262,4 +267,5 @@ endif() gtest_discover_tests(cache_only_tests WORKING_DIRECTORY $ DISCOVERY_MODE PRE_TEST + DISCOVERY_TIMEOUT 60 ) diff --git a/sdk_v2/cpp/test/sdk_api/ep_detection_test.cc b/sdk_v2/cpp/test/sdk_api/ep_detection_test.cc index dee373a04..993fc0bd3 100644 --- a/sdk_v2/cpp/test/sdk_api/ep_detection_test.cc +++ b/sdk_v2/cpp/test/sdk_api/ep_detection_test.cc @@ -75,7 +75,7 @@ TEST_F(EpDetectionApiTest, IsEpDownloadInProgress_ConsistentAcrossCalls) { } #ifdef _WIN32 -// On Windows 11 24H2+, WinML should discover at least one EP. +// WinML 2.x reg-free runtime supports Windows 10 19H1 (build 18362) and later. // This test verifies the full Manager → EpDetector → WinMLEpBootstrapper chain. TEST_F(EpDetectionApiTest, GetDiscoverableEps_WindowsHasWinMLProviders) { auto eps = manager().GetDiscoverableEps(); diff --git a/sdk_v2/cs/src/FoundryLocalManager.cs b/sdk_v2/cs/src/FoundryLocalManager.cs index 4453c44f1..7099b1ea9 100644 --- a/sdk_v2/cs/src/FoundryLocalManager.cs +++ b/sdk_v2/cs/src/FoundryLocalManager.cs @@ -240,8 +240,7 @@ await Task.Run(() => } } - // Merge AdditionalSettings with build-flavor-specific defaults (e.g. WinML Bootstrap). - // Done as a local dict so we don't mutate the user-supplied AdditionalSettings. + // Merge AdditionalSettings with user-supplied entries. var additionalSettings = new Dictionary(StringComparer.Ordinal); if (_config.AdditionalSettings != null) { @@ -255,15 +254,6 @@ await Task.Run(() => } } -#if IS_WINML - // WinML build needs the native side to bootstrap the Windows App Runtime. - // Caller can override by setting "Bootstrap" explicitly in AdditionalSettings. - if (!additionalSettings.ContainsKey("Bootstrap")) - { - additionalSettings["Bootstrap"] = "true"; - } -#endif - if (additionalSettings.Count > 0) { // Create a KeyValuePairs from additional settings diff --git a/sdk_v2/cs/src/Microsoft.AI.Foundry.Local.csproj b/sdk_v2/cs/src/Microsoft.AI.Foundry.Local.csproj index 8114eadaf..378ac27e0 100644 --- a/sdk_v2/cs/src/Microsoft.AI.Foundry.Local.csproj +++ b/sdk_v2/cs/src/Microsoft.AI.Foundry.Local.csproj @@ -89,9 +89,8 @@ Microsoft.AI.Foundry.Local.WinML Microsoft.AI.Foundry.Local.WinML $(DefineConstants);IS_WINML - net9.0-windows10.0.26100.0 + net9.0-windows10.0.18362.0 win-x64;win-arm64 - 10.0.17763.0 diff --git a/sdk_v2/deps_versions_winml.json b/sdk_v2/deps_versions_winml.json deleted file mode 100644 index c2f43bfb5..000000000 --- a/sdk_v2/deps_versions_winml.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "_comment": "WinML variant of sdk_v2/deps_versions.json. Selected by FOUNDRY_LOCAL_USE_WINML=ON (cmake) and FL_PYTHON_PACKAGE_NAME=foundry-local-sdk-winml (python build backend).", - "onnxruntime": { "version": "1.23.2.3" }, - "onnxruntime-genai": { "version": "0.13.2" } -} diff --git a/sdk_v2/js/script/copy-native.mjs b/sdk_v2/js/script/copy-native.mjs index 8fcb05340..c52fd0b37 100644 --- a/sdk_v2/js/script/copy-native.mjs +++ b/sdk_v2/js/script/copy-native.mjs @@ -43,7 +43,7 @@ const destDir = resolve(pkgRoot, "prebuilds", `${process.platform}-${process.arc mkdirSync(destDir, { recursive: true }); // Files to copy: the foundry_local shared lib + its sibling runtime deps -// (ORT, ORT-GenAI, and on Windows the WinML/AppRuntime bootstraps). The +// (ORT, ORT-GenAI, and on Windows the WinML 2.x reg-free runtime DLL). The // addon's process must be able to resolve foundry_local's load-time deps // via the OS default search (which on Windows includes the addon's own // directory, on POSIX is satisfied by our rpath of $ORIGIN/@loader_path). @@ -55,7 +55,6 @@ const wanted = (() => { "onnxruntime-genai.dll", "onnxruntime_providers_shared.dll", "Microsoft.Windows.AI.MachineLearning.dll", - "Microsoft.WindowsAppRuntime.Bootstrap.dll", ]; } if (process.platform === "darwin") { diff --git a/sdk_v2/js/src/foundryLocalManager.ts b/sdk_v2/js/src/foundryLocalManager.ts index 3830c00da..e3499462c 100644 --- a/sdk_v2/js/src/foundryLocalManager.ts +++ b/sdk_v2/js/src/foundryLocalManager.ts @@ -3,9 +3,6 @@ // `FoundryLocalError` from the native addon. `create()` and `createAsync()` are factory wrappers around the // constructor — the native layer is the source of truth for instance identity. -import { existsSync } from "node:fs"; -import { resolve } from "node:path"; - import { type Catalog, wrapNativeCatalog } from "./catalog.js"; import { FOUNDRY_LOCAL_CONFIG_KEYS, type FoundryLocalConfig } from "./configuration.js"; import { @@ -13,38 +10,9 @@ import { configureNativeLoader, getAddon, getPreloadedLibraryPath, - getResolvedLibraryDir, } from "./detail/native.js"; import type { EpDownloadResult, EpInfo } from "./types.js"; -/** - * On Windows WinML builds, the native side needs the Windows App Runtime bootstrapped (the build co-locates - * `Microsoft.WindowsAppRuntime.Bootstrap.dll` next to `foundry_local.dll`). Detect that layout at runtime and - * inject `additionalSettings.Bootstrap = "true"` unless the caller already set it. Returns a shallow-copied - * config so the caller's `additionalSettings` is never mutated. No-op on non-Windows. - * - * Mirrors the `IS_WINML`-gated block in C# `FoundryLocalManager` (sdk_v2/cs/src/FoundryLocalManager.cs), but - * driven by a filesystem probe instead of a compile-time flag. - */ - -export function applyBootstrapAutoDetect(config: FoundryLocalConfig, libraryDir: string): FoundryLocalConfig { - if (process.platform !== "win32") return config; - const existing = config.additionalSettings; - // Defer to native validation when additionalSettings is present but not a plain object — spreading a string, - // array, or other non-record would silently produce a "valid" object and mask the TypeError the native layer - // would otherwise throw. - if (existing !== undefined && (existing === null || typeof existing !== "object" || Array.isArray(existing))) { - return config; - } - if (existing?.Bootstrap !== undefined) return config; - const bootstrap = resolve(libraryDir, "Microsoft.WindowsAppRuntime.Bootstrap.dll"); - if (!existsSync(bootstrap)) return config; - return { - ...config, - additionalSettings: { ...(existing ?? {}), Bootstrap: "true" }, - }; -} - export class FoundryLocalManager { readonly #native: NativeManager; #catalog: Catalog | undefined; @@ -89,8 +57,7 @@ export class FoundryLocalManager { } } } - const merged = applyBootstrapAutoDetect(config, getResolvedLibraryDir(config.libraryPath)); - this.#native = new (getAddon().Manager)(merged); + this.#native = new (getAddon().Manager)(config); } /** diff --git a/sdk_v2/js/test/bootstrap-autodetect.test.ts b/sdk_v2/js/test/bootstrap-autodetect.test.ts deleted file mode 100644 index 9295e2ccb..000000000 --- a/sdk_v2/js/test/bootstrap-autodetect.test.ts +++ /dev/null @@ -1,75 +0,0 @@ -// Tests for `applyBootstrapAutoDetect`. Exercised directly (no addon load required) — that's the whole point -// of extracting it as a pure helper. -import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; -import { tmpdir } from "node:os"; -import { join } from "node:path"; - -import { afterEach, beforeEach, describe, expect, it } from "vitest"; - -import { applyBootstrapAutoDetect } from "../src/foundryLocalManager.js"; - -// Platform-conditional registration (rather than `describe.skip`) so the inactive -// suite is never collected — keeps vitest's skipped count at 0 on both platforms, -// so a non-zero skip count is always a real signal worth investigating. -const isWindows = process.platform === "win32"; - -if (isWindows) describe("applyBootstrapAutoDetect (Windows)", () => { - let tmpDir: string; - - beforeEach(() => { - tmpDir = mkdtempSync(join(tmpdir(), "fl-js-v2-bootstrap-")); - }); - - afterEach(() => { - rmSync(tmpDir, { recursive: true, force: true }); - }); - - it("injects Bootstrap=true when the bootstrap DLL is co-located", () => { - writeFileSync(join(tmpDir, "Microsoft.WindowsAppRuntime.Bootstrap.dll"), "dummy"); - const result = applyBootstrapAutoDetect({ appName: "test" }, tmpDir); - expect(result.additionalSettings).toEqual({ Bootstrap: "true" }); - }); - - it("preserves existing additionalSettings while injecting Bootstrap", () => { - writeFileSync(join(tmpDir, "Microsoft.WindowsAppRuntime.Bootstrap.dll"), "dummy"); - const result = applyBootstrapAutoDetect({ appName: "test", additionalSettings: { foo: "bar" } }, tmpDir); - expect(result.additionalSettings).toEqual({ foo: "bar", Bootstrap: "true" }); - }); - - it("does not overwrite a caller-supplied Bootstrap value", () => { - writeFileSync(join(tmpDir, "Microsoft.WindowsAppRuntime.Bootstrap.dll"), "dummy"); - const result = applyBootstrapAutoDetect({ appName: "test", additionalSettings: { Bootstrap: "false" } }, tmpDir); - expect(result.additionalSettings).toEqual({ Bootstrap: "false" }); - }); - - it("does not mutate the caller's config or additionalSettings object", () => { - writeFileSync(join(tmpDir, "Microsoft.WindowsAppRuntime.Bootstrap.dll"), "dummy"); - const original = { appName: "test", additionalSettings: { foo: "bar" } }; - const originalSettings = original.additionalSettings; - const result = applyBootstrapAutoDetect(original, tmpDir); - expect(original.additionalSettings).toBe(originalSettings); - expect(original.additionalSettings).toEqual({ foo: "bar" }); - expect(result).not.toBe(original); - expect(result.additionalSettings).not.toBe(originalSettings); - }); - - it("returns the config unchanged when the bootstrap DLL is absent", () => { - const config = { appName: "test", additionalSettings: { foo: "bar" } }; - const result = applyBootstrapAutoDetect(config, tmpDir); - expect(result).toBe(config); - }); -}); - -if (!isWindows) describe("applyBootstrapAutoDetect (non-Windows)", () => { - it("is a no-op regardless of files present in the directory", () => { - const tmp = mkdtempSync(join(tmpdir(), "fl-js-v2-bootstrap-")); - try { - writeFileSync(join(tmp, "Microsoft.WindowsAppRuntime.Bootstrap.dll"), "dummy"); - const config = { appName: "test" }; - const result = applyBootstrapAutoDetect(config, tmp); - expect(result).toBe(config); - } finally { - rmSync(tmp, { recursive: true, force: true }); - } - }); -}); diff --git a/sdk_v2/python/README.md b/sdk_v2/python/README.md index a8291525b..3d7b1bbc3 100644 --- a/sdk_v2/python/README.md +++ b/sdk_v2/python/README.md @@ -145,7 +145,6 @@ config = Configuration( app_name="MyApp", model_cache_dir="/path/to/cache", # optional log_level=LogLevel.INFORMATION, # optional (default: Warning) - additional_settings={"Bootstrap": "false"}, # optional. winml only ) FoundryLocalManager.initialize(config) manager = FoundryLocalManager.instance diff --git a/sdk_v2/python/_build_backend/__init__.py b/sdk_v2/python/_build_backend/__init__.py index d3bb42dd3..87f920cf3 100644 --- a/sdk_v2/python/_build_backend/__init__.py +++ b/sdk_v2/python/_build_backend/__init__.py @@ -54,7 +54,6 @@ _PYPROJECT = Path(__file__).resolve().parent.parent / "pyproject.toml" _SDK_V2_ROOT = _PYPROJECT.resolve().parent.parent _DEPS_JSON_STD = _SDK_V2_ROOT / "deps_versions.json" -_DEPS_JSON_WINML = _SDK_V2_ROOT / "deps_versions_winml.json" # Match ``name = "..."`` only inside the [project] table. The regex is # anchored to the first ``name = "foundry-local-sdk"`` occurrence which @@ -108,16 +107,16 @@ def _patch_pyproject_text(original: str, *, override_name: str | None, deps_file def _maybe_patch_name() -> Generator[None, None, None]: """Context manager that rewrites pyproject.toml during PEP 517 hook execution. - Always rewrites ORT/GenAI version pins from the appropriate deps JSON - (single source of truth). Conditionally rewrites the project name when - ``FL_PYTHON_PACKAGE_NAME`` selects the WinML variant. + Always rewrites ORT/GenAI version pins from deps_versions.json (single + source of truth). Conditionally rewrites the project name when + ``FL_PYTHON_PACKAGE_NAME`` selects the WinML variant. The WinML and + standard flavors share the same ORT/GenAI versions; only the + distribution name differs. """ override = os.environ.get(_ENV_VAR, "").strip() or None - is_winml = override == _WINML_PKG_NAME - deps_file = _DEPS_JSON_WINML if is_winml else _DEPS_JSON_STD original = _PYPROJECT.read_text(encoding="utf-8") - patched = _patch_pyproject_text(original, override_name=override, deps_file=deps_file) + patched = _patch_pyproject_text(original, override_name=override, deps_file=_DEPS_JSON_STD) if patched == original: # Nothing to rewrite (e.g. JSON already matches and no name override). diff --git a/sdk_v2/python/pyproject.toml b/sdk_v2/python/pyproject.toml index d1bc1d71c..7b6e7478b 100644 --- a/sdk_v2/python/pyproject.toml +++ b/sdk_v2/python/pyproject.toml @@ -38,10 +38,10 @@ classifiers = [ ] # ORT/GenAI version pins below use sentinel ``0.0.0``. The real versions -# come from sdk_v2/deps_versions.json (or deps_versions_winml.json for the -# WinML variant) — the _build_backend rewrites these pins from JSON at -# wheel-build time. JSON is the single source of truth; bumping ORT -# versions is a one-file edit. +# come from sdk_v2/deps_versions.json — the _build_backend rewrites these +# pins from JSON at wheel-build time. JSON is the single source of truth; +# bumping ORT versions is a one-file edit. The WinML wheel variant shares +# the same ORT/GenAI versions and only differs in the distribution name. # # If a wheel ever ships with ``==0.0.0`` it means the backend wasn't # invoked (e.g. raw setuptools bypass) — pip install will fail loudly diff --git a/sdk_v2/python/test/integration/test_configuration_native.py b/sdk_v2/python/test/integration/test_configuration_native.py index 1b8e99960..1874e57d0 100644 --- a/sdk_v2/python/test/integration/test_configuration_native.py +++ b/sdk_v2/python/test/integration/test_configuration_native.py @@ -71,7 +71,7 @@ def test_catalog_urls_accepted(self): def test_additional_settings_accepted(self): c = Configuration( app_name="BuildNativeTest", - additional_settings={"Bootstrap": "false", "K2": "v2"}, + additional_settings={"K1": "v1", "K2": "v2"}, ) with _native_config(c) as ptr: assert ptr is not None From 331c4dbf5a453d901d749be7ad698f565f7abd8c Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Wed, 10 Jun 2026 02:34:23 -0500 Subject: [PATCH 02/35] Nits --- .pipelines/foundry-local-packaging.yml | 7 +--- .pipelines/v2/sdk_v2-pipeline-plan.md | 4 +- .../v2/templates/steps-build-windows.yml | 11 ++---- .../v2/templates/steps-prefetch-nuget.yml | 7 ++-- sdk_v2/cpp/CMakeLists.txt | 7 +--- sdk_v2/cpp/cmake/FindOnnxRuntime.cmake | 3 +- sdk_v2/cpp/cmake/FindOnnxRuntimeGenAI.cmake | 3 -- sdk_v2/cpp/cmake/FindWinMLEpCatalog.cmake | 2 +- sdk_v2/cpp/docs/EpDetectionPlan.md | 3 +- .../src/ep_detection/cuda_ep_bootstrapper.cc | 4 -- .../src/ep_detection/winml_ep_bootstrapper.cc | 37 ++++++++++++++++++- sdk_v2/cs/src/FoundryLocalManager.cs | 1 + .../cs/src/Microsoft.AI.Foundry.Local.csproj | 1 + 13 files changed, 53 insertions(+), 37 deletions(-) diff --git a/.pipelines/foundry-local-packaging.yml b/.pipelines/foundry-local-packaging.yml index decdbb837..844348a78 100644 --- a/.pipelines/foundry-local-packaging.yml +++ b/.pipelines/foundry-local-packaging.yml @@ -54,16 +54,13 @@ parameters: variables: - group: FoundryLocal-ESRP-Signing # C++ SDK (sdk_v2/cpp) native dependency versions. Must match cmake defaults -# in sdk_v2/deps_versions.json. WinML and non-WinML builds now share a single -# ORT line (WinML 2.x is reg-free and uses the standard ORT package). +# in sdk_v2/deps_versions.json. - name: cppOrtVersion value: '1.25.1' - name: cppGenaiVersion value: '0.13.2' - name: cppWinmlVersion - # Microsoft.Windows.AI.MachineLearning (WinML 2.x). Reg-free, no Windows App - # SDK bootstrap, supports Windows 10 19H1 (build 18362) and later. - value: '2.1.6' + value: '2.1.70' - name: cppBuildConfig value: 'RelWithDebInfo' diff --git a/.pipelines/v2/sdk_v2-pipeline-plan.md b/.pipelines/v2/sdk_v2-pipeline-plan.md index d52ff16af..c88e1af34 100644 --- a/.pipelines/v2/sdk_v2-pipeline-plan.md +++ b/.pipelines/v2/sdk_v2-pipeline-plan.md @@ -70,8 +70,6 @@ are gated separately via `.pipelines/v1/templates/stages-sdk-v1.yml`. 8. **Single source of truth for ORT/GenAI versions.** ORT and GenAI versions live in `sdk_v2/deps_versions.json`. The file shape is `{ "onnxruntime": { "version": "..." }, "onnxruntime-genai": { "version": "..." } }`. - WinML and non-WinML builds share the same ORT pin (WinML 2.x is reg-free - and uses the standard ORT package). Consumers: - **C++ build:** `sdk_v2/cpp/cmake/Find{OnnxRuntime,OnnxRuntimeGenAI}.cmake` read versions via `file(READ)` + `string(JSON ... GET ...)`. The @@ -254,7 +252,7 @@ Versions are pipeline-level variables, currently: * `ortVersion` `1.25.1` (`Microsoft.ML.OnnxRuntime.Foundry`) * `genaiVersion` `0.13.2` (`Microsoft.ML.OnnxRuntimeGenAI.Foundry`) -* `winmlVersion` `2.1.6` (`Microsoft.Windows.AI.MachineLearning`, WinML 2.x reg-free) +* `winmlVersion` `2.1.70` (`Microsoft.Windows.AI.MachineLearning`, WinML 2.x reg-free) These must be kept in sync with the cmake defaults and with `sdk_v2/deps_versions.json` (decision 8). When bumping, update both places diff --git a/.pipelines/v2/templates/steps-build-windows.yml b/.pipelines/v2/templates/steps-build-windows.yml index 0b2f6ecc2..0e17f07b6 100644 --- a/.pipelines/v2/templates/steps-build-windows.yml +++ b/.pipelines/v2/templates/steps-build-windows.yml @@ -9,9 +9,8 @@ # buildConfig – CMake config (Debug, Release, RelWithDebInfo, MinSizeRel) # ortVersion – Microsoft.ML.OnnxRuntime.Foundry version # genaiVersion – Microsoft.ML.OnnxRuntimeGenAI.Foundry version -# winmlVersion – Microsoft.Windows.AI.MachineLearning version (WinML 2.x reg-free) -# useWinml – Build the WinML variant (--use_winml). WinML and non-WinML -# builds now share the same ORT version. +# winmlVersion – Microsoft.Windows.AI.MachineLearning version +# useWinml – Build the WinML variant (--use_winml). # runTests – Whether to run tests # stageHeaders – Whether to stage public headers as a separate artifact @@ -152,10 +151,8 @@ steps: # sdk_v2/cpp/triplets/x64-windows.cmake), so the only runtime payload is # foundry_local.dll itself. # - WinML build: foundry_local.dll picks up one extra runtime dependency, -# Microsoft.Windows.AI.MachineLearning.dll. WinML 2.x is reg-free, so this -# single self-contained native DLL replaces the previous WinAppSDK bootstrap -# plumbing — no MddBootstrapInitialize2 and no separate Bootstrap.dll. The -# WinML DLL is delay-loaded (see /DELAYLOAD in CMakeLists.txt) so it is +# Microsoft.Windows.AI.MachineLearning.dll, which must travel with the wheel. +# The WinML DLL is delay-loaded (see /DELAYLOAD in CMakeLists.txt) so it is # NOT needed at foundry_local.dll load time, but the cmake post-build copy # stages it next to foundry_local.dll for runtime EP discovery. ORT/GenAI # come from the onnxruntime-core / onnxruntime-genai-core pip deps. diff --git a/.pipelines/v2/templates/steps-prefetch-nuget.yml b/.pipelines/v2/templates/steps-prefetch-nuget.yml index 4bc608d82..47c0c9a6b 100644 --- a/.pipelines/v2/templates/steps-prefetch-nuget.yml +++ b/.pipelines/v2/templates/steps-prefetch-nuget.yml @@ -1,5 +1,5 @@ -# Pre-download ORT / GenAI / (optionally) WinML NuGet packages from -# nuget.org and emit cmake_extra_defines pointing at them. +# Pre-download ORT / GenAI / (optionally) WinML NuGet packages from the +# aiinfra ADO feed and emit cmake_extra_defines pointing at them. # # Sets the pipeline variable `cmakeFetchDefines` containing the space-separated # `KEY=PATH` pairs to pass to build.py --cmake_extra_defines. @@ -7,7 +7,7 @@ # Parameters: # ortVersion – Microsoft.ML.OnnxRuntime.Foundry version # genaiVersion – Microsoft.ML.OnnxRuntimeGenAI.Foundry version -# winmlVersion – Microsoft.Windows.AI.MachineLearning version (Windows only, e.g. 2.1.6) +# winmlVersion – Microsoft.Windows.AI.MachineLearning version (Windows only) # includeWinml – Download WinML and emit WINML_EP_CATALOG_FETCH_URL # includeOrtGpuLinux – Also download Microsoft.ML.OnnxRuntime.Gpu.Linux (Linux only) # shell – 'pwsh' (Windows/macOS) or 'bash' (Linux) @@ -76,7 +76,6 @@ steps: if ($${{ parameters.includeWinml }}) { # WinML 2.x (Microsoft.Windows.AI.MachineLearning) is reg-free — a single self-contained # native package with no transitive Windows App SDK Foundation dependency to resolve. - # Direct download from nuget.org is sufficient. $winmlUrl = "$feed/Microsoft.Windows.AI.MachineLearning/${{ parameters.winmlVersion }}" $winmlOut = "$cacheDir/winml.nupkg" Write-Host "Downloading Microsoft.Windows.AI.MachineLearning ${{ parameters.winmlVersion }}" diff --git a/sdk_v2/cpp/CMakeLists.txt b/sdk_v2/cpp/CMakeLists.txt index 9987a8f90..b2e71e66e 100644 --- a/sdk_v2/cpp/CMakeLists.txt +++ b/sdk_v2/cpp/CMakeLists.txt @@ -78,8 +78,7 @@ if(FOUNDRY_LOCAL_BUILD_TESTS) enable_testing() endif() -# ORT and ORT GenAI — acquired via FetchContent from nuget.org. WinML and non-WinML -# builds share the same ORT package and version. +# ORT and ORT GenAI — acquired via FetchContent from nuget.org. list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") find_package(OnnxRuntimeGenAI REQUIRED) find_package(OnnxRuntime REQUIRED) @@ -403,9 +402,7 @@ if(TARGET OnnxRuntime::OnnxRuntime) ) endif() - # Copy WinML EP Catalog DLL for runtime EP discovery. WinML 2.x is reg-free — - # the DLL loads directly with no Windows App SDK bootstrap, so co-locating it - # is the only runtime requirement. + # Copy WinML EP Catalog DLL for runtime EP discovery if(WinMLEpCatalog_FOUND AND EXISTS "${WINML_EP_CATALOG_DLL_DIR}/Microsoft.Windows.AI.MachineLearning.dll") add_custom_command(TARGET foundry_local POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different diff --git a/sdk_v2/cpp/cmake/FindOnnxRuntime.cmake b/sdk_v2/cpp/cmake/FindOnnxRuntime.cmake index 98d01373d..0982d511a 100644 --- a/sdk_v2/cpp/cmake/FindOnnxRuntime.cmake +++ b/sdk_v2/cpp/cmake/FindOnnxRuntime.cmake @@ -242,8 +242,7 @@ else() set_target_properties(OnnxRuntime::OnnxRuntime PROPERTIES IMPORTED_IMPLIB "${_ORT_LIB_DIR}/onnxruntime.lib" ) - # On Windows the runtime DLL sits next to the import lib for both flavors — - # the historical WinML SDK layout (runtimes-framework/) is no longer used. + # On Windows, the runtime DLL sits next to the import lib for both flavors. if(NOT _ORT_DLL_DIR) set(_ORT_DLL_DIR "${_ORT_LIB_DIR}") endif() diff --git a/sdk_v2/cpp/cmake/FindOnnxRuntimeGenAI.cmake b/sdk_v2/cpp/cmake/FindOnnxRuntimeGenAI.cmake index 3163b6f11..f83002d64 100644 --- a/sdk_v2/cpp/cmake/FindOnnxRuntimeGenAI.cmake +++ b/sdk_v2/cpp/cmake/FindOnnxRuntimeGenAI.cmake @@ -3,9 +3,6 @@ # # All platforms / flavors: Microsoft.ML.OnnxRuntimeGenAI.Foundry # -# WinML and non-WinML builds share the same GenAI package and version, pinned -# in sdk_v2/deps_versions.json. -# # When ORT_GENAI_HOME is set, uses the local ORT GenAI build instead of NuGet. # Otherwise uses FetchContent from nuget.org. # Creates an IMPORTED target: OnnxRuntimeGenAI::OnnxRuntimeGenAI diff --git a/sdk_v2/cpp/cmake/FindWinMLEpCatalog.cmake b/sdk_v2/cpp/cmake/FindWinMLEpCatalog.cmake index e08691753..7debe49bf 100644 --- a/sdk_v2/cpp/cmake/FindWinMLEpCatalog.cmake +++ b/sdk_v2/cpp/cmake/FindWinMLEpCatalog.cmake @@ -30,7 +30,7 @@ endif() # Latest GA Microsoft.Windows.AI.MachineLearning on nuget.org. Bump as new GA # releases ship; the WinMLEpCatalog.h C ABI is stable across 2.0.x and 2.1.x. -set(_WINML_EP_CATALOG_MIN_VERSION "2.1.6") +set(_WINML_EP_CATALOG_MIN_VERSION "2.1.70") if(NOT WINML_EP_CATALOG_VERSION) set(WINML_EP_CATALOG_VERSION "${_WINML_EP_CATALOG_MIN_VERSION}") diff --git a/sdk_v2/cpp/docs/EpDetectionPlan.md b/sdk_v2/cpp/docs/EpDetectionPlan.md index 2e4c46ad8..fd6f74981 100644 --- a/sdk_v2/cpp/docs/EpDetectionPlan.md +++ b/sdk_v2/cpp/docs/EpDetectionPlan.md @@ -45,7 +45,7 @@ C ABI (foundry_local_c.h) ### Microsoft.Windows.AI.MachineLearning NuGet (WinML 2.x) -**Package:** `Microsoft.Windows.AI.MachineLearning` 2.1.6 (or newer GA) +**Package:** `Microsoft.Windows.AI.MachineLearning` 2.1.70 (or newer GA) WinML 2.x is reg-free: the package ships a single self-contained native DLL that loads directly on Windows 10 19H1 (build 18362) and later. There is no @@ -148,7 +148,6 @@ STDAPI WinMLEpEnsureReadyAsync(WinMLEpHandle ep, WinMLAsyncBlock* async); **Validation:** Write a minimal test that calls `WinMLEpCatalogCreate()` + `WinMLEpCatalogEnumProviders()` and prints discovered EPs. Run on Windows 10 19H1+ -(WinML 2.x is reg-free; no Windows App SDK bootstrap required). --- diff --git a/sdk_v2/cpp/src/ep_detection/cuda_ep_bootstrapper.cc b/sdk_v2/cpp/src/ep_detection/cuda_ep_bootstrapper.cc index c2ec408e5..c9c89d298 100644 --- a/sdk_v2/cpp/src/ep_detection/cuda_ep_bootstrapper.cc +++ b/sdk_v2/cpp/src/ep_detection/cuda_ep_bootstrapper.cc @@ -29,10 +29,6 @@ constexpr const char* kUserAgent = "FoundryLocal"; constexpr int kMaxInstallAttempts = 5; // CUDA EP package is built against the ONNX Runtime version we link against. -// WinML and non-WinML builds now both link ORT 1.25.1, so a single download -// + hash set covers both. The historical WinML-specific ORT 1.23.2 pin (and -// its companion cuda-ep-20260501-182408.zip) was retired when WinML 2.x -// adopted the unified ORT line. constexpr const char* kDownloadUrl = "https://foundrypackages-ffhrdhbxb7gpdreh.b02.azurefd.net/cuda-ep-20260501-062935.zip"; diff --git a/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc b/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc index 4a161a79d..1b77bb763 100644 --- a/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc +++ b/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc @@ -21,6 +21,33 @@ namespace fl { +namespace { + +/// Look up the running Windows build number via ``ntdll!RtlGetVersion``. We +/// use ``GetProcAddress`` instead of including ```` to avoid the +/// header's macro pollution; the function signature is stable and documented. +/// Returns 0 if the lookup fails (purely diagnostic — never gates behavior). +DWORD QueryWindowsBuild() { + using RtlGetVersionFn = LONG(WINAPI*)(PRTL_OSVERSIONINFOW); + HMODULE ntdll = ::GetModuleHandleW(L"ntdll.dll"); + if (!ntdll) { + return 0; + } + auto rtl_get_version = reinterpret_cast( + ::GetProcAddress(ntdll, "RtlGetVersion")); + if (!rtl_get_version) { + return 0; + } + RTL_OSVERSIONINFOW info{}; + info.dwOSVersionInfoSize = sizeof(info); + if (rtl_get_version(&info) != 0) { + return 0; + } + return info.dwBuildNumber; +} + +} // namespace + #if FOUNDRY_LOCAL_HAS_EP_CATALOG WinMLEpBootstrapper::WinMLEpBootstrapper(std::string name, EpRegistrationCallback register_ep, std::shared_ptr catalog_ref, WinMLEpHandle ep_handle) @@ -120,8 +147,16 @@ std::vector> WinMLEpBootstrapper::DiscoverP HMODULE winml_dll = LoadLibraryW(L"Microsoft.Windows.AI.MachineLearning.dll"); if (!winml_dll) { + // Diagnostic only — older Windows (< build 18362) ships without + // Microsoft.Windows.AI.MachineLearning.dll, so EP discovery is expected + // to fail there. Include GetLastError() and the OS build to give the + // user a clear hint instead of an opaque "DLL not available". + DWORD load_err = ::GetLastError(); + DWORD os_build = QueryWindowsBuild(); logger.Log(LogLevel::Information, - "WinML EP catalog: DLL not available — EP discovery disabled"); + fmt::format("WinML EP catalog: DLL not available — EP discovery disabled " + "(LoadLibrary err={}, Windows build {})", + load_err, os_build)); return {}; } // Keep the DLL loaded — the delay-load stubs will resolve against it. diff --git a/sdk_v2/cs/src/FoundryLocalManager.cs b/sdk_v2/cs/src/FoundryLocalManager.cs index 7099b1ea9..54863556f 100644 --- a/sdk_v2/cs/src/FoundryLocalManager.cs +++ b/sdk_v2/cs/src/FoundryLocalManager.cs @@ -241,6 +241,7 @@ await Task.Run(() => } // Merge AdditionalSettings with user-supplied entries. + // Done as a local dict so we don't mutate the user-supplied AdditionalSettings. var additionalSettings = new Dictionary(StringComparer.Ordinal); if (_config.AdditionalSettings != null) { diff --git a/sdk_v2/cs/src/Microsoft.AI.Foundry.Local.csproj b/sdk_v2/cs/src/Microsoft.AI.Foundry.Local.csproj index 378ac27e0..17adc8709 100644 --- a/sdk_v2/cs/src/Microsoft.AI.Foundry.Local.csproj +++ b/sdk_v2/cs/src/Microsoft.AI.Foundry.Local.csproj @@ -91,6 +91,7 @@ $(DefineConstants);IS_WINML net9.0-windows10.0.18362.0 win-x64;win-arm64 + 10.0.17763.0 From 0187b8ff171625d1520bee9d7b580dadd22062aa Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Wed, 10 Jun 2026 03:00:34 -0500 Subject: [PATCH 03/35] Guard QueryWindowsBuild helper inside FOUNDRY_LOCAL_HAS_EP_CATALOG The helper is only referenced from DiscoverProviders' EP-catalog code path. When FOUNDRY_LOCAL_HAS_EP_CATALOG=0 (non-WinML Windows build) the file is still compiled but the helper has no callers, which can trip MSVC C4505 (unreferenced local function has been removed) under /W4 builds. Moving the function definition inside the same preprocessor guard that gates its sole call site makes the compilation symmetric. No behavior change. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc b/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc index 1b77bb763..c2ea072be 100644 --- a/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc +++ b/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc @@ -21,6 +21,8 @@ namespace fl { +#if FOUNDRY_LOCAL_HAS_EP_CATALOG + namespace { /// Look up the running Windows build number via ``ntdll!RtlGetVersion``. We @@ -48,7 +50,6 @@ DWORD QueryWindowsBuild() { } // namespace -#if FOUNDRY_LOCAL_HAS_EP_CATALOG WinMLEpBootstrapper::WinMLEpBootstrapper(std::string name, EpRegistrationCallback register_ep, std::shared_ptr catalog_ref, WinMLEpHandle ep_handle) : name_(std::move(name)), From fdcaafc2c8692019051bfa9a53c23685ac4cdaa1 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Wed, 10 Jun 2026 09:13:10 -0500 Subject: [PATCH 04/35] FindWinMLEpCatalog: drop unused WINML_EP_CATALOG_HEADER_DIR output The find-module advertised WINML_EP_CATALOG_HEADER_DIR as a public output (set as CACHE PATH ... FORCE and printed via message(STATUS)), but no caller in the repo reads it. Headers reach consumers through the WindowsML::Api target's INTERFACE_INCLUDE_DIRECTORIES, propagated by the WinMLEpCatalog::WinMLEpCatalog alias. The companion WINML_EP_CATALOG_DLL_DIR is genuinely consumed by the post-build DLL-copy step in sdk_v2/cpp/CMakeLists.txt, so only the header variable is dead. Drop the cache var, its STATUS line, and the corresponding header-block doc entry. Verified zero remaining references via 'git grep' and a clean configure + build of foundry_local on Windows. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- sdk_v2/cpp/cmake/FindWinMLEpCatalog.cmake | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/sdk_v2/cpp/cmake/FindWinMLEpCatalog.cmake b/sdk_v2/cpp/cmake/FindWinMLEpCatalog.cmake index 7debe49bf..f55466e1c 100644 --- a/sdk_v2/cpp/cmake/FindWinMLEpCatalog.cmake +++ b/sdk_v2/cpp/cmake/FindWinMLEpCatalog.cmake @@ -14,8 +14,7 @@ # 19H1 (build 18362) or newer. No MddBootstrapInitialize2 plumbing is needed. # # Re-exports an ALIAS target: WinMLEpCatalog::WinMLEpCatalog -> WindowsML::Api -# Sets: WINML_EP_CATALOG_HEADER_DIR (= WINML_INCLUDE_DIR), -# WINML_EP_CATALOG_DLL_DIR (= WINML_BINARY_DIR) +# Sets: WINML_EP_CATALOG_DLL_DIR (= WINML_BINARY_DIR) if(WinMLEpCatalog_FOUND) return() @@ -110,13 +109,13 @@ endif() set_target_properties(WindowsML::Api PROPERTIES IMPORTED_GLOBAL TRUE) add_library(WinMLEpCatalog::WinMLEpCatalog ALIAS WindowsML::Api) -# Export paths set by the official config (WINML_INCLUDE_DIR/_BINARY_DIR) under -# our existing variable names for the post-build DLL-copy step. -set(WINML_EP_CATALOG_HEADER_DIR "${WINML_INCLUDE_DIR}" CACHE PATH "WinML EP Catalog include directory" FORCE) +# Export the binary dir set by the official config (WINML_BINARY_DIR) under +# our existing variable name for the post-build DLL-copy step. The header dir +# is not re-exported; consumers get include paths via the WinMLEpCatalog::WinMLEpCatalog +# target's INTERFACE_INCLUDE_DIRECTORIES. set(WINML_EP_CATALOG_DLL_DIR "${WINML_BINARY_DIR}" CACHE PATH "WinML EP Catalog native DLL directory" FORCE) set(WinMLEpCatalog_FOUND TRUE) message(STATUS "WinML EP Catalog: ${_WINML_EP_ROOT}") message(STATUS " Target: WinMLEpCatalog::WinMLEpCatalog -> WindowsML::Api") -message(STATUS " Headers: ${WINML_EP_CATALOG_HEADER_DIR}") message(STATUS " DLL dir: ${WINML_EP_CATALOG_DLL_DIR}") From 97139a963a713e84883397bc5e62e87fca0ca5a4 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Wed, 10 Jun 2026 10:19:50 -0500 Subject: [PATCH 05/35] winml_ep_bootstrapper: prefer OSVERSIONINFOW over RTL_OSVERSIONINFOW MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The RTL_OSVERSIONINFOW name (and PRTL_OSVERSIONINFOW pointer alias) live in , transitively included via — so the previous code did compile cleanly. But the canonical Win32 type is OSVERSIONINFOW, which is the same struct (both names appear in the same typedef in ) and is what all the documented Win32 RTL_OSVERSIONINFOW consumers use. Switching to the public name removes any ambiguity for readers/static analysers that wonder whether is required (it isn't, but the Rtl- prefix invites the question). Pure rename — no behavior change. foundry_local builds clean on Windows. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc b/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc index c2ea072be..4b1c49809 100644 --- a/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc +++ b/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc @@ -28,9 +28,12 @@ namespace { /// Look up the running Windows build number via ``ntdll!RtlGetVersion``. We /// use ``GetProcAddress`` instead of including ```` to avoid the /// header's macro pollution; the function signature is stable and documented. +/// ``RtlGetVersion`` accepts an ``OSVERSIONINFOW`` (same struct layout as +/// ``RTL_OSVERSIONINFOW`` — both are typedef aliases in ````) so +/// callers don't need any ``Rtl``-specific headers either. /// Returns 0 if the lookup fails (purely diagnostic — never gates behavior). DWORD QueryWindowsBuild() { - using RtlGetVersionFn = LONG(WINAPI*)(PRTL_OSVERSIONINFOW); + using RtlGetVersionFn = LONG(WINAPI*)(OSVERSIONINFOW*); HMODULE ntdll = ::GetModuleHandleW(L"ntdll.dll"); if (!ntdll) { return 0; @@ -40,7 +43,7 @@ DWORD QueryWindowsBuild() { if (!rtl_get_version) { return 0; } - RTL_OSVERSIONINFOW info{}; + OSVERSIONINFOW info{}; info.dwOSVersionInfoSize = sizeof(info); if (rtl_get_version(&info) != 0) { return 0; From 3d7ee58eb2fe113ec8da5ba96622ab003408dcc5 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Wed, 10 Jun 2026 14:46:49 -0500 Subject: [PATCH 06/35] tests: cover WinML 2.x EnsureReady end-to-end register path The existing EP detection suite only exercised catalog enumeration (WinMLEpCatalogCreate + WinMLEpCatalogEnumProviders) and the legacy Foundry-managed CUDA/WebGPU download path. The WinML 2.x EnsureReady -> GetLibraryPath -> ORT register chain had no automated coverage despite being the headline feature of this PR. Add DownloadAndRegister_WinMLEp_RegistersFromOsCatalog: picks any discoverable EP that SharedTestEnv has not already registered (i.e. one served by the OS WinML catalog, not Foundry's own download), invokes DownloadAndRegisterEps for just that name, and asserts the post-state surfaces through GetDiscoverableEps with is_registered=true. Skipped via GTEST_SKIP on minimal CI images that expose no WinML EPs, so it never hard-fails Linux/macOS or stripped Windows runners. Verified locally on Win11 with Intel OpenVINO EP installed: [info] EP registration: 'OpenVINOExecutionProvider' registered successfully (library=C:\\\\Program Files\\\\WindowsApps\\\\Microsoft CorporationII.WinML.Intel.OpenVINO.EP.1.8_*\\\\onnxruntime_ providers_openvino_plugin.dll, version=1.4.1+f33af4f) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- sdk_v2/cpp/test/sdk_api/ep_detection_test.cc | 46 ++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/sdk_v2/cpp/test/sdk_api/ep_detection_test.cc b/sdk_v2/cpp/test/sdk_api/ep_detection_test.cc index 993fc0bd3..c47be7119 100644 --- a/sdk_v2/cpp/test/sdk_api/ep_detection_test.cc +++ b/sdk_v2/cpp/test/sdk_api/ep_detection_test.cc @@ -92,4 +92,50 @@ TEST_F(EpDetectionApiTest, GetDiscoverableEps_WindowsHasWinMLProviders) { } } } + +// Exercises the WinML 2.x download/register path end-to-end: +// WinMLEpEnsureReady → WinMLEpGetLibraryPath → ORT RegisterExecutionProvider. +// Picks any discoverable EP that SharedTestEnv has NOT already registered +// (i.e. one served by the OS WinML catalog rather than Foundry's own download). +// Skipped on minimal images where the OS exposes no WinML EPs. +TEST_F(EpDetectionApiTest, DownloadAndRegister_WinMLEp_RegistersFromOsCatalog) { + auto eps_before = manager().GetDiscoverableEps(); + + std::string target; + for (const auto& ep : eps_before) { + // Skip Foundry-managed EPs (CUDA / WebGPU) — these don't exercise the + // WinML 2.x C API path. We want EPs discovered by the OS catalog. + if (ep.name == "CUDAExecutionProvider" || ep.name == "WebGpuExecutionProvider") { + continue; + } + if (!ep.is_registered) { + target = ep.name; + break; + } + } + + if (target.empty()) { + GTEST_SKIP() << "No unregistered WinML-catalog EP available on this machine"; + } + + std::cout << "[ INFO ] Registering WinML EP via 2.x catalog: " << target << std::endl; + + ASSERT_NO_THROW(manager().DownloadAndRegisterEps( + {target}, [](std::string_view name, float pct) { + std::cout << " " << name << ": " << static_cast(pct) << "%" << std::endl; + return true; + })) << "DownloadAndRegisterEps threw for " << target; + + // Verify the post-register state surfaces through the public API. + auto eps_after = manager().GetDiscoverableEps(); + bool found_registered = false; + for (const auto& ep : eps_after) { + if (ep.name == target) { + EXPECT_TRUE(ep.is_registered) << target << " should be marked registered after EnsureReady"; + found_registered = true; + break; + } + } + EXPECT_TRUE(found_registered) << target << " disappeared from discoverable list"; +} #endif From 8436bf758886293fe068fddb14053b269582d96a Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Wed, 10 Jun 2026 16:12:55 -0500 Subject: [PATCH 07/35] cs/test: accept '.git' file in addition to '.git' directory GetRepoRoot in Utils.cs walks parents looking for a '.git' directory to anchor test data lookups. In a git worktree '.git' is a regular file containing 'gitdir:
/.git/worktrees/' rather than a directory, so Directory.Exists returns false and the walk runs to the drive root, throwing 'Could not find git repository root from test file location' before any test executes. Accept either form so dotnet test works from worktrees as well as regular checkouts. No other resolution logic changes. Surfaced while running build_and_test_all.ps1 -UseWinml from a worktree; pre-existing bug (Utils.cs last touched in 93e400a4, unrelated to this PR). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- sdk_v2/cs/test/FoundryLocal.Tests/Utils.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sdk_v2/cs/test/FoundryLocal.Tests/Utils.cs b/sdk_v2/cs/test/FoundryLocal.Tests/Utils.cs index ba8d6b975..e684e1e5d 100644 --- a/sdk_v2/cs/test/FoundryLocal.Tests/Utils.cs +++ b/sdk_v2/cs/test/FoundryLocal.Tests/Utils.cs @@ -558,6 +558,8 @@ private static List BuildTestCatalog(bool includeCuda = true) private static string GetSourceFilePath([CallerFilePath] string path = "") => path; // Gets the root directory of the foundry-local-sdk repository by finding the .git directory. + // In a regular checkout `.git` is a directory; in a git worktree it's a file pointing to + // the main repo's worktrees/ entry — accept either form. private static string GetRepoRoot() { var sourceFile = GetSourceFilePath(); @@ -565,7 +567,8 @@ private static string GetRepoRoot() while (dir != null) { - if (Directory.Exists(Path.Combine(dir.FullName, ".git"))) + var gitPath = Path.Combine(dir.FullName, ".git"); + if (Directory.Exists(gitPath) || File.Exists(gitPath)) return dir.FullName; dir = dir.Parent; From 84f81b9c49b50ce2831e153df41d6127503c5a5a Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Wed, 10 Jun 2026 17:14:10 -0500 Subject: [PATCH 08/35] build: only compile WinML EP bootstrapper when WinML SKU is enabled Previously winml_ep_bootstrapper.cc was added to FOUNDRY_LOCAL_PLATFORM_SOURCES unconditionally for any Windows build, and its body used #if FOUNDRY_LOCAL_HAS_EP_CATALOG to stub out the implementation when the WinML EP catalog NuGet package was not available. That meant the file was still parsed/compiled (and added to the objects archive) on stock Windows builds with USE_WINML=OFF, even though every exported function was a no-op. Two clean-ups: 1. CMake now adds the source only when WinMLEpCatalog_FOUND is true. With the default FOUNDRY_LOCAL_USE_WINML=OFF (no find_package call), this excludes the translation unit entirely. Verified with both configs: - USE_WINML=ON -> winml_ep_bootstrapper.obj present, full path runs - USE_WINML=OFF -> no winml_ep_bootstrapper.obj, foundry_local.dll links and all test exes build 2. manager.cc now guards both the #include and the DiscoverProviders() call site on FOUNDRY_LOCAL_HAS_EP_CATALOG instead of just _WIN32. Non-WinML Windows builds skip the call cleanly; the Windows block above still owns / for the other Windows-only includes. Because the .cc is no longer compiled when HAS_EP_CATALOG=0, the internal #if !FOUNDRY_LOCAL_HAS_EP_CATALOG stub branches in DownloadAndRegister() and DiscoverProviders() (and the matching guards in the header) are now dead code and have been removed. The remaining file is structurally guaranteed to see HAS_EP_CATALOG=1. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- sdk_v2/cpp/CMakeLists.txt | 12 ++++++++- .../src/ep_detection/winml_ep_bootstrapper.cc | 26 ++++--------------- .../src/ep_detection/winml_ep_bootstrapper.h | 16 +++++------- sdk_v2/cpp/src/manager.cc | 7 ++++- 4 files changed, 29 insertions(+), 32 deletions(-) diff --git a/sdk_v2/cpp/CMakeLists.txt b/sdk_v2/cpp/CMakeLists.txt index b2e71e66e..90febae6e 100644 --- a/sdk_v2/cpp/CMakeLists.txt +++ b/sdk_v2/cpp/CMakeLists.txt @@ -103,7 +103,6 @@ set(FOUNDRY_LOCAL_PLATFORM_SOURCES) if(WIN32) list(APPEND FOUNDRY_LOCAL_PLATFORM_SOURCES src/util/stacktrace_windows.cc - src/ep_detection/winml_ep_bootstrapper.cc src/platform/windows/path.cc ) else() @@ -113,6 +112,17 @@ else() ) endif() +# WinML EP bootstrapper is only built when the WinML EP catalog package is +# available — the source unconditionally references its headers/types. Other +# Windows builds (no WinML SKU) skip it entirely; manager.cc gates the call +# site on FOUNDRY_LOCAL_HAS_EP_CATALOG so DiscoverProviders is only invoked +# when this translation unit is linked in. +if(WinMLEpCatalog_FOUND) + list(APPEND FOUNDRY_LOCAL_PLATFORM_SOURCES + src/ep_detection/winml_ep_bootstrapper.cc + ) +endif() + if(ANDROID) list(APPEND FOUNDRY_LOCAL_PLATFORM_SOURCES src/platform/android/ssl_cert_checker.cc) endif() diff --git a/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc b/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc index 4b1c49809..a8e9e8491 100644 --- a/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc +++ b/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc @@ -1,5 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +// +// This translation unit is only compiled when FOUNDRY_LOCAL_USE_WINML=ON and +// the WinML EP catalog NuGet package was resolved at CMake time. See +// sdk_v2/cpp/CMakeLists.txt for the source-list gating. The corresponding +// header (and this file) unconditionally reference WinML 2.x catalog APIs. #include "ep_detection/winml_ep_bootstrapper.h" #include "logger.h" @@ -9,20 +14,14 @@ #include #include -#ifdef _WIN32 - // WinML EP Catalog C API — delay-loaded via Microsoft.Windows.AI.MachineLearning.dll -#if FOUNDRY_LOCAL_HAS_EP_CATALOG #include -#endif #define WIN32_LEAN_AND_MEAN #include namespace fl { -#if FOUNDRY_LOCAL_HAS_EP_CATALOG - namespace { /// Look up the running Windows build number via ``ntdll!RtlGetVersion``. We @@ -59,7 +58,6 @@ WinMLEpBootstrapper::WinMLEpBootstrapper(std::string name, EpRegistrationCallbac register_ep_(std::move(register_ep)), catalog_ref_(std::move(catalog_ref)), ep_handle_(ep_handle) {} -#endif const std::string& WinMLEpBootstrapper::Name() const { return name_; @@ -79,11 +77,6 @@ bool WinMLEpBootstrapper::DownloadAndRegister(bool force, return true; } -#if !FOUNDRY_LOCAL_HAS_EP_CATALOG - logger.Log(LogLevel::Warning, - fmt::format("WinML EP {}: EP catalog not available at compile time", name_)); - return false; -#else // Ask the OS to download/prepare the EP if needed. HRESULT hr = WinMLEpEnsureReady(ep_handle_); @@ -134,17 +127,11 @@ bool WinMLEpBootstrapper::DownloadAndRegister(bool force, // Library path + version are logged by the central register_ep callback; // no extra bootstrapper-side line needed. return true; -#endif } std::vector> WinMLEpBootstrapper::DiscoverProviders( EpRegistrationCallback register_ep, ILogger& logger) { -#if !FOUNDRY_LOCAL_HAS_EP_CATALOG - (void)register_ep; - (void)logger; - return {}; -#else // Pre-check that the WinML DLL is loadable. The DLL is delay-loaded, so // calling WinML functions without it present would cause a structured // exception. Loading it explicitly is cleaner than SEH. @@ -229,9 +216,6 @@ std::vector> WinMLEpBootstrapper::DiscoverP fmt::format("WinML EP catalog: discovered {} provider(s)", ctx.bootstrappers.size())); return std::move(ctx.bootstrappers); -#endif } } // namespace fl - -#endif // _WIN32 diff --git a/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.h b/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.h index 96e9625a6..765dab076 100644 --- a/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.h +++ b/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.h @@ -1,15 +1,17 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +// +// This translation unit is only compiled when FOUNDRY_LOCAL_USE_WINML=ON and +// the WinML EP catalog NuGet package was resolved at CMake time +// (WinMLEpCatalog_FOUND, which sets FOUNDRY_LOCAL_HAS_EP_CATALOG=1). The +// gating happens in sdk_v2/cpp/CMakeLists.txt. All callers must guard +// references on FOUNDRY_LOCAL_HAS_EP_CATALOG. #pragma once -#ifdef _WIN32 - #include "ep_detection/ep_bootstrapper.h" #include "ep_detection/ep_types.h" -#if FOUNDRY_LOCAL_HAS_EP_CATALOG #include -#endif #include #include @@ -41,7 +43,7 @@ class WinMLEpBootstrapper : public IEpBootstrapper { ILogger& logger) override; /// Discovers all WinML EPs available on this system. - /// Returns empty on non-Windows, unsupported OS version, or missing WinML DLL. + /// Returns empty on unsupported OS version or missing WinML DLL. /// @param register_ep Callback called after EnsureReady to register the EP with ORT. /// @param logger Logger for diagnostic output. static std::vector> DiscoverProviders( @@ -54,7 +56,6 @@ class WinMLEpBootstrapper : public IEpBootstrapper { bool registered_ = false; EpRegistrationCallback register_ep_; -#if FOUNDRY_LOCAL_HAS_EP_CATALOG // Shared across all bootstrappers from the same DiscoverProviders() call // to keep the catalog alive until the last bootstrapper is destroyed. std::shared_ptr catalog_ref_; @@ -62,9 +63,6 @@ class WinMLEpBootstrapper : public IEpBootstrapper { WinMLEpBootstrapper(std::string name, EpRegistrationCallback register_ep, std::shared_ptr catalog_ref, WinMLEpHandle ep_handle); -#endif }; } // namespace fl - -#endif // _WIN32 diff --git a/sdk_v2/cpp/src/manager.cc b/sdk_v2/cpp/src/manager.cc index 110374ef9..7b5e50766 100644 --- a/sdk_v2/cpp/src/manager.cc +++ b/sdk_v2/cpp/src/manager.cc @@ -31,6 +31,9 @@ #define WIN32_LEAN_AND_MEAN #include #include +#endif + +#if FOUNDRY_LOCAL_HAS_EP_CATALOG #include "ep_detection/winml_ep_bootstrapper.h" #endif @@ -237,8 +240,10 @@ Manager::Manager(const Configuration& config) // Discover bootstrappers from available EP sources std::vector> bootstrappers; -#ifdef _WIN32 +#if FOUNDRY_LOCAL_HAS_EP_CATALOG // WinML EPs — enumerate from the OS EP catalog (Windows 10 19H1+ reg-free runtime). + // Only present when the build was configured with FOUNDRY_LOCAL_USE_WINML=ON and + // the WinML EP catalog NuGet package was successfully resolved at CMake time. auto winml_providers = WinMLEpBootstrapper::DiscoverProviders(register_ep, *logger_); for (auto& p : winml_providers) { bootstrappers.push_back(std::move(p)); From 70825fba54afa85a6a581b8482670a95f3f2bb74 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Wed, 10 Jun 2026 20:47:09 -0500 Subject: [PATCH 09/35] docs: clarify WinML bootstrapper OS version requirements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous docstring ("Windows 11 24H2+ (build 26100) only") was overly restrictive. The bundled Microsoft.Windows.AI.MachineLearning redist DLL loads on Windows 10 19H1+ (build 18362) — that's the floor for the code path itself. Build 26100 (Win11 24H2) is only the floor for the OS to actually have any WinML EPs installed via Store/WU; on earlier builds enumeration returns an empty list and the caller falls back to other bootstrappers. No behavior change — comment only. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.h b/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.h index 765dab076..e00fda186 100644 --- a/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.h +++ b/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.h @@ -23,7 +23,13 @@ class ILogger; /// Bootstrapper for a single WinML-based execution provider. /// Each instance wraps one WinMLEpHandle from the WinML EP catalog. -/// Windows 11 24H2+ (build 26100) only. +/// +/// Code path works on Windows 10 19H1+ (build 18362) — the minimum OS +/// for the bundled WinML 2.x redist DLL (Microsoft.Windows.AI.MachineLearning). +/// Actual EP discovery returns providers only on Windows 11 24H2+ (build 26100), +/// where the OS-delivered EP catalog is populated via Windows Update / Store. +/// On earlier builds the DLL loads, the catalog initializes, and enumeration +/// returns zero providers — the caller falls back to its other bootstrappers. class WinMLEpBootstrapper : public IEpBootstrapper { public: ~WinMLEpBootstrapper() override = default; From 6c4f55cc76d79014f2bf69a514b1400e8526a2b9 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Thu, 11 Jun 2026 13:20:48 -0500 Subject: [PATCH 10/35] build: drop unused FOUNDRY_LOCAL_USE_WINML compile definition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The macro was exported as a public compile definition but never read by any C++ source file. All source-level gating uses FOUNDRY_LOCAL_HAS_EP_CATALOG (derived from the find_package outcome WinMLEpCatalog_FOUND). The CMake variable FOUNDRY_LOCAL_USE_WINML is still needed as user intent — it controls whether find_package is even attempted — but exporting it as a compile-time macro was dead code. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- sdk_v2/cpp/CMakeLists.txt | 6 ------ sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc | 4 ++-- sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.h | 10 +++++----- sdk_v2/cpp/src/manager.cc | 4 ++-- 4 files changed, 9 insertions(+), 15 deletions(-) diff --git a/sdk_v2/cpp/CMakeLists.txt b/sdk_v2/cpp/CMakeLists.txt index 90febae6e..ac2f814c5 100644 --- a/sdk_v2/cpp/CMakeLists.txt +++ b/sdk_v2/cpp/CMakeLists.txt @@ -274,12 +274,6 @@ function(foundry_local_configure_target TARGET LINK_SCOPE) target_compile_definitions(${TARGET} ${LINK_SCOPE} FOUNDRY_LOCAL_HAS_WEB_SERVICE=1) endif() - if(FOUNDRY_LOCAL_USE_WINML) - target_compile_definitions(${TARGET} PUBLIC FOUNDRY_LOCAL_USE_WINML=1) - else() - target_compile_definitions(${TARGET} PUBLIC FOUNDRY_LOCAL_USE_WINML=0) - endif() - if(WinMLEpCatalog_FOUND) target_link_libraries(${TARGET} ${LINK_SCOPE} WinMLEpCatalog::WinMLEpCatalog) target_compile_definitions(${TARGET} ${LINK_SCOPE} FOUNDRY_LOCAL_HAS_EP_CATALOG=1) diff --git a/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc b/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc index a8e9e8491..e07c5f2f1 100644 --- a/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc +++ b/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc @@ -1,8 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. // -// This translation unit is only compiled when FOUNDRY_LOCAL_USE_WINML=ON and -// the WinML EP catalog NuGet package was resolved at CMake time. See +// This translation unit is only compiled when the WinML EP catalog NuGet +// package was resolved at CMake time (WinMLEpCatalog_FOUND). See // sdk_v2/cpp/CMakeLists.txt for the source-list gating. The corresponding // header (and this file) unconditionally reference WinML 2.x catalog APIs. #include "ep_detection/winml_ep_bootstrapper.h" diff --git a/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.h b/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.h index e00fda186..87c04e13b 100644 --- a/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.h +++ b/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.h @@ -1,11 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. // -// This translation unit is only compiled when FOUNDRY_LOCAL_USE_WINML=ON and -// the WinML EP catalog NuGet package was resolved at CMake time -// (WinMLEpCatalog_FOUND, which sets FOUNDRY_LOCAL_HAS_EP_CATALOG=1). The -// gating happens in sdk_v2/cpp/CMakeLists.txt. All callers must guard -// references on FOUNDRY_LOCAL_HAS_EP_CATALOG. +// This translation unit is only compiled when the WinML EP catalog NuGet +// package was resolved at CMake time (WinMLEpCatalog_FOUND, which also sets +// the C++ macro FOUNDRY_LOCAL_HAS_EP_CATALOG=1). Source-list gating happens +// in sdk_v2/cpp/CMakeLists.txt; all C++ callers must guard references on +// FOUNDRY_LOCAL_HAS_EP_CATALOG. #pragma once #include "ep_detection/ep_bootstrapper.h" diff --git a/sdk_v2/cpp/src/manager.cc b/sdk_v2/cpp/src/manager.cc index 7b5e50766..b10a1a3a5 100644 --- a/sdk_v2/cpp/src/manager.cc +++ b/sdk_v2/cpp/src/manager.cc @@ -242,8 +242,8 @@ Manager::Manager(const Configuration& config) #if FOUNDRY_LOCAL_HAS_EP_CATALOG // WinML EPs — enumerate from the OS EP catalog (Windows 10 19H1+ reg-free runtime). - // Only present when the build was configured with FOUNDRY_LOCAL_USE_WINML=ON and - // the WinML EP catalog NuGet package was successfully resolved at CMake time. + // Only present when the WinML EP catalog NuGet package was successfully resolved + // at CMake time (gated on WinMLEpCatalog_FOUND in sdk_v2/cpp/CMakeLists.txt). auto winml_providers = WinMLEpBootstrapper::DiscoverProviders(register_ep, *logger_); for (auto& p : winml_providers) { bootstrappers.push_back(std::move(p)); From 509d9395e3be6fd4da97593b33589cd200bcef8a Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Thu, 11 Jun 2026 15:16:41 -0500 Subject: [PATCH 11/35] cs: collapse WinML SKU TFM to net8.0;net9.0 (drop Windows-specific TFM) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The C# SDK is a pure P/Invoke wrapper over foundry_local.dll. No source references any Windows.* / Microsoft.Windows.* / WinRT API in either the SDK or any sample, so the WinML SKU does not need net9.0-windows10.0.18362.0. Sharing the cross-platform net8.0;net9.0 TFMs with the non-WinML SKU makes the two builds easier to keep in sync and is a precondition for any future merge of the two NuGet packages. Removed: - TargetPlatformMinVersion (Windows-TFM only) - WindowsPackageType=None / EnableCoreMrtTooling=false (Windows-TFM only) - NoWarn CsWinRT1028 (CsWinRT projection unused) - IS_WINML compile symbol (never \#if\'d in any source) The WinML OS-version floor (Win10 19H1+) is still enforced in C++ by winml_ep_bootstrapper.cc (delay-loaded WindowsML::Api + OSVERSIONINFOW check) and by the WinML EP catalog NuGet itself — not by the .NET TFM. The USE_WINML test-side compile symbol is kept (VisionTests uses it). Verified: dotnet build with and without /p:UseWinML=true produces Microsoft.AI.Foundry.Local{,.WinML}.dll for net8.0 + net9.0 (+ netstandard2.0 for non-WinML), 0 warnings, 0 errors. C# test project builds clean under /p:UseWinML=true. 88 of 101 tests pass; 6 environmental OOMs in chat/embedding tests (KV cache allocation) unrelated to this change; 7 skipped. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../AudioTranscriptionExample.csproj | 12 ------------ samples/cs/embeddings/Embeddings.csproj | 12 ------------ .../FoundryLocalWebServer.csproj | 12 ------------ .../LiveAudioTranscriptionExample.csproj | 12 ------------ .../ModelManagementExample.csproj | 12 ------------ .../NativeChatCompletions.csproj | 12 ------------ .../ToolCallingFoundryLocalSdk.csproj | 12 ------------ .../ToolCallingFoundryLocalWebServer.csproj | 12 ------------ .../TutorialChatAssistant.csproj | 12 ------------ .../TutorialDocumentSummarizer.csproj | 12 ------------ .../TutorialToolCalling.csproj | 12 ------------ .../TutorialVoiceToText.csproj | 12 ------------ samples/cs/verify-winml/VerifyWinML.csproj | 10 ---------- sdk_v2/DEVELOPMENT.md | 4 ++-- sdk_v2/cs/src/Microsoft.AI.Foundry.Local.csproj | 16 +++++++++------- .../Microsoft.AI.Foundry.Local.Tests.csproj | 12 +++++------- 16 files changed, 16 insertions(+), 170 deletions(-) diff --git a/samples/cs/audio-transcription-example/AudioTranscriptionExample.csproj b/samples/cs/audio-transcription-example/AudioTranscriptionExample.csproj index 4f048e152..ce8a65f04 100644 --- a/samples/cs/audio-transcription-example/AudioTranscriptionExample.csproj +++ b/samples/cs/audio-transcription-example/AudioTranscriptionExample.csproj @@ -4,18 +4,6 @@ Exe enable enable - - - - - net9.0-windows10.0.18362.0 - ARM64;x64 - None - false - - - - net9.0 diff --git a/samples/cs/embeddings/Embeddings.csproj b/samples/cs/embeddings/Embeddings.csproj index 870c34acd..97cb8ef34 100644 --- a/samples/cs/embeddings/Embeddings.csproj +++ b/samples/cs/embeddings/Embeddings.csproj @@ -4,18 +4,6 @@ Exe enable enable - - - - - net9.0-windows10.0.18362.0 - ARM64;x64 - None - false - - - - net9.0 diff --git a/samples/cs/foundry-local-web-server/FoundryLocalWebServer.csproj b/samples/cs/foundry-local-web-server/FoundryLocalWebServer.csproj index a7c1a3766..77fc929d6 100644 --- a/samples/cs/foundry-local-web-server/FoundryLocalWebServer.csproj +++ b/samples/cs/foundry-local-web-server/FoundryLocalWebServer.csproj @@ -4,18 +4,6 @@ Exe enable enable - - - - - net9.0-windows10.0.18362.0 - ARM64;x64 - None - false - - - - net9.0 diff --git a/samples/cs/live-audio-transcription/LiveAudioTranscriptionExample.csproj b/samples/cs/live-audio-transcription/LiveAudioTranscriptionExample.csproj index 1a276b73d..4a0eed349 100644 --- a/samples/cs/live-audio-transcription/LiveAudioTranscriptionExample.csproj +++ b/samples/cs/live-audio-transcription/LiveAudioTranscriptionExample.csproj @@ -4,18 +4,6 @@ Exe enable enable - - - - - net9.0-windows10.0.18362.0 - ARM64;x64 - None - false - - - - net9.0 diff --git a/samples/cs/model-management-example/ModelManagementExample.csproj b/samples/cs/model-management-example/ModelManagementExample.csproj index 870c34acd..97cb8ef34 100644 --- a/samples/cs/model-management-example/ModelManagementExample.csproj +++ b/samples/cs/model-management-example/ModelManagementExample.csproj @@ -4,18 +4,6 @@ Exe enable enable - - - - - net9.0-windows10.0.18362.0 - ARM64;x64 - None - false - - - - net9.0 diff --git a/samples/cs/native-chat-completions/NativeChatCompletions.csproj b/samples/cs/native-chat-completions/NativeChatCompletions.csproj index 870c34acd..97cb8ef34 100644 --- a/samples/cs/native-chat-completions/NativeChatCompletions.csproj +++ b/samples/cs/native-chat-completions/NativeChatCompletions.csproj @@ -4,18 +4,6 @@ Exe enable enable - - - - - net9.0-windows10.0.18362.0 - ARM64;x64 - None - false - - - - net9.0 diff --git a/samples/cs/tool-calling-foundry-local-sdk/ToolCallingFoundryLocalSdk.csproj b/samples/cs/tool-calling-foundry-local-sdk/ToolCallingFoundryLocalSdk.csproj index 870c34acd..97cb8ef34 100644 --- a/samples/cs/tool-calling-foundry-local-sdk/ToolCallingFoundryLocalSdk.csproj +++ b/samples/cs/tool-calling-foundry-local-sdk/ToolCallingFoundryLocalSdk.csproj @@ -4,18 +4,6 @@ Exe enable enable - - - - - net9.0-windows10.0.18362.0 - ARM64;x64 - None - false - - - - net9.0 diff --git a/samples/cs/tool-calling-foundry-local-web-server/ToolCallingFoundryLocalWebServer.csproj b/samples/cs/tool-calling-foundry-local-web-server/ToolCallingFoundryLocalWebServer.csproj index a7c1a3766..77fc929d6 100644 --- a/samples/cs/tool-calling-foundry-local-web-server/ToolCallingFoundryLocalWebServer.csproj +++ b/samples/cs/tool-calling-foundry-local-web-server/ToolCallingFoundryLocalWebServer.csproj @@ -4,18 +4,6 @@ Exe enable enable - - - - - net9.0-windows10.0.18362.0 - ARM64;x64 - None - false - - - - net9.0 diff --git a/samples/cs/tutorial-chat-assistant/TutorialChatAssistant.csproj b/samples/cs/tutorial-chat-assistant/TutorialChatAssistant.csproj index f07da7a75..fcc9257da 100644 --- a/samples/cs/tutorial-chat-assistant/TutorialChatAssistant.csproj +++ b/samples/cs/tutorial-chat-assistant/TutorialChatAssistant.csproj @@ -4,18 +4,6 @@ Exe enable enable - - - - - net9.0-windows10.0.18362.0 - ARM64;x64 - None - false - - - - net9.0 diff --git a/samples/cs/tutorial-document-summarizer/TutorialDocumentSummarizer.csproj b/samples/cs/tutorial-document-summarizer/TutorialDocumentSummarizer.csproj index f07da7a75..fcc9257da 100644 --- a/samples/cs/tutorial-document-summarizer/TutorialDocumentSummarizer.csproj +++ b/samples/cs/tutorial-document-summarizer/TutorialDocumentSummarizer.csproj @@ -4,18 +4,6 @@ Exe enable enable - - - - - net9.0-windows10.0.18362.0 - ARM64;x64 - None - false - - - - net9.0 diff --git a/samples/cs/tutorial-tool-calling/TutorialToolCalling.csproj b/samples/cs/tutorial-tool-calling/TutorialToolCalling.csproj index f07da7a75..fcc9257da 100644 --- a/samples/cs/tutorial-tool-calling/TutorialToolCalling.csproj +++ b/samples/cs/tutorial-tool-calling/TutorialToolCalling.csproj @@ -4,18 +4,6 @@ Exe enable enable - - - - - net9.0-windows10.0.18362.0 - ARM64;x64 - None - false - - - - net9.0 diff --git a/samples/cs/tutorial-voice-to-text/TutorialVoiceToText.csproj b/samples/cs/tutorial-voice-to-text/TutorialVoiceToText.csproj index f07da7a75..fcc9257da 100644 --- a/samples/cs/tutorial-voice-to-text/TutorialVoiceToText.csproj +++ b/samples/cs/tutorial-voice-to-text/TutorialVoiceToText.csproj @@ -4,18 +4,6 @@ Exe enable enable - - - - - net9.0-windows10.0.18362.0 - ARM64;x64 - None - false - - - - net9.0 diff --git a/samples/cs/verify-winml/VerifyWinML.csproj b/samples/cs/verify-winml/VerifyWinML.csproj index 151f655f3..860aa6740 100644 --- a/samples/cs/verify-winml/VerifyWinML.csproj +++ b/samples/cs/verify-winml/VerifyWinML.csproj @@ -4,16 +4,6 @@ Exe enable enable - - - - net9.0-windows10.0.18362.0 - x64;ARM64 - None - false - - - net9.0 diff --git a/sdk_v2/DEVELOPMENT.md b/sdk_v2/DEVELOPMENT.md index c9117dd13..2312f16c8 100644 --- a/sdk_v2/DEVELOPMENT.md +++ b/sdk_v2/DEVELOPMENT.md @@ -23,7 +23,7 @@ All four SDKs (C++, C#, Python, JS/TS) build on **Windows**, **Linux**, and | CMake | 3.20 | Driven by `sdk_v2/cpp/build.py`; do not invoke `cmake --build` directly. | | vcpkg | recent | Set `VCPKG_ROOT`, or use the copy bundled with Visual Studio (auto-detected). | | Python | 3.11–3.14, **64-bit** | Required by `build.py` and for the Python SDK. 32-bit Python will not work. | -| .NET SDK | 9.0 | C# projects target `net8.0;net9.0;netstandard2.0` (and `net462` on Windows from the .NET Framework Targeting Pack via VS); the WinML variant targets `net9.0-windows10.0.26100.0`. | +| .NET SDK | 9.0 | C# projects target `net8.0;net9.0;netstandard2.0` (and `net462` on Windows from the .NET Framework Targeting Pack via VS). The WinML SKU shares the same `net8.0;net9.0` TFMs — the Windows OS-version floor for WinML is enforced by the native runtime, not by the .NET TFM. | | Node.js | 20 LTS or newer | Brings `npm`. The JS SDK declares `"engines": { "node": ">=20" }`. | | PowerShell | 7+ (`pwsh`) | The one-shot script and `samples/js/test-v2.ps1` are written for PowerShell 7. | @@ -32,7 +32,7 @@ All four SDKs (C++, C#, Python, JS/TS) build on **Windows**, **Linux**, and | Tool | Version | Notes | | --------------------------- | ------------------------ | --------------------------------------------------------------------- | | Visual Studio 2026 (v18) | Enterprise / Professional / Community | Install the **Desktop development with C++** and **.NET desktop development** workloads. Provides MSVC, Windows SDK, and vcpkg. | -| Windows SDK | 10.0.26100 (VS workload) | Needed for the WinML target framework. | +| Windows SDK | 10.0.26100 (VS workload) | Needed by the WinML C++ EP bootstrapper (uses `windows.h` / `OSVERSIONINFOW`). | | .NET Framework 4.6.2 Targeting Pack | (VS workload) | One C# test target framework is `net462`. | Launch your dev shell with **x64** explicitly — `Enter-VsDevShell` defaults diff --git a/sdk_v2/cs/src/Microsoft.AI.Foundry.Local.csproj b/sdk_v2/cs/src/Microsoft.AI.Foundry.Local.csproj index 17adc8709..cd4c1c6de 100644 --- a/sdk_v2/cs/src/Microsoft.AI.Foundry.Local.csproj +++ b/sdk_v2/cs/src/Microsoft.AI.Foundry.Local.csproj @@ -88,14 +88,16 @@ Microsoft Foundry Local SDK for WinML Microsoft.AI.Foundry.Local.WinML Microsoft.AI.Foundry.Local.WinML - $(DefineConstants);IS_WINML - net9.0-windows10.0.18362.0 + + net8.0;net9.0 win-x64;win-arm64 - - 10.0.17763.0 - - - $(NoWarn);CsWinRT1028 diff --git a/sdk_v2/cs/test/FoundryLocal.Tests/Microsoft.AI.Foundry.Local.Tests.csproj b/sdk_v2/cs/test/FoundryLocal.Tests/Microsoft.AI.Foundry.Local.Tests.csproj index 1196a1b6c..cafd5d07d 100644 --- a/sdk_v2/cs/test/FoundryLocal.Tests/Microsoft.AI.Foundry.Local.Tests.csproj +++ b/sdk_v2/cs/test/FoundryLocal.Tests/Microsoft.AI.Foundry.Local.Tests.csproj @@ -48,13 +48,11 @@ - net9.0-windows10.0.26100.0 - 10.0.17763.0 - None - + + net8.0 $(DefineConstants);USE_WINML From 81e02331896ccfd328c181e0046632a0c3a4870a Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Thu, 11 Jun 2026 16:38:06 -0500 Subject: [PATCH 12/35] docs: correct WinML Windows SDK note and stale memory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Windows SDK note in sdk_v2/DEVELOPMENT.md scoped the requirement to the WinML EP bootstrapper and pinned 10.0.26100, both wrong: - The Windows SDK is needed for the entire C++ Windows build (any windows.h consumer), not just the WinML bootstrapper. - 10.0.26100 is not pinned anywhere — no WindowsTargetPlatformVersion or CMAKE_SYSTEM_VERSION. The number was inherited from the now-dropped net9.0-windows10.0.26100.0 C# TFM. OSVERSIONINFOW has been in since the Windows 2000-era SDKs. Also corrected memories/repo/cs-local-packages.md which still claimed the WinML SKU single-targets net9.0-windows10.0.26100.0 — that was true before commit bbcb7fde; both SKUs now share the net8.0;net9.0 TFM set. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- memories/repo/cs-local-packages.md | 2 +- sdk_v2/DEVELOPMENT.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/memories/repo/cs-local-packages.md b/memories/repo/cs-local-packages.md index 986b78267..2851a696e 100644 --- a/memories/repo/cs-local-packages.md +++ b/memories/repo/cs-local-packages.md @@ -17,7 +17,7 @@ dotnet pack src/Microsoft.AI.Foundry.Local.csproj -o ../../local-packages /p:IsP dotnet pack src/Microsoft.AI.Foundry.Local.csproj -o ../../local-packages /p:IsPacking=true /p:UseWinML=true /p:TreatWarningsAsErrors=false -c Release ``` - `IsPacking=true` auto-sets `Version=0.5.0-dev.local.` (see `Microsoft.AI.Foundry.Local.csproj`). -- `UseWinML=true` flips `PackageId`/`AssemblyName` to `Microsoft.AI.Foundry.Local.WinML`, single-targets `net9.0-windows10.0.26100.0`. +- `UseWinML=true` flips `PackageId`/`AssemblyName` to `Microsoft.AI.Foundry.Local.WinML`; both SKUs share the `net8.0;net9.0` TFM set (WinML SKU omits `netstandard2.0`). - Cross-platform + WinML are independent packages — most samples reference WinML on Windows and the cross-platform package elsewhere, so both must be packed. ## Full clean-rebuild diff --git a/sdk_v2/DEVELOPMENT.md b/sdk_v2/DEVELOPMENT.md index 2312f16c8..e80d25aa7 100644 --- a/sdk_v2/DEVELOPMENT.md +++ b/sdk_v2/DEVELOPMENT.md @@ -32,7 +32,7 @@ All four SDKs (C++, C#, Python, JS/TS) build on **Windows**, **Linux**, and | Tool | Version | Notes | | --------------------------- | ------------------------ | --------------------------------------------------------------------- | | Visual Studio 2026 (v18) | Enterprise / Professional / Community | Install the **Desktop development with C++** and **.NET desktop development** workloads. Provides MSVC, Windows SDK, and vcpkg. | -| Windows SDK | 10.0.26100 (VS workload) | Needed by the WinML C++ EP bootstrapper (uses `windows.h` / `OSVERSIONINFOW`). | +| Windows SDK | 10.0 (VS workload) | Required by the C++ build for any `windows.h` consumer. The exact patch number is not pinned — whatever the VS C++ workload installs (currently 10.0.26100) works. | | .NET Framework 4.6.2 Targeting Pack | (VS workload) | One C# test target framework is `net462`. | Launch your dev shell with **x64** explicitly — `Enter-VsDevShell` defaults From fc5389d92fa58f3a6020d8c8b5617819dc8c04a2 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Thu, 11 Jun 2026 16:39:09 -0500 Subject: [PATCH 13/35] docs: clarify per-project TFM matrix in DEVELOPMENT.md Previous wording lumped the SDK, test, and sample csproj TFM sets together, which was inaccurate: netstandard2.0 is SDK-only, net462 is tests-on-Windows- only, and samples target net9.0 only. The WinML SKU shares the SDK TFMs (net8.0;net9.0) but the WinML test branch is just net8.0. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- sdk_v2/DEVELOPMENT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk_v2/DEVELOPMENT.md b/sdk_v2/DEVELOPMENT.md index e80d25aa7..66f18e37a 100644 --- a/sdk_v2/DEVELOPMENT.md +++ b/sdk_v2/DEVELOPMENT.md @@ -23,7 +23,7 @@ All four SDKs (C++, C#, Python, JS/TS) build on **Windows**, **Linux**, and | CMake | 3.20 | Driven by `sdk_v2/cpp/build.py`; do not invoke `cmake --build` directly. | | vcpkg | recent | Set `VCPKG_ROOT`, or use the copy bundled with Visual Studio (auto-detected). | | Python | 3.11–3.14, **64-bit** | Required by `build.py` and for the Python SDK. 32-bit Python will not work. | -| .NET SDK | 9.0 | C# projects target `net8.0;net9.0;netstandard2.0` (and `net462` on Windows from the .NET Framework Targeting Pack via VS). The WinML SKU shares the same `net8.0;net9.0` TFMs — the Windows OS-version floor for WinML is enforced by the native runtime, not by the .NET TFM. | +| .NET SDK | 9.0 | The SDK targets `net8.0;net9.0;netstandard2.0`; the test project additionally targets `net462` on Windows (via the .NET Framework Targeting Pack from VS); samples target `net9.0`. The WinML SKU shares the same SDK TFMs (minus `netstandard2.0`) — the Windows OS-version floor for WinML 2.x is enforced by the native runtime (`LoadLibraryW` + `RtlGetVersion` in `winml_ep_bootstrapper.cc`), not by a .NET TFM. | | Node.js | 20 LTS or newer | Brings `npm`. The JS SDK declares `"engines": { "node": ">=20" }`. | | PowerShell | 7+ (`pwsh`) | The one-shot script and `samples/js/test-v2.ps1` are written for PowerShell 7. | From 4f727315313497945c8ffae38faf8efc8128a997 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Thu, 11 Jun 2026 16:52:07 -0500 Subject: [PATCH 14/35] cs/test: re-enable VisionTests under WinML SKU VisionTests was skipped under USE_WINML on the rationale that the WinML SKU pinned an older ORT than the base SDK. That hasn't been true since WinML 2.x: both SKUs now share a single ORT pin from sdk_v2/deps_versions.json (see FindOnnxRuntime.cmake:4-10), so the same ops are available in both builds. The test already prefers a CPU variant, so it doesn't depend on the WinML EP catalog at all. Drop the #if USE_WINML early return, drop the now-dead CS0649 pragma (the field is always assigned), and drop the USE_WINML compile symbol from the test csproj (no remaining consumers). The WinML PropertyGroup keeps only the TargetFrameworks override (net8.0) because the WinML SDK omits netstandard2.0 and so cannot satisfy a net462 ProjectReference. Verified: default + WinML test SKUs both build clean with zero warnings; both run 94 passed / 7 skipped / 0 failed (the 7 includes the 2 VisionTests methods, which skip via SkipTestException on dev boxes that haven't cached a vision-language-chat model). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Microsoft.AI.Foundry.Local.Tests.csproj | 6 +----- sdk_v2/cs/test/FoundryLocal.Tests/VisionTests.cs | 11 ----------- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/sdk_v2/cs/test/FoundryLocal.Tests/Microsoft.AI.Foundry.Local.Tests.csproj b/sdk_v2/cs/test/FoundryLocal.Tests/Microsoft.AI.Foundry.Local.Tests.csproj index cafd5d07d..584ab47a0 100644 --- a/sdk_v2/cs/test/FoundryLocal.Tests/Microsoft.AI.Foundry.Local.Tests.csproj +++ b/sdk_v2/cs/test/FoundryLocal.Tests/Microsoft.AI.Foundry.Local.Tests.csproj @@ -48,12 +48,8 @@ - + net8.0 - $(DefineConstants);USE_WINML diff --git a/sdk_v2/cs/test/FoundryLocal.Tests/VisionTests.cs b/sdk_v2/cs/test/FoundryLocal.Tests/VisionTests.cs index 03399b88f..07be22307 100644 --- a/sdk_v2/cs/test/FoundryLocal.Tests/VisionTests.cs +++ b/sdk_v2/cs/test/FoundryLocal.Tests/VisionTests.cs @@ -17,23 +17,13 @@ namespace Microsoft.AI.Foundry.Local.Tests; [SkipUnlessIntegration] internal sealed class VisionTests { -#pragma warning disable CS0649 // assigned in non-WinML Setup; under USE_WINML the field is never written private static IModel? model; -#pragma warning restore CS0649 private static string TestImagePath => Utils.TestDataPath("Taittinger.jpg"); [Before(Class)] public static async Task Setup() { -#if USE_WINML - // The WinML variant pins an older ORT than the base SDK. Cataloged vision - // models use ops (e.g. com.microsoft:CausalConvWithState) that aren't - // registered in that ORT, so no vision model can load. Skip the whole suite. - Console.WriteLine("VisionTests: skipped on WinML build (ORT version lacks required ops)"); - await Task.CompletedTask; - return; -#else try { var manager = FoundryLocalManager.Instance; @@ -86,7 +76,6 @@ public static async Task Setup() Console.WriteLine($"VisionTests setup failed: {ex}"); throw; } -#endif } [Test] From 52093e3d0c3f37401bed295e6e32b379748543c5 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Thu, 11 Jun 2026 16:55:33 -0500 Subject: [PATCH 15/35] docs: rewrite WinML comments to describe code, not change rationale MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Several comments authored during this PR read like commit-message rationale (negations, comparisons to prior state) rather than self-contained descriptions of what the code does. Rewrite them to describe the current behavior directly so they remain useful once the migration is no longer recent context. Pure comment edits in FindOnnxRuntime.cmake, FindWinMLEpCatalog.cmake, winml_ep_bootstrapper.cc, and the WinML test csproj — no code or build changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- sdk_v2/cpp/cmake/FindOnnxRuntime.cmake | 12 +++---- sdk_v2/cpp/cmake/FindWinMLEpCatalog.cmake | 34 ++++++++----------- .../src/ep_detection/winml_ep_bootstrapper.cc | 19 +++++------ .../Microsoft.AI.Foundry.Local.Tests.csproj | 2 +- 4 files changed, 29 insertions(+), 38 deletions(-) diff --git a/sdk_v2/cpp/cmake/FindOnnxRuntime.cmake b/sdk_v2/cpp/cmake/FindOnnxRuntime.cmake index 0982d511a..197894e01 100644 --- a/sdk_v2/cpp/cmake/FindOnnxRuntime.cmake +++ b/sdk_v2/cpp/cmake/FindOnnxRuntime.cmake @@ -1,13 +1,11 @@ # Copyright (c) Microsoft. All rights reserved. # Find/acquire ONNX Runtime. # -# ORT is always sourced from Microsoft.ML.OnnxRuntime.Foundry (or -# Microsoft.ML.OnnxRuntime on Android) via FetchContent — nuget.org for releases, -# the ORT-Nightly ADO feed for -dev- versions. The FOUNDRY_LOCAL_USE_WINML flag -# does NOT change the ORT package source or version — it only opts in to the -# WinML EP catalog (handled by FindWinMLEpCatalog.cmake). WinML and non-WinML -# builds share a single ORT pin from sdk_v2/deps_versions.json since both -# flavors are built against the same ORT line now. +# Sources ORT from Microsoft.ML.OnnxRuntime.Foundry (or Microsoft.ML.OnnxRuntime +# on Android) via FetchContent — nuget.org for releases, the ORT-Nightly ADO +# feed for -dev- versions. The version comes from sdk_v2/deps_versions.json and +# is shared by WinML and non-WinML builds; FOUNDRY_LOCAL_USE_WINML only gates +# the WinML EP catalog in FindWinMLEpCatalog.cmake. # # Creates an IMPORTED target: OnnxRuntime::OnnxRuntime diff --git a/sdk_v2/cpp/cmake/FindWinMLEpCatalog.cmake b/sdk_v2/cpp/cmake/FindWinMLEpCatalog.cmake index f55466e1c..603e0ed7f 100644 --- a/sdk_v2/cpp/cmake/FindWinMLEpCatalog.cmake +++ b/sdk_v2/cpp/cmake/FindWinMLEpCatalog.cmake @@ -1,19 +1,17 @@ # Copyright (c) Microsoft. All rights reserved. # Find/acquire the WinML EP Catalog C API from Microsoft.Windows.AI.MachineLearning. # -# Downloads the NuGet package if needed, then defers to the package's first-party -# CMake config (build/cmake/microsoft.windows.ai.machinelearning-config.cmake) for -# target discovery. The config defines WindowsML::Api (EP catalog) and -# WindowsML::OnnxRuntime; we link only WindowsML::Api here so that ORT remains -# sourced from FindOnnxRuntime.cmake (Microsoft.ML.OnnxRuntime, same version for -# WinML and non-WinML builds). +# Downloads the NuGet package if needed, then loads the package's first-party +# CMake config (build/cmake/microsoft.windows.ai.machinelearning-config.cmake) +# for target discovery. The config defines WindowsML::Api (EP catalog) and +# WindowsML::OnnxRuntime; only WindowsML::Api is linked here — ORT comes from +# FindOnnxRuntime.cmake. # -# Reg-free runtime: WinML 2.x (Microsoft.Windows.AI.MachineLearning) does not -# require the Windows App SDK runtime bootstrap. The package ships a self-contained -# native DLL that can be loaded directly from any unpackaged app on Windows 10 -# 19H1 (build 18362) or newer. No MddBootstrapInitialize2 plumbing is needed. +# Microsoft.Windows.AI.MachineLearning ships a self-contained native DLL that +# loads directly from any unpackaged app on Windows 10 19H1 (build 18362) or +# newer, with no Windows App SDK runtime bootstrap required. # -# Re-exports an ALIAS target: WinMLEpCatalog::WinMLEpCatalog -> WindowsML::Api +# Exposes an ALIAS target: WinMLEpCatalog::WinMLEpCatalog -> WindowsML::Api # Sets: WINML_EP_CATALOG_DLL_DIR (= WINML_BINARY_DIR) if(WinMLEpCatalog_FOUND) @@ -81,7 +79,7 @@ else() SOURCE https://api.nuget.org/v3/index.json) endif() -# Defer to the package's first-party CMake config for target discovery and layout +# Load the package's first-party CMake config for target discovery and layout # resolution. The config lives at build/cmake/-config.cmake # and defines WindowsML::Api / WindowsML::OnnxRuntime / WindowsML::DirectML. set(_WINML_EP_CONFIG_DIR "${_WINML_EP_ROOT}/build/cmake") @@ -103,16 +101,14 @@ if(NOT TARGET WindowsML::Api) return() endif() -# Re-export under our existing name so consumers don't need to change. Promote -# WindowsML::Api to GLOBAL so the alias is visible in any subdirectory that may -# link foundry_local transitively. +# Promote WindowsML::Api to GLOBAL so the alias is visible in any subdirectory +# that links foundry_local transitively, then expose it as WinMLEpCatalog::WinMLEpCatalog. set_target_properties(WindowsML::Api PROPERTIES IMPORTED_GLOBAL TRUE) add_library(WinMLEpCatalog::WinMLEpCatalog ALIAS WindowsML::Api) -# Export the binary dir set by the official config (WINML_BINARY_DIR) under -# our existing variable name for the post-build DLL-copy step. The header dir -# is not re-exported; consumers get include paths via the WinMLEpCatalog::WinMLEpCatalog -# target's INTERFACE_INCLUDE_DIRECTORIES. +# Mirror the official config's WINML_BINARY_DIR as WINML_EP_CATALOG_DLL_DIR +# for the post-build DLL-copy step. Include paths flow through the +# WinMLEpCatalog::WinMLEpCatalog target's INTERFACE_INCLUDE_DIRECTORIES. set(WINML_EP_CATALOG_DLL_DIR "${WINML_BINARY_DIR}" CACHE PATH "WinML EP Catalog native DLL directory" FORCE) set(WinMLEpCatalog_FOUND TRUE) diff --git a/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc b/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc index e07c5f2f1..0a6fb4b00 100644 --- a/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc +++ b/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc @@ -24,13 +24,11 @@ namespace fl { namespace { -/// Look up the running Windows build number via ``ntdll!RtlGetVersion``. We -/// use ``GetProcAddress`` instead of including ```` to avoid the -/// header's macro pollution; the function signature is stable and documented. -/// ``RtlGetVersion`` accepts an ``OSVERSIONINFOW`` (same struct layout as -/// ``RTL_OSVERSIONINFOW`` — both are typedef aliases in ````) so -/// callers don't need any ``Rtl``-specific headers either. -/// Returns 0 if the lookup fails (purely diagnostic — never gates behavior). +/// Look up the running Windows build number via ``ntdll!RtlGetVersion``, +/// resolved through ``GetProcAddress`` to avoid pulling ```` and +/// its macro pollution. ``RtlGetVersion`` accepts an ``OSVERSIONINFOW`` +/// (typedef-aliased to ``RTL_OSVERSIONINFOW`` in ````). +/// Returns 0 on failure; the value is purely diagnostic and never gates behavior. DWORD QueryWindowsBuild() { using RtlGetVersionFn = LONG(WINAPI*)(OSVERSIONINFOW*); HMODULE ntdll = ::GetModuleHandleW(L"ntdll.dll"); @@ -138,10 +136,9 @@ std::vector> WinMLEpBootstrapper::DiscoverP HMODULE winml_dll = LoadLibraryW(L"Microsoft.Windows.AI.MachineLearning.dll"); if (!winml_dll) { - // Diagnostic only — older Windows (< build 18362) ships without - // Microsoft.Windows.AI.MachineLearning.dll, so EP discovery is expected - // to fail there. Include GetLastError() and the OS build to give the - // user a clear hint instead of an opaque "DLL not available". + // Microsoft.Windows.AI.MachineLearning.dll only ships on Windows 10 19H1 + // (build 18362) and newer; older builds fall through to the other + // bootstrappers. Log GetLastError() and the OS build for diagnostics. DWORD load_err = ::GetLastError(); DWORD os_build = QueryWindowsBuild(); logger.Log(LogLevel::Information, diff --git a/sdk_v2/cs/test/FoundryLocal.Tests/Microsoft.AI.Foundry.Local.Tests.csproj b/sdk_v2/cs/test/FoundryLocal.Tests/Microsoft.AI.Foundry.Local.Tests.csproj index 584ab47a0..39ce89b55 100644 --- a/sdk_v2/cs/test/FoundryLocal.Tests/Microsoft.AI.Foundry.Local.Tests.csproj +++ b/sdk_v2/cs/test/FoundryLocal.Tests/Microsoft.AI.Foundry.Local.Tests.csproj @@ -48,7 +48,7 @@ - + net8.0 From 8d4f73cd06b8995d26278594340a138cdee6d24d Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Thu, 11 Jun 2026 19:04:33 -0500 Subject: [PATCH 16/35] test(cs): multi-target net8.0;net9.0 for build coverage, run on net9.0 Add net9.0 to the test csproj TargetFrameworks (alongside net8.0 and net462 on Windows) so the test code compiles against both LTS .NET runtimes. Filter the test runs via --framework so the .NET (Core) test suite only executes once on net9.0: * net9.0 - .NET (Core) test surface (run + build). * net8.0 - build-only (back-compat covers net8.0 consumers). * net462 - runtime coverage of the netstandard2.0 + polyfill path (Windows, non-WinML only; run + build). Update both the v2 pipeline (.pipelines/v2/templates/steps-test-cs.yml) and the local convenience script (sdk_v2/build_and_test_all.ps1) to split into 'dotnet build' + per-framework 'dotnet test --no-build'. Verified locally: net9.0 default 94/7/0 (1m22s), net462 default 94/7/0 (1m41s), net9.0 WinML 94/7/0 (1m19s); all 3 TFMs build with 0 warnings under both SKUs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .pipelines/v2/templates/steps-test-cs.yml | 27 +++++++++++--- sdk_v2/build_and_test_all.ps1 | 35 ++++++++++++++----- .../Microsoft.AI.Foundry.Local.Tests.csproj | 20 ++++++----- 3 files changed, 60 insertions(+), 22 deletions(-) diff --git a/.pipelines/v2/templates/steps-test-cs.yml b/.pipelines/v2/templates/steps-test-cs.yml index 00b1902cd..7ee98c50e 100644 --- a/.pipelines/v2/templates/steps-test-cs.yml +++ b/.pipelines/v2/templates/steps-test-cs.yml @@ -168,10 +168,29 @@ steps: pwsh: true script: | $proj = "$(Build.SourcesDirectory)/sdk_v2/cs/test/FoundryLocal.Tests/Microsoft.AI.Foundry.Local.Tests.csproj" - dotnet test $proj ` - --no-build --configuration Release ` - /p:UseWinML=${{ parameters.isWinML }} - if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + + # Test TFMs: + # * net9.0 covers the .NET (Core) runtime test surface. + # * net462 (Windows only, non-WinML) exercises the netstandard2.0 surface + # + polyfills at runtime on .NET Framework. + # The csproj also targets net8.0, but only as build-time coverage — the .NET + # back-compat guarantee means a successful net9.0 test run validates the same + # assemblies for net8.0 consumers. + $frameworks = @('net9.0') + $isWin = $IsWindows -or ($PSVersionTable.Platform -eq $null) + $isWinML = '${{ parameters.isWinML }}' -eq 'True' + if ($isWin -and -not $isWinML) { + $frameworks += 'net462' + } + + foreach ($tfm in $frameworks) { + Write-Host "=== dotnet test --framework $tfm ===" + dotnet test $proj ` + --no-build --configuration Release ` + --framework $tfm ` + /p:UseWinML=${{ parameters.isWinML }} + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + } env: TF_BUILD: 'true' FOUNDRY_TEST_DATA_DIR: ${{ parameters.testDataSharedDir }} diff --git a/sdk_v2/build_and_test_all.ps1 b/sdk_v2/build_and_test_all.ps1 index fff54329a..e01561bdd 100644 --- a/sdk_v2/build_and_test_all.ps1 +++ b/sdk_v2/build_and_test_all.ps1 @@ -150,15 +150,32 @@ try { Invoke-Step 'cs' { Push-Location $csDir try { - $dotnetArgs = @( - 'test', - 'Microsoft.AI.Foundry.Local.SDK.sln', - '-c', $dotnetConfig, - '--nologo' - ) - if ($UseWinml) { $dotnetArgs += '-p:UseWinML=true' } - dotnet @dotnetArgs - if ($LASTEXITCODE -ne 0) { throw "dotnet test exit $LASTEXITCODE" } + $sln = 'Microsoft.AI.Foundry.Local.SDK.sln' + $buildArgs = @('build', $sln, '-c', $dotnetConfig, '--nologo') + if ($UseWinml) { $buildArgs += '-p:UseWinML=true' } + dotnet @buildArgs + if ($LASTEXITCODE -ne 0) { throw "dotnet build exit $LASTEXITCODE" } + + # The test csproj multi-targets net8.0/net9.0 (and net462 on Windows + # non-WinML) for build coverage; run the .NET (Core) test suite once + # on net9.0 (back-compat covers net8.0 consumers) plus net462 on + # Windows non-WinML to exercise the netstandard polyfills at runtime. + $frameworks = @('net9.0') + if ($IsWindows -and -not $UseWinml) { $frameworks += 'net462' } + + foreach ($tfm in $frameworks) { + Write-Host "=== dotnet test --framework $tfm ===" + $testArgs = @( + 'test', $sln, + '-c', $dotnetConfig, + '--no-build', + '--framework', $tfm, + '--nologo' + ) + if ($UseWinml) { $testArgs += '-p:UseWinML=true' } + dotnet @testArgs + if ($LASTEXITCODE -ne 0) { throw "dotnet test ($tfm) exit $LASTEXITCODE" } + } } finally { Pop-Location } diff --git a/sdk_v2/cs/test/FoundryLocal.Tests/Microsoft.AI.Foundry.Local.Tests.csproj b/sdk_v2/cs/test/FoundryLocal.Tests/Microsoft.AI.Foundry.Local.Tests.csproj index 39ce89b55..0d99e2388 100644 --- a/sdk_v2/cs/test/FoundryLocal.Tests/Microsoft.AI.Foundry.Local.Tests.csproj +++ b/sdk_v2/cs/test/FoundryLocal.Tests/Microsoft.AI.Foundry.Local.Tests.csproj @@ -1,13 +1,15 @@  - - net462;net8.0 - net8.0 + + net462;net8.0;net9.0 + net8.0;net9.0 enable enable latest @@ -48,8 +50,8 @@ - - net8.0 + + net8.0;net9.0 From 84f917aa3edcca83df4c8b848a17d827756545b0 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Fri, 12 Jun 2026 11:27:15 -0500 Subject: [PATCH 17/35] manager: drop stale WebGPU/WinML coexistence TODO The TODO from #758 anticipated needing a 'WinML-aware WebGPU path' when WinML 2.0 eventually adds WebGPU to its OS EP catalog. With WinML 2.x now bootstrapped (this PR), the design is clear: Foundry's WebGPU EP registers under 'Foundry.WebGPU' while WinML catalog EPs use their own names (e.g., 'OpenVINOExecutionProvider'), so the two paths coexist as separate registrations without conflict. No follow-up work is needed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- sdk_v2/cpp/src/manager.cc | 2 -- 1 file changed, 2 deletions(-) diff --git a/sdk_v2/cpp/src/manager.cc b/sdk_v2/cpp/src/manager.cc index b10a1a3a5..18b386954 100644 --- a/sdk_v2/cpp/src/manager.cc +++ b/sdk_v2/cpp/src/manager.cc @@ -260,8 +260,6 @@ Manager::Manager(const Configuration& config) } // WebGPU EP — always available (no hardware detection needed). - // TODO(@bmehta001): When WinML 2.0 adds WebGPU support, add a WinML-aware - // WebGPU path here that can coexist with the WinML EPs discovered above. const auto webgpu_ep_dir = cache_dir / "webgpu-ep"; bootstrappers.push_back(std::make_unique(webgpu_ep_dir.string(), register_ep)); } From d9ffe60b7fe60298e8013ff808a21fbc44a47f00 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Fri, 12 Jun 2026 11:40:25 -0500 Subject: [PATCH 18/35] test/ep_detection: drop stale CUDA/WebGPU name skip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirrors the prior commit dropping the WinML/WebGPU coexistence TODO. Foundry's CUDA + WebGPU are pre-registered by SharedTestEnv, so the unregistered candidate is naturally an OS WinML catalog EP — no explicit name filter is needed. Verified: EpDetectionApiTest.DownloadAndRegister_WinMLEp_RegistersFromOsCatalog still picks OpenVINOExecutionProvider on Windows 11 24H2 and passes the full WinML 2.x register path end-to-end (7/7 EpDetectionApiTest tests green). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- sdk_v2/cpp/test/sdk_api/ep_detection_test.cc | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/sdk_v2/cpp/test/sdk_api/ep_detection_test.cc b/sdk_v2/cpp/test/sdk_api/ep_detection_test.cc index c47be7119..c386051a5 100644 --- a/sdk_v2/cpp/test/sdk_api/ep_detection_test.cc +++ b/sdk_v2/cpp/test/sdk_api/ep_detection_test.cc @@ -95,19 +95,15 @@ TEST_F(EpDetectionApiTest, GetDiscoverableEps_WindowsHasWinMLProviders) { // Exercises the WinML 2.x download/register path end-to-end: // WinMLEpEnsureReady → WinMLEpGetLibraryPath → ORT RegisterExecutionProvider. -// Picks any discoverable EP that SharedTestEnv has NOT already registered -// (i.e. one served by the OS WinML catalog rather than Foundry's own download). +// Picks the first discoverable EP that is not already registered. SharedTestEnv +// pre-registers Foundry's CUDA + WebGPU, so the unregistered candidate is always +// served by the OS WinML catalog. // Skipped on minimal images where the OS exposes no WinML EPs. TEST_F(EpDetectionApiTest, DownloadAndRegister_WinMLEp_RegistersFromOsCatalog) { auto eps_before = manager().GetDiscoverableEps(); std::string target; for (const auto& ep : eps_before) { - // Skip Foundry-managed EPs (CUDA / WebGPU) — these don't exercise the - // WinML 2.x C API path. We want EPs discovered by the OS catalog. - if (ep.name == "CUDAExecutionProvider" || ep.name == "WebGpuExecutionProvider") { - continue; - } if (!ep.is_registered) { target = ep.name; break; From 8cd80620aa9c747f6d87edff2fd6e20a7af6ffbf Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Fri, 12 Jun 2026 13:58:03 -0500 Subject: [PATCH 19/35] test/cs: collapse duplicate TargetFrameworks declarations Replace 3 conditional blocks with 2: a base 'net8.0;net9.0' and a single Windows-default-only override that adds net462. The previous third block (WinML SKU) redeclared the same 'net8.0;net9.0' the base already produces. Also hoist and to the top of the PropertyGroup so the TargetFrameworks condition can reference them directly. Verified: default SKU emits net462+net8.0+net9.0; WinML SKU emits net8.0+net9.0; both build clean (0 warnings). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Microsoft.AI.Foundry.Local.Tests.csproj | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/sdk_v2/cs/test/FoundryLocal.Tests/Microsoft.AI.Foundry.Local.Tests.csproj b/sdk_v2/cs/test/FoundryLocal.Tests/Microsoft.AI.Foundry.Local.Tests.csproj index 0d99e2388..77611864c 100644 --- a/sdk_v2/cs/test/FoundryLocal.Tests/Microsoft.AI.Foundry.Local.Tests.csproj +++ b/sdk_v2/cs/test/FoundryLocal.Tests/Microsoft.AI.Foundry.Local.Tests.csproj @@ -1,21 +1,23 @@  - - net462;net8.0;net9.0 - net8.0;net9.0 + false + true + + + net8.0;net9.0 + net462;net8.0;net9.0 + enable enable latest false - true - false false @@ -49,11 +51,6 @@ - - - net8.0;net9.0 - - From 103696a7e4ef3a1138297ce7be29650c360ba50a Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Fri, 12 Jun 2026 15:05:03 -0500 Subject: [PATCH 20/35] cleanup: drop stale WinAppSDK installs, _winml.json ref, orphan JS helper - .pipelines/v2: remove WinAppSDK 1.8 runtime install from C# and Python test steps for isWinML=true (WinML 2.x is reg-free, no WinAppSDK dep). - sdk_v2/DEVELOPMENT.md: drop _winml.json from build row (file was deleted earlier in this PR; deps_versions.json is the sole source now). - sdk_v2/js/src/detail/native.ts: delete orphaned getResolvedLibraryDir (sole caller applyBootstrapAutoDetect was removed; not re-exported). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .pipelines/v2/templates/steps-test-cs.yml | 14 -------------- .pipelines/v2/templates/steps-test-python.yml | 14 -------------- sdk_v2/DEVELOPMENT.md | 2 +- sdk_v2/js/src/detail/native.ts | 10 ---------- 4 files changed, 1 insertion(+), 39 deletions(-) diff --git a/.pipelines/v2/templates/steps-test-cs.yml b/.pipelines/v2/templates/steps-test-cs.yml index 7ee98c50e..c483857fc 100644 --- a/.pipelines/v2/templates/steps-test-cs.yml +++ b/.pipelines/v2/templates/steps-test-cs.yml @@ -76,20 +76,6 @@ steps: - task: NuGetAuthenticate@1 displayName: 'Authenticate NuGet feeds' -- ${{ if eq(parameters.isWinML, true) }}: - - task: PowerShell@2 - displayName: 'Install Windows App SDK Runtime' - inputs: - targetType: 'inline' - pwsh: true - script: | - $installerUrl = "https://aka.ms/windowsappsdk/1.8/latest/windowsappruntimeinstall-x64.exe" - $installerPath = "$env:TEMP\windowsappruntimeinstall.exe" - Invoke-WebRequest -Uri $installerUrl -OutFile $installerPath - & $installerPath --quiet --force - if ($LASTEXITCODE -ne 0) { throw "Windows App SDK Runtime install failed" } - errorActionPreference: 'stop' - # Per-job NuGet isolation to prevent "Central Directory corrupt" / file-locking # errors when multiple C# test jobs (regular + WinML) run concurrently on the # same reused agent. Keyed by $(System.JobId); cleaned on each run. diff --git a/.pipelines/v2/templates/steps-test-python.yml b/.pipelines/v2/templates/steps-test-python.yml index 3b95b71fd..9396a7cce 100644 --- a/.pipelines/v2/templates/steps-test-python.yml +++ b/.pipelines/v2/templates/steps-test-python.yml @@ -30,20 +30,6 @@ steps: addToPath: true architecture: '${{ parameters.pythonArchitecture }}' -- ${{ if eq(parameters.isWinML, true) }}: - - task: PowerShell@2 - displayName: 'Install Windows App SDK Runtime' - inputs: - targetType: 'inline' - pwsh: true - script: | - $installerUrl = "https://aka.ms/windowsappsdk/1.8/latest/windowsappruntimeinstall-x64.exe" - $installerPath = "$env:TEMP\windowsappruntimeinstall.exe" - Invoke-WebRequest -Uri $installerUrl -OutFile $installerPath - & $installerPath --quiet --force - if ($LASTEXITCODE -ne 0) { throw "Windows App SDK Runtime install failed" } - errorActionPreference: 'stop' - # Job-local venv so installs never pollute the agent's site-packages. - task: PowerShell@2 displayName: 'Create test venv' diff --git a/sdk_v2/DEVELOPMENT.md b/sdk_v2/DEVELOPMENT.md index 66f18e37a..bc8afb6c2 100644 --- a/sdk_v2/DEVELOPMENT.md +++ b/sdk_v2/DEVELOPMENT.md @@ -64,7 +64,7 @@ install per-SDK package dependencies on first run: | SDK | What runs | | ------ | -------------------------------------------------------------------------------------- | -| C++ | `python build.py [--config ...] [--use_winml] [--skip_tests]` — configure + build + ctest. vcpkg restores native deps; ORT/GenAI come from NuGet via FetchContent (versions from `sdk_v2/deps_versions.json` or `_winml.json`). | +| C++ | `python build.py [--config ...] [--use_winml] [--skip_tests]` — configure + build + ctest. vcpkg restores native deps; ORT/GenAI come from NuGet via FetchContent (versions from `sdk_v2/deps_versions.json`). | | C# | `dotnet test Microsoft.AI.Foundry.Local.SDK.sln -c Release [-p:UseWinML=true]` — restores NuGet packages on demand. | | Python | `python -m pip install -e .[dev]` (compiles the cffi extension; needs MSVC/Clang) → `python -m pytest test/`. | | JS | `npm install` (runs `node-gyp` against the C++ build output) → `npm run build` → `npm test` (vitest). | diff --git a/sdk_v2/js/src/detail/native.ts b/sdk_v2/js/src/detail/native.ts index c9f8a7356..683afe86f 100644 --- a/sdk_v2/js/src/detail/native.ts +++ b/sdk_v2/js/src/detail/native.ts @@ -390,13 +390,3 @@ export function configureNativeLoader(opts: { libraryPath?: string }): void { export function getPreloadedLibraryPath(): string | undefined { return preloaded; } - -/** - * Returns the directory the native foundry_local shared library is resolved from for the given config — - * either the caller's explicit `libraryPath` or the prebuild directory the addon itself lives in (which is - * where `copy-native:dev` and CI prebuild populate `foundry_local.{dll,so,dylib}`). - */ -export function getResolvedLibraryDir(libraryPath?: string): string { - if (libraryPath !== undefined && libraryPath !== "") return libraryPath; - return prebuildDir; -} From 68361f3433e958724dd039f10a0fb72f896ce666 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Fri, 12 Jun 2026 19:42:18 -0500 Subject: [PATCH 21/35] cleanup: drop stale Win32 include + WindowsAppRuntime/version doc refs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - manager.cc: remove dead /WIN32_LEAN_AND_MEAN block — no Win32 symbols are used in this TU after MddBootstrap and winml_bootstrap.h were removed; comes transitively from ep_detection/ep_types.h. - copy-native.mjs, pack-prebuilds.mjs: drop WindowsAppRuntime from header comments — WindowsAppRuntime is no longer bundled or copied after the WinML 2.x reg-free migration. - EpDetectionPlan.md: fix v1.8.2141 -> 2.1.70 package version and rewrite the OS-version gate step to match actual implementation (build number is diagnostic only; gating is at WinMLEpCatalogCreate). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- sdk_v2/cpp/docs/EpDetectionPlan.md | 5 +++-- sdk_v2/cpp/src/manager.cc | 6 ------ sdk_v2/js/script/copy-native.mjs | 4 ++-- sdk_v2/js/script/pack-prebuilds.mjs | 2 +- 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/sdk_v2/cpp/docs/EpDetectionPlan.md b/sdk_v2/cpp/docs/EpDetectionPlan.md index fd6f74981..27807276d 100644 --- a/sdk_v2/cpp/docs/EpDetectionPlan.md +++ b/sdk_v2/cpp/docs/EpDetectionPlan.md @@ -77,7 +77,7 @@ because ORT's C API is ABI-stable. ### WinML EP Catalog C API Summary -The v1.8.2141 package provides a pure C API (no WinRT/C++/WinRT projection needed): +The `Microsoft.Windows.AI.MachineLearning` 2.1.70 package provides a pure C API (no WinRT/C++/WinRT projection needed): ```c // Catalog lifecycle @@ -237,7 +237,8 @@ static std::vector> ``` **Implementation:** -1. OS version check: `IsWindowsVersionOrGreater(10, 0, 26100)`. Return empty if not. +1. Query Windows build number via `RtlGetVersion` for diagnostics only — never gates + behavior. Gating happens at `WinMLEpCatalogCreate()` (DLL load failure → empty). 2. `WinMLEpCatalogCreate()` — if this fails (DLL not found / delay-load failure), log info, return empty. Not an error. 3. `WinMLEpCatalogEnumProviders()` — callback collects `WinMLEpHandle` + `WinMLEpInfo`. diff --git a/sdk_v2/cpp/src/manager.cc b/sdk_v2/cpp/src/manager.cc index 18b386954..3702bd000 100644 --- a/sdk_v2/cpp/src/manager.cc +++ b/sdk_v2/cpp/src/manager.cc @@ -27,12 +27,6 @@ #include "telemetry/telemetry_logger.h" #include "utils.h" -#ifdef _WIN32 -#define WIN32_LEAN_AND_MEAN -#include -#include -#endif - #if FOUNDRY_LOCAL_HAS_EP_CATALOG #include "ep_detection/winml_ep_bootstrapper.h" #endif diff --git a/sdk_v2/js/script/copy-native.mjs b/sdk_v2/js/script/copy-native.mjs index c52fd0b37..694b41716 100644 --- a/sdk_v2/js/script/copy-native.mjs +++ b/sdk_v2/js/script/copy-native.mjs @@ -1,5 +1,5 @@ -// Dev-time only. Copies foundry_local.{dll,so,dylib} AND its ORT/GenAI/WinML/ -// WindowsAppRuntime siblings from sdk_v2/cpp/build///bin/ +// Dev-time only. Copies foundry_local.{dll,so,dylib} AND its ORT/GenAI/WinML +// siblings from sdk_v2/cpp/build///bin/ // / into sdk_v2/js/prebuilds/-/ so // `npm test` works locally without the developer having to configure // Configuration.libraryPath or set env vars. diff --git a/sdk_v2/js/script/pack-prebuilds.mjs b/sdk_v2/js/script/pack-prebuilds.mjs index 8416cc785..b8129effd 100644 --- a/sdk_v2/js/script/pack-prebuilds.mjs +++ b/sdk_v2/js/script/pack-prebuilds.mjs @@ -1,5 +1,5 @@ // CI-only. Stages the foundry_local shared library into prebuilds/ for npm -// publish. ORT / ORT-GenAI / WinML / WindowsAppRuntime are NOT bundled — the +// publish. ORT / ORT-GenAI / WinML are NOT bundled — the // install-native.cjs postinstall hook fetches them from NuGet at the user's // machine (see ort-loading-contract.instructions.md). The .node addon itself // is already produced into prebuilds/-/ by `node-gyp rebuild`. From c79332b7d527fca99f2cef5a4850e17a7f6fa805 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Fri, 12 Jun 2026 20:43:11 -0500 Subject: [PATCH 22/35] winml_ep: forward incremental download progress + sync doc with impl WinMLEpEnsureReady was synchronous, so the caller's progress callback only ever saw a 0->100% jump after the entire EP download finished. Switch to WinMLEpEnsureReadyAsync with a WinMLAsyncBlock that forwards the fractional progress (0.0-1.0) to the existing percent callback (scaled to 0-99 so 100% remains reserved for post-registration), and wait synchronously via WinMLAsyncGetStatus(..., TRUE). Caller-requested cancellation (progress callback returns false) routes through WinMLAsyncCancel. Also bring EpDetectionPlan.md into agreement with the as-built code: Name()/IsRegistered()/DownloadAndRegister() descriptions now match the actual implementation, and DiscoverProviders documents the EpRegistrationCallback parameter. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- sdk_v2/cpp/docs/EpDetectionPlan.md | 17 ++++-- .../src/ep_detection/winml_ep_bootstrapper.cc | 59 +++++++++++++++++-- 2 files changed, 67 insertions(+), 9 deletions(-) diff --git a/sdk_v2/cpp/docs/EpDetectionPlan.md b/sdk_v2/cpp/docs/EpDetectionPlan.md index 27807276d..563a5c964 100644 --- a/sdk_v2/cpp/docs/EpDetectionPlan.md +++ b/sdk_v2/cpp/docs/EpDetectionPlan.md @@ -224,16 +224,23 @@ New methods return empty/no-op. **Design:** - Each instance wraps a single `WinMLEpHandle` obtained from catalog enumeration. -- `Name()` → `WinMLEpGetName()` -- `IsRegistered()` → `WinMLEpGetReadyState() == WinMLEpReadyState_Ready` -- `DownloadAndRegister()` → `WinMLEpEnsureReadyAsync()` with `WinMLAsyncBlock` - progress callback, then synchronous wait via `WinMLAsyncGetStatus(async, TRUE)`. +- `Name()` returns the EP name captured during enumeration. +- `IsRegistered()` returns whether the EP has been successfully registered with ORT + (tracked via a `registered_` flag flipped on by `DownloadAndRegister`). Note this + is ORT-registration state, not WinML readyState — an EP can be `Ready` in the + WinML catalog but not yet registered with ORT. +- `DownloadAndRegister()` → `WinMLEpEnsureReadyAsync()` with a `WinMLAsyncBlock` + whose progress callback forwards fractional progress (0.0–1.0) to the caller's + percent callback (0–100), then `WinMLAsyncGetStatus(async, TRUE)` for a + synchronous wait. Caller-requested cancellation goes through `WinMLAsyncCancel`. **Discovery (static factory):** ```cpp // Returns one bootstrapper per discovered WinML EP. Empty if unavailable. +// register_ep is plumbed through construction time so each bootstrapper can +// register its library with ORT inside DownloadAndRegister(). static std::vector> - DiscoverProviders(ILogger& logger); + DiscoverProviders(EpRegistrationCallback register_ep, ILogger& logger); ``` **Implementation:** diff --git a/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc b/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc index 0a6fb4b00..29489793d 100644 --- a/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc +++ b/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc @@ -11,10 +11,12 @@ #include +#include #include #include // WinML EP Catalog C API — delay-loaded via Microsoft.Windows.AI.MachineLearning.dll +#include #include #define WIN32_LEAN_AND_MEAN @@ -48,6 +50,34 @@ DWORD QueryWindowsBuild() { return info.dwBuildNumber; } +/// Context handed to ``WinMLAsyncBlock`` so the progress thunk can forward +/// fractional progress to the caller-supplied percentage callback and signal +/// cancellation back through ``WinMLAsyncCancel``. +struct EnsureReadyAsyncCtx { + const std::string* name = nullptr; + const fl::IEpBootstrapper::ProgressCallback* progress_cb = nullptr; + std::atomic cancel_requested{false}; +}; + +/// Forwards WinML's fractional download progress (0.0-1.0) to our percentage +/// callback (0-100). Reserves 100% for the post-registration step so the user +/// sees a distinct transition once ORT registration succeeds. +void CALLBACK EnsureReadyProgressThunk(WinMLAsyncBlock* async, double progress) { + auto* ctx = static_cast(async->context); + if (!ctx || !ctx->progress_cb || !*ctx->progress_cb) { + return; + } + + double pct = progress * 99.0; + if (pct < 0.0) pct = 0.0; + if (pct > 99.0) pct = 99.0; + + if (!(*ctx->progress_cb)(*ctx->name, static_cast(pct))) { + ctx->cancel_requested.store(true, std::memory_order_release); + WinMLAsyncCancel(async); + } +} + } // namespace WinMLEpBootstrapper::WinMLEpBootstrapper(std::string name, EpRegistrationCallback register_ep, @@ -75,12 +105,33 @@ bool WinMLEpBootstrapper::DownloadAndRegister(bool force, return true; } - // Ask the OS to download/prepare the EP if needed. - HRESULT hr = WinMLEpEnsureReady(ep_handle_); + // Ask the OS to download/prepare the EP if needed. We use the async variant + // so we can forward fractional download progress to the caller and respect + // cancellation; WinMLAsyncGetStatus(..., TRUE) gives us a synchronous wait. + EnsureReadyAsyncCtx ctx; + ctx.name = &name_; + ctx.progress_cb = &progress_cb; + + WinMLAsyncBlock block{}; + block.context = &ctx; + block.callback = nullptr; + block.progress = (progress_cb ? &EnsureReadyProgressThunk : nullptr); + + HRESULT hr = WinMLEpEnsureReadyAsync(ep_handle_, &block); + if (SUCCEEDED(hr)) { + hr = WinMLAsyncGetStatus(&block, TRUE); + } + WinMLAsyncClose(&block); if (FAILED(hr)) { - logger.Log(LogLevel::Warning, - fmt::format("WinML EP {}: EnsureReady failed (hr=0x{:08X})", name_, static_cast(hr))); + if (ctx.cancel_requested.load(std::memory_order_acquire)) { + logger.Log(LogLevel::Information, + fmt::format("WinML EP {}: cancelled by caller", name_)); + } else { + logger.Log(LogLevel::Warning, + fmt::format("WinML EP {}: EnsureReady failed (hr=0x{:08X})", name_, + static_cast(hr))); + } return false; } From 5f107d1aca5f9ec8b0f44b3ba965874d7a0fa3f1 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Fri, 12 Jun 2026 21:17:44 -0500 Subject: [PATCH 23/35] winml_ep: report raw progress, cache library path, single-SoT version - winml_ep_bootstrapper.cc: pass WinML progress through as a percent directly (it reports 0-100, not 0-1, matching the WinRT projection's IAsyncOperationWithProgress<_, double> convention used in neutron-server). Drop the artificial 99% cap. - winml_ep_bootstrapper: cache library_path_ captured during enumeration and reuse it in DownloadAndRegister to skip the redundant WinMLEpGetLibraryPath{Size,} pair when the EP was already Ready. - winml_ep_bootstrapper.h: document that ep_handle_'s lifetime is owned by catalog_ref_, so per-EP cleanup is intentionally absent. - deps_versions.json: add windows-ai-machinelearning entry; expand the _comment to call out that pipeline literals must match (validation step below enforces). - FindWinMLEpCatalog.cmake: read WINML_EP_CATALOG_VERSION from deps_versions.json via string(JSON), matching FindOnnxRuntime.cmake. - steps-prefetch-nuget.yml: add a 'Validate pinned versions' guard that reads deps_versions.json and fails the pipeline if any of cppOrtVersion / cppGenaiVersion / cppWinmlVersion drifts. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../v2/templates/steps-prefetch-nuget.yml | 33 +++++++++++ sdk_v2/cpp/cmake/FindWinMLEpCatalog.cmake | 16 ++++-- .../src/ep_detection/winml_ep_bootstrapper.cc | 55 ++++++++++--------- .../src/ep_detection/winml_ep_bootstrapper.h | 3 + sdk_v2/deps_versions.json | 7 ++- 5 files changed, 80 insertions(+), 34 deletions(-) diff --git a/.pipelines/v2/templates/steps-prefetch-nuget.yml b/.pipelines/v2/templates/steps-prefetch-nuget.yml index 47c0c9a6b..cdd451e29 100644 --- a/.pipelines/v2/templates/steps-prefetch-nuget.yml +++ b/.pipelines/v2/templates/steps-prefetch-nuget.yml @@ -32,6 +32,39 @@ parameters: steps: +# Fail fast if the pipeline-pinned versions drift from the cmake source of +# truth in sdk_v2/deps_versions.json. +- task: PowerShell@2 + displayName: 'Validate pinned versions match deps_versions.json' + inputs: + targetType: inline + pwsh: true + script: | + $ErrorActionPreference = 'Stop' + $depsFile = Join-Path "$(Build.SourcesDirectory)" "sdk_v2/deps_versions.json" + if (-not (Test-Path $depsFile)) { throw "deps_versions.json not found at $depsFile" } + $deps = Get-Content $depsFile -Raw | ConvertFrom-Json + $expected = @{ + 'onnxruntime' = '${{ parameters.ortVersion }}' + 'onnxruntime-genai' = '${{ parameters.genaiVersion }}' + } + if ('${{ parameters.winmlVersion }}' -ne '') { + $expected['windows-ai-machinelearning'] = '${{ parameters.winmlVersion }}' + } + $errors = @() + foreach ($key in $expected.Keys) { + $actual = $deps.$key.version + if ($actual -ne $expected[$key]) { + $errors += " $key`: pipeline says '$($expected[$key])', deps_versions.json says '$actual'" + } + } + if ($errors.Count -gt 0) { + Write-Host "Version drift detected between pipeline literals and sdk_v2/deps_versions.json:" + $errors | ForEach-Object { Write-Host $_ } + throw "Bump the matching cpp*Version variable in .pipelines/foundry-local-packaging.yml" + } + Write-Host "Pinned versions agree with sdk_v2/deps_versions.json." + - ${{ if eq(parameters.shell, 'pwsh') }}: - task: PowerShell@2 displayName: 'Pre-download NuGet packages' diff --git a/sdk_v2/cpp/cmake/FindWinMLEpCatalog.cmake b/sdk_v2/cpp/cmake/FindWinMLEpCatalog.cmake index 603e0ed7f..78793feeb 100644 --- a/sdk_v2/cpp/cmake/FindWinMLEpCatalog.cmake +++ b/sdk_v2/cpp/cmake/FindWinMLEpCatalog.cmake @@ -25,12 +25,18 @@ if(NOT CMAKE_SYSTEM_NAME STREQUAL "Windows") return() endif() -# Latest GA Microsoft.Windows.AI.MachineLearning on nuget.org. Bump as new GA -# releases ship; the WinMLEpCatalog.h C ABI is stable across 2.0.x and 2.1.x. -set(_WINML_EP_CATALOG_MIN_VERSION "2.1.70") - +# Version comes from sdk_v2/deps_versions.json (single source of truth, same +# pattern as FindOnnxRuntime.cmake). The WinMLEpCatalog.h C ABI is stable +# across 2.0.x and 2.1.x. Override at the cmake command line with +# -DWINML_EP_CATALOG_VERSION=... if(NOT WINML_EP_CATALOG_VERSION) - set(WINML_EP_CATALOG_VERSION "${_WINML_EP_CATALOG_MIN_VERSION}") + set(_DEPS_FILE "${CMAKE_CURRENT_LIST_DIR}/../../deps_versions.json") + if(NOT EXISTS "${_DEPS_FILE}") + message(FATAL_ERROR "Required versions file not found: ${_DEPS_FILE}") + endif() + file(READ "${_DEPS_FILE}" _DEPS_JSON) + string(JSON WINML_EP_CATALOG_VERSION GET "${_DEPS_JSON}" "windows-ai-machinelearning" "version") + message(STATUS "WINML_EP_CATALOG_VERSION=${WINML_EP_CATALOG_VERSION} (from ${_DEPS_FILE})") endif() # The package's own CMake config FATAL_ERRORs on architectures it doesn't ship diff --git a/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc b/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc index 29489793d..93332bde9 100644 --- a/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc +++ b/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc @@ -59,18 +59,18 @@ struct EnsureReadyAsyncCtx { std::atomic cancel_requested{false}; }; -/// Forwards WinML's fractional download progress (0.0-1.0) to our percentage -/// callback (0-100). Reserves 100% for the post-registration step so the user -/// sees a distinct transition once ORT registration succeeds. +/// Forwards WinML's download progress to our percentage callback. WinML +/// reports a percent value (0-100) directly, matching how the WinRT +/// projection's IAsyncOperationWithProgress<_, double> reports progress. void CALLBACK EnsureReadyProgressThunk(WinMLAsyncBlock* async, double progress) { auto* ctx = static_cast(async->context); if (!ctx || !ctx->progress_cb || !*ctx->progress_cb) { return; } - double pct = progress * 99.0; + double pct = progress; if (pct < 0.0) pct = 0.0; - if (pct > 99.0) pct = 99.0; + if (pct > 100.0) pct = 100.0; if (!(*ctx->progress_cb)(*ctx->name, static_cast(pct))) { ctx->cancel_requested.store(true, std::memory_order_release); @@ -135,34 +135,37 @@ bool WinMLEpBootstrapper::DownloadAndRegister(bool force, return false; } - // Retrieve the library path from the EP handle. - size_t path_size = 0; - hr = WinMLEpGetLibraryPathSize(ep_handle_, &path_size); + // Reuse the path captured during enumeration when the EP was already Ready; + // otherwise fetch it now that EnsureReady has populated it. + if (library_path_.empty()) { + size_t path_size = 0; + hr = WinMLEpGetLibraryPathSize(ep_handle_, &path_size); - if (FAILED(hr) || path_size == 0) { - logger.Log(LogLevel::Warning, fmt::format("WinML EP {}: failed to get library path size (hr=0x{:08X})", name_, - static_cast(hr))); - return false; - } + if (FAILED(hr) || path_size == 0) { + logger.Log(LogLevel::Warning, fmt::format("WinML EP {}: failed to get library path size (hr=0x{:08X})", name_, + static_cast(hr))); + return false; + } - std::string path(path_size, '\0'); - hr = WinMLEpGetLibraryPath(ep_handle_, path.size(), path.data(), nullptr); + std::string path(path_size, '\0'); + hr = WinMLEpGetLibraryPath(ep_handle_, path.size(), path.data(), nullptr); - if (FAILED(hr)) { - logger.Log(LogLevel::Warning, fmt::format("WinML EP {}: failed to get library path (hr=0x{:08X})", name_, - static_cast(hr))); - return false; - } + if (FAILED(hr)) { + logger.Log(LogLevel::Warning, fmt::format("WinML EP {}: failed to get library path (hr=0x{:08X})", name_, + static_cast(hr))); + return false; + } - // The API may include a trailing null in the reported size. - if (!path.empty() && path.back() == '\0') { - path.pop_back(); - } + // The API may include a trailing null in the reported size. + if (!path.empty() && path.back() == '\0') { + path.pop_back(); + } - library_path_ = path; + library_path_ = std::move(path); + } // Register with ORT via the callback. - if (!register_ep_(name_, std::filesystem::path(path))) { + if (!register_ep_(name_, std::filesystem::path(library_path_))) { logger.Log(LogLevel::Warning, fmt::format("WinML EP {}: ORT registration failed", name_)); return false; } diff --git a/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.h b/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.h index 87c04e13b..2c14f9348 100644 --- a/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.h +++ b/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.h @@ -65,6 +65,9 @@ class WinMLEpBootstrapper : public IEpBootstrapper { // Shared across all bootstrappers from the same DiscoverProviders() call // to keep the catalog alive until the last bootstrapper is destroyed. std::shared_ptr catalog_ref_; + // Owned by the catalog (no per-EP release in the WinML C API), so this + // raw pointer is valid as long as catalog_ref_ outlives it. Per-EP + // cleanup is intentionally absent — do not add Release-like calls here. WinMLEpHandle ep_handle_ = nullptr; WinMLEpBootstrapper(std::string name, EpRegistrationCallback register_ep, diff --git a/sdk_v2/deps_versions.json b/sdk_v2/deps_versions.json index 204f90a7c..a668dfc68 100644 --- a/sdk_v2/deps_versions.json +++ b/sdk_v2/deps_versions.json @@ -1,5 +1,6 @@ { - "_comment": "Single source of truth for ORT and ORT-GenAI versions in sdk_v2. Read by sdk_v2/cpp/cmake/FindOnnxRuntime*.cmake and sdk_v2/python/_build_backend/__init__.py.", - "onnxruntime": { "version": "1.25.1" }, - "onnxruntime-genai": { "version": "0.13.2" } + "_comment": "Single source of truth for native dependency versions in sdk_v2. Read by sdk_v2/cpp/cmake/Find*.cmake and sdk_v2/python/_build_backend/__init__.py. The .pipelines/foundry-local-packaging.yml literals must match; the 'Validate pinned versions' step fails the build on drift.", + "onnxruntime": { "version": "1.25.1" }, + "onnxruntime-genai": { "version": "0.13.2" }, + "windows-ai-machinelearning": { "version": "2.1.70" } } From 9bee1a7e1be8bd0d7a509aa7db33a252129b7dd8 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Sat, 13 Jun 2026 13:37:43 -0500 Subject: [PATCH 24/35] cleanup: address Copilot review comments - steps-prefetch-nuget.yml: drop -UseBasicParsing on Invoke-WebRequest for the WinML download; PowerShell Core always uses the basic parser, so the switch is a no-op (kept for the standard NuGet loop unchanged since both behaviors match). - _build_backend/__init__.py: remove dead _WINML_PKG_NAME constant; the WinML name override flows entirely through FL_PYTHON_PACKAGE_NAME. - EpDetectionPlan.md: add missing trailing period in Phase 1 validation note. Two other Copilot comments not actioned: the OSVERSIONINFOW concern was already addressed in an earlier commit (current code uses OSVERSIONINFOW from , which is layout-compatible with RTL_OSVERSIONINFOW and avoids needing ), and the "2.1.6 vs 2.1.70" PR-description mismatch isn't present (PR body consistently says 2.1.70). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .pipelines/v2/templates/steps-prefetch-nuget.yml | 2 +- sdk_v2/cpp/docs/EpDetectionPlan.md | 2 +- sdk_v2/python/_build_backend/__init__.py | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.pipelines/v2/templates/steps-prefetch-nuget.yml b/.pipelines/v2/templates/steps-prefetch-nuget.yml index cdd451e29..661075294 100644 --- a/.pipelines/v2/templates/steps-prefetch-nuget.yml +++ b/.pipelines/v2/templates/steps-prefetch-nuget.yml @@ -112,7 +112,7 @@ steps: $winmlUrl = "$feed/Microsoft.Windows.AI.MachineLearning/${{ parameters.winmlVersion }}" $winmlOut = "$cacheDir/winml.nupkg" Write-Host "Downloading Microsoft.Windows.AI.MachineLearning ${{ parameters.winmlVersion }}" - Invoke-WebRequest -Uri $winmlUrl -OutFile $winmlOut -UseBasicParsing + Invoke-WebRequest -Uri $winmlUrl -OutFile $winmlOut if (-not (Test-Path $winmlOut)) { throw "WinML download failed" } Write-Host " -> $winmlOut ($((Get-Item $winmlOut).Length) bytes)" $defines += "WINML_EP_CATALOG_FETCH_URL=$winmlOut" diff --git a/sdk_v2/cpp/docs/EpDetectionPlan.md b/sdk_v2/cpp/docs/EpDetectionPlan.md index 563a5c964..f26964d2a 100644 --- a/sdk_v2/cpp/docs/EpDetectionPlan.md +++ b/sdk_v2/cpp/docs/EpDetectionPlan.md @@ -147,7 +147,7 @@ STDAPI WinMLEpEnsureReadyAsync(WinMLEpHandle ep, WinMLAsyncBlock* async); not be present on older systems **Validation:** Write a minimal test that calls `WinMLEpCatalogCreate()` + -`WinMLEpCatalogEnumProviders()` and prints discovered EPs. Run on Windows 10 19H1+ +`WinMLEpCatalogEnumProviders()` and prints discovered EPs. Run on Windows 10 19H1+. --- diff --git a/sdk_v2/python/_build_backend/__init__.py b/sdk_v2/python/_build_backend/__init__.py index 87f920cf3..a7a90014b 100644 --- a/sdk_v2/python/_build_backend/__init__.py +++ b/sdk_v2/python/_build_backend/__init__.py @@ -50,7 +50,6 @@ _ENV_VAR = "FL_PYTHON_PACKAGE_NAME" -_WINML_PKG_NAME = "foundry-local-sdk-winml" _PYPROJECT = Path(__file__).resolve().parent.parent / "pyproject.toml" _SDK_V2_ROOT = _PYPROJECT.resolve().parent.parent _DEPS_JSON_STD = _SDK_V2_ROOT / "deps_versions.json" From a87cb9bb22fba327b82d69ca26bd9ce265b994e7 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Sat, 13 Jun 2026 20:53:20 -0500 Subject: [PATCH 25/35] docs: fix EpDetectionPlan progress-callback description MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Phase 3 design note still described the old fractional (0.0–1.0) progress contract that was removed when the artificial 99% cap was dropped. The current implementation in winml_ep_bootstrapper.cc treats WinML's progress value as a percentage (0–100) directly, matching the WinRT IAsyncOperationWithProgress<_, double> projection, and forwards it to the caller's percent callback after clamping — no scale conversion happens. Update the doc to match. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- sdk_v2/cpp/docs/EpDetectionPlan.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sdk_v2/cpp/docs/EpDetectionPlan.md b/sdk_v2/cpp/docs/EpDetectionPlan.md index f26964d2a..ff81f63d5 100644 --- a/sdk_v2/cpp/docs/EpDetectionPlan.md +++ b/sdk_v2/cpp/docs/EpDetectionPlan.md @@ -230,9 +230,11 @@ New methods return empty/no-op. is ORT-registration state, not WinML readyState — an EP can be `Ready` in the WinML catalog but not yet registered with ORT. - `DownloadAndRegister()` → `WinMLEpEnsureReadyAsync()` with a `WinMLAsyncBlock` - whose progress callback forwards fractional progress (0.0–1.0) to the caller's - percent callback (0–100), then `WinMLAsyncGetStatus(async, TRUE)` for a - synchronous wait. Caller-requested cancellation goes through `WinMLAsyncCancel`. + whose progress callback forwards WinML's progress value (a percentage in + 0–100, matching the WinRT `IAsyncOperationWithProgress<_, double>` projection) + straight through to the caller's percent callback after clamping, then + `WinMLAsyncGetStatus(async, TRUE)` for a synchronous wait. Caller-requested + cancellation goes through `WinMLAsyncCancel`. **Discovery (static factory):** ```cpp From c07888f2f7ec76cf72024c6dab2b85bf47cb4b2e Mon Sep 17 00:00:00 2001 From: Bhavesh Mehta Date: Mon, 15 Jun 2026 13:28:13 -0500 Subject: [PATCH 26/35] winml: drop misleading 'fallback' framing in bootstrapper comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The EpDetector unions discovery results across CUDA / WebGPU / OpenVINO / WinML bootstrappers; there is no caller-side fallback when WinML's DiscoverProviders returns empty (Win10 19H1+ without 24H2, or DLL missing) — the other bootstrappers were always going to run regardless. - winml_ep_bootstrapper.h: drop the 'On earlier builds... caller falls back to its other bootstrappers' sentences from the class doc. The preceding two lines already state that EP discovery returns providers only on Win11 24H2+, which is the only consumer-relevant fact. - winml_ep_bootstrapper.cc: rewrite the LoadLibrary-failure comment to drop 'older builds fall through to the other bootstrappers' for the same reason; keep the OS-floor fact + diagnostics rationale. - docs/EpDetectionPlan.md: rewrite the Risks/Mitigation entry the same way ('returns empty when the DLL is missing — no WinML bootstrappers join the discovery set'). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- sdk_v2/cpp/docs/EpDetectionPlan.md | 2 +- sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc | 3 +-- sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.h | 2 -- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/sdk_v2/cpp/docs/EpDetectionPlan.md b/sdk_v2/cpp/docs/EpDetectionPlan.md index ff81f63d5..935d3c53b 100644 --- a/sdk_v2/cpp/docs/EpDetectionPlan.md +++ b/sdk_v2/cpp/docs/EpDetectionPlan.md @@ -404,7 +404,7 @@ static const std::map kModelIdToRequiredEP = { | Risk | Mitigation | |------|------------| -| `Microsoft.Windows.AI.MachineLearning.dll` not present on every Windows install | Delay-load + graceful fallback in `WinMLEpBootstrapper::DiscoverProviders` | +| `Microsoft.Windows.AI.MachineLearning.dll` not present on every Windows install | `WinMLEpBootstrapper::DiscoverProviders` probes via `LoadLibraryW` and returns empty when the DLL is missing — no WinML bootstrappers join the discovery set | | CUDA download URLs / hashes change per release | Hardcode per release, mirroring C# pattern | | ORT `GetEpDevices()` not available in our ORT build | Check ORT version/API availability. Fallback to CPU-only device map | | Cross-platform CUDA EP registration | Windows: download from CDN. Linux: register from co-located `.so` | diff --git a/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc b/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc index 93332bde9..891a5b1e8 100644 --- a/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc +++ b/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc @@ -191,8 +191,7 @@ std::vector> WinMLEpBootstrapper::DiscoverP if (!winml_dll) { // Microsoft.Windows.AI.MachineLearning.dll only ships on Windows 10 19H1 - // (build 18362) and newer; older builds fall through to the other - // bootstrappers. Log GetLastError() and the OS build for diagnostics. + // (build 18362) and newer. Log GetLastError() and the OS build for diagnostics. DWORD load_err = ::GetLastError(); DWORD os_build = QueryWindowsBuild(); logger.Log(LogLevel::Information, diff --git a/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.h b/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.h index 2c14f9348..7dd2933a3 100644 --- a/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.h +++ b/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.h @@ -28,8 +28,6 @@ class ILogger; /// for the bundled WinML 2.x redist DLL (Microsoft.Windows.AI.MachineLearning). /// Actual EP discovery returns providers only on Windows 11 24H2+ (build 26100), /// where the OS-delivered EP catalog is populated via Windows Update / Store. -/// On earlier builds the DLL loads, the catalog initializes, and enumeration -/// returns zero providers — the caller falls back to its other bootstrappers. class WinMLEpBootstrapper : public IEpBootstrapper { public: ~WinMLEpBootstrapper() override = default; From 01a272563644fdeb5c4d09d1b4e5cda743e58a8a Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Mon, 15 Jun 2026 14:08:07 -0500 Subject: [PATCH 27/35] winml: load WinML DLL from foundry_local.dll's own directory Bindings that load foundry_local.dll from a non-default directory (JS prebuilds, Python wheels with FOUNDRY_LOCAL_LIB_DIR off, embedded C++ hosts that drop the runtime alongside their app binary) don't push that directory onto the process search path. A bare-name LoadLibraryW for Microsoft.Windows.AI.MachineLearning.dll then silently fails, the catalog returns zero providers, and DiscoverEps quietly returns only the always-on bootstrappers (CUDA + WebGPU) with no surfaced error. Resolve the sibling path explicitly via GetModuleHandleExW + GetModuleFileNameW + LOAD_WITH_ALTERED_SEARCH_PATH so the WinML DLL is found wherever foundry_local.dll lives. Fall back to the bare-name lookup so a system-installed copy still wins on the default search path when no sibling is present. Verified end-to-end against the dev build on Windows 11 24H2: cpp, cs, python, and js SDKs each return all 4 expected EPs (OpenVINO, NvTensorRTRTX, CUDA, WebGPU). The JS path previously returned only CUDA + WebGPU unless libraryPath was passed in FoundryLocalConfig. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/ep_detection/winml_ep_bootstrapper.cc | 52 ++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc b/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc index 891a5b1e8..8a1ca5848 100644 --- a/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc +++ b/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc @@ -50,6 +50,47 @@ DWORD QueryWindowsBuild() { return info.dwBuildNumber; } +/// Try to load ``Microsoft.Windows.AI.MachineLearning.dll`` from the directory +/// that hosts ``foundry_local.dll`` (i.e., the module containing this code). +/// Returns ``NULL`` if the module path cannot be resolved or the sibling file +/// is absent — callers should fall back to a bare-name ``LoadLibraryW`` so a +/// system-installed copy on the default search path still wins. +/// +/// Rationale: bindings that load ``foundry_local.dll`` from a non-default +/// directory (e.g., the JS prebuilds folder) don't push that directory onto +/// the process search path, so a bare ``LoadLibraryW("Microsoft.Windows.AI.MachineLearning.dll")`` +/// silently misses the sibling DLL and EP discovery returns zero providers. +/// Resolving the path explicitly removes that dependency on caller-side +/// PATH / ``os.add_dll_directory`` setup. +HMODULE LoadWinMLDllFromOwnDirectory() { + HMODULE own_module = nullptr; + if (!::GetModuleHandleExW( + GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + reinterpret_cast(&LoadWinMLDllFromOwnDirectory), + &own_module) || + own_module == nullptr) { + return nullptr; + } + + wchar_t module_path[MAX_PATH]; + DWORD len = ::GetModuleFileNameW(own_module, module_path, MAX_PATH); + if (len == 0 || len >= MAX_PATH) { + return nullptr; + } + + std::wstring path(module_path, len); + const size_t slash = path.find_last_of(L"\\/"); + if (slash == std::wstring::npos) { + return nullptr; + } + path.resize(slash + 1); + path += L"Microsoft.Windows.AI.MachineLearning.dll"; + + return ::LoadLibraryExW(path.c_str(), nullptr, + LOAD_WITH_ALTERED_SEARCH_PATH); +} + /// Context handed to ``WinMLAsyncBlock`` so the progress thunk can forward /// fractional progress to the caller-supplied percentage callback and signal /// cancellation back through ``WinMLAsyncCancel``. @@ -187,7 +228,16 @@ std::vector> WinMLEpBootstrapper::DiscoverP // Pre-check that the WinML DLL is loadable. The DLL is delay-loaded, so // calling WinML functions without it present would cause a structured // exception. Loading it explicitly is cleaner than SEH. - HMODULE winml_dll = LoadLibraryW(L"Microsoft.Windows.AI.MachineLearning.dll"); + // + // Try the sibling-of-foundry_local.dll location first so bindings that + // distribute the WinML DLL alongside foundry_local.dll (JS prebuilds, + // Python wheels, C# packed runtimes) don't need to also push that + // directory onto the process PATH. Fall back to the bare-name lookup + // so a system-installed copy on the default search path still wins. + HMODULE winml_dll = LoadWinMLDllFromOwnDirectory(); + if (!winml_dll) { + winml_dll = LoadLibraryW(L"Microsoft.Windows.AI.MachineLearning.dll"); + } if (!winml_dll) { // Microsoft.Windows.AI.MachineLearning.dll only ships on Windows 10 19H1 From 28f10b428525765f4c0a143208581fd03ba89e99 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Tue, 16 Jun 2026 15:35:19 -0500 Subject: [PATCH 28/35] =?UTF-8?q?deps:=20bump=20ORT=201.25.1=20=E2=86=92?= =?UTF-8?q?=201.26.0,=20GenAI=200.13.2=20=E2=86=92=200.14.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Aligns with neutron-server's WinML 2.0 dependency matrix. Both packages are published on NuGet. Updated in deps_versions.json (cmake source of truth), foundry-local-packaging.yml (pipeline literals), and the pipeline plan doc. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .pipelines/foundry-local-packaging.yml | 4 ++-- .pipelines/v2/sdk_v2-pipeline-plan.md | 4 ++-- sdk_v2/deps_versions.json | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.pipelines/foundry-local-packaging.yml b/.pipelines/foundry-local-packaging.yml index 844348a78..bedd8a3d1 100644 --- a/.pipelines/foundry-local-packaging.yml +++ b/.pipelines/foundry-local-packaging.yml @@ -56,9 +56,9 @@ variables: # C++ SDK (sdk_v2/cpp) native dependency versions. Must match cmake defaults # in sdk_v2/deps_versions.json. - name: cppOrtVersion - value: '1.25.1' + value: '1.26.0' - name: cppGenaiVersion - value: '0.13.2' + value: '0.14.1' - name: cppWinmlVersion value: '2.1.70' - name: cppBuildConfig diff --git a/.pipelines/v2/sdk_v2-pipeline-plan.md b/.pipelines/v2/sdk_v2-pipeline-plan.md index c88e1af34..243a3503f 100644 --- a/.pipelines/v2/sdk_v2-pipeline-plan.md +++ b/.pipelines/v2/sdk_v2-pipeline-plan.md @@ -250,8 +250,8 @@ purposes: Versions are pipeline-level variables, currently: -* `ortVersion` `1.25.1` (`Microsoft.ML.OnnxRuntime.Foundry`) -* `genaiVersion` `0.13.2` (`Microsoft.ML.OnnxRuntimeGenAI.Foundry`) +* `ortVersion` `1.26.0` (`Microsoft.ML.OnnxRuntime.Foundry`) +* `genaiVersion` `0.14.1` (`Microsoft.ML.OnnxRuntimeGenAI.Foundry`) * `winmlVersion` `2.1.70` (`Microsoft.Windows.AI.MachineLearning`, WinML 2.x reg-free) These must be kept in sync with the cmake defaults and with diff --git a/sdk_v2/deps_versions.json b/sdk_v2/deps_versions.json index a668dfc68..f5edc27f5 100644 --- a/sdk_v2/deps_versions.json +++ b/sdk_v2/deps_versions.json @@ -1,6 +1,6 @@ { "_comment": "Single source of truth for native dependency versions in sdk_v2. Read by sdk_v2/cpp/cmake/Find*.cmake and sdk_v2/python/_build_backend/__init__.py. The .pipelines/foundry-local-packaging.yml literals must match; the 'Validate pinned versions' step fails the build on drift.", - "onnxruntime": { "version": "1.25.1" }, - "onnxruntime-genai": { "version": "0.13.2" }, + "onnxruntime": { "version": "1.26.0" }, + "onnxruntime-genai": { "version": "0.14.1" }, "windows-ai-machinelearning": { "version": "2.1.70" } } From d67f260c2c472ca9fc6da3d4b81bb8b4a1eea062 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Tue, 16 Jun 2026 18:15:28 -0500 Subject: [PATCH 29/35] =?UTF-8?q?test(cs):=20drop=20net9.0=20TFM=20from=20?= =?UTF-8?q?test=20project=20=E2=80=94=20test=20against=20LTS=20only?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Respond to review feedback: testing net8.0 (LTS) is sufficient for CI. The library still multi-targets net8.0;net9.0;netstandard2.0 for packaging. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Microsoft.AI.Foundry.Local.Tests.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk_v2/cs/test/FoundryLocal.Tests/Microsoft.AI.Foundry.Local.Tests.csproj b/sdk_v2/cs/test/FoundryLocal.Tests/Microsoft.AI.Foundry.Local.Tests.csproj index 77611864c..9ab9aa853 100644 --- a/sdk_v2/cs/test/FoundryLocal.Tests/Microsoft.AI.Foundry.Local.Tests.csproj +++ b/sdk_v2/cs/test/FoundryLocal.Tests/Microsoft.AI.Foundry.Local.Tests.csproj @@ -11,8 +11,8 @@ and the vtable async/await pipeline all run on .NET Framework. CI and local scripts pass an explicit framework filter so the .NET (Core) test suite only runs once. --> - net8.0;net9.0 - net462;net8.0;net9.0 + net8.0 + net462;net8.0 enable enable From 41479a7e29eb535b9b347743ccd6318e68bdb6f7 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Tue, 16 Jun 2026 18:16:14 -0500 Subject: [PATCH 30/35] fix: use dynamic buffer for module path in LoadWinMLDllFromOwnDirectory Replace fixed MAX_PATH stack buffer with a dynamically-grown std::wstring to support long paths (>260 chars) when the user has enabled them via the Windows registry or app manifest. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/ep_detection/winml_ep_bootstrapper.cc | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc b/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc index 8a1ca5848..98b6d5823 100644 --- a/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc +++ b/sdk_v2/cpp/src/ep_detection/winml_ep_bootstrapper.cc @@ -73,21 +73,22 @@ HMODULE LoadWinMLDllFromOwnDirectory() { return nullptr; } - wchar_t module_path[MAX_PATH]; - DWORD len = ::GetModuleFileNameW(own_module, module_path, MAX_PATH); - if (len == 0 || len >= MAX_PATH) { - return nullptr; + // Resolve own directory to find the sibling WinML DLL. + // Dynamic buffer supports long paths (>MAX_PATH) when enabled. + std::wstring buf(MAX_PATH, L'\0'); + DWORD len; + while ((len = ::GetModuleFileNameW(own_module, buf.data(), + static_cast(buf.size()))) == buf.size()) { + buf.resize(buf.size() * 2); } - - std::wstring path(module_path, len); - const size_t slash = path.find_last_of(L"\\/"); - if (slash == std::wstring::npos) { + if (len == 0) { return nullptr; } - path.resize(slash + 1); - path += L"Microsoft.Windows.AI.MachineLearning.dll"; - return ::LoadLibraryExW(path.c_str(), nullptr, + auto dll_path = std::filesystem::path(buf.data(), buf.data() + len) + .parent_path() / L"Microsoft.Windows.AI.MachineLearning.dll"; + + return ::LoadLibraryExW(dll_path.c_str(), nullptr, LOAD_WITH_ALTERED_SEARCH_PATH); } From c2f2c31f4ae64e0d3e4e2fa2ab505d5440326e1c Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Wed, 17 Jun 2026 00:40:18 -0500 Subject: [PATCH 31/35] fix(macOS): add libonnxruntime.1.dylib symlink for dyld resolution ORT's install_name is @rpath/libonnxruntime.1.dylib (major-version soname), but we only created the unversioned symlink. dyld couldn't find the library at test time on macOS arm64. Add the major-version symlink alongside the unversioned one. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- sdk_v2/cpp/CMakeLists.txt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/sdk_v2/cpp/CMakeLists.txt b/sdk_v2/cpp/CMakeLists.txt index ac2f814c5..ce6af757f 100644 --- a/sdk_v2/cpp/CMakeLists.txt +++ b/sdk_v2/cpp/CMakeLists.txt @@ -418,8 +418,8 @@ if(TARGET OnnxRuntime::OnnxRuntime) # macOS: copy dylibs so consumers that only link libfoundry_local.dylib (e.g. cache_only_tests) find the correct # ORT version instead of any system-installed ORT, which would cause an Ort::InitApi() version mismatch. # - # The ORT nupkg's libonnxruntime.dylib has LC_ID_DYLIB @rpath/libonnxruntime..dylib, so we copy it to - # the versioned filename that dependents (e.g. libonnxruntime-genai.dylib) look up via @rpath. + # The ORT nupkg ships libonnxruntime.dylib with install_name @rpath/libonnxruntime.1.dylib (major-version soname), + # so we copy it under the full-version name and create both the major-version and unversioned symlinks. add_custom_command(TARGET foundry_local POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${ORT_GENAI_LIB_DIR}/libonnxruntime-genai.dylib" @@ -430,7 +430,12 @@ if(TARGET OnnxRuntime::OnnxRuntime) ) # Conventional unversioned symlink so -lonnxruntime resolves at link time. + # Major-version symlink (libonnxruntime.1.dylib) so dyld resolves the + # soname that ORT embeds as its install_name. add_custom_command(TARGET foundry_local POST_BUILD + COMMAND ${CMAKE_COMMAND} -E create_symlink + libonnxruntime.${ORT_VERSION}.dylib + $/libonnxruntime.1.dylib COMMAND ${CMAKE_COMMAND} -E create_symlink libonnxruntime.${ORT_VERSION}.dylib $/libonnxruntime.dylib From d4da6f6752a85c4616b7a59bd62522596bf38d5c Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Wed, 17 Jun 2026 01:41:03 -0500 Subject: [PATCH 32/35] fix(macOS): name ORT dylib by its soversion so dyld resolves it ORT 1.26.0 changed libonnxruntime.dylib's install_name from @rpath/libonnxruntime..dylib to @rpath/libonnxruntime.1.dylib (the soversion), via the onnxruntime SOVERSION change (microsoft/onnxruntime #28101) and the Foundry nupkg packaging update (#28191). Our macOS POST_BUILD step still copied the library to libonnxruntime..dylib -- a name nothing references -- so foundry_local_tests failed to load ORT on macOS arm64 (dyld could not find @rpath/libonnxruntime.1.dylib). Name the copied library by the soversion (major version) to match ORT's install_name, keeping the unversioned symlink that GenAI dlopen()s at runtime and that -lonnxruntime resolves at link time. Mirrors the existing Linux libonnxruntime.so.1 handling. Verified by inspecting the published nupkgs: 1.25.1 install_name was @rpath/libonnxruntime.1.25.1.dylib; 1.26.0 is @rpath/libonnxruntime.1.dylib. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- sdk_v2/cpp/CMakeLists.txt | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/sdk_v2/cpp/CMakeLists.txt b/sdk_v2/cpp/CMakeLists.txt index ce6af757f..f0c5b7efd 100644 --- a/sdk_v2/cpp/CMakeLists.txt +++ b/sdk_v2/cpp/CMakeLists.txt @@ -418,26 +418,23 @@ if(TARGET OnnxRuntime::OnnxRuntime) # macOS: copy dylibs so consumers that only link libfoundry_local.dylib (e.g. cache_only_tests) find the correct # ORT version instead of any system-installed ORT, which would cause an Ort::InitApi() version mismatch. # - # The ORT nupkg ships libonnxruntime.dylib with install_name @rpath/libonnxruntime.1.dylib (major-version soname), - # so we copy it under the full-version name and create both the major-version and unversioned symlinks. + # The ORT nupkg's libonnxruntime.dylib has install_name @rpath/libonnxruntime..dylib (the major version + # is the soname; e.g. libonnxruntime.1.dylib), so foundry_local records exactly that name as its ORT dependency. + # Copy the real library under the soname so dyld resolves it at load time. + string(REGEX MATCH "^[0-9]+" ORT_VERSION_MAJOR "${ORT_VERSION}") add_custom_command(TARGET foundry_local POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${ORT_GENAI_LIB_DIR}/libonnxruntime-genai.dylib" $ COMMAND ${CMAKE_COMMAND} -E copy_if_different "${ORT_LIB_DIR}/libonnxruntime.dylib" - "$/libonnxruntime.${ORT_VERSION}.dylib" + "$/libonnxruntime.${ORT_VERSION_MAJOR}.dylib" ) - # Conventional unversioned symlink so -lonnxruntime resolves at link time. - # Major-version symlink (libonnxruntime.1.dylib) so dyld resolves the - # soname that ORT embeds as its install_name. + # Unversioned symlink: GenAI dlopen()s "libonnxruntime.dylib" at runtime, and -lonnxruntime resolves it at link time. add_custom_command(TARGET foundry_local POST_BUILD COMMAND ${CMAKE_COMMAND} -E create_symlink - libonnxruntime.${ORT_VERSION}.dylib - $/libonnxruntime.1.dylib - COMMAND ${CMAKE_COMMAND} -E create_symlink - libonnxruntime.${ORT_VERSION}.dylib + libonnxruntime.${ORT_VERSION_MAJOR}.dylib $/libonnxruntime.dylib ) elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR ANDROID) From a4b84b9bebf3de341a060f1913e20e49bea4d02c Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Wed, 17 Jun 2026 02:31:24 -0500 Subject: [PATCH 33/35] docs(python): correct stale ORT macOS install_name comment ORT's macOS dylib install_name is the soversion (@rpath/libonnxruntime.1.dylib) since the upstream SOVERSION change, not the full-version form the comment claimed. Reword to describe the mechanism (a versioned install_name leaf, not the bare libonnxruntime.dylib GenAI dlopens) without pinning to a specific version. No behavior change; the GenAI->ORT symlink is unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/foundry_local_sdk/_native/lib_loader.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/sdk_v2/python/src/foundry_local_sdk/_native/lib_loader.py b/sdk_v2/python/src/foundry_local_sdk/_native/lib_loader.py index 59034f203..1369759c8 100644 --- a/sdk_v2/python/src/foundry_local_sdk/_native/lib_loader.py +++ b/sdk_v2/python/src/foundry_local_sdk/_native/lib_loader.py @@ -280,13 +280,12 @@ def prepare_native_dependencies(foundry_local_dir: pathlib.Path) -> list: # macOS only: GenAI's static initializer does its own dlopen("libonnxruntime.dylib"), # which on Darwin only matches by leafname against dyld search paths or images - # whose install_name leaf is exactly "libonnxruntime.dylib". The wheel ships - # the ORT dylib as "libonnxruntime..dylib" (install_name - # "@rpath/libonnxruntime..dylib"), so neither match path succeeds and - # GenAI aborts in dyld init before any of our code runs. The second name GenAI - # tries is "/libonnxruntime.dylib", so a symlink there fixes it. - # Linux dlopen consults the loaded-soname table by leafname and finds our - # already-RTLD_GLOBAL'd image without help. + # whose install_name leaf is exactly "libonnxruntime.dylib". ORT's dylib has a + # versioned install_name instead (the soversion, e.g. "@rpath/libonnxruntime.1.dylib"), + # so neither match path succeeds and GenAI aborts in dyld init before any of our code + # runs. The second name GenAI tries is "/libonnxruntime.dylib", so a symlink + # there fixes it. Linux dlopen consults the loaded-soname table by leafname and finds + # our already-RTLD_GLOBAL'd image without help. if sys.platform == "darwin": symlink_path = genai_path.parent / "libonnxruntime.dylib" try: From e16dba09192493207a95561802e25a56aa1e7271 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Wed, 17 Jun 2026 09:21:30 -0500 Subject: [PATCH 34/35] fix(macOS): align JS ORT staging with the soversion name Match the C++ build's macOS layout (libonnxruntime.1.dylib real file + unversioned symlink) in the JS SDK so the ORT dylib is named by its actual install_name soversion rather than the full version: - install-native.cjs: stage the downloaded ORT dylib as libonnxruntime..dylib (the soversion / install_name) with an unversioned libonnxruntime.dylib symlink for GenAI's runtime dlopen and link-time -lonnxruntime; correct the comment. - copy-native.mjs: also copy libonnxruntime.1.dylib into dev prebuilds so the preload below resolves it locally. - native.ts: preload ORT from the soversion path (libonnxruntime.1.dylib) on macOS instead of the unversioned name. The JS loader preloads ORT by absolute path, so this is correctness/consistency alignment, not a runtime break. No behavior change on Windows/Linux. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- sdk_v2/js/script/copy-native.mjs | 7 ++++++- sdk_v2/js/script/install-native.cjs | 21 +++++++++++++-------- sdk_v2/js/src/detail/native.ts | 10 +++++++--- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/sdk_v2/js/script/copy-native.mjs b/sdk_v2/js/script/copy-native.mjs index 694b41716..42a3534a6 100644 --- a/sdk_v2/js/script/copy-native.mjs +++ b/sdk_v2/js/script/copy-native.mjs @@ -58,7 +58,12 @@ const wanted = (() => { ]; } if (process.platform === "darwin") { - return ["libfoundry_local.dylib", "libonnxruntime.dylib", "libonnxruntime-genai.dylib"]; + return [ + "libfoundry_local.dylib", + "libonnxruntime.dylib", + "libonnxruntime.1.dylib", + "libonnxruntime-genai.dylib", + ]; } return ["libfoundry_local.so", "libonnxruntime.so", "libonnxruntime.so.1", "libonnxruntime-genai.so"]; })(); diff --git a/sdk_v2/js/script/install-native.cjs b/sdk_v2/js/script/install-native.cjs index 9c6205dea..fb18a16ae 100644 --- a/sdk_v2/js/script/install-native.cjs +++ b/sdk_v2/js/script/install-native.cjs @@ -62,12 +62,16 @@ const ortPackageName = isLinuxX64 ? 'Microsoft.ML.OnnxRuntime.Gpu.Linux' : 'Micr const ortVersion = deps.onnxruntime.version; const genaiVersion = deps['onnxruntime-genai'].version; +// ORT's dylib/so soname uses the major version only (e.g. libonnxruntime.1.dylib, +// libonnxruntime.so.1), which is the name libfoundry_local records as its dependency. +const ortMajor = ortVersion.split('.')[0]; + // Expected post-install filenames per platform. On Linux/macOS we rename or // symlink the unversioned ORT lib to a versioned name to match what // libfoundry_local.{so,dylib} actually requests at load time. function expectedOrt() { if (os.platform() === 'linux') return 'libonnxruntime.so.1'; - if (os.platform() === 'darwin') return `libonnxruntime.${ortVersion}.dylib`; + if (os.platform() === 'darwin') return `libonnxruntime.${ortMajor}.dylib`; return 'onnxruntime.dll'; } function expectedGenai() { @@ -240,17 +244,18 @@ function applyOrtPlatformAliases(binDir, ortVersion) { } } } else if (os.platform() === 'darwin') { + const major = ortVersion.split('.')[0]; const unv = path.join(binDir, 'libonnxruntime.dylib'); - const versioned = path.join(binDir, `libonnxruntime.${ortVersion}.dylib`); + const versioned = path.join(binDir, `libonnxruntime.${major}.dylib`); if (fs.existsSync(unv) && !fs.existsSync(versioned)) { - // libfoundry_local.dylib references @rpath/libonnxruntime..dylib - // (the LC_ID_DYLIB baked into the nupkg's dylib). Provide that file. + // libfoundry_local.dylib references @rpath/libonnxruntime..dylib + // (the install_name baked into the nupkg's dylib). Provide that file. fs.renameSync(unv, versioned); - console.log(` Renamed libonnxruntime.dylib -> libonnxruntime.${ortVersion}.dylib`); - // Keep an unversioned symlink so `-lonnxruntime` link-time lookups still work. + console.log(` Renamed libonnxruntime.dylib -> libonnxruntime.${major}.dylib`); + // GenAI dlopen()s "libonnxruntime.dylib" at runtime and -lonnxruntime resolves it at link time. try { - fs.symlinkSync(`libonnxruntime.${ortVersion}.dylib`, unv); - console.log(` Created symlink libonnxruntime.dylib -> libonnxruntime.${ortVersion}.dylib`); + fs.symlinkSync(`libonnxruntime.${major}.dylib`, unv); + console.log(` Created symlink libonnxruntime.dylib -> libonnxruntime.${major}.dylib`); } catch (err) { console.warn(` Could not create libonnxruntime.dylib symlink: ${err.message}`); } diff --git a/sdk_v2/js/src/detail/native.ts b/sdk_v2/js/src/detail/native.ts index 683afe86f..7e607d7d9 100644 --- a/sdk_v2/js/src/detail/native.ts +++ b/sdk_v2/js/src/detail/native.ts @@ -284,12 +284,16 @@ function nativeLibBasename(): string { /** * Candidate basenames for an ORT-family library on the current platform. The loader tries them in order and - * uses the first that exists on disk. Linux ORT ships as `libonnxruntime.so.1` in some packaging variants - * (CMake symlink missing); the `.so.1` fallback covers that case. GenAI does not have a `.so.1` variant. + * uses the first that exists on disk. macOS stages the real ORT library under its soversion + * (`libonnxruntime.1.dylib`, ORT's install_name). Linux ships `libonnxruntime.so` with a `.so.1` soname + * fallback for packaging variants that omit the unversioned symlink. GenAI has no versioned variant. */ function ortCandidateBasenames(name: "onnxruntime" | "onnxruntime-genai"): string[] { if (process.platform === "win32") return [`${name}.dll`]; - if (process.platform === "darwin") return [`lib${name}.dylib`]; + if (process.platform === "darwin") { + if (name === "onnxruntime") return ["libonnxruntime.1.dylib"]; + return [`lib${name}.dylib`]; + } if (name === "onnxruntime") return ["libonnxruntime.so", "libonnxruntime.so.1"]; return [`lib${name}.so`]; } From cd80a8818b1724d95fcbd5b90f7d5b18f99a92b9 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Wed, 17 Jun 2026 12:17:28 -0500 Subject: [PATCH 35/35] docs(js): explain dev copy stages both ORT dylib names foundry_local loads ORT by its soversion install_name (libonnxruntime.1.dylib) while GenAI dlopen()s the unversioned libonnxruntime.dylib, so both must be present. Note why this dev-only script copies both as plain files instead of symlinking like the shipped paths. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- sdk_v2/js/script/copy-native.mjs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sdk_v2/js/script/copy-native.mjs b/sdk_v2/js/script/copy-native.mjs index 42a3534a6..4cdb44e86 100644 --- a/sdk_v2/js/script/copy-native.mjs +++ b/sdk_v2/js/script/copy-native.mjs @@ -58,6 +58,12 @@ const wanted = (() => { ]; } if (process.platform === "darwin") { + // The ORT dylib is referenced under two fixed names: foundry_local loads it by + // its soversion install_name (libonnxruntime.1.dylib), while GenAI's static + // initializer dlopen()s the unversioned libonnxruntime.dylib. Both must sit + // beside the addon. Shipped packages symlink one name to the other to avoid + // duplicating the ~24MB binary; this dev-only staging just copies both, since + // the duplicate never leaves the machine. return [ "libfoundry_local.dylib", "libonnxruntime.dylib",