diff --git a/AIDevGallery/Pages/Scenarios/ScenarioPage.xaml b/AIDevGallery/Pages/Scenarios/ScenarioPage.xaml index 6f604816..e36da8b4 100644 --- a/AIDevGallery/Pages/Scenarios/ScenarioPage.xaml +++ b/AIDevGallery/Pages/Scenarios/ScenarioPage.xaml @@ -222,6 +222,7 @@ diff --git a/AIDevGallery/Pages/Scenarios/ScenarioPage.xaml.cs b/AIDevGallery/Pages/Scenarios/ScenarioPage.xaml.cs index acbf21d4..cbd360cb 100644 --- a/AIDevGallery/Pages/Scenarios/ScenarioPage.xaml.cs +++ b/AIDevGallery/Pages/Scenarios/ScenarioPage.xaml.cs @@ -1,470 +1,488 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using AIDevGallery.Controls; -using AIDevGallery.Helpers; -using AIDevGallery.Models; -using AIDevGallery.ProjectGenerator; -using AIDevGallery.Samples; -using AIDevGallery.Samples.SharedCode; -using AIDevGallery.Telemetry.Events; -using AIDevGallery.Utils; -using Microsoft.ML.OnnxRuntime; -using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Controls; -using Microsoft.UI.Xaml.Navigation; -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Threading.Tasks; -using Windows.ApplicationModel.DataTransfer; - -namespace AIDevGallery.Pages; - -internal record WinMlEp(List HardwareAccelerators, string Name, string ShortName, string DeviceType); - -internal sealed partial class ScenarioPage : Page -{ - private readonly Dictionary executionProviderDevicePolicies = new() - { - { "Default", ExecutionProviderDevicePolicy.DEFAULT }, - { "Max Efficency", ExecutionProviderDevicePolicy.MAX_EFFICIENCY }, - { "Max Performance", ExecutionProviderDevicePolicy.MAX_PERFORMANCE }, - { "Minimize Overall Power", ExecutionProviderDevicePolicy.MIN_OVERALL_POWER }, - { "Prefer NPU", ExecutionProviderDevicePolicy.PREFER_NPU }, - { "Prefer GPU", ExecutionProviderDevicePolicy.PREFER_GPU }, - { "Prefer CPU", ExecutionProviderDevicePolicy.PREFER_CPU }, - }; - - private Scenario? scenario; - private List? samples; - private Sample? sample; - private ObservableCollection modelDetails = new(); - private static List? supportedHardwareAccelerators; - - public ScenarioPage() - { - this.InitializeComponent(); - this.Loaded += (s, e) => - BackgroundShadow.Receivers.Add(ShadowCastGrid); - App.MainWindow.ModelPicker.SelectedModelsChanged += ModelOrApiPicker_SelectedModelsChanged; - this.Unloaded += (s, e) => App.MainWindow.ModelPicker.SelectedModelsChanged -= ModelOrApiPicker_SelectedModelsChanged; - } - - protected override void OnNavigatedTo(NavigationEventArgs e) - { - VisualStateManager.GoToState(this, "PageLoading", true); - base.OnNavigatedTo(e); - _ = LoadPage(e.Parameter); - } - - private async Task LoadPage(object parameter) - { - if (parameter is Scenario scenario) - { - this.scenario = scenario; - await LoadPicker(); - } - else if (parameter is SampleNavigationArgs sampleArgs) - { - this.scenario = ScenarioCategoryHelpers.AllScenarioCategories.SelectMany(sc => sc.Scenarios).FirstOrDefault(s => s.ScenarioType == sampleArgs.Sample.Scenario); - await LoadPicker(sampleArgs.ModelDetails); - - if (sampleArgs.OpenCodeView.HasValue && sampleArgs.OpenCodeView.Value) - { - CodeToggle.IsChecked = true; - HandleCodePane(); - } - } - - samples = SampleDetails.Samples.Where(sample => sample.Scenario == this.scenario!.ScenarioType).ToList(); - } - - private async Task LoadPicker(ModelDetails? initialModelToLoad = null) - { - if (scenario == null) - { - return; - } - - samples = [.. SampleDetails.Samples.Where(sample => sample.Scenario == scenario.ScenarioType)]; - - if (samples.Count == 0) - { - return; - } - - List> modelDetailsList = [samples.SelectMany(s => s.Model1Types).ToList()]; - - // assume if first sample has two models, then all of them should need two models - if (samples[0].Model2Types != null) - { - modelDetailsList.Add(samples.SelectMany(s => s.Model2Types!).ToList()); - } - - var preSelectedModels = await App.MainWindow.ModelPicker.Load(modelDetailsList, initialModelToLoad); - HandleModelSelectionChanged(preSelectedModels); - - if (preSelectedModels.Contains(null) || preSelectedModels.Count == 0) - { - // user needs to select a model if one is not selected at first - App.MainWindow.ModelPicker.Show(preSelectedModels); - return; - } - } - - private static async Task> GetSupportedHardwareAccelerators() - { - if (supportedHardwareAccelerators != null) - { - return supportedHardwareAccelerators; - } - - OrtEnv.Instance(); - var catalog = Microsoft.Windows.AI.MachineLearning.ExecutionProviderCatalog.GetDefault(); - - try - { - var registeredProviders = await catalog.EnsureAndRegisterCertifiedAsync(); - } - catch (Exception) - { - } - - supportedHardwareAccelerators = [new([HardwareAccelerator.CPU], "CPU", "CPU", "CPU")]; - - foreach (var keyValuePair in WinMLHelpers.GetEpDeviceMap()) - { - var epName = keyValuePair.Key; - var epDevices = keyValuePair.Value; - var epDeviceTypes = epDevices.Select(d => d.HardwareDevice.Type.ToString()); - - switch (epName) - { - case "VitisAIExecutionProvider": - supportedHardwareAccelerators.Add(new([HardwareAccelerator.VitisAI, HardwareAccelerator.NPU], "VitisAIExecutionProvider", "VitisAI", "NPU")); - break; - - case "OpenVINOExecutionProvider": - if (epDeviceTypes.Contains("CPU")) - { - supportedHardwareAccelerators.Add(new([HardwareAccelerator.OpenVINO, HardwareAccelerator.CPU], "OpenVINOExecutionProvider", "OpenVINO", "CPU")); - } - - if (epDeviceTypes.Contains("GPU")) - { - supportedHardwareAccelerators.Add(new([HardwareAccelerator.OpenVINO, HardwareAccelerator.GPU], "OpenVINOExecutionProvider", "OpenVINO", "GPU")); - } - - if (epDeviceTypes.Contains("NPU")) - { - supportedHardwareAccelerators.Add(new([HardwareAccelerator.OpenVINO, HardwareAccelerator.NPU], "OpenVINOExecutionProvider", "OpenVINO", "NPU")); - } - - break; - - case "QNNExecutionProvider": - supportedHardwareAccelerators.Add(new([HardwareAccelerator.QNN, HardwareAccelerator.NPU], "QNNExecutionProvider", "QNN", "NPU")); - break; - - case "DmlExecutionProvider": - supportedHardwareAccelerators.Add(new([HardwareAccelerator.DML, HardwareAccelerator.GPU], "DmlExecutionProvider", "DML", "GPU")); - break; - - case "NvTensorRTRTXExecutionProvider": - supportedHardwareAccelerators.Add(new([HardwareAccelerator.NvTensorRT, HardwareAccelerator.GPU], "NvTensorRTRTXExecutionProvider", "NvTensorRT", "GPU")); - break; - } - } - - return supportedHardwareAccelerators; - } - - private async void HandleModelSelectionChanged(List selectedModels) - { - if (selectedModels.Contains(null) || selectedModels.Count == 0) - { - // user needs to select a model - VisualStateManager.GoToState(this, "NoModelSelected", true); - return; - } - - VisualStateManager.GoToState(this, "PageLoading", true); - - modelDetails.Clear(); - selectedModels.ForEach(modelDetails.Add); - - // 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)) - { - var delayTask = Task.Delay(1000); - var supportedHardwareAcceleratorsTask = GetSupportedHardwareAccelerators(); - - if (await Task.WhenAny(delayTask, supportedHardwareAcceleratorsTask) == delayTask) - { - VisualStateManager.GoToState(this, "PageLoadingWithMessage", true); - } - - var supportedHardwareAccelerators = await supportedHardwareAcceleratorsTask; - - HashSet eps = [supportedHardwareAccelerators[0]]; - - DeviceComboBox.Items.Clear(); - - foreach (var hardwareAccelerator in selectedModels.SelectMany(m => m!.HardwareAccelerators).Distinct()) - { - foreach (var ep in supportedHardwareAccelerators.Where(ep => ep.HardwareAccelerators.Contains(hardwareAccelerator))) - { - eps.Add(ep); - } - } - - foreach (var ep in eps.OrderBy(ep => ep.Name)) - { - DeviceComboBox.Items.Add(ep); - } - - UpdateWinMLFlyout(); - - WinMlModelOptionsButton.Visibility = Visibility.Visible; - } - else - { - WinMlModelOptionsButton.Visibility = Visibility.Collapsed; - } - - if (selectedModels.Count == 1) - { - // add the second model with null - selectedModels = [selectedModels[0], null]; - } - - List viableSamples = samples!.Where(s => - IsModelFromTypes(s.Model1Types, selectedModels[0]) && - IsModelFromTypes(s.Model2Types, selectedModels[1])).ToList(); - - if (viableSamples.Count == 0) - { - // this should never happen - App.MainWindow.ModelPicker.Show(selectedModels); - return; - } - - if (viableSamples.Count > 1) - { - SampleSelection.Items.Clear(); - foreach (var sample in viableSamples) - { - SampleSelection.Items.Add(sample); - } - - SampleSelection.SelectedItem = viableSamples[0]; - SampleContainer.ShowFooter = true; - } - else - { - SampleContainer.ShowFooter = false; - LoadSample(viableSamples[0]); - } - } - - private void UpdateWinMLFlyout() - { - var options = App.AppData.WinMLSampleOptions; - if (options.Policy != null) - { - var key = executionProviderDevicePolicies.FirstOrDefault(kvp => kvp.Value == options.Policy).Key; - ExecutionPolicyComboBox.SelectedItem = key; - WinMlModelOptionsButtonText.Text = key; - DeviceComboBox.SelectedIndex = 0; - segmentedControl.SelectedIndex = 0; - } - else if (options.EpName != null) - { - var selectedDevice = DeviceComboBox.Items.Where(i => (i as WinMlEp)?.Name == options.EpName && (i as WinMlEp)?.DeviceType == options.DeviceType).FirstOrDefault(); - if (selectedDevice != null) - { - DeviceComboBox.SelectedItem = selectedDevice; - } - else - { - DeviceComboBox.SelectedIndex = 0; - } - - ExecutionPolicyComboBox.SelectedIndex = 0; - CompileModelCheckBox.IsChecked = options.CompileModel; - WinMlModelOptionsButtonText.Text = (DeviceComboBox.SelectedItem as WinMlEp)?.ShortName; - segmentedControl.SelectedIndex = 1; - } - - // in case already saved options do not apply to this sample - _ = UpdateSampleOptions(); - } - - private void LoadSample(Sample? sampleToLoad) - { - sample = sampleToLoad; - - if (sample == null) - { - return; - } - - VisualStateManager.GoToState(this, "ModelSelected", true); - - // 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); - _ = App.AppData.AddMru( - new MostRecentlyUsedItem() - { - Type = MostRecentlyUsedItemType.Scenario, - ItemId = scenario!.Id, - Icon = scenario.Icon, - Description = scenario.Description, - SubItemId = modelDetails[0]!.Id, - DisplayName = scenario.Name - }, - modelDetails.Select(m => (m!.Id, m.HardwareAccelerators.First())).ToList()); - } - - private bool IsModelFromTypes(List? types, ModelDetails? model) - { - if (types == null && model == null) - { - return true; - } - - if (types == null || model == null) - { - return false; - } - - if (types.Contains(ModelType.LanguageModels) && model.IsLanguageModel()) - { - return true; - } - - List modelIds = []; - - foreach (var type in types) - { - modelIds.AddRange(ModelDetailsHelper.GetModelDetailsForModelType(type).Select(m => m.Id)); - if (App.AppData.TryGetUserAddedModelIds(type, out var ids)) - { - modelIds.AddRange(ids!); - } - } - - return modelIds.Any(id => id == model.Id); - } - - private void CopyButton_Click(object sender, RoutedEventArgs e) - { - var dataPackage = new DataPackage(); - dataPackage.SetText("aidevgallery://scenarios/" + scenario!.Id); - Clipboard.SetContentWithOptions(dataPackage, null); - } - - private void CodeToggle_Click(object sender, RoutedEventArgs args) - { - HandleCodePane(); - } - - private void HandleCodePane() - { - if (sample != null) - { - ToggleCodeButtonEvent.Log(sample.Name ?? string.Empty, CodeToggle.IsChecked == true); - } - - if (CodeToggle.IsChecked == true) - { - SampleContainer.ShowCode(); - } - else - { - SampleContainer.HideCode(); - } - } - - private void ExportSampleToggle_Click(object sender, RoutedEventArgs e) - { - if (sender is not Button button || sample == null) - { - return; - } - - _ = Generator.AskGenerateAndOpenAsync(sample, modelDetails.Where(m => m != null).Select(m => m!), App.AppData.WinMLSampleOptions, XamlRoot); - } - - private void ActionButtonsGrid_SizeChanged(object sender, SizeChangedEventArgs e) - { - // Calculate if the modelselectors collide with the export/code buttons - if ((ActionsButtonHolderPanel.ActualWidth + ButtonsPanel.ActualWidth) >= e.NewSize.Width) - { - VisualStateManager.GoToState(this, "NarrowLayout", true); - } - else - { - VisualStateManager.GoToState(this, "WideLayout", true); - } - } - - private void ModelBtn_Click(object sender, RoutedEventArgs e) - { - App.MainWindow.ModelPicker.Show(modelDetails.ToList()); - } - - private void ModelOrApiPicker_SelectedModelsChanged(object sender, List modelDetails) - { - HandleModelSelectionChanged(modelDetails); - } - - private void SampleSelection_SelectionChanged(object sender, SelectionChangedEventArgs e) - { - var selectedSample = e.AddedItems - .OfType() - .ToList().FirstOrDefault(); - - LoadSample(selectedSample); - } - - private async void ApplySampleOptions(object sender, RoutedEventArgs e) - { - WinMLOptionsFlyout.Hide(); - await UpdateSampleOptions(); - LoadSample(sample); - } - - private async Task UpdateSampleOptions() - { - var oldOptions = App.AppData.WinMLSampleOptions; - - if (segmentedControl.SelectedIndex == 0) - { - var key = (ExecutionPolicyComboBox.SelectedItem as string) ?? executionProviderDevicePolicies.Keys.First(); - WinMlModelOptionsButtonText.Text = key; - App.AppData.WinMLSampleOptions = new WinMlSampleOptions(executionProviderDevicePolicies[key], null, false, null); - } - else - { - var device = (DeviceComboBox.SelectedItem as WinMlEp) ?? (DeviceComboBox.Items.First() as WinMlEp); - WinMlModelOptionsButtonText.Text = device!.ShortName; - App.AppData.WinMLSampleOptions = new WinMlSampleOptions(null, device.Name, CompileModelCheckBox.IsChecked!.Value, device.DeviceType); - } - - if (oldOptions == App.AppData.WinMLSampleOptions) - { - return; - } - - await App.AppData.SaveAsync(); - } - - private void WinMLOptionsFlyout_Opening(object sender, object e) - { - UpdateWinMLFlyout(); - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using AIDevGallery.Controls; +using AIDevGallery.Helpers; +using AIDevGallery.Models; +using AIDevGallery.ProjectGenerator; +using AIDevGallery.Samples; +using AIDevGallery.Samples.SharedCode; +using AIDevGallery.Telemetry.Events; +using AIDevGallery.Utils; +using Microsoft.ML.OnnxRuntime; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Navigation; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading.Tasks; +using Windows.ApplicationModel.DataTransfer; + +namespace AIDevGallery.Pages; + +internal record WinMlEp(List HardwareAccelerators, string Name, string ShortName, string DeviceType); + +internal sealed partial class ScenarioPage : Page +{ + private readonly Dictionary executionProviderDevicePolicies = new() + { + { "Default", ExecutionProviderDevicePolicy.DEFAULT }, + { "Max Efficency", ExecutionProviderDevicePolicy.MAX_EFFICIENCY }, + { "Max Performance", ExecutionProviderDevicePolicy.MAX_PERFORMANCE }, + { "Minimize Overall Power", ExecutionProviderDevicePolicy.MIN_OVERALL_POWER }, + { "Prefer NPU", ExecutionProviderDevicePolicy.PREFER_NPU }, + { "Prefer GPU", ExecutionProviderDevicePolicy.PREFER_GPU }, + { "Prefer CPU", ExecutionProviderDevicePolicy.PREFER_CPU }, + }; + + private Scenario? scenario; + private List? samples; + private Sample? sample; + private ObservableCollection modelDetails = new(); + private static List? supportedHardwareAccelerators; + + public ScenarioPage() + { + this.InitializeComponent(); + this.Loaded += (s, e) => + BackgroundShadow.Receivers.Add(ShadowCastGrid); + App.MainWindow.ModelPicker.SelectedModelsChanged += ModelOrApiPicker_SelectedModelsChanged; + this.Unloaded += (s, e) => App.MainWindow.ModelPicker.SelectedModelsChanged -= ModelOrApiPicker_SelectedModelsChanged; + } + + protected override void OnNavigatedTo(NavigationEventArgs e) + { + VisualStateManager.GoToState(this, "PageLoading", true); + base.OnNavigatedTo(e); + _ = LoadPage(e.Parameter); + } + + private async Task LoadPage(object parameter) + { + if (parameter is Scenario scenario) + { + this.scenario = scenario; + await LoadPicker(); + } + else if (parameter is SampleNavigationArgs sampleArgs) + { + this.scenario = ScenarioCategoryHelpers.AllScenarioCategories.SelectMany(sc => sc.Scenarios).FirstOrDefault(s => s.ScenarioType == sampleArgs.Sample.Scenario); + await LoadPicker(sampleArgs.ModelDetails); + + if (sampleArgs.OpenCodeView.HasValue && sampleArgs.OpenCodeView.Value) + { + CodeToggle.IsChecked = true; + HandleCodePane(); + } + } + + samples = SampleDetails.Samples.Where(sample => sample.Scenario == this.scenario!.ScenarioType).ToList(); + } + + private async Task LoadPicker(ModelDetails? initialModelToLoad = null) + { + if (scenario == null) + { + return; + } + + samples = [.. SampleDetails.Samples.Where(sample => sample.Scenario == scenario.ScenarioType)]; + + if (samples.Count == 0) + { + return; + } + + List> modelDetailsList = [samples.SelectMany(s => s.Model1Types).ToList()]; + + // assume if first sample has two models, then all of them should need two models + if (samples[0].Model2Types != null) + { + modelDetailsList.Add(samples.SelectMany(s => s.Model2Types!).ToList()); + } + + var preSelectedModels = await App.MainWindow.ModelPicker.Load(modelDetailsList, initialModelToLoad); + HandleModelSelectionChanged(preSelectedModels); + + if (preSelectedModels.Contains(null) || preSelectedModels.Count == 0) + { + // user needs to select a model if one is not selected at first + App.MainWindow.ModelPicker.Show(preSelectedModels); + return; + } + } + + private static async Task> GetSupportedHardwareAccelerators() + { + if (supportedHardwareAccelerators != null) + { + return supportedHardwareAccelerators; + } + + OrtEnv.Instance(); + var catalog = Microsoft.Windows.AI.MachineLearning.ExecutionProviderCatalog.GetDefault(); + + try + { + var registeredProviders = await catalog.EnsureAndRegisterCertifiedAsync(); + } + catch (Exception) + { + } + + supportedHardwareAccelerators = [new([HardwareAccelerator.CPU], "CPU", "CPU", "CPU")]; + + foreach (var keyValuePair in WinMLHelpers.GetEpDeviceMap()) + { + var epName = keyValuePair.Key; + var epDevices = keyValuePair.Value; + var epDeviceTypes = epDevices.Select(d => d.HardwareDevice.Type.ToString()); + + switch (epName) + { + case "VitisAIExecutionProvider": + supportedHardwareAccelerators.Add(new([HardwareAccelerator.VitisAI, HardwareAccelerator.NPU], "VitisAIExecutionProvider", "VitisAI", "NPU")); + break; + + case "OpenVINOExecutionProvider": + if (epDeviceTypes.Contains("CPU")) + { + supportedHardwareAccelerators.Add(new([HardwareAccelerator.OpenVINO, HardwareAccelerator.CPU], "OpenVINOExecutionProvider", "OpenVINO", "CPU")); + } + + if (epDeviceTypes.Contains("GPU")) + { + supportedHardwareAccelerators.Add(new([HardwareAccelerator.OpenVINO, HardwareAccelerator.GPU], "OpenVINOExecutionProvider", "OpenVINO", "GPU")); + } + + if (epDeviceTypes.Contains("NPU")) + { + supportedHardwareAccelerators.Add(new([HardwareAccelerator.OpenVINO, HardwareAccelerator.NPU], "OpenVINOExecutionProvider", "OpenVINO", "NPU")); + } + + break; + + case "QNNExecutionProvider": + supportedHardwareAccelerators.Add(new([HardwareAccelerator.QNN, HardwareAccelerator.NPU], "QNNExecutionProvider", "QNN", "NPU")); + break; + + case "DmlExecutionProvider": + supportedHardwareAccelerators.Add(new([HardwareAccelerator.DML, HardwareAccelerator.GPU], "DmlExecutionProvider", "DML", "GPU")); + break; + + case "NvTensorRTRTXExecutionProvider": + supportedHardwareAccelerators.Add(new([HardwareAccelerator.NvTensorRT, HardwareAccelerator.GPU], "NvTensorRTRTXExecutionProvider", "NvTensorRT", "GPU")); + break; + } + } + + return supportedHardwareAccelerators; + } + + private async void HandleModelSelectionChanged(List selectedModels) + { + if (selectedModels.Contains(null) || selectedModels.Count == 0) + { + // user needs to select a model + VisualStateManager.GoToState(this, "NoModelSelected", true); + return; + } + + VisualStateManager.GoToState(this, "PageLoading", true); + + modelDetails.Clear(); + selectedModels.ForEach(modelDetails.Add); + + // 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)) + { + var delayTask = Task.Delay(1000); + var supportedHardwareAcceleratorsTask = GetSupportedHardwareAccelerators(); + + if (await Task.WhenAny(delayTask, supportedHardwareAcceleratorsTask) == delayTask) + { + VisualStateManager.GoToState(this, "PageLoadingWithMessage", true); + } + + var supportedHardwareAccelerators = await supportedHardwareAcceleratorsTask; + + HashSet eps = [supportedHardwareAccelerators[0]]; + + DeviceComboBox.Items.Clear(); + + foreach (var hardwareAccelerator in selectedModels.SelectMany(m => m!.HardwareAccelerators).Distinct()) + { + foreach (var ep in supportedHardwareAccelerators.Where(ep => ep.HardwareAccelerators.Contains(hardwareAccelerator))) + { + eps.Add(ep); + } + } + + foreach (var ep in eps.OrderBy(ep => ep.Name)) + { + DeviceComboBox.Items.Add(ep); + } + + UpdateWinMLFlyout(); + + WinMlModelOptionsButton.Visibility = Visibility.Visible; + } + else + { + WinMlModelOptionsButton.Visibility = Visibility.Collapsed; + } + + if (selectedModels.Count == 1) + { + // add the second model with null + selectedModels = [selectedModels[0], null]; + } + + List viableSamples = samples!.Where(s => + IsModelFromTypes(s.Model1Types, selectedModels[0]) && + IsModelFromTypes(s.Model2Types, selectedModels[1])).ToList(); + + if (viableSamples.Count == 0) + { + // this should never happen + App.MainWindow.ModelPicker.Show(selectedModels); + return; + } + + if (viableSamples.Count > 1) + { + SampleSelection.Items.Clear(); + foreach (var sample in viableSamples) + { + SampleSelection.Items.Add(sample); + } + + SampleSelection.SelectedItem = viableSamples[0]; + SampleContainer.ShowFooter = true; + } + else + { + SampleContainer.ShowFooter = false; + LoadSample(viableSamples[0]); + } + } + + private void UpdateWinMLFlyout() + { + var options = App.AppData.WinMLSampleOptions; + if (options.Policy != null) + { + var key = executionProviderDevicePolicies.FirstOrDefault(kvp => kvp.Value == options.Policy).Key; + ExecutionPolicyComboBox.SelectedItem = key; + WinMlModelOptionsButtonText.Text = key; + DeviceComboBox.SelectedIndex = 0; + segmentedControl.SelectedIndex = 0; + } + else if (options.EpName != null) + { + var selectedDevice = DeviceComboBox.Items.Where(i => (i as WinMlEp)?.Name == options.EpName && (i as WinMlEp)?.DeviceType == options.DeviceType).FirstOrDefault(); + if (selectedDevice != null) + { + DeviceComboBox.SelectedItem = selectedDevice; + } + else + { + DeviceComboBox.SelectedIndex = 0; + } + + ExecutionPolicyComboBox.SelectedIndex = 0; + CompileModelCheckBox.IsChecked = options.CompileModel; + WinMlModelOptionsButtonText.Text = (DeviceComboBox.SelectedItem as WinMlEp)?.ShortName; + segmentedControl.SelectedIndex = 1; + UpdateCompileModelVisibility(); + } + + // in case already saved options do not apply to this sample + _ = UpdateSampleOptions(); + } + + private void LoadSample(Sample? sampleToLoad) + { + sample = sampleToLoad; + + if (sample == null) + { + return; + } + + VisualStateManager.GoToState(this, "ModelSelected", true); + + // 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); + _ = App.AppData.AddMru( + new MostRecentlyUsedItem() + { + Type = MostRecentlyUsedItemType.Scenario, + ItemId = scenario!.Id, + Icon = scenario.Icon, + Description = scenario.Description, + SubItemId = modelDetails[0]!.Id, + DisplayName = scenario.Name + }, + modelDetails.Select(m => (m!.Id, m.HardwareAccelerators.First())).ToList()); + } + + private bool IsModelFromTypes(List? types, ModelDetails? model) + { + if (types == null && model == null) + { + return true; + } + + if (types == null || model == null) + { + return false; + } + + if (types.Contains(ModelType.LanguageModels) && model.IsLanguageModel()) + { + return true; + } + + List modelIds = []; + + foreach (var type in types) + { + modelIds.AddRange(ModelDetailsHelper.GetModelDetailsForModelType(type).Select(m => m.Id)); + if (App.AppData.TryGetUserAddedModelIds(type, out var ids)) + { + modelIds.AddRange(ids!); + } + } + + return modelIds.Any(id => id == model.Id); + } + + private void CopyButton_Click(object sender, RoutedEventArgs e) + { + var dataPackage = new DataPackage(); + dataPackage.SetText("aidevgallery://scenarios/" + scenario!.Id); + Clipboard.SetContentWithOptions(dataPackage, null); + } + + private void CodeToggle_Click(object sender, RoutedEventArgs args) + { + HandleCodePane(); + } + + private void HandleCodePane() + { + if (sample != null) + { + ToggleCodeButtonEvent.Log(sample.Name ?? string.Empty, CodeToggle.IsChecked == true); + } + + if (CodeToggle.IsChecked == true) + { + SampleContainer.ShowCode(); + } + else + { + SampleContainer.HideCode(); + } + } + + private void ExportSampleToggle_Click(object sender, RoutedEventArgs e) + { + if (sender is not Button button || sample == null) + { + return; + } + + _ = Generator.AskGenerateAndOpenAsync(sample, modelDetails.Where(m => m != null).Select(m => m!), App.AppData.WinMLSampleOptions, XamlRoot); + } + + private void ActionButtonsGrid_SizeChanged(object sender, SizeChangedEventArgs e) + { + // Calculate if the modelselectors collide with the export/code buttons + if ((ActionsButtonHolderPanel.ActualWidth + ButtonsPanel.ActualWidth) >= e.NewSize.Width) + { + VisualStateManager.GoToState(this, "NarrowLayout", true); + } + else + { + VisualStateManager.GoToState(this, "WideLayout", true); + } + } + + private void ModelBtn_Click(object sender, RoutedEventArgs e) + { + App.MainWindow.ModelPicker.Show(modelDetails.ToList()); + } + + private void ModelOrApiPicker_SelectedModelsChanged(object sender, List modelDetails) + { + HandleModelSelectionChanged(modelDetails); + } + + private void SampleSelection_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + var selectedSample = e.AddedItems + .OfType() + .ToList().FirstOrDefault(); + + LoadSample(selectedSample); + } + + private async void ApplySampleOptions(object sender, RoutedEventArgs e) + { + WinMLOptionsFlyout.Hide(); + await UpdateSampleOptions(); + LoadSample(sample); + } + + private async Task UpdateSampleOptions() + { + var oldOptions = App.AppData.WinMLSampleOptions; + + if (segmentedControl.SelectedIndex == 0) + { + var key = (ExecutionPolicyComboBox.SelectedItem as string) ?? executionProviderDevicePolicies.Keys.First(); + WinMlModelOptionsButtonText.Text = key; + App.AppData.WinMLSampleOptions = new WinMlSampleOptions(executionProviderDevicePolicies[key], null, false, null); + } + else + { + var device = (DeviceComboBox.SelectedItem as WinMlEp) ?? (DeviceComboBox.Items.First() as WinMlEp); + WinMlModelOptionsButtonText.Text = device!.ShortName; + App.AppData.WinMLSampleOptions = new WinMlSampleOptions(null, device.Name, CompileModelCheckBox.IsChecked!.Value, device.DeviceType); + } + + if (oldOptions == App.AppData.WinMLSampleOptions) + { + return; + } + + await App.AppData.SaveAsync(); + } + + private void WinMLOptionsFlyout_Opening(object sender, object e) + { + UpdateWinMLFlyout(); + UpdateCompileModelVisibility(); + } + + private void DeviceComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + UpdateCompileModelVisibility(); + } + + private void UpdateCompileModelVisibility() + { + var device = DeviceComboBox.SelectedItem as WinMlEp; + bool supported = device != null && WinMLHelpers.IsCompileModelSupported(device.DeviceType); + CompileModelCheckBox.Visibility = supported ? Visibility.Visible : Visibility.Collapsed; + if (!supported) + { + CompileModelCheckBox.IsChecked = false; + } + } } \ No newline at end of file diff --git a/AIDevGallery/Samples/SharedCode/StableDiffusionCode/VaeDecoder.cs b/AIDevGallery/Samples/SharedCode/StableDiffusionCode/VaeDecoder.cs index 5304bee6..65409240 100644 --- a/AIDevGallery/Samples/SharedCode/StableDiffusionCode/VaeDecoder.cs +++ b/AIDevGallery/Samples/SharedCode/StableDiffusionCode/VaeDecoder.cs @@ -59,9 +59,6 @@ private Task GetInferenceSession(StableDiffusionConfig config, sessionOptions.AddFreeDimensionOverrideByName("batch", 1); sessionOptions.AddFreeDimensionOverrideByName("channels", 4); - sessionOptions.AddFreeDimensionOverrideByName("height", config.Height / 8); - sessionOptions.AddFreeDimensionOverrideByName("width", config.Width / 8); - if (policy != null) { sessionOptions.SetEpSelectionPolicy(policy.Value); diff --git a/AIDevGallery/Samples/SharedCode/WinMLHelpers.cs b/AIDevGallery/Samples/SharedCode/WinMLHelpers.cs index 5538b473..2e76c189 100644 --- a/AIDevGallery/Samples/SharedCode/WinMLHelpers.cs +++ b/AIDevGallery/Samples/SharedCode/WinMLHelpers.cs @@ -1,95 +1,162 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.ML.OnnxRuntime; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace AIDevGallery.Samples.SharedCode; - -internal static class WinMLHelpers -{ - public static bool AppendExecutionProviderFromEpName(this SessionOptions sessionOptions, string epName, string? deviceType, OrtEnv? environment = null) - { - if (epName == "CPU") - { - // No need to append CPU execution provider - return true; - } - - environment ??= OrtEnv.Instance(); - var epDeviceMap = GetEpDeviceMap(environment); - - if (epDeviceMap.TryGetValue(epName, out var devices)) - { - Dictionary epOptions = new(StringComparer.OrdinalIgnoreCase); - switch (epName) - { - case "DmlExecutionProvider": - // 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": - var device = devices.Where(d => d.HardwareDevice.Type.ToString().Equals(deviceType, StringComparison.Ordinal)).FirstOrDefault(); - sessionOptions.AppendExecutionProvider(environment, [device], epOptions); - return true; - case "QNNExecutionProvider": - // Configure performance mode for QNN EP - epOptions["htp_performance_mode"] = "high_performance"; - break; - default: - break; - } - - sessionOptions.AppendExecutionProvider(environment, devices, epOptions); - return true; - } - - return false; - } - - public static string? GetCompiledModel(this SessionOptions sessionOptions, string modelPath, string device) - { - var compiledModelPath = Path.Combine(Path.GetDirectoryName(modelPath) ?? string.Empty, Path.GetFileNameWithoutExtension(modelPath)) + $".{device}.onnx"; - - if (!File.Exists(compiledModelPath)) - { - using OrtModelCompilationOptions compilationOptions = new(sessionOptions); - compilationOptions.SetInputModelPath(modelPath); - compilationOptions.SetOutputModelPath(compiledModelPath); - compilationOptions.CompileModel(); - } - - if (File.Exists(compiledModelPath)) - { - return compiledModelPath; - } - - return null; - } - - public static Dictionary> GetEpDeviceMap(OrtEnv? environment = null) - { - environment ??= OrtEnv.Instance(); - IReadOnlyList epDevices = environment.GetEpDevices(); - Dictionary> epDeviceMap = new(StringComparer.OrdinalIgnoreCase); - - foreach (OrtEpDevice device in epDevices) - { - string name = device.EpName; - - if (!epDeviceMap.TryGetValue(name, out List? value)) - { - value = []; - epDeviceMap[name] = value; - } - - value.Add(device); - } - - return epDeviceMap; - } +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.ML.OnnxRuntime; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; + +namespace AIDevGallery.Samples.SharedCode; + +internal static class WinMLHelpers +{ + public static bool AppendExecutionProviderFromEpName(this SessionOptions sessionOptions, string epName, string? deviceType, OrtEnv? environment = null) + { + if (epName == "CPU") + { + // No need to append CPU execution provider + return true; + } + + environment ??= OrtEnv.Instance(); + var epDeviceMap = GetEpDeviceMap(environment); + + if (epDeviceMap.TryGetValue(epName, out var devices)) + { + Dictionary epOptions = new(StringComparer.OrdinalIgnoreCase); + switch (epName) + { + case "DmlExecutionProvider": + // 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": + var device = devices.Where(d => d.HardwareDevice.Type.ToString().Equals(deviceType, StringComparison.Ordinal)).FirstOrDefault(); + sessionOptions.AppendExecutionProvider(environment, [device], epOptions); + return true; + case "QNNExecutionProvider": + // Configure performance mode for QNN EP + epOptions["htp_performance_mode"] = "high_performance"; + break; + default: + break; + } + + sessionOptions.AppendExecutionProvider(environment, devices, epOptions); + return true; + } + + return false; + } + + public static string? GetCompiledModel(this SessionOptions sessionOptions, string modelPath, string device) + { + if (IsCompileModelSupported(device) == false) + { + return null; + } + + var compiledModelPath = Path.Combine(Path.GetDirectoryName(modelPath) ?? string.Empty, Path.GetFileNameWithoutExtension(modelPath)) + $".{device}.onnx"; + + if (!File.Exists(compiledModelPath)) + { + try + { + using OrtModelCompilationOptions compilationOptions = new(sessionOptions); + compilationOptions.SetInputModelPath(modelPath); + compilationOptions.SetOutputModelPath(compiledModelPath); + compilationOptions.CompileModel(); + } + catch (Exception ex) + { + Debug.WriteLine($"WARNING: Model compilation failed for {device}: {ex.Message}"); + + // Clean up any empty or corrupted files that may have been created + if (File.Exists(compiledModelPath)) + { + try + { + File.Delete(compiledModelPath); + Debug.WriteLine($"Deleted corrupted compiled model file: {compiledModelPath}"); + } + catch + { + // Ignore deletion errors + } + } + + return null; + } + } + + // Validate that the compiled model file exists and is not empty + if (File.Exists(compiledModelPath)) + { + var fileInfo = new FileInfo(compiledModelPath); + if (fileInfo.Length > 0) + { + return compiledModelPath; + } + } + + return null; + } + + public static Dictionary> GetEpDeviceMap(OrtEnv? environment = null) + { + environment ??= OrtEnv.Instance(); + IReadOnlyList epDevices = environment.GetEpDevices(); + Dictionary> epDeviceMap = new(StringComparer.OrdinalIgnoreCase); + + foreach (OrtEpDevice device in epDevices) + { + string name = device.EpName; + + if (!epDeviceMap.TryGetValue(name, out List? value)) + { + value = []; + epDeviceMap[name] = value; + } + + value.Add(device); + } + + return epDeviceMap; + } + + /// + /// Determines whether model compilation should be surfaced based on device type. + /// + /// Device type string (e.g., "CPU", "GPU", "NPU"). + /// False for CPU; true for other known accelerator types. + public static bool IsCompileModelSupported(string? deviceType) + { + if (string.IsNullOrWhiteSpace(deviceType)) + { + return false; + } + + // NOTE: Skip compilation for the CPU execution provider. + // - EPContext is an EP-specific offline-compiled/partitioned graph artifact that requires the + // execution provider to implement serialization/deserialization of its optimized graph. + // - ONNX Runtime's CPU EP does NOT implement EPContext model generation or loading. Invoking + // OrtModelCompilationOptions.CompileModel() for CPU attempts to emit a "*.CPU.onnx" EPContext + // artifact, which fails (commonly with InvalidProtobuf) because no EPContext is produced/understood + // by the CPU EP. + // Behavior: + // - For CPU, we return null here so callers fall back to the original ONNX model without attempting + // EPContext compilation. + // - Other EPs (e.g., DirectML, OpenVINO, QNN) may support EPContext depending on the ORT build, + // platform drivers, and hardware; for those we allow compilation to proceed. + if (string.Equals(deviceType, "CPU", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + return string.Equals(deviceType, "GPU", StringComparison.OrdinalIgnoreCase) + || string.Equals(deviceType, "NPU", StringComparison.OrdinalIgnoreCase); + } } \ No newline at end of file