Partilhar via


Comece com o Phi3 e outros modelos de linguagem na sua aplicação Windows com o ONNX Runtime Generative AI

Este artigo orienta você na criação de um aplicativo WinUI 3 que usa um modelo Phi3 e a biblioteca ONNX Runtime Generative AI para implementar um aplicativo de bate-papo de IA generativo simples. Os modelos de linguagem grande (LLMs) permitem que você adicione recursos de geração, transformação, raciocínio e tradução de texto ao seu aplicativo. Para obter mais informações sobre como usar modelos de IA e aprendizado de máquina em seu aplicativo do Windows, consulte Introdução ao uso de modelos de IA e Machine Learning em seu aplicativo do Windows. Para obter mais informações sobre o tempo de execução ONNX e a IA generativa, consulte Generative AI with ONNX Runtime.

Ao utilizar recursos de IA, recomendamos que você revise: Desenvolvendo aplicativos e recursos de IA generativa responsável no Windows.

Qual é o ONNX Runtime

ONNX Runtime é um acelerador de modelo de aprendizado de máquina multiplataforma, com uma interface flexível para integrar bibliotecas específicas de hardware. ONNX Runtime pode ser usado com modelos de PyTorch, Tensorflow/Keras, TFLite, scikit-learne outros frameworks. Para mais informações, visite o site ONNX Runtime em https://onnxruntime.ai/docs/.

Pré-requisitos

  • Seu dispositivo deve ter o modo de desenvolvedor ativado. Para obter mais informações, consulte Habilitar seu dispositivo para desenvolvimento.
  • Visual Studio 2022 ou posterior com o workload de desenvolvimento .NET para desktop.

Criar um novo aplicativo WinUI em C#

No Visual Studio, crie um novo projeto. Na caixa de diálogo Criar um novo projeto, defina o filtro de idioma para "C#" e o filtro do tipo de projeto para "winui", depois selecione o modelo Aplicativo em branco, Empacotado (WinUI3 no ambiente de trabalho). Nomeie o novo projeto como "GenAIExample".

Adicionar referências ao pacote ONNX Runtime Generative AI Nuget

No Explorador de Soluções, clique com o botão direito do mouse em Dependências e selecione Gerir pacotes NuGet.... No gestor de pacotes NuGet, selecione o separador Procurar. Procure "Microsoft.ML.OnnxRuntimeGenAI.DirectML", selecione a versão estável mais recente na lista suspensa de Versões e clique em Instalar.

Adicione um arquivo de modelo e vocabulário ao seu projeto

No Explorador de Soluções, clique com o botão direito do mouse no seu projeto e selecione Adicionar ->Nova Pasta. Nomeie a nova pasta "Modelos". Para este exemplo, usaremos o modelo de https://huggingface.co/microsoft/Phi-3-mini-4k-instruct-onnx/tree/main/directml/directml-int4-awq-block-128.

Existem várias maneiras diferentes de recuperar modelos. Para este passo a passo, usaremos a CLI (Hugging Face Command Line Interface). Se você obter os modelos usando outro método, talvez seja necessário ajustar os caminhos de arquivo para o modelo no código de exemplo. Para obter informações sobre como instalar a CLI do Hugging Face e configurar sua conta para usá-la, consulte Command Line Interface (CLI).

Depois de instalar a CLI, abra um terminal, navegue até o diretório Models criado e digite o seguinte comando.

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

Quando a operação estiver concluída, verifique se existe o seguinte ficheiro: [Project Directory]\Models\directml\directml-int4-awq-block-128\model.onnx.

No Gerenciador de Soluções , expanda a pasta "directml-int4-awq-block-128" e selecione todos os arquivos na pasta. No painel Propriedades do Arquivo , defina Copiar para Diretório de Saída como "Copiar se for mais recente".

Adicionar uma interface do usuário simples para interagir com o modelo

Para este exemplo, criaremos uma interface do usuário muito simplista composta por um TextBox para especificar um prompt, um Button para enviar o prompt e um TextBlock para exibir mensagens de status e as respostas do modelo. Substitua o elemento padrão StackPanel no MainWindow.xaml pelo seguinte 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>

Inicializar o modelo

Em MainWindow.xaml.cs, adicione uma instrução 'using' para o namespace Microsoft.ML.OnnxRuntimeGenAI.

using Microsoft.ML.OnnxRuntimeGenAI;

Declare variáveis de membro dentro da definição de classe MainPage para o Model e o Tokenizer. Defina o local para os arquivos de modelo que adicionamos nas etapas anteriores.

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

Crie um método auxiliar para inicializar o modelo de forma assíncrona. Esse método invoca o construtor da classe Model, fornecendo o caminho do diretório do modelo. Em seguida, cria um novo Tokenizer a partir do modelo.

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

Para este exemplo, carregaremos o modelo quando a janela principal for ativada. Atualize o construtor de página para registar um manipulador para o evento Activated.

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

O evento Activated pode ser gerado várias vezes, portanto, no manipulador de eventos, verifique se o modelo é nulo antes de inicializá-lo.

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

Enviar o prompt para o modelo

Crie um método auxiliar que envie o prompt para o modelo e, em seguida, retorne de forma assíncrona os resultados ao chamador com um IAsyncEnumerable.

Nesse método, a classe Generator é usada em um loop, chamando GenerateNextToken em cada passagem para recuperar o que o modelo prevê que os próximos caracteres, chamados de token, devem ser baseados no prompt de entrada. O loop é executado até que o gerador método IsDone retorne true ou até que qualquer um dos tokens "<|end|>", "<|system|>", ou "<|user|>" são recebidos, o que sinaliza que podemos parar de gerar 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;
    }
}

Adicione o código da interface do usuário para enviar o prompt e exibir os resultados

No manipulador de cliques do botão , verifique primeiro se o modelo não é nulo. Crie uma cadeia de caracteres de prompt com o sistema e o prompt do usuário e chame InferStreaming, atualizando o TextBlock com cada parte da resposta.

O modelo usado neste exemplo foi treinado para aceitar prompts no formato a seguir, onde systemPrompt são as instruções de como o modelo deve se comportar e userPrompt é a pergunta do usuário.

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

Os modelos devem documentar as suas convenções imediatas. Para este modelo, o formato está documentado no cartão modelo 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;
        }
    }
}

Execute o exemplo

No Visual Studio, na lista suspensa Plataformas de Solução, verifique se o processador de destino está definido como x64. A biblioteca ONNXRuntime Generative AI não suporta x86. Crie e execute o projeto. Aguarde até que o TextBlock indique que o modelo foi carregado. Digite um prompt na caixa de texto do prompt e clique no botão enviar. Você deve ver os resultados preencherem gradualmente o bloco de texto.

Ver também