Partager via


Commencez 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 de ONNX Runtime Generative AI pour implémenter une application de conversation IA générative simple. Les modèles de langage volumineux (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 de modèles IA et Machine Learning dans votre application Windows, consultez Bien démarrer 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, voir L'IA générative avec ONNX Runtime.

Lors de l’utilisation des fonctionnalités IA, nous vous recommandons de passer en revue : Développement d’applications et de fonctionnalités d’INTELLIGENCE artificielle responsable sur Windows.

Qu'est-ce que 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 les modèles de PyTorch, Tensorflow/Keras, TFLite, scikit-learnet d’autres frameworks. Pour plus d’informations, consultez le site web ONNX Runtime à l'adresse https://onnxruntime.ai/docs/.

Conditions préalables

Créer une application WinUI C#

Dans Visual Studio, créez un projet. Dans la boîte de dialogue Créer un projet, définissez le filtre de langue sur « C# » et le filtre de type de projet sur « winui », puis sélectionnez le modèle Application vide, Empaquetée (WinUI3 dans Desktop) modèle. 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, puis 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 Version, puis cliquez sur Installer.

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

Dans l'Explorateur de solutions, cliquez avec le bouton droit de la souris sur votre projet et sélectionnez Ajouter> un nouveau dossier. Nommez le nouveau dossier « Models ». Pour cet exemple, nous allons utiliser le modèle à partir 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 en ligne de commande (CLI) de Hugging Face. Si vous obtenez les modèles à l’aide d’une autre méthode, vous devrez peut-être ajuster les chemins de fichier au modèle dans l’exemple de code. Pour plus d’informations sur l’installation de l'interface en ligne de commande (CLI) Hugging Face et la configuration de votre compte pour l’utiliser, consultez interface 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éé, puis 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éroulez le dossier « directml-int4-awq-block-128 » et sélectionnez tous les fichiers du dossier. Dans le volet Propriétés du fichier, définissez 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 un TextBox pour spécifier une invite, un bouton pour envoyer l’invite et un TextBlock pour afficher les messages d’état et les réponses du modèle. Remplacez l’élément par défaut StackPanel 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 dans la définition de la classe MainPage pour le modèle et le tokenizer. 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 de la classe Model, en lui transmettant le chemin d'accès au répertoire du modèle. Ensuite, il crée un nouveau Tokenizer à 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. Mettre à jour le constructeur de la page afin d'enregistrer un gestionnaire pour l'événement Activated.

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

L’événement activé peut être déclenché plusieurs fois. Par conséquent, dans le gestionnaire d’événements, vérifiez 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'aide qui soumet la requête au modèle, puis renvoie de manière asynchrone les résultats à l'appelant à l'aide d'un IAsyncEnumerable.

Dans cette méthode, la classe Generator est utilisée dans une boucle, appelant GenerateNextToken à chaque passage pour récupérer ce que le modèle prédit que les prochains caractères, appelés token, devraient être en fonction de la requête d'entrée. La boucle s’exécute jusqu’à ce que le générateur isDone méthode retourne true ou jusqu’à ce que l’un des jetons «<|end|>« , "<|system|>« ou »<|user|>« sont reçus, ce qui signale que nous pouvons arrêter la génération de 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 du clic sur le bouton, vérifiez d'abord que le modèle n'est pas nul. Créez une requête avec l'invite du système et de l'utilisateur et appelez InferStreaming, en mettant à jour le TextBlock avec chaque partie de la réponse.

Le modèle utilisé dans cet exemple a été formé pour accepter les requêtes selon le format suivant ; où systemPrompt représente les instructions sur le comportement du modèle et userPrompt la question de l'utilisateur.

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

Les modèles doivent documenter leurs conventions de requête. Pour ce modèle, le format est documenté sur la carte de 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 solutions, vérifiez que le processeur cible est défini sur x64. La bibliothèque ONNXRuntime Generative AI ne prend pas en charge x86. Générez et exécutez le projet. Attendez que le 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