Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 48 additions & 7 deletions AIDevGallery/Models/GenAIConfig.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace AIDevGallery.Models;
Expand All @@ -25,6 +29,8 @@ internal class Decoder
{
[JsonPropertyName("session_options")]
public required GenAISessionOptions SessionOptions { get; set; }
[JsonPropertyName("pipeline")]
public PipelineItem[]? Pipeline { get; set; }
}

internal class GenAISessionOptions
Expand All @@ -33,18 +39,53 @@ internal class GenAISessionOptions
public required ProviderOptions[] ProviderOptions { get; set; }
}

internal class ProviderOptions
internal class PipelineItem
{
[JsonPropertyName("dml")]
public Dml? Dml { get; set; }
[JsonPropertyName("cuda")]
public Cuda? Cuda { get; set; }
[JsonExtensionData]
public Dictionary<string, JsonElement>? Stages { get; set; }
}

internal class Dml
internal class PipelineStage
{
[JsonPropertyName("session_options")]
public GenAISessionOptions? SessionOptions { get; set; }
}

Comment thread
weiyuanyue marked this conversation as resolved.
internal class Cuda
internal class ProviderOptions
{
[JsonExtensionData]
public Dictionary<string, JsonElement>? ExtensionData { get; set; }

public bool HasProvider(string name)
{
if (ExtensionData == null)
{
return false;
}

return ExtensionData.Keys.Any(k => k.Equals(name, StringComparison.OrdinalIgnoreCase));
}

public Dictionary<string, string>? GetProviderOptions(string name)
{
if (ExtensionData == null)
{
return null;
}

var key = ExtensionData.Keys.FirstOrDefault(k => k.Equals(name, StringComparison.OrdinalIgnoreCase));
if (key == null)
{
return null;
}

try
{
return JsonSerializer.Deserialize(ExtensionData[key].GetRawText(), AIDevGallery.Utils.SourceGenerationContext.Default.DictionaryStringString);
}
catch
{
return new Dictionary<string, string>();
}
}
}
20 changes: 18 additions & 2 deletions AIDevGallery/Models/ModelCompatibility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,27 @@ public static ModelCompatibility GetModelCompatibility(ModelDetails modelDetails
compatibility = ModelCompatibilityState.NotCompatible;
description = "This model is not currently supported on Arm64 devices.";
}
else if (modelDetails.HardwareAccelerators.Contains(HardwareAccelerator.CPU) ||
(modelDetails.HardwareAccelerators.Contains(HardwareAccelerator.QNN) && DeviceUtils.IsArm64()))
else if (modelDetails.HardwareAccelerators.Contains(HardwareAccelerator.CPU))
{
compatibility = ModelCompatibilityState.Compatible;
}
else if (modelDetails.HardwareAccelerators.Contains(HardwareAccelerator.QNN) && DeviceUtils.IsArm64())
{
compatibility = ModelCompatibilityState.Compatible;
}
else if (modelDetails.HardwareAccelerators.Contains(HardwareAccelerator.NPU))
{
// Check if any NPU is available using ONNX Runtime's EP detection
if (DeviceUtils.HasNPU())
Comment thread
haoliuu marked this conversation as resolved.
{
compatibility = ModelCompatibilityState.Compatible;
}
else
{
compatibility = ModelCompatibilityState.NotCompatible;
description = "This model requires an NPU (Neural Processing Unit). No compatible NPU was detected on your device.";
}
}
else if (
(modelDetails.HardwareAccelerators.Contains(HardwareAccelerator.DML) || modelDetails.HardwareAccelerators.Contains(HardwareAccelerator.GPU))
&& !DeviceUtils.IsArm64())
Expand Down
34 changes: 20 additions & 14 deletions AIDevGallery/Pages/Scenarios/ScenarioPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,38 +142,38 @@ private static async Task<List<WinMlEp>> GetSupportedHardwareAccelerators()

