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
| Element | Wert |
|---|---|
| Virtuelle Maschine | Tarif iGPU Pro: 6 vCPU, 24 GB RAM, Debian 13, Kernel 7.3 |
| CPU / GPU | Intel Core i5-13500, integrierte GPU Intel UHD 770 (mit anderen VMs geteilt) |
| Software | nanochat (Commit 92d63d4), PyTorch 2.9.1 XPU-Build, torch.compile mit Triton XPU |
| Modell | d4: 4 Schichten, Breite 256, Kontext 512 Tokens, Vokabular 32.768 |
| Pre-Training | 1.500 Schritte zu 8.192 Tokens (12,3 Millionen Tokens) |
| SFT | 220 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×2048 | TFLOPS |
|---|---|
| fp32 | 0,71 |
| bf16 | 0,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
| Schritt | Dauer | Detail |
|---|---|---|
| Installation (Pakete, nanochat, PyTorch XPU) | ~1 min | |
| Daten (8 Dateien) + Tokenizer | 50 s | auf der CPU |
| 1. Iteration des Pre-Trainings | 83 s | Kompilierung der GPU-Kernels, nur einmal |
| Pre-Training, 1.500 Schritte | 33 min | 1,33 s/Schritt, ~6.150 Tokens/s, 1,5 GB GPU-Speicher |
| SFT, 220 Schritte | ~6 min | ~1,5 s/Schritt |
| 1. Antwort im Chat | 3 min | Kompilierung |
| Weitere Antworten | 13 bis 15 s | lange Antworten, bis zum Token-Limit |
GPU gegen CPU, gleiche Arbeit
| SFT, Schritte zu 16.384 Tokens | s/Schritt | Tokens/s |
|---|---|---|
| GPU Intel UHD 770 | 2,64 | 6.200 |
| 6 vCPU (i5-13500) | 3,0 bis 3,5 | 4.630 |
Die Lernkurve
| Anfang | Ende | |
|---|---|---|
| Pre-Training, Bits pro Byte in der Validierung | 3,20 | 1,52 |
| SFT, Bits pro Byte in der Validierung | 1,35 | 1,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):
- 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.
- 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.
- 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/Schritt | Im Vergleich zur GPU allein in fp32 (1,40 s) |
|---|---|---|
| 16 + 0 (nur GPU) | 1,40 | Referenz |
| 12 + 4 (3 oder 4 Threads) | 1,16 | −17 % |
| 10 + 6 (5 Threads) | 1,32 | −6 % |
| 10 + 6 (4 Threads) | 1,41 | kein Gewinn |
| 8 + 8 (4 Threads) | 1,43 | langsamer |
Ü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 Schritte | Nur GPU (bf16) | GPU + CPU (fp32) |
|---|---|---|
| Zeit pro Schritt | 1,33 s | 1,13 s |
| Tokens pro Sekunde | ~6.150 | ~7.200 |
| Gesamtdauer | 33 min | 28 min |
| Finale Validierung (Bits pro Byte) | 1,52 | 1,55 |
| SFT, danach Chat | identisch | identisch |
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)
- Die GPU erkennen: nanochat kennt nur
cuda,mpsundcpu. Wir ergänzenxpu(Erkennung, Synchronisierung, Speicher). - 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. - Level-Zero-Header:
torch.compilekompiliert seine GPU-Kernels mit Triton XPU, das dafür das Paketlibze-devbraucht. - 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.
- 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. - Ein Loss von
nan, wenn ein Batch kein einziges Antwort-Token enthält (Division 0/0). Wir teilen durch mindestens 1. - 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).
- Der Chat lud ein altes Modell:
chat_clinimmt 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.