Compartir a través de


Introducción a Phi3 y otros modelos de lenguaje en la aplicación de Windows con ONNX Runtime Generative AI

Este artículo le guía a través de la creación de una aplicación WinUI 3 que usa un modelo Phi3 y la biblioteca de ONNX Runtime Generative AI para implementar una aplicación de chat de IA generativa sencilla. Los modelos de lenguaje grande (LLM) permiten agregar funcionalidades de generación, transformación, razonamiento y traducción de texto a la aplicación. Para obtener más información sobre el uso de modelos de inteligencia artificial y aprendizaje automático en la aplicación de Windows, consulte Introducción al uso de modelos de IA y Machine Learning en la aplicación de Windows. Para obtener más información sobre el entorno de ejecución de ONNX y la inteligencia artificial generativa, consulte Generative AI with ONNX Runtime.

Al utilizar características de IA, recomendamos que revise: Desarrollo de Aplicaciones y Características de IA Generativa Responsable en Windows.

¿Cuál es el ONNX Runtime

ONNX Runtime es un acelerador de modelos de aprendizaje automático multiplataforma, con una interfaz flexible para integrar bibliotecas específicas de hardware. ONNX Runtime se pueden usar con modelos de PyTorch, Tensorflow/Keras, TFLite, scikit-learny otros marcos. Para obtener más información, consulta el sitio web de ONNX Runtime en https://onnxruntime.ai/docs/.

Prerrequisitos

  • El dispositivo debe tener habilitado el modo de desarrollador. Para obtener más información, vea Habilitar el dispositivo para el desarrollo.
  • Visual Studio 2022 o posterior con la carga de trabajo de desarrollo de escritorio de .NET.

Creación de una nueva aplicación WinUI de C#

En Visual Studio, cree un nuevo proyecto. En el cuadro de diálogo Crear un nuevo proyecto, establezca el filtro de idioma en "C#" y el filtro de tipo de proyecto en "winui" y, a continuación, seleccione la plantilla Aplicación vacía, empaquetada (WinUI3 en escritorio). Asigne al nuevo proyecto el nombre "GenAIExample".

Añadir referencias al paquete NuGet ONNX Runtime Generative AI

En el Explorador de Soluciones, haga clic con el botón derecho en Dependencias y seleccione Administrar paquetes NuGet.... En el administrador de paquetes NuGet, seleccione la pestaña Examinar. Busque "Microsoft.ML.OnnxRuntimeGenAI.DirectML", seleccione la versión estable más reciente en el menú desplegable Versión y luego haga clic en Instalar.

Agregar un modelo y un archivo de vocabulario al proyecto

En Explorador de soluciones, haga clic con el botón derecho en su proyecto y seleccione Agregar->Nueva carpeta. Asigne a la nueva carpeta el nombre "Models". En este ejemplo, usaremos el modelo de https://huggingface.co/microsoft/Phi-3-mini-4k-instruct-onnx/tree/main/directml/directml-int4-awq-block-128.

Existen varias formas de recuperar modelos. Para este tutorial, usaremos la interfaz de línea de comandos (CLI) de Hugging Face. Si obtiene los modelos mediante otro método, es posible que tenga que ajustar las rutas de acceso de archivo al modelo en el código de ejemplo. Para obtener información sobre cómo instalar la CLI de Hugging Face y configurar la cuenta para usarla, consulte interfaz de la línea de comandos (CLI).

Después de instalar la CLI, abra un terminal, vaya al directorio Models que creó y escriba el siguiente comando.

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

Cuando se complete la operación, compruebe que existe el siguiente archivo: [Project Directory]\Models\directml\directml-int4-awq-block-128\model.onnx.

En Explorador de soluciones, expanda la carpeta "directml-int4-awq-block-128" y seleccione todos los archivos de la carpeta. En el panel Propiedades de archivo, establezca Copiar al directorio de salida en "Copiar si es más nuevo".

Agregar una interfaz de usuario sencilla para interactuar con el modelo

En este ejemplo, crearemos una interfaz de usuario muy simplista que tenga un TextBox para especificar un mensaje, un botón de para enviar la solicitud y un TextBlock para mostrar mensajes de estado y las respuestas del modelo. Reemplace el elemento StackPanel predeterminado en MainWindow.xaml por el código XAML siguiente.

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

Inicialización del modelo

En MainWindow.xaml.cs, agregue una directiva de uso para el espacio de nombres Microsoft.ML.OnnxRuntimeGenAI.

using Microsoft.ML.OnnxRuntimeGenAI;

Declara variables miembro dentro de la definición de la clase MainPage para el Modelo y el Tokenizer. Establezca la ubicación de los archivos de modelo que hemos agregado en los pasos 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");

Cree un método auxiliar para inicializar el modelo de forma asincrónica. Este método llama al constructor de la clase Modelo, pasando la ruta al directorio del modelo. A continuación se crea un nuevo Tokenizer a partir del 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";
        });
    });
}

En este ejemplo, se cargará el modelo cuando se active la ventana principal. Actualice el constructor de página para registrar un controlador para el evento Activado.

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

El evento Activado puede activarse múltiples veces, así que en el controlador de eventos, comprueba que el modelo es nulo antes de inicializarlo.

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

Enviar la solicitud al modelo.

Cree un método de ayuda que envíe la solicitud al modelo y luego devuelva asíncronamente los resultados a la persona que llama con un IAsyncEnumerable.

En este método, la clase Generator se usa en un bucle, llamando a GenerateNextToken en cada pasada para recuperar lo que el modelo predice que los próximos caracteres, llamados token, deben ser basados en la entrada de la consulta. El bucle se ejecuta hasta que el generador IsDone método devuelve true o hasta que cualquiera de los tokens "<|end|>", "<|system|>" o "<|user|>" se reciben, lo que indica que podemos dejar de generar 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;
    }
}

Agregar código de interfaz de usuario para enviar el mensaje y mostrar los resultados

En el controlador de clic Botón, primero verifique que el modelo no es nulo. Cree una cadena de consulta con la consulta del sistema y del usuario y llame a InferStreaming, actualizando el TextBlock con cada parte de la respuesta.

El modelo usado en este ejemplo se ha entrenado para aceptar solicitudes en el formato siguiente, donde systemPrompt es las instrucciones para el comportamiento del modelo y userPrompt es la pregunta del usuario.

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

Los modelos deben documentar sus convenciones sobre las instrucciones. Para este modelo, el formato está documentado en la tarjeta del 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;
        }
    }
}

Ejecución del ejemplo

En Visual Studio , en el menú desplegable Plataformas de solución, asegúrese de que el procesador de destino está establecido en x64. La biblioteca de IA generativa de ONNXRuntime no admite x86. Compile y ejecute el proyecto. Espere a que el TextBlock indique que se ha cargado el modelo. Escriba un mensaje en el cuadro de texto del mensaje y haga clic en el botón Enviar. Debería ver que los resultados rellenan gradualmente el bloque de texto.

Consulte también