Freigeben über


Erste Schritte mit Phi3 und anderen Sprachmodellen in Ihrer Windows-App mit der ONNX Runtime Generative AI

Dieser Artikel führt Sie durch das Erstellen einer WinUI 3-App, die ein Phi3-Modell und die ONNX Runtime Generative AI Bibliothek verwendet, um eine einfache Chat-App mit generative KI zu implementieren. Mit großen Sprachmodellen (LLMs) können Sie Ihrer App Textgenerierungs-, Transformations-, Schlussfolgerungs- und Übersetzungsfunktionen hinzufügen. Weitere Informationen zur Verwendung von KI- und Machine Learning-Modellen in Ihrer Windows-App finden Sie unter Erste Schritte mit KI- und Machine Learning-Modellen in Ihrer Windows-App. Weitere Informationen zur ONNX-Laufzeit und zur generativen KI finden Sie unter Generative KI mit ONNX Runtime.

Was ist der ONNX Runtime

ONNX Runtime ist ein plattformübergreifender Machine Learning-Modellbeschleuniger mit einer flexiblen Schnittstelle zur Integration hardwarespezifischer Bibliotheken. ONNX Runtime kann mit Modellen von PyTorch, Tensorflow/Keras, TFLite, scikit-learn und anderen Frameworks verwendet werden. Weitere Informationen findest du auf der ONNX Runtime Website unter https://onnxruntime.ai/docs/.

Voraussetzungen

Erstellen einer neuen C#-WinUI-App

Erstellen Sie in Visual Studio ein neues Projekt. Legen Sie im Dialogfeld Neues Projekt erstellen den Sprachfilter auf „C++“ und den Projekttypfilter auf „winui“ fest, und wählen Sie dann die Vorlage Leere App, Verpackt (WinUI3 in Desktop) aus. Nennen Sie das neue Projekt „GenAIExample“.

Fügen Sie Verweise auf das ONNX Runtime Generative AI-Nuget-Paket hinzu.

Klicken Sie in Projektmappen-Explorer mit der rechten Maustaste auf Abhängigkeiten, und wählen Sie NuGet-Pakete verwalten aus. Wählen Sie im NuGet-Paket-Manager die Registerkarte Durchsuchen aus. Suchen Sie nach Microsoft.ML.OnnxRuntimeGenAI.DirectML, wählen Sie die neueste stabile Version in der Dropdownliste Version aus, und klicken Sie dann auf Installieren.

Hinzufügen einer Modell- und Vokabulardatei zu Ihrem Projekt

Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf Ihr Projekt, und wählen Sie Hinzufügen>Neuer Ordner aus. Nennen Sie den neuen Ordner „Modelle“. In diesem Beispiel verwenden wir das Modell von https://huggingface.co/microsoft/Phi-3-mini-4k-instruct-onnx/tree/main/directml/directml-int4-awq-block-128.

Es gibt verschiedene Möglichkeiten zum Abrufen von Modellen. Für diese exemplarische Vorgehensweise verwenden wir die HuggingFace-Befehlszeilenschnittstelle (CLI). Wenn Sie die Modelle mit einer anderen Methode abrufen, müssen Sie die Dateipfade möglicherweise im Beispielcode an das Modell anpassen. Informationen zum Installieren der HuggingFace CLI und zum Einrichten Ihres Kontos für die Verwendung finden Sie unter Command Line Interface (CLI).

Öffnen Sie nach der Installation der CLI ein Terminal, navigieren Sie zu dem von Ihnen erstellten Models-Verzeichnis, und geben Sie den folgenden Befehl ein.