switch (epName)
{
case "VitisAIExecutionProvider":
supportedHardwareAccelerators.Add(new([HardwareAccelerator.VitisAI, HardwareAccelerator.NPU], "VitisAIExecutionProvider", "VitisAI", "NPU"));
case ExecutionProviderNames.VitisAI:
supportedHardwareAccelerators.Add(new([HardwareAccelerator.VitisAI, HardwareAccelerator.NPU], ExecutionProviderNames.VitisAI, "VitisAI", "NPU"));
break;

case "OpenVINOExecutionProvider":
case ExecutionProviderNames.OpenVINO:
if (epDeviceTypes.Contains("CPU"))
{
supportedHardwareAccelerators.Add(new([HardwareAccelerator.OpenVINO, HardwareAccelerator.CPU], "OpenVINOExecutionProvider", "OpenVINO", "CPU"));
supportedHardwareAccelerators.Add(new([HardwareAccelerator.OpenVINO, HardwareAccelerator.CPU], ExecutionProviderNames.OpenVINO, "OpenVINO", "CPU"));
}

if (epDeviceTypes.Contains("GPU"))
{
supportedHardwareAccelerators.Add(new([HardwareAccelerator.OpenVINO, HardwareAccelerator.GPU], "OpenVINOExecutionProvider", "OpenVINO", "GPU"));
supportedHardwareAccelerators.Add(new([HardwareAccelerator.OpenVINO, HardwareAccelerator.GPU], ExecutionProviderNames.OpenVINO, "OpenVINO", "GPU"));
}

if (epDeviceTypes.Contains("NPU"))
{
supportedHardwareAccelerators.Add(new([HardwareAccelerator.OpenVINO, HardwareAccelerator.NPU], "OpenVINOExecutionProvider", "OpenVINO", "NPU"));
supportedHardwareAccelerators.Add(new([HardwareAccelerator.OpenVINO, HardwareAccelerator.NPU], ExecutionProviderNames.OpenVINO, "OpenVINO", "NPU"));
}

break;

case "QNNExecutionProvider":
supportedHardwareAccelerators.Add(new([HardwareAccelerator.QNN, HardwareAccelerator.NPU], "QNNExecutionProvider", "QNN", "NPU"));
case ExecutionProviderNames.QNN:
supportedHardwareAccelerators.Add(new([HardwareAccelerator.QNN, HardwareAccelerator.NPU], ExecutionProviderNames.QNN, "QNN", "NPU"));
break;

case "DmlExecutionProvider":
supportedHardwareAccelerators.Add(new([HardwareAccelerator.DML, HardwareAccelerator.GPU], "DmlExecutionProvider", "DML", "GPU"));
case ExecutionProviderNames.DML:
supportedHardwareAccelerators.Add(new([HardwareAccelerator.DML, HardwareAccelerator.GPU], ExecutionProviderNames.DML, "DML", "GPU"));
break;

case "NvTensorRTRTXExecutionProvider":
supportedHardwareAccelerators.Add(new([HardwareAccelerator.NvTensorRT, HardwareAccelerator.GPU], "NvTensorRTRTXExecutionProvider", "NvTensorRT", "GPU"));
case ExecutionProviderNames.NvTensorRTRTX:
supportedHardwareAccelerators.Add(new([HardwareAccelerator.NvTensorRT, HardwareAccelerator.GPU], ExecutionProviderNames.NvTensorRTRTX, "NvTensorRT", "GPU"));
break;
}
}
Expand All @@ -193,7 +193,13 @@ private async void HandleModelSelectionChanged(List<ModelDetails?> selectedModel
VisualStateManager.GoToState(this, "PageLoading", true);

modelDetails.Clear();
selectedModels.ForEach(modelDetails.Add);
foreach (var model in selectedModels)
{
if (model != null)
{
modelDetails.Add(model!);
}
}

// temporary fix EP dropdown list for useradded local languagemodel
if (selectedModels.Any(m => m != null && m.IsOnnxModel() && string.IsNullOrEmpty(m.ParameterSize) && m.Id.StartsWith("useradded-local-languagemodel", System.StringComparison.InvariantCultureIgnoreCase) == false))
Expand Down Expand Up @@ -316,7 +322,7 @@ private void LoadSample(Sample? sampleToLoad)

// TODO: don't load sample if model is not cached, but still let code to be seen
// this would probably be handled in the SampleContainer
_ = SampleContainer.LoadSampleAsync(sample, [.. modelDetails], App.AppData.WinMLSampleOptions);
_ = SampleContainer.LoadSampleAsync(sample, modelDetails.Where(m => m != null).Select(m => m!).ToList(), App.AppData.WinMLSampleOptions);
_ = App.AppData.AddMru(
new MostRecentlyUsedItem()
{
Expand Down
18 changes: 9 additions & 9 deletions AIDevGallery/Samples/SharedCode/WinMLHelpers.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using AIDevGallery.Utils;
using Microsoft.ML.OnnxRuntime;
using System;
using System.Collections.Generic;
Expand All @@ -12,32 +13,32 @@ namespace AIDevGallery.Samples.SharedCode;

internal static class WinMLHelpers
{
public static bool AppendExecutionProviderFromEpName(this SessionOptions sessionOptions, string epName, string? deviceType, OrtEnv? environment = null)
public static bool AppendExecutionProviderFromEpName(this SessionOptions sessionOptions, string epName, string? deviceType)
{
if (epName == "CPU")
{
// No need to append CPU execution provider
return true;
}

environment ??= OrtEnv.Instance();
var epDeviceMap = GetEpDeviceMap(environment);
var environment = OrtEnv.Instance();
var epDeviceMap = GetEpDeviceMap();

if (epDeviceMap.TryGetValue(epName, out var devices))
{
Dictionary<string, string> epOptions = new(StringComparer.OrdinalIgnoreCase);
switch (epName)
{
case "DmlExecutionProvider":
case ExecutionProviderNames.DML:
// Configure performance mode for Dml EP
// Dml some times have multiple devices which cause exception, we pick the first one here
sessionOptions.AppendExecutionProvider(environment, [devices[0]], epOptions);
return true;
case "OpenVINOExecutionProvider":
case ExecutionProviderNames.OpenVINO:
var device = devices.Where(d => d.HardwareDevice.Type.ToString().Equals(deviceType, StringComparison.Ordinal)).FirstOrDefault();
sessionOptions.AppendExecutionProvider(environment, [device], epOptions);
return true;
case "QNNExecutionProvider":
case ExecutionProviderNames.QNN:
// Configure performance mode for QNN EP
epOptions["htp_performance_mode"] = "high_performance";
break;
Comment thread
weiyuanyue marked this conversation as resolved.
Expand Down Expand Up @@ -105,10 +106,9 @@ public static bool AppendExecutionProviderFromEpName(this SessionOptions session
return null;
}

public static Dictionary<string, List<OrtEpDevice>> GetEpDeviceMap(OrtEnv? environment = null)
public static Dictionary<string, List<OrtEpDevice>> GetEpDeviceMap()
{
environment ??= OrtEnv.Instance();
IReadOnlyList<OrtEpDevice> epDevices = environment.GetEpDevices();
IReadOnlyList<OrtEpDevice> epDevices = DeviceUtils.GetEpDevices();
Dictionary<string, List<OrtEpDevice>> epDeviceMap = new(StringComparer.OrdinalIgnoreCase);

foreach (OrtEpDevice device in epDevices)
Expand Down
4 changes: 2 additions & 2 deletions AIDevGallery/Utils/AppUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -285,10 +285,10 @@ public static string GetThemeAssetSuffix()
{
var accessibilitySettings = new AccessibilitySettings();
bool isHighContrast = accessibilitySettings.HighContrast;
if(isHighContrast)
if (isHighContrast)
{
string hcThemeName = accessibilitySettings.HighContrastScheme;
if(hcThemeName == "High Contrast White")
if (hcThemeName == "High Contrast White")
{
return ".light";
}
Expand Down
71 changes: 69 additions & 2 deletions AIDevGallery/Utils/DeviceUtils.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.ML.OnnxRuntime;
using System;
using System.Linq;
using Windows.Win32.Foundation;
using Windows.Win32.Graphics.Dxgi;

namespace AIDevGallery.Utils;

internal static class DeviceUtils
{
private static readonly object _epDevicesLock = new();
private static System.Collections.Generic.IReadOnlyList<OrtEpDevice>? _cachedEpDevices;

public static int GetBestDeviceId()
{
int deviceId = 0;
Expand Down Expand Up @@ -107,8 +112,70 @@ public static ulong GetVram()
return maxDedicatedVideoMemory;
}

public static bool IsArm64()
public static bool IsArm64() =>
System.Runtime.InteropServices.RuntimeInformation.OSArchitecture == System.Runtime.InteropServices.Architecture.Arm64;

public static bool HasNPU() => HasExecutionProvider(device =>
device.HardwareDevice.Type.ToString().Equals("NPU", StringComparison.OrdinalIgnoreCase));

private static bool HasExecutionProvider(Func<OrtEpDevice, bool> predicate)
{
return System.Runtime.InteropServices.RuntimeInformation.OSArchitecture == System.Runtime.InteropServices.Architecture.Arm64;
try
{
return GetEpDevices().Any(predicate);
}
catch
{
return false;
}
}

/// <summary>
/// Gets the list of available ONNX Runtime Execution Provider devices.
/// This method ensures that certified EPs (like OpenVINO, QNN, DML) are registered before querying,
/// as OrtEnv.GetEpDevices() only returns already-registered providers.
/// Results are cached to avoid repeated registration overhead.
/// </summary>
/// <returns>A read-only list of available ONNX Runtime Execution Provider devices.</returns>
public static System.Collections.Generic.IReadOnlyList<OrtEpDevice> GetEpDevices()
Comment thread
weiyuanyue marked this conversation as resolved.
{
if (_cachedEpDevices != null)
{
return _cachedEpDevices;
}

lock (_epDevicesLock)
{
if (_cachedEpDevices != null)
{
return _cachedEpDevices;
}

try
{
OrtEnv.Instance();
var catalog = Microsoft.Windows.AI.MachineLearning.ExecutionProviderCatalog.GetDefault();

try
{
catalog.EnsureAndRegisterCertifiedAsync().GetAwaiter().GetResult();
}
catch (Exception ex)
{
// Log but continue
Telemetry.TelemetryFactory.Get<Telemetry.ITelemetry>().LogException("GetEpDevices_RegistrationFailed", ex);
}

_cachedEpDevices = OrtEnv.Instance().GetEpDevices();
}
catch (Exception ex)
{
// Log the failure to get EP devices - this could indicate ONNX Runtime initialization issues
Telemetry.TelemetryFactory.Get<Telemetry.ITelemetry>().LogException("GetEpDevices_Failed", ex);
_cachedEpDevices = System.Array.Empty<OrtEpDevice>();
}

return _cachedEpDevices;
}
}
}
Loading