diff --git a/examples/csharp/Genny/.gitignore b/examples/csharp/Genny/.gitignore
new file mode 100644
index 000000000..496192431
--- /dev/null
+++ b/examples/csharp/Genny/.gitignore
@@ -0,0 +1,346 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+**/Properties/launchSettings.json
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# CodeRush
+.cr/
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+/docs/build
+src/TensorFlowNET.Native/bazel-*
+src/TensorFlowNET.Native/c_api.h
+/.vscode
+test/TensorFlowNET.Examples/mnist
+
+
+# training model resources
+.resources
+/redist
+*.xml
+*.xsd
+
+# docs
+site/
+
+docker-test-output/*
diff --git a/examples/csharp/Genny/Assets/Screenshot1.PNG b/examples/csharp/Genny/Assets/Screenshot1.PNG
new file mode 100644
index 000000000..59ef9f19a
Binary files /dev/null and b/examples/csharp/Genny/Assets/Screenshot1.PNG differ
diff --git a/examples/csharp/Genny/Assets/Screenshot2.PNG b/examples/csharp/Genny/Assets/Screenshot2.PNG
new file mode 100644
index 000000000..d1c635481
Binary files /dev/null and b/examples/csharp/Genny/Assets/Screenshot2.PNG differ
diff --git a/examples/csharp/Genny/Genny.sln b/examples/csharp/Genny/Genny.sln
new file mode 100644
index 000000000..3a30e258e
--- /dev/null
+++ b/examples/csharp/Genny/Genny.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.9.34622.214
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Genny", "Genny\Genny.csproj", "{831197BD-63C7-4C0F-AD0E-4F6783CBB5C0}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug_Cuda|x64 = Debug_Cuda|x64
+ Debug|x64 = Debug|x64
+ Release_Cuda|x64 = Release_Cuda|x64
+ Release|x64 = Release|x64
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {831197BD-63C7-4C0F-AD0E-4F6783CBB5C0}.Debug_Cuda|x64.ActiveCfg = Debug_Cuda|x64
+ {831197BD-63C7-4C0F-AD0E-4F6783CBB5C0}.Debug_Cuda|x64.Build.0 = Debug_Cuda|x64
+ {831197BD-63C7-4C0F-AD0E-4F6783CBB5C0}.Debug|x64.ActiveCfg = Debug|x64
+ {831197BD-63C7-4C0F-AD0E-4F6783CBB5C0}.Debug|x64.Build.0 = Debug|x64
+ {831197BD-63C7-4C0F-AD0E-4F6783CBB5C0}.Release_Cuda|x64.ActiveCfg = Release_Cuda|x64
+ {831197BD-63C7-4C0F-AD0E-4F6783CBB5C0}.Release_Cuda|x64.Build.0 = Release_Cuda|x64
+ {831197BD-63C7-4C0F-AD0E-4F6783CBB5C0}.Release|x64.ActiveCfg = Release|x64
+ {831197BD-63C7-4C0F-AD0E-4F6783CBB5C0}.Release|x64.Build.0 = Release|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {A7159277-CA72-45A9-8327-E3BF29214643}
+ EndGlobalSection
+EndGlobal
diff --git a/examples/csharp/Genny/Genny/App.xaml b/examples/csharp/Genny/Genny/App.xaml
new file mode 100644
index 000000000..ec5ea8fd1
--- /dev/null
+++ b/examples/csharp/Genny/Genny/App.xaml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
diff --git a/examples/csharp/Genny/Genny/App.xaml.cs b/examples/csharp/Genny/Genny/App.xaml.cs
new file mode 100644
index 000000000..b6e61e540
--- /dev/null
+++ b/examples/csharp/Genny/Genny/App.xaml.cs
@@ -0,0 +1,11 @@
+using System.Windows;
+
+namespace Genny
+{
+ ///
+ /// Interaction logic for App.xaml
+ ///
+ public partial class App : Application
+ {
+ }
+}
diff --git a/examples/csharp/Genny/Genny/AssemblyInfo.cs b/examples/csharp/Genny/Genny/AssemblyInfo.cs
new file mode 100644
index 000000000..b0ec82757
--- /dev/null
+++ b/examples/csharp/Genny/Genny/AssemblyInfo.cs
@@ -0,0 +1,10 @@
+using System.Windows;
+
+[assembly: ThemeInfo(
+ ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
+ //(used if a resource is not found in the page,
+ // or application resource dictionaries)
+ ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
+ //(used if a resource is not found in the page,
+ // app, or any theme specific resource dictionaries)
+)]
diff --git a/examples/csharp/Genny/Genny/Controls/SearchOptionsControl.xaml b/examples/csharp/Genny/Genny/Controls/SearchOptionsControl.xaml
new file mode 100644
index 000000000..2983243b5
--- /dev/null
+++ b/examples/csharp/Genny/Genny/Controls/SearchOptionsControl.xaml
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/csharp/Genny/Genny/Controls/SearchOptionsControl.xaml.cs b/examples/csharp/Genny/Genny/Controls/SearchOptionsControl.xaml.cs
new file mode 100644
index 000000000..6386a43de
--- /dev/null
+++ b/examples/csharp/Genny/Genny/Controls/SearchOptionsControl.xaml.cs
@@ -0,0 +1,30 @@
+using Genny.ViewModel;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace Genny.Controls
+{
+ ///
+ /// Interaction logic for SearchOptionsControl.xaml
+ ///
+ public partial class SearchOptionsControl : UserControl
+ {
+ public SearchOptionsControl()
+ {
+ InitializeComponent();
+ }
+
+ public static readonly DependencyProperty SearchOptionsProperty =
+ DependencyProperty.Register(nameof(SearchOptions), typeof(SearchOptionsModel), typeof(SearchOptionsControl), new PropertyMetadata(new SearchOptionsModel()));
+
+
+ ///
+ /// Gets or sets the search options.
+ ///
+ public SearchOptionsModel SearchOptions
+ {
+ get { return (SearchOptionsModel)GetValue(SearchOptionsProperty); }
+ set { SetValue(SearchOptionsProperty, value); }
+ }
+ }
+}
diff --git a/examples/csharp/Genny/Genny/Extensions.cs b/examples/csharp/Genny/Genny/Extensions.cs
new file mode 100644
index 000000000..5074df1e2
--- /dev/null
+++ b/examples/csharp/Genny/Genny/Extensions.cs
@@ -0,0 +1,50 @@
+using Genny.ViewModel;
+using Microsoft.ML.OnnxRuntimeGenAI;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows;
+
+namespace Genny
+{
+ internal static class Extensions
+ {
+
+ ///
+ /// Applies the search options to the generator parameters.
+ ///
+ /// The generator parameters.
+ /// The search options.
+ internal static void ApplySearchOptions(this GeneratorParams generatorParams, SearchOptionsModel searchOptions)
+ {
+ generatorParams.SetSearchOption("top_p", searchOptions.TopP);
+ generatorParams.SetSearchOption("top_k", searchOptions.TopK);
+ generatorParams.SetSearchOption("temperature", searchOptions.Temperature);
+ generatorParams.SetSearchOption("repetition_penalty", searchOptions.RepetitionPenalty);
+ generatorParams.SetSearchOption("past_present_share_buffer", searchOptions.PastPresentShareBuffer);
+ generatorParams.SetSearchOption("num_return_sequences", searchOptions.NumReturnSequences);
+ generatorParams.SetSearchOption("no_repeat_ngram_size", searchOptions.NoRepeatNgramSize);
+ generatorParams.SetSearchOption("min_length", searchOptions.MinLength);
+ generatorParams.SetSearchOption("max_length", searchOptions.MaxLength);
+ generatorParams.SetSearchOption("length_penalty", searchOptions.LengthPenalty);
+ generatorParams.SetSearchOption("early_stopping", searchOptions.EarlyStopping);
+ generatorParams.SetSearchOption("do_sample", searchOptions.DoSample);
+ generatorParams.SetSearchOption("diversity_penalty", searchOptions.DiversityPenalty);
+ }
+
+ internal static Task EncodeAsync(this Tokenizer tokenizer, string input, CancellationToken cancellationToken = default)
+ {
+ return Application.Current.Dispatcher.Invoke(() =>
+ {
+ return Task.Run(() => tokenizer.Encode(input), cancellationToken);
+ });
+ }
+
+ internal static Task DecodeAsync(this Tokenizer tokenizer, int[] input, CancellationToken cancellationToken = default)
+ {
+ return Application.Current.Dispatcher.Invoke(() =>
+ {
+ return Task.Run(() => tokenizer.Decode(input), cancellationToken);
+ });
+ }
+ }
+}
diff --git a/examples/csharp/Genny/Genny/Genny.csproj b/examples/csharp/Genny/Genny/Genny.csproj
new file mode 100644
index 000000000..d4928ad9f
--- /dev/null
+++ b/examples/csharp/Genny/Genny/Genny.csproj
@@ -0,0 +1,25 @@
+
+
+
+ WinExe
+ net6.0-windows
+ disable
+ disable
+ true
+ true
+ x64
+ x64
+ Debug;Release;Debug_Cuda;Release_Cuda;
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/csharp/Genny/Genny/Images/robot.png b/examples/csharp/Genny/Genny/Images/robot.png
new file mode 100644
index 000000000..96edd0fb1
Binary files /dev/null and b/examples/csharp/Genny/Genny/Images/robot.png differ
diff --git a/examples/csharp/Genny/Genny/Images/user.png b/examples/csharp/Genny/Genny/Images/user.png
new file mode 100644
index 000000000..dcaf32f59
Binary files /dev/null and b/examples/csharp/Genny/Genny/Images/user.png differ
diff --git a/examples/csharp/Genny/Genny/MainWindow.xaml b/examples/csharp/Genny/Genny/MainWindow.xaml
new file mode 100644
index 000000000..3d721f96b
--- /dev/null
+++ b/examples/csharp/Genny/Genny/MainWindow.xaml
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/csharp/Genny/Genny/MainWindow.xaml.cs b/examples/csharp/Genny/Genny/MainWindow.xaml.cs
new file mode 100644
index 000000000..10522632a
--- /dev/null
+++ b/examples/csharp/Genny/Genny/MainWindow.xaml.cs
@@ -0,0 +1,132 @@
+using Genny.Utils;
+using Genny.ViewModel;
+using Microsoft.ML.OnnxRuntimeGenAI;
+using System;
+using System.ComponentModel;
+using System.IO;
+using System.Runtime.CompilerServices;
+using System.Text.Json;
+using System.Threading.Tasks;
+using System.Windows;
+
+namespace Genny
+{
+ ///
+ /// Interaction logic for MainWindow.xaml
+ ///
+ public partial class MainWindow : Window, INotifyPropertyChanged
+ {
+ private Model _model;
+ private Tokenizer _tokenizer;
+ private ConfigurationModel _configuration;
+ private string _modelPath = "D:\\Repositories\\phi2_onnx";
+ private bool _isModelLoaded;
+
+ public MainWindow()
+ {
+ OpenModelCommand = new RelayCommand(OpenModelAsync);
+ LoadModelCommand = new RelayCommand(LoadModelAsync, CanExecuteLoadModel);
+ InitializeComponent();
+ }
+
+ public RelayCommand OpenModelCommand { get; }
+ public RelayCommand LoadModelCommand { get; }
+
+ public Model Model
+ {
+ get { return _model; }
+ set { _model = value; NotifyPropertyChanged(); }
+ }
+
+ public Tokenizer Tokenizer
+ {
+ get { return _tokenizer; }
+ set { _tokenizer = value; NotifyPropertyChanged(); }
+ }
+
+ public ConfigurationModel Configuration
+ {
+ get { return _configuration; }
+ set { _configuration = value; NotifyPropertyChanged(); }
+ }
+
+
+ public bool IsModelLoaded
+ {
+ get { return _isModelLoaded; }
+ set { _isModelLoaded = value; NotifyPropertyChanged(); }
+ }
+
+ public string ModelPath
+ {
+ get { return _modelPath; }
+ set { _modelPath = value; NotifyPropertyChanged(); }
+ }
+
+
+ private Task OpenModelAsync()
+ {
+ var folderBrowserDialog = new System.Windows.Forms.FolderBrowserDialog
+ {
+ Description = "Model Folder Path",
+ UseDescriptionForTitle = true,
+ };
+ var dialogResult = folderBrowserDialog.ShowDialog();
+ if (dialogResult == System.Windows.Forms.DialogResult.OK)
+ ModelPath = folderBrowserDialog.SelectedPath;
+
+ return Task.CompletedTask;
+ }
+
+
+ private async Task LoadModelAsync()
+ {
+ await UnloadModelAsync();
+ try
+ {
+ Configuration = await LoadConfigAsync(ModelPath);
+ await Task.Run(() =>
+ {
+ Model = new Model(ModelPath);
+ Tokenizer = new Tokenizer(_model);
+ });
+ IsModelLoaded = true;
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show(ex.Message, "Model Load Error", MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ }
+
+
+ private bool CanExecuteLoadModel()
+ {
+ return !string.IsNullOrWhiteSpace(ModelPath);
+ }
+
+
+ private Task UnloadModelAsync()
+ {
+ _model?.Dispose();
+ _tokenizer?.Dispose();
+ IsModelLoaded = false;
+ return Task.CompletedTask;
+ }
+
+
+ private static async Task LoadConfigAsync(string modelPath)
+ {
+ var configPath = Path.Combine(modelPath, "genai_config.json");
+ var configJson = await File.ReadAllTextAsync(configPath);
+ return JsonSerializer.Deserialize(configJson);
+ }
+
+ #region INotifyPropertyChanged
+ public event PropertyChangedEventHandler PropertyChanged;
+ public void NotifyPropertyChanged([CallerMemberName] string property = "")
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
+ }
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/examples/csharp/Genny/Genny/Utils/AutoScrollBehavior.cs b/examples/csharp/Genny/Genny/Utils/AutoScrollBehavior.cs
new file mode 100644
index 000000000..afc99ee71
--- /dev/null
+++ b/examples/csharp/Genny/Genny/Utils/AutoScrollBehavior.cs
@@ -0,0 +1,47 @@
+using System.Windows;
+using System.Windows.Controls;
+
+namespace Genny.Utils
+{
+ ///
+ /// Behaviour to auto scroll to the bottom went the content changes, e.g appending text
+ ///
+ public static class AutoScrollBehavior
+ {
+ public static readonly DependencyProperty AutoScrollProperty =
+ DependencyProperty.RegisterAttached("AutoScroll", typeof(bool), typeof(AutoScrollBehavior), new PropertyMetadata(false, AutoScrollPropertyChanged));
+
+ public static void AutoScrollPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
+ {
+ var scrollViewer = obj as ScrollViewer;
+ if (scrollViewer != null && (bool)args.NewValue)
+ {
+ scrollViewer.ScrollChanged += ScrollViewer_ScrollChanged;
+ scrollViewer.ScrollToEnd();
+ }
+ else
+ {
+ scrollViewer.ScrollChanged -= ScrollViewer_ScrollChanged;
+ }
+ }
+
+ private static void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
+ {
+ if (e.ExtentHeightChange != 0)
+ {
+ var scrollViewer = sender as ScrollViewer;
+ scrollViewer?.ScrollToBottom();
+ }
+ }
+
+ public static bool GetAutoScroll(DependencyObject obj)
+ {
+ return (bool)obj.GetValue(AutoScrollProperty);
+ }
+
+ public static void SetAutoScroll(DependencyObject obj, bool value)
+ {
+ obj.SetValue(AutoScrollProperty, value);
+ }
+ }
+}
diff --git a/examples/csharp/Genny/Genny/Utils/RelayCommand.cs b/examples/csharp/Genny/Genny/Utils/RelayCommand.cs
new file mode 100644
index 000000000..131f27b01
--- /dev/null
+++ b/examples/csharp/Genny/Genny/Utils/RelayCommand.cs
@@ -0,0 +1,111 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows.Input;
+
+namespace Genny.Utils
+{
+ ///
+ /// Basic Relay command implemtation
+ ///
+ ///
+ public class RelayCommand : ICommand
+ {
+ private readonly Func _execute;
+ private readonly Func _canExecute;
+ private long _isExecuting;
+
+ public RelayCommand(Func execute, Func canExecute = null)
+ {
+ _execute = execute;
+ _canExecute = canExecute ?? (() => true);
+ }
+
+ public event EventHandler CanExecuteChanged
+ {
+ add { CommandManager.RequerySuggested += value; }
+ remove { CommandManager.RequerySuggested -= value; }
+ }
+
+ public void RaiseCanExecuteChanged()
+ {
+ CommandManager.InvalidateRequerySuggested();
+ }
+
+ public bool CanExecute(object parameter)
+ {
+ if (Interlocked.Read(ref _isExecuting) != 0)
+ return false;
+
+ return _canExecute();
+ }
+
+ public async void Execute(object parameter)
+ {
+ Interlocked.Exchange(ref _isExecuting, 1);
+ RaiseCanExecuteChanged();
+
+ try
+ {
+ await _execute();
+ }
+ finally
+ {
+ Interlocked.Exchange(ref _isExecuting, 0);
+ RaiseCanExecuteChanged();
+ }
+ }
+ }
+
+ ///
+ /// Basic Relay command with type argument implemtation
+ ///
+ ///
+ public class RelayCommand : ICommand
+ {
+ private readonly Func _execute;
+ private readonly Func _canExecute;
+ private long _isExecuting;
+
+ public RelayCommand(Func execute, Func canExecute = null)
+ {
+ _execute = execute;
+ _canExecute = canExecute ?? (o => true);
+ }
+
+ public event EventHandler CanExecuteChanged
+ {
+ add { CommandManager.RequerySuggested += value; }
+ remove { CommandManager.RequerySuggested -= value; }
+ }
+
+ public void RaiseCanExecuteChanged()
+ {
+ CommandManager.InvalidateRequerySuggested();
+ }
+
+ public bool CanExecute(object parameter)
+ {
+ if (Interlocked.Read(ref _isExecuting) != 0)
+ return false;
+
+ return _canExecute(parameter is T r ? r : default);
+ }
+
+ public async void Execute(object parameter)
+ {
+ Interlocked.Exchange(ref _isExecuting, 1);
+ RaiseCanExecuteChanged();
+
+ try
+ {
+ await _execute((T)parameter);
+ }
+ finally
+ {
+ Interlocked.Exchange(ref _isExecuting, 0);
+ RaiseCanExecuteChanged();
+ }
+ }
+ }
+}
diff --git a/examples/csharp/Genny/Genny/Utils/ShiftEnterBehavior.cs b/examples/csharp/Genny/Genny/Utils/ShiftEnterBehavior.cs
new file mode 100644
index 000000000..1d922c2fd
--- /dev/null
+++ b/examples/csharp/Genny/Genny/Utils/ShiftEnterBehavior.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+
+namespace Genny.Utils
+{
+ ///
+ /// Behaviour to use Shift + Enfer to add a new line to a TextBox allowing IsDefault Commands to be fired on Enter
+ ///
+ public class ShiftEnterBehavior
+ {
+ public static readonly DependencyProperty EnableProperty =
+ DependencyProperty.RegisterAttached("Enable", typeof(bool), typeof(ShiftEnterBehavior), new PropertyMetadata(false, OnEnableChanged));
+
+ public static bool GetEnable(DependencyObject obj)
+ {
+ return (bool)obj.GetValue(EnableProperty);
+ }
+
+ public static void SetEnable(DependencyObject obj, bool value)
+ {
+ obj.SetValue(EnableProperty, value);
+ }
+
+ private static void OnEnableChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
+ {
+ if (obj is TextBox textBox)
+ {
+ bool attach = (bool)e.NewValue;
+
+ if (attach)
+ {
+ DataObject.AddPastingHandler(textBox, TextBox_OnPaste);
+ textBox.PreviewKeyDown += TextBox_PreviewKeyDown;
+ }
+ else
+ {
+ DataObject.RemovePastingHandler(textBox, TextBox_OnPaste);
+ textBox.PreviewKeyDown -= TextBox_PreviewKeyDown;
+ }
+ }
+ }
+
+ private static void TextBox_PreviewKeyDown(object sender, KeyEventArgs e)
+ {
+ // If Shift + Enter is pressed append a new line
+ if (e.Key == Key.Enter && Keyboard.Modifiers == ModifierKeys.Shift && sender is TextBox textBox)
+ {
+ e.Handled = true;
+ textBox.AppendText(Environment.NewLine);
+ textBox.CaretIndex = textBox.Text.Length;
+ }
+ }
+
+ private static void TextBox_OnPaste(object sender, DataObjectPastingEventArgs e)
+ {
+ // Because AcceptsReturn is false we need to intercept paste to allow new lines
+ if (sender is TextBox textBox && e.DataObject.GetDataPresent(DataFormats.UnicodeText))
+ {
+ e.CancelCommand();
+ textBox.AppendText(e.DataObject.GetData(DataFormats.UnicodeText) as string);
+ }
+ }
+ }
+}
diff --git a/examples/csharp/Genny/Genny/ViewModel/ConfigurationModel.cs b/examples/csharp/Genny/Genny/ViewModel/ConfigurationModel.cs
new file mode 100644
index 000000000..5e78ff95b
--- /dev/null
+++ b/examples/csharp/Genny/Genny/ViewModel/ConfigurationModel.cs
@@ -0,0 +1,13 @@
+using System.Text.Json.Serialization;
+
+namespace Genny.ViewModel
+{
+ public class ConfigurationModel
+ {
+ [JsonPropertyName("model")]
+ public ModelOptionsModel ModelOptions { get; set; }
+
+ [JsonPropertyName("search")]
+ public SearchOptionsModel SearchOptions { get; set; }
+ }
+}
diff --git a/examples/csharp/Genny/Genny/ViewModel/ModelOptionsModel.cs b/examples/csharp/Genny/Genny/ViewModel/ModelOptionsModel.cs
new file mode 100644
index 000000000..bb7fc341d
--- /dev/null
+++ b/examples/csharp/Genny/Genny/ViewModel/ModelOptionsModel.cs
@@ -0,0 +1,14 @@
+using System.Text.Json.Serialization;
+
+namespace Genny.ViewModel
+{
+ public class ModelOptionsModel
+ {
+ [JsonPropertyName("type")]
+ public string Type { get; set; }
+
+ [JsonPropertyName("context_length")]
+ public int ContextLength { get; set; }
+ }
+
+}
diff --git a/examples/csharp/Genny/Genny/ViewModel/ResultModel.cs b/examples/csharp/Genny/Genny/ViewModel/ResultModel.cs
new file mode 100644
index 000000000..b51bd66db
--- /dev/null
+++ b/examples/csharp/Genny/Genny/ViewModel/ResultModel.cs
@@ -0,0 +1,34 @@
+using System;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+
+namespace Genny.ViewModel
+{
+ public class ResultModel : INotifyPropertyChanged
+ {
+ private string _content;
+ private bool _isUserInput;
+
+ public string Content
+ {
+ get { return _content; }
+ set { _content = value; NotifyPropertyChanged(); }
+ }
+
+ public bool IsUserInput
+ {
+ get { return _isUserInput; }
+ set { _isUserInput = value; NotifyPropertyChanged(); }
+ }
+
+ public DateTime Timestamp { get; } = DateTime.Now;
+
+ #region INotifyPropertyChanged
+ public event PropertyChangedEventHandler PropertyChanged;
+ public void NotifyPropertyChanged([CallerMemberName] string property = "")
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
+ }
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/examples/csharp/Genny/Genny/ViewModel/SearchOptionsModel.cs b/examples/csharp/Genny/Genny/ViewModel/SearchOptionsModel.cs
new file mode 100644
index 000000000..2fe6b3ab7
--- /dev/null
+++ b/examples/csharp/Genny/Genny/ViewModel/SearchOptionsModel.cs
@@ -0,0 +1,132 @@
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using System.Text.Json.Serialization;
+
+namespace Genny.ViewModel
+{
+ public class SearchOptionsModel : INotifyPropertyChanged
+ {
+ private int _topK = 50;
+ private float _topP = 0.9f;
+ private float _temperature = 1;
+ private float _repetitionPenalty = 1;
+ private bool _pastPresentShareBuffer = false;
+ private int _numReturnSequences = 1;
+ private int _numBeams = 1;
+ private int _noRepeatNgramSize = 0;
+ private int _minLength = 0;
+ private int _maxLength = 200;
+ private float _lengthPenalty = 1;
+ private bool _earlyStopping = true;
+ private bool _doSample = false;
+ private float _diversityPenalty = 0;
+
+ [JsonPropertyName("top_k")]
+ public int TopK
+ {
+ get { return _topK; }
+ set { _topK = value; NotifyPropertyChanged(); }
+ }
+
+ [JsonPropertyName("top_p")]
+ public float TopP
+ {
+ get { return _topP; }
+ set { _topP = value; NotifyPropertyChanged(); }
+ }
+
+ [JsonPropertyName("temperature")]
+ public float Temperature
+ {
+ get { return _temperature; }
+ set { _temperature = value; NotifyPropertyChanged(); }
+ }
+
+ [JsonPropertyName("repetition_penalty")]
+ public float RepetitionPenalty
+ {
+ get { return _repetitionPenalty; }
+ set { _repetitionPenalty = value; NotifyPropertyChanged(); }
+ }
+
+ [JsonPropertyName("past_present_share_buffer")]
+ public bool PastPresentShareBuffer
+ {
+ get { return _pastPresentShareBuffer; }
+ set { _pastPresentShareBuffer = value; NotifyPropertyChanged(); }
+ }
+
+ [JsonPropertyName("num_return_sequences")]
+ public int NumReturnSequences
+ {
+ get { return _numReturnSequences; }
+ set { _numReturnSequences = value; NotifyPropertyChanged(); }
+ }
+
+ [JsonPropertyName("num_beams")]
+ public int NumBeams
+ {
+ get { return _numBeams; }
+ set { _numBeams = value; NotifyPropertyChanged(); }
+ }
+
+ [JsonPropertyName("no_repeat_ngram_size")]
+ public int NoRepeatNgramSize
+ {
+ get { return _noRepeatNgramSize; }
+ set { _noRepeatNgramSize = value; NotifyPropertyChanged(); }
+ }
+
+ [JsonPropertyName("min_length")]
+ public int MinLength
+ {
+ get { return _minLength; }
+ set { _minLength = value; NotifyPropertyChanged(); }
+ }
+
+ [JsonPropertyName("max_length")]
+ public int MaxLength
+ {
+ get { return _maxLength; }
+ set { _maxLength = value; NotifyPropertyChanged(); }
+ }
+
+ [JsonPropertyName("length_penalty")]
+ public float LengthPenalty
+ {
+ get { return _lengthPenalty; }
+ set { _lengthPenalty = value; NotifyPropertyChanged(); }
+ }
+
+ [JsonPropertyName("diversity_penalty")]
+ public float DiversityPenalty
+ {
+ get { return _diversityPenalty; }
+ set { _diversityPenalty = value; NotifyPropertyChanged(); }
+ }
+
+ [JsonPropertyName("early_stopping")]
+ public bool EarlyStopping
+ {
+ get { return _earlyStopping; }
+ set { _earlyStopping = value; NotifyPropertyChanged(); }
+ }
+
+ [JsonPropertyName("do_sample")]
+ public bool DoSample
+ {
+ get { return _doSample; }
+ set { _doSample = value; NotifyPropertyChanged(); }
+ }
+
+
+
+ #region INotifyPropertyChanged
+ public event PropertyChangedEventHandler PropertyChanged;
+ public void NotifyPropertyChanged([CallerMemberName] string property = "")
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
+ }
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/examples/csharp/Genny/Genny/ViewModel/TokenModel.cs b/examples/csharp/Genny/Genny/ViewModel/TokenModel.cs
new file mode 100644
index 000000000..a87d9a50e
--- /dev/null
+++ b/examples/csharp/Genny/Genny/ViewModel/TokenModel.cs
@@ -0,0 +1,4 @@
+namespace Genny.ViewModel
+{
+ public record TokenModel(int Id, string Content);
+}
\ No newline at end of file
diff --git a/examples/csharp/Genny/Genny/Views/StatefulView.xaml b/examples/csharp/Genny/Genny/Views/StatefulView.xaml
new file mode 100644
index 000000000..71473e6d8
--- /dev/null
+++ b/examples/csharp/Genny/Genny/Views/StatefulView.xaml
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/csharp/Genny/Genny/Views/StatefulView.xaml.cs b/examples/csharp/Genny/Genny/Views/StatefulView.xaml.cs
new file mode 100644
index 000000000..d399d3005
--- /dev/null
+++ b/examples/csharp/Genny/Genny/Views/StatefulView.xaml.cs
@@ -0,0 +1,196 @@
+using Genny.Utils;
+using Genny.ViewModel;
+using Microsoft.ML.OnnxRuntimeGenAI;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace Genny.Views
+{
+ ///
+ /// Interaction logic for StatefulView.xaml
+ ///
+ public partial class StatefulView : UserControl, INotifyPropertyChanged
+ {
+ private string _prompt;
+ private readonly List _pastTokens;
+ private CancellationTokenSource _cancellationTokenSource;
+
+ public StatefulView()
+ {
+ _pastTokens = new List();
+ ClearCommand = new RelayCommand(ClearAsync);
+ CancelCommand = new RelayCommand(CancelAsync);
+ GenerateCommand = new RelayCommand(GenerateAsync, CanExecuteGenerate);
+ ResultHistory = new ObservableCollection();
+ InitializeComponent();
+ }
+
+ public static readonly DependencyProperty ModelProperty =
+ DependencyProperty.Register(nameof(Model), typeof(Model), typeof(StatefulView));
+
+ public static readonly DependencyProperty TokenizerProperty =
+ DependencyProperty.Register(nameof(Tokenizer), typeof(Tokenizer), typeof(StatefulView));
+
+ public static readonly DependencyProperty ModelOptionsProperty =
+ DependencyProperty.Register(nameof(ModelOptions), typeof(ModelOptionsModel), typeof(StatefulView));
+
+ public static readonly DependencyProperty SearchOptionsProperty =
+ DependencyProperty.Register(nameof(SearchOptions), typeof(SearchOptionsModel), typeof(StatefulView));
+
+ public RelayCommand ClearCommand { get; }
+ public RelayCommand CancelCommand { get; }
+ public RelayCommand GenerateCommand { get; }
+ public ResultModel CurrentResult { get; set; }
+ public ObservableCollection ResultHistory { get; }
+
+ public Model Model
+ {
+ get { return (Model)GetValue(ModelProperty); }
+ set { SetValue(ModelProperty, value); }
+ }
+
+ public Tokenizer Tokenizer
+ {
+ get { return (Tokenizer)GetValue(TokenizerProperty); }
+ set { SetValue(TokenizerProperty, value); }
+ }
+
+ public ModelOptionsModel ModelOptions
+ {
+ get { return (ModelOptionsModel)GetValue(ModelOptionsProperty); }
+ set { SetValue(ModelOptionsProperty, value); }
+ }
+
+ public SearchOptionsModel SearchOptions
+ {
+ get { return (SearchOptionsModel)GetValue(SearchOptionsProperty); }
+ set { SetValue(SearchOptionsProperty, value); }
+ }
+
+ public string Prompt
+ {
+ get { return _prompt; }
+ set { _prompt = value; NotifyPropertyChanged(); }
+ }
+
+
+ private async Task GenerateAsync()
+ {
+ try
+ {
+ var userInput = new ResultModel
+ {
+ Content = Prompt,
+ IsUserInput = true
+ };
+
+ Prompt = null;
+ CurrentResult = null;
+ ResultHistory.Add(userInput);
+ _cancellationTokenSource = new CancellationTokenSource();
+ await foreach (var sentencePiece in RunInferenceAsync(userInput.Content, _cancellationTokenSource.Token))
+ {
+ if (CurrentResult == null)
+ {
+ if (string.IsNullOrWhiteSpace(sentencePiece.Content)) // Ingore preceding '\n'
+ continue;
+
+ ResultHistory.Add(CurrentResult = new ResultModel());
+ }
+ CurrentResult.Content += sentencePiece.Content;
+ }
+ }
+ catch (OperationCanceledException)
+ {
+ CurrentResult.Content += "\n\n[Operation Canceled]";
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show(ex.Message, "Inference Error", MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ }
+
+
+ private bool CanExecuteGenerate()
+ {
+ return !string.IsNullOrWhiteSpace(Prompt);
+ }
+
+
+ private Task CancelAsync()
+ {
+ _cancellationTokenSource?.Cancel();
+ return Task.CompletedTask;
+ }
+
+
+ private Task ClearAsync()
+ {
+ _pastTokens.Clear();
+ ResultHistory.Clear();
+ return Task.CompletedTask;
+ }
+
+
+ private async IAsyncEnumerable RunInferenceAsync(string prompt, [EnumeratorCancellation] CancellationToken cancellationToken)
+ {
+ var sequences = await Tokenizer.EncodeAsync(prompt, cancellationToken);
+
+ // Add Tokens to history
+ AddPastTokens(sequences);
+
+ using var generatorParams = new GeneratorParams(Model);
+ generatorParams.ApplySearchOptions(SearchOptions);
+
+ // max_length is per message, so increment max_length for next call
+ var newMaxLength = Math.Min(_pastTokens.Count + SearchOptions.MaxLength, ModelOptions.ContextLength);
+ generatorParams.SetSearchOption("max_length", newMaxLength);
+
+ generatorParams.SetInputIDs(CollectionsMarshal.AsSpan(_pastTokens), (ulong)_pastTokens.Count, 1);
+
+ using var tokenizerStream = Tokenizer.CreateStream();
+ using var generator = new Generator(Model, generatorParams);
+ while (!generator.IsDone())
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ yield return await Task.Run(() =>
+ {
+ generator.ComputeLogits();
+ generator.GenerateNextToken();
+
+ var tokenId = generator.GetSequence(0)[^1];
+ return new TokenModel(tokenId, tokenizerStream.Decode(tokenId));
+ }, cancellationToken);
+ }
+ }
+
+
+ private void AddPastTokens(Sequences sequences)
+ {
+ _pastTokens.AddRange(sequences[0].ToArray());
+
+ // Only keep (context_length - max_length) worth of history
+ while (_pastTokens.Count > ModelOptions.ContextLength - SearchOptions.MaxLength)
+ {
+ _pastTokens.RemoveAt(0);
+ }
+ }
+
+ #region INotifyPropertyChanged
+ public event PropertyChangedEventHandler PropertyChanged;
+ public void NotifyPropertyChanged([CallerMemberName] string property = "")
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
+ }
+ #endregion
+ }
+}
diff --git a/examples/csharp/Genny/Genny/Views/StatelessView.xaml b/examples/csharp/Genny/Genny/Views/StatelessView.xaml
new file mode 100644
index 000000000..b36b103bf
--- /dev/null
+++ b/examples/csharp/Genny/Genny/Views/StatelessView.xaml
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/csharp/Genny/Genny/Views/StatelessView.xaml.cs b/examples/csharp/Genny/Genny/Views/StatelessView.xaml.cs
new file mode 100644
index 000000000..a24ef02fb
--- /dev/null
+++ b/examples/csharp/Genny/Genny/Views/StatelessView.xaml.cs
@@ -0,0 +1,171 @@
+using Genny.Utils;
+using Genny.ViewModel;
+using Microsoft.ML.OnnxRuntimeGenAI;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace Genny.Views
+{
+ ///
+ /// Interaction logic for StatelessView.xaml
+ ///
+ public partial class StatelessView : UserControl, INotifyPropertyChanged
+ {
+ private string _prompt;
+ private CancellationTokenSource _cancellationTokenSource;
+
+ public StatelessView()
+ {
+ ClearCommand = new RelayCommand(ClearAsync);
+ CancelCommand = new RelayCommand(CancelAsync);
+ GenerateCommand = new RelayCommand(GenerateAsync, CanExecuteGenerate);
+ ResultHistory = new ObservableCollection();
+ InitializeComponent();
+ }
+
+ public static readonly DependencyProperty ModelProperty =
+ DependencyProperty.Register(nameof(Model), typeof(Model), typeof(StatelessView));
+
+ public static readonly DependencyProperty TokenizerProperty =
+ DependencyProperty.Register(nameof(Tokenizer), typeof(Tokenizer), typeof(StatelessView));
+
+ public static readonly DependencyProperty ModelOptionsProperty =
+ DependencyProperty.Register(nameof(ModelOptions), typeof(ModelOptionsModel), typeof(StatelessView));
+
+ public static readonly DependencyProperty SearchOptionsProperty =
+ DependencyProperty.Register(nameof(SearchOptions), typeof(SearchOptionsModel), typeof(StatelessView));
+
+ public RelayCommand ClearCommand { get; }
+ public RelayCommand CancelCommand { get; }
+ public RelayCommand GenerateCommand { get; }
+ public ResultModel CurrentResult { get; set; }
+ public ObservableCollection ResultHistory { get; }
+
+ public Model Model
+ {
+ get { return (Model)GetValue(ModelProperty); }
+ set { SetValue(ModelProperty, value); }
+ }
+
+ public Tokenizer Tokenizer
+ {
+ get { return (Tokenizer)GetValue(TokenizerProperty); }
+ set { SetValue(TokenizerProperty, value); }
+ }
+
+ public ModelOptionsModel ModelOptions
+ {
+ get { return (ModelOptionsModel)GetValue(ModelOptionsProperty); }
+ set { SetValue(ModelOptionsProperty, value); }
+ }
+
+ public SearchOptionsModel SearchOptions
+ {
+ get { return (SearchOptionsModel)GetValue(SearchOptionsProperty); }
+ set { SetValue(SearchOptionsProperty, value); }
+ }
+
+ public string Prompt
+ {
+ get { return _prompt; }
+ set { _prompt = value; NotifyPropertyChanged(); }
+ }
+
+
+ private async Task GenerateAsync()
+ {
+ try
+ {
+ var userInput = new ResultModel
+ {
+ Content = Prompt,
+ IsUserInput = true
+ };
+
+ Prompt = null;
+ CurrentResult = null;
+ ResultHistory.Add(userInput);
+ _cancellationTokenSource = new CancellationTokenSource();
+ await foreach (var sentencePiece in RunInferenceAsync(userInput.Content, _cancellationTokenSource.Token))
+ {
+ if (CurrentResult == null)
+ {
+ if (string.IsNullOrWhiteSpace(sentencePiece.Content)) // Ingore preceding '\n'
+ continue;
+
+ ResultHistory.Add(CurrentResult = new ResultModel());
+ }
+ CurrentResult.Content += sentencePiece.Content;
+ }
+ }
+ catch (OperationCanceledException)
+ {
+ CurrentResult.Content += "\n\n[Operation Canceled]";
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show(ex.Message, "Inference Error", MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ }
+
+
+ private bool CanExecuteGenerate()
+ {
+ return !string.IsNullOrWhiteSpace(Prompt);
+ }
+
+
+ private Task CancelAsync()
+ {
+ _cancellationTokenSource?.Cancel();
+ return Task.CompletedTask;
+ }
+
+
+ private Task ClearAsync()
+ {
+ ResultHistory.Clear();
+ return Task.CompletedTask;
+ }
+
+ private async IAsyncEnumerable RunInferenceAsync(string prompt, [EnumeratorCancellation] CancellationToken cancellationToken)
+ {
+ var sequences = await Tokenizer.EncodeAsync(prompt, cancellationToken);
+
+ using var generatorParams = new GeneratorParams(Model);
+ generatorParams.ApplySearchOptions(SearchOptions);
+ generatorParams.SetInputSequences(sequences);
+
+ using var tokenizerStream = Tokenizer.CreateStream();
+ using var generator = new Generator(Model, generatorParams);
+ while (!generator.IsDone())
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ yield return await Task.Run(() =>
+ {
+ generator.ComputeLogits();
+ generator.GenerateNextToken();
+
+ var tokenId = generator.GetSequence(0)[^1];
+ return new TokenModel(tokenId, tokenizerStream.Decode(tokenId));
+ }, cancellationToken);
+ }
+ }
+
+ #region INotifyPropertyChanged
+ public event PropertyChangedEventHandler PropertyChanged;
+ public void NotifyPropertyChanged([CallerMemberName] string property = "")
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
+ }
+ #endregion
+ }
+}
diff --git a/examples/csharp/Genny/Genny/Views/TokenizerView.xaml b/examples/csharp/Genny/Genny/Views/TokenizerView.xaml
new file mode 100644
index 000000000..b69e64dee
--- /dev/null
+++ b/examples/csharp/Genny/Genny/Views/TokenizerView.xaml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/csharp/Genny/Genny/Views/TokenizerView.xaml.cs b/examples/csharp/Genny/Genny/Views/TokenizerView.xaml.cs
new file mode 100644
index 000000000..a6b488fa6
--- /dev/null
+++ b/examples/csharp/Genny/Genny/Views/TokenizerView.xaml.cs
@@ -0,0 +1,93 @@
+using Genny.Utils;
+using Microsoft.ML.OnnxRuntimeGenAI;
+using System;
+using System.ComponentModel;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace Genny.Views
+{
+ ///
+ /// Interaction logic for TokenizerView.xaml
+ ///
+ public partial class TokenizerView : UserControl, INotifyPropertyChanged
+ {
+ private string _encodeResult;
+ private string _decodeResult;
+
+ public TokenizerView()
+ {
+ EncodeCommand = new RelayCommand(EncodeAsync);
+ DecodeCommand = new RelayCommand(DecodeAsync);
+ InitializeComponent();
+ }
+
+ public static readonly DependencyProperty TokenizerProperty =
+ DependencyProperty.Register(nameof(Tokenizer), typeof(Tokenizer), typeof(TokenizerView));
+
+ public RelayCommand EncodeCommand { get; }
+ public RelayCommand DecodeCommand { get; }
+
+ public Tokenizer Tokenizer
+ {
+ get { return (Tokenizer)GetValue(TokenizerProperty); }
+ set { SetValue(TokenizerProperty, value); }
+ }
+
+ public string EncodeResult
+ {
+ get { return _encodeResult; }
+ set { _encodeResult = value; NotifyPropertyChanged(); }
+ }
+
+ public string DecodeResult
+ {
+ get { return _decodeResult; }
+ set { _decodeResult = value; NotifyPropertyChanged(); }
+ }
+
+
+ private async Task EncodeAsync(string input)
+ {
+ EncodeResult = null;
+ try
+ {
+ var sequences = await Tokenizer.EncodeAsync(input);
+ EncodeResult = string.Join(", ", sequences[0].ToArray());
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show(ex.Message, "Tokenizer Encode Error", MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ }
+
+
+ private async Task DecodeAsync(string input)
+ {
+ DecodeResult = null;
+ try
+ {
+ var intArray = input
+ .Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)
+ .Select(int.Parse)
+ .ToArray();
+ DecodeResult = await Tokenizer.DecodeAsync(intArray);
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show(ex.Message, "Tokenizer Decode Error", MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ }
+
+ #region INotifyPropertyChanged
+ public event PropertyChangedEventHandler PropertyChanged;
+ public void NotifyPropertyChanged([CallerMemberName] string property = "")
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
+ }
+ #endregion
+ }
+}
diff --git a/examples/csharp/Genny/README.md b/examples/csharp/Genny/README.md
new file mode 100644
index 000000000..117d93722
--- /dev/null
+++ b/examples/csharp/Genny/README.md
@@ -0,0 +1,48 @@
+## Genny
+A example UI for debugging and testing models with OnnxRuntime-GenAI
+
+| | |
+| :--- | :--- |
+ |
+
+______________________
+
+## Run Genny
+* Open `Genny.sln` in VisualStudio and run `Debug` or `Release` to launch the application
+* Enter or Select your model folder path
+* Click Load Model (this may take a few minutes)
+
+
+## CPU or GPU
+* `Debug` or `Release` to launch the application with CPU support
+* `Debug_Cuda` or `Release_Cuda` to launch the application with CUDA GPU support
+
+
+## Models
+You can generate the model using the ONNX Runtime Generative AI model builder, or bring your own model.
+
+To generate the model with model builder:
+
+1. Install the python package
+
+ Install the Python package according to the [installation instructions](https://onnxruntime.ai/docs/genai/howto/install).
+
+2. Install the model builder script dependencies
+
+ ```bash
+ pip install numpy
+ pip install transformers
+ pip install torch
+ pip install onnx
+ pip install onnxruntime
+ ```
+
+3. Run the model builder script to export, optimize, and quantize the model. More details can be found [here](../../../src/python/py/models/README.md)
+
+ ```bash
+ python -m onnxruntime_genai.models.builder -m models/phi-2 -e cpu -p int4 -o models/phi2-int4
+ ```
+
+The model builder also generates the configuration needed by the API to run generation. You can modify the config according to your scenario.
+
+If you bring your own model, you need to provide the configuration. See the [config reference](https://onnxruntime.ai/docs/genai/reference/config).
diff --git a/examples/csharp/HelloPhi2.csproj b/examples/csharp/HelloPhi2/HelloPhi2.csproj
similarity index 100%
rename from examples/csharp/HelloPhi2.csproj
rename to examples/csharp/HelloPhi2/HelloPhi2.csproj
diff --git a/examples/csharp/HelloPhi2.sln b/examples/csharp/HelloPhi2/HelloPhi2.sln
similarity index 100%
rename from examples/csharp/HelloPhi2.sln
rename to examples/csharp/HelloPhi2/HelloPhi2.sln
diff --git a/examples/csharp/Program.cs b/examples/csharp/HelloPhi2/Program.cs
similarity index 100%
rename from examples/csharp/Program.cs
rename to examples/csharp/HelloPhi2/Program.cs
diff --git a/examples/csharp/README.md b/examples/csharp/HelloPhi2/README.md
similarity index 94%
rename from examples/csharp/README.md
rename to examples/csharp/HelloPhi2/README.md
index 7052a02d4..c5fadc7c4 100644
--- a/examples/csharp/README.md
+++ b/examples/csharp/HelloPhi2/README.md
@@ -28,7 +28,7 @@ To generate the model with model builder:
pip install onnxruntime
```
-3. Run the model builder script to export, optimize, and quantize the model. More details can be found [here](../../src/python/py/models/README.md)
+3. Run the model builder script to export, optimize, and quantize the model. More details can be found [here](../../../src/python/py/models/README.md)
```bash
cd examples/python