Das technische Making-of zu Teil 3 unserer Serie über eigene Experimente mit Entscheidungsmodellen. Preise, Regionen und Treiberstände: Stand Oktober 2026.

Kurz gesagt: Wir haben ein kleines Entscheidungsmodell per LoRA als Job in Azure Container Apps auf einer serverlosen NVIDIA T4 nachtrainiert. Der Job lief 4 Stunden und 45 Minuten. Auf der Rechnung standen dank des monatlichen Freikontingents 3,26 Euro – zum Listenpreis wären es rund 10 US-Dollar gewesen. Die meiste Zeit kostete nicht das Training, sondern die Konfiguration: Registry-Timeout, Regionen, command statt Argumenten, Treiber und ein fehlender Compiler.

Drei Tage auf der CPU – oder ein paar Stunden auf einer GPU

Für unsere Experimente mit dem Strands Decider 2B haben wir das Modell nachtrainiert: 3.000 Kniffel-Spielsituationen, 6.000 Trainingsbeispiele, zwei Durchläufe. Was dabei herauskam, steht in Teil 3 der Serie. Hier geht es darum, wie wir das Training technisch umgesetzt haben.

Alles andere lief lokal auf einer virtuellen Maschine mit vier CPU-Kernen: das Erzeugen der Daten, die Kalibrierung und sämtliche Auswertungen. Für das Training selbst reichte das nicht. Ein einzelnes Trainingsbeispiel dauerte dort rund 44 Sekunden – für den ganzen Lauf wären das etwa drei Tage gewesen.

Eine eigene GPU anzuschaffen, lohnt sich für einen einzelnen Lauf nicht. Eine GPU-VM in der Cloud hätte man starten, einrichten und vor allem wieder stoppen müssen. Wir haben uns deshalb für einen Job in Azure Container Apps mit serverloser GPU entschieden: Der Container startet, trainiert, schreibt seine Ergebnisse weg und beendet sich. Abgerechnet wird sekundengenau, danach fallen keine Rechenkosten mehr an.

Der Aufbau

Azure Container Apps bietet serverlose GPUs vom Typ NVIDIA T4 und A100 an, für dauerhaft laufende Apps ebenso wie für Jobs, die einmal laufen und dann enden. Wir haben ein Workload-Profil mit einer NVIDIA Tesla T4 genutzt: 8 vCPU, 56 GiB Arbeitsspeicher und 16 GB Grafikspeicher.

Der wichtigste Grundsatz beim Aufbau: Das Container-Image enthält nur Code und fest versionierte Abhängigkeiten. Modelle, Trainingsdaten und Ergebnisse liegen nicht im Image, sondern auf eingebundenen Dateifreigaben (Azure Files):

flowchart LR
    R["Eigene Container-Registry<br/>Image mit Code + Abhängigkeiten"] --> J["Container Apps Job<br/>NVIDIA T4"]
    C["/cache<br/>Modelle, einmalig geladen"] --> J
    D["/data<br/>Trainingsdaten, nur lesend"] --> J
    J --> O["/output<br/>Checkpoints, Logs, Ergebnis"]
    L["Lokale CPU-VM<br/>Daten erzeugen, auswerten"] -.-> D
    O -.-> L

Das hat mehrere Vorteile. Das Image bleibt kleiner, und ein neuer Datensatz braucht kein neues Image. Die Modelle haben wir einmalig mit einem eigenen kleinen Job von Hugging Face in den Cache geladen – in genau den Versionen, die wir festgelegt hatten. Danach lief jeder weitere Start ohne Internetzugriff auf Modelle. So ist sichergestellt, dass das Training nicht plötzlich mit einer neueren Modellversion arbeitet.

Vor dem eigentlichen Training haben wir einen Test-Job laufen lassen, der nur prüft, ob die GPU da ist und welcher Treiber installiert ist. Er kostet wenige Sekunden und hat sich gelohnt (dazu unten mehr).

