Phi-3 Vision mit ONNX als lokales SLM für die Bild-Analyse
Lesedauer: 3 Minuten

Vor nicht allzu langer Zeit hat Microsoft seine Small Language Models der Phi-Familie veröffentlicht. Während der Microsoft Build-Konferenz in Seattle wurde das neue Phi-3 Vision-Modell vorgestellt. Nun möchte ich dir zeigen, wie du dieses Small Language Model ganz einfach in deiner C#-Anwendung mit der ONNX-Version verwenden kannst, um Bilder zu analysieren und zu bearbeiten.

ONNX (Open Neural Network Exchange) ist ein Format, das KI-Modelle portabel und interoperabel über verschiedene Frameworks und Hardware hinweg macht. Es bietet ein gemeinsames Format für maschinelle Lernmodelle, erleichtert deren Austausch zwischen verschiedenen Frameworks und optimiert für verschiedene Hardwareumgebungen.

Lass uns zunächst die entsprechende ONNX-Version des Phi-3 Vision-Modells herunterladen. Stelle sicher, dass Git LFS (Large File Storage) installiert ist, indem du den folgenden Befehl ausführst:

git lfs install

Nun können wir das Modell von Hugging Face klonen.

git clone https://huggingface.co/microsoft/Phi-3-vision-128k-instruct

Im heruntergeladenen Ordner findest du ein Verzeichnis namens cpu-int4-rtn-block-32-acc-level-4. Dieses Verzeichnis enthält alle notwendigen Dateien. Notiere dir den Pfad zu diesem Ordner.

Öffne Visual Studio und erstelle eine neue Konsolenanwendung mit .NET 8. Installiere die beiden NuGet-Pakete Spectre.Console und Microsoft.ML.OnnxRuntimeGenAI über den NuGet-Paket-Manager.

Erstelle einen Ordner namens Utils. Erstelle in diesem Ordner eine Klasse namens ConsoleHelper. Diese Klasse dient als Wrapper für das Spectre.Console-Paket und stellt einige nützliche Methoden bereit. Die Methode ShowHeader zeigt einen stilisierten Header in der Konsole an. Die Methode GetFolderPath ruft den Pfad zu dem Ordner ab, der die ONNX-Dateien enthält. Die Methode GetFilePath ruft den Pfad zu einer Bilddatei ab. Die Methode WriteToConsole schreibt einen angegebenen Text in die Konsole.

using Spectre.Console;

namespace Phi3VisionOnnxConsole.Utils;

internal static class ConsoleHelper
{
    public static void ShowHeader()
    {
        AnsiConsole.Clear();

        Grid grid = new();
        grid.AddColumn();
        grid.AddRow(new FigletText("Phi-3 Vision ONNX").Centered().Color(Color.Red));
        grid.AddRow(Align.Center(new Panel("[red]Sample by Thomas Sebastian Jensen ([link]https://www.tsjdev-apps.de[/])[/]")));

        AnsiConsole.Write(grid);
        AnsiConsole.WriteLine();
    }

    public static string GetFolderPath(string prompt)
    {
        ShowHeader();

        return AnsiConsole.Prompt(
            new TextPrompt<string>(prompt)
            .PromptStyle("white")
            .ValidationErrorMessage("[red]Invalid path[/]")
            .Validate(dictPath =>
            {
                if (!Directory.Exists(dictPath))
                {
                    return ValidationResult.Error("[red]Path does not exist[/]");
                }

                return ValidationResult.Success();
            }));
    }

    public static string GetFilePath(string prompt)
    {
        ShowHeader();

        return AnsiConsole.Prompt(
            new TextPrompt<string>(prompt)
            .PromptStyle("white")
            .ValidationErrorMessage("[red]Invalid path[/]")
            .Validate(filePath =>
            {
                if (!File.Exists(filePath))
                {
                    return ValidationResult.Error("[red]File does not exist[/]");
                }

                if (!filePath.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase) &&
                    !filePath.EndsWith(".jpeg", StringComparison.OrdinalIgnoreCase) &&
                    !filePath.EndsWith(".png", StringComparison.OrdinalIgnoreCase))
                {
                    return ValidationResult.Error("[red]File is not a picture[/]");
                }

                return ValidationResult.Success();
            }));
    }

    public static void WriteToConsole(string text)
    {
        AnsiConsole.Markup($"[white]{text}[/]");
    }
}

Erstelle im selben Utils-Ordner eine neue Datei namens Statics.cs. Diese Datei enthält statische Eingabeaufforderungen und andere statische Inhalte.

namespace Phi3VisionOnnxConsole.Utils;

