共用方式為


開始使用 Windows 應用程式中的 Phi3 和其他語言模型 ONNX Runtime Generative AI

本文將逐步引導您建立 WinUI 3 應用程式,該應用程式會使用 Phi3 模型和連結 ONNX Runtime Generative AI 庫來實作簡單的產生 AI 聊天應用程式。 大型語言模型 (LLM) 可讓您將文字產生、轉換、推理和翻譯功能新增至您的應用程式。 如需在 Windows 應用程式中使用 AI 和機器學習模型的詳細資訊,請參閱在 Windows 應用程式中開始使用 AI 和 機器學習 模型。 如需 ONNX 運行時間和行性 AI 的詳細資訊,請參閱 使用 ONNX Runtime的 Generative AI。

什麼是 ONNX Runtime

ONNX Runtime 是跨平台機器學習模型加速器,具有彈性介面來整合硬體特定連結庫。 ONNX Runtime 可以搭配來自 PyTorch、Tensorflow/Keras、TFLite、 scikit-learn和其他架構的模型使用。 如需詳細資訊,請參閱 ONNX Runtime 位於 https://onnxruntime.ai/docs/的網站。

必要條件

  • 您的裝置必須啟用開發人員模式。 如需詳細資訊,請參閱啟用您的裝置以用於開發
  • Visual Studio 2022 或更新版本搭配 .NET 桌面開發工作負載。

建立新的 C# WinUI 應用程式

在 Visual Studio 中,建立新專案。 在 [ 建立新專案 ] 對話框中,將語言篩選器設定為 “C#”,並將專案類型篩選設定為 “winui”,然後選取 [空白應用程式]、[封裝] (Desktop 中的 WinUI3) 範本。 將新專案命名為 「GenAIExample」。

新增 Nuget 套件的 ONNX Runtime Generative AI 參考

在 [方案總管] 中,以滑鼠右鍵按兩下 [相依性],然後選取 [管理 NuGet 套件...]。在 NuGet 套件管理員中,選取 [流覽] 索引標籤。搜尋 “Microsoft.ML.OnnxRuntimeGenAI.DirectML”,在 [版本] 下拉式清單中選取最新的穩定版本,然後按兩下 [安裝]。

將模型和詞彙檔案新增至您的專案

方案總管 中,以滑鼠右鍵按下您的項目,然後選取 [新增>資料夾]。 將新資料夾命名為 「Models」。 在此範例中,我們將使用 來自 https://huggingface.co/microsoft/Phi-3-mini-4k-instruct-onnx/tree/main/directml/directml-int4-awq-block-128的模型。

有數種不同的方法來擷取模型。 在本逐步解說中,我們將使用擁抱臉部命令行介面 (CLI)。 如果您使用其他方法取得模型,您可能必須調整範例程序代碼中的模型檔案路徑。 如需安裝擁抱臉部 CLI 並設定您的帳戶以使用它的資訊,請參閱 命令行介面 (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」 資料夾,然後選取資料夾中的所有檔案。 在 [檔案屬性] 窗格中,將 [複製到輸出目錄] 設定為 [如果更新時複製]。

新增簡單的UI以與模型互動

在此範例中,我們將建立一個非常簡單的UI,其具有 用於指定提示的TextBox提交提示的Button ,以及 用來顯示狀態消息的 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,新增 Microsoft.ML.OnnxRuntimeGenAI 命名空間的 using 指示詞。

using Microsoft.ML.OnnxRuntimeGenAI;

在 Model 和 Tokenizer 的 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 類別的建構函式,並傳入模型目錄的路徑。 接下來,它會從模型建立新的 Tokenizer

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

在此範例中,我們會在啟動主視窗時載入模型。 更新頁面建構函式以註冊 Activated 事件的處理程式

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

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

新增UI程式代碼以提交提示並顯示結果

在 [按鈕] 按兩下處理程式中,先確認模型不是 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 Generative AI 連結庫不支援 x86。 建置並執行專案。 等候 TextBlock 指出模型已載入。 在提示文字框中輸入提示,然後按兩下 [提交] 按鈕。 您應該會看到結果逐漸填入文字塊。

另請參閱