Condividi tramite


Introduzione a Phi3 e ad altri modelli linguistici nell'app di Windows con ONNX Runtime Generative AI

Questo articolo illustra come creare un'app WinUI 3 che usa un modello Phi3 e la libreria ONNX Runtime Generative AI per implementare una semplice app di chat di intelligenza artificiale generativa. I modelli linguistici di grandi dimensioni consentono di aggiungere funzionalità di generazione, trasformazione, ragionamento e traduzione di testo all'app. Per altre informazioni sull'uso di modelli di intelligenza artificiale e Machine Learning nell'app windows, vedere Introduzione all'uso di modelli di intelligenza artificiale e Machine Learning nell'app di Windows. Per ulteriori informazioni sul runtime ONNX e sull'intelligenza artificiale generativa, consulta Intelligenza artificiale generativa con ONNX Runtime.

Quando si usano le funzionalità di intelligenza artificiale, è consigliabile esaminare: Sviluppo di applicazioni e funzionalità di intelligenza artificiale responsabile in Windows.

Che cos'è il ONNX Runtime

ONNX Runtime è un acceleratore di modelli di Machine Learning multipiattaforma, con un'interfaccia flessibile per integrare librerie specifiche dell'hardware. ONNX Runtime può essere usato con i modelli di PyTorch, Tensorflow/Keras, TFLite, scikit-learne altri framework. Per altre informazioni, vedere il sito Web ONNX Runtime all'indirizzo https://onnxruntime.ai/docs/.

Prerequisiti

  • Il dispositivo deve avere la modalità sviluppatore abilitata. Per altre informazioni, vedere Abilitare il dispositivo per lo sviluppo.
  • Visual Studio 2022 o versione successiva con il carico di lavoro per lo sviluppo di applicazioni desktop .NET.

Creare una nuova app WinUI C#

In Visual Studio creare un nuovo progetto. Nella finestra di dialogo Crea un nuovo progetto imposta il filtro del linguaggio su "C#" e il filtro del tipo di progetto su "winui", quindi seleziona il modello app vuota, Packaged (WinUI3 in Desktop). Denominare il nuovo progetto "GenAIExample".

Aggiungere riferimenti al pacchetto NuGet ONNX Runtime Generative AI

In Esplora soluzioni, fare clic con il pulsante destro del mouse su Dipendenze e selezionare Gestisci pacchetti NuGet.... Nella Gestione pacchetti NuGet, selezionare la scheda Sfoglia. Cercare "Microsoft.ML.OnnxRuntimeGenAI.DirectML", selezionare la versione stabile più recente nel menu a discesa Versione e quindi fare clic su Installa.

Aggiungere un modello e un file di vocabolario al progetto

In Esplora soluzioni, cliccare con il tasto destro sul progetto e selezionare Aggiungi->Nuova cartella. Assegnare alla nuova cartella il nome "Models". Per questo esempio verrà usato il modello da https://huggingface.co/microsoft/Phi-3-mini-4k-instruct-onnx/tree/main/directml/directml-int4-awq-block-128.

Esistono diversi modi per recuperare i modelli. Per questa procedura dettagliata, utilizzeremo l'interfaccia Hugging Face per la riga di comando (CLI). Se si ottengono i modelli usando un altro metodo, potrebbe essere necessario modificare i percorsi di file per il modello nel codice di esempio. Per informazioni sull'installazione dell'interfaccia della riga di comando di Hugging Face e sulla configurazione dell'account per il suo utilizzo, vedere interfaccia della riga di comando (CLI).

Dopo aver installato la CLI, apri un terminale, naviga nella directory Models che hai creato e digita il comando seguente.

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

Al termine dell'operazione, verificare che il file seguente esista: [Project Directory]\Models\directml\directml-int4-awq-block-128\model.onnx.

In Esplora soluzioni, espandi la cartella "directml-int4-awq-block-128" e seleziona tutti i file nella cartella. Nel riquadro Proprietà file, impostare Copia nella directory di output a "Copia se più recente".

Aggiungere una semplice interfaccia utente per interagire con il modello

Per questo esempio verrà creata un'interfaccia utente molto semplicistica con un TextBox per specificare un prompt, un Button per l'invio della richiesta e un TextBlock per visualizzare i messaggi di stato e le risposte del modello. Sostituire l'elemento predefinito StackPanel in MainWindow.xaml con il codice XAML seguente.

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

Inizializzare il modello

Nella sezione MainWindow.xaml.cs, aggiungi una direttiva using per lo spazio dei nomi Microsoft.ML.OnnxRuntimeGenAI.

using Microsoft.ML.OnnxRuntimeGenAI;

Dichiarare le variabili membro all'interno della definizione di classe MainPage per la modello di e l'Tokenizer. Imposta il percorso per i file del modello aggiunti nei passaggi precedenti.

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

Creare un metodo helper per inizializzare in modo asincrono il modello. Questo metodo chiama il costruttore per la classe modello , passando il percorso alla directory del modello. Il sistema crea quindi un nuovo Tokenizer dal modello.

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

Per questo esempio, il modello verrà caricato quando viene attivata la finestra principale. Aggiornare il costruttore della pagina per registrare un gestore per l'evento attivato.

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

L'evento Attivato può essere generato più volte, quindi nel gestore eventi si assicuri che il modello sia null prima di inizializzarlo.

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

Inviare il prompt al modello

Crea una funzione di supporto che invii il prompt al modello e poi restituisca in modo asincrono i risultati al chiamante con un IAsyncEnumerable.

In questo metodo, la classe generatore viene usata in un ciclo, chiamando GenerateNextToken in ogni passaggio per recuperare ciò che il modello prevede che i successivi caratteri, denominati token, debbano essere in base al prompt di input. Il ciclo viene eseguito fino a quando il generatore metodo IsDone restituisce true oppure fino a quando viene ricevuto uno dei token "<|end|>", "<|system|>" o "<|user|>", segnalandoci che possiamo interrompere la generazione di token.

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

Aggiungere il codice dell'interfaccia utente per inviare la richiesta e visualizzare i risultati

Nel Pulsante gestore clic verificare innanzitutto che il modello non sia Null. Crea una stringa di richiesta con il prompt del sistema e dell'utente e chiama InferStreaming, aggiornando il TextBlock con ogni parte della risposta.

Il modello usato in questo esempio è stato sottoposto a training per accettare richieste nel formato seguente, dove systemPrompt è le istruzioni per il comportamento del modello e userPrompt è la domanda dell'utente.

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

I modelli devono documentare le convenzioni di prompt. Per questo modello il formato è documentato sulla scheda del modello di Hugging Face .

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

Eseguire l'esempio

In Visual Studio, nel menu a tendina Piattaforme di soluzione, assicurati che il processore di destinazione sia impostato su x64. La libreria ONNXRuntime Generative per intelligenza artificiale non supporta x86. Compilare ed eseguire il progetto. Attendere che il TextBlock indichi che il modello è stato caricato. Digitare un prompt nella casella di testo del prompt e fare clic sul pulsante Invia. Dovresti vedere i risultati riempire gradualmente il blocco di testo.

Vedere anche