LLM trainen op een iGPU: nanochat (Karpathy) op een gewone geïntegreerde GPU
nanochat is het project van Andrej Karpathy dat in één leesbare repository alle stappen van een “ChatGPT” laat zien: tokenizer, pretraining, fine-tuning (SFT) en een chatinterface. Het is bedoeld voor 8 NVIDIA H100-GPU’s, maar er is ook een kleinere configuratie voor de CPU.
Wij hebben het volledig op een iGPU laten draaien: de Intel UHD 770, de GPU die in de processor geïntegreerd is (een desktop-Core i5), zonder enige aparte videokaart en zonder CUDA. Dat alles met PyTorch XPU, de native ondersteuning voor Intel GPU’s in PyTorch, en daarna met iGPU en CPU die samen rekenen.
Resultaten in het kort
- Een gewone iGPU is genoeg om een LLM van begin tot eind te trainen: installatie, data, tokenizer, pretraining (33 min), SFT (ongeveer 6 min) en de eerste antwoorden in minder dan een uur, op de geïntegreerde GPU van de VM, zonder videokaart.
- 1,33 s per pretrainingstap, dus ongeveer 6.150 tokens/s, voor een klein model met 4 lagen. Voor hetzelfde werk is de GPU ongeveer 1,3 keer sneller dan de 6 vCPU’s.
- iGPU en CPU samen: als de CPU 4 van de 16 regels van elke batch krijgt, gaat de pretraining van 1,33 naar 1,13 s per stap (15% minder tijd), met vrijwel dezelfde modelkwaliteit (validatie 1,55 tegenover 1,52). Meer werk aan de CPU geven maakt het juist trager.
- Het model dat eruit komt speelt de assistent en spreekt Engels, maar blijft onsamenhangend: dat komt door de grootte van het model (37 miljoen parameters, vooral embeddingtabellen, 39 min training), niet door het platform.
- Er waren 4 aanpassingen voor de Intel GPU en 4 correcties in nanochat nodig voor een kleine configuratie. Alles zit in een script met één commando, onderaan dit artikel.
Waarom een iGPU?
Een iGPU (geïntegreerde GPU) is het grafische deel dat in de processor zit, op dezelfde chip als de CPU-cores. Normaal gebruik je hem voor het beeld; hier dient hij om te rekenen.
- Hij deelt het RAM van de server in plaats van eigen videogeheugen te hebben. Geen VRAM-limiet van 8 of 12 GB, en geen dure kopieën tussen CPU en GPU: daardoor werkt de training met iGPU + CPU verderop zo goed. Ons model gebruikte maar 1,5 GB.
- PyTorch ondersteunt hem native (XPU-build), inclusief
torch.compile: dezelfde code als met CUDA, op een paar regels na. - Hij kost een fractie van een dedicated GPU: ons iGPU Pro-pakket kost €44,99 per maand excl. btw, terwijl je voor een dedicated GPU in de cloud vaak €300 tot €500 per maand betaalt.
- Hij is veel minder krachtig (minder dan 1 TFLOPS): hij vervangt geen dedicated GPU om een groot model te trainen. Hij is genoeg om te leren, te experimenteren en kleine modellen te draaien.
Testopstelling
| Onderdeel | Waarde |
|---|---|
| Virtuele machine | iGPU Pro-pakket: 6 vCPU, 24 GB RAM, Debian 13, kernel 7.3 |
| CPU / GPU | Intel Core i5-13500, geïntegreerde Intel UHD 770-GPU (gedeeld met andere VM’s) |
| Software | nanochat (commit 92d63d4), PyTorch 2.9.1 XPU-build, torch.compile met Triton XPU |
| Model | d4: 4 lagen, breedte 256, context van 512 tokens, vocabulaire van 32.768 |
| Pretraining | 1.500 stappen van 8.192 tokens (12,3 miljoen tokens) |
| SFT | 220 stappen van 8.192 tokens, mix van gesprekken, MMLU en GSM8K |
Dit is de “CPU”-configuratie van nanochat, verkleind zodat hij in minder dan een uur klaar is. De echte
nanochat (speedrun.sh, model met 20 lagen) heeft ongeveer 4 uur nodig op 8 H100’s: dat is drie
ordes van grootte meer rekenwerk, buiten bereik van een geïntegreerde GPU.
1. De cijfers
Ruwe rekenkracht
| Matrixvermenigvuldiging 2048×2048 | TFLOPS |
|---|---|
| fp32 | 0,71 |
| bf16 | 0,73 |
De GPU is gedeeld: als een andere VM hem gebruikt, maten we tot 0,27 TFLOPS. Deze GPU heeft geen aparte matrixeenheden, dus bf16 is niet sneller dan fp32, maar het gebruikt wel half zoveel geheugen.
Training en chat
| Stap | Duur | Detail |
|---|---|---|
| Installatie (pakketten, nanochat, PyTorch XPU) | ~1 min | |
| Data (8 bestanden) + tokenizer | 50 s | op de CPU |
| 1e iteratie van de pretraining | 83 s | compilatie van de GPU-kernels, maar één keer |
| Pretraining, 1.500 stappen | 33 min | 1,33 s/stap, ~6.150 tokens/s, 1,5 GB GPU-geheugen |
| SFT, 220 stappen | ~6 min | ~1,5 s/stap |
| 1e antwoord van de chat | 3 min | compilatie |
| Volgende antwoorden | 13 tot 15 s | lange antwoorden, tot de tokenlimiet |
GPU tegen CPU, hetzelfde werk
| SFT, stappen van 16.384 tokens | s/stap | tokens/s |
|---|---|---|
| Intel UHD 770-GPU | 2,64 | 6.200 |
| 6 vCPU (i5-13500) | 3,0 tot 3,5 | 4.630 |
De leercurve
| Begin | Eind | |
|---|---|---|
| Pretraining, bits per byte bij validatie | 3,20 | 1,52 |
| SFT, bits per byte bij validatie | 1,35 | 1,10 |
Hoe lager, hoe beter het model tekst voorspelt. Beide curves dalen gelijkmatig, zonder uitschieters.
2. iGPU en CPU samen
De GPU rekent terwijl de 6 vCPU’s wachten. Daarom hebben we ze allebei tegelijk laten werken (correctie nanochat_hybrid_patch.py):
- Elke batch van 16 regels wordt in tweeën gesplitst: een deel gaat naar de GPU, de rest naar een kopie van het model in het CPU-geheugen. Beide rekenen parallel.
- De gradiënten van beide delen worden opgeteld, naar verhouding van het aantal regels. Wiskundig is dat dezelfde berekening als een hele batch op één processor.
- De optimizer werkt het model op de GPU bij, daarna worden de gewichten naar de CPU-kopie gekopieerd. Op een geïntegreerde GPU delen GPU en CPU hetzelfde geheugen: die kopie kost ongeveer 0,1 s per stap.
Alles draait in fp32: de CPU van deze server heeft geen snelle bf16-instructies, en op deze GPU is fp32 even snel als bf16.
Hoeveel werk geef je de CPU?
| Regels GPU + CPU (CPU-threads) | s/stap | Ten opzichte van alleen GPU in fp32 (1,40 s) |
|---|---|---|
| 16 + 0 (alleen GPU) | 1,40 | referentie |
| 12 + 4 (3 of 4 threads) | 1,16 | −17% |
| 10 + 6 (5 threads) | 1,32 | −6% |
| 10 + 6 (4 threads) | 1,41 | geen winst |
| 8 + 8 (4 threads) | 1,43 | trager |
Tegen de intuïtie in: meer regels aan de CPU geven maakt alles trager. Onze metingen laten zien dat de CPU altijd vóór de GPU klaar is, maar dat het GPU-deel trager wordt als de CPU rekent: de cores die rekenen zijn niet meer beschikbaar om de GPU aan te sturen (kernels starten, automatische differentiatie regelen). De beste instelling laat dus ruimte over: 4 van de 16 regels en 3 rekenthreads op 6 vCPU’s.
De volledige run, alleen GPU tegen GPU + CPU
| Pretraining, 1.500 stappen | Alleen GPU (bf16) | GPU + CPU (fp32) |
|---|---|---|
| Tijd per stap | 1,33 s | 1,13 s |
| Tokens per seconde | ~6.150 | ~7.200 |
| Totale duur | 33 min | 28 min |
| Eindvalidatie (bits per byte) | 1,52 | 1,55 |
| SFT en daarna chat | identiek | identiek |
De eindvalidatie is in de hybride run iets slechter (1,55 tegenover 1,52). De oorzaak hebben we nog niet geïsoleerd: de overstap naar fp32, of een effect van de verdeling tussen GPU en CPU.
Twee beperkingen om te kennen: de winst hangt af van het aantal vCPU’s (met 2 vCPU’s valt er niets te verdelen), en we hebben deze methode alleen op de pretraining toegepast, de langste stap. De SFT draait alleen op de GPU.
3. Wat het model antwoordt
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”…
Het model heeft de vorm van een assistentantwoord geleerd en woordverbanden (France, Paris), maar niet redeneren: met 4 lagen en 39 minuten rekenen is dat het verwachte resultaat. Het basismodel, vóór de SFT, deed niet meer dan de tekst voortzetten zonder te antwoorden.
4. Wat er veranderd moest worden
Voor de Intel GPU (PyTorch XPU)
- De GPU herkennen: nanochat kent alleen
cuda,mpsencpu. We voegenxputoe (detectie, synchronisatie, geheugen). - Geen rekenen in dubbele precisie (fp64) op deze GPU: één regel in de berekening van de roterende
embeddings ging via fp64 en crashte (
UR error: 45). We herschrijven hem in fp32. - Level Zero-headers:
torch.compilecompileert zijn GPU-kernels met Triton XPU, dat het pakketlibze-devnodig heeft. - Limiet van 512 work-items per groep: het sorteren van de 32.768 tokens van het vocabulaire (
topk) gaat over die limiet. We doen het op de CPU, de kosten zijn verwaarloosbaar.
Voor nanochat in een kleine configuratie
Deze vier problemen treden ook op de CPU op: ze komen door de verkleinde configuratie (context van 512 tokens), niet door de GPU.
- De SFT-loader loopt vast: hij neemt alleen gesprekken die in hun geheel in 513 tokens passen.
De langere blijven in zijn wachtrij tot die vol is, daarna levert hij alleen nog opvulling: het
model leert niets meer, ontspoort en eindigt in
nan. We korten de gesprekken in tot 513 tokens. - Een loss van
nanals een batch geen enkel antwoordtoken bevat (deling 0/0). We delen door minstens 1. - Een te hoge learning rate voor zo’n klein model: we gebruiken 20% van de standaardwaarde voor de SFT, en we stoppen bij 220 stappen (daarna stortte het model ineens in, altijd bij dezelfde stap).
- De chat laadde een oud model:
chat_clineemt de checkpoint met het hoogste stapnummer, niet de nieuwste. We geven hem-s 219mee.
5. nanochat op je iGPU draaien, stap voor stap
Alles gebeurt via SSH op de VM (Debian 13, recente kernel). Reken op een uur, waarvan 40 minuten rekenen.
Alle code (script, Docker, correcties) staat op GitHub: LinuxGPU-VPS/examples/nanochat-xpu.
Snelle optie: één commando
curl -fsSLO https://raw.githubusercontent.com/LinuxGPU-VPS/examples/main/nanochat-xpu/run.sh
sudo bash run.sh # alleen iGPU
sudo HYBRID_CPU_ROWS=4 bash run.sh # iGPU + CPU
Het script voert precies de stappen hieronder uit, meet de tijd van elke stap in
~/nanochat-logs/timeline.txt en stopt bij de eerste fout.
Docker-optie
Dezelfde pipeline in een container, zonder iets anders dan Docker op de machine te installeren:
git clone https://github.com/LinuxGPU-VPS/examples
cd examples/nanochat-xpu
docker compose up --build # alleen iGPU
HYBRID_CPU_ROWS=4 docker compose up --build # iGPU + CPU
docker compose run --rm nanochat /opt/linuxgpu/train.sh chat # daarna chatten met het model
Het image (Ubuntu 24.04) bevat de Intel-GPU-driver (Level Zero), PyTorch XPU, nanochat en de correcties. De container bereikt de iGPU via /dev/dri. Data en modellen komen in ./data, de duur van elke stap in ./logs/timeline.txt.
1. Controleren of de iGPU zichtbaar is
ls /dev/dri # card0 en renderD128: renderD128 is de iGPU
Op onze VM’s toont het commando gpu-check ook „Intel(R) UHD Graphics 770” en test het OpenCL,
Vulkan en video-encoding.
2. De systeempakketten installeren
sudo apt-get install -y git curl build-essential libze-dev
libze-dev bevat de Level Zero-headers, de low-level interface van Intel GPU’s. Zonder die headers
mislukt torch.compile op het moment dat het zijn kernels voor de iGPU compileert (fout
level_zero/ze_api.h: No such file or directory).
3. nanochat en PyTorch voor Intel GPU (XPU) installeren
git clone https://github.com/karpathy/nanochat && cd nanochat
git checkout 92d63d4 # de geteste versie
curl -LsSf https://astral.sh/uv/install.sh | sh && export PATH=$HOME/.local/bin:$PATH
uv venv && uv sync --extra cpu # afhankelijkheden van nanochat (met PyTorch CPU)
VIRTUAL_ENV=$PWD/.venv uv pip install --reinstall torch==2.9.1 \
--index-url https://download.pytorch.org/whl/xpu # vervangt PyTorch CPU door de 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 is de naam die PyTorch aan Intel GPU’s geeft, zoals cuda voor NVIDIA. De XPU-build brengt
alles mee wat nodig is (SYCL-runtime, Triton XPU voor torch.compile): je hoeft geen propriëtaire
driver te installeren. Draai daarna uv sync niet opnieuw, want dan komt de CPU-versie terug.
4. De correcties voor de iGPU toepassen
curl -fsSLO https://raw.githubusercontent.com/LinuxGPU-VPS/examples/main/nanochat-xpu/nanochat_xpu_patch.py
python3 nanochat_xpu_patch.py .
De patch wijzigt een paar regels van nanochat. De drie onmisbare wijzigingen voor de iGPU:
# nanochat/common.py: de iGPU kiezen als die aanwezig is
elif torch.xpu.is_available():
device_type = "xpu"
# nanochat/gpt.py: de iGPU heeft geen fp64, we berekenen de frequenties in fp32
inv_freq = torch.exp(channel_range * (-math.log(base) / head_dim))
# nanochat/engine.py: het sorteren van de 32.768 tokens gaat over de limiet van de iGPU (512 work-items)
vals, idx = torch.topk(logits.float().cpu(), k, dim=-1)
Hij voegt ook de hierboven beschreven correcties voor nanochat toe (SFT-loader, loss nan,
niet-eindige gradiënten).
5. De omgeving instellen
export NANOCHAT_BASE_DIR=$HOME/.cache/nanochat # data, tokenizer en modellen
export NANOCHAT_DTYPE=bfloat16 # anders rekent nanochat in fp32 buiten CUDA
export OverrideDefaultFP64Settings=1 # fp64-emulatie door de Intel-driver,
export IGC_EnableDPEmulation=1 # voor het geval een bewerking die nodig heeft
P=.venv/bin/python
6. De data downloaden en de tokenizer trainen (CPU, ~1 min)
$P -m nanochat.dataset -n 8
$P -m scripts.tok_train --max-chars=2000000000
7. Het model pretrainen op de iGPU (~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 stuurt het rekenwerk naar de iGPU. De eerste stap duurt ongeveer 80 s: PyTorch
compileert zijn kernels voor de iGPU, eenmalig. Tijdens het trainen toont nvtop (geïnstalleerd op onze
VM’s) de belasting van de iGPU.
Om de iGPU en de CPU samen te laten werken (15% minder tijd), pas je ook de tweede patch toe en start je hetzelfde commando met drie variabelen:
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 ... # dezelfde opties als hierboven
8. Fine-tuning (SFT) en daarna chatten (~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 # gespreksmodus
-s 219 kiest het model uit de SFT (de laatste stap). Zonder deze optie neemt chat_cli het
model met het hoogste stapnummer, dat van een eerdere poging kan komen.
Wat je moet onthouden
- Een iGPU traint echt een klein taalmodel, zonder aparte videokaart: native PyTorch,
torch.compile, geen CUDA, en de CPU kan hem bijstaan (15% minder tijd). - Het is de juiste plek om te leren hoe een LLM van begin tot eind werkt, de code aan te passen en het effect binnen een paar minuten te zien, niet om een bruikbaar model voor productie te trainen.
- Om een al getraind model te serveren, kun je beter onze gidsen lezen over Hugging Face-modellen op een iGPU en inferentie met OpenVINO.
Probeer het
Al onze VM’s hebben een Intel UHD 770-iGPU die direct na de installatie bruikbaar is voor PyTorch XPU, OpenCL en Vulkan, vanaf €14,99 per maand excl. btw. Deze test draaide op het iGPU Pro-pakket (6 vCPU, 24 GB RAM, 240 GB SSD, het grootste deel van de iGPU), voor €44,99 per maand excl. btw: de juiste keuze om te trainen, dankzij de vCPU’s die de iGPU bijstaan. Bekijk de iGPU-pakketten.