Blog

Ein LLM auf einer iGPU trainieren: nanochat (Karpathy) auf einer einfachen integrierten GPU

Keine Grafikkarte, kein CUDA: Eine iGPU Intel UHD 770 trainiert nanochat, Karpathys Mini-ChatGPT, komplett in weniger als einer Stunde mit PyTorch XPU. Gemessene Zahlen, iGPU + CPU, startfertiges Skript.

Ein LLM auf einer iGPU trainieren: nanochat (Karpathy) auf einer einfachen integrierten GPU

nanochat ist das Projekt von Andrej Karpathy, das in einem einzigen, gut lesbaren Repository alle Schritte eines „ChatGPT“ zeigt: Tokenizer, Pre-Training, Fine-Tuning (SFT) und Chat-Oberfläche. Es ist für 8 NVIDIA-H100-GPUs gedacht, bietet aber auch eine verkleinerte Konfiguration für die CPU.

Wir haben es vollständig auf einer iGPU laufen lassen: der Intel UHD 770, der in den Prozessor integrierten GPU (ein Desktop-Core-i5), ganz ohne dedizierte Grafikkarte und ohne CUDA. Das Ganze mit PyTorch XPU, der nativen Unterstützung für Intel-GPUs in PyTorch, und danach mit iGPU und CPU, die gemeinsam rechnen.

Ergebnisse in Kürze

  • Eine einfache iGPU reicht, um ein LLM komplett zu trainieren: Installation, Daten, Tokenizer, Pre-Training (33 min), SFT (etwa 6 min) und erste Antworten in weniger als einer Stunde, auf der integrierten GPU der VM, ohne Grafikkarte.
  • 1,33 s pro Pre-Training-Schritt, also etwa 6.150 Tokens/s, für ein kleines Modell mit 4 Schichten. Bei derselben Arbeit ist die GPU etwa 1,3-mal schneller als die 6 vCPU.
  • iGPU und CPU gemeinsam: Übernimmt die CPU 4 von 16 Zeilen jedes Batches, sinkt das Pre-Training von 1,33 auf 1,13 s pro Schritt (15 % weniger Zeit), bei fast gleicher Modellqualität (Validierung 1,55 statt 1,52). Gibt man der CPU mehr Arbeit, wird es dagegen langsamer.
  • Das fertige Modell spielt den Assistenten und spricht Englisch, bleibt aber inkohärent: Das liegt an der Modellgröße (37 Millionen Parameter, vor allem Embedding-Tabellen, 39 min Training), nicht an der Plattform.
  • Nötig waren 4 Anpassungen für die Intel-GPU und 4 Korrekturen an nanochat für eine kleine Konfiguration. Alles steckt in einem Skript mit einem einzigen Befehl, am Ende des Artikels.

Warum eine iGPU?

Eine iGPU (integrierte GPU) ist der Grafikteil, der im Prozessor sitzt, auf demselben Chip wie die CPU-Kerne. Normalerweise ist sie für die Bildschirmausgabe gedacht; hier dient sie zum Rechnen.

  • Sie teilt sich den RAM des Servers, statt eigenen Videospeicher zu haben. Keine VRAM-Grenze bei 8 oder 12 GB und kein teures Kopieren zwischen CPU und GPU: Genau das macht das Training mit iGPU + CPU weiter unten effizient. Unser Modell hat nur 1,5 GB belegt.
  • PyTorch unterstützt sie nativ (XPU-Build), einschließlich torch.compile: derselbe Code wie mit CUDA, bis auf wenige Zeilen.
  • Sie kostet einen Bruchteil einer dedizierten GPU: Unser Tarif iGPU Pro liegt bei 44,99 € netto pro Monat, während eine dedizierte GPU in der Cloud oft 300 bis 500 € pro Monat kostet.
  • Sie ist deutlich weniger leistungsstark (unter 1 TFLOPS): Sie ersetzt keine dedizierte GPU, um ein großes Modell zu trainieren. Sie reicht aber, um zu lernen, zu experimentieren und kleine Modelle laufen zu lassen.

Testbedingungen

