Anwendungsbeispiele für Microsofts Universalwerkzeug

Experimente mit AI Dev Gallery

Experimente mit AI Dev Gallery

Anwendungsbeispiele für Microsofts Universalwerkzeug

Experimente mit AI Dev Gallery


Mit der AI Dev Gallery stellt Microsoft ein Projekt zur Verfügung, dass es Entwickler:innen ermöglicht, AI unter Windows auf verschiedene Arten zu nutzen. Niedrigschwellig zugänglich eignet es sich als wahres Universalwerkzeug und als generativer AI-Assistent.

Microsoft forciert die Nutzung von Windows als Hostbetriebssystem für alle Arten von AI Payloads. Mit der AI Dev Gallery steht nun ein Communityprojekt zur Verfügung, in dem Microsoft verschiedene Spielarten der Nutzung von AI unter Windows zu illustrieren sucht. Sinn des zur Verfügung gestellten Codeskeletts ist dabei – wie so oft – das Bereitstellen eines niederschwelligen Zugangs, der Entwickler:innen die ersten Schritte bei der Arbeit mit künstlicher Intelligenz unter Windows erleichtern soll. Außerdem ist der Code vergleichsweise modular aufgebaut – dank der MIT-Lizenz ist es mit geringem Aufwand möglich, verschiedene Elemente, die für die Erledigung vorliegender Aufgaben sinnvoll sind, direkt in hauseigene Projekte zu übernehmen.

Armierung und Inbetriebnahme des Codebeispiels

Wie im Fall vieler anderer „gemeinschaftlich“ entwickelter Systeme setzt Microsoft für die Verteilung des Quellcodes auch hier auf das hauseigene Versionskontrollportal GitHub. Spezifisch findet sich das Repository unter [1]. Interessant sind außerdem die vergleichsweise moderaten Systemanforderungen (Abb. 1).

hanna_aidev_1

Abb. 1: Das vorliegende Beispiel funktioniert auch mit Windows 10

Hervorzuheben ist, dass der Gutteil der implementierten Modelle auch mit einer vergleichsweise leistungsschwachen GPU funktionierenden sollen – Microsoft möchte explizit als Abstraktionsschicht arbeiten, die Entwickler:innen von den Verfügbarkeits- und sonstigen Problemen mit NVIDIA-Grafikkarten abschirmt.

In den folgenden Schritten sollen deshalb zwei Testsysteme zum Einsatz kommen: Einerseits eine betagte AMD-Achtkernworkstation mit einer AMD Thousand Islands GPU und Windows 10. Andererseits wollen wir auf einen Laptop setzen, der als Grafikkarte eine NVIDIA RTX 2060 mitbringt – interessanterweise mit nur 6 GB VRAM. Als Betriebssystem kommt hier Windows 11 zum Einsatz. Als Entwicklungsumgebung kommt auf beiden Maschinen Visual Studio 2022 in der Version 17.13.0 Preview 5 zum Einsatz. Wichtig ist, dass die Payload WinUI-Anwendungsentwicklung im Visual-Studio-Installer installiert ist.

Ob der Möglichkeit des Auftretens von verbundenen Repositorys empfiehlt es sich, den Quellcode der Solution unter Nutzung des Git-Kommandozeilenclients herunterzuladen. Normalerweise ist dieser Teil der VS-Installation – öffnen Sie im ersten Schritt die Entwicklerkommandozeile und geben Sie danach folgenden Befehl ein, um den Download loszutreten:

C:\Users\tamha\source\repos>git clone https://github.com/microsoft/AI-Dev-Gallery.git
Cloning into 'AI-Dev-Gallery'...
. . .

Im nächsten Schritt muss die Datei AI-Dev-Gallery\AIDevGallery.sln geöffnet werden – ein netter Weg ist das direkte Ausführen nach dem in Abbildung 2 gezeigten Schema. Im nächsten Schritt folgt die Ausführung der Konfiguration AIDevGallery (Packaged), die zum Aufscheinen des in Abbildung 3 gezeigten Startbildschirms führt.

hanna_aidev_2

Abb. 2: Das Eingeben des Dateinamens einer Solution führt bei korrekt konfiguriertem Visual Studio zum Durchstarten der IDE

hanna_aidev_3

Abb. 3: Die Leistungsschau ist gefechtsbereit

Analyse der in der Applikation angebotenen Möglichkeiten