internal class Statics
{
    public const string SystemPrompt
        = "You are an AI assistant that helps people find information. " +
        "Answer the questions briefly.";

    public const string UserImagePrompt
        = "Describe the image.";

    public const string PictureInputPrompt
        = "Enter the path to the [yellow]picture file[/]:";

    public const string ModelLoadingMessage
        = "[yellow]Loading Model...[/]";

    public const string AnalyzeImageMessage
        = "[yellow]Analyze Image...[/]";

    public const string ModelInputPrompt
        = "Enter the path to the [yellow]model folder[/]:";

    public const string OutputPrompt =
        "[green]Output:[/]";

    public const string RestartPrompt =
        "Press any key to analyze another image.";
}

Öffne die Datei Program.cs und ersetze deren Inhalt durch den folgenden Codeausschnitt. Ich werde den Code anschließend erläutern.

using Microsoft.ML.OnnxRuntimeGenAI;
using Phi3VisionOnnxConsole.Utils;

ConsoleHelper.ShowHeader();

string modelPath = ConsoleHelper.GetFolderPath(Statics.ModelInputPrompt);

ConsoleHelper.ShowHeader();
ConsoleHelper.WriteToConsole(Statics.ModelLoadingMessage);

using Model model = new(modelPath);
using MultiModalProcessor processor = new(model);
using Tokenizer tokenizer = new(model);

ConsoleHelper.ShowHeader();

while (true)
{
    string picturePath = ConsoleHelper.GetFilePath(Statics.PictureInputPrompt);

    Images image = Images.Load(picturePath);

    ConsoleHelper.ShowHeader();

    ConsoleHelper.WriteToConsole(Statics.AnalyzeImageMessage);
    ConsoleHelper.WriteToConsole(Environment.NewLine);
    ConsoleHelper.WriteToConsole(Environment.NewLine);
    ConsoleHelper.WriteToConsole(Statics.OutputPrompt);

    string fullPrompt = $"{Statics.SystemPrompt}{Statics.UserImagePrompt}";

    NamedTensors inputTensors = processor.ProcessImages(fullPrompt, image);

    using GeneratorParams generatorParams = new(model);
    generatorParams.SetSearchOption("max_length", 2048);
    generatorParams.SetInputs(inputTensors);

    using Generator generator = new(model, generatorParams);

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

        string output = tokenizer.Decode(generator.GetSequence(0)[^1..]);

        if (output.Contains("</s>"))
        {
            break;
        }

        ConsoleHelper.WriteToConsole(output);
    }

    ConsoleHelper.WriteToConsole(Environment.NewLine);
    ConsoleHelper.WriteToConsole(Environment.NewLine);
    ConsoleHelper.WriteToConsole(Statics.RestartPrompt);

    Console.ReadKey();
}

Zuerst zeigen wir unseren Header an und bitten dich dann, den Pfad zum ONNX-Modellordner anzugeben.

Als Nächstes laden wir das Modell und bereiten den Tokenizer vor. Wir fragen dich nach dem Bild, das du verwenden möchtest. Wir verwenden die Methode Images.Load, um das entsprechende Bild zu laden. Wir richten auch die GeneratorParams ein, indem wir die gewünschte Tokenlänge angeben und unsere Bilddaten hinzufügen.

Je nachdem, auf welcher Maschine der Code ausgeführt wird, kann dieser Prozess einige Sekunden dauern. Daher zeigen wir eine einfache Ladeanzeige an.

Wir verwenden die Generator-Klasse, um die Antwort zu generieren und in die Konsole zu schreiben.

Lass uns nun die Anwendung ausführen. Zuerst musst du den Pfad zum Modellordner angeben.

Als nächstes musst du den Pfad zum Bild angeben.

Als Beispiel verwende ich das folgende Bild.

Der folgende Screenshot zeigt die Bildbeschreibung mithilfe des lokalen ONNX-Modells.

Durch Befolgen der in diesem Leitfaden beschriebenen Schritte hast du das Phi-3 Vision-Modell erfolgreich mithilfe von ONNX in eine .NET-Konsolenanwendung integriert.

Du kannst nun deine Bilder mit deinem lokalen Small Language Model analysieren und bearbeiten und die Möglichkeiten von ONNX für eine effiziente und portable Bereitstellung von KI-Modellen nutzen.

Für den vollständigen Quellcode und weitere Details besuche bitte mein GitHub-Repository.

Abkürzungen der Künstlichen Intelligenz OenAI DevDay 2024: Die wichtigsten Neuerungen im Überblick Mit Azure OpenAI Bilder in C# generieren