huggingface-cli download microsoft/Phi-3-mini-4k-instruct-onnx --include directml/* --local-dir .

Wenn der Vorgang abgeschlossen ist, überprüfen Sie, ob die folgende Datei vorhanden ist: [Project Directory]\Models\directml\directml-int4-awq-block-128\model.onnx.

Erweitern Sie in Projektmappen-Explorer den Ordner „directml-int4-awq-block-128“, und wählen Sie alle Dateien im Ordner aus. Legen Sie im Bereich Dateieigenschaften den Wert für In Ausgabeverzeichnis kopieren auf „Kopieren, wenn neuer“ fest.

Hinzufügen einer einfachen Benutzeroberfläche zur Interaktion mit dem Modell

In diesem Beispiel wird eine sehr vereinfachte Benutzeroberfläche mit einem TextBox-Element zum Angeben einer Eingabeaufforderung, einer Schaltfläche zum Übermitteln der Eingabeaufforderung und eines TextBlock zum Anzeigen von Statusmeldungen sowie den Antworten aus dem Modell erstellt. Ersetzen Sie das standardmäßige StackPanel-Element durch MainWindow.xaml den folgenden 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>

Initialisieren des Modells

Fügen Sie in MainWindow.xaml.cs eine using-Direktive für den Microsoft.ML.OnnxRuntimeGenAI-Namespace hinzu.

using Microsoft.ML.OnnxRuntimeGenAI;

Deklarieren Sie Membervariablen innerhalb der MainPageKlassendefinition für das Modell und den Tokenizer. Legen Sie den Speicherort für die Modelldateien fest, die wir in den vorherigen Schritten hinzugefügt haben.

private Model? model = null;
private Tokenizer? tokenizer = null;
private readonly string ModelDir = 
    Path.Combine(AppDomain.CurrentDomain.BaseDirectory,
        @"Models\directml\directml-int4-awq-block-128");

Erstellen Sie eine Hilfsmethode, um das Modell asynchron zu initialisieren. Diese Methode ruft den Konstruktor für die Model-Klasse auf und übergibt den Pfad an das Modellverzeichnis. Als Nächstes wird ein neuer Tokenizer aus dem Modell erstellt.

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";
        });
    });
}

In diesem Beispiel wird das Modell geladen, wenn das Hauptfenster aktiviert wird. Aktualisieren Sie den Seitenkonstruktor, um einen Handler für das Activated-Ereignis zu registrieren.

public MainWindow()
{
    this.InitializeComponent();
    this.Activated += MainWindow_Activated;
}

Das Activated-Ereignis kann mehrmals ausgelöst werden. Überprüfen Sie daher im Ereignishandler, ob das Modell null ist, bevor Sie es initialisieren.

private async void MainWindow_Activated(object sender, WindowActivatedEventArgs args)
{
    if (model == null)
    {
        await InitializeModelAsync();
    }
}

Senden des Prompts an das Modell

Erstellen Sie eine Hilfsmethode, mit der die Aufforderung an das Modell übermittelt wird, und geben Sie dann asynchron die Ergebnisse mit einer IAsyncEnumerable an den Aufrufer zurück.

In dieser Methode wird die Generator-Klasse in einer Schleife verwendet, wobei GenerateNextToken in jedem Durchlauf aufgerufen wird, um abzurufen, was das Modell voraussagt. Für die nächsten Zeichen, die als Token bezeichnet werden, sollten auf der Eingabeaufforderung basieren. Die Schleife wird ausgeführt, bis die IsDone-Generatormethode „true“ anzeigt oder bis eines der Token „<|end|>“, „<|system|>“ oder „<|user|>“ empfangen wird, was signalisiert, dass wir das Generieren von Token beenden können.

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;
    }
}

Fügen Sie UI-Code hinzu, um die Eingabeaufforderung zu übermitteln und die Ergebnisse anzuzeigen.

Überprüfen Sie zunächst im Button-Klickhandler, dass das Modell nicht null ist. Erstellen Sie eine Eingabeaufforderungszeichenfolge mit der System- und Benutzeraufforderung, und rufen Sie InferStreaming auf, wodurch der TextBlock mit jedem Teil der Antwort aktualisiert wird.

Das in diesem Beispiel verwendete Modell wurde trainiert, um Eingabeaufforderungen im folgenden Format zu akzeptieren. Dabei handelt es sich bei systemPrompt um die Anweisungen für das Verhalten des Modells und bei userPrompt um die Frage des Benutzers.

<|system|>{systemPrompt}<|end|><|user|>{userPrompt}<|end|><|assistant|>

Modelle sollten ihre Aufforderungskonventionen dokumentieren. Für dieses Modell wird das Format auf der HuggingFace-Modellkarte dokumentiert.

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;
        }
    }
}

Ausführen des Beispiels

Stellen Sie in Visual Studio in der Dropdownliste Lösungsplattformen sicher, dass der Zielprozessor auf x64 festgelegt ist. Die ONNXRuntime Generative AI-Bibliothek unterstützt x86 nicht. Erstellen Sie das Projekt, und führen Sie es aus. Warten Sie, bis TextBlock angibt, dass das Modell geladen wurde. Geben Sie eine Eingabeaufforderung in das Textfeld der Eingabeaufforderung ein, und klicken Sie auf die Schaltfläche zum Übermitteln. Die Ergebnisse sollten den Textblock schrittweise auffüllen.

Weitere Informationen