Egal ob im Embedded- oder im AI-Bereich: Codebeispiele erschließen sich erfahrungsgemäß immer dann am besten, wenn man sie in Aktion sieht und erst danach die für die vorliegenden bzw. eigenen Aufgaben relevanten Teile des Beispielcodes zu isolieren versucht.

Die in Abbildung 3 gezeigte Übersicht ist dabei von eher geringerer Relevanz – wichtig ist erstens die in Abbildung 4 gezeigte Liste der Beispiele, zweitens die in Abbildung 5 gezeigte Modellverwaltung.

hanna_aidev_4

Abb. 4: Zur Ausführung von Payloads ist logischerweise ...

hanna_aidev_5

Abb. 5: ... ein Modell erforderlich

Bei diesen beiden auf der AMD-Achtkern-Workstation erzeugten Screenshots ist zu beachten, dass einige Modelle ausgegraut dargestellt werden. Das liegt daran, dass das Codebeispiel selbsttätig feststellt, dass die in der Workstation verbaute Grafikkarte nicht zur Beschleunigung von AI-Algorithmen geeignet ist und die Ausführung dieser Modelle auf der vorliegenden Hardware deshalb nicht zielführend erscheint.

In der in Abbildung 4 gezeigten Auswahlliste finden sich verschiedenste Beispielapplikationen, die diverse Aufgaben im Bereich der generativen AI und der Verarbeitung von LLMs abbilden. Die Modelle sind dabei gekapselt, die nicht ausgegrauten Kacheln erlauben das vergleichsweise bequeme Herunterladen der zur Ausführung der Codebeispiele benötigten Dateien.

Ob des nicht unerheblichen Remanentspeicherverbrauchs kann auch das Settings-Tab relevant sein. Es lässt sich über das am Bildschirmrand unten links eingeblendete Zahnradpiktogramm aktivieren und informiert über den Aufenthaltsort des Modellcaches und die Pfade zu den einzelnen Modellen. Der Stammpfad für die Modelldaten lautete auf der Workstation des Autors C:\Users\tamha\.cache\aigallery, einzelne Modelle wurden in Unterordnern wie beispielsweise C:\Users\tamha\.cache\aigallery\microsoft--Phi-3.5-mini-instruct-onnx untergebracht.

Für einen ersten Versuch wollen wir uns für die Option Text | Paraphrase Text entscheiden. Als Modell soll Phi 3.5 Mini CPU ACC4 zum Einsatz kommen. Nach dem Anklicken des Downloadsymbols erscheint ein Dialog (Abb. 6), der zum Akzeptieren und Abnicken der Lizenz des Modells auffordert.

hanna_aidev_6

Abb. 6: Die Zustimmung ist immer erforderlich

Anschließend erscheint oben rechts ein an Edge oder Google Chrome erinnerndes Fenster, in dem die Applikation über den Fortschritt des Herunterladens der mitunter sehr umfangreichen Modelldateien informiert. Nach getanem Download informiert die Applikation übrigens auch durch eine Desktop-Notification darüber, dass das Modell ab sofort für Experimente zur Verfügung steht.

Mit dem jeweiligen Modell betreibbare Codebeispiele blenden fortan außerdem nicht mehr die in den Abbildungen gezeigte Modellauswahl ein. Stattdessen finden sich nun verschiedenartigste Eingabefenster, die die „Interaktion“ mit den jeweiligen Modellen unter Berücksichtigung der ausgewählten Aufgabe ermöglichen.

Zu beachten ist, dass die Ausführung von KI-Modellen auf der CPU der Workstation nicht wirklich erfolgreich ist. Das Umschreiben eines 100 Zeichen langen englischsprachigen Texts brachte nach rund eineinhalb Minuten ein erstes Ergebnis, das Modell rechnete im Hintergrund allerdings noch einige Minuten weiter und verbesserte die ausgegebene Zusammenfassung einige Male. Zu beachten war dabei außerdem, dass die erste Ausgabe nicht wirklich korrekt war.

In der Rubrik Smart Controls findet sich dabei eine „halblokale“ Version der unter [2] bereitstehenden und zumindest vor einigen Wochen noch nur für den Webeinsatz vorgesehenen intelligenten Steuerelemente aus dem Hause Microsoft.

In der Rubrik Code findet sich dann im Allgemeinen das, was man erwarten würde – Interessant ist, dass in Image und Audio and Video auch Applikationen zur Verfügung stehen, die mit vollwertigen Multimediainhalten makeln. Besonders interessant ist Audio and Video | Transcribe Audio or Video – in den folgenden Schritten lud der Autor Whisper Medium CPU herunter, um einen kleinen Versuch zu wagen.

