Introducción a Phi3 y otros modelos de lenguaje en su aplicación de Windows con la ONNX Runtime Generative AI
En este artículo se explica cómo crear una aplicación WinUI 3 que use un modelo Phi3 y la biblioteca ONNX Runtime Generative AI para implementar una sencilla aplicación de chat IA generativa. Los grandes modelos de lenguaje (LLM) permiten agregar capacidades de generación, transformación, razonamiento y traducción de texto a tu aplicación. Para más información sobre el uso de modelos de IA y Machine Learning 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 más información sobre el tiempo de ejecución de ONNX y la IA generativa, consulte IA Generativa con ONNX Runtime.
Qué es el ONNX Runtime
ONNX Runtime es un acelerador de modelos de Machine Learning multiplataforma, con una interfaz flexible para integrar librerías específicas de hardware. ONNX Runtime puede usarse con modelos de PyTorch, Tensorflow/Keras, TFLite, scikit-learn y otros marcos. Para obtener más información, consulta el sitio web de ONNX Runtime en https://onnxruntime.ai/docs/.
Requisitos previos
- 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 .NET.
Crear una nueva aplicación C# WinUI
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). Nombre el nuevo proyecto "GenAIExample".
Añadir referencias al paquete NuGet ONNX Runtime Generative AI
En Explorador de soluciones, haga clic con el botón derecho en Dependencias y seleccione Administrar paquetes NuGet.... En el gestor de paquetes NuGet, seleccione la pestaña Examinar. Busque "Microsoft.ML.OnnxRuntimeGenAI.DirectML", seleccione la última versión estable en el menú desplegable Versión y, a continuación, 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. Nombra la nueva carpeta "Modelos". Para 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 archivo al modelo en el código de ejemplo. Para obtener información sobre cómo instalar la CLI de Hugging Face y configurar su 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 finalice 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 simple que tiene un TextBox para especificar una consulta, un Botón para enviar el mensaje, y un TextBlock para mostrar los mensajes de estado y las respuestas del modelo. Reemplace el elemento predeterminado StackPanel en MainWindow.xaml
con el siguiente 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 el 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 agregamos 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 de ayuda para inicializar el modelo de forma asíncrona. 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";
});
});
}
Para este ejemplo, cargaremos 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 método generador IsDonedevuelve verdadero o hasta que se recibe cualquiera de los tokens <|end |>", “<|system |>”, o "<|user |>", 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 la consulta 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 entrenó para aceptar instrucciones con el siguiente formato, en el que systemPrompt
son las instrucciones sobre cómo debe comportarse el 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;
}
}
}
Ejecutar el 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 librería ONNXRuntime IA Generativa no soporta x86. Crear y ejecutar el proyecto. Espere a que el TextBlock indique que el modelo se cargó. Escriba un mensaje en el cuadro de texto y haga clic en el botón Enviar. Debería ver cómo los resultados van apareciendo gradualmente en el bloque de texto.