Wprowadzenie do platformy Phi3 i innych modeli językowych w aplikacji systemu Windows za pomocą ONNX Runtime Generative AI
W tym artykule opisano proces tworzenia aplikacji WinUI 3 korzystającej z modelu Phi3 i biblioteki ONNX Runtime Generative AI w celu zaimplementowania prostej aplikacji do generowania czatu sztucznej inteligencji. Duże modele językowe (LLM) umożliwiają dodawanie do aplikacji funkcji generowania tekstu, przekształcania, rozumowania i tłumaczenia. Aby uzyskać więcej informacji na temat używania modeli sztucznej inteligencji i uczenia maszynowego w aplikacji systemu Windows, zobacz Wprowadzenie do sztucznej inteligencji w systemie Windows. Aby uzyskać więcej informacji na temat środowiska uruchomieniowego ONNX i generowania sztucznej inteligencji, zobacz Generowanie sztucznej inteligencji za pomocą ONNX Runtime.
Podczas korzystania z funkcji sztucznej inteligencji zalecamy przejrzenie: Opracowywanie odpowiedzialnych aplikacji i funkcji sztucznej inteligencji w systemie Windows.
Co to jest ONNX Runtime
ONNX Runtime to wieloplatformowy akcelerator modelu uczenia maszynowego z elastycznym interfejsem integrowania bibliotek specyficznych dla sprzętu. ONNX Runtime można używać z modelami z platform PyTorch, Tensorflow/Keras, TFLite, scikit-learni innych platform. Aby uzyskać więcej informacji, zobacz witrynę internetową ONNX Runtime pod adresem https://onnxruntime.ai/docs/.
Warunki wstępne
- Urządzenie musi mieć włączony tryb dewelopera. Aby uzyskać więcej informacji, zobacz Aktywuj swoje urządzenie do celów projektowych.
- Program Visual Studio 2022 lub nowszy z zestawem narzędzi do tworzenia aplikacji desktopowych .NET.
Tworzenie nowej aplikacji WinUI w języku C#
W programie Visual Studio utwórz nowy projekt. W oknie dialogowym Tworzenie nowego projektu ustaw filtr języka na "C#", a filtr typu projektu na "winui", a następnie wybierz szablon "Pusta aplikacja, Pakietowa (WinUI3 w Desktop)". Nazwij nowy projekt "GenAIExample".
Dodawanie odwołań do pakietu Nuget ONNX Runtime Generative AI
W Eksploratorze rozwiązań kliknij prawym przyciskiem myszy pozycję Zależności i wybierz pozycję Zarządzaj pakietami NuGet.... W Menedżerze pakietów NuGet wybierz kartę Przeglądaj. Wyszukaj ciąg "Microsoft.ML.OnnxRuntimeGenAI.DirectML", wybierz najnowszą stabilną wersję z listy rozwijanej Wersja, a następnie kliknij pozycję Zainstaluj.
Dodawanie modelu i pliku słownictwa do projektu
W Eksploratorze rozwiązań kliknij prawym przyciskiem myszy swój projekt i wybierz Dodaj>Nowy folder. Nazwij nowy folder "Models". W tym przykładzie użyjemy modelu z https://huggingface.co/microsoft/Phi-3-mini-4k-instruct-onnx/tree/main/directml/directml-int4-awq-block-128.
Istnieje kilka różnych sposobów odzyskiwania modeli. W tym przewodniku użyjemy interfejsu wiersza polecenia Hugging Face (Hugging Face Command Line Interface, CLI). Jeśli uzyskasz modele przy użyciu innej metody, może być konieczne dostosowanie ścieżek plików do modelu w przykładowym kodzie. Aby uzyskać informacje na temat instalowania CLI Hugging Face i konfigurowania konta do korzystania z niego, zobacz interfejs wiersza polecenia (CLI).
Po zainstalowaniu CLI, otwórz terminal, przejdź do utworzonego katalogu Models
i wpisz następujące polecenie.
huggingface-cli download microsoft/Phi-3-mini-4k-instruct-onnx --include directml/* --local-dir .
Po zakończeniu operacji sprawdź, czy istnieje następujący plik: [Project Directory]\Models\directml\directml-int4-awq-block-128\model.onnx
.
W eksploratorze rozwiązań rozwiń folder "directml-int4-awq-block-128" i wybierz wszystkie pliki w folderze. W okienku właściwości pliku ustaw Kopiuj do katalogu wyjściowego na wartość "Kopiuj, jeśli jest nowsza".
Dodawanie prostego interfejsu użytkownika do interakcji z modelem
W tym przykładzie utworzymy bardzo uproszczony interfejs użytkownika, który ma TextBox do wprowadzania monitu, Przycisk do przesyłania monitu oraz TextBlock do wyświetlania komunikatów o stanie i odpowiedzi z modelu. Zastąp domyślny element StackPanel w MainWindow.xaml
następującym kodem XAML.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Column ="0">
<TextBox x:Name="promptTextBox" Text="Compose a haiku about coding."/>
<Button x:Name="myButton" Click="myButton_Click">Submit prompt</Button>
</StackPanel>
<Border Grid.Column="1" Margin="20">
<TextBlock x:Name="responseTextBlock" TextWrapping="WrapWholeWords"/>
</Border>
</Grid>
Inicjowanie modelu
W MainWindow.xaml.cs
dodaj dyrektywę `using` dla przestrzeni nazw Microsoft.ML.OnnxRuntimeGenAI.
using Microsoft.ML.OnnxRuntimeGenAI;
Zadeklaruj zmienne składowe wewnątrz definicji klasy MainPage dla modelu i tokenizera. Ustaw lokalizację plików modelu dodanych w poprzednich krokach.
private Model? model = null;
private Tokenizer? tokenizer = null;
private readonly string ModelDir =
Path.Combine(AppDomain.CurrentDomain.BaseDirectory,
@"Models\directml\directml-int4-awq-block-128");
Utwórz metodę pomocnika, aby asynchronicznie zainicjować model. Ta metoda wywołuje konstruktor klasy Model, przekazując jako argument ścieżkę do katalogu modelu. Następnie utworzy nowy Tokenizer z modelu.
public Task InitializeModelAsync()
{
DispatcherQueue.TryEnqueue(() =>
{
responseTextBlock.Text = "Loading model...";
});
return Task.Run(() =>
{
var sw = Stopwatch.StartNew();
model = new Model(ModelDir);
tokenizer = new Tokenizer(model);
sw.Stop();
DispatcherQueue.TryEnqueue(() =>
{
responseTextBlock.Text = $"Model loading took {sw.ElapsedMilliseconds} ms";
});
});
}
W tym przykładzie załadujemy model po aktywowaniu okna głównego. Zaktualizuj konstruktor strony, aby zarejestrować program obsługi dla zdarzenia Aktywowane.
public MainWindow()
{
this.InitializeComponent();
this.Activated += MainWindow_Activated;
}
Zdarzenie Aktywowane może być wywoływane wiele razy, dlatego w procedurze obsługi zdarzeń sprawdź, czy model ma wartość null przed jego zainicjowaniem.
private async void MainWindow_Activated(object sender, WindowActivatedEventArgs args)
{
if (model == null)
{
await InitializeModelAsync();
}
}
Prześlij monit do modelu
Utwórz funkcję pomocniczą, która przesyła monit do modelu, a następnie asynchronicznie zwraca wyniki do wywołującego za pomocą IAsyncEnumerable.
W tej metodzie klasa generatora jest używana w pętli, wywołując przy każdym przebiegu GenerateNextToken, aby uzyskać przewidywanie modelu dotyczące kilku następnych znaków, nazywanych tokenem, co powinno być oparte na dostarczonej podpowiedzi. Pętla działa, dopóki metoda generatora IsDone nie zwróci wartości true, albo dopóki nie zostanie odebrany dowolny z tokenów "<|end|>", "<|system|>" lub "<|user|>", co sygnalizuje, że możemy przestać generować tokeny.
public async IAsyncEnumerable<string> InferStreaming(string prompt)
{
if (model == null || tokenizer == null)
{
throw new InvalidOperationException("Model is not ready");
}
var generatorParams = new GeneratorParams(model);
var sequences = tokenizer.Encode(prompt);
generatorParams.SetSearchOption("max_length", 2048);
generatorParams.SetInputSequences(sequences);
generatorParams.TryGraphCaptureWithMaxBatchSize(1);
using var tokenizerStream = tokenizer.CreateStream();
using var generator = new Generator(model, generatorParams);
StringBuilder stringBuilder = new();
while (!generator.IsDone())
{
string part;
try
{
await Task.Delay(10).ConfigureAwait(false);
generator.ComputeLogits();
generator.GenerateNextToken();
part = tokenizerStream.Decode(generator.GetSequence(0)[^1]);
stringBuilder.Append(part);
if (stringBuilder.ToString().Contains("<|end|>")
|| stringBuilder.ToString().Contains("<|user|>")
|| stringBuilder.ToString().Contains("<|system|>"))
{
break;
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
break;
}
yield return part;
}
}
Dodawanie kodu interfejsu użytkownika w celu przesłania monitu i wyświetlenia wyników
W procedurze obsługi kliknięcia przycisku najpierw upewnij się, że model nie jest pusty. Utwórz ciąg znaków monitu z monitem systemu i użytkownika, a następnie wywołaj InferStreaming, aktualizując TextBlock z każdą częścią odpowiedzi.
Model użyty w tym przykładzie został wytrenowany do akceptowania monitów w następującym formacie, gdzie systemPrompt
to instrukcje dotyczące zachowania modelu, a userPrompt
jest pytaniem od użytkownika.
<|system|>{systemPrompt}<|end|><|user|>{userPrompt}<|end|><|assistant|>
Modele powinny dokumentować konwencje monitów. W przypadku tego modelu format jest udokumentowany na karcie modelu Huggingface.
private async void myButton_Click(object sender, RoutedEventArgs e)
{
responseTextBlock.Text = "";
if(model != null)
{
var systemPrompt = "You are a helpful assistant.";
var userPrompt = promptTextBox.Text;
var prompt = $@"<|system|>{systemPrompt}<|end|><|user|>{userPrompt}<|end|><|assistant|>";
await foreach (var part in InferStreaming(prompt))
{
responseTextBlock.Text += part;
}
}
}
Uruchamianie przykładu
W programie Visual Studio, w rozwijanym menu Platformy rozwiązań, upewnij się, że procesor docelowy jest ustawiony na x64. Biblioteka ONNXRuntime Generative AI nie obsługuje architektury x86. Skompiluj i uruchom projekt. Poczekaj, aż TextBlock zasygnalizuje, że model został załadowany. Wpisz monit w polu tekstowym monitu i kliknij przycisk Prześlij. Powinieneś zobaczyć, jak wyniki stopniowo wypełniają blok tekstu.