Dela via


Kom igång med Phi3 och andra språkmodeller i din Windows-app med ONNX Runtime Generative AI

Den här artikeln beskriver hur du skapar en WinUI 3-app som använder en Phi3-modell och ONNX Runtime Generative AI-biblioteket för att implementera en enkel generativ AI-chattapp. Med stora språkmodeller (LLM) kan du lägga till funktioner för textgenerering, transformering, resonemang och översättning i din app. Mer information om hur du använder AI- och maskininlärningsmodeller i din Windows-app finns i Komma igång med AI i Windows. Mer information om ONNX-körning och generativ AI finns i Generative AI med ONNX Runtime.

När du använder AI-funktioner rekommenderar vi att du granskar: Utveckla ansvarsfulla generativa AI-program och funktioner i Windows.

Vad är ONNX Runtime

ONNX Runtime är en plattformsoberoende maskininlärningsmodellaccelerator med ett flexibelt gränssnitt för att integrera maskinvaruspecifika bibliotek. ONNX Runtime kan användas med modeller från PyTorch, Tensorflow/Keras, TFLite, scikit-learnoch andra ramverk. Mer information finns på ONNX Runtime webbplats på https://onnxruntime.ai/docs/.

Förutsättningar

  • Enheten måste ha aktiverat utvecklarläge. Mer information finns i Aktivera enheten för utveckling.
  • Visual Studio 2022 eller senare med .NET desktoputveckling arbetsbelastning.

Skapa en ny C#WinUI-app

Skapa ett nytt projekt i Visual Studio. I dialogrutan Skapa ett nytt projekt anger du språkfiltret till "C#" och projekttypsfiltret till "winui" och väljer sedan Tom app, Paketerad (WinUI3 i Desktop) mall. Ge det nya projektet namnet "GenAIExample".

Lägga till referenser till ONNX Runtime Generative AI Nuget-paketet

I Solution Explorerhögerklickar du på Dependencies och väljer Hantera NuGet-paket.... I NuGet-pakethanteraren väljer du fliken Bläddra. Sök efter "Microsoft.ML.OnnxRuntimeGenAI.DirectML", välj den senaste stabila versionen i listrutan Version och klicka sedan på Install.

Lägga till en modell- och ordförrådsfil i projektet

I Solution Explorerhögerklickar du på projektet och väljer Lägg till>ny mapp. Ge den nya mappen namnet "Models". I det här exemplet använder vi modellen från https://huggingface.co/microsoft/Phi-3-mini-4k-instruct-onnx/tree/main/directml/directml-int4-awq-block-128.

Det finns flera olika sätt att hämta modeller. I den här genomgången använder vi CLI (Hugging Face Command Line Interface). Om du får modellerna med en annan metod kan du behöva justera filsökvägarna till modellen i exempelkoden. Information om hur du installerar Hugging Face CLI och konfigurerar ditt konto för användning finns i Kommandoradsgränssnitt (CLI).

När du har installerat CLI öppnar du en terminal, navigerar till den Models katalog som du skapade och skriver följande kommando.

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

När åtgärden är klar kontrollerar du att följande fil finns: [Project Directory]\Models\directml\directml-int4-awq-block-128\model.onnx.

I Solution Explorerexpanderar du mappen "directml-int4-awq-block-128" och markerar alla filer i mappen. I fönstret Filegenskaper anger du Kopiera till utgångskatalog till "Kopiera om den är nyare".

Lägga till ett enkelt användargränssnitt för att interagera med modellen

I det här exemplet skapar vi ett mycket förenklat användargränssnitt som har en TextBox- för att ange en fråga, en Knapp för att skicka prompten och en TextBlock- för att visa statusmeddelanden och svar från modellen. Ersätt standardelementet StackPanel i MainWindow.xaml med följande 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>

Initiera modellen

I MainWindow.xaml.cslägger du till ett användningsdirektiv för namnområdet Microsoft.ML.OnnxRuntimeGenAI.

using Microsoft.ML.OnnxRuntimeGenAI;

Deklarera medlemsvariabler i klassdefinitionen MainPage för Model och Tokenizer. Ange platsen för modellfilerna som vi lade till i föregående steg.

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

Skapa en hjälpmetod för att asynkront initiera modellen. Den här metoden anropar konstruktorn för klassen Model och skickar sökvägen till modellkatalogen. Därefter skapas en ny tokeniserare med hjälp av modellen.

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

I det här exemplet läser vi in modellen när huvudfönstret aktiveras. Uppdatera sidkonstruktorn för att registrera en hanterare för händelsen Aktiverad.

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

Händelsen Aktiverad kan aktiveras flera gånger, så i händelsehanteraren kontrollerar du att modellen är null innan den initieras.

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

Skicka uppmaningen till modellen

Skapa en hjälpande metod som asynkront skickar uppmaningen till modellen och sedan returnerar resultaten till anroparen med en IAsyncEnumerable.

I den här metoden används klassen Generator i en slinga och anropar GenerateNextToken i varje pass för att hämta vad modellen förutspår att de närmaste tecknen, kallade en token, bör vara baserat på indataprompten. Loopen körs tills metoden IsDone för generatorn returnerar true eller tills någon av tokens "<|end|>", "<|system|>", eller "<|user|>" tas emot, vilket signalerar att vi kan sluta generera 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;
    }
}

Lägg till UI-kod för att skicka prompten och visa resultatet

I klickhanteraren för -knappen, kontrollera först att modellen inte är null. Skapa en frågesträng med systemets och användarens fråga och anropa InferStreaming, uppdatera TextBlock med varje del av svaret.

Modellen som används i det här exemplet har tränats att acceptera uppmaningar i följande format, där systemPrompt är instruktionerna för hur modellen ska bete sig och userPrompt är frågan från användaren.

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

Modeller bör dokumentera sina promptkonventioner. För den här modellen dokumenteras formatet på Huggingface-modellkortet.

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

Kör exemplet

Kontrollera att målprocessorn är inställd på x64 i listrutan Lösningsplattformar i Visual Studio. ONNXRuntime Generative AI-biblioteket stöder inte x86. Skapa och kör projektet. Vänta tills TextBlock anger att modellen har lästs in. Skriv en uppmaning i textrutan och klicka på knappen Skicka. Du bör se resultatet gradvis fylla i textblocket.

Se även