Partager via


Commencer avec Phi3 et d’autres modèles de langage dans votre application Windows avec ONNX Runtime Generative AI

Cet article vous guide tout au long de la création d’une application WinUI 3 qui utilise un modèle Phi3 et la bibliothèque ONNX Runtime Generative AI pour implémenter une application de conversation d’IA générative simple. Les grandes modèles de langage (LLMs) vous permettent d’ajouter des fonctionnalités de génération de texte, de transformation, de raisonnement et de traduction à votre application. Pour plus d’informations sur l’utilisation des modèles IA et Machine Learning dans votre application Windows, consultez Commencer avec les modèles IA et Machine Learning dans votre application Windows. Pour plus d’informations sur le runtime ONNX et l’IA générative, consultez IA générative avec ONNX Runtime.

Quel est le ONNX Runtime

ONNX Runtime est un accélérateur de modèle Machine Learning multiplateforme avec une interface flexible pour intégrer des bibliothèques spécifiques au matériel. ONNX Runtime peut être utilisé avec des modèles à partir de PyTorch, Tensorflow/Keras, TFLite, scikit-learn et d’autres frameworks. Pour plus d’informations, consultez le site web ONNX Runtime à l’adresse suivante : https://onnxruntime.ai/docs/.

Prérequis

  • Le mode développeur doit être activé sur votre appareil. Pour plus d’informations, consultez Activer votre appareil pour le développement.
  • Visual Studio 2022 ou version ultérieure avec la charge de travail de développement de bureau .NET.

Créer une application C# WinUI

Dans Visual Studio, créez un projet. Dans la boîte de dialogue Créer un projet, définissez le filtre de langage sur « C# » et le filtre de type de projet sur « winui », puis sélectionnez le modèle Blank app, Packaged (WinUI3 in Desktop). Nommez le nouveau projet « GenAIExample ».

Ajouter des références au package Nuget ONNX Runtime Generative AI

Dans Explorateur de solutions, cliquez avec le bouton droit sur Dépendances et sélectionnez Gérer les packages NuGet.... Dans le gestionnaire de package NuGet, sélectionnez l’onglet Parcourir. Recherchez « Microsoft.ML.OnnxRuntimeGenAI.DirectML », sélectionnez la dernière version stable dans la liste déroulante de Version, puis cliquez sur Installer.

Ajouter un modèle et un fichier de vocabulaire à votre projet

Dans l’Explorateur de solutions, cliquez avec le bouton droit sur le projet, puis sélectionnez Ajouter->Nouveau dossier. Nommez ce nouveau dossier « Modèles ». Pour cet exemple, nous allons utiliser le modèle de https://huggingface.co/microsoft/Phi-3-mini-4k-instruct-onnx/tree/main/directml/directml-int4-awq-block-128.

Il existe plusieurs façons de récupérer des modèles. Pour cette procédure pas à pas, nous allons utiliser l’interface de ligne de commande (CLI) Hugging Face. Si vous obtenez les modèles à l’aide d’une autre méthode, vous devrez peut-être ajuster les chemins d’accès aux fichiers au modèle dans l’exemple de code. Pour plus d’informations sur l’installation de l’interface CLI Hugging Face et la configuration de votre compte pour l’utiliser, consultez lnterface de ligne de commande (CLI).

Après avoir installé l’interface CLI, ouvrez un terminal, accédez au répertoire Models que vous avez créé et tapez la commande suivante.

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

Une fois l’opération terminée, vérifiez que le fichier suivant existe : [Project Directory]\Models\directml\directml-int4-awq-block-128\model.onnx.

Dans Explorateur de solutions, développez le dossier « directml-int4-awq-block-128 », puis sélectionnez tous les fichiers du dossier. Dans le volet Propriétés de fichier, définissez la valeur Copier dans le répertoire de sortie sur « Copier si plus récent ».

Ajouter une interface utilisateur simple pour interagir avec le modèle

Pour cet exemple, nous allons créer une interface utilisateur très simpliste qui a une TextBox pour spécifier une invite, un bouton pour envoyer l’invite et un TextBlock pour afficher les messages de statut et les réponses du modèle. Remplacez l’élément StackPanel par défaut dans MainWindow.xaml par le code XAML suivant.

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

Initialiser le modèle

Dans MainWindow.xaml.cs, ajoutez une directive using pour l’espace de noms Microsoft.ML.OnnxRuntimeGenAI.

using Microsoft.ML.OnnxRuntimeGenAI;

Déclarez des variables membres à l’intérieur de la définition de classe MainPage pour le modèle et le générateur de jetons. Définissez l’emplacement des fichiers de modèle que nous avons ajoutés aux étapes précédentes.

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

Créez une méthode d’assistance pour initialiser de façon asynchrone le modèle. Cette méthode appelle le constructeur pour la classe Modèle, en transférant le chemin d’accès au répertoire du modèle. Elle crée ensuite un générateur de jetons à partir du modèle.

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

Pour cet exemple, nous allons charger le modèle lorsque la fenêtre principale est activée. Mettez à jour le constructeur de page pour inscrire un gestionnaire pour l’événement Activé.

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

L’événement Activé peut être déclenché plusieurs fois. Dans le gestionnaire d’événements, assurez-vous que le modèle est null avant de l’initialiser.

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

Envoyer l’invite au modèle

Créez une méthode d’assistance qui envoie l’invite au modèle, puis retourne de manière asynchrone les résultats à l’appelant avec un IAsyncEnumerable.

Dans cette méthode, la classe Générateur est utilisée dans une boucle, appelant GenerateNextToken dans chaque passage pour récupérer ce que le modèle prédit. Les caractères suivants, appelés jetons, doivent être basés sur l’invite d’entrée. La boucle s’exécute jusqu’à ce que la méthode IsDone du générateur retourne true ou jusqu’à ce que l’un des jetons « <|end|> », « <|system|> » ou « <|user|> » soit reçu, ce qui signale que nous pouvons arrêter de générer des jetons.

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

Ajouter du code d’interface utilisateur pour envoyer l’invite et afficher les résultats

Dans le gestionnaire de clics de bouton, vérifiez d’abord que le modèle n’est pas null. Créez une chaîne d’invite avec l’invite système et utilisateur et appelez InferStreaming, mettant à jour TextBlock avec chaque partie de la réponse.

Le modèle utilisé dans cet exemple a été entraîné pour accepter les invites au format suivant, où systemPrompt sont les instructions pour le comportement du modèle et userPrompt est la question de l’utilisateur.

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

Les modèles doivent documenter leurs conventions d’invite. Pour ce modèle, le format est documenté sur la carte du modèle 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;
        }
    }
}

Exécuter l’exemple

Dans Visual Studio, dans la liste déroulante Plateformes de solution, vérifiez que le processeur cible est défini sur x64. La bibliothèque d’IA générative ONNXRuntime ne prend pas en charge x86. Générez et exécutez le projet. Attendez que TextBlock indique que le modèle a été chargé. Tapez une invite dans la zone de texte d’invite, puis cliquez sur le bouton Envoyer. Vous devez voir les résultats remplir progressivement le bloc de texte.

Voir aussi