So lief das Training

Schritt Dauer
Image laden (rund 3,3 GB) gut 2 Minuten pro Start
Modell laden und Ausgangswert messen rund 2,5 Minuten
Training: 187 Optimierungsschritte 4,7 Stunden
Job insgesamt, vom Start bis zum Ende 4 Stunden 45 Minuten
Spitzenbedarf Grafikspeicher 8 von 16 GB

Die T4 war pro Trainingsbeispiel rund 17-mal schneller als unsere CPU-VM. Der Lauf ging in einem Stück durch; die Zwischenstände, die wir alle zehn Schritte gespeichert haben, mussten wir nicht nutzen.

Ausgewertet haben wir anschließend wieder lokal auf der CPU – das ursprüngliche Modell ebenso wie das nachtrainierte. Das war Absicht: Wenn beide Modelle auf derselben Hardware gemessen werden, können kleine Rechenunterschiede zwischen GPU und CPU den Vergleich nicht verfälschen. Die Cloud lieferte nur die Rechenleistung fürs Training.

Was es gekostet hat

Die Azure-Kostenübersicht zeigte für den Tag 3,22 Euro für Container Apps und 0,04 Euro für den Speicher – zusammen 3,26 Euro. Netzwerk-Traffic und Log Analytics schlugen mit 0,00 Euro zu Buche. In dem Betrag stecken alle GPU-Läufe des Tages: der Test-Job samt zwei Fehlstarts, das einmalige Laden der Modelle, der abgebrochene erste Trainingsversuch und das eigentliche Training. Zusammen waren das grob fünf GPU-Stunden.

Ehrlicherweise muss man dazusagen, warum es so wenig war. Bei serverlosen GPUs berechnet Azure nicht nur die Grafikkarte, sondern auch die zugeteilten Prozessorkerne und den Arbeitsspeicher. Unser Job lief mit 8 Kernen und 56 GiB. Nach der offiziellen Preisliste für unsere Region kostet diese Ausstattung rund 2 US-Dollar pro Stunde – die GPU selbst macht davon nur gut ein Achtel aus, den Rest teilen sich Prozessor und Arbeitsspeicher.

Dass am Ende nur gut 3 Euro auf der Rechnung standen, liegt am monatlichen Freikontingent von Container Apps: Pro Abonnement und Kalendermonat sind die ersten 180.000 vCPU-Sekunden und 360.000 GiB-Sekunden kostenlos. Bei 8 Kernen deckt das gut sechs Stunden Prozessorzeit ab – unser gesamtes Training – und knapp zwei Stunden des Arbeitsspeichers. Ohne Freikontingent hätten wir nach unserer Rechnung rund 10 bis 11 US-Dollar bezahlt. Auch das ist für ein Modelltraining wenig. Wer aber mehrere Läufe im Monat plant oder Container Apps schon für andere Anwendungen nutzt, sollte mit dem vollen Preis kalkulieren.

Unabhängig vom genauen Betrag gilt, was wir auch in Teil 3 geschrieben haben: Die Rechenzeit war der kleinste Posten. Die meiste Zeit steckte in der Konfiguration – und in den folgenden Stolpersteinen.

Die Stolpersteine

Der Upload in die eigene Registry brach ab

Unser Image liegt in einer eigenen Container-Registry, die hinter zwei Proxys läuft. Unsere Images sind normalerweise deutlich kleiner – dieses brachte mit CUDA, PyTorch und den übrigen Abhängigkeiten mehrere Gigabyte mit. Der erste Push scheiterte mit HTTP 499 und „invalid content range". Die Ursache: Traefik bricht Anfragen seit Version 2.11.2 standardmäßig nach 60 Sekunden ab, wenn sie bis dahin nicht vollständig gelesen sind. Docker lädt jede Schicht eines Images in einer einzigen Anfrage hoch – bei mehreren Gigabyte dauert das länger. Das Aufteilen der großen Schicht in kleinere hat nicht geholfen, erst ein längerer Timeout an Traefiks Einstiegspunkt. Wie unsere Registry aufgebaut ist und warum der HAProxy davor den Upload nicht abgebrochen hat, beschreiben wir ausführlich in Docker-Registry hinter Traefik: Push bricht nach 60 Sekunden ab.