Zu beachten ist an diesem Beispiel erstens, dass es eine asynchrone Transkription realisiert. Nach dem Anklicken des Start Recording-Knopfs samplet die Applikation Spracheingabedaten, um sie erst nach dem Stoppen der Aufnahme en bloc zu transkribieren. Wie in Abbildung 7 zu sehen ist, zeigt sich das System dabei in der Lage, mit dem österreichischen Akzent des Autors zurechtzukommen. Interessant ist außerdem, dass das vorliegende Beispiel vier der acht Kerne der Workstations auslastet und schneller arbeitet als das vom Autor zum Test herangezogene Dragon 13 aus dem (nun ja zu Microsoft gehörenden) Hause Lernout & Hauspie.

hanna_aidev_7

Abb. 7: Die Transkriptionen von Englisch funktioniert durchaus gut

Relevant ist außerdem, dass die Combobox die Auswahl verschiedenster anderer Sprachen erlaubt – ein kurzer Versuch des Autors mit Deutsch funktionierte ebenfalls ohne große Probleme.

Interessant ist außerdem die Rubrik Enhance Image. Dort findet sich eine Modellvorlage, die für die Ausführung auf Qualcomm-NPU-Beschleunigereinheiten optimiert ist (Abb. 8).

hanna_aidev_8

Abb. 8: Dieses Modell fühlt sich abseits von X86-Prozessoren besonders wohl

Für Personen mit OpenAI-Abonnement dürfte relevant sein, dass sich das Programm auch zur Generierung von Bildern heranziehen lässt. Das ist allerdings eine Aufgabe, die ohne GPU-Beschleunigung nicht sinnvoll zu bewerkstelligen ist – Rechenzeiten im Stundenbereich sind mit CPUs insbesondere bei komplexeren Aufgaben vorstellbar.

Direkter Export von Codebeispielen

Obwohl sich die vorliegende Solution hervorragend als „lokal ausführbare AI-Nutzer-Applikation“ einspannen lassen, dürfte die Raison d’Etre des Codes mit Sicherheit die Inspiration von eigenen Experimenten im Bereich von Microsofts AI-System sein.

Nach der erfolgreichen Ausführbarmachung eines Beispiels – spezifisch setzt Microsoft hier das Herunterladen eines Modells voraus – erscheinen im oberen rechten Teil der Applikation die beiden Knöpfe Code und Export. Das Anklicken des Code-Tabs öffnet dabei eine Pane, die den von den verschiedenen Microsoft-GUI-Beispielen bekannten Codeanzeigedialog öffnet. Die Beispiele bestehen dabei normalerweise aus drei Files; eines davon ist eine XAML-Datei, die für die Interaktion mit dem Benutzerinterface verantwortlich ist.

Das Anklicken der Export-Option führt stattdessen zum Aufscheinen des in Abbildung 9 gezeigten Dialogs. Er erlaubt die Festlegung der Art und Weise, wie das exportierte Beispiel auf seine Modellinformationen zugreift. Der Autor wird in den folgenden Schritten schon zur Vermeidung der Duplizierung die Option wählen, die Modelldaten aus dem weiter oben in der Settings-Pane beschriebenen globalen Modellcache zu beziehen.

hanna_aidev_9

Abb. 9: Bitte wählen Sie den Ablageort der Modelldaten

Im nächsten Schritt erscheint ein Common-Dialog, der zur Auswahl eines idealerweise leeren Ordners auffordert. In ihm kommt danach ein Verzeichnis namens GenerateSample (kein Tippfehler) unter, in dem sich eine neue Visual-Studio-Solution befindet. Diese implementiert – logischerweise – nur noch die als zu exportieren markierte Funktion. Die Struktur des generierten Projekts präsentiert sich dann wie in Abbildung 10 gezeigt.

hanna_aidev_10

Abb. 10: Von der AI Dev Gallery generierte Projektskeletten sind durchaus umfangreich

Sowohl App.xaml als auch MainWindow.xaml sind dabei „Container“, die die eigentliche, in der Datei Sample.xaml unterkommende Intelligenz laden. Adapter zwischen der GUI-Logik und dem im Hintergrund stehenden Modell ist die Klasse Sample, die nach folgendem Schema als Member im Code-Behind entsteht:

