Delen via


Aan de slag met Phi3 en andere taalmodellen in uw Windows-app met ONNX Runtime Generative AI

In dit artikel wordt uitgelegd hoe u een WinUI 3-app maakt die gebruikmaakt van een Phi3-model en de ONNX Runtime Generative AI-bibliotheek om een eenvoudige generatieve AI-chat-app te implementeren. Met grote taalmodellen (LLM's) kunt u tekstgeneratie-, transformatie-, redenerings- en vertaalmogelijkheden toevoegen aan uw app. Zie Aan de slag met AI- en Machine Learning-modellen in uw Windows-appvoor meer informatie over het gebruik van AI- en machine learning-modellen in uw Windows-app. Zie Generatieve AI met ONNX Runtimevoor meer informatie over ONNX-runtime en generatieve AI.

Bij het gebruik van AI-functies raden we u aan het volgende te bekijken: Ontwikkelen van verantwoorde Generatieve AI-toepassingen en -functies in Windows.

Wat is de ONNX Runtime

ONNX Runtime is een platformoverschrijdende machine learning-modelversneller met een flexibele interface voor het integreren van hardwarespecifieke bibliotheken. ONNX Runtime kan worden gebruikt met modellen van PyTorch, Tensorflow/Keras, TFLite, scikit-learnen andere frameworks. Zie de ONNX Runtime website op https://onnxruntime.ai/docs/voor meer informatie.

Voorwaarden

Een nieuwe C# WinUI-app maken

Maak in Visual Studio een nieuw project. Stel in het dialoogvenster Een nieuw project maken het taalfilter in op 'C#' en het projecttypefilter op "winui." Selecteer vervolgens de Lege app, Verpakt (WinUI3 in Desktop) sjabloon. Noem het nieuwe project 'GenAIExample'.

Verwijzingen toevoegen aan het nuget-pakket ONNX Runtime Generative AI

Klik in Solution Explorermet de rechtermuisknop op Afhankelijkheden en selecteer NuGet-pakketten beheren.... Selecteer in NuGet-pakketbeheer het tabblad Bladeren. Zoek naar Microsoft.ML.OnnxRuntimeGenAI.DirectML, selecteer de meest recente stabiele versie in de vervolgkeuzelijst Versie en klik vervolgens op Installeren.

Een model- en woordenlijstbestand toevoegen aan uw project

Klik in Solution Explorer-met de rechtermuisknop op uw project en selecteer Toevoegen>Nieuwe map. Geef de nieuwe map de naam 'Modellen'. In dit voorbeeld gebruiken we het model uit https://huggingface.co/microsoft/Phi-3-mini-4k-instruct-onnx/tree/main/directml/directml-int4-awq-block-128.

Er zijn verschillende manieren om modellen op te halen. Voor deze walkthrough gebruiken we de Hugging Face Command Line Interface (CLI). Als u de modellen met een andere methode krijgt, moet u mogelijk de bestandspaden aanpassen aan het model in de voorbeeldcode. Zie CLI (Opdrachtregelinterface)voor meer informatie over het installeren van de Hugging Face CLI en het instellen van uw account om dit te gebruiken.

Nadat u de CLI hebt geïnstalleerd, opent u een terminal, gaat u naar de Models map die u hebt gemaakt en typt u de volgende opdracht.

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

Wanneer de bewerking is voltooid, controleert u of het volgende bestand bestaat: [Project Directory]\Models\directml\directml-int4-awq-block-128\model.onnx.

Vouw in Solution Explorerde map directml-int4-awq-block-128 uit en selecteer alle bestanden in de map. Stel in het deelvenster Bestandseigenschappen, Kopiëren naar doelmap in op "Kopiëren als nieuwer".

Een eenvoudige gebruikersinterface toevoegen om met het model te communiceren

In dit voorbeeld maken we een zeer eenvoudige gebruikersinterface met een tekstvak voor het opgeven van een prompt, een Knop voor het verzenden van de prompt en een TextBlock- voor het weergeven van statusberichten en de antwoorden van het model. Vervang het standaardelement StackPanel in MainWindow.xaml door de volgende 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>

Het model initialiseren

Voeg in MainWindow.xaml.cseen using-instructie toe voor de Microsoft.ML.OnnxRuntimeGenAI naamruimte.

using Microsoft.ML.OnnxRuntimeGenAI;

Declareer lidvariabelen in de MainPage klassedefinitie voor de Model- en de Tokenizer-. Stel de locatie in voor de modelbestanden die we in de vorige stappen hebben toegevoegd.

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

Maak een helpermethode om het model asynchroon te initialiseren. Met deze methode wordt de constructor aangeroepen voor de Model-klasse, waarbij het pad naar de modelmap wordt doorgegeven. Vervolgens maakt het een nieuwe Tokenizer van het model.

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 dit voorbeeld laden we het model wanneer het hoofdvenster wordt geactiveerd. Werk de paginaconstructor bij om een handler te registreren voor de gebeurtenis Geactiveerd.

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

De geactiveerde gebeurtenis kan meerdere keren worden gegenereerd, dus controleer in de gebeurtenis-handler of het model null is voordat het wordt geïnitialiseerd.

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

De prompt naar het model verzenden

Maak een hulpmethode die de prompt naar het model verzendt en vervolgens de resultaten asynchroon met een IAsyncEnumerablenaar de aanroeper retourneert.

In deze methode wordt de Generator--klasse in een herhalende lus gebruikt, waarbij elke keer GenerateNextToken wordt aangeroepen om op te halen wat het model voorspelt dat de volgende tekens, tokens genoemd, zouden moeten zijn, gebaseerd op de invoerprompt. De lus loopt totdat de generator IsDone-methode waar retourneert of tot een van de tokens "<|end|>", "<|system|>", of "<|user|>" worden ontvangen, wat aangeeft dat we kunnen stoppen met het genereren van tokens.

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

UI-code toevoegen om de prompt in te dienen en de resultaten weer te geven

Controleer in de -knop klikhandler eerst of het model niet null is. Maak een prompttekstreeks met de systeem- en gebruikersprompt en roep InferStreamingaan, waarna de TextBlock wordt bijgewerkt na elk ontvangen deel van het antwoord.

Het model dat in dit voorbeeld wordt gebruikt, is getraind om prompts in de volgende indeling te accepteren, waarbij systemPrompt de instructies zijn voor het gedrag van het model en userPrompt de vraag van de gebruiker is.

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

Modellen moeten hun promptconventies documenteren. Voor dit model wordt het formaat gedocumenteerd op de Huggingface modelkaart.

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

Het voorbeeld uitvoeren

Controleer in Visual Studio in de vervolgkeuzelijst Solution Platforms of de doelprocessor is ingesteld op x64. De ONNXRuntime Ative AI-bibliotheek biedt geen ondersteuning voor x86. Bouw het project en voer het uit. Wacht tot de TextBlock- aangeeft dat het model is geladen. Typ een prompt in het tekstvak prompt en klik op de knop Verzenden. U ziet dat de resultaten het tekstblok geleidelijk vullen.

Zie ook