Der Portal-Assistent legt eigene Ressourcen an

Beim Anlegen des Jobs über das Azure-Portal hat der Assistent eine neue Container-Apps-Umgebung samt Log-Analytics-Arbeitsbereich erstellt, statt die vorbereitete zu verwenden. Das ist nicht nur unordentlich: Dateifreigaben werden pro Umgebung eingebunden. Wer Speicher vorbereitet hat, muss ihn in der neuen Umgebung erneut hinterlegen.

Speicher und GPU in verschiedenen Regionen

Serverlose T4-GPUs gibt es nicht in jeder Azure-Region. Unser bestehender Speicher lag in Nordeuropa – dort gibt es keine. Die GPU lief in West US 2. Damit Modelle und Zwischenstände nicht über den Atlantik geladen werden, haben wir einen zweiten Speicher in derselben Region angelegt.

Für unsere synthetischen Kniffel-Daten war die US-Region unproblematisch. Für echte Kundendaten würden wir eine EU-Region wählen: Serverlose T4-GPUs gibt es unter anderem in Sweden Central, West Europe, France Central und Italy North. Wichtig ist dann, dass Rechenumgebung und Speicher in derselben Region liegen.

„Command" ist nicht „Arguments"

In Container Apps ersetzt das Feld command den Einstiegspunkt (ENTRYPOINT) des Images. Unser Image startet über einen kleinen Init-Prozess, der den eigentlichen Befehl aufruft. Drei Starts gingen verloren: einmal durch ein überzähliges Zeichen im Portal-Feld „Befehlsüberschreibung", einmal durch einen leeren Befehl, der als Liste mit einem leeren Eintrag gespeichert wurde, einmal durch einen Befehl ohne das Trennzeichen, das der Init-Prozess erwartet. Den Einstiegspunkt des Images behält man nur mit einer wirklich leeren Liste.

Dazu kam: Die Azure CLI interpretiert Argumente, die mit -- beginnen, als ihre eigenen Optionen. Argumente für unser Trainingsskript ließen sich so nicht übergeben. Die Lösung war, den Job komplett als YAML-Datei zu beschreiben und mit az containerapp job update --yaml zu aktualisieren. Das ist ohnehin besser: Die Konfiguration ist dann versionierbar und nachvollziehbar.

Die GPU hat ihre Eigenheiten

Automatische Wiederholungen ohne Zwischenstand bringen nichts

Container Apps kann einen fehlgeschlagenen Job automatisch neu starten. Unser Skript verweigert aber bewusst einen Neustart von vorne, wenn es keinen gespeicherten Zwischenstand gibt. Die Wiederholungen des abgebrochenen ersten Versuchs haben deshalb nur Minuten gekostet. Sinnvoll sind automatische Wiederholungen nur, wenn regelmäßig Zwischenstände gespeichert werden und der Job dort weitermachen kann.

Kleinigkeiten, die trotzdem Zeit kosten

Training in der Cloud, Betrieb im eigenen Haus

Training und Betrieb eines Modells sind zwei verschiedene Dinge – mit sehr unterschiedlichem Bedarf an Hardware. Beim Training durchläuft jedes Beispiel das Modell vorwärts und wieder rückwärts, und zusätzlich zu den Gewichten liegen Gradienten und Zustände des Optimierers im Speicher. Bei uns waren das bis zu 8 GiB Grafikspeicher und gut viereinhalb Stunden auf der T4. Im Betrieb, der sogenannten Inferenz, rechnet das Modell nur noch vorwärts: eine Anfrage, eine Antwort.

