From ef7edb89807e106e4c048206b5b2e4d95880022e Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Wed, 17 Jun 2026 17:46:51 -0500 Subject: [PATCH 01/13] refactor(macOS): simplify ORT dylib staging to mirror Linux MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The macOS ORT staging renamed the shipped libonnxruntime.dylib to a versioned name and symlinked the bare name back — the reverse of the Linux layout sitting right beside it, and an extra rename for no benefit. Keep the library under its shipped name (libonnxruntime.dylib) and add only the soname symlink (libonnxruntime.1.dylib -> libonnxruntime.dylib), exactly mirroring the Linux libonnxruntime.so / libonnxruntime.so.1 handling. Both names still resolve: foundry_local loads ORT by its soname install_name, GenAI dlopens the unversioned name. Net one symlink instead of a rename plus symlink. Applied in the C++ build (CMakeLists.txt) and the JS published install (install-native.cjs); the JS runtime preload (native.ts) now lists the shipped name first with the soname as fallback, matching the Linux ordering. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- sdk_v2/cpp/CMakeLists.txt | 17 +++++++---------- sdk_v2/js/script/install-native.cjs | 23 ++++++++++------------- sdk_v2/js/src/detail/native.ts | 8 ++++---- 3 files changed, 21 insertions(+), 27 deletions(-) diff --git a/sdk_v2/cpp/CMakeLists.txt b/sdk_v2/cpp/CMakeLists.txt index 671c7648..9ab3a91f 100644 --- a/sdk_v2/cpp/CMakeLists.txt +++ b/sdk_v2/cpp/CMakeLists.txt @@ -395,9 +395,10 @@ 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 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. + # The ORT nupkg ships the library as libonnxruntime.dylib with install_name @rpath/libonnxruntime..dylib + # (the major version is the soname; e.g. libonnxruntime.1.dylib). foundry_local records that soname as its ORT + # dependency, while GenAI dlopen()s the unversioned libonnxruntime.dylib at runtime — so we keep the shipped file + # under its own name and add a soname symlink beside it. Mirrors the Linux libonnxruntime.so / .so.1 layout below. 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 @@ -405,14 +406,10 @@ if(TARGET OnnxRuntime::OnnxRuntime) $ COMMAND ${CMAKE_COMMAND} -E copy_if_different "${ORT_LIB_DIR}/libonnxruntime.dylib" - "$/libonnxruntime.${ORT_VERSION_MAJOR}.dylib" - ) - - # 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_MAJOR}.dylib - $/libonnxruntime.dylib + libonnxruntime.dylib + $/libonnxruntime.${ORT_VERSION_MAJOR}.dylib ) elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR ANDROID) # Linux and Android both use .so files diff --git a/sdk_v2/js/script/install-native.cjs b/sdk_v2/js/script/install-native.cjs index fb18a16a..3c615dd6 100644 --- a/sdk_v2/js/script/install-native.cjs +++ b/sdk_v2/js/script/install-native.cjs @@ -227,9 +227,10 @@ async function installPackage(artifact, tempDir, binDir) { // Mirror the platform-specific post-build steps that sdk_v2/cpp/CMakeLists.txt // runs after building libfoundry_local. The Foundry ORT nupkg ships only the -// unversioned `libonnxruntime.{so,dylib}`, but our libfoundry_local records -// versioned SONAME/install_name dependencies, so we have to create the -// matching alias next to it. +// unversioned `libonnxruntime.{so,dylib}`, but libfoundry_local records a +// versioned SONAME/install_name dependency (libonnxruntime.so.1 / +// libonnxruntime.1.dylib), so we add that soname symlink next to the shipped +// file. GenAI continues to dlopen the unversioned name, which stays as-is. function applyOrtPlatformAliases(binDir, ortVersion) { if (os.platform() === 'linux') { const unv = path.join(binDir, 'libonnxruntime.so'); @@ -246,18 +247,14 @@ 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.${major}.dylib`); - if (fs.existsSync(unv) && !fs.existsSync(versioned)) { - // 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.${major}.dylib`); - // GenAI dlopen()s "libonnxruntime.dylib" at runtime and -lonnxruntime resolves it at link time. + const soname = path.join(binDir, `libonnxruntime.${major}.dylib`); + if (fs.existsSync(unv) && !fs.existsSync(soname)) { try { - fs.symlinkSync(`libonnxruntime.${major}.dylib`, unv); - console.log(` Created symlink libonnxruntime.dylib -> libonnxruntime.${major}.dylib`); + fs.symlinkSync('libonnxruntime.dylib', soname); + console.log(` Created symlink libonnxruntime.${major}.dylib -> libonnxruntime.dylib`); } catch (err) { - console.warn(` Could not create libonnxruntime.dylib symlink: ${err.message}`); + fs.copyFileSync(unv, soname); + console.log(` Copied libonnxruntime.dylib -> libonnxruntime.${major}.dylib (symlink failed: ${err.message})`); } } } diff --git a/sdk_v2/js/src/detail/native.ts b/sdk_v2/js/src/detail/native.ts index 7e607d7d..0e3e128f 100644 --- a/sdk_v2/js/src/detail/native.ts +++ b/sdk_v2/js/src/detail/native.ts @@ -284,14 +284,14 @@ 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. 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. + * uses the first that exists on disk. The Foundry nupkg ships the unversioned `libonnxruntime.{so,dylib}`; + * we add the versioned soname (`libonnxruntime.so.1` / `libonnxruntime.1.dylib`) beside it, which is the name + * libfoundry_local actually records, so both are listed as fallbacks. GenAI has no versioned variant. */ function ortCandidateBasenames(name: "onnxruntime" | "onnxruntime-genai"): string[] { if (process.platform === "win32") return [`${name}.dll`]; if (process.platform === "darwin") { - if (name === "onnxruntime") return ["libonnxruntime.1.dylib"]; + if (name === "onnxruntime") return ["libonnxruntime.dylib", "libonnxruntime.1.dylib"]; return [`lib${name}.dylib`]; } if (name === "onnxruntime") return ["libonnxruntime.so", "libonnxruntime.so.1"]; From 0a9a7411afebb9c627e11156b2947a0512e6ba31 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Wed, 17 Jun 2026 17:52:44 -0500 Subject: [PATCH 02/13] refactor(native): always enable WinML EP catalog on Windows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the FOUNDRY_LOCAL_USE_WINML build flag and the WinML/non-WinML native split. WinML 2.x is reg-free, delay-loaded, and degrades gracefully when the DLL is absent, so there is no reason to gate it: find_package(WinMLEpCatalog) now runs unconditionally on Windows (if(WIN32)) and is skipped elsewhere. All downstream behavior already keyed off WinMLEpCatalog_FOUND, so the EP-catalog link, the FOUNDRY_LOCAL_HAS_EP_CATALOG define, and the WinML DLL post-build copy are unchanged. - CMakeLists.txt: drop the option; gate the find_package on WIN32. - build.py: drop --use_winml; keep --winml_sdk_version as an optional version override (CMake decides inclusion by platform). - FindOnnxRuntime.cmake: remove the dead FOUNDRY_LOCAL_USE_WINML message block. - nuget/pack.py: the runtime package always forwards the WinML DLL when present (no logic change — it already did); comments/help updated, single package id. - build_and_test_all.ps1: drop the -UseWinml variant switch. The native runtime NuGet (Microsoft.AI.Foundry.Local.Runtime) now ships the WinML DLL on Windows automatically; the separate .Runtime.WinML package is gone. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- sdk_v2/build_and_test_all.ps1 | 40 +++++------------------ sdk_v2/cpp/CMakeLists.txt | 8 ++--- sdk_v2/cpp/build.py | 22 ++++--------- sdk_v2/cpp/cmake/FindOnnxRuntime.cmake | 22 +------------ sdk_v2/cpp/cmake/FindWinMLEpCatalog.cmake | 4 +-- sdk_v2/cpp/nuget/pack.py | 24 +++++++------- 6 files changed, 33 insertions(+), 87 deletions(-) diff --git a/sdk_v2/build_and_test_all.ps1 b/sdk_v2/build_and_test_all.ps1 index e01561bd..e08e51ce 100644 --- a/sdk_v2/build_and_test_all.ps1 +++ b/sdk_v2/build_and_test_all.ps1 @@ -15,14 +15,6 @@ unless -ContinueOnError is supplied, and prints a per-SDK pass/fail summary at the end. -.PARAMETER UseWinml - Build the WinML variant across all SDKs: - * C++: passes --use_winml to build.py - * C#: passes -p:UseWinML=true to dotnet test - * Python: sets FL_PYTHON_PACKAGE_NAME=foundry-local-sdk-winml before pip install - * JS: rebuilds the native addon against the WinML C++ build - Windows only. - .PARAMETER Skip SDKs to skip. Any of: cpp, cs, python, js. @@ -39,11 +31,7 @@ .EXAMPLE pwsh ./build_and_test_all.ps1 - # Full build + test, no WinML. - -.EXAMPLE - pwsh ./build_and_test_all.ps1 -UseWinml - # Full build + test against the WinML variant. + # Full build + test of all SDKs. .EXAMPLE pwsh ./build_and_test_all.ps1 -Only cpp,js -SkipCppTests @@ -51,7 +39,6 @@ #> [CmdletBinding()] param( - [switch] $UseWinml, [ValidateSet('cpp', 'cs', 'python', 'js')] [string[]] $Skip = @(), [ValidateSet('cpp', 'cs', 'python', 'js')] @@ -71,10 +58,6 @@ $csDir = Join-Path $sdkRoot 'cs' $pythonDir = Join-Path $sdkRoot 'python' $jsDir = Join-Path $sdkRoot 'js' -if ($UseWinml -and -not $IsWindows) { - throw "-UseWinml is Windows-only." -} - # Resolve which SDKs to run. $all = @('cpp', 'cs', 'python', 'js') if ($Only) { @@ -100,7 +83,7 @@ function Invoke-Step { ) Write-Host "" Write-Host "============================================================" -ForegroundColor Cyan - Write-Host "==> [$Name] start (UseWinml=$UseWinml)" -ForegroundColor Cyan + Write-Host "==> [$Name] start" -ForegroundColor Cyan Write-Host "============================================================" -ForegroundColor Cyan $start = Get-Date $ok = $false @@ -133,7 +116,6 @@ try { if ('cpp' -in $targets) { Invoke-Step 'cpp' { $args = @('build.py', '--config', $Config) - if ($UseWinml) { $args += '--use_winml' } if ($SkipCppTests) { $args += '--skip_tests' } Push-Location $cppDir try { @@ -152,16 +134,15 @@ try { try { $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. + # The test csproj multi-targets net8.0/net9.0 (and net462 on Windows) + # for build coverage; run the .NET (Core) test suite once on net9.0 + # (back-compat covers net8.0 consumers) plus net462 on Windows to + # exercise the netstandard polyfills at runtime. $frameworks = @('net9.0') - if ($IsWindows -and -not $UseWinml) { $frameworks += 'net462' } + if ($IsWindows) { $frameworks += 'net462' } foreach ($tfm in $frameworks) { Write-Host "=== dotnet test --framework $tfm ===" @@ -172,7 +153,6 @@ try { '--framework', $tfm, '--nologo' ) - if ($UseWinml) { $testArgs += '-p:UseWinML=true' } dotnet @testArgs if ($LASTEXITCODE -ne 0) { throw "dotnet test ($tfm) exit $LASTEXITCODE" } } @@ -214,8 +194,7 @@ print(sys.executable) $env:Platform = 'x64' } - $env:FL_PYTHON_PACKAGE_NAME = - if ($UseWinml) { 'foundry-local-sdk-winml' } else { 'foundry-local-sdk' } + $env:FL_PYTHON_PACKAGE_NAME = 'foundry-local-sdk' try { python -m pip install -e '.[dev]' if ($LASTEXITCODE -ne 0) { throw "pip install exit $LASTEXITCODE" } @@ -241,8 +220,7 @@ print(sys.executable) npm install if ($LASTEXITCODE -ne 0) { throw "npm install exit $LASTEXITCODE" } - # JS picks up the native library copied from the C++ build dir; - # the WinML/non-WinML distinction is whichever C++ build ran above. + # JS picks up the native library copied from the C++ build dir. npm run build if ($LASTEXITCODE -ne 0) { throw "npm run build exit $LASTEXITCODE" } diff --git a/sdk_v2/cpp/CMakeLists.txt b/sdk_v2/cpp/CMakeLists.txt index 9ab3a91f..7159b738 100644 --- a/sdk_v2/cpp/CMakeLists.txt +++ b/sdk_v2/cpp/CMakeLists.txt @@ -43,7 +43,6 @@ endif() option(FOUNDRY_LOCAL_BUILD_TESTS "Build unit tests" ON) option(FOUNDRY_LOCAL_BUILD_EXAMPLES "Build example programs" ON) option(FOUNDRY_LOCAL_BUILD_SERVICE "Build web service support (requires oat++)" ON) -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 @@ -81,9 +80,10 @@ 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. Only fetched -# when the WinML SKU is requested so non-WinML builds don't pull the catalog DLL. -if(FOUNDRY_LOCAL_USE_WINML) +# WinML EP Catalog — Windows-only, for hardware EP discovery and download. The +# WinML 2.x runtime is reg-free, delay-loaded, and degrades gracefully when the +# DLL is absent, so it is always included on Windows; other platforms skip it. +if(WIN32) find_package(WinMLEpCatalog) endif() diff --git a/sdk_v2/cpp/build.py b/sdk_v2/cpp/build.py index 82acbff4..b1274cd6 100644 --- a/sdk_v2/cpp/build.py +++ b/sdk_v2/cpp/build.py @@ -160,17 +160,10 @@ class HelpFormatter(argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescript parser.add_argument( "--skip_service", action="store_true", help="Skip building web service support (oat++)." ) - parser.add_argument( - "--use_winml", action="store_true", - 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.Windows.AI.MachineLearning NuGet package (used for the WinML EP " - "catalog when --use_winml is set; defaults to the version pinned in " - "FindWinMLEpCatalog.cmake).", + help="Override the Microsoft.Windows.AI.MachineLearning NuGet version for the WinML EP " + "catalog (Windows only). Defaults to the version pinned in deps_versions.json.", ) # Cross-compilation (mutually exclusive targets) @@ -462,13 +455,10 @@ def configure(args: argparse.Namespace) -> None: if build_tests == "ON": command += ["-DVCPKG_MANIFEST_FEATURES=tests"] - if args.use_winml: - command += ["-DFOUNDRY_LOCAL_USE_WINML=ON"] - if 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"] + # WinML EP catalog is enabled automatically on Windows by CMake. Allow an + # optional version override for the Microsoft.Windows.AI.MachineLearning NuGet. + if args.winml_sdk_version: + command += [f"-DWINML_EP_CATALOG_VERSION={args.winml_sdk_version}"] if args.ort_home: command += [f"-DORT_HOME={args.ort_home}"] diff --git a/sdk_v2/cpp/cmake/FindOnnxRuntime.cmake b/sdk_v2/cpp/cmake/FindOnnxRuntime.cmake index 197894e0..43a6f6ec 100644 --- a/sdk_v2/cpp/cmake/FindOnnxRuntime.cmake +++ b/sdk_v2/cpp/cmake/FindOnnxRuntime.cmake @@ -4,8 +4,7 @@ # 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. +# is shared by all platforms. # # Creates an IMPORTED target: OnnxRuntime::OnnxRuntime @@ -41,25 +40,6 @@ else() message(FATAL_ERROR "Unsupported platform for OnnxRuntime: ${CMAKE_GENERATOR_PLATFORM} on ${CMAKE_SYSTEM_NAME}") endif() -if(NOT CMAKE_SYSTEM_NAME STREQUAL "Windows") - # WinML is only available on Windows - set(FOUNDRY_LOCAL_USE_WINML OFF) -endif() - -if(FOUNDRY_LOCAL_USE_WINML) - # FOUNDRY_LOCAL_USE_WINML opts in to the WinML EP catalog (see FindWinMLEpCatalog.cmake) but - # does NOT change where ORT comes from. We always link against our own ORT - # (Microsoft.ML.OnnxRuntime.Foundry) because it enables CUDA and WebGPU EPs. - # - # Which onnxruntime.dll the process actually binds to at runtime is determined by the - # binding-side preload contract (see sdk_v2/cpp/docs/OrtRuntimeLoading.md), not by build - # layout. Co-location of our onnxruntime.dll next to foundry_local.dll keeps in-tree - # tests and examples zero-config, but is not a correctness guarantee for arbitrary - # deployments — bindings preload the intended onnxruntime.dll by absolute path before - # loading foundry_local. - message(STATUS "FOUNDRY_LOCAL_USE_WINML=ON: WinML EP catalog enabled; ORT still sourced from Microsoft.ML.OnnxRuntime.Foundry") -endif() - if(ORT_HOME) # Use a pre-extracted ORT directory (e.g. from build.py --ort_home). # Android: expects headers/ and jni// from an extracted AAR. diff --git a/sdk_v2/cpp/cmake/FindWinMLEpCatalog.cmake b/sdk_v2/cpp/cmake/FindWinMLEpCatalog.cmake index 78793fee..dc732a4f 100644 --- a/sdk_v2/cpp/cmake/FindWinMLEpCatalog.cmake +++ b/sdk_v2/cpp/cmake/FindWinMLEpCatalog.cmake @@ -41,8 +41,8 @@ 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. +# disable instead of halting configuration when someone builds e.g. ARM64EC on +# Windows. if(CMAKE_GENERATOR_PLATFORM) string(TOUPPER "${CMAKE_GENERATOR_PLATFORM}" _WINML_PLATFORM_UPPER) elseif(CMAKE_VS_PLATFORM_NAME) diff --git a/sdk_v2/cpp/nuget/pack.py b/sdk_v2/cpp/nuget/pack.py index 205d6d7f..902ba288 100644 --- a/sdk_v2/cpp/nuget/pack.py +++ b/sdk_v2/cpp/nuget/pack.py @@ -43,12 +43,11 @@ # 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 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. +# On Windows the build also drops Microsoft.Windows.AI.MachineLearning.dll — the +# reg-free WinML 2.x runtime — next to foundry_local.dll (the cmake post-build +# copy in sdk_v2/cpp/CMakeLists.txt). We forward any entry from OPTIONAL_SIBLINGS +# that is present in the upstream artifact dir; absent files are silently skipped, +# so the same packing path serves every platform. RIDS: dict[str, tuple[str, str]] = { "win_x64": ("win-x64", "foundry_local.dll"), "win_arm64": ("win-arm64", "foundry_local.dll"), @@ -57,9 +56,8 @@ } # Sibling files copied into runtimes//native/ when present in the upstream -# 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. +# artifact. Windows builds drop Microsoft.Windows.AI.MachineLearning.dll alongside +# foundry_local.dll; other platforms don't, so presence alone drives inclusion. OPTIONAL_SIBLINGS: tuple[str, ...] = ( "Microsoft.Windows.AI.MachineLearning.dll", ) @@ -78,9 +76,9 @@ def _parse_args() -> argparse.Namespace: parser.add_argument("--genai_version", required=True, 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, ships the WinML 2.x " - "reg-free runtime alongside foundry_local).") + help="NuGet package id for the native runtime. On Windows the package " + "includes the reg-free WinML 2.x runtime " + "(Microsoft.Windows.AI.MachineLearning.dll) alongside foundry_local.") for arg_name, (rid, lib) in RIDS.items(): parser.add_argument(f"--{arg_name}", type=Path, default=None, @@ -154,7 +152,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.Windows.AI.MachineLearning.dll. + # Microsoft.Windows.AI.MachineLearning.dll (the WinML 2.x runtime). # 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) From 4078cdc2af439902df346dc166f09f42e52e7a34 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Wed, 17 Jun 2026 17:55:31 -0500 Subject: [PATCH 03/13] =?UTF-8?q?refactor(cs):=20single=20package=20?= =?UTF-8?q?=E2=80=94=20drop=20the=20.WinML=20SKU=20and=20UseWinML?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The C# layer is a pure P/Invoke wrapper with the same net8.0;net9.0 TFM for both flavors, so the WinML/non-WinML split only differed by package id and which native Runtime package it referenced. Collapse to one package: - Remove the UseWinML property, the win-only RuntimeIdentifiers override, and the WinML override PropertyGroup (PackageId/AssemblyName Microsoft.AI.Foundry.Local.WinML). - Reference the single Microsoft.AI.Foundry.Local.Runtime (which now ships the WinML DLL on Windows); drop the .Runtime.WinML conditional reference. - Tests: drop UseWinML; net462 coverage now keys only on Windows. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../cs/src/Microsoft.AI.Foundry.Local.csproj | 31 +------------------ .../Microsoft.AI.Foundry.Local.Tests.csproj | 3 +- 2 files changed, 2 insertions(+), 32 deletions(-) diff --git a/sdk_v2/cs/src/Microsoft.AI.Foundry.Local.csproj b/sdk_v2/cs/src/Microsoft.AI.Foundry.Local.csproj index cd4c1c6d..d61b6b7e 100644 --- a/sdk_v2/cs/src/Microsoft.AI.Foundry.Local.csproj +++ b/sdk_v2/cs/src/Microsoft.AI.Foundry.Local.csproj @@ -29,13 +29,6 @@ true snupkg true - - - false - win-x64;win-arm64 @@ -81,24 +73,6 @@ - - - - Microsoft AI Foundry Local for WinML - Microsoft Foundry Local SDK for WinML - Microsoft.AI.Foundry.Local.WinML - Microsoft.AI.Foundry.Local.WinML - - net8.0;net9.0 - win-x64;win-arm64 - @@ -132,12 +106,9 @@ Option 2 (local build): set FoundryLocalNativeBinDir to point at a C++ build output, e.g.: dotnet build /p:FoundryLocalNativeBinDir=..\..\cpp\build\Windows\RelWithDebInfo\bin\RelWithDebInfo Option 3 (default): auto-detect the C++ build output from the repo-relative 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 9ab9aa85..be39e7c2 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,7 +1,6 @@  - false true net8.0 - net462;net8.0 + net462;net8.0 enable enable From 0046197f1a4f5bf3e7571ce89e2f05a4251db9c2 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Wed, 17 Jun 2026 17:59:00 -0500 Subject: [PATCH 04/13] =?UTF-8?q?refactor(python):=20single=20wheel=20?= =?UTF-8?q?=E2=80=94=20drop=20the=20WinML=20variant?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The base and WinML wheels shared identical ORT/GenAI pins and source; only the distribution name differed (set via FL_PYTHON_PACKAGE_NAME). With one flavor: - _build_backend: drop the FL_PYTHON_PACKAGE_NAME name-rewrite; keep only the ORT/GenAI pin rewriting from deps_versions.json (rename the helper accordingly, drop the now-unused os import). - installer.py: drop the --winml flag; always (re)install foundry-local-sdk. - pyproject.toml: drop the WinML-variant comment. The WinML DLL is bundled on Windows by the native artifact the wheel stages, so the single wheel carries it automatically (no packaging change needed). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- sdk_v2/python/_build_backend/__init__.py | 74 ++++++------------- sdk_v2/python/pyproject.toml | 3 +- .../foundry_local_sdk/_native/installer.py | 20 ++--- 3 files changed, 29 insertions(+), 68 deletions(-) diff --git a/sdk_v2/python/_build_backend/__init__.py b/sdk_v2/python/_build_backend/__init__.py index a7a90014..a54913cf 100644 --- a/sdk_v2/python/_build_backend/__init__.py +++ b/sdk_v2/python/_build_backend/__init__.py @@ -1,27 +1,20 @@ # PEP 517 backend shim for foundry-local-sdk. # -# Delegates every hook to ``setuptools.build_meta``. The only added -# behavior is a temporary patch of ``pyproject.toml`` when the -# ``FL_PYTHON_PACKAGE_NAME`` environment variable is set, so the same -# source tree can produce two differently-named wheels: -# -# * unset -> foundry-local-sdk (default) -# * foundry-local-sdk-winml -> foundry-local-sdk-winml (WinML SKU) +# Delegates every hook to ``setuptools.build_meta``. The only added behavior is a +# temporary patch of ``pyproject.toml`` during the build that rewrites the +# ORT/GenAI version pins from ``deps_versions.json`` (the single source of truth), +# so the dependency versions never drift from the native build. # # This is wired into pyproject.toml via:: # # [build-system] # build-backend = "_build_backend" # backend-path = ["."] -# -# Local dev workflow is unchanged: without the env var, ``python -m build`` -# produces ``foundry-local-sdk`` exactly as before. from __future__ import annotations import contextlib import json -import os import re from collections.abc import Generator from pathlib import Path @@ -49,17 +42,10 @@ _HAS_EDITABLE = False -_ENV_VAR = "FL_PYTHON_PACKAGE_NAME" _PYPROJECT = Path(__file__).resolve().parent.parent / "pyproject.toml" _SDK_V2_ROOT = _PYPROJECT.resolve().parent.parent _DEPS_JSON_STD = _SDK_V2_ROOT / "deps_versions.json" -# Match ``name = "..."`` only inside the [project] table. The regex is -# anchored to the first ``name = "foundry-local-sdk"`` occurrence which -# is the project name (the build backend itself is referenced by module -# name, not by a quoted package name, so there's no ambiguity). -_NAME_PATTERN = re.compile(r'(?m)^name\s*=\s*"foundry-local-sdk"') - # Patterns for rewriting ORT/GenAI version pins in the dependencies list. # Each captures the package name + ``==`` and we substitute in the version # read from the appropriate deps_versions JSON. @@ -81,41 +67,23 @@ def _read_versions(deps_file: Path) -> tuple[str, str]: return str(ort), str(genai) -def _patch_pyproject_text(original: str, *, override_name: str | None, deps_file: Path) -> str: - """Return *original* with the project name and ORT/GenAI version pins rewritten. - - * ``override_name`` — when set, replaces ``name = "foundry-local-sdk"``. - * ``deps_file`` — JSON file that supplies the ORT and GenAI version pins. - """ - patched = original - if override_name and override_name != "foundry-local-sdk": - patched, n = _NAME_PATTERN.subn(f'name = "{override_name}"', patched, count=1) - if n != 1: - raise RuntimeError( - f"Could not find canonical project name line in {_PYPROJECT} to patch " - f"for {_ENV_VAR}={override_name!r}." - ) - +def _patch_pyproject_text(original: str, *, deps_file: Path) -> str: + """Return *original* with the ORT/GenAI version pins rewritten from *deps_file*.""" ort_ver, genai_ver = _read_versions(deps_file) - patched = _ORT_PIN_PATTERN.sub(lambda m: f"{m.group(1)}{ort_ver}", patched) + patched = _ORT_PIN_PATTERN.sub(lambda m: f"{m.group(1)}{ort_ver}", original) patched = _GENAI_PIN_PATTERN.sub(lambda m: f"{m.group(1)}{genai_ver}", patched) return patched @contextlib.contextmanager -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 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 +def _rewrite_version_pins() -> Generator[None, None, None]: + """Rewrite pyproject.toml ORT/GenAI pins from deps_versions.json during build. + deps_versions.json is the single source of truth for the native dependency + versions; rewriting here keeps the wheel's declared pins in lockstep. + """ original = _PYPROJECT.read_text(encoding="utf-8") - patched = _patch_pyproject_text(original, override_name=override, deps_file=_DEPS_JSON_STD) + patched = _patch_pyproject_text(original, deps_file=_DEPS_JSON_STD) if patched == original: # Nothing to rewrite (e.g. JSON already matches and no name override). @@ -130,40 +98,40 @@ def _maybe_patch_name() -> Generator[None, None, None]: def get_requires_for_build_wheel(config_settings=None): - with _maybe_patch_name(): + with _rewrite_version_pins(): return _orig_get_requires_for_build_wheel(config_settings) def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None): - with _maybe_patch_name(): + with _rewrite_version_pins(): return _orig_prepare_metadata_for_build_wheel(metadata_directory, config_settings) def build_wheel(wheel_directory, config_settings=None, metadata_directory=None): - with _maybe_patch_name(): + with _rewrite_version_pins(): return _orig_build_wheel(wheel_directory, config_settings, metadata_directory) def get_requires_for_build_sdist(config_settings=None): - with _maybe_patch_name(): + with _rewrite_version_pins(): return _orig_get_requires_for_build_sdist(config_settings) def build_sdist(sdist_directory, config_settings=None): - with _maybe_patch_name(): + with _rewrite_version_pins(): return _orig_build_sdist(sdist_directory, config_settings) if _HAS_EDITABLE: def get_requires_for_build_editable(config_settings=None): - with _maybe_patch_name(): + with _rewrite_version_pins(): return _orig_get_requires_for_build_editable(config_settings) def prepare_metadata_for_build_editable(metadata_directory, config_settings=None): - with _maybe_patch_name(): + with _rewrite_version_pins(): return _orig_prepare_metadata_for_build_editable(metadata_directory, config_settings) def build_editable(wheel_directory, config_settings=None, metadata_directory=None): - with _maybe_patch_name(): + with _rewrite_version_pins(): return _orig_build_editable(wheel_directory, config_settings, metadata_directory) diff --git a/sdk_v2/python/pyproject.toml b/sdk_v2/python/pyproject.toml index 7b6e7478..6bed13b3 100644 --- a/sdk_v2/python/pyproject.toml +++ b/sdk_v2/python/pyproject.toml @@ -40,8 +40,7 @@ classifiers = [ # ORT/GenAI version pins below use sentinel ``0.0.0``. The real versions # 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. +# bumping ORT versions is a one-file edit. # # 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/src/foundry_local_sdk/_native/installer.py b/sdk_v2/python/src/foundry_local_sdk/_native/installer.py index f1d3094f..51a2541e 100644 --- a/sdk_v2/python/src/foundry_local_sdk/_native/installer.py +++ b/sdk_v2/python/src/foundry_local_sdk/_native/installer.py @@ -2,13 +2,12 @@ Usage:: - foundry-local-install [--winml] [--verbose] + foundry-local-install [--verbose] -Re-installs the SDK wheel (``foundry-local-sdk`` or ``foundry-local-sdk-winml``) -via pip — pip then resolves the ORT and GenAI runtime packages declared as -dependencies in ``pyproject.toml``. After install we materialise the platform's -DLL search path / symlink workarounds and verify ``onnxruntime`` and -``onnxruntime_genai`` import cleanly. +Re-installs the SDK wheel (``foundry-local-sdk``) via pip — pip then resolves the +ORT and GenAI runtime packages declared as dependencies in ``pyproject.toml``. +After install we materialise the platform's DLL search path / symlink workarounds +and verify ``onnxruntime`` and ``onnxruntime_genai`` import cleanly. This is the v2 equivalent of the legacy ``foundry-local-install`` command. v2 ships a single SDK wheel (rather than the legacy split between ``-sdk`` and @@ -57,14 +56,9 @@ def main(argv: list[str] | None = None) -> int: prog="foundry-local-install", description=( "(Re)install the Foundry Local SDK wheel and verify its ORT/GenAI " - "native dependencies are reachable. Use --winml for the WinML variant." + "native dependencies are reachable." ), ) - parser.add_argument( - "--winml", - action="store_true", - help="Install the WinML variant (foundry-local-sdk-winml) instead of the standard wheel.", - ) parser.add_argument( "--verbose", action="store_true", @@ -72,7 +66,7 @@ def main(argv: list[str] | None = None) -> int: ) args = parser.parse_args(argv) - package = "foundry-local-sdk-winml" if args.winml else "foundry-local-sdk" + package = "foundry-local-sdk" print(f"[foundry-local] Installing {package} (will pull ORT/GenAI runtime deps)...") try: subprocess.check_call([sys.executable, "-m", "pip", "install", "--upgrade", package]) From 7e3a49f2798413447544053537438c29c1be8538 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Wed, 17 Jun 2026 18:00:14 -0500 Subject: [PATCH 05/13] feat(js): bundle the WinML 2.x runtime in the npm package on Windows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The published JS package previously shipped only foundry_local and never the WinML EP catalog DLL, so WinML hardware EPs never worked out of the box. Bundle Microsoft.Windows.AI.MachineLearning.dll into prebuilds/ on Windows (copied as an optional sibling — skipped with a warning if absent), matching the single-package WinML-included flavor used by the C#/Python SDKs. ORT/GenAI are still fetched by the install-native.cjs postinstall hook. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- sdk_v2/js/script/pack-prebuilds.mjs | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/sdk_v2/js/script/pack-prebuilds.mjs b/sdk_v2/js/script/pack-prebuilds.mjs index b8129eff..36e1d091 100644 --- a/sdk_v2/js/script/pack-prebuilds.mjs +++ b/sdk_v2/js/script/pack-prebuilds.mjs @@ -1,6 +1,7 @@ -// CI-only. Stages the foundry_local shared library into prebuilds/ for npm -// publish. ORT / ORT-GenAI / WinML are NOT bundled — the -// install-native.cjs postinstall hook fetches them from NuGet at the user's +// CI-only. Stages the foundry_local shared library — and, on Windows, the +// reg-free WinML 2.x runtime DLL (Microsoft.Windows.AI.MachineLearning.dll) — +// into prebuilds/ for npm publish. ORT / ORT-GenAI are NOT bundled; the +// install-native.cjs postinstall hook fetches them from NuGet on the user's // machine (see ort-loading-contract.instructions.md). The .node addon itself // is already produced into prebuilds/-/ by `node-gyp rebuild`. // @@ -41,13 +42,20 @@ if (!existsSync(sourceDir)) { const destDir = resolve(pkgRoot, "prebuilds", `${process.platform}-${process.arch}`); mkdirSync(destDir, { recursive: true }); -// Only foundry_local — ORT siblings are explicitly excluded. +// foundry_local is required; ORT/GenAI siblings are excluded (fetched at install +// time). The WinML EP catalog DLL is an optional sibling bundled on Windows. const wanted = (() => { if (process.platform === "win32") return ["foundry_local.dll"]; if (process.platform === "darwin") return ["libfoundry_local.dylib"]; return ["libfoundry_local.so"]; })(); +// Optional native siblings — copied when present, skipped (with a warning) when +// not. The reg-free WinML 2.x runtime ships next to foundry_local.dll on Windows +// so WinML hardware EPs work out of the box without an install-time download. +const optional = + process.platform === "win32" ? ["Microsoft.Windows.AI.MachineLearning.dll"] : []; + let copied = 0; const available = new Set(readdirSync(sourceDir)); for (const file of wanted) { @@ -63,6 +71,19 @@ for (const file of wanted) { copied += 1; } +for (const file of optional) { + if (!available.has(file)) { + console.warn(`[pack-prebuilds] optional file not found, skipping: ${file}`); + continue; + } + const src = resolve(sourceDir, file); + const dst = resolve(destDir, file); + copyFileSync(src, dst); + const size = statSync(dst).size; + console.log(`[pack-prebuilds] ${file} (${size} bytes)`); + copied += 1; +} + console.log(`[pack-prebuilds] Copied ${copied} file(s) to ${destDir}`); // Also copy deps_versions.json to the package root so install-native.cjs can From 7bd4eef18711034c526a3765e01d533be298b4c7 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Wed, 17 Jun 2026 18:17:23 -0500 Subject: [PATCH 06/13] ci(v2): collapse WinML/non-WinML pipeline into one build per SDK MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Windows native build now always includes the reg-free WinML 2.x runtime, so there is no second flavor to build, pack, or test. Remove the duplicate variant stages and parameters across the v2 pipeline: - stages-sdk-v2: invoke C# and Python stages once (drop the winml invocations). - stages-build-native: drop the cpp_build_win_*_winml stages and the cpp_pack_nuget_winml pack; the single cpp-nuget now carries WinML on Windows. - stages-cs / stages-python: drop the variant parameter and the winml stages; artifacts lose the -base/-winml suffix (cs-sdk-v2, python-sdk-). - steps-build-windows: always prefetch WinML and stage the WinML DLL; drop the useWinml branches (build.py auto-enables WinML on Windows). - steps-build/test-cs, steps-build/test-python: drop the isWinML parameter and the now-no-op /p:UseWinML; net462 coverage keys only on Windows. - steps-pack-nuget: drop the variant parameter; pack one Runtime package. - steps-build-js: stage Microsoft.Windows.AI.MachineLearning.dll into the Windows prebuild dir so the npm package bundles WinML (excluded from ESRP signing — it is Microsoft-signed). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../v2/templates/stages-build-native.yml | 125 +--- .pipelines/v2/templates/stages-cs.yml | 352 ++++------- .pipelines/v2/templates/stages-python.yml | 549 +++++++----------- .pipelines/v2/templates/stages-sdk-v2.yml | 23 +- .pipelines/v2/templates/steps-build-cs.yml | 10 +- .pipelines/v2/templates/steps-build-js.yml | 17 +- .../v2/templates/steps-build-python.yml | 12 +- .../v2/templates/steps-build-windows.yml | 46 +- .pipelines/v2/templates/steps-pack-nuget.yml | 86 +-- .pipelines/v2/templates/steps-test-cs.yml | 13 +- .pipelines/v2/templates/steps-test-python.yml | 3 - 11 files changed, 412 insertions(+), 824 deletions(-) diff --git a/.pipelines/v2/templates/stages-build-native.yml b/.pipelines/v2/templates/stages-build-native.yml index 09a75c33..b8f8f0e8 100644 --- a/.pipelines/v2/templates/stages-build-native.yml +++ b/.pipelines/v2/templates/stages-build-native.yml @@ -1,13 +1,12 @@ # Native build + pack stages for the Foundry Local C++ SDK. # -# Per-platform stages each produce a `cpp-native-[-winml]` pipeline -# artifact. Two pack stages assemble the artifacts into separate NuGet -# packages on every build (PR and main): +# Per-platform stages each produce a `cpp-native-` pipeline artifact. On +# Windows the build always includes the reg-free WinML 2.x runtime. A single +# pack stage assembles the artifacts into one NuGet package on every build +# (PR and main): # -# pack_nuget – Microsoft.AI.Foundry.Local.Runtime -# (win-x64, win-arm64, linux-x64, osx-arm64) -# pack_nuget_winml – Microsoft.AI.Foundry.Local.Runtime.WinML -# (win-x64, win-arm64; both built with --use_winml) +# pack_nuget – Microsoft.AI.Foundry.Local.Runtime +# (win-x64, win-arm64, linux-x64, osx-arm64; Windows ships WinML) parameters: - name: buildConfig @@ -149,81 +148,10 @@ stages: runTests: true # ==================================================================== - # Windows x64 (WinML) — build + test - # The WinML variant links against the WinML-aligned ORT (1.23.x), which - # is older than the base ORT and lacks some ops used by cataloged vision - # models (e.g. com.microsoft:CausalConvWithState). The C++ VisionFixture - # and the C# VisionTests both skip themselves on WinML so the rest of - # the suite still runs against this configuration. - # ==================================================================== - - stage: cpp_build_win_x64_winml - displayName: 'C++ Native: Windows x64 (WinML)' - dependsOn: - - compute_version - jobs: - - job: build - pool: - name: onnxruntime-Win-CPU-2022 - os: windows - templateContext: - inputs: - - input: pipelineArtifact - artifactName: 'version-info' - targetPath: '$(Pipeline.Workspace)/version-info' - outputs: - - output: pipelineArtifact - artifactName: 'cpp-native-win-x64-winml' - targetPath: '$(Build.ArtifactStagingDirectory)/native' - steps: - - template: steps-build-windows.yml - parameters: - arch: x64 - buildConfig: ${{ parameters.buildConfig }} - ortVersion: ${{ parameters.ortVersion }} - genaiVersion: ${{ parameters.genaiVersion }} - winmlVersion: ${{ parameters.winmlVersion }} - useWinml: true - runTests: true - stageHeaders: false - - # ==================================================================== - # Windows ARM64 (WinML) — cross-compile only - # ==================================================================== - - stage: cpp_build_win_arm64_winml - displayName: 'C++ Native: Windows ARM64 (WinML)' - dependsOn: - - compute_version - jobs: - - job: build - pool: - name: onnxruntime-Win-CPU-2022 - os: windows - templateContext: - inputs: - - input: pipelineArtifact - artifactName: 'version-info' - targetPath: '$(Pipeline.Workspace)/version-info' - outputs: - - output: pipelineArtifact - artifactName: 'cpp-native-win-arm64-winml' - targetPath: '$(Build.ArtifactStagingDirectory)/native' - steps: - - template: steps-build-windows.yml - parameters: - arch: arm64 - buildConfig: ${{ parameters.buildConfig }} - ortVersion: ${{ parameters.ortVersion }} - genaiVersion: ${{ parameters.genaiVersion }} - winmlVersion: ${{ parameters.winmlVersion }} - useWinml: true - runTests: false - stageHeaders: false - - # ==================================================================== - # Pack — base NuGet package (all 4 platforms) + # Pack — NuGet package (all 4 platforms; Windows includes WinML) # ==================================================================== - stage: cpp_pack_nuget - displayName: 'C++ Native: Pack NuGet (base)' + displayName: 'C++ Native: Pack NuGet' dependsOn: - compute_version - cpp_build_win_x64 @@ -261,40 +189,3 @@ stages: parameters: ortVersion: ${{ parameters.ortVersion }} genaiVersion: ${{ parameters.genaiVersion }} - variant: base - - # ==================================================================== - # Pack — WinML NuGet package (Windows x64 + arm64 only) - # ==================================================================== - - stage: cpp_pack_nuget_winml - displayName: 'C++ Native: Pack NuGet (WinML)' - dependsOn: - - compute_version - - cpp_build_win_x64_winml - - cpp_build_win_arm64_winml - jobs: - - job: pack - pool: - name: onnxruntime-Win-CPU-2022 - os: windows - templateContext: - inputs: - - input: pipelineArtifact - artifactName: 'version-info' - targetPath: '$(Pipeline.Workspace)/version-info' - - input: pipelineArtifact - artifactName: 'cpp-native-win-x64-winml' - targetPath: '$(Pipeline.Workspace)/cpp-native-win-x64-winml' - - input: pipelineArtifact - artifactName: 'cpp-native-win-arm64-winml' - targetPath: '$(Pipeline.Workspace)/cpp-native-win-arm64-winml' - outputs: - - output: pipelineArtifact - artifactName: 'cpp-nuget-winml' - targetPath: '$(Build.ArtifactStagingDirectory)/nuget' - steps: - - template: steps-pack-nuget.yml - parameters: - ortVersion: ${{ parameters.ortVersion }} - genaiVersion: ${{ parameters.genaiVersion }} - variant: winml diff --git a/.pipelines/v2/templates/stages-cs.yml b/.pipelines/v2/templates/stages-cs.yml index c271dda1..3bb83a0e 100644 --- a/.pipelines/v2/templates/stages-cs.yml +++ b/.pipelines/v2/templates/stages-cs.yml @@ -1,241 +1,145 @@ -# Build + test stages for the sdk_v2 C# SDK, parameterized by variant. +# Build + test stages for the sdk_v2 C# SDK. # -# variant: base -> Microsoft.AI.Foundry.Local (build + test win/linux/osx) -# variant: winml -> Microsoft.AI.Foundry.Local.WinML (build + test win only) +# Produces Microsoft.AI.Foundry.Local (build + test win/linux/osx). On Windows +# the native runtime it consumes bundles the reg-free WinML 2.x runtime, so a +# single package serves every platform. # -# Depends on the matching native pack stage from stages-build-native.yml: -# base -> cpp_pack_nuget (artifact: cpp-nuget) -# winml -> cpp_pack_nuget_winml (artifact: cpp-nuget-winml) +# Depends on the native pack stage from stages-build-native.yml: +# cpp_pack_nuget (artifact: cpp-nuget) # -# Produces a `cs-sdk-v2-` pipeline artifact containing the signed -# .nupkg (+ .snupkg). - -parameters: -- name: variant - type: string - default: 'base' - values: ['base', 'winml'] - -# Per-variant config block selected at template-expansion time. Keeps the -# stage bodies free of nested ${{ if }} branching for static naming. -- name: _config_base - type: object - default: - suffix: 'base' - isWinML: false - packStage: 'cpp_pack_nuget' - nativeArtifact: 'cpp-nuget' - csArtifact: 'cs-sdk-v2-base' - -- name: _config_winml - type: object - default: - suffix: 'winml' - isWinML: true - packStage: 'cpp_pack_nuget_winml' - nativeArtifact: 'cpp-nuget-winml' - csArtifact: 'cs-sdk-v2-winml' +# Produces a `cs-sdk-v2` pipeline artifact containing the signed .nupkg (+ .snupkg). stages: # ==================================================================== # Build stage — produces the C# SDK NuGet package. # ==================================================================== -- ${{ if eq(parameters.variant, 'base') }}: - - stage: cs_build_base - displayName: 'C# SDK: Build (base)' - dependsOn: - - compute_version - - ${{ parameters._config_base.packStage }} - jobs: - - job: build - pool: - name: onnxruntime-Win-CPU-2022 - os: windows - templateContext: - inputs: - - input: pipelineArtifact - artifactName: 'version-info' - targetPath: '$(Pipeline.Workspace)/version-info' - - input: pipelineArtifact - artifactName: ${{ parameters._config_base.nativeArtifact }} - targetPath: '$(Pipeline.Workspace)/${{ parameters._config_base.nativeArtifact }}' - outputs: - - output: pipelineArtifact - artifactName: ${{ parameters._config_base.csArtifact }} - targetPath: '$(Build.ArtifactStagingDirectory)/cs-sdk' - steps: - - checkout: self - clean: true - - template: steps-build-cs.yml - parameters: - flNugetDir: '$(Pipeline.Workspace)/${{ parameters._config_base.nativeArtifact }}' - isWinML: false - outputDir: '$(Build.ArtifactStagingDirectory)/cs-sdk' - -- ${{ if eq(parameters.variant, 'winml') }}: - - stage: cs_build_winml - displayName: 'C# SDK: Build (WinML)' - dependsOn: - - compute_version - - ${{ parameters._config_winml.packStage }} - jobs: - - job: build - pool: - name: onnxruntime-Win-CPU-2022 - os: windows - templateContext: - inputs: - - input: pipelineArtifact - artifactName: 'version-info' - targetPath: '$(Pipeline.Workspace)/version-info' - - input: pipelineArtifact - artifactName: ${{ parameters._config_winml.nativeArtifact }} - targetPath: '$(Pipeline.Workspace)/${{ parameters._config_winml.nativeArtifact }}' - outputs: - - output: pipelineArtifact - artifactName: ${{ parameters._config_winml.csArtifact }} - targetPath: '$(Build.ArtifactStagingDirectory)/cs-sdk' - steps: - - checkout: self - clean: true - - template: steps-build-cs.yml - parameters: - flNugetDir: '$(Pipeline.Workspace)/${{ parameters._config_winml.nativeArtifact }}' - isWinML: true - outputDir: '$(Build.ArtifactStagingDirectory)/cs-sdk' +- stage: cs_build + displayName: 'C# SDK: Build' + dependsOn: + - compute_version + - cpp_pack_nuget + jobs: + - job: build + pool: + name: onnxruntime-Win-CPU-2022 + os: windows + templateContext: + inputs: + - input: pipelineArtifact + artifactName: 'version-info' + targetPath: '$(Pipeline.Workspace)/version-info' + - input: pipelineArtifact + artifactName: 'cpp-nuget' + targetPath: '$(Pipeline.Workspace)/cpp-nuget' + outputs: + - output: pipelineArtifact + artifactName: 'cs-sdk-v2' + targetPath: '$(Build.ArtifactStagingDirectory)/cs-sdk' + steps: + - checkout: self + clean: true + - template: steps-build-cs.yml + parameters: + flNugetDir: '$(Pipeline.Workspace)/cpp-nuget' + outputDir: '$(Build.ArtifactStagingDirectory)/cs-sdk' # ==================================================================== # Test — Windows x64 # ==================================================================== -- ${{ if eq(parameters.variant, 'base') }}: - - stage: cs_test_win_x64 - displayName: 'C# SDK: Test Windows x64 (base)' - dependsOn: - - cs_build_base - jobs: - - job: test - pool: - name: onnxruntime-Win-CPU-2022 - os: windows - templateContext: - inputs: - - input: pipelineArtifact - artifactName: 'version-info' - targetPath: '$(Pipeline.Workspace)/version-info' - - input: pipelineArtifact - artifactName: ${{ parameters._config_base.nativeArtifact }} - targetPath: '$(Pipeline.Workspace)/${{ parameters._config_base.nativeArtifact }}' - steps: - - checkout: self - clean: true - - template: ../../templates/checkout-steps.yml@self - parameters: - repoName: test-data-shared - - template: steps-test-cs.yml - parameters: - flNugetDir: '$(Pipeline.Workspace)/${{ parameters._config_base.nativeArtifact }}' - isWinML: false - testDataSharedDir: '$(Build.SourcesDirectory)/test-data-shared' - -- ${{ if eq(parameters.variant, 'winml') }}: - - stage: cs_test_win_x64_winml - displayName: 'C# SDK: Test Windows x64 (WinML)' - dependsOn: - - cs_build_winml - jobs: - - job: test - pool: - name: onnxruntime-Win-CPU-2022 - os: windows - templateContext: - inputs: - - input: pipelineArtifact - artifactName: 'version-info' - targetPath: '$(Pipeline.Workspace)/version-info' - - input: pipelineArtifact - artifactName: ${{ parameters._config_winml.nativeArtifact }} - targetPath: '$(Pipeline.Workspace)/${{ parameters._config_winml.nativeArtifact }}' - steps: - - checkout: self - clean: true - - template: ../../templates/checkout-steps.yml@self - parameters: - repoName: test-data-shared - - template: steps-test-cs.yml - parameters: - flNugetDir: '$(Pipeline.Workspace)/${{ parameters._config_winml.nativeArtifact }}' - isWinML: true - testDataSharedDir: '$(Build.SourcesDirectory)/test-data-shared' +- stage: cs_test_win_x64 + displayName: 'C# SDK: Test Windows x64' + dependsOn: + - cs_build + jobs: + - job: test + pool: + name: onnxruntime-Win-CPU-2022 + os: windows + templateContext: + inputs: + - input: pipelineArtifact + artifactName: 'version-info' + targetPath: '$(Pipeline.Workspace)/version-info' + - input: pipelineArtifact + artifactName: 'cpp-nuget' + targetPath: '$(Pipeline.Workspace)/cpp-nuget' + steps: + - checkout: self + clean: true + - template: ../../templates/checkout-steps.yml@self + parameters: + repoName: test-data-shared + - template: steps-test-cs.yml + parameters: + flNugetDir: '$(Pipeline.Workspace)/cpp-nuget' + testDataSharedDir: '$(Build.SourcesDirectory)/test-data-shared' # ==================================================================== -# Test — Linux x64 / macOS ARM64 (base variant only; WinML is Windows-only) +# Test — Linux x64 / macOS ARM64 # ==================================================================== -- ${{ if eq(parameters.variant, 'base') }}: - - stage: cs_test_linux_x64 - displayName: 'C# SDK: Test Linux x64 (base)' - dependsOn: - - cs_build_base - jobs: - - job: test - pool: - name: onnxruntime-Ubuntu2404-AMD-CPU - os: linux - templateContext: - inputs: - - input: pipelineArtifact - artifactName: 'version-info' - targetPath: '$(Pipeline.Workspace)/version-info' - - input: pipelineArtifact - artifactName: ${{ parameters._config_base.nativeArtifact }} - targetPath: '$(Pipeline.Workspace)/${{ parameters._config_base.nativeArtifact }}' - steps: - - checkout: self - clean: true - - template: ../../templates/checkout-steps.yml@self - parameters: - repoName: test-data-shared - - template: steps-test-cs.yml - parameters: - flNugetDir: '$(Pipeline.Workspace)/${{ parameters._config_base.nativeArtifact }}' - isWinML: false - testDataSharedDir: '$(Build.SourcesDirectory)/test-data-shared' +- stage: cs_test_linux_x64 + displayName: 'C# SDK: Test Linux x64' + dependsOn: + - cs_build + jobs: + - job: test + pool: + name: onnxruntime-Ubuntu2404-AMD-CPU + os: linux + templateContext: + inputs: + - input: pipelineArtifact + artifactName: 'version-info' + targetPath: '$(Pipeline.Workspace)/version-info' + - input: pipelineArtifact + artifactName: 'cpp-nuget' + targetPath: '$(Pipeline.Workspace)/cpp-nuget' + steps: + - checkout: self + clean: true + - template: ../../templates/checkout-steps.yml@self + parameters: + repoName: test-data-shared + - template: steps-test-cs.yml + parameters: + flNugetDir: '$(Pipeline.Workspace)/cpp-nuget' + testDataSharedDir: '$(Build.SourcesDirectory)/test-data-shared' - - stage: cs_test_osx_arm64 - displayName: 'C# SDK: Test macOS ARM64 (base)' - dependsOn: - - cs_build_base - jobs: - - job: test - pool: - name: AcesShared - os: macOS - demands: - - ImageOverride -equals ACES_VM_SharedPool_Sequoia - templateContext: - inputs: - - input: pipelineArtifact - artifactName: 'version-info' - targetPath: '$(Pipeline.Workspace)/version-info' - - input: pipelineArtifact - artifactName: ${{ parameters._config_base.nativeArtifact }} - targetPath: '$(Pipeline.Workspace)/${{ parameters._config_base.nativeArtifact }}' - steps: - - checkout: self - clean: true - - bash: | - set -euo pipefail - if ! command -v git-lfs >/dev/null 2>&1; then - brew install git-lfs - fi - git lfs install - displayName: 'Install git-lfs (macOS)' - - template: ../../templates/checkout-steps.yml@self - parameters: - repoName: test-data-shared - - template: steps-test-cs.yml - parameters: - flNugetDir: '$(Pipeline.Workspace)/${{ parameters._config_base.nativeArtifact }}' - isWinML: false - testDataSharedDir: '$(Build.SourcesDirectory)/test-data-shared' - additionalTestArgs: '--settings $(Build.SourcesDirectory)/sdk_v2/cs/test/FoundryLocal.Tests/sequential.runsettings' +- stage: cs_test_osx_arm64 + displayName: 'C# SDK: Test macOS ARM64' + dependsOn: + - cs_build + jobs: + - job: test + pool: + name: AcesShared + os: macOS + demands: + - ImageOverride -equals ACES_VM_SharedPool_Sequoia + templateContext: + inputs: + - input: pipelineArtifact + artifactName: 'version-info' + targetPath: '$(Pipeline.Workspace)/version-info' + - input: pipelineArtifact + artifactName: 'cpp-nuget' + targetPath: '$(Pipeline.Workspace)/cpp-nuget' + steps: + - checkout: self + clean: true + - bash: | + set -euo pipefail + if ! command -v git-lfs >/dev/null 2>&1; then + brew install git-lfs + fi + git lfs install + displayName: 'Install git-lfs (macOS)' + - template: ../../templates/checkout-steps.yml@self + parameters: + repoName: test-data-shared + - template: steps-test-cs.yml + parameters: + flNugetDir: '$(Pipeline.Workspace)/cpp-nuget' + testDataSharedDir: '$(Build.SourcesDirectory)/test-data-shared' + additionalTestArgs: '--settings $(Build.SourcesDirectory)/sdk_v2/cs/test/FoundryLocal.Tests/sequential.runsettings' diff --git a/.pipelines/v2/templates/stages-python.yml b/.pipelines/v2/templates/stages-python.yml index 7a07c7da..e6b859dc 100644 --- a/.pipelines/v2/templates/stages-python.yml +++ b/.pipelines/v2/templates/stages-python.yml @@ -1,346 +1,233 @@ -# Build + test stages for the sdk_v2 Python SDK, parameterized by variant. +# Build + test stages for the sdk_v2 Python SDK (foundry-local-sdk). # -# variant: base -> foundry-local-sdk (build win-x64 + win-arm64 + linux-x64 + osx-arm64; -# test win-x64 + linux-x64 + osx-arm64) -# variant: winml -> foundry-local-sdk-winml (build + test win-x64; build-only win-arm64) +# Builds win-x64 + win-arm64 + linux-x64 + osx-arm64; tests win-x64 + linux-x64 +# + osx-arm64 (win-arm64 is build-only, matching the C# matrix). # # Each build stage depends on the matching native build stage from -# stages-build-native.yml and consumes the `cpp-native-[-winml]` -# pipeline artifact directly (no NuGet — the wheel bundles the native lib -# at src/foundry_local_sdk/_native//). +# stages-build-native.yml and consumes the `cpp-native-` pipeline artifact +# directly (no NuGet — the wheel bundles the native lib at +# src/foundry_local_sdk/_native//). On Windows that native artifact carries +# the reg-free WinML 2.x runtime, so the single wheel includes WinML support. # -# Each build stage emits one wheel under the -# `python-sdk--` pipeline artifact. - -parameters: -- name: variant - type: string - default: 'base' - values: ['base', 'winml'] +# Each build stage emits one wheel under the `python-sdk-` pipeline artifact. stages: # ===================================================================== -# Base variant: build win-x64 / win-arm64 / linux-x64 / osx-arm64 +# Build win-x64 / win-arm64 / linux-x64 / osx-arm64 # ===================================================================== -- ${{ if eq(parameters.variant, 'base') }}: - - - stage: python_build_base_win_x64 - displayName: 'Python SDK: Build Windows x64 (base)' - dependsOn: - - compute_version - - cpp_build_win_x64 - jobs: - - job: build - pool: - name: onnxruntime-Win-CPU-2022 - os: windows - templateContext: - inputs: - - input: pipelineArtifact - artifactName: 'version-info' - targetPath: '$(Pipeline.Workspace)/version-info' - - input: pipelineArtifact - artifactName: 'cpp-native-win-x64' - targetPath: '$(Pipeline.Workspace)/cpp-native-win-x64' - outputs: - - output: pipelineArtifact - artifactName: 'python-sdk-base-win-x64' - targetPath: '$(Build.ArtifactStagingDirectory)/python-sdk' - steps: - - checkout: self - clean: true - - template: steps-build-python.yml - parameters: - nativeArtifactDir: '$(Pipeline.Workspace)/cpp-native-win-x64' - rid: 'win-x64' - isWinML: false - outputDir: '$(Build.ArtifactStagingDirectory)/python-sdk' - - - stage: python_build_base_win_arm64 - displayName: 'Python SDK: Build Windows ARM64 (base)' - dependsOn: - - compute_version - - cpp_build_win_arm64 - jobs: - - job: build - pool: - name: onnxruntime-Win-CPU-2022 - os: windows - templateContext: - inputs: - - input: pipelineArtifact - artifactName: 'version-info' - targetPath: '$(Pipeline.Workspace)/version-info' - - input: pipelineArtifact - artifactName: 'cpp-native-win-arm64' - targetPath: '$(Pipeline.Workspace)/cpp-native-win-arm64' - outputs: - - output: pipelineArtifact - artifactName: 'python-sdk-base-win-arm64' - targetPath: '$(Build.ArtifactStagingDirectory)/python-sdk' - steps: - - checkout: self - clean: true - - template: steps-build-python.yml - parameters: - nativeArtifactDir: '$(Pipeline.Workspace)/cpp-native-win-arm64' - rid: 'win-arm64' - isWinML: false - targetArch: 'arm64' - outputDir: '$(Build.ArtifactStagingDirectory)/python-sdk' - - - stage: python_build_base_linux_x64 - displayName: 'Python SDK: Build Linux x64 (base)' - dependsOn: - - compute_version - - cpp_build_linux_x64 - jobs: - - job: build - pool: - name: onnxruntime-Ubuntu2404-AMD-CPU - os: linux - templateContext: - inputs: - - input: pipelineArtifact - artifactName: 'version-info' - targetPath: '$(Pipeline.Workspace)/version-info' - - input: pipelineArtifact - artifactName: 'cpp-native-linux-x64' - targetPath: '$(Pipeline.Workspace)/cpp-native-linux-x64' - outputs: - - output: pipelineArtifact - artifactName: 'python-sdk-base-linux-x64' - targetPath: '$(Build.ArtifactStagingDirectory)/python-sdk' - steps: - - checkout: self - clean: true - - template: steps-build-python.yml - parameters: - nativeArtifactDir: '$(Pipeline.Workspace)/cpp-native-linux-x64' - rid: 'linux-x64' - isWinML: false - outputDir: '$(Build.ArtifactStagingDirectory)/python-sdk' +- stage: python_build_win_x64 + displayName: 'Python SDK: Build Windows x64' + dependsOn: + - compute_version + - cpp_build_win_x64 + jobs: + - job: build + pool: + name: onnxruntime-Win-CPU-2022 + os: windows + templateContext: + inputs: + - input: pipelineArtifact + artifactName: 'version-info' + targetPath: '$(Pipeline.Workspace)/version-info' + - input: pipelineArtifact + artifactName: 'cpp-native-win-x64' + targetPath: '$(Pipeline.Workspace)/cpp-native-win-x64' + outputs: + - output: pipelineArtifact + artifactName: 'python-sdk-win-x64' + targetPath: '$(Build.ArtifactStagingDirectory)/python-sdk' + steps: + - checkout: self + clean: true + - template: steps-build-python.yml + parameters: + nativeArtifactDir: '$(Pipeline.Workspace)/cpp-native-win-x64' + rid: 'win-x64' + outputDir: '$(Build.ArtifactStagingDirectory)/python-sdk' - - stage: python_build_base_osx_arm64 - displayName: 'Python SDK: Build macOS ARM64 (base)' - dependsOn: - - compute_version - - cpp_build_osx_arm64 - jobs: - - job: build - pool: - name: AcesShared - os: macOS - demands: - - ImageOverride -equals ACES_VM_SharedPool_Sequoia - templateContext: - inputs: - - input: pipelineArtifact - artifactName: 'version-info' - targetPath: '$(Pipeline.Workspace)/version-info' - - input: pipelineArtifact - artifactName: 'cpp-native-osx-arm64' - targetPath: '$(Pipeline.Workspace)/cpp-native-osx-arm64' - outputs: - - output: pipelineArtifact - artifactName: 'python-sdk-base-osx-arm64' - targetPath: '$(Build.ArtifactStagingDirectory)/python-sdk' - steps: - - checkout: self - clean: true - - template: steps-build-python.yml - parameters: - nativeArtifactDir: '$(Pipeline.Workspace)/cpp-native-osx-arm64' - rid: 'osx-arm64' - isWinML: false - pythonArchitecture: 'arm64' - outputDir: '$(Build.ArtifactStagingDirectory)/python-sdk' +- stage: python_build_win_arm64 + displayName: 'Python SDK: Build Windows ARM64' + dependsOn: + - compute_version + - cpp_build_win_arm64 + jobs: + - job: build + pool: + name: onnxruntime-Win-CPU-2022 + os: windows + templateContext: + inputs: + - input: pipelineArtifact + artifactName: 'version-info' + targetPath: '$(Pipeline.Workspace)/version-info' + - input: pipelineArtifact + artifactName: 'cpp-native-win-arm64' + targetPath: '$(Pipeline.Workspace)/cpp-native-win-arm64' + outputs: + - output: pipelineArtifact + artifactName: 'python-sdk-win-arm64' + targetPath: '$(Build.ArtifactStagingDirectory)/python-sdk' + steps: + - checkout: self + clean: true + - template: steps-build-python.yml + parameters: + nativeArtifactDir: '$(Pipeline.Workspace)/cpp-native-win-arm64' + rid: 'win-arm64' + targetArch: 'arm64' + outputDir: '$(Build.ArtifactStagingDirectory)/python-sdk' - # ================================================================= - # Tests — base variant (win-x64, linux-x64, osx-arm64). - # win-arm64 is build-only (matches legacy + C# matrix). - # ================================================================= - - stage: python_test_base_win_x64 - displayName: 'Python SDK: Test Windows x64 (base)' - dependsOn: - - python_build_base_win_x64 - jobs: - - job: test - pool: - name: onnxruntime-Win-CPU-2022 - os: windows - templateContext: - inputs: - - input: pipelineArtifact - artifactName: 'python-sdk-base-win-x64' - targetPath: '$(Pipeline.Workspace)/python-sdk-base-win-x64' - steps: - - checkout: self - clean: true - - template: ../../templates/checkout-steps.yml@self - parameters: - repoName: test-data-shared - - template: steps-test-python.yml - parameters: - wheelDir: '$(Pipeline.Workspace)/python-sdk-base-win-x64' - testDataSharedDir: '$(Build.SourcesDirectory)/test-data-shared' - isWinML: false +- stage: python_build_linux_x64 + displayName: 'Python SDK: Build Linux x64' + dependsOn: + - compute_version + - cpp_build_linux_x64 + jobs: + - job: build + pool: + name: onnxruntime-Ubuntu2404-AMD-CPU + os: linux + templateContext: + inputs: + - input: pipelineArtifact + artifactName: 'version-info' + targetPath: '$(Pipeline.Workspace)/version-info' + - input: pipelineArtifact + artifactName: 'cpp-native-linux-x64' + targetPath: '$(Pipeline.Workspace)/cpp-native-linux-x64' + outputs: + - output: pipelineArtifact + artifactName: 'python-sdk-linux-x64' + targetPath: '$(Build.ArtifactStagingDirectory)/python-sdk' + steps: + - checkout: self + clean: true + - template: steps-build-python.yml + parameters: + nativeArtifactDir: '$(Pipeline.Workspace)/cpp-native-linux-x64' + rid: 'linux-x64' + outputDir: '$(Build.ArtifactStagingDirectory)/python-sdk' - - stage: python_test_base_linux_x64 - displayName: 'Python SDK: Test Linux x64 (base)' - dependsOn: - - python_build_base_linux_x64 - jobs: - - job: test - pool: - name: onnxruntime-Ubuntu2404-AMD-CPU - os: linux - templateContext: - inputs: - - input: pipelineArtifact - artifactName: 'python-sdk-base-linux-x64' - targetPath: '$(Pipeline.Workspace)/python-sdk-base-linux-x64' - steps: - - checkout: self - clean: true - - template: ../../templates/checkout-steps.yml@self - parameters: - repoName: test-data-shared - - template: steps-test-python.yml - parameters: - wheelDir: '$(Pipeline.Workspace)/python-sdk-base-linux-x64' - testDataSharedDir: '$(Build.SourcesDirectory)/test-data-shared' - isWinML: false - - - stage: python_test_base_osx_arm64 - displayName: 'Python SDK: Test macOS ARM64 (base)' - dependsOn: - - python_build_base_osx_arm64 - jobs: - - job: test - pool: - name: AcesShared - os: macOS - demands: - - ImageOverride -equals ACES_VM_SharedPool_Sequoia - templateContext: - inputs: - - input: pipelineArtifact - artifactName: 'python-sdk-base-osx-arm64' - targetPath: '$(Pipeline.Workspace)/python-sdk-base-osx-arm64' - steps: - - checkout: self - clean: true - - bash: | - set -euo pipefail - if ! command -v git-lfs >/dev/null 2>&1; then - brew install git-lfs - fi - git lfs install - displayName: 'Install git-lfs (macOS)' - - template: ../../templates/checkout-steps.yml@self - parameters: - repoName: test-data-shared - - template: steps-test-python.yml - parameters: - wheelDir: '$(Pipeline.Workspace)/python-sdk-base-osx-arm64' - testDataSharedDir: '$(Build.SourcesDirectory)/test-data-shared' - isWinML: false - pythonArchitecture: 'arm64' - -# ===================================================================== -# WinML variant: Windows only (x64 + arm64); only x64 is tested. -# ===================================================================== -- ${{ if eq(parameters.variant, 'winml') }}: +- stage: python_build_osx_arm64 + displayName: 'Python SDK: Build macOS ARM64' + dependsOn: + - compute_version + - cpp_build_osx_arm64 + jobs: + - job: build + pool: + name: AcesShared + os: macOS + demands: + - ImageOverride -equals ACES_VM_SharedPool_Sequoia + templateContext: + inputs: + - input: pipelineArtifact + artifactName: 'version-info' + targetPath: '$(Pipeline.Workspace)/version-info' + - input: pipelineArtifact + artifactName: 'cpp-native-osx-arm64' + targetPath: '$(Pipeline.Workspace)/cpp-native-osx-arm64' + outputs: + - output: pipelineArtifact + artifactName: 'python-sdk-osx-arm64' + targetPath: '$(Build.ArtifactStagingDirectory)/python-sdk' + steps: + - checkout: self + clean: true + - template: steps-build-python.yml + parameters: + nativeArtifactDir: '$(Pipeline.Workspace)/cpp-native-osx-arm64' + rid: 'osx-arm64' + pythonArchitecture: 'arm64' + outputDir: '$(Build.ArtifactStagingDirectory)/python-sdk' - - stage: python_build_winml_win_x64 - displayName: 'Python SDK: Build Windows x64 (WinML)' - dependsOn: - - compute_version - - cpp_build_win_x64_winml - jobs: - - job: build - pool: - name: onnxruntime-Win-CPU-2022 - os: windows - templateContext: - inputs: - - input: pipelineArtifact - artifactName: 'version-info' - targetPath: '$(Pipeline.Workspace)/version-info' - - input: pipelineArtifact - artifactName: 'cpp-native-win-x64-winml' - targetPath: '$(Pipeline.Workspace)/cpp-native-win-x64-winml' - outputs: - - output: pipelineArtifact - artifactName: 'python-sdk-winml-win-x64' - targetPath: '$(Build.ArtifactStagingDirectory)/python-sdk' - steps: - - checkout: self - clean: true - - template: steps-build-python.yml - parameters: - nativeArtifactDir: '$(Pipeline.Workspace)/cpp-native-win-x64-winml' - rid: 'win-x64' - isWinML: true - outputDir: '$(Build.ArtifactStagingDirectory)/python-sdk' +# ================================================================= +# Tests — win-x64, linux-x64, osx-arm64. win-arm64 is build-only. +# ================================================================= +- stage: python_test_win_x64 + displayName: 'Python SDK: Test Windows x64' + dependsOn: + - python_build_win_x64 + jobs: + - job: test + pool: + name: onnxruntime-Win-CPU-2022 + os: windows + templateContext: + inputs: + - input: pipelineArtifact + artifactName: 'python-sdk-win-x64' + targetPath: '$(Pipeline.Workspace)/python-sdk-win-x64' + steps: + - checkout: self + clean: true + - template: ../../templates/checkout-steps.yml@self + parameters: + repoName: test-data-shared + - template: steps-test-python.yml + parameters: + wheelDir: '$(Pipeline.Workspace)/python-sdk-win-x64' + testDataSharedDir: '$(Build.SourcesDirectory)/test-data-shared' - - stage: python_build_winml_win_arm64 - displayName: 'Python SDK: Build Windows ARM64 (WinML)' - dependsOn: - - compute_version - - cpp_build_win_arm64_winml - jobs: - - job: build - pool: - name: onnxruntime-Win-CPU-2022 - os: windows - templateContext: - inputs: - - input: pipelineArtifact - artifactName: 'version-info' - targetPath: '$(Pipeline.Workspace)/version-info' - - input: pipelineArtifact - artifactName: 'cpp-native-win-arm64-winml' - targetPath: '$(Pipeline.Workspace)/cpp-native-win-arm64-winml' - outputs: - - output: pipelineArtifact - artifactName: 'python-sdk-winml-win-arm64' - targetPath: '$(Build.ArtifactStagingDirectory)/python-sdk' - steps: - - checkout: self - clean: true - - template: steps-build-python.yml - parameters: - nativeArtifactDir: '$(Pipeline.Workspace)/cpp-native-win-arm64-winml' - rid: 'win-arm64' - isWinML: true - targetArch: 'arm64' - outputDir: '$(Build.ArtifactStagingDirectory)/python-sdk' +- stage: python_test_linux_x64 + displayName: 'Python SDK: Test Linux x64' + dependsOn: + - python_build_linux_x64 + jobs: + - job: test + pool: + name: onnxruntime-Ubuntu2404-AMD-CPU + os: linux + templateContext: + inputs: + - input: pipelineArtifact + artifactName: 'python-sdk-linux-x64' + targetPath: '$(Pipeline.Workspace)/python-sdk-linux-x64' + steps: + - checkout: self + clean: true + - template: ../../templates/checkout-steps.yml@self + parameters: + repoName: test-data-shared + - template: steps-test-python.yml + parameters: + wheelDir: '$(Pipeline.Workspace)/python-sdk-linux-x64' + testDataSharedDir: '$(Build.SourcesDirectory)/test-data-shared' - - stage: python_test_winml_win_x64 - displayName: 'Python SDK: Test Windows x64 (WinML)' - dependsOn: - - python_build_winml_win_x64 - jobs: - - job: test - pool: - name: onnxruntime-Win-CPU-2022 - os: windows - templateContext: - inputs: - - input: pipelineArtifact - artifactName: 'python-sdk-winml-win-x64' - targetPath: '$(Pipeline.Workspace)/python-sdk-winml-win-x64' - steps: - - checkout: self - clean: true - - template: ../../templates/checkout-steps.yml@self - parameters: - repoName: test-data-shared - - template: steps-test-python.yml - parameters: - wheelDir: '$(Pipeline.Workspace)/python-sdk-winml-win-x64' - testDataSharedDir: '$(Build.SourcesDirectory)/test-data-shared' - isWinML: true +- stage: python_test_osx_arm64 + displayName: 'Python SDK: Test macOS ARM64' + dependsOn: + - python_build_osx_arm64 + jobs: + - job: test + pool: + name: AcesShared + os: macOS + demands: + - ImageOverride -equals ACES_VM_SharedPool_Sequoia + templateContext: + inputs: + - input: pipelineArtifact + artifactName: 'python-sdk-osx-arm64' + targetPath: '$(Pipeline.Workspace)/python-sdk-osx-arm64' + steps: + - checkout: self + clean: true + - bash: | + set -euo pipefail + if ! command -v git-lfs >/dev/null 2>&1; then + brew install git-lfs + fi + git lfs install + displayName: 'Install git-lfs (macOS)' + - template: ../../templates/checkout-steps.yml@self + parameters: + repoName: test-data-shared + - template: steps-test-python.yml + parameters: + wheelDir: '$(Pipeline.Workspace)/python-sdk-osx-arm64' + testDataSharedDir: '$(Build.SourcesDirectory)/test-data-shared' + pythonArchitecture: 'arm64' diff --git a/.pipelines/v2/templates/stages-sdk-v2.yml b/.pipelines/v2/templates/stages-sdk-v2.yml index 5a1c59ba..755edd17 100644 --- a/.pipelines/v2/templates/stages-sdk-v2.yml +++ b/.pipelines/v2/templates/stages-sdk-v2.yml @@ -2,8 +2,9 @@ # # Composes: # 1. Native C++ build + pack (templates/stages-build-native.yml) -# 2. C# SDK base + WinML (templates/stages-cs.yml × 2) -# 3. Python SDK base + WinML (templates/stages-python.yml × 2) +# 2. C# SDK (templates/stages-cs.yml) +# 3. Python SDK (templates/stages-python.yml) +# 4. JS SDK (templates/stages-js.yml) # # Assumes the caller has already emitted a `compute_version` stage that # publishes the `version-info` pipeline artifact (containing sdkVersion.txt @@ -31,25 +32,11 @@ stages: genaiVersion: ${{ parameters.genaiVersion }} winmlVersion: ${{ parameters.winmlVersion }} -# ── C# SDK (base) ── +# ── C# SDK ── - template: stages-cs.yml - parameters: - variant: base - -# ── C# SDK (WinML) ── -- template: stages-cs.yml - parameters: - variant: winml -# ── Python SDK (base) ── +# ── Python SDK ── - template: stages-python.yml - parameters: - variant: base - -# ── Python SDK (WinML) ── -- template: stages-python.yml - parameters: - variant: winml # ── JS SDK (single multi-platform tarball) ── - template: stages-js.yml diff --git a/.pipelines/v2/templates/steps-build-cs.yml b/.pipelines/v2/templates/steps-build-cs.yml index 1c77af19..2ebf7e08 100644 --- a/.pipelines/v2/templates/steps-build-cs.yml +++ b/.pipelines/v2/templates/steps-build-cs.yml @@ -1,8 +1,8 @@ # Inner steps to restore, build, sign, and pack the sdk_v2 C# SDK -# (Microsoft.AI.Foundry.Local / Microsoft.AI.Foundry.Local.WinML). +# (Microsoft.AI.Foundry.Local). # # The caller is responsible for placing the Foundry Local Runtime nupkg -# (Microsoft.AI.Foundry.Local.Runtime[.WinML]) in `flNugetDir`, and for +# (Microsoft.AI.Foundry.Local.Runtime) in `flNugetDir`, and for # downloading the `version-info` pipeline artifact so this template can # read $(Pipeline.Workspace)/version-info/sdkVersion.txt. @@ -10,9 +10,6 @@ parameters: - name: flNugetDir type: string displayName: 'Path to directory containing the Foundry Local Runtime .nupkg' -- name: isWinML - type: boolean - default: false - name: outputDir type: string default: '$(Build.ArtifactStagingDirectory)/cs-sdk' @@ -92,7 +89,6 @@ steps: if (-not (Test-Path $proj)) { throw "Project not found: $proj" } dotnet restore $proj ` --configfile "$(customNugetConfig)" ` - /p:UseWinML=${{ parameters.isWinML }} ` /p:FoundryLocalRuntimeVersion=$(packageVersion) ` /p:FoundryLocalNativeBinDir= if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } @@ -105,7 +101,6 @@ steps: script: | dotnet build "$(Build.SourcesDirectory)/sdk_v2/cs/src/Microsoft.AI.Foundry.Local.csproj" ` --no-restore --configuration Release ` - /p:UseWinML=${{ parameters.isWinML }} ` /p:FoundryLocalRuntimeVersion=$(packageVersion) ` /p:FoundryLocalNativeBinDir= if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } @@ -145,7 +140,6 @@ steps: --no-build --no-restore --configuration Release ` --output "${{ parameters.outputDir }}" ` /p:PackageVersion=$(packageVersion) ` - /p:UseWinML=${{ parameters.isWinML }} ` /p:FoundryLocalRuntimeVersion=$(packageVersion) ` /p:FoundryLocalNativeBinDir= ` /p:IncludeSymbols=true ` diff --git a/.pipelines/v2/templates/steps-build-js.yml b/.pipelines/v2/templates/steps-build-js.yml index 5e6ab1e7..1b0cba2c 100644 --- a/.pipelines/v2/templates/steps-build-js.yml +++ b/.pipelines/v2/templates/steps-build-js.yml @@ -123,9 +123,10 @@ steps: FOUNDRY_LOCAL_PREBUILD_DIR: '$(Build.SourcesDirectory)/sdk_v2/js/prebuilds/$(prebuildDir)' # Stage prebuilds/-/ with the two .node addons + foundry_local -# shared library. binding.gyp's copy_addon_to_prebuilds target already -# drops the .node files into sdk_v2/js/prebuilds/-/; we copy -# from there into the artifact dir alongside the shared library. +# shared library (and, on Windows, the reg-free WinML 2.x runtime DLL). +# binding.gyp's copy_addon_to_prebuilds target already drops the .node files +# into sdk_v2/js/prebuilds/-/; we copy from there into the artifact +# dir alongside the shared library. - ${{ if or(eq(parameters.rid, 'win-x64'), eq(parameters.rid, 'win-arm64')) }}: - pwsh: | $dst = "${{ parameters.outputDir }}/prebuilds/$(prebuildDir)" @@ -145,6 +146,16 @@ steps: Copy-Item $nativeLibPath -Destination $dst -Force Write-Host " staged $(nativeLib)" + # WinML 2.x runtime ships next to foundry_local.dll so WinML EPs work out + # of the box. It is Microsoft-signed (NuGet) — excluded from ESRP signing. + $winmlDll = Join-Path "${{ parameters.nativeArtifactDir }}" 'Microsoft.Windows.AI.MachineLearning.dll' + if (Test-Path $winmlDll) { + Copy-Item $winmlDll -Destination $dst -Force + Write-Host " staged Microsoft.Windows.AI.MachineLearning.dll" + } else { + Write-Warning "WinML runtime DLL not found in native artifact: $winmlDll" + } + Get-ChildItem $dst | ForEach-Object { Write-Host " $($_.Name) $($_.Length) bytes" } displayName: 'Stage prebuild directory' diff --git a/.pipelines/v2/templates/steps-build-python.yml b/.pipelines/v2/templates/steps-build-python.yml index fe81d69c..50f86c9d 100644 --- a/.pipelines/v2/templates/steps-build-python.yml +++ b/.pipelines/v2/templates/steps-build-python.yml @@ -13,13 +13,10 @@ parameters: - name: nativeArtifactDir type: string - displayName: 'Path to the downloaded cpp-native-[-winml] artifact directory' + displayName: 'Path to the downloaded cpp-native- artifact directory' - name: rid type: string displayName: 'Runtime identifier (win-x64, win-arm64, linux-x64, osx-arm64)' -- name: isWinML - type: boolean - default: false - name: outputDir type: string default: '$(Build.ArtifactStagingDirectory)/python-sdk' @@ -270,13 +267,6 @@ steps: script: | $outDir = "${{ parameters.outputDir }}" New-Item -ItemType Directory -Force -Path $outDir | Out-Null - $isWinML = [bool]::Parse('${{ parameters.isWinML }}') - if ($isWinML) { - $env:FL_PYTHON_PACKAGE_NAME = 'foundry-local-sdk-winml' - Write-Host "WinML variant: FL_PYTHON_PACKAGE_NAME=$env:FL_PYTHON_PACKAGE_NAME" - } else { - Remove-Item Env:FL_PYTHON_PACKAGE_NAME -ErrorAction SilentlyContinue - } $targetArch = '${{ parameters.targetArch }}' $extraArgs = @() if ($targetArch -eq 'arm64') { diff --git a/.pipelines/v2/templates/steps-build-windows.yml b/.pipelines/v2/templates/steps-build-windows.yml index 0e17f07b..66b4624d 100644 --- a/.pipelines/v2/templates/steps-build-windows.yml +++ b/.pipelines/v2/templates/steps-build-windows.yml @@ -10,7 +10,6 @@ # ortVersion – Microsoft.ML.OnnxRuntime.Foundry version # genaiVersion – Microsoft.ML.OnnxRuntimeGenAI.Foundry 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 @@ -26,9 +25,6 @@ parameters: type: string - name: winmlVersion type: string -- name: useWinml - type: boolean - default: false - name: runTests type: boolean default: false @@ -60,7 +56,7 @@ steps: ortVersion: ${{ parameters.ortVersion }} genaiVersion: ${{ parameters.genaiVersion }} winmlVersion: ${{ parameters.winmlVersion }} - includeWinml: ${{ parameters.useWinml }} + includeWinml: true shell: pwsh # Bake the pipeline-computed version into the binary so FoundryLocalGetVersionString() @@ -83,12 +79,13 @@ steps: repoName: test-data-shared basePath: '$(Agent.BuildDirectory)' -- ${{ if and(eq(parameters.arch, 'x64'), eq(parameters.useWinml, false)) }}: +- ${{ if eq(parameters.arch, 'x64') }}: - script: >- python build.py --configure --build --config ${{ parameters.buildConfig }} --cmake_generator "Visual Studio 17 2022" + --winml_sdk_version ${{ parameters.winmlVersion }} --cmake_extra_defines $(cmakeFetchDefines) displayName: 'Configure and build (x64)' workingDirectory: $(Build.SourcesDirectory)/sdk_v2/cpp @@ -96,26 +93,13 @@ steps: VCPKG_ROOT: $(Build.BinariesDirectory)\vcpkg PKG_CONFIG: $(Build.BinariesDirectory)\tools\pkg-config.bat -- ${{ if and(eq(parameters.arch, 'x64'), eq(parameters.useWinml, true)) }}: - - script: >- - python build.py - --configure --build - --config ${{ parameters.buildConfig }} - --cmake_generator "Visual Studio 17 2022" - --use_winml --winml_sdk_version ${{ parameters.winmlVersion }} - --cmake_extra_defines $(cmakeFetchDefines) - displayName: 'Configure and build (x64, WinML)' - workingDirectory: $(Build.SourcesDirectory)/sdk_v2/cpp - env: - VCPKG_ROOT: $(Build.BinariesDirectory)\vcpkg - PKG_CONFIG: $(Build.BinariesDirectory)\tools\pkg-config.bat - -- ${{ if and(eq(parameters.arch, 'arm64'), eq(parameters.useWinml, false)) }}: +- ${{ if eq(parameters.arch, 'arm64') }}: - script: >- python build.py --configure --build --arm64 --config ${{ parameters.buildConfig }} --cmake_generator "Visual Studio 17 2022" + --winml_sdk_version ${{ parameters.winmlVersion }} --cmake_extra_defines $(cmakeFetchDefines) displayName: 'Configure and build (arm64 cross-compile)' workingDirectory: $(Build.SourcesDirectory)/sdk_v2/cpp @@ -123,20 +107,6 @@ steps: VCPKG_ROOT: $(Build.BinariesDirectory)\vcpkg PKG_CONFIG: $(Build.BinariesDirectory)\tools\pkg-config.bat -- ${{ if and(eq(parameters.arch, 'arm64'), eq(parameters.useWinml, true)) }}: - - script: >- - python build.py - --configure --build --arm64 - --config ${{ parameters.buildConfig }} - --cmake_generator "Visual Studio 17 2022" - --use_winml --winml_sdk_version ${{ parameters.winmlVersion }} - --cmake_extra_defines $(cmakeFetchDefines) - displayName: 'Configure and build (arm64 cross-compile, WinML)' - workingDirectory: $(Build.SourcesDirectory)/sdk_v2/cpp - env: - VCPKG_ROOT: $(Build.BinariesDirectory)\vcpkg - PKG_CONFIG: $(Build.BinariesDirectory)\tools\pkg-config.bat - - ${{ if and(eq(parameters.runTests, true), eq(parameters.arch, 'x64')) }}: - script: python build.py --test --config ${{ parameters.buildConfig }} displayName: 'Run tests' @@ -170,11 +140,9 @@ steps: $sources = @( (Join-Path $binDir 'foundry_local.dll'), (Join-Path $binDir 'foundry_local.pdb'), - (Join-Path $linkDir 'foundry_local.lib') + (Join-Path $linkDir 'foundry_local.lib'), + (Join-Path $binDir 'Microsoft.Windows.AI.MachineLearning.dll') ) - if ($${{ parameters.useWinml }}) { - $sources += (Join-Path $binDir 'Microsoft.Windows.AI.MachineLearning.dll') - } foreach ($s in $sources) { if (-not (Test-Path $s)) { diff --git a/.pipelines/v2/templates/steps-pack-nuget.yml b/.pipelines/v2/templates/steps-pack-nuget.yml index bde97d77..eb088e4b 100644 --- a/.pipelines/v2/templates/steps-pack-nuget.yml +++ b/.pipelines/v2/templates/steps-pack-nuget.yml @@ -4,21 +4,15 @@ # Inputs are downloaded by the parent job's templateContext.inputs into # $(Pipeline.Workspace)//. # -# Variants: -# base -> Microsoft.AI.Foundry.Local.Runtime -# (win-x64, win-arm64, linux-x64, osx-arm64) -# winml -> Microsoft.AI.Foundry.Local.Runtime.WinML -# (win-x64, win-arm64 only — both built with --use_winml) +# Produces Microsoft.AI.Foundry.Local.Runtime (win-x64, win-arm64, linux-x64, +# osx-arm64). On Windows the artifacts carry the reg-free WinML 2.x runtime DLL, +# which pack.py forwards automatically. parameters: - name: ortVersion type: string - name: genaiVersion type: string -- name: variant - type: string - default: 'base' - values: ['base', 'winml'] steps: @@ -45,58 +39,30 @@ steps: } } -- ${{ if eq(parameters.variant, 'base') }}: - - task: PowerShell@2 - displayName: 'Pack NuGet package (base)' - inputs: - targetType: inline - pwsh: true - script: | - $ErrorActionPreference = 'Stop' - - $version = (Get-Content "$(Pipeline.Workspace)/version-info/sdkVersion.txt" -Raw).Trim() - Write-Host "Packing version: $version" - - $outDir = "$(Build.ArtifactStagingDirectory)/nuget" - New-Item -ItemType Directory -Force -Path $outDir | Out-Null - - python "$(Build.SourcesDirectory)/sdk_v2/cpp/nuget/pack.py" ` - --version "$version" ` - --package_id "Microsoft.AI.Foundry.Local.Runtime" ` - --ort_version "${{ parameters.ortVersion }}" ` - --genai_version "${{ parameters.genaiVersion }}" ` - --win_x64 "$(Pipeline.Workspace)/cpp-native-win-x64" ` - --win_arm64 "$(Pipeline.Workspace)/cpp-native-win-arm64" ` - --linux_x64 "$(Pipeline.Workspace)/cpp-native-linux-x64" ` - --osx_arm64 "$(Pipeline.Workspace)/cpp-native-osx-arm64" ` - --output_dir "$outDir" - - Write-Host "Generated packages:" - Get-ChildItem $outDir -Filter '*.nupkg' | ForEach-Object { Write-Host " $($_.Name)" } - -- ${{ if eq(parameters.variant, 'winml') }}: - - task: PowerShell@2 - displayName: 'Pack NuGet package (WinML)' - inputs: - targetType: inline - pwsh: true - script: | - $ErrorActionPreference = 'Stop' +- task: PowerShell@2 + displayName: 'Pack NuGet package' + inputs: + targetType: inline + pwsh: true + script: | + $ErrorActionPreference = 'Stop' - $version = (Get-Content "$(Pipeline.Workspace)/version-info/sdkVersion.txt" -Raw).Trim() - Write-Host "Packing version: $version" + $version = (Get-Content "$(Pipeline.Workspace)/version-info/sdkVersion.txt" -Raw).Trim() + Write-Host "Packing version: $version" - $outDir = "$(Build.ArtifactStagingDirectory)/nuget" - New-Item -ItemType Directory -Force -Path $outDir | Out-Null + $outDir = "$(Build.ArtifactStagingDirectory)/nuget" + New-Item -ItemType Directory -Force -Path $outDir | Out-Null - python "$(Build.SourcesDirectory)/sdk_v2/cpp/nuget/pack.py" ` - --version "$version" ` - --package_id "Microsoft.AI.Foundry.Local.Runtime.WinML" ` - --ort_version "${{ parameters.ortVersion }}" ` - --genai_version "${{ parameters.genaiVersion }}" ` - --win_x64 "$(Pipeline.Workspace)/cpp-native-win-x64-winml" ` - --win_arm64 "$(Pipeline.Workspace)/cpp-native-win-arm64-winml" ` - --output_dir "$outDir" + python "$(Build.SourcesDirectory)/sdk_v2/cpp/nuget/pack.py" ` + --version "$version" ` + --package_id "Microsoft.AI.Foundry.Local.Runtime" ` + --ort_version "${{ parameters.ortVersion }}" ` + --genai_version "${{ parameters.genaiVersion }}" ` + --win_x64 "$(Pipeline.Workspace)/cpp-native-win-x64" ` + --win_arm64 "$(Pipeline.Workspace)/cpp-native-win-arm64" ` + --linux_x64 "$(Pipeline.Workspace)/cpp-native-linux-x64" ` + --osx_arm64 "$(Pipeline.Workspace)/cpp-native-osx-arm64" ` + --output_dir "$outDir" - Write-Host "Generated packages:" - Get-ChildItem $outDir -Filter '*.nupkg' | ForEach-Object { Write-Host " $($_.Name)" } + Write-Host "Generated packages:" + Get-ChildItem $outDir -Filter '*.nupkg' | ForEach-Object { Write-Host " $($_.Name)" } diff --git a/.pipelines/v2/templates/steps-test-cs.yml b/.pipelines/v2/templates/steps-test-cs.yml index 73274e27..4bca8072 100644 --- a/.pipelines/v2/templates/steps-test-cs.yml +++ b/.pipelines/v2/templates/steps-test-cs.yml @@ -8,9 +8,6 @@ parameters: - name: flNugetDir type: string displayName: 'Path to directory containing the Foundry Local Runtime .nupkg' -- name: isWinML - type: boolean - default: false - name: testDataSharedDir type: string displayName: 'Absolute path to a checked-out test-data-shared working tree' @@ -81,7 +78,7 @@ steps: displayName: 'Authenticate NuGet feeds' # Per-job NuGet isolation to prevent "Central Directory corrupt" / file-locking -# errors when multiple C# test jobs (regular + WinML) run concurrently on the +# errors when multiple C# test jobs run concurrently on the # same reused agent. Keyed by $(System.JobId); cleaned on each run. - task: PowerShell@2 displayName: 'Set isolated NuGet packages path' @@ -117,7 +114,6 @@ steps: dotnet restore $proj ` --configfile "$(customNugetConfig)" ` - /p:UseWinML=${{ parameters.isWinML }} ` /p:FoundryLocalRuntimeVersion=$(packageVersion) ` /p:FoundryLocalNativeBinDir= ` /p:RuntimeIdentifiers=$rid @@ -125,7 +121,6 @@ steps: dotnet build $proj ` --no-restore --configuration Release ` - /p:UseWinML=${{ parameters.isWinML }} ` /p:FoundryLocalRuntimeVersion=$(packageVersion) ` /p:FoundryLocalNativeBinDir= ` /p:RuntimeIdentifiers=$rid @@ -161,15 +156,14 @@ steps: # Test TFMs: # * net9.0 covers the .NET (Core) runtime test surface. - # * net462 (Windows only, non-WinML) exercises the netstandard2.0 surface + # * net462 (Windows only) 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) { + if ($isWin) { $frameworks += 'net462' } @@ -178,7 +172,6 @@ steps: dotnet test $proj ` --no-build --configuration Release ` --framework $tfm ` - /p:UseWinML=${{ parameters.isWinML }} ` ${{ parameters.additionalTestArgs }} if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } } diff --git a/.pipelines/v2/templates/steps-test-python.yml b/.pipelines/v2/templates/steps-test-python.yml index 9396a7cc..2bf2b303 100644 --- a/.pipelines/v2/templates/steps-test-python.yml +++ b/.pipelines/v2/templates/steps-test-python.yml @@ -12,9 +12,6 @@ parameters: - name: testDataSharedDir type: string displayName: 'Absolute path to a checked-out test-data-shared working tree' -- name: isWinML - type: boolean - default: false - name: pythonArchitecture type: string default: 'x64' From 38771b536af30899ed1a9750ceb831a4094b80fe Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Wed, 17 Jun 2026 18:18:58 -0500 Subject: [PATCH 07/13] samples(cs): use the unified Microsoft.AI.Foundry.Local package The single SDK package now bundles the WinML runtime on Windows, so the samples no longer need the OS-conditional split between Microsoft.AI.Foundry.Local.WinML (Windows) and Microsoft.AI.Foundry.Local (other platforms). Collapse every sample csproj to one unconditional package reference, drop the .WinML PackageVersion from Directory.Packages.props, and update the READMEs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- samples/cs/Directory.Packages.props | 3 +-- samples/cs/README.md | 7 +------ .../AudioTranscriptionExample.csproj | 8 +------- samples/cs/embeddings/Embeddings.csproj | 8 +------- .../FoundryLocalWebServer.csproj | 8 +------- .../LiveAudioTranscriptionExample.csproj | 8 +------- .../ModelManagementExample.csproj | 8 +------- .../NativeChatCompletions.csproj | 8 +------- .../ToolCallingFoundryLocalSdk.csproj | 8 +------- .../ToolCallingFoundryLocalWebServer.csproj | 8 +------- .../TutorialChatAssistant.csproj | 8 +------- .../TutorialDocumentSummarizer.csproj | 8 +------- .../tutorial-tool-calling/TutorialToolCalling.csproj | 8 +------- .../tutorial-voice-to-text/TutorialVoiceToText.csproj | 8 +------- samples/cs/verify-winml/README.md | 6 +++--- samples/cs/verify-winml/VerifyWinML.csproj | 10 +--------- 16 files changed, 18 insertions(+), 104 deletions(-) diff --git a/samples/cs/Directory.Packages.props b/samples/cs/Directory.Packages.props index 431b27a8..bc8bd3ea 100644 --- a/samples/cs/Directory.Packages.props +++ b/samples/cs/Directory.Packages.props @@ -5,8 +5,7 @@ - - + diff --git a/samples/cs/README.md b/samples/cs/README.md index fb594717..1fa48259 100644 --- a/samples/cs/README.md +++ b/samples/cs/README.md @@ -1,11 +1,6 @@ # 🚀 Foundry Local C# Samples -These samples demonstrate how to use the Foundry Local C# SDK. Each sample uses a **unified project file** that automatically detects your operating system and selects the optimal NuGet package: - -- **Windows**: Uses `Microsoft.AI.Foundry.Local.WinML` for hardware acceleration via Windows ML. -- **macOS / Linux**: Uses `Microsoft.AI.Foundry.Local` for cross-platform support. - -Both packages provide the same APIs, so the same source code works on all platforms. +These samples demonstrate how to use the Foundry Local C# SDK. Each sample uses the `Microsoft.AI.Foundry.Local` NuGet package, which bundles the WinML runtime on Windows automatically. ## Samples diff --git a/samples/cs/audio-transcription-example/AudioTranscriptionExample.csproj b/samples/cs/audio-transcription-example/AudioTranscriptionExample.csproj index 12b6962b..68f4199c 100644 --- a/samples/cs/audio-transcription-example/AudioTranscriptionExample.csproj +++ b/samples/cs/audio-transcription-example/AudioTranscriptionExample.csproj @@ -7,13 +7,7 @@ net9.0 - - - - - - - + diff --git a/samples/cs/embeddings/Embeddings.csproj b/samples/cs/embeddings/Embeddings.csproj index 5b67d2a5..d5a4063f 100644 --- a/samples/cs/embeddings/Embeddings.csproj +++ b/samples/cs/embeddings/Embeddings.csproj @@ -7,13 +7,7 @@ net9.0 - - - - - - - + diff --git a/samples/cs/foundry-local-web-server/FoundryLocalWebServer.csproj b/samples/cs/foundry-local-web-server/FoundryLocalWebServer.csproj index 9a6cd1bc..1005ed07 100644 --- a/samples/cs/foundry-local-web-server/FoundryLocalWebServer.csproj +++ b/samples/cs/foundry-local-web-server/FoundryLocalWebServer.csproj @@ -7,13 +7,7 @@ net9.0 - - - - - - - + diff --git a/samples/cs/live-audio-transcription/LiveAudioTranscriptionExample.csproj b/samples/cs/live-audio-transcription/LiveAudioTranscriptionExample.csproj index 6289e861..42750f2b 100644 --- a/samples/cs/live-audio-transcription/LiveAudioTranscriptionExample.csproj +++ b/samples/cs/live-audio-transcription/LiveAudioTranscriptionExample.csproj @@ -7,13 +7,7 @@ net9.0 - - - - - - - + diff --git a/samples/cs/model-management-example/ModelManagementExample.csproj b/samples/cs/model-management-example/ModelManagementExample.csproj index 5b67d2a5..d5a4063f 100644 --- a/samples/cs/model-management-example/ModelManagementExample.csproj +++ b/samples/cs/model-management-example/ModelManagementExample.csproj @@ -7,13 +7,7 @@ net9.0 - - - - - - - + diff --git a/samples/cs/native-chat-completions/NativeChatCompletions.csproj b/samples/cs/native-chat-completions/NativeChatCompletions.csproj index 5b67d2a5..d5a4063f 100644 --- a/samples/cs/native-chat-completions/NativeChatCompletions.csproj +++ b/samples/cs/native-chat-completions/NativeChatCompletions.csproj @@ -7,13 +7,7 @@ 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 5b67d2a5..d5a4063f 100644 --- a/samples/cs/tool-calling-foundry-local-sdk/ToolCallingFoundryLocalSdk.csproj +++ b/samples/cs/tool-calling-foundry-local-sdk/ToolCallingFoundryLocalSdk.csproj @@ -7,13 +7,7 @@ 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 9a6cd1bc..1005ed07 100644 --- a/samples/cs/tool-calling-foundry-local-web-server/ToolCallingFoundryLocalWebServer.csproj +++ b/samples/cs/tool-calling-foundry-local-web-server/ToolCallingFoundryLocalWebServer.csproj @@ -7,13 +7,7 @@ net9.0 - - - - - - - + diff --git a/samples/cs/tutorial-chat-assistant/TutorialChatAssistant.csproj b/samples/cs/tutorial-chat-assistant/TutorialChatAssistant.csproj index 20467e09..972ec621 100644 --- a/samples/cs/tutorial-chat-assistant/TutorialChatAssistant.csproj +++ b/samples/cs/tutorial-chat-assistant/TutorialChatAssistant.csproj @@ -7,13 +7,7 @@ net9.0 - - - - - - - + diff --git a/samples/cs/tutorial-document-summarizer/TutorialDocumentSummarizer.csproj b/samples/cs/tutorial-document-summarizer/TutorialDocumentSummarizer.csproj index 20467e09..972ec621 100644 --- a/samples/cs/tutorial-document-summarizer/TutorialDocumentSummarizer.csproj +++ b/samples/cs/tutorial-document-summarizer/TutorialDocumentSummarizer.csproj @@ -7,13 +7,7 @@ net9.0 - - - - - - - + diff --git a/samples/cs/tutorial-tool-calling/TutorialToolCalling.csproj b/samples/cs/tutorial-tool-calling/TutorialToolCalling.csproj index 20467e09..972ec621 100644 --- a/samples/cs/tutorial-tool-calling/TutorialToolCalling.csproj +++ b/samples/cs/tutorial-tool-calling/TutorialToolCalling.csproj @@ -7,13 +7,7 @@ net9.0 - - - - - - - + diff --git a/samples/cs/tutorial-voice-to-text/TutorialVoiceToText.csproj b/samples/cs/tutorial-voice-to-text/TutorialVoiceToText.csproj index 20467e09..972ec621 100644 --- a/samples/cs/tutorial-voice-to-text/TutorialVoiceToText.csproj +++ b/samples/cs/tutorial-voice-to-text/TutorialVoiceToText.csproj @@ -7,13 +7,7 @@ net9.0 - - - - - - - + diff --git a/samples/cs/verify-winml/README.md b/samples/cs/verify-winml/README.md index 88540fbc..4a0ff47b 100644 --- a/samples/cs/verify-winml/README.md +++ b/samples/cs/verify-winml/README.md @@ -11,10 +11,10 @@ EP-backed model variants and finishes with one native streaming chat check. ## Build & Run -This sample uses the `Microsoft.AI.Foundry.Local.WinML` SDK package selected by +This sample uses the `Microsoft.AI.Foundry.Local` SDK package selected by the shared central package versions. The SDK package owns its native -`Microsoft.AI.Foundry.Local.Core.WinML` dependency, so it restores the matching -Core package transitively. +WinML runtime dependency on Windows, so it restores the matching +runtime package transitively. ```bash dotnet run diff --git a/samples/cs/verify-winml/VerifyWinML.csproj b/samples/cs/verify-winml/VerifyWinML.csproj index 860aa674..dc4f1df1 100644 --- a/samples/cs/verify-winml/VerifyWinML.csproj +++ b/samples/cs/verify-winml/VerifyWinML.csproj @@ -7,15 +7,7 @@ net9.0 - - $(NETCoreSdkRuntimeIdentifier) - - - - - - - + From 85ef8629e32830718310a09b033a214921c2ff17 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Wed, 17 Jun 2026 18:38:15 -0500 Subject: [PATCH 08/13] docs(v2): describe the unified package instead of WinML/non-WinML flavors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The single canonical package now bundles WinML on Windows, so the docs and sample manifests no longer describe a separate WinML SKU/wheel/npm package or the UseWinML / --winml / FL_PYTHON_PACKAGE_NAME switches: - README, sdk_v2 cs/python READMEs, DEVELOPMENT.md, CppPortGuide.md, EpDetectionPlan.md: install one package; WinML acceleration is built in on Windows. - sdk_v2-pipeline-plan.md: collapse the variant stages/artifacts/graph to one flavor; correct decision 10 — the Windows native stage now stages the delay-loaded Microsoft.Windows.AI.MachineLearning.dll into the single nupkg. - samples (js package.json, python requirements.txt): reference the single foundry-local-sdk package; drop the optional/platform-split winml pins. - samples-integration-test.yml: drop the now-dead WinML pack step (the samples no longer reference the .WinML package — this was the NU1202 failure source). - build_and_test_all.ps1 / pyproject.toml: drop the dead FL_PYTHON_PACKAGE_NAME plumbing; the PEP 517 backend now only rewrites ORT pins. - memories/repo/cs-local-packages.md: one pack command, one package. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../workflows/samples-integration-test.yml | 11 -- .pipelines/v2/sdk_v2-pipeline-plan.md | 108 +++++++++--------- README.md | 8 -- memories/repo/cs-local-packages.md | 4 +- samples/js/README.md | 2 +- .../audio-transcription-example/package.json | 3 - .../chat-and-audio-foundry-local/package.json | 3 - .../js/copilot-sdk-foundry-local/package.json | 3 - .../js/electron-chat-application/package.json | 3 - samples/js/embeddings/package.json | 3 - .../package.json | 3 - .../js/native-chat-completions/package.json | 3 - .../tool-calling-foundry-local/package.json | 3 - .../js/tutorial-chat-assistant/package.json | 3 - .../tutorial-document-summarizer/package.json | 3 - samples/js/tutorial-tool-calling/package.json | 3 - .../js/tutorial-voice-to-text/package.json | 3 - samples/js/web-server-example/package.json | 3 - samples/python/README.md | 2 +- .../audio-transcription/requirements.txt | 3 +- samples/python/embeddings/requirements.txt | 3 +- .../langchain-integration/requirements.txt | 3 +- .../live-audio-transcription/requirements.txt | 3 +- .../native-chat-completions/requirements.txt | 3 +- samples/python/tool-calling/requirements.txt | 3 +- .../tutorial-chat-assistant/requirements.txt | 3 +- .../requirements.txt | 3 +- .../tutorial-tool-calling/requirements.txt | 3 +- .../tutorial-voice-to-text/requirements.txt | 3 +- samples/python/verify-winml/README.md | 4 +- samples/python/verify-winml/requirements.txt | 2 +- samples/python/web-server-responses/README.md | 3 +- .../web-server-responses/requirements.txt | 3 +- samples/python/web-server/requirements.txt | 3 +- sdk_v2/DEVELOPMENT.md | 15 +-- sdk_v2/build_and_test_all.ps1 | 2 - sdk_v2/cpp/docs/CppPortGuide.md | 1 - sdk_v2/cpp/docs/EpDetectionPlan.md | 2 +- sdk_v2/cs/README.md | 16 +-- sdk_v2/python/README.md | 26 +---- sdk_v2/python/pyproject.toml | 5 +- 41 files changed, 84 insertions(+), 202 deletions(-) diff --git a/.github/workflows/samples-integration-test.yml b/.github/workflows/samples-integration-test.yml index 7ca9192f..574a055d 100644 --- a/.github/workflows/samples-integration-test.yml +++ b/.github/workflows/samples-integration-test.yml @@ -200,17 +200,6 @@ jobs: /p:TreatWarningsAsErrors=false ` --configuration Release - # Build WinML SDK package (Windows only) - if ($IsWindows) { - dotnet pack sdk/cs/src/Microsoft.AI.Foundry.Local.csproj ` - -o local-packages ` - /p:Version=0.9.0-dev-20260324 ` - /p:UseWinML=true ` - /p:IsPacking=true ` - /p:TreatWarningsAsErrors=false ` - --configuration Release - } - Write-Host "Local packages:" Get-ChildItem local-packages/*.nupkg | ForEach-Object { Write-Host " $($_.Name)" } diff --git a/.pipelines/v2/sdk_v2-pipeline-plan.md b/.pipelines/v2/sdk_v2-pipeline-plan.md index 243a3503..744b92ca 100644 --- a/.pipelines/v2/sdk_v2-pipeline-plan.md +++ b/.pipelines/v2/sdk_v2-pipeline-plan.md @@ -39,9 +39,10 @@ are gated separately via `.pipelines/v1/templates/stages-sdk-v1.yml`. `/p:FoundryLocalRuntimeVersion=$(sdkVersion)` where `sdkVersion` is the same string used to pack the Runtime nupkg. SDK and Runtime always ship as a matched pair. -2. **C# WinML SKU.** `UseWinML=true` flips the `PackageReference` to - `Microsoft.AI.Foundry.Local.Runtime.WinML`. The csproj has two - `UseWinML`-conditional blocks (one for `.Runtime`, one for `.Runtime.WinML`). +2. **Single C# package bundles WinML.** The SDK csproj references one + `Microsoft.AI.Foundry.Local.Runtime` package, which carries the reg-free + WinML 2.x runtime on Windows. There is no separate WinML SKU or + `UseWinML` switch. 3. **Python SDK wheel bundles `foundry_local` directly** at `_native//foundry_local.{ext}`. No separate `foundry-local-runtime` wheel. Trade-off accepted: SDK and runtime upgrade together. Non-SDK @@ -62,11 +63,11 @@ are gated separately via `.pipelines/v1/templates/stages-sdk-v1.yml`. per-track files (`sdkVersion.txt`, `pyVersion.txt`, plus `*.v1.txt` counterparts). Both the sdk_v2 and sdk_v1 coordinators read from this artifact rather than recomputing a timestamp. -7. **Python WinML wheel name via custom PEP 517 backend.** The build - backend at `sdk_v2/python/_build_backend/__init__.py` wraps - `setuptools.build_meta` and rewrites the project name to - `foundry-local-sdk-winml` when `FL_PYTHON_PACKAGE_NAME` is set in the - environment. The same backend also handles ORT pin rewriting (decision 8). +7. **Single Python wheel name; custom PEP 517 backend rewrites ORT pins.** + One wheel, `foundry-local-sdk`, bundles the WinML runtime on Windows. The + build backend at `sdk_v2/python/_build_backend/__init__.py` wraps + `setuptools.build_meta` solely to rewrite the ORT/GenAI pins (decision 8); + it no longer rewrites the project name. 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": "..." } }`. @@ -83,7 +84,7 @@ are gated separately via `.pipelines/v1/templates/stages-sdk-v1.yml`. Bumping ORT/GenAI is a one-file edit per variant. 9. **ORT/GenAI come from public PyPI.** No private feed plumbing required for the wheel install path: - - `onnxruntime-core` (Windows/macOS, standard + WinML variants) + - `onnxruntime-core` (Windows/macOS) - `onnxruntime-genai-core` (Windows/macOS) - `onnxruntime-gpu` / `onnxruntime-genai-cuda` (Linux) @@ -97,15 +98,21 @@ are gated separately via `.pipelines/v1/templates/stages-sdk-v1.yml`. copy the artifact verbatim — they do not re-filter. This keeps the "what ships next to `foundry_local`" decision in exactly one place. - Staging includes all `*.dll`/`*.pdb` (Windows), `*.so`/`*.so.*`/`lib*.so*` - (Linux), `*.dylib` (macOS) from the build output bin directory and - excludes: - - ORT/GenAI (`onnxruntime*`, `Microsoft.Windows.AI.MachineLearning.*`) — - provided by pip on the Python side and by NuGet on the C# side. - - Test/example binaries (`*_tests.*`, `*_example.*`, `gtest*`, - `cmake_test_discovery_*`). - - The step fails loudly if `foundry_local` itself is missing. + Each staging step copies an explicit allow-list, not a glob: just the + redistributable `foundry_local` library (`.dll` + `.pdb` + `.lib` on + Windows, `libfoundry_local.so` / `.dylib` elsewhere). vcpkg statically + links the rest of the closure (ORT/GenAI/azure-*/curl/ssl/zlib/spdlog/fmt) + into `foundry_local` itself, so nothing else needs to travel. ORT/GenAI + are resolved separately at runtime — from pip on the Python side and from + NuGet on the C# side. + + On Windows the allow-list also includes the delay-loaded + `Microsoft.Windows.AI.MachineLearning.dll` (the reg-free WinML 2.x + runtime, ~922 KB, Microsoft-signed). It is the single payload difference + that used to distinguish the WinML SKU; bundling it unconditionally is + what lets one package serve every consumer. + + Each step fails loudly if its primary library is missing. 11. **Python runtime ORT discovery.** `lib_loader.py::prepare_native_dependencies()` bridges between the in-wheel `foundry_local` and the pip-installed ORT packages: @@ -120,7 +127,7 @@ are gated separately via `.pipelines/v1/templates/stages-sdk-v1.yml`. 12. **`foundry-local-install` CLI.** Declared via `[project.scripts]` in `pyproject.toml`, backed by `sdk_v2/python/src/foundry_local_sdk/_native/installer.py`. Flags: - `--winml`, `--verbose`. Verifies installed packages via + `--verbose`. Verifies installed packages via `importlib.util.find_spec` to avoid triggering DLL load during verification. @@ -130,18 +137,18 @@ are gated separately via `.pipelines/v1/templates/stages-sdk-v1.yml`. .pipelines/v2/ └── templates/ ├── stages-sdk-v2.yml # Coordinator: native + cs + python - ├── stages-build-native.yml # 6 build stages + 2 pack stages (C++) - ├── stages-cs.yml # C# build + test (variant: base | winml) - ├── stages-python.yml # Python build + test (variant: base | winml) + ├── stages-build-native.yml # 4 build stages + 1 pack stage (C++) + ├── stages-cs.yml # C# build + test + ├── stages-python.yml # Python build + test ├── steps-prefetch-nuget.yml # ORT/GenAI/WinML NuGet pre-fetch (pwsh + bash) - ├── steps-build-windows.yml # arch: x64 | arm64; useWinml: true | false + ├── steps-build-windows.yml # arch: x64 | arm64 (always bundles WinML) ├── steps-build-linux.yml ├── steps-build-macos.yml ├── steps-build-cs.yml # restore + build + ESRP-sign + pack + ESRP-sign nupkg ├── steps-test-cs.yml # restore + build + run tests ├── steps-build-python.yml # pass-through copy + python -m build --wheel ├── steps-test-python.yml # install wheel + pytest - └── steps-pack-nuget.yml # Runs sdk_v2/cpp/nuget/pack.py (variant: base | winml) + └── steps-pack-nuget.yml # Runs sdk_v2/cpp/nuget/pack.py ``` The repo-shared `.pipelines/templates/checkout-steps.yml` is reused for @@ -155,32 +162,25 @@ compute_version | |-- cpp_build_win_x64 ----------+ |-- cpp_build_win_arm64 --------| - |-- cpp_build_linux_x64 --------+--> pack_nuget --+--> cs_build_base --+--> cs_test_win_x64 - |-- cpp_build_osx_arm64 --------+ | |--> cs_test_linux_x64 - | | +--> cs_test_osx_arm64 - | +--> (cs-sdk-v2-base artifact) - | - | +--> py_build_base_win_x64 --> py_test_base_win_x64 - +-->|--> py_build_base_linux_x64 --> py_test_base_linux_x64 - | +--> py_build_base_osx_arm64 --> py_test_base_osx_arm64 + |-- cpp_build_linux_x64 --------+--> pack_nuget --+--> cs_build --+--> cs_test_win_x64 + |-- cpp_build_osx_arm64 --------+ | |--> cs_test_linux_x64 + | | +--> cs_test_osx_arm64 + | +--> (cs-sdk-v2 artifact) | - |-- cpp_build_win_x64_winml ---+ - |-- cpp_build_win_arm64_winml -+--> pack_nuget_winml --> cs_build_winml --> cs_test_win_x64_winml - | | - | +--> py_build_winml_win_x64 --> py_test_winml_win_x64 - | py_build_winml_win_arm64 (no test — cross-compile) + | +--> py_build_win_x64 --> py_test_win_x64 + +-->|--> py_build_linux_x64 --> py_test_linux_x64 + +--> py_build_osx_arm64 --> py_test_osx_arm64 ``` * All build stages are independent (`dependsOn: [compute_version]`) and run in parallel. -* Both pack stages run on every build (PR and `main`). -* 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 pack stage runs on every build (PR and `main`). +* The Windows native build always bundles the reg-free WinML 2.x runtime, + which links against the same `ortVersion` as every other platform — there + is no separate WinML-aligned ORT pin or build flavor. +* Tests run on `cpp_build_win_x64`, `cpp_build_linux_x64`, and + `cpp_build_osx_arm64`. The ARM64 Windows stage cross-compiles from an x64 + host and skips tests. ## Per-stage artifacts @@ -189,15 +189,12 @@ Published via 1ES `templateContext.outputs` (no manual `PublishPipelineArtifact` | Stage | Artifact name | Contents | |-----------------------------|--------------------------------|------------------------------------------------------------| | `compute_version` | `version-info` | `sdkVersion.txt`, `pyVersion.txt`, `flcVersion.txt` | -| `cpp_build_win_x64` | `cpp-native-win-x64` | `foundry_local.dll`, `foundry_local.pdb` + vcpkg closure | +| `cpp_build_win_x64` | `cpp-native-win-x64` | `foundry_local.dll`, `.pdb`, `.lib` + `Microsoft.Windows.AI.MachineLearning.dll` | | `cpp_build_win_x64` | `cpp-native-include` | Public headers (sourced once, from win-x64) | -| `cpp_build_win_arm64` | `cpp-native-win-arm64` | `foundry_local.dll`, `foundry_local.pdb` + vcpkg closure | -| `cpp_build_linux_x64` | `cpp-native-linux-x64` | `libfoundry_local.so` + vcpkg closure | -| `cpp_build_osx_arm64` | `cpp-native-osx-arm64` | `libfoundry_local.dylib` + vcpkg closure | -| `cpp_build_win_x64_winml` | `cpp-native-win-x64-winml` | `foundry_local.dll`, `foundry_local.pdb` (WinML) | -| `cpp_build_win_arm64_winml` | `cpp-native-win-arm64-winml` | `foundry_local.dll`, `foundry_local.pdb` (WinML) | -| `cpp_pack_nuget` | `cpp-nuget` | `Microsoft.AI.Foundry.Local.Runtime..nupkg` | -| `cpp_pack_nuget_winml` | `cpp-nuget-winml` | `Microsoft.AI.Foundry.Local.Runtime.WinML..nupkg` | +| `cpp_build_win_arm64` | `cpp-native-win-arm64` | `foundry_local.dll`, `.pdb`, `.lib` + `Microsoft.Windows.AI.MachineLearning.dll` | +| `cpp_build_linux_x64` | `cpp-native-linux-x64` | `libfoundry_local.so` | +| `cpp_build_osx_arm64` | `cpp-native-osx-arm64` | `libfoundry_local.dylib` | +| `cpp_pack_nuget` | `cpp-nuget` | `Microsoft.AI.Foundry.Local.Runtime..nupkg` (bundles WinML on Windows) | ## Versioning @@ -278,13 +275,12 @@ set the env var. SDK *build* stages do not need test-data-shared. Test data is fetched on every stage that runs tests: * `cpp_build_win_x64` -* `cpp_build_win_x64_winml` * `cpp_build_linux_x64` * `cpp_build_osx_arm64` * All `cs_test_*` and `py_test_*` stages -Skipped on `cpp_build_win_arm64` and `cpp_build_win_arm64_winml` -(cross-compile, no test execution on the host). +Skipped on `cpp_build_win_arm64` (cross-compile, no test execution on the +host). ## Build commands diff --git a/README.md b/README.md index 405f45d9..b86e183c 100644 --- a/README.md +++ b/README.md @@ -48,10 +48,6 @@ User data never leaves the device, responses start immediately with zero network 1. Install the SDK: ```bash - # Windows (recommended for hardware acceleration) - npm install foundry-local-sdk-winml - - # macOS/linux npm install foundry-local-sdk ``` @@ -90,10 +86,6 @@ User data never leaves the device, responses start immediately with zero network 1. Install the SDK: ```bash - # Windows (recommended for hardware acceleration) - pip install foundry-local-sdk-winml - - # macOS/Linux pip install foundry-local-sdk ``` diff --git a/memories/repo/cs-local-packages.md b/memories/repo/cs-local-packages.md index 2851a696..7285ecc5 100644 --- a/memories/repo/cs-local-packages.md +++ b/memories/repo/cs-local-packages.md @@ -14,11 +14,9 @@ From `sdk_v2/cs/`: ``` dotnet pack src/Microsoft.AI.Foundry.Local.csproj -o ../../local-packages /p:IsPacking=true /p:TreatWarningsAsErrors=false -c Release -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`; 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. +- The single `Microsoft.AI.Foundry.Local` package targets `net8.0;net9.0;netstandard2.0` and bundles the WinML 2.x runtime on Windows automatically — there is no separate WinML SKU to pack. ## Full clean-rebuild ``` diff --git a/samples/js/README.md b/samples/js/README.md index 0b7d677c..98ee6b49 100644 --- a/samples/js/README.md +++ b/samples/js/README.md @@ -48,4 +48,4 @@ These samples demonstrate how to use the Foundry Local JavaScript SDK (`foundry- ``` > [!TIP] -> Each sample's `package.json` includes `foundry-local-sdk` as a dependency and `foundry-local-sdk-winml` as an optional dependency. On **Windows**, the WinML variant installs automatically for broader hardware acceleration. On **macOS and Linux**, the standard SDK is used. Just run `npm install` — platform detection is handled for you. +> Each sample's `package.json` includes the single `foundry-local-sdk` package. On **Windows**, it bundles WinML hardware acceleration automatically. Just run `npm install` — no separate package or flag is needed. diff --git a/samples/js/audio-transcription-example/package.json b/samples/js/audio-transcription-example/package.json index 14a2aafa..4bcaa8d1 100644 --- a/samples/js/audio-transcription-example/package.json +++ b/samples/js/audio-transcription-example/package.json @@ -8,8 +8,5 @@ }, "dependencies": { "foundry-local-sdk": "latest" - }, - "optionalDependencies": { - "foundry-local-sdk-winml": "latest" } } diff --git a/samples/js/chat-and-audio-foundry-local/package.json b/samples/js/chat-and-audio-foundry-local/package.json index 7404589e..700775a6 100644 --- a/samples/js/chat-and-audio-foundry-local/package.json +++ b/samples/js/chat-and-audio-foundry-local/package.json @@ -8,8 +8,5 @@ }, "dependencies": { "foundry-local-sdk": "latest" - }, - "optionalDependencies": { - "foundry-local-sdk-winml": "latest" } } diff --git a/samples/js/copilot-sdk-foundry-local/package.json b/samples/js/copilot-sdk-foundry-local/package.json index b2457d9a..d01a25a9 100644 --- a/samples/js/copilot-sdk-foundry-local/package.json +++ b/samples/js/copilot-sdk-foundry-local/package.json @@ -12,9 +12,6 @@ "foundry-local-sdk": "latest", "zod": "^3.0.0" }, - "optionalDependencies": { - "foundry-local-sdk-winml": "latest" - }, "devDependencies": { "tsx": "^4.0.0", "typescript": "^5.0.0" diff --git a/samples/js/electron-chat-application/package.json b/samples/js/electron-chat-application/package.json index 3609b2ee..4c2461ea 100644 --- a/samples/js/electron-chat-application/package.json +++ b/samples/js/electron-chat-application/package.json @@ -12,9 +12,6 @@ "highlight.js": "^11.11.1", "marked": "^15.0.6" }, - "optionalDependencies": { - "foundry-local-sdk-winml": "latest" - }, "devDependencies": { "electron": "^42.3.3" } diff --git a/samples/js/embeddings/package.json b/samples/js/embeddings/package.json index 8353cb65..e0215fa2 100644 --- a/samples/js/embeddings/package.json +++ b/samples/js/embeddings/package.json @@ -8,8 +8,5 @@ }, "dependencies": { "foundry-local-sdk": "latest" - }, - "optionalDependencies": { - "foundry-local-sdk-winml": "latest" } } diff --git a/samples/js/langchain-integration-example/package.json b/samples/js/langchain-integration-example/package.json index bb5fb635..4511cdf3 100644 --- a/samples/js/langchain-integration-example/package.json +++ b/samples/js/langchain-integration-example/package.json @@ -10,8 +10,5 @@ "@langchain/core": "latest", "@langchain/openai": "latest", "foundry-local-sdk": "latest" - }, - "optionalDependencies": { - "foundry-local-sdk-winml": "latest" } } diff --git a/samples/js/native-chat-completions/package.json b/samples/js/native-chat-completions/package.json index eeba0acd..5a3aa0cc 100644 --- a/samples/js/native-chat-completions/package.json +++ b/samples/js/native-chat-completions/package.json @@ -8,8 +8,5 @@ }, "dependencies": { "foundry-local-sdk": "latest" - }, - "optionalDependencies": { - "foundry-local-sdk-winml": "latest" } } diff --git a/samples/js/tool-calling-foundry-local/package.json b/samples/js/tool-calling-foundry-local/package.json index 6ae9c032..d31258b3 100644 --- a/samples/js/tool-calling-foundry-local/package.json +++ b/samples/js/tool-calling-foundry-local/package.json @@ -8,8 +8,5 @@ "dependencies": { "foundry-local-sdk": "latest", "openai": "^6.25.0" - }, - "optionalDependencies": { - "foundry-local-sdk-winml": "latest" } } diff --git a/samples/js/tutorial-chat-assistant/package.json b/samples/js/tutorial-chat-assistant/package.json index 8a36a288..2fcb60f6 100644 --- a/samples/js/tutorial-chat-assistant/package.json +++ b/samples/js/tutorial-chat-assistant/package.json @@ -8,8 +8,5 @@ }, "dependencies": { "foundry-local-sdk": "latest" - }, - "optionalDependencies": { - "foundry-local-sdk-winml": "latest" } } diff --git a/samples/js/tutorial-document-summarizer/package.json b/samples/js/tutorial-document-summarizer/package.json index c97e416f..af370c4a 100644 --- a/samples/js/tutorial-document-summarizer/package.json +++ b/samples/js/tutorial-document-summarizer/package.json @@ -8,8 +8,5 @@ }, "dependencies": { "foundry-local-sdk": "latest" - }, - "optionalDependencies": { - "foundry-local-sdk-winml": "latest" } } diff --git a/samples/js/tutorial-tool-calling/package.json b/samples/js/tutorial-tool-calling/package.json index ab7f62d6..ee44785c 100644 --- a/samples/js/tutorial-tool-calling/package.json +++ b/samples/js/tutorial-tool-calling/package.json @@ -8,8 +8,5 @@ }, "dependencies": { "foundry-local-sdk": "latest" - }, - "optionalDependencies": { - "foundry-local-sdk-winml": "latest" } } diff --git a/samples/js/tutorial-voice-to-text/package.json b/samples/js/tutorial-voice-to-text/package.json index 3efb0d4b..19a2d0bf 100644 --- a/samples/js/tutorial-voice-to-text/package.json +++ b/samples/js/tutorial-voice-to-text/package.json @@ -8,8 +8,5 @@ }, "dependencies": { "foundry-local-sdk": "latest" - }, - "optionalDependencies": { - "foundry-local-sdk-winml": "latest" } } diff --git a/samples/js/web-server-example/package.json b/samples/js/web-server-example/package.json index 33670514..f97dee96 100644 --- a/samples/js/web-server-example/package.json +++ b/samples/js/web-server-example/package.json @@ -9,8 +9,5 @@ "dependencies": { "foundry-local-sdk": "latest", "openai": "latest" - }, - "optionalDependencies": { - "foundry-local-sdk-winml": "latest" } } diff --git a/samples/python/README.md b/samples/python/README.md index 49e99c8a..6be2c7c7 100644 --- a/samples/python/README.md +++ b/samples/python/README.md @@ -45,4 +45,4 @@ These samples demonstrate how to use Foundry Local with Python. ``` > [!TIP] -> Each sample's `requirements.txt` uses environment markers to automatically install the right SDK for your platform. On **Windows**, `foundry-local-sdk-winml` is installed for broader hardware acceleration. On **macOS and Linux**, the standard `foundry-local-sdk` is used. Just run `pip install -r requirements.txt` — platform detection is handled for you. +> Each sample's `requirements.txt` installs the single `foundry-local-sdk` package. On **Windows**, it bundles WinML hardware acceleration automatically. Just run `pip install -r requirements.txt` — no separate package or flag is needed. diff --git a/samples/python/audio-transcription/requirements.txt b/samples/python/audio-transcription/requirements.txt index 7602a48b..c79aa6dd 100644 --- a/samples/python/audio-transcription/requirements.txt +++ b/samples/python/audio-transcription/requirements.txt @@ -1,2 +1 @@ -foundry-local-sdk; sys_platform != "win32" -foundry-local-sdk-winml; sys_platform == "win32" +foundry-local-sdk diff --git a/samples/python/embeddings/requirements.txt b/samples/python/embeddings/requirements.txt index 7602a48b..c79aa6dd 100644 --- a/samples/python/embeddings/requirements.txt +++ b/samples/python/embeddings/requirements.txt @@ -1,2 +1 @@ -foundry-local-sdk; sys_platform != "win32" -foundry-local-sdk-winml; sys_platform == "win32" +foundry-local-sdk diff --git a/samples/python/langchain-integration/requirements.txt b/samples/python/langchain-integration/requirements.txt index 9a6b6181..0ded700a 100644 --- a/samples/python/langchain-integration/requirements.txt +++ b/samples/python/langchain-integration/requirements.txt @@ -1,5 +1,4 @@ -foundry-local-sdk; sys_platform != "win32" -foundry-local-sdk-winml; sys_platform == "win32" +foundry-local-sdk openai langchain-openai langchain-core diff --git a/samples/python/live-audio-transcription/requirements.txt b/samples/python/live-audio-transcription/requirements.txt index 6677976f..70244f87 100644 --- a/samples/python/live-audio-transcription/requirements.txt +++ b/samples/python/live-audio-transcription/requirements.txt @@ -1,5 +1,4 @@ -foundry-local-sdk; sys_platform != "win32" -foundry-local-sdk-winml; sys_platform == "win32" +foundry-local-sdk # pyaudio is optional — only needed for live microphone capture. # Install manually: pip install pyaudio # The sample falls back to synthetic audio if pyaudio is unavailable. diff --git a/samples/python/native-chat-completions/requirements.txt b/samples/python/native-chat-completions/requirements.txt index 7602a48b..c79aa6dd 100644 --- a/samples/python/native-chat-completions/requirements.txt +++ b/samples/python/native-chat-completions/requirements.txt @@ -1,2 +1 @@ -foundry-local-sdk; sys_platform != "win32" -foundry-local-sdk-winml; sys_platform == "win32" +foundry-local-sdk diff --git a/samples/python/tool-calling/requirements.txt b/samples/python/tool-calling/requirements.txt index 7602a48b..c79aa6dd 100644 --- a/samples/python/tool-calling/requirements.txt +++ b/samples/python/tool-calling/requirements.txt @@ -1,2 +1 @@ -foundry-local-sdk; sys_platform != "win32" -foundry-local-sdk-winml; sys_platform == "win32" +foundry-local-sdk diff --git a/samples/python/tutorial-chat-assistant/requirements.txt b/samples/python/tutorial-chat-assistant/requirements.txt index 7602a48b..c79aa6dd 100644 --- a/samples/python/tutorial-chat-assistant/requirements.txt +++ b/samples/python/tutorial-chat-assistant/requirements.txt @@ -1,2 +1 @@ -foundry-local-sdk; sys_platform != "win32" -foundry-local-sdk-winml; sys_platform == "win32" +foundry-local-sdk diff --git a/samples/python/tutorial-document-summarizer/requirements.txt b/samples/python/tutorial-document-summarizer/requirements.txt index 7602a48b..c79aa6dd 100644 --- a/samples/python/tutorial-document-summarizer/requirements.txt +++ b/samples/python/tutorial-document-summarizer/requirements.txt @@ -1,2 +1 @@ -foundry-local-sdk; sys_platform != "win32" -foundry-local-sdk-winml; sys_platform == "win32" +foundry-local-sdk diff --git a/samples/python/tutorial-tool-calling/requirements.txt b/samples/python/tutorial-tool-calling/requirements.txt index 7602a48b..c79aa6dd 100644 --- a/samples/python/tutorial-tool-calling/requirements.txt +++ b/samples/python/tutorial-tool-calling/requirements.txt @@ -1,2 +1 @@ -foundry-local-sdk; sys_platform != "win32" -foundry-local-sdk-winml; sys_platform == "win32" +foundry-local-sdk diff --git a/samples/python/tutorial-voice-to-text/requirements.txt b/samples/python/tutorial-voice-to-text/requirements.txt index 7602a48b..c79aa6dd 100644 --- a/samples/python/tutorial-voice-to-text/requirements.txt +++ b/samples/python/tutorial-voice-to-text/requirements.txt @@ -1,2 +1 @@ -foundry-local-sdk; sys_platform != "win32" -foundry-local-sdk-winml; sys_platform == "win32" +foundry-local-sdk diff --git a/samples/python/verify-winml/README.md b/samples/python/verify-winml/README.md index eabfd720..3a7320eb 100644 --- a/samples/python/verify-winml/README.md +++ b/samples/python/verify-winml/README.md @@ -17,8 +17,8 @@ If you want to reuse your existing Python environment instead, delete that environment's `Lib\site-packages\foundry_local_core` directory before reinstalling so stale native files are not left behind. -`requirements.txt` installs the WinML SDK variant, which brings the matching -WinML native package transitively. Either install path is enough: +`requirements.txt` installs the single `foundry-local-sdk` package, which bundles +WinML hardware acceleration on Windows automatically. Either install path is enough: ```bash python -m venv .venv diff --git a/samples/python/verify-winml/requirements.txt b/samples/python/verify-winml/requirements.txt index 481d9dc4..c79aa6dd 100644 --- a/samples/python/verify-winml/requirements.txt +++ b/samples/python/verify-winml/requirements.txt @@ -1 +1 @@ -foundry-local-sdk-winml +foundry-local-sdk diff --git a/samples/python/web-server-responses/README.md b/samples/python/web-server-responses/README.md index 95666d91..9c76df27 100644 --- a/samples/python/web-server-responses/README.md +++ b/samples/python/web-server-responses/README.md @@ -18,8 +18,7 @@ pip install -r requirements.txt That installs: -- `foundry-local-sdk` on non-Windows platforms -- `foundry-local-sdk-winml` on Windows +- `foundry-local-sdk`, which bundles WinML hardware acceleration on Windows automatically - `openai` The sample downloads/registers Foundry Local execution providers and downloads the `qwen2.5-0.5b` model the first time it runs. diff --git a/samples/python/web-server-responses/requirements.txt b/samples/python/web-server-responses/requirements.txt index db870f60..5a0f14ae 100644 --- a/samples/python/web-server-responses/requirements.txt +++ b/samples/python/web-server-responses/requirements.txt @@ -1,3 +1,2 @@ -foundry-local-sdk; sys_platform != "win32" -foundry-local-sdk-winml; sys_platform == "win32" +foundry-local-sdk openai diff --git a/samples/python/web-server/requirements.txt b/samples/python/web-server/requirements.txt index db870f60..5a0f14ae 100644 --- a/samples/python/web-server/requirements.txt +++ b/samples/python/web-server/requirements.txt @@ -1,3 +1,2 @@ -foundry-local-sdk; sys_platform != "win32" -foundry-local-sdk-winml; sys_platform == "win32" +foundry-local-sdk openai diff --git a/sdk_v2/DEVELOPMENT.md b/sdk_v2/DEVELOPMENT.md index bc8afb6c..febd9a8d 100644 --- a/sdk_v2/DEVELOPMENT.md +++ b/sdk_v2/DEVELOPMENT.md @@ -13,7 +13,7 @@ If that passes, your machine is correctly configured. ## Prerequisites All four SDKs (C++, C#, Python, JS/TS) build on **Windows**, **Linux**, and -**macOS**. The WinML variant (`-UseWinml`) is Windows-only. +**macOS**. WinML 2.x hardware acceleration is bundled automatically on Windows. ### All platforms @@ -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 | 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. | +| .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 single package bundles WinML 2.x on Windows — its OS-version floor 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. | @@ -64,8 +64,8 @@ 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`). | -| C# | `dotnet test Microsoft.AI.Foundry.Local.SDK.sln -c Release [-p:UseWinML=true]` — restores NuGet packages on demand. | +| C++ | `python build.py [--config ...] [--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` — 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). | @@ -75,9 +75,6 @@ install per-SDK package dependencies on first run: # Full build + test, default config (RelWithDebInfo / Release). pwsh ./build_and_test_all.ps1 -# WinML variant across all SDKs (Windows only). -pwsh ./build_and_test_all.ps1 -UseWinml - # Just rebuild the native and the JS bindings, skip the slow C++ test pass. pwsh ./build_and_test_all.ps1 -Only cpp,js -SkipCppTests @@ -102,7 +99,3 @@ See `pwsh ./build_and_test_all.ps1 -?` for the full parameter list. or pass `--build_dir` to `build.py`. The C# tests pin an absolute path to `sdk_v2/cpp/build///`; bypassing `build.py` puts the binary somewhere else. -* **Switching between WinML and non-WinML** — the C++ FetchContent NuGet - packages (`Microsoft.ML.OnnxRuntime.Foundry` vs `.WinML`) are cached and - do not auto-refresh on variant flip. If you hit linker errors after - toggling `-UseWinml`, wipe `sdk_v2/cpp/build/` and rerun. diff --git a/sdk_v2/build_and_test_all.ps1 b/sdk_v2/build_and_test_all.ps1 index e08e51ce..3dd01c36 100644 --- a/sdk_v2/build_and_test_all.ps1 +++ b/sdk_v2/build_and_test_all.ps1 @@ -194,7 +194,6 @@ print(sys.executable) $env:Platform = 'x64' } - $env:FL_PYTHON_PACKAGE_NAME = 'foundry-local-sdk' try { python -m pip install -e '.[dev]' if ($LASTEXITCODE -ne 0) { throw "pip install exit $LASTEXITCODE" } @@ -202,7 +201,6 @@ print(sys.executable) python -m pytest test/ -v if ($LASTEXITCODE -ne 0) { throw "pytest exit $LASTEXITCODE" } } finally { - Remove-Item Env:FL_PYTHON_PACKAGE_NAME -ErrorAction SilentlyContinue if ($null -eq $restoreTgt) { Remove-Item Env:VSCMD_ARG_TGT_ARCH -ErrorAction SilentlyContinue } else { $env:VSCMD_ARG_TGT_ARCH = $restoreTgt } if ($null -eq $restoreHost) { Remove-Item Env:VSCMD_ARG_HOST_ARCH -ErrorAction SilentlyContinue } else { $env:VSCMD_ARG_HOST_ARCH = $restoreHost } if ($null -eq $restorePlat) { Remove-Item Env:Platform -ErrorAction SilentlyContinue } else { $env:Platform = $restorePlat } diff --git a/sdk_v2/cpp/docs/CppPortGuide.md b/sdk_v2/cpp/docs/CppPortGuide.md index 5eaac27a..3a350604 100644 --- a/sdk_v2/cpp/docs/CppPortGuide.md +++ b/sdk_v2/cpp/docs/CppPortGuide.md @@ -474,7 +474,6 @@ It ensures all threads are joined during shutdown. The C# equivalent relies on FOUNDRY_LOCAL_BUILD_TESTS=ON # Unit tests (default ON) FOUNDRY_LOCAL_BUILD_EXAMPLES=ON # Example programs (default ON) FOUNDRY_LOCAL_BUILD_SERVICE=ON # Web service (requires oatpp) (default ON) -FOUNDRY_LOCAL_USE_WINML=OFF # WinML support ``` --- diff --git a/sdk_v2/cpp/docs/EpDetectionPlan.md b/sdk_v2/cpp/docs/EpDetectionPlan.md index 935d3c53..26433cbd 100644 --- a/sdk_v2/cpp/docs/EpDetectionPlan.md +++ b/sdk_v2/cpp/docs/EpDetectionPlan.md @@ -141,7 +141,7 @@ STDAPI WinMLEpEnsureReadyAsync(WinMLEpHandle ep, WinMLAsyncBlock* async); - Model catalog headers (we have our own) **CMake integration:** -- `find_package(WinMLEpCatalog)` — gated behind `FOUNDRY_LOCAL_USE_WINML` +- `find_package(WinMLEpCatalog)` — gated behind `if(WIN32)` (always enabled on Windows) - Windows-only. - Linked with `/DELAYLOAD:Microsoft.Windows.AI.MachineLearning.dll` — the DLL may not be present on older systems diff --git a/sdk_v2/cs/README.md b/sdk_v2/cs/README.md index 2d3d5adc..253c6a8a 100644 --- a/sdk_v2/cs/README.md +++ b/sdk_v2/cs/README.md @@ -11,7 +11,7 @@ The Foundry Local C# SDK provides a .NET interface for running AI models locally - **Download progress** — wire up an `Action` callback for real-time download percentage - **Model variants** — select specific hardware/quantization variants per model alias - **Optional web service** — start an OpenAI-compatible REST endpoint (`/v1/chat_completions`, `/v1/models`) -- **WinML acceleration** — opt-in Windows hardware acceleration with automatic EP download +- **WinML acceleration** — built-in Windows hardware acceleration with automatic EP download - **Full async/await** — every operation supports `CancellationToken` and async patterns - **IDisposable** — deterministic cleanup of native resources @@ -32,19 +32,9 @@ Or open [Microsoft.AI.Foundry.Local.SDK.sln](./Microsoft.AI.Foundry.Local.SDK.sl ## WinML: Automatic Hardware Acceleration (Windows) -On Windows, Foundry Local can leverage WinML for GPU/NPU hardware acceleration via ONNX Runtime execution providers (EPs). EPs are large binaries downloaded on first use and cached for subsequent runs. +On Windows, Foundry Local leverages WinML for GPU/NPU hardware acceleration via ONNX Runtime execution providers (EPs). EPs are large binaries downloaded on first use and cached for subsequent runs. -Install the WinML package variant instead: - -```bash -dotnet add package Microsoft.AI.Foundry.Local.WinML -``` - -Or build from source with: - -```bash -dotnet build src/Microsoft.AI.Foundry.Local.csproj /p:UseWinML=true -``` +WinML acceleration is built into the `Microsoft.AI.Foundry.Local` package — it bundles the reg-free WinML 2.x runtime on Windows automatically, with no separate package or build flag required. ### Triggering EP download diff --git a/sdk_v2/python/README.md b/sdk_v2/python/README.md index 3d7b1bbc..262a56c0 100644 --- a/sdk_v2/python/README.md +++ b/sdk_v2/python/README.md @@ -15,22 +15,11 @@ The Foundry Local Python SDK is a native Python binding for the Foundry Local C+ ## Installation -Two package variants are published — choose the one that matches your target hardware: - -| Variant | Package | Native backends | -|---|---|---| -| Standard (cross-platform) | `foundry-local-sdk` | CPU / WebGPU / CUDA | -| WinML (Windows only) | `foundry-local-sdk-winml` | Windows ML + all standard backends | - ```bash -# Standard (cross-platform — Linux, macOS, Windows) pip install foundry-local-sdk - -# WinML (Windows only) -pip install foundry-local-sdk-winml ``` -The wheel ships the Foundry Local native library and pulls the matching ONNX Runtime + ONNX Runtime GenAI runtime packages as dependencies. The two variants are mutually exclusive — install only one per environment. +The wheel ships the Foundry Local native library — bundling the reg-free WinML 2.x runtime on Windows for hardware acceleration — and pulls the matching ONNX Runtime + ONNX Runtime GenAI runtime packages as dependencies. ### Building from source @@ -56,12 +45,7 @@ Then build the wheel: ```bash cd sdk_v2/python - -# Standard wheel python -m build --wheel - -# WinML wheel (uses the build_backend.py shim) -python -m build --wheel -C winml=true ``` For editable installs during development: @@ -72,15 +56,11 @@ pip install -e . ### Installing native runtime dependencies for development / CI -`foundry-local-install` is a convenience wrapper for end-user / CI environments that want the published wheel plus its ORT / ONNX Runtime GenAI runtime packages installed and verified in one step. It runs `pip install --upgrade foundry-local-sdk[-winml]` from PyPI and then probes that `onnxruntime[_core]` and `onnxruntime_genai[_core]` import cleanly. +`foundry-local-install` is a convenience wrapper for end-user / CI environments that want the published wheel plus its ORT / ONNX Runtime GenAI runtime packages installed and verified in one step. It runs `pip install --upgrade foundry-local-sdk` from PyPI and then probes that `onnxruntime[_core]` and `onnxruntime_genai[_core]` import cleanly. ```bash -# Standard foundry-local-install -# WinML (Windows only) -foundry-local-install --winml - # Add --verbose to print resolved binary paths after installation. ``` @@ -425,7 +405,7 @@ Enums: `ItemType`, `TextItemType`, `MessageRole`, `TensorDataType`. | Function | CLI name | Description | |---|---|---| -| `foundry_local_sdk._native.installer.main` | `foundry-local-install` | Install and verify native binaries (`--winml` for the WinML variant, `--verbose` to print resolved paths) | +| `foundry_local_sdk._native.installer.main` | `foundry-local-install` | Install and verify native binaries (`--verbose` to print resolved paths) | ## Running tests diff --git a/sdk_v2/python/pyproject.toml b/sdk_v2/python/pyproject.toml index 6bed13b3..0c5bda0b 100644 --- a/sdk_v2/python/pyproject.toml +++ b/sdk_v2/python/pyproject.toml @@ -1,8 +1,7 @@ [build-system] requires = ["setuptools>=68", "cffi>=1.16", "wheel"] -# Custom backend wraps setuptools.build_meta to honour the -# FL_PYTHON_PACKAGE_NAME env var (used by CI to produce the -# `foundry-local-sdk-winml` variant from the same source tree). +# Custom backend wraps setuptools.build_meta to rewrite the ORT/GenAI +# version pins from sdk_v2/deps_versions.json at wheel-build time. build-backend = "_build_backend" backend-path = ["."] From 8f1d2d895f88c9c695bb2cbf63808cdf69180669 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Wed, 17 Jun 2026 18:45:32 -0500 Subject: [PATCH 09/13] www: show one install command per SDK in the download dropdown With the WinML and non-WinML package flavors merged into a single canonical package per SDK, there is no separate WinML install command to offer. Drop the per-SDK Windows/WinML button (and its winmlId/winmlCommand fields) and the now redundant Cross-platform badge, leaving one install command per SDK. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../lib/components/download-dropdown.svelte | 47 +++---------------- 1 file changed, 6 insertions(+), 41 deletions(-) diff --git a/www/src/lib/components/download-dropdown.svelte b/www/src/lib/components/download-dropdown.svelte index 811ba8ec..ce7a37ef 100644 --- a/www/src/lib/components/download-dropdown.svelte +++ b/www/src/lib/components/download-dropdown.svelte @@ -52,8 +52,6 @@ icon: string; crossPlatformId: string; crossPlatformCommand: string; - winmlId: string; - winmlCommand: string; }; const cliInstallOptions: CliInstallOption[] = [ @@ -83,36 +81,28 @@ label: 'Python SDK', icon: PythonIcon, crossPlatformId: 'python-cross-platform', - crossPlatformCommand: 'pip install foundry-local-sdk', - winmlId: 'python-winml', - winmlCommand: 'pip install foundry-local-sdk-winml' + crossPlatformCommand: 'pip install foundry-local-sdk' }, { id: 'javascript', label: 'JavaScript SDK', icon: JavaScriptIcon, crossPlatformId: 'javascript-cross-platform', - crossPlatformCommand: 'npm install foundry-local-sdk', - winmlId: 'javascript-winml', - winmlCommand: 'npm install foundry-local-sdk-winml' + crossPlatformCommand: 'npm install foundry-local-sdk' }, { id: 'csharp', label: 'C# SDK', icon: CSharpIcon, crossPlatformId: 'csharp-cross-platform', - crossPlatformCommand: 'dotnet add package Microsoft.AI.Foundry.Local', - winmlId: 'csharp-winml', - winmlCommand: 'dotnet add package Microsoft.AI.Foundry.Local.WinML' + crossPlatformCommand: 'dotnet add package Microsoft.AI.Foundry.Local' }, { id: 'rust', label: 'Rust SDK', icon: RustIcon, crossPlatformId: 'rust-cross-platform', - crossPlatformCommand: 'cargo add foundry-local-sdk', - winmlId: 'rust-winml', - winmlCommand: 'cargo add foundry-local-sdk --features winml' + crossPlatformCommand: 'cargo add foundry-local-sdk' } ]; @@ -163,17 +153,13 @@ {item.label} -
+
- -
{/each} From d1bb2853757dbd4d4b8e49c45f9d27c804017699 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Thu, 18 Jun 2026 15:53:35 -0500 Subject: [PATCH 10/13] fix(js): clean process exit after loading a native Manager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Once a FoundryLocalManager exists, ONNX Runtime's process-wide teardown races with Node's environment teardown on a natural exit and crashes (access violation / heap corruption) — a long-standing ORT-on-exit issue, not specific to this SDK. It reproduces as far back as the first JS SDK commit; the crash is avoided only by releasing the Manager and then leaving via process.exit(), which skips Node's graceful teardown. Track live managers and replicate that automatically: on 'beforeExit' dispose them and exit explicitly; on 'exit' dispose so a direct process.exit() releases the ORT environment before the C runtime tears the ORT libraries down. Natural exit, process.exit(), and dispose()+exit are now all clean, and the JS test workers no longer segfault on teardown. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- sdk_v2/js/src/foundryLocalManager.ts | 38 ++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/sdk_v2/js/src/foundryLocalManager.ts b/sdk_v2/js/src/foundryLocalManager.ts index e3499462..99e350a7 100644 --- a/sdk_v2/js/src/foundryLocalManager.ts +++ b/sdk_v2/js/src/foundryLocalManager.ts @@ -13,6 +13,41 @@ import { } from "./detail/native.js"; import type { EpDownloadResult, EpInfo } from "./types.js"; +// Once a native Manager exists, ONNX Runtime's process-wide teardown races with +// Node's environment teardown on a natural exit and crashes — a long-standing +// ORT-on-exit issue, independent of this SDK. It is avoided only by releasing +// the Manager and then leaving via `process.exit()`, which skips that graceful +// teardown. So track live managers and do exactly that on the way out: dispose +// on `beforeExit` then exit explicitly; an `exit` handler covers callers who +// invoke `process.exit()` themselves (releasing the env before the C runtime +// tears the ORT libraries down keeps their static destructors benign). +const liveManagers = new Set(); +let exitHandlersInstalled = false; + +function disposeLiveManagers(): void { + for (const manager of [...liveManagers]) { + try { + manager.dispose(); + } catch { + // Best-effort: a dispose failure must not block process exit. + } + } +} + +function installExitHandlersOnce(): void { + if (exitHandlersInstalled) { + return; + } + exitHandlersInstalled = true; + process.on("beforeExit", () => { + disposeLiveManagers(); + process.exit(process.exitCode ?? 0); + }); + process.on("exit", () => { + disposeLiveManagers(); + }); +} + export class FoundryLocalManager { readonly #native: NativeManager; #catalog: Catalog | undefined; @@ -58,6 +93,8 @@ export class FoundryLocalManager { } } this.#native = new (getAddon().Manager)(config); + liveManagers.add(this); + installExitHandlersOnce(); } /** @@ -179,6 +216,7 @@ export class FoundryLocalManager { * (and any method on a `Catalog` or `Model` obtained through this manager) throws a `FoundryLocalError`. */ dispose(): void { + liveManagers.delete(this); this.#native.dispose(); this.#catalog = undefined; this.#urls = []; From a1af249690df5619b8fc7bea86faf1edadcc956d Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Fri, 19 Jun 2026 11:07:27 -0500 Subject: [PATCH 11/13] samples(cs): migrate the vision sample to the unified package The #821 web-server vision sample was added with the old WinML/non-WinML flavor split (net9.0-windows10.0.18362.0 + Microsoft.AI.Foundry.Local.WinML on Windows, base package elsewhere). Collapse it to the unified pattern used by every other C# sample: a single net9.0 target and a single central-managed Microsoft.AI.Foundry.Local PackageReference. Builds against the unified package alongside all 14 C# samples. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...oundryLocalWebServerResponsesVision.csproj | 24 +------------------ 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/samples/cs/foundry-local-web-server-responses-vision/FoundryLocalWebServerResponsesVision.csproj b/samples/cs/foundry-local-web-server-responses-vision/FoundryLocalWebServerResponsesVision.csproj index 06e29a5d..ead8de10 100644 --- a/samples/cs/foundry-local-web-server-responses-vision/FoundryLocalWebServerResponsesVision.csproj +++ b/samples/cs/foundry-local-web-server-responses-vision/FoundryLocalWebServerResponsesVision.csproj @@ -4,32 +4,10 @@ Exe enable enable - - - - - net9.0-windows10.0.18362.0 - ARM64;x64 - None - false - - - - net9.0 - - $(NETCoreSdkRuntimeIdentifier) - - - - - - - - - + From a39daab5baa8ca89e639fcc9ee40ed3f74be29a6 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Fri, 19 Jun 2026 12:10:49 -0500 Subject: [PATCH 12/13] sdk_v2: scrub vestigial WinML/non-WinML separation from comments, docs, backend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The flavor split is gone from sdk_v2 code, but stale references to it lingered in comments and docs. Reword them to describe the unified reality — WinML is always on on Windows, so the distinction is platform, not flavor: - cpp/CMakeLists.txt, cpp/test/CMakeLists.txt, js/script/copy-native.mjs: "no WinML SKU" / "WinML build" / "non-WinML build" -> platform wording. - cpp/cmake/FindOnnxRuntime.cmake: drop the now-vacuous "for both flavors". - .pipelines/v2: steps-build-windows.yml (Standard-vs-WinML build split -> one Windows build), steps-test-python.yml ("Both variants" -> per-job wheel), and the plan docs ("per variant"; deferred "WinML variant" bullet). - build_and_test_all.ps1: tighten the net9.0/net462 test comment. Also simplify python/_build_backend: with deps_versions_winml.json gone there is a single deps file, so drop the _STD suffix and the deps_file parameter threaded through _read_versions/_patch_pyproject_text. Verified it still rewrites all four ORT/GenAI pins from deps_versions.json. sdk_v2-only; the V1 sdk/ + .pipelines/v1 generation keeps its flavor split. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .pipelines/v2/sdk_v2-js-pipeline-plan.md | 1 - .pipelines/v2/sdk_v2-pipeline-plan.md | 2 +- .../v2/templates/steps-build-windows.yml | 20 ++++++------- .pipelines/v2/templates/steps-test-python.yml | 4 +-- sdk_v2/build_and_test_all.ps1 | 6 ++-- sdk_v2/cpp/CMakeLists.txt | 8 ++--- sdk_v2/cpp/cmake/FindOnnxRuntime.cmake | 2 +- sdk_v2/cpp/test/CMakeLists.txt | 2 +- sdk_v2/js/script/copy-native.mjs | 4 +-- sdk_v2/python/_build_backend/__init__.py | 29 +++++++++---------- 10 files changed, 36 insertions(+), 42 deletions(-) diff --git a/.pipelines/v2/sdk_v2-js-pipeline-plan.md b/.pipelines/v2/sdk_v2-js-pipeline-plan.md index fd149a9c..ec5d2ebd 100644 --- a/.pipelines/v2/sdk_v2-js-pipeline-plan.md +++ b/.pipelines/v2/sdk_v2-js-pipeline-plan.md @@ -19,7 +19,6 @@ Out of scope (deferred): - **Linux ARM64.** Cross-cutting ARM64 work item will add this for C++, C#, Python, and JS together. -- **WinML variant.** JS is scoped out by the base plan. - **npm publishing.** Pipeline produces the `.tgz` as an artifact only. ## Supported platforms diff --git a/.pipelines/v2/sdk_v2-pipeline-plan.md b/.pipelines/v2/sdk_v2-pipeline-plan.md index 744b92ca..a6bad8c9 100644 --- a/.pipelines/v2/sdk_v2-pipeline-plan.md +++ b/.pipelines/v2/sdk_v2-pipeline-plan.md @@ -81,7 +81,7 @@ are gated separately via `.pipelines/v1/templates/stages-sdk-v1.yml`. at wheel-build time. If the backend is ever bypassed, `pip install` fails fast with "no matching version" (intentional loud failure). - Bumping ORT/GenAI is a one-file edit per variant. + Bumping ORT/GenAI is a one-file edit. 9. **ORT/GenAI come from public PyPI.** No private feed plumbing required for the wheel install path: - `onnxruntime-core` (Windows/macOS) diff --git a/.pipelines/v2/templates/steps-build-windows.yml b/.pipelines/v2/templates/steps-build-windows.yml index 66b4624d..cc439f54 100644 --- a/.pipelines/v2/templates/steps-build-windows.yml +++ b/.pipelines/v2/templates/steps-build-windows.yml @@ -115,17 +115,15 @@ steps: VCPKG_ROOT: $(Build.BinariesDirectory)\vcpkg FOUNDRY_TEST_DATA_DIR: $(Agent.BuildDirectory)\test-data-shared -# Stage the redistributable native artifacts. -# - Standard build: vcpkg statically links ORT/GenAI/azure-*/spdlog/fmt/ -# 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 runtime dependency, -# 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. +# Stage the redistributable native artifacts. vcpkg statically links +# ORT/GenAI/azure-*/spdlog/fmt/libcurl/libssl/zlib/brotli* into foundry_local.dll +# (see sdk_v2/cpp/triplets/x64-windows.cmake), so foundry_local.dll carries that +# payload itself. It also picks up one extra runtime dependency, +# 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. - task: PowerShell@2 displayName: 'Stage native artifacts' inputs: diff --git a/.pipelines/v2/templates/steps-test-python.yml b/.pipelines/v2/templates/steps-test-python.yml index 2bf2b303..5dfd4bab 100644 --- a/.pipelines/v2/templates/steps-test-python.yml +++ b/.pipelines/v2/templates/steps-test-python.yml @@ -53,8 +53,8 @@ steps: targetType: inline pwsh: true script: | - # Locate the wheel. Both variants land in their own per-job artifact - # so a single match is expected. + # Locate the wheel. Each job's wheel lands in its own artifact, so a + # single match is expected. $wheels = @(Get-ChildItem "${{ parameters.wheelDir }}" -Recurse -Filter '*.whl') if ($wheels.Count -eq 0) { throw "No .whl found under ${{ parameters.wheelDir }}" } if ($wheels.Count -gt 1) { diff --git a/sdk_v2/build_and_test_all.ps1 b/sdk_v2/build_and_test_all.ps1 index 3dd01c36..cb44da79 100644 --- a/sdk_v2/build_and_test_all.ps1 +++ b/sdk_v2/build_and_test_all.ps1 @@ -137,10 +137,8 @@ try { dotnet @buildArgs if ($LASTEXITCODE -ne 0) { throw "dotnet build exit $LASTEXITCODE" } - # The test csproj multi-targets net8.0/net9.0 (and net462 on Windows) - # for build coverage; run the .NET (Core) test suite once on net9.0 - # (back-compat covers net8.0 consumers) plus net462 on Windows to - # exercise the netstandard polyfills at runtime. + # Test on net9.0 (back-compat covers net8.0 consumers); on Windows + # also run net462 to exercise the netstandard2.0 polyfills. $frameworks = @('net9.0') if ($IsWindows) { $frameworks += 'net462' } diff --git a/sdk_v2/cpp/CMakeLists.txt b/sdk_v2/cpp/CMakeLists.txt index 7159b738..6faa7fe1 100644 --- a/sdk_v2/cpp/CMakeLists.txt +++ b/sdk_v2/cpp/CMakeLists.txt @@ -111,10 +111,10 @@ 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. +# available — the source unconditionally references its headers/types, which +# exist only on Windows. 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 diff --git a/sdk_v2/cpp/cmake/FindOnnxRuntime.cmake b/sdk_v2/cpp/cmake/FindOnnxRuntime.cmake index 43a6f6ec..f1138587 100644 --- a/sdk_v2/cpp/cmake/FindOnnxRuntime.cmake +++ b/sdk_v2/cpp/cmake/FindOnnxRuntime.cmake @@ -220,7 +220,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. + # On Windows, the runtime DLL sits next to the import lib. if(NOT _ORT_DLL_DIR) set(_ORT_DLL_DIR "${_ORT_LIB_DIR}") endif() diff --git a/sdk_v2/cpp/test/CMakeLists.txt b/sdk_v2/cpp/test/CMakeLists.txt index 08e23caf..cbe2974f 100644 --- a/sdk_v2/cpp/test/CMakeLists.txt +++ b/sdk_v2/cpp/test/CMakeLists.txt @@ -105,7 +105,7 @@ else() endif() include(GoogleTest) -# DISCOVERY_TIMEOUT defaults to 5s, which is not enough for the WinML build: +# DISCOVERY_TIMEOUT defaults to 5s, which is not enough for the Windows 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. diff --git a/sdk_v2/js/script/copy-native.mjs b/sdk_v2/js/script/copy-native.mjs index 4cdb44e8..d8a0e95b 100644 --- a/sdk_v2/js/script/copy-native.mjs +++ b/sdk_v2/js/script/copy-native.mjs @@ -78,8 +78,8 @@ let copied = 0; const available = new Set(readdirSync(sourceDir)); for (const file of wanted) { if (!available.has(file)) { - // Skip optional deps that aren't present in this build flavor (e.g. - // WinML bits in a non-WinML build). + // Skip optional deps not produced on this platform (e.g. the Windows ML + // runtime only exists in Windows builds). continue; } const src = resolve(sourceDir, file); diff --git a/sdk_v2/python/_build_backend/__init__.py b/sdk_v2/python/_build_backend/__init__.py index a54913cf..b6de7e94 100644 --- a/sdk_v2/python/_build_backend/__init__.py +++ b/sdk_v2/python/_build_backend/__init__.py @@ -43,33 +43,32 @@ _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 = _PYPROJECT.parent.parent / "deps_versions.json" -# Patterns for rewriting ORT/GenAI version pins in the dependencies list. -# Each captures the package name + ``==`` and we substitute in the version -# read from the appropriate deps_versions JSON. +# Patterns for rewriting ORT/GenAI version pins in the dependencies list. Each +# captures the package name + ``==`` and we substitute in the version read from +# deps_versions.json. _ORT_PIN_PATTERN = re.compile(r'("onnxruntime(?:-core|-gpu)==)[^\s";]+') _GENAI_PIN_PATTERN = re.compile(r'("onnxruntime-genai(?:-core|-cuda)==)[^\s";]+') -def _read_versions(deps_file: Path) -> tuple[str, str]: - if not deps_file.is_file(): - raise RuntimeError(f"Required versions file not found: {deps_file}") - data = json.loads(deps_file.read_text(encoding="utf-8")) +def _read_versions() -> tuple[str, str]: + if not _DEPS_JSON.is_file(): + raise RuntimeError(f"Required versions file not found: {_DEPS_JSON}") + data = json.loads(_DEPS_JSON.read_text(encoding="utf-8")) try: ort = data["onnxruntime"]["version"] genai = data["onnxruntime-genai"]["version"] except (KeyError, TypeError) as exc: raise RuntimeError( - f"{deps_file} is missing required keys 'onnxruntime.version' / 'onnxruntime-genai.version'" + f"{_DEPS_JSON} is missing required keys 'onnxruntime.version' / 'onnxruntime-genai.version'" ) from exc return str(ort), str(genai) -def _patch_pyproject_text(original: str, *, deps_file: Path) -> str: - """Return *original* with the ORT/GenAI version pins rewritten from *deps_file*.""" - ort_ver, genai_ver = _read_versions(deps_file) +def _patch_pyproject_text(original: str) -> str: + """Return *original* with the ORT/GenAI version pins rewritten from deps_versions.json.""" + ort_ver, genai_ver = _read_versions() patched = _ORT_PIN_PATTERN.sub(lambda m: f"{m.group(1)}{ort_ver}", original) patched = _GENAI_PIN_PATTERN.sub(lambda m: f"{m.group(1)}{genai_ver}", patched) return patched @@ -83,10 +82,10 @@ def _rewrite_version_pins() -> Generator[None, None, None]: versions; rewriting here keeps the wheel's declared pins in lockstep. """ original = _PYPROJECT.read_text(encoding="utf-8") - patched = _patch_pyproject_text(original, deps_file=_DEPS_JSON_STD) + patched = _patch_pyproject_text(original) if patched == original: - # Nothing to rewrite (e.g. JSON already matches and no name override). + # Pins already match deps_versions.json — nothing to rewrite. yield return From b153c79e0f8d6e00c83427948d01c38058fadc83 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Fri, 19 Jun 2026 16:24:01 -0500 Subject: [PATCH 13/13] js: point GenAI at ORT via ORT_LIB_PATH; drop the unversioned dylib alias onnxruntime-genai's InitApi() (built with dlopen on macOS-non-framework and Linux, per its CMakeLists) honors the ORT_LIB_PATH env var: it dlopens that exact file before its hardcoded unversioned libonnxruntime.{dylib,so} fallback. Set it in the JS addon loader to the ORT we ship, so the package no longer needs the unversioned alias purely for GenAI: - detail/native.ts: applyOrtLibPath() sets ORT_LIB_PATH (only if unset) to the resolved ORT in the prebuilds dir, or the configured libraryPath, before the addon -- and thus GenAI's lazy InitApi -- loads. No-op on Windows (ORT is linked directly, no dlopen). ortCandidateBasenames now prefers the versioned soname. - install-native.cjs: rename the extracted unversioned ORT to the versioned soname (libonnxruntime.1.dylib / .so.1) that libfoundry_local records, instead of symlinking and keeping both -- one file, no symlink. - copy-native.mjs: dev staging copies the versioned soname only, matching ship. macOS CI (js_test_osx_arm64) runs npm ci (install-native postinstall) + vitest with FOUNDRY_TEST_DATA_DIR set, so the real-model path that loads GenAI exercises this end to end. The C++ build keeps its own ORT symlink for the C++ test harness, which does not set ORT_LIB_PATH. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- sdk_v2/js/script/copy-native.mjs | 13 +++---- sdk_v2/js/script/install-native.cjs | 54 +++++++++++++---------------- sdk_v2/js/src/detail/native.ts | 39 ++++++++++++++++++--- 3 files changed, 63 insertions(+), 43 deletions(-) diff --git a/sdk_v2/js/script/copy-native.mjs b/sdk_v2/js/script/copy-native.mjs index d8a0e95b..79a29190 100644 --- a/sdk_v2/js/script/copy-native.mjs +++ b/sdk_v2/js/script/copy-native.mjs @@ -58,20 +58,17 @@ 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. + // Copy only the versioned ORT soname (libonnxruntime.1.dylib) — the name + // libfoundry_local records. GenAI is pointed at it via ORT_LIB_PATH (set by the + // addon loader in detail/native.ts), so the unversioned alias isn't needed. The + // C++ build stages the versioned name as a symlink; copyFileSync dereferences it. return [ "libfoundry_local.dylib", - "libonnxruntime.dylib", "libonnxruntime.1.dylib", "libonnxruntime-genai.dylib", ]; } - return ["libfoundry_local.so", "libonnxruntime.so", "libonnxruntime.so.1", "libonnxruntime-genai.so"]; + return ["libfoundry_local.so", "libonnxruntime.so.1", "libonnxruntime-genai.so"]; })(); let copied = 0; diff --git a/sdk_v2/js/script/install-native.cjs b/sdk_v2/js/script/install-native.cjs index 3c615dd6..3e654adc 100644 --- a/sdk_v2/js/script/install-native.cjs +++ b/sdk_v2/js/script/install-native.cjs @@ -225,38 +225,32 @@ async function installPackage(artifact, tempDir, binDir) { ); } -// Mirror the platform-specific post-build steps that sdk_v2/cpp/CMakeLists.txt -// runs after building libfoundry_local. The Foundry ORT nupkg ships only the -// unversioned `libonnxruntime.{so,dylib}`, but libfoundry_local records a -// versioned SONAME/install_name dependency (libonnxruntime.so.1 / -// libonnxruntime.1.dylib), so we add that soname symlink next to the shipped -// file. GenAI continues to dlopen the unversioned name, which stays as-is. -function applyOrtPlatformAliases(binDir, ortVersion) { +// The Foundry ORT nupkg extracts the unversioned `libonnxruntime.{so,dylib}`, but +// libfoundry_local records a versioned SONAME/install_name dependency +// (libonnxruntime.so.1 / libonnxruntime.1.dylib). Rename the extracted file to that +// versioned soname so foundry_local resolves it via rpath. GenAI is pointed at the +// same file by the ORT_LIB_PATH env var the addon loader sets (onnxruntime-genai +// honors it before its unversioned-name fallback), so no unversioned alias is +// shipped. Windows uses onnxruntime.dll, which has no soname. +function normalizeOrtLibName(binDir, ortVersion) { + let unversioned; + let versioned; if (os.platform() === 'linux') { - const unv = path.join(binDir, 'libonnxruntime.so'); - const soname = path.join(binDir, 'libonnxruntime.so.1'); - if (fs.existsSync(unv) && !fs.existsSync(soname)) { - try { - fs.symlinkSync('libonnxruntime.so', soname); - console.log(` Created symlink libonnxruntime.so.1 -> libonnxruntime.so`); - } catch (err) { - fs.copyFileSync(unv, soname); - console.log(` Copied libonnxruntime.so -> libonnxruntime.so.1 (symlink failed: ${err.message})`); - } - } + unversioned = path.join(binDir, 'libonnxruntime.so'); + versioned = path.join(binDir, 'libonnxruntime.so.1'); } else if (os.platform() === 'darwin') { const major = ortVersion.split('.')[0]; - const unv = path.join(binDir, 'libonnxruntime.dylib'); - const soname = path.join(binDir, `libonnxruntime.${major}.dylib`); - if (fs.existsSync(unv) && !fs.existsSync(soname)) { - try { - fs.symlinkSync('libonnxruntime.dylib', soname); - console.log(` Created symlink libonnxruntime.${major}.dylib -> libonnxruntime.dylib`); - } catch (err) { - fs.copyFileSync(unv, soname); - console.log(` Copied libonnxruntime.dylib -> libonnxruntime.${major}.dylib (symlink failed: ${err.message})`); - } - } + unversioned = path.join(binDir, 'libonnxruntime.dylib'); + versioned = path.join(binDir, `libonnxruntime.${major}.dylib`); + } else { + return; + } + if (fs.existsSync(versioned)) { + return; + } + if (fs.existsSync(unversioned)) { + fs.renameSync(unversioned, versioned); + console.log(` Renamed ${path.basename(unversioned)} -> ${path.basename(versioned)}`); } } @@ -269,7 +263,7 @@ function applyOrtPlatformAliases(binDir, ortVersion) { for (const artifact of ARTIFACTS) { await installPackage(artifact, tempDir, BIN_DIR); } - applyOrtPlatformAliases(BIN_DIR, ortVersion); + normalizeOrtLibName(BIN_DIR, ortVersion); console.log('[foundry-local] Native runtime install complete.'); } catch (err) { console.error('[foundry-local] Installation failed:', err instanceof Error ? err.message : err); diff --git a/sdk_v2/js/src/detail/native.ts b/sdk_v2/js/src/detail/native.ts index 0e3e128f..0f5c2c75 100644 --- a/sdk_v2/js/src/detail/native.ts +++ b/sdk_v2/js/src/detail/native.ts @@ -258,6 +258,8 @@ function loadAddon(): NativeAddon { `Native addon not found at ${addonPath}.\nBuild it locally with:\n npm run copy-native:dev && npm run build:native\n(requires the C++ SDK to be built first via\n \`python sdk_v2/cpp/build.py --configure --build --config RelWithDebInfo\`).\nAlternatively, pass \`libraryPath\` in the FoundryLocalConfig (or call \`configureNativeLoader\`) to point at a directory containing the native library.`, ); } + // Point GenAI at the ORT bundled next to the addon before the addon loads. + applyOrtLibPath(prebuildDir); const require = createRequire(import.meta.url); return require(addonPath) as NativeAddon; } @@ -284,20 +286,44 @@ 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. The Foundry nupkg ships the unversioned `libonnxruntime.{so,dylib}`; - * we add the versioned soname (`libonnxruntime.so.1` / `libonnxruntime.1.dylib`) beside it, which is the name - * libfoundry_local actually records, so both are listed as fallbacks. GenAI has no versioned variant. + * uses the first that exists on disk. We ship the versioned soname (`libonnxruntime.so.1` / + * `libonnxruntime.1.dylib`) — the name libfoundry_local records — and point GenAI at it via ORT_LIB_PATH + * (see applyOrtLibPath). The unversioned name is kept only as a tolerant fallback for layouts that still + * stage it (e.g. the raw C++ build output). GenAI has no versioned variant of its own. */ function ortCandidateBasenames(name: "onnxruntime" | "onnxruntime-genai"): string[] { if (process.platform === "win32") return [`${name}.dll`]; if (process.platform === "darwin") { - if (name === "onnxruntime") return ["libonnxruntime.dylib", "libonnxruntime.1.dylib"]; + if (name === "onnxruntime") return ["libonnxruntime.1.dylib", "libonnxruntime.dylib"]; return [`lib${name}.dylib`]; } - if (name === "onnxruntime") return ["libonnxruntime.so", "libonnxruntime.so.1"]; + if (name === "onnxruntime") return ["libonnxruntime.so.1", "libonnxruntime.so"]; return [`lib${name}.so`]; } +/** + * Point onnxruntime-genai at the exact ORT we ship, via the `ORT_LIB_PATH` env var. + * + * On macOS/Linux the GenAI library (built with dlopen support) resolves ORT in its `InitApi()` by first + * dlopen-ing `$ORT_LIB_PATH`, then falling back to the unversioned `libonnxruntime.{dylib,so}` leafname. We + * ship only the versioned soname (the name libfoundry_local records), so setting `ORT_LIB_PATH` to it is what + * lets the package omit the unversioned alias entirely. Windows links ORT directly (no dlopen), so this is a + * no-op there. + * + * Must run before the addon loads (GenAI's `InitApi` is lazy, fired by the first model load). Never clobbers a + * caller-provided `ORT_LIB_PATH`. + */ +function applyOrtLibPath(directory: string): void { + if (process.platform === "win32" || process.env.ORT_LIB_PATH) return; + for (const basename of ortCandidateBasenames("onnxruntime")) { + const fullPath = resolve(directory, basename); + if (existsSync(fullPath)) { + process.env.ORT_LIB_PATH = fullPath; + return; + } + } +} + /** * Pre-load ORT and ORT-GenAI from `directory` by absolute path so that when foundry_local is loaded, the OS * loader resolves its NEEDED entries against the already-resident modules instead of doing a filesystem search @@ -377,6 +403,9 @@ export function configureNativeLoader(opts: { libraryPath?: string }): void { // (.github/instructions/ort-loading-contract.instructions.md) — every binding must do this. preloadOrtIfPresent(libraryPath); + // Point GenAI at the ORT in this directory (takes precedence over the default prebuilds lookup). + applyOrtLibPath(libraryPath); + try { getPreloadAddon().preloadLibrary(fullPath); } catch (err) {