ElementWert
Virtuelle MaschineTarif iGPU Pro: 6 vCPU, 24 GB RAM, Debian 13, Kernel 7.3
CPU / GPUIntel Core i5-13500, integrierte GPU Intel UHD 770 (mit anderen VMs geteilt)
Softwarenanochat (Commit 92d63d4), PyTorch 2.9.1 XPU-Build, torch.compile mit Triton XPU
Modelld4: 4 Schichten, Breite 256, Kontext 512 Tokens, Vokabular 32.768
Pre-Training1.500 Schritte zu 8.192 Tokens (12,3 Millionen Tokens)
SFT220 Schritte zu 8.192 Tokens, Mischung aus Konversationen, MMLU und GSM8K

Das ist die „CPU“-Konfiguration von nanochat, so verkleinert, dass sie in weniger als einer Stunde durchläuft. Das echte nanochat (speedrun.sh, Modell mit 20 Schichten) braucht etwa 4 h auf 8 H100: drei Größenordnungen mehr Rechenaufwand, außer Reichweite einer integrierten GPU.

1. Die Zahlen

Reine Rechenleistung

Matrixprodukt 2048×2048TFLOPS
fp320,71
bf160,73

Die GPU ist geteilt: Wenn eine andere VM sie belegt, haben wir bis hinunter zu 0,27 TFLOPS gemessen. Diese GPU hat keine eigenen Matrix-Einheiten, deshalb ist bf16 nicht schneller als fp32, braucht aber nur halb so viel Speicher.

Training und Chat

SchrittDauerDetail
Installation (Pakete, nanochat, PyTorch XPU)~1 min
Daten (8 Dateien) + Tokenizer50 sauf der CPU
1. Iteration des Pre-Trainings83 sKompilierung der GPU-Kernels, nur einmal
Pre-Training, 1.500 Schritte33 min1,33 s/Schritt, ~6.150 Tokens/s, 1,5 GB GPU-Speicher
SFT, 220 Schritte~6 min~1,5 s/Schritt
1. Antwort im Chat3 minKompilierung
Weitere Antworten13 bis 15 slange Antworten, bis zum Token-Limit

GPU gegen CPU, gleiche Arbeit

SFT, Schritte zu 16.384 Tokenss/SchrittTokens/s
GPU Intel UHD 7702,646.200
6 vCPU (i5-13500)3,0 bis 3,54.630

Die Lernkurve

AnfangEnde
Pre-Training, Bits pro Byte in der Validierung3,201,52
SFT, Bits pro Byte in der Validierung1,351,10

Je niedriger, desto besser sagt das Modell den Text voraus. Beide Kurven fallen gleichmäßig, ohne Ausreißer.

2. iGPU und CPU gemeinsam

Die GPU rechnet, während die 6 vCPU warten. Deshalb haben wir beide gleichzeitig arbeiten lassen (Patch nanochat_hybrid_patch.py):

  1. Jeder Batch mit 16 Zeilen wird zweigeteilt: Ein Teil geht an die GPU, der andere an eine Kopie des Modells im CPU-Speicher. Beide rechnen parallel.
  2. Die Gradienten beider Teile werden addiert, gewichtet nach der Zahl der Zeilen. Mathematisch ist das dieselbe Rechnung wie ein ganzer Batch auf einem einzigen Prozessor.
  3. Der Optimizer aktualisiert das Modell auf der GPU, danach werden die Gewichte in die CPU-Kopie zurückkopiert. Bei einer integrierten GPU teilen sich GPU und CPU denselben Speicher: Diese Kopie kostet etwa 0,1 s pro Schritt.

Alles läuft in fp32: Die CPU dieses Servers hat keine schnellen bf16-Befehle, und auf dieser GPU ist fp32 ebenso schnell wie bf16.

Wie viel Arbeit bekommt die CPU?

Zeilen GPU + CPU (CPU-Threads)s/SchrittIm Vergleich zur GPU allein in fp32 (1,40 s)
16 + 0 (nur GPU)1,40Referenz
12 + 4 (3 oder 4 Threads)1,16−17 %
10 + 6 (5 Threads)1,32−6 %
10 + 6 (4 Threads)1,41kein Gewinn
8 + 8 (4 Threads)1,43langsamer

