使用 ONNX Runtime Generative AI,在 Windows 應用程式中開始使用 Phi3 和其他語言模型
本文將逐步引導您建立使用 Phi3 模型和 ONNX Runtime Generative AI 連結庫的 WinUI 3 應用程式,以實作簡單的產生 AI 聊天應用程式。 大型語言模型 (LLM) 可讓您將文字產生、轉換、推理和翻譯功能新增至您的應用程式。 如需在 Windows 應用程式中使用 AI 和機器學習模型的詳細資訊,請參閱在 Windows 上開始使用 AI。 如需 ONNX runtime 和生成式 AI 的詳細資訊,請參閱 Generative AI 使用 ONNX Runtime。
使用 AI 功能時,建議您檢閱:在 Windows上開發負責任的產生式 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」。
將參考新增至 ONNX Runtime Generative AI NuGet 套件
在 [方案總管]中,右鍵點擊 [相依性],然後選取 [管理 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的模型。
有數種不同的方法來擷取模型。 在本逐步解說中,我們將使用 Hugging Face 命令列介面(CLI)。 如果您使用其他方法取得模型,您可能必須調整範例程序代碼中的模型檔案路徑。 如需安裝 Hugging Face 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。 使用下列 XAML 取代 中預設 MainWindow.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;
在 MainPage 類別定義中,宣告針對 Model 和 Tokenizer的成員變數。 設定我們在先前步驟中新增之模型檔案的位置。
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程式代碼以提交提示並顯示結果
在 [按鈕] 點擊事件處理程式中,先確認模型不是空值。 使用系統和使用者提示創建提示字串,然後呼叫 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 顯示模型已載入。 在提示文字框中輸入提示,然後按兩下 [提交] 按鈕。 您應該會看到結果逐漸填入文字塊。
另請參閱
- 在 Windows 上開始使用 AI
- 使用 Generative AI 搭配 ONNX Runtime