ONNX Runtime Generative AI を使用して、Windows アプリで Phi3 やその他の言語モデルを使ってみる
この記事では、Phi3 モデルと ONNX Runtime Generative AI ライブラリを使用して単純な生成 AI チャット アプリを実装する WinUI 3 アプリを作成する手順について説明します。 大きな言語モデル (LLM) を使用すると、テキスト生成、変換、推論、翻訳の機能をアプリに追加できます。 Windows アプリで AI モデルと機械学習モデルを使用する方法の詳細については、「Windows アプリで AI モデルと Machine Learning モデルの使用を開始する」を参照してください。 ONNX ランタイムとジェネレーティブ AI の詳細については、「
AI 機能を利用する場合は、「Windows でのレスポンシブル生成 AI アプリケーションと機能の開発」を確認することをお勧めします。
ONNX Runtime とは
ONNX Runtime は、ハードウェア固有のライブラリを統合するための柔軟なインターフェイスを備えたクロスプラットフォーム機械学習モデル アクセラレータです。 ONNX Runtime は、PyTorch、Tensorflow/Keras、TFLite、scikit-learn、およびその他のフレームワークのモデルで使用できます。 詳細については、https://onnxruntime.ai/docs/の ONNX Runtime Web サイトを参照してください。
前提 条件
- デバイスで開発者モードが有効になっている必要があります。 詳しくは、「デバイスを開発用に有効にする」をご覧ください。
- .NET デスクトップ開発ワークロードを含む Visual Studio 2022 以降。
新しい C# WinUI アプリを作成する
Visual Studio で、新しいプロジェクトを作成します。 [新しいプロジェクト の作成] ダイアログで、言語フィルターを "C#" に設定し、プロジェクトの種類フィルターを "winui" に設定し、空のアプリ 、パッケージ (デスクトップの WinUI3) テンプレートを選択します。 新しいプロジェクトに "GenAIExample" という名前を付けます。
ONNX Runtime Generative AI Nuget パッケージへの参照を追加する
ソリューション エクスプローラーで、[依存関係] を右クリックし、[NuGet パッケージの管理] を選択します。NuGet パッケージ マネージャーで、[参照] タブを選択します。「Microsoft.ML.OnnxRuntimeGenAI.DirectML」を検索し、[バージョン] ドロップダウンで最新の安定バージョンを選択して [インストール] をクリックします。
モデルとボキャブラリ ファイルをプロジェクトに追加する
ソリューション エクスプローラー
モデルを取得するには、いくつかの方法があります。 このチュートリアルでは、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
.
ソリューション エクスプローラー
モデルと対話するための単純な UI を追加する
この例では、プロンプトを指定するための TextBox、プロンプトを送信するための Button、ステータス メッセージとモデルからの応答を表示するための TextBlock を含む非常に単純な UI を作成します。 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を使用して呼び出し元に非同期的に結果を返すヘルパー メソッドを作成します。
このメソッドでは、
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 がモデルが読み込まれたことを示すのを待ちます。 プロンプト テキスト ボックスにプロンプトを入力し、[送信] ボタンをクリックします。 結果がテキストブロックに徐々に表示されるのを確認できるはずです。
関連項目
- Windows アプリ で AI モデルと Machine Learning モデルの使用を開始する
- 生成 AI を ONNX Runtime を使用して実行する