Überraschend: Mehr Zeilen für die CPU bremsen alles aus. Unsere Messungen zeigen, dass die CPU immer vor der GPU fertig ist, der GPU-Teil aber langsamer wird, wenn die CPU rechnet: Die rechnenden Kerne stehen nicht mehr zur Verfügung, um die GPU zu steuern (ihre Kernels zu starten, die automatische Differenzierung zu verwalten). Die beste Einstellung lässt deshalb Luft: 4 von 16 Zeilen und 3 Rechen-Threads bei 6 vCPU.

Der komplette Lauf, nur GPU gegen GPU + CPU

Pre-Training, 1.500 SchritteNur GPU (bf16)GPU + CPU (fp32)
Zeit pro Schritt1,33 s1,13 s
Tokens pro Sekunde~6.150~7.200
Gesamtdauer33 min28 min
Finale Validierung (Bits pro Byte)1,521,55
SFT, danach Chatidentischidentisch

Die finale Validierung ist im Hybridlauf etwas schlechter (1,55 statt 1,52). Die Ursache haben wir noch nicht isoliert: der Wechsel zu fp32 oder ein Effekt der Aufteilung zwischen GPU und CPU.

Zwei Einschränkungen: Der Gewinn hängt von der Zahl der vCPU ab (mit 2 vCPU gibt es nichts zu teilen), und wir haben die Methode nur auf das Pre-Training angewendet, den längsten Schritt. Das SFT läuft nur auf der GPU.

3. Was das Modell antwortet

What is the capital of France? The city of France has a fundamental concept in Japan, which is a city of France’s Italian history and country. […] a fundamental concept in Paris and the way to showcase the world’s most fundamental and modern aspects of the history…

Hi! Who are you? I’m glad you’ve got a few minutes of time. We’re passionate about the concept of “A”…

Das Modell hat die Form einer Assistenten-Antwort und Wortassoziationen (France, Paris) gelernt, aber nicht das Schlussfolgern: Mit 4 Schichten und 39 Minuten Rechenzeit ist das das erwartete Ergebnis. Das Basismodell vor dem SFT hat den Text nur fortgesetzt, ohne zu antworten.

4. Was wir ändern mussten

Für die Intel-GPU (PyTorch XPU)

  1. Die GPU erkennen: nanochat kennt nur cuda, mps und cpu. Wir ergänzen xpu (Erkennung, Synchronisierung, Speicher).
  2. Keine Berechnung in doppelter Genauigkeit (fp64) auf dieser GPU: Eine Zeile in der Berechnung der rotierenden Embeddings lief über fp64 und stürzte ab (UR error: 45). Wir schreiben sie in fp32 um.
  3. Level-Zero-Header: torch.compile kompiliert seine GPU-Kernels mit Triton XPU, das dafür das Paket libze-dev braucht.
  4. Limit von 512 Work-Items pro Gruppe: Das Sortieren der 32.768 Tokens des Vokabulars (topk) überschreitet dieses Limit. Wir erledigen es auf der CPU, die Kosten sind vernachlässigbar.

Für nanochat in kleiner Konfiguration

Diese vier Probleme treten auch auf der CPU auf: Sie kommen von der verkleinerten Konfiguration (Kontext von 512 Tokens), nicht von der GPU.

  1. Der SFT-Loader blockiert: Er nimmt nur Konversationen, die vollständig in 513 Tokens passen. Längere bleiben in seiner Warteschlange, bis sie voll ist, danach liefert er nur noch Padding: Das Modell lernt nichts mehr, driftet ab und endet bei nan. Wir kürzen die Konversationen auf 513 Tokens.
  2. Ein Loss von nan, wenn ein Batch kein einziges Antwort-Token enthält (Division 0/0). Wir teilen durch mindestens 1.
  3. Eine zu hohe Learning Rate für ein so kleines Modell: Wir verwenden 20 % des Standardwerts für das SFT und hören bei 220 Schritten auf (darüber brach das Modell plötzlich ein, immer beim selben Schritt).
  4. Der Chat lud ein altes Modell: chat_cli nimmt den Checkpoint mit der höchsten Schrittnummer, nicht den neuesten. Wir übergeben ihm -s 219.

5. nanochat auf Ihrer iGPU ausführen, Schritt für Schritt