Genau so haben wir es selbst gemacht. Das fertig trainierte Modell lief für die Auswertung wieder auf unserer lokalen VM mit vier CPU-Kernen, ganz ohne Grafikkarte: 1.206 Modellaufrufe in rund 2,4 Stunden, etwa sieben bis acht Sekunden pro Entscheidung. Auf einer GPU ginge das schneller – für unseren Zweck war es mehr als ausreichend.

Training Betrieb
Rechenaufwand vorwärts und rückwärts, viele Durchläufe nur vorwärts, je Anfrage ein Durchlauf
Hardware bei uns Cloud-GPU (T4), 4,7 Stunden lokale VM mit 4 CPU-Kernen
Wie oft einmalig oder selten laufend
Passendes Kostenmodell Abrechnung nach Laufzeit eigene Hardware

Daraus ergibt sich ein Muster, das für viele Unternehmen sinnvoll sein kann: in der Cloud trainieren, lokal betreiben. Die Grafikkarte mietet man für die wenigen Stunden des Trainings, das fertige Modell läuft danach auf eigener Hardware im eigenen Netz. Anfragen mit vertraulichen Inhalten verlassen im Betrieb dann das Haus nicht. Für das Training selbst gilt das nicht: Die Trainingsdaten gehen in die Cloud. Bei uns waren sie vollständig synthetisch erzeugt; wer mit echten Kundendaten trainiert, sollte das Training ebenfalls in einer EU-Region planen oder lokal durchführen.

Wie viel Hardware der Betrieb tatsächlich braucht, hängt von der Größe des Modells und von der Zahl gleichzeitiger Anfragen ab. Ein kleines, spezialisiertes Modell wie unseres kommt mit einer CPU aus; für mittelgroße Sprachmodelle und mehrere Nutzer braucht ein KI-Server im Unternehmen eine passende Grafikkarte oder ein Gerät mit viel gemeinsamem Speicher. Einen Überblick gibt unser Ratgeber Lokale Sprachmodelle im Unternehmen: welche Hardware reicht?, und wie wir KI lokal betreiben, beschreiben wir unter Lokale KI für Unternehmen.

Was wir beim nächsten Mal genauso machen würden

  1. Image ohne Daten und Modelle, alles Veränderliche auf eingebundenem Speicher.
  2. Modellversionen fest vorgeben und einmalig in einen Cache laden.
  3. Zuerst ein Test-Job für GPU, Treiber und Compiler – erst dann das Training.
  4. Konfiguration als YAML statt Klicks im Portal.
  5. Speicher und Rechenumgebung in derselben Region, für echte Daten in der EU.
  6. Zwischenstände speichern, damit Wiederholungen etwas nützen.
  7. Auswertung auf derselben Hardware für alle verglichenen Modelle.
  8. Training und Betrieb trennen: die GPU nur für das Training mieten, das fertige Modell dort betreiben, wo die Daten liegen.

Als Microsoft-Partner im Cloud-Solution-Provider-Programm (CSP) unterstützen wir Unternehmen bei Azure – vom Abonnement und der Lizenzierung bis zur Umsetzung solcher Workloads. Sie möchten ein Modell trainieren oder KI-Workloads in die Cloud bringen? Mehr zu unseren IT-Services und zu KI-Agenten für Unternehmen – oder sprechen Sie uns direkt an.


Quellen: Microsoft Learn – Serverless GPUs in Azure Container Apps · Microsoft Learn – Container Apps: Container-Konfiguration (command/args) · Microsoft Learn – Abrechnung in Container Apps (Freikontingent) · Azure – Preise Container Apps · Microsoft Learn – Storage Mounts in Container Apps · Microsoft Learn – az containerapp job · Microsoft Learn – Azure Files: symbolische Links unter Linux · Traefik – Migration v2.11 (readTimeout 60 s) · NVIDIA – CUDA GPUs und Compute Capability · NVIDIA – CUDA Toolkit Release Notes · Hugging Face – Cache verwalten