internal sealed partial class Sample : Microsoft.UI.Xaml.Controls.Page
{
  private IChatClient? model;

Die eigentliche Initialisierung kommt dann nach dem in Listing 1 gezeigten Schema ebenfalls ohne Raketenwissenschaft aus. Die nur teilweise abgedruckten Parameter dienen dabei dazu, dem Modell nach dem Start grundlegende Kontextinformationen zur Seite zu stellen.

Listing 1

protected override async void OnNavigatedTo(Microsoft.UI.Xaml.Navigation.NavigationEventArgs e)
{
  model = await GenAIModel.CreateAsync(@"C:\Users\tamha\.cache\aigallery\microsoft--Phi-3.5-mini-instruct-onnx\main\cpu_and_mobile\cpu-int4-awq-block-128-acc-level-4", new LlmPromptTemplate
  {
    System = "<|system|>\n{{CONTENT}}<|end|>\n", . . .

Die eigentliche Intelligenz des Chatbots findet sich dann in einer nach dem Schema in Listing 2 aufgebauten for-each-Schleife. Im Prinzip kommt hier das bereits aus der Vergangenheit bekannte AI API zum Einsatz, das sich um die Interaktion zwischen dem Benutzercode und dem Modell kümmert.

Listing 2

Task.Run(
  async () => {
    string systemPrompt = "You generate text based on a user-provided topic. Respond with only the generated content and no extraneous text.";
    string userPrompt = "Generate text based on the topic: " + topic;
    cts = new CancellationTokenSource();
    IsProgressVisible = true;
    await foreach (var messagePart in model.CompleteStreamingAsync(
      [
        new ChatMessage(ChatRole.System, systemPrompt),
        new ChatMessage(ChatRole.User, userPrompt)
      ],
      null,
      cts.Token))

Sorgfältigere Reflektion bzw. Analyse der weiter oben kommentarlos abgedruckten Variable führt in den Unterordner Utils/GenAIModel.cs, wo sich eine nach folgendem Schema aufgebaute weitere Hilfsklasse verbirgt:

internal class GenAIModel : IChatClient, IDisposable {
  private const string TEMPLATE_PLACEHOLDER = "{{CONTENT}}";

Interessant sind an dieser Datei einige Elemente. Erstens erfolgt die Konstruktion des an das Modell gesendeten Prompts nach folgendem Schema durch Wiederkäuen der in der Vergangenheit durchgeführten Interaktionen in einer hier nicht vollständig abgedruckten for-each-Schleife:

private string GetPrompt(IEnumerable<ChatMessage> history) {
  . . .
  StringBuilder prompt = new();
  . . .
  for (var i = 0; i < history.Count(); i++) { . . .

Sinn dieser Vorgehensweise ist die Realisierung einer Art Gedächtnis, die dem Modell das Zurückblicken und das Berücksichtigen der in der Vergangenheit durchgeführten Interaktionen bei der Beantwortung der aktuell zu erledigenden Anfragen ermöglicht.

Für die eigentliche Interaktion mit dem Sprachmodell kommt dann die Klasse Microsoft.ML.OnnxRuntimeGenAI.Generator zum Einsatz. Zur Generation der Modellantwort dient eine von Embedded-Systemen bekannte Arbeitsschleife. Die eigentliche Interaktion erfolgt nach dem immer selben Schema: Im ersten Schritt bekommt die Generatorklasse Informationen eingeschrieben, die sie zur Erledigung der Gefechtsaufgabe benötigt. Danach stellen wir etwas Rechenleistung zur Verfügung, um anschließend die vom Generator zurückgegebenen Ergebnisse zu überprüfen (Listing 3).

Listing 3

while (!generator.IsDone()) {
  string part;
    . . .
    generator.ComputeLogits();
    generator.GenerateNextToken();
    part = tokenizerStream.Decode(generator.GetSequence(0)[^1]);
    if (ct.IsCancellationRequested && stopTokensAvailable) {
      part = _template!.Stop!.Last();
    }
    stringBuilder.Append(part);

Wichtig ist, dass die Überprüfung hier nicht nur auf Komplettheit erfolgt. Das vorliegende Modell überprüft stattdessen auch, ob ein sogenanntes „Stop-Token“ bzw. „Stop-Word“ aufgetreten ist – auch in diesem Fall beendet es die Verarbeitung.

Analyse der Ur-Solution

Obwohl sich an dieser Stelle auch ein Versuch mit dem Bildgenerator anbieten würde, wollen wir stattdessen in die „Original-Solution“ AI Dev Gallery zurückkehren. Der darin enthaltene Code ist unter anderem deshalb interessant, weil er den soeben besprochenen Projektgenerator realisiert – eine durchaus anspruchsvolle Aufgabe, der man als Entwickler:in insbesondere beim Anbieten von Steuerelementbibliotheken und Co. immer wieder gegenübersteht.

Wer den dafür zuständigen Teil der Projektmappe „Project Generator“ im Detail betrachtet, sieht im Projektmappen-Explorer die in Abbildung 11 gezeigte Struktur.

hanna_aidev_11

Abb. 11: Alte Bekannte melden sich zum Dienst

Auffällig ist hier im ersten Schritt der Template-Ordner, der von der Struktur her stark an das im letzten Schritt detailliert analysierte Projektskelett erinnert. Für die eigentliche Konversion kommt dann die Klasse AIDevGallery.ProjectGenerator.Generator zum Einsatz, die vor allem aus einer asynchronen Arbeitermethode besteht:

private async Task<string> GenerateAsyncInternal(Sample sample, Dictionary<ModelType, (string CachedModelDirectoryPath, string ModelUrl, HardwareAccelerator HardwareAccelerator)> models, bool copyModelLocally, List<(string PackageName, string? Version)> packageReferences, string outputPath, CancellationToken cancellationToken)
{
  var projectName = $"{sample.Name}Sample";
  . . .

Interessant ist an der Methode vor allem die Art der Projektgenerierung. Im ersten Schritt wird dabei die Boolesche Variable copyModelLocally überprüft – sie legt fest, ob der Projektgenerator die Modellinformationen aus dem globalen Modellcache beziehen oder als Teil bzw. Ressourcen im Ausgabeprojekt unterbringen soll.

Im nächsten Schritt folgt die Erzeugung des Ausgabeordners und das Übertragen einiger modellspezifischer Codedateien. Dabei wird übrigens abermals eine Reflexion gegen die in AI Dev Gallery enthaltenen Ressourcen durchgeführt; spezifisch ist nun der Ordner Models als Ziel betroffen.

Im nächsten Schritt verwendet die Methode einen nach folgendem Schema aufgebauten Greifer, um alle zu rehydrierenden Dateien anhand ihrer Dateiendungen zu erkennen:

var files = Directory.GetFiles(templatePath, "*.*", SearchOption.AllDirectories).Where(file => extensions.Any(file.EndsWith));

Im nächsten Schritt folgt eine for-each-Schleife, die die verschiedenen Dateien anhand ihrer Inhaltsarten unterschiedlichen Bearbeitungswegen zuweist. Microsoft konstruierte die im Template-Ordner liegenden Dateien insofern intelligent, als die für den Generator relevanten Passagen durch aus Fragezeichen bestehende Prä- und Postfix-Passagen markiert sind. Der Austausch erfolgt dann ganz primitiv durch eine Gruppe von Stringoperationen – im Interesse der Kompaktheit drucken wir hier nur einen der Aufrufe von Replace ab (Listing 4).

Listing 4

var content = await File.ReadAllTextAsync(file, cancellationToken);
// Replace the variables
content = content.Replace("$projectname$", projectName);
. . .
content = content.Replace("$DotNetVersion$", DotNetVersion);
// Write the file
await File.WriteAllTextAsync(outputPathFile, content, cancellationToken);

Interessant ist außerdem noch die Frage, wie die Verwaltung der NuGet-Pakete in der Solution erfolgt. Hervorzuheben ist, dass Microsoft die eigentliche Stringverarbeitung für die Paketdeklarationen in einer „Methoden-lokalen“ Methode bewerkstelligt. Ihre Signatur präsentiert sich folgendermaßen:

static void AddPackageReference(ProjectItemGroupElement itemGroup, string packageName, string? version)
{
  . . .

Fazit

Mit der AI Dev Gallery schickt Microsoft ein wahres Universalwerkzeug ins Rennen. Das Produkt taugt als generativer AI-Assistent, als Projektskelett-Exporter und zu guter Letzt auch als Einführung in verschiedenste fortgeschrittene Arten der Kodierung. Unterm Strich ein Projektbeispiel, dass jede:r Entwickler:in zumindest einmal gesehen haben sollte.

Tam Hanna

Tam Hanna befasst sich seit der Zeit des Palm IIIc mit Programmierung und Anwendung von Handcomputern. Er entwickelt Programme für diverse Plattformen, betreibt Onlinenewsdienste zum Thema und steht für Fragen, Trainings und Vorträge gern zur Verfügung.


Weitere Artikel zu diesem Thema