Alles läuft per SSH auf der VM (Debian 13, aktueller Kernel). Rechnen Sie mit einer Stunde, davon 40 Minuten Rechenzeit.

Der gesamte Code (Skript, Docker, Patches) liegt auf GitHub: LinuxGPU-VPS/examples/nanochat-xpu.

Schnelle Variante: ein einziger Befehl

curl -fsSLO https://raw.githubusercontent.com/LinuxGPU-VPS/examples/main/nanochat-xpu/run.sh
sudo bash run.sh                      # nur iGPU
sudo HYBRID_CPU_ROWS=4 bash run.sh    # iGPU + CPU

Das Skript führt genau die folgenden Schritte aus, misst die Zeit jedes Schritts in ~/nanochat-logs/timeline.txt und bricht beim ersten Fehler ab.

Docker-Variante

Dieselbe Pipeline in einem Container, ohne außer Docker etwas auf dem Rechner zu installieren:

git clone https://github.com/LinuxGPU-VPS/examples
cd examples/nanochat-xpu
docker compose up --build                       # nur iGPU
HYBRID_CPU_ROWS=4 docker compose up --build     # iGPU + CPU
docker compose run --rm nanochat /opt/linuxgpu/train.sh chat   # danach mit dem Modell chatten

Das Image (Ubuntu 24.04) enthält den Intel-GPU-Treiber (Level Zero), PyTorch XPU, nanochat und die Patches. Der Container greift über /dev/dri auf die iGPU zu. Daten und Modelle landen in ./data, die Dauer jedes Schritts in ./logs/timeline.txt.

1. Prüfen, ob die iGPU sichtbar ist

ls /dev/dri    # card0 und renderD128: renderD128 ist die iGPU

Auf unseren VMs zeigt der Befehl gpu-check außerdem „Intel(R) UHD Graphics 770“ an und testet OpenCL, Vulkan und die Videokodierung.

2. Systempakete installieren

sudo apt-get install -y git curl build-essential libze-dev

libze-dev enthält die Level-Zero-Header, die Low-Level-Schnittstelle der Intel-GPUs. Ohne sie scheitert torch.compile beim Kompilieren seiner Kernels für die iGPU (Fehler level_zero/ze_api.h: No such file or directory).

3. nanochat und PyTorch für Intel-GPUs (XPU) installieren

git clone https://github.com/karpathy/nanochat && cd nanochat
git checkout 92d63d4                  # die getestete Version
curl -LsSf https://astral.sh/uv/install.sh | sh && export PATH=$HOME/.local/bin:$PATH
uv venv && uv sync --extra cpu        # Abhängigkeiten von nanochat (mit PyTorch CPU)
VIRTUAL_ENV=$PWD/.venv uv pip install --reinstall torch==2.9.1 \
  --index-url https://download.pytorch.org/whl/xpu   # ersetzt PyTorch CPU durch den XPU-Build
.venv/bin/python -c "import torch; print(torch.xpu.is_available(), torch.xpu.get_device_name(0))"
# True Intel(R) UHD Graphics 770

xpu ist der Name, den PyTorch den Intel-GPUs gibt, wie cuda für NVIDIA. Der XPU-Build bringt alles Nötige mit (SYCL-Runtime, Triton XPU für torch.compile): Es muss kein proprietärer Treiber installiert werden. Führen Sie uv sync danach nicht erneut aus, sonst wird wieder die CPU-Version installiert.

4. Die Patches für die iGPU anwenden

curl -fsSLO https://raw.githubusercontent.com/LinuxGPU-VPS/examples/main/nanochat-xpu/nanochat_xpu_patch.py
python3 nanochat_xpu_patch.py .

Der Patch ändert einige Zeilen von nanochat. Die drei für die iGPU unverzichtbaren Änderungen:

# nanochat/common.py: die iGPU wählen, wenn sie vorhanden ist
elif torch.xpu.is_available():
    device_type = "xpu"

# nanochat/gpt.py: die iGPU hat kein fp64, wir berechnen die Frequenzen in fp32
inv_freq = torch.exp(channel_range * (-math.log(base) / head_dim))

# nanochat/engine.py: das Sortieren der 32.768 Tokens überschreitet das Limit der iGPU (512 Work-Items)
vals, idx = torch.topk(logits.float().cpu(), k, dim=-1)

