Blog

LLM trainen op een iGPU: nanochat (Karpathy) op een gewone geïntegreerde GPU

Geen videokaart, geen CUDA: een Intel UHD 770-iGPU traint nanochat, de mini-ChatGPT van Karpathy, van begin tot eind in minder dan een uur met PyTorch XPU. Gemeten cijfers, iGPU + CPU, een script dat klaar is om te starten.

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

OnderdeelWaarde
Virtuele machineiGPU Pro-pakket: 6 vCPU, 24 GB RAM, Debian 13, kernel 7.3
CPU / GPUIntel Core i5-13500, geïntegreerde Intel UHD 770-GPU (gedeeld met andere VM’s)
Softwarenanochat (commit 92d63d4), PyTorch 2.9.1 XPU-build, torch.compile met Triton XPU
Modeld4: 4 lagen, breedte 256, context van 512 tokens, vocabulaire van 32.768
Pretraining1.500 stappen van 8.192 tokens (12,3 miljoen tokens)
SFT220 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×2048TFLOPS
fp320,71
bf160,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

StapDuurDetail
Installatie (pakketten, nanochat, PyTorch XPU)~1 min
Data (8 bestanden) + tokenizer50 sop de CPU
1e iteratie van de pretraining83 scompilatie van de GPU-kernels, maar één keer
Pretraining, 1.500 stappen33 min1,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 chat3 mincompilatie
Volgende antwoorden13 tot 15 slange antwoorden, tot de tokenlimiet

GPU tegen CPU, hetzelfde werk

SFT, stappen van 16.384 tokenss/staptokens/s
Intel UHD 770-GPU2,646.200
6 vCPU (i5-13500)3,0 tot 3,54.630

De leercurve

BeginEind
Pretraining, bits per byte bij validatie3,201,52
SFT, bits per byte bij validatie1,351,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):

  1. 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.
  2. 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.
  3. 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/stapTen opzichte van alleen GPU in fp32 (1,40 s)
16 + 0 (alleen GPU)1,40referentie
12 + 4 (3 of 4 threads)1,16−17%
10 + 6 (5 threads)1,32−6%
10 + 6 (4 threads)1,41geen winst
8 + 8 (4 threads)1,43trager

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 stappenAlleen GPU (bf16)GPU + CPU (fp32)
Tijd per stap1,33 s1,13 s
Tokens per seconde~6.150~7.200
Totale duur33 min28 min
Eindvalidatie (bits per byte)1,521,55
SFT en daarna chatidentiekidentiek

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)

  1. De GPU herkennen: nanochat kent alleen cuda, mps en cpu. We voegen xpu toe (detectie, synchronisatie, geheugen).
  2. 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.
  3. Level Zero-headers: torch.compile compileert zijn GPU-kernels met Triton XPU, dat het pakket libze-dev nodig heeft.
  4. 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.

  1. 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.
  2. Een loss van nan als een batch geen enkel antwoordtoken bevat (deling 0/0). We delen door minstens 1.
  3. 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).
  4. De chat laadde een oud model: chat_cli neemt de checkpoint met het hoogste stapnummer, niet de nieuwste. We geven hem -s 219 mee.

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.