開始使用 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 指出模型已載入。 在提示文字框中輸入提示,然後按兩下 [提交] 按鈕。 您應該會看到結果逐漸填入文字塊。