Поделиться через


Начало работы с Phi3 и другими языковыми моделями в приложении Для Windows ONNX Runtime Generative AI

В этой статье описывается создание приложения WinUI 3, использующего модель Phi3 и ONNX Runtime Generative AI библиотеку для реализации простого приложения чата искусственного интеллекта. Большие языковые модели (LLMs) позволяют добавлять в приложение возможности создания текста, преобразования, анализа и перевода. Дополнительные сведения об использовании моделей искусственного интеллекта и машинного обучения в приложении Windows см. в статье "Начало работы с ИИ" и Машинное обучение моделей в приложении Windows. Дополнительные сведения о среде выполнения ONNX и генерируемом ИИ см. в разделе "Создание искусственного интеллекта с ONNX Runtimeпомощью".

Что такое ONNX Runtime

ONNX Runtime — это кроссплатформенный акселератор модели машинного обучения с гибким интерфейсом для интеграции библиотек, относящихся к оборудованию. ONNX Runtime можно использовать с моделями из PyTorch, Tensorflow/Keras, TFLite и scikit-learnдругих платформ. Дополнительные сведения см. на ONNX Runtime веб-сайте https://onnxruntime.ai/docs/.

Необходимые компоненты

  • Устройство должно быть включено в режиме разработчика. Дополнительные сведения см. в разделе "Включение устройства для разработки".
  • Visual Studio 2022 или более поздней версии с рабочей нагрузкой разработки классических приложений .NET.

Создание нового приложения WinUI C#

В Visual Studio создайте проект . В диалоговом окне "Создание проекта" задайте для фильтра языка значение "C#", а фильтр типа проекта — "winui", а затем выберите пустое приложение, упаковав (WinUI3 в desktop). Назовите новый проект GenAIExample.

Добавление ссылок на ONNX Runtime Generative AI пакет Nuget

В Обозреватель решений щелкните правой кнопкой мыши зависимости и выберите пункт "Управление пакетами NuGet...". В диспетчере пакетов NuGet выберите вкладку "Обзор". Найдите "Microsoft.ML.OnnxRuntimeGenAI.DirectML", выберите последнюю стабильную версию в раскрывающемся списке "Версия" и нажмите кнопку "Установить".

Добавление файла модели и словаря в проект

В Обозреватель решений щелкните проект правой кнопкой мыши и выберите "Добавить новую> папку". Назовите новую папку "Модели". В этом примере мы будем использовать модель из https://huggingface.co/microsoft/Phi-3-mini-4k-instruct-onnx/tree/main/directml/directml-int4-awq-block-128.

Существует несколько различных способов извлечения моделей. В этом пошаговом руководстве мы будем использовать интерфейс командной строки распознавания лиц (CLI). При получении моделей с помощью другого метода может потребоваться настроить пути к файлу модели в примере кода. Сведения об установке интерфейса командной строки Обнимания лиц и настройке учетной записи для его использования см. в разделе "Интерфейс командной строки( 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 и выберите все файлы в папке. В области "Свойства файла" установите для параметра Copy to Output Directory значение Copy if new.

Добавление простого пользовательского интерфейса для взаимодействия с моделью

В этом примере мы создадим очень простой пользовательский интерфейс с текстовым полем для указания запроса, кнопки для отправки запроса и TextBlock для отображения сообщений о состоянии и ответов модели. Замените элемент MainWindow.xaml StackPanel по умолчанию следующим кодом 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 указывает, что модель загружена. Введите запрос в текстовое поле запроса и нажмите кнопку "Отправить". Результаты будут постепенно заполняться текстовым блоком.

См. также