Начните работать с Phi3 и другими языковыми моделями в вашем приложении Windows с ONNX Runtime Generative AI
В этой статье описывается создание приложения WinUI 3, использующего модель Phi3 и библиотеку ONNX Runtime Generative AI для реализации простого приложения чата искусственного интеллекта. Большие языковые модели (LLMs) позволяют добавлять в приложение возможности создания текста, преобразования, анализа и перевода. Дополнительные сведения об использовании моделей ИИ и машинного обучения в приложении Windows см. в статье Начало работы с моделями ИИ и машинного обучения в приложении Windows. Дополнительные сведения о среде выполнения ONNX и генерируемом ИИ см. в разделе Generative AI с ONNX Runtime.
При использовании функций ИИ рекомендуется ознакомиться с: Разработка ответственных приложений и функций генеративного искусственного интеллекта в Windows.
Что такое ONNX Runtime
ONNX Runtime — это кроссплатформенный акселератор модели машинного обучения с гибким интерфейсом для интеграции библиотек, относящихся к оборудованию. ONNX Runtime можно использовать с моделями из PyTorch, Tensorflow/Keras, TFLite, scikit-learnи других платформ. Дополнительные сведения см. на веб-сайте ONNX Runtimehttps://onnxruntime.ai/docs/.
Необходимые условия
- Устройство должно быть включено в режиме разработчика. Для получения дополнительной информации см. Включение устройства для разработки.
- Visual Studio 2022 или новее с рабочей нагрузкой разработки настольных приложений .NET.
Создание нового приложения WinUI C#
В Visual Studio создайте проект. В диалоговом окне Создание нового проекта установите фильтр языка на «C#» и фильтр типа проекта на «winui», затем выберите шаблон Пустое приложение, упакованное (WinUI3 в Desktop). Назовите новый проект GenAIExample.
Добавление ссылок на пакет Nuget ONNX Runtime Generative AI
В Обозревателе решенийщелкните правой кнопкой мыши на Зависимости и выберите Управление пакетами NuGet.... В диспетчере пакетов NuGet выберите вкладку Обзор. Найдите "Microsoft.ML.OnnxRuntimeGenAI.DirectML", выберите последнюю стабильную версию в раскрывающемся списке Версия и нажмите Установить.
Добавление файла модели и словаря в проект
В обозревателе решенийщелкните правой кнопкой мыши на проекте и выберите Добавить ->Новая папка. Назовите новую папку "Модели". В этом примере мы будем использовать модель из https://huggingface.co/microsoft/Phi-3-mini-4k-instruct-onnx/tree/main/directml/directml-int4-awq-block-128.
Существует несколько различных способов извлечения моделей. В этом пошаговом руководстве мы будем использовать интерфейс командной строки Hugging Face (CLI). При получении моделей с помощью другого метода может потребоваться настроить пути к файлу модели в примере кода. Для получения информации об установке интерфейса командной строки (CLI) Hugging Face и настройке учетной записи для его использования см. в разделе Интерфейс командной строки (CLI).
После установки интерфейса командной строки откройте терминал, перейдите к созданному каталогу Models
и введите следующую команду.
huggingface-cli download microsoft/Phi-3-mini-4k-instruct-onnx --include directml/* --local-dir .
После завершения операции убедитесь, что существует следующий файл: [Project Directory]\Models\directml\directml-int4-awq-block-128\model.onnx
.
В обозревателе решенийразверните папку directml-int4-awq-block-128 и выберите все файлы в папке. В области свойств файла
Добавление простого пользовательского интерфейса для взаимодействия с моделью
В этом примере мы создадим очень простой пользовательский интерфейс с TextBox для указания запроса, кнопкой для отправки запроса и TextBlock для отображения сообщений о состоянии и ответов модели. Замените элемент StackPanel по умолчанию в MainWindow.xaml
следующим 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>
Инициализация модели
В MainWindow.xaml.cs
добавьте директиву using для пространства имен Microsoft.ML.OnnxRuntimeGenAI.
using Microsoft.ML.OnnxRuntimeGenAI;
Объявите переменные-члены внутри определения класса MainPage для модели и токенизатора. Задайте расположение для файлов модели, добавленных на предыдущих шагах.
private Model? model = null;
private Tokenizer? tokenizer = null;
private readonly string ModelDir =
Path.Combine(AppDomain.CurrentDomain.BaseDirectory,
@"Models\directml\directml-int4-awq-block-128");
Создайте вспомогательный метод для асинхронной инициализации модели. Этот метод вызывает конструктор для класса Model, передав путь к каталогу модели. Затем он создает новый токенизатор на основе модели.
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";
});
});
}
В этом примере мы загрузим модель при активации главного окна. Обновите конструктор страницы, чтобы зарегистрировать обработчик для события Активировано.
public MainWindow()
{
this.InitializeComponent();
this.Activated += MainWindow_Activated;
}
Событие Активировано может вызываться несколько раз. Поэтому в обработчике события убедитесь, что модель равна NULL перед инициализацией.
private async void MainWindow_Activated(object sender, WindowActivatedEventArgs args)
{
if (model == null)
{
await InitializeModelAsync();
}
}
Отправьте запрос в модель
Создайте вспомогательный метод, который отправляет запрос в модель и затем асинхронно возвращает результаты вызывающему с IAsyncEnumerable.
В этом методе в цикле используется класс генератора , который в каждом проходе вызывает GenerateNextToken, чтобы модель спрогнозировала, какими должны быть следующие несколько символов, которые называются токеном, на основе входной строки. Цикл выполняется до тех пор, пока метод IsDone не возвращает значение true или пока не будет получен любой из токенов "<|end|>", "<|system|>", или "<|user|>", что сигнализирует о том, что мы можем остановить создание токенов.
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;
}
}
Добавление кода пользовательского интерфейса для отправки запроса и отображения результатов
В обработчике нажатия кнопки сначала убедитесь, что модель не имеет значения NULL. Создайте строку запроса, соединяя системный и пользовательский запросы, и вызовите InferStreaming, обновляя TextBlock каждой частью ответа.
Модель, используемая в этом примере, была обучена принимать запросы в следующем формате, где systemPrompt
приведены инструкции о том, как модель должна вести себя, и userPrompt
вопрос от пользователя.
<|system|>{systemPrompt}<|end|><|user|>{userPrompt}<|end|><|assistant|>
Модели должны документировать свои конвенции запросов. Для этой модели формат задокументирован на карточке модели 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;
}
}
}
Запустите пример
В Visual Studio в раскрывающемся списке платформ решений убедитесь, что целевой процессор имеет значение x64. Библиотека генеративного ИИ ONNXRuntime не поддерживает x86. Соберите и запустите проект. Дождитесь, пока TextBlock укажет, что модель загружена. Введите запрос в текстовое поле запроса и нажмите кнопку "Отправить". Вы должны увидеть, как результаты постепенно заполняют текстовый блок.