.NET(C#) コンソールアプリで、Phi-3-vision を実行し画像を入力する

2024/06/08
★★★

Phi-3-vision

前回は、Microsoft Research が開発した SLM(Small Language Model) Phi-3 を .NET(C#) でローカル実行する方法を説明しました。

前回は、テキスト入力をサポートするモデルを使用しましたが、今回は、画像入力をサポートするモデル Phi-3-vision を使用したコード例を説明します。

今回も Hugging Face で公開されている ONNX 形式のモデルを使用したいと思います。2024/06 現在では、以下のモデルが公開されています。

CPU, CUDA 向けのモデルが公開されており、現状では、DirectML 向けのモデルは公開されていません。CUDA 向けのモデルを使用する場合には、もちろん、NVIDIA GPU が必要で、CUDA のセットアップも必要となります。

さらに、CUDA 向けのモデルは、以下の 2 つのモデルが公開されています。

FP16 は、INT4 より精度が高く、INT4 は、FP16 よりパフォーマンスが高いということでしょう。

ONNX ランタイム向けに最適化された ONNX 形式ファイルのダウンロード

前回 と同様に、huggingface-cli でモデルをダウンロードします。

CPU 向け ONNX モデルをダウンロードする場合

huggingface-cli download microsoft/Phi-3-vision-128k-instruct-onnx-cpu --include cpu-int4-rtn-block-32-acc-level-4/* --local-dir .

FP16 CUDA 向け ONNX モデルをダウンロードする場合

huggingface-cli download microsoft/Phi-3-vision-128k-instruct-onnx-cuda --include cuda-fp16/* --local-dir .

INT4 CUDA 向け ONNX モデルをダウンロードする場合

huggingface-cli download microsoft/Phi-3-vision-128k-instruct-onnx-cuda --include cuda-int4-rtn-block-32/* --local-dir .

生成 AI 用 ONNX ランタイムのインストール

Visual Studio 2022 で、コンソールアプリケーションを作成し、以下のパッケージを Nuget からインストールします。

CPU 向け ONNX モデルを使用する場合

CUDA 向け ONNX モデルを使用する場合

ONNX ランタイムは、CPU 向け、CUDA 向け、DirectML 向けで、パッケージが分かれているので、モデルによってパッケージを使い分けます。

モデルの配置

ダウンロードした各ファイルをプロジェクトにコピーします。 コピーしたすべてのファイルのプロパティ [出力ディレクトリにコピー] を [新しい場合はコピーする] に設定します。

モデルの配置とプロパティの設定

これで、ビルド時の出力ディレクトリに各ファイルがコピーされるようになります。

コード サンプル

モデルに入力したい画像を用意しておきます。

以下のようにコードを書きます。 用意した画像のパスを imagePath に設定します。

using Microsoft.ML.OnnxRuntimeGenAI;
using System.Diagnostics;

namespace Phi3VConsoleApp001
{
    internal class Program
    {
        static void Main(string[] args)
        {            
            // システムプロンプト
            var systemPrompt = "あなたはAIアシスタントで、ユーザーがアップロードした画像を詳しく説明する能力があります。";
            // ユーザープロンプト
            var userPrompt = "この画像の内容を教えて下さい。";            
            // 画像のパス
            string imagePath = @"<入力画像のパス>";

            // プロンプトのテンプレート
            var prompt = $@"<|system|>{systemPrompt}<|end|><|user|><|image_1|>{userPrompt}<|end|><|assistant|>";

            // モデルが格納されているパス
            string modelDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"models\cpu-int4-rtn-block-32-acc-level-4");
            //string modelDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"models\cuda-fp16");
            //string modelDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"models\cuda-int4-rtn-block-32");

            var sw = Stopwatch.StartNew();

            // モデルのロード
            using Model model = new(modelDir);
            using MultiModalProcessor processor = new(model);
            using var tokenizerStream = processor.CreateStream();
            
            sw.Stop();            

            Console.WriteLine($"Model loading took {sw.ElapsedMilliseconds} ms.");

            sw = Stopwatch.StartNew();

            Console.WriteLine("Processing image and prompt...");
            using var images = Images.Load(imagePath);
            var inputTensors = processor.ProcessImages(prompt, images);

            Console.WriteLine("Generating response...");
            // 各パラメータを設定
            using GeneratorParams generatorParams = new GeneratorParams(model);
            generatorParams.SetSearchOption("max_length", 3072);
            generatorParams.SetInputs(inputTensors);

            // 応答の生成
            using var generator = new Generator(model, generatorParams);

            while (!generator.IsDone())
            {
                try
                {
                    generator.ComputeLogits();
                    generator.GenerateNextToken();

                    // トークンの取り出し
                    var outputTokens = generator.GetSequence(0)[^1];
                    // トークンのデコード
                    var output = tokenizerStream.Decode(outputTokens);

                    Console.Write(output);
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex);
                    break;
                }
            }

            Console.WriteLine();
            sw.Stop();
            Console.WriteLine($"Response generation took {sw.ElapsedMilliseconds} ms.");
        }
    }
}

実行結果

画像は、以下のレポートの「第Ⅱ-1-6-3図 オンラインコミュニケーションツール(Microsoft Teams及びZoom)の利用状況」をキャプチャーして使用しました。

以下は、キャプチャーした画像です。 オンラインコミュニケーションツール(Microsoft Teams及びZoom)の利用状況

CPU 向け ONNX モデルでの実行結果は、以下のようになります。回答生成に、158 秒程かかりました。

おおむね内容はあっているようにも見えますが、「データは2018年1月から」と X 軸の開始を読み間違えたり、Zoom のプロットがない 2020/01 以前の期間についても説明がされていたりします。 CPU 向け ONNX モデルでの実行結果

回答生成時は、CPU が、90% 近くまで使用されます。 CPU 向け ONNX モデルでの実行時 CPU

FP16 CUDA 向け ONNX モデルでの実行結果は、以下のようになります。回答生成に、592 秒程かかりました。

CPU とは異なり、X 軸の開始年月の読み間違えはなくなっています。Zoom のプロットがない 2020/01 以前の期間についての説明は改善されていません。

FP16 CUDA 向け ONNX モデルでの実行結果

回答生成時は、GPU が、100% 近くまで使用されます。私が使用している GPU の性能が低いのはあると思いますが、10 分くらいこの状態です。 FP16 CUDA 向け ONNX モデルでの実行時 GPU

INT4 CUDA 向け ONNX モデルでの実行結果は、以下のようになります。回答生成に、141 秒程かかりました。

FP16 CUDA 向け ONNX モデルと同様の結果に見えます。CPU 向け ONNX モデルよりわずかに速く処理が終わっています。

INT4 CUDA 向け ONNX モデルでの実行結果

こちらも回答生成時は、GPU が、100% 近くまで使用されます。 INT4 CUDA 向け ONNX モデルでの実行時 GPU

同じタスクを GPT-4o で行ったところ、Teams と Zoom の系列を正しく読み取ることができており、Zoom の開始時期に誤りはあるもののその他は正しく説明されています。応答は数秒で返ってきます。 GPT-4o 実行結果

Phi-3-vision を利用することで、ローカルで画像を入力としたタスクが実行できます。GPT-4o と比較すると精度が劣るようにも見えますが、今回使用した画像よりも簡易なものであれば、活用ができるシーンもあるのではないでしょうか。これくらいの精度のモデルが、容易にローカル実行できるという視点で見るべきでしょう。 また、スペックはある程度のものが必要となりますが、今後 Copilot+ PC により SoC に NPU が標準搭載されるようになれば、その点は改善されるようになると思います。 当然、Phi-3-vision の後続モデルも開発が継続されていると思われますので、現状を見ると、今後に非常に期待できる性能だと思います。

コメント (0)

コメントの投稿