Er fügt außerdem die oben beschriebenen Korrekturen an nanochat hinzu (SFT-Loader, Loss nan, nicht endliche Gradienten).

5. Die Umgebung einrichten

export NANOCHAT_BASE_DIR=$HOME/.cache/nanochat   # Daten, Tokenizer und Modelle
export NANOCHAT_DTYPE=bfloat16                   # sonst rechnet nanochat außerhalb von CUDA in fp32
export OverrideDefaultFP64Settings=1             # fp64-Emulation durch den Intel-Treiber,
export IGC_EnableDPEmulation=1                   # falls eine Operation sie benötigt
P=.venv/bin/python

6. Die Daten herunterladen und den Tokenizer trainieren (CPU, ~1 min)

$P -m nanochat.dataset -n 8
$P -m scripts.tok_train --max-chars=2000000000

7. Das Modell auf der iGPU vortrainieren (~33 min)

$P -m scripts.base_train --device-type=xpu --depth=4 --head-dim=64 --window-pattern=L \
  --max-seq-len=512 --device-batch-size=16 --total-batch-size=8192 \
  --eval-every=500 --eval-tokens=131072 --core-metric-every=-1 --sample-every=500 \
  --num-iterations=1500 --run=dummy

--device-type=xpu schickt die Berechnung auf die iGPU. Der erste Schritt dauert etwa 80 s: PyTorch kompiliert seine Kernels für die iGPU, nur ein einziges Mal. Während des Trainings zeigt nvtop (auf unseren VMs installiert) die Auslastung der iGPU.

Damit iGPU und CPU gemeinsam arbeiten (−15 % Zeit), wenden Sie zusätzlich den zweiten Patch an und starten denselben Befehl mit drei Variablen:

curl -fsSLO https://raw.githubusercontent.com/LinuxGPU-VPS/examples/main/nanochat-xpu/nanochat_hybrid_patch.py
python3 nanochat_hybrid_patch.py .
NANOCHAT_DTYPE=float32 NANOCHAT_CPU_ROWS=4 NANOCHAT_CPU_THREADS=3 \
  $P -m scripts.base_train --device-type=xpu ...   # dieselben Optionen wie oben

8. Fine-Tuning (SFT) und dann Chat (~6 min)

NANOCHAT_CLIP=1.0 $P -m scripts.chat_sft --device-type=xpu --eval-every=50 \
  --eval-tokens=65536 --chatcore-every=-1 --num-iterations=220 --init-lr-frac=0.2 --run=dummy
$P -m scripts.chat_cli --device-type=xpu -s 219 -p "What is the capital of France?"
$P -m scripts.chat_cli --device-type=xpu -s 219    # Konversationsmodus

-s 219 wählt das aus dem SFT hervorgegangene Modell (den letzten Schritt). Ohne diese Option nimmt chat_cli das Modell mit der höchsten Schrittnummer, das aus einem früheren Versuch stammen kann.

Das Wichtigste

  • Eine iGPU trainiert tatsächlich ein kleines Sprachmodell, ohne dedizierte Grafikkarte: natives PyTorch, torch.compile, kein CUDA, und die CPU kann sie unterstützen (15 % weniger Zeit).
  • Sie ist das richtige Terrain zum Lernen: um zu verstehen, wie ein LLM von Anfang bis Ende funktioniert, den Code zu ändern und die Wirkung in wenigen Minuten zu sehen, nicht um ein produktiv nutzbares Modell zu trainieren.
  • Um ein bereits trainiertes Modell bereitzustellen, lesen Sie besser unsere Anleitungen zu Hugging-Face-Modellen auf der iGPU und Inferenz mit OpenVINO.

Ausprobieren

Alle unsere VMs haben eine iGPU Intel UHD 770, die direkt nach der Installation mit PyTorch XPU, OpenCL und Vulkan nutzbar ist, ab 14,99 € netto pro Monat. Dieser Test lief auf dem Tarif iGPU Pro (6 vCPU, 24 GB RAM, 240 GB SSD, der größte Anteil der iGPU) für 44,99 € netto pro Monat: die richtige Wahl zum Trainieren, dank der vCPU, die die iGPU unterstützen. iGPU-Tarife ansehen.