microgpt – GPT-Training und Inferenz in 200 Zeilen reinem Python implementiert
(karpathy.github.io)- Ein von karpathy veröffentlichtes Kunstprojekt. Implementiert den gesamten GPT-Algorithmus in einer einzigen Datei mit 200 Zeilen, ganz ohne externe Abhängigkeiten
- Der Unterschied zu produktiven LLMs liegt nur in Größe und Effizienz; der Kern ist identisch. Wer diesen Code versteht, hat das algorithmische Wesen von GPT verstanden
- Enthält Dataset, Tokenizer, Autograd-Engine, eine GPT-2-ähnliche Transformer-Architektur, den Adam-Optimierer sowie Trainings- und Inferenzschleifen
- Als Quintessenz aus 10 Jahren LLM-Vereinfachungsarbeit in früheren Projekten wie micrograd, makemore und nanogpt verdichtet es das Wesen von GPT auf eine minimale Form, die sich nicht weiter vereinfachen lässt
- Trainiert auf 32.000 Namensdatensätzen und erzeugt plausibel wirkende neue Namen, wobei alle Berechnungen direkt mit skalarem Autograd ausgeführt werden
- Der Trainingsprozess besteht aus Verlustberechnung → Backpropagation → Adam-Update und ist in etwa einer Minute ausführbar
Überblick über microgpt
- microgpt ist ein 200 Zeilen langes Python-Skript, das den Trainings- und Inferenzprozess eines GPT-Modells vollständig implementiert
- Ohne externe Bibliotheken enthält es Dataset, Tokenizer, Autograd, Modell, Optimierer und Trainingsschleife vollständig in einer Datei
- Führt bestehende Projekte wie micrograd, makemore und nanogpt in einer einzigen Datei zusammen
- Eine Implementierung, die nur noch den algorithmischen Kern übrig lässt, auf dem Niveau von „nicht weiter vereinfachbar“
- Der vollständige Code ist als GitHub Gist, Webseite und Google Colab verfügbar
Aufbau des Datasets
- Der Treibstoff großer Sprachmodelle ist ein Textdatenstrom. In der Produktion werden Internet-Webseiten verwendet, in microgpt dagegen ein einfaches Beispiel mit 32.000 Namen, jeweils einer pro Zeile
- Jeder Name wird als ein einzelnes "Dokument" behandelt, und das Ziel des Modells ist es, statistische Muster in den Daten zu lernen, um ähnliche neue Dokumente zu erzeugen
- Nach dem Training „halluziniert“ das Modell plausible neue Namen wie "kamon", "karai" oder "vialan"
- Aus Sicht von ChatGPT ist auch ein Gespräch mit einem Nutzer nur ein "ungewöhnlich geformtes Dokument"; initialisiert man ein Dokument per Prompt, entspricht die Antwort des Modells einer statistischen Dokumentvervollständigung
Tokenizer
- Da neuronale Netze nicht mit Zeichen, sondern mit Zahlen arbeiten, braucht man eine Methode, um Text in Sequenzen ganzzahliger Token-IDs umzuwandeln und wieder zurückzuführen
- Produktive Tokenizer wie tiktoken (verwendet von GPT-4) arbeiten aus Effizienzgründen mit Zeichenblöcken, aber der einfachste Tokenizer weist jedem eindeutigen Zeichen im Dataset eine ganze Zahl zu
- Die Kleinbuchstaben a-z werden sortiert, und jedem Zeichen wird per Index eine ID zugewiesen; der Integerwert selbst hat keine Bedeutung, jedes Token ist ein eigenständiges diskretes Symbol
- Ein spezielles BOS-Token (Beginning of Sequence) signalisiert „ein neues Dokument beginnt/endet“, sodass "emma" als
[BOS, e, m, m, a, BOS]eingerahmt wird - Die endgültige Vokabulargröße beträgt 27 (26 Kleinbuchstaben + 1 BOS)
Automatische Differentiation (Autograd)
- Für das Training neuronaler Netze braucht man Gradienten: Für jeden Parameter muss man wissen: „Wenn ich diesen Wert leicht erhöhe, steigt der Verlust dann, sinkt er, und um wie viel?“
- Der Rechengraph hat viele Eingaben (Modellparameter und Eingabetokens), läuft aber auf einen einzigen skalaren Output, den Verlust (loss), hinaus
- Backpropagation startet am Output und verfolgt den Graphen rückwärts. Dabei stützt sie sich auf die Kettenregel der Differentialrechnung, um die Gradienten des Verlusts bezüglich aller Eingaben zu berechnen
- Implementiert über die Value-Klasse: Jeder Value kapselt einen einzelnen Skalar (
.data) und verfolgt, wie er berechnet wurde- Bei Operationen wie Addition oder Multiplikation speichert ein neuer Value die Eingaben (
_children) und die lokalen Ableitungen (_local_grads) der jeweiligen Operation - Beispiel:
__mul__speichert ∂(a·b)/∂a=b und ∂(a·b)/∂b=a
- Bei Operationen wie Addition oder Multiplikation speichert ein neuer Value die Eingaben (
- Unterstützte Operationsbausteine: Addition, Multiplikation, Potenzieren, log, exp, ReLU
- Die Methode
backward()durchläuft den Graphen in umgekehrt topologischer Reihenfolge und wendet in jedem Schritt die Kettenregel an- Am Verlustknoten beginnt sie mit
self.grad = 1(∂L/∂L=1) - Die lokalen Gradienten werden entlang des Pfads multipliziert und bis zu den Parametern weitergegeben
- Am Verlustknoten beginnt sie mit
- Akkumulation mit += (keine Zuweisung): Wenn sich der Graph verzweigt, fließen aus jedem Zweig unabhängig Gradienten ein und müssen aufsummiert werden (eine Folge der mehrvariablen Kettenregel)
- Algorithmisch ist das identisch zu PyTorchs
.backward(), arbeitet aber auf Skalar- statt Tensor-Ebene und ist dadurch deutlich einfacher, jedoch weniger effizient
Parameterinitialisierung
- Parameter sind das Wissen des Modells: eine große Menge an Fließkommazahlen, die zufällig starten und während des Trainings wiederholt optimiert werden
- Sie werden mit kleinen Zufallswerten aus einer Gaußschen Verteilung initialisiert
- Sie bestehen aus im
state_dictbenannten Matrizen: Embedding-Tabelle, Attention-Gewichte, MLP-Gewichte und finale Ausgabeprojektion - Einstellung der Hyperparameter:
n_embd = 16: Embedding-Dimensionn_head = 4: Anzahl der Attention-Headsn_layer = 1: Anzahl der Layerblock_size = 16: maximale Sequenzlänge
- Das kleine Modell hat 4.192 Parameter (GPT-2 hat 1,6 Milliarden, moderne LLMs Hunderte Milliarden)
Architektur
- Die Modellarchitektur ist eine zustandslose Funktion: Sie nimmt Token, Position, Parameter sowie zwischengespeicherte Schlüssel/Werte früherer Positionen entgegen und gibt Logits (Scores) für das nächste Token zurück
- Orientiert sich an GPT-2, aber leicht vereinfacht: RMSNorm (statt LayerNorm), keine Biases, ReLU (statt GeLU)
-
Hilfsfunktionen
- linear: berechnet per Matrix-Vektor-Multiplikation ein Skalarprodukt für jede Zeile der Gewichtungsmatrix, eine gelernte lineare Transformation als Grundbaustein neuronaler Netze
- softmax: wandelt rohe Scores (Logits) in eine Wahrscheinlichkeitsverteilung um, wobei alle Werte im Bereich [0,1] liegen und in Summe 1 ergeben; zur numerischen Stabilität wird zuerst der Maximalwert abgezogen
- rmsnorm: skaliert einen Vektor so um, dass er eine Einheits-Root-Mean-Square hat, damit Aktivierungen beim Durchlaufen des Netzes nicht wachsen oder schrumpfen; stabilisiert das Training
-
Modellstruktur
- Embeddings: Token-ID und Positions-ID verweisen jeweils auf Zeilen in den Embedding-Tabellen (
wte,wpe); die beiden Vektoren werden addiert, um gleichzeitig zu kodieren, was das Token ist und wo es sich in der Sequenz befindet- Moderne LLMs überspringen Positions-Embeddings und verwenden relative Positionierungstechniken wie RoPE
- Attention-Block: projiziert das aktuelle Token in drei Vektoren: Q (Query), K (Key), V (Value)
- Query: „Wonach suche ich?“, Key: „Was trage ich in mir?“, Value: „Was liefere ich, wenn ich ausgewählt werde?“
- Beispiel: Beim Vorhersagen nach dem zweiten „m“ in „emma“ kann das Modell eine Query wie „Welcher Vokal kam zuletzt?“ lernen; das frühere „e“ passt gut zu dieser Query und erhält ein hohes Attention-Gewicht
- Keys und Values werden dem KV-Cache hinzugefügt, sodass auf frühere Positionen verwiesen werden kann
- Jeder Attention-Head berechnet das Skalarprodukt zwischen der Query und allen zwischengespeicherten Keys (skaliert mit √d_head), erhält per softmax die Attention-Gewichte und bildet die gewichtete Summe der zwischengespeicherten Values
- Die Ausgaben aller Heads werden verkettet und mit
attn_woprojiziert - Der Attention-Block ist der einzige Ort, an dem ein Token an Position t die früheren Token 0..t-1 „sehen“ kann; Attention ist der Kommunikationsmechanismus zwischen Token
- MLP-Block: ein 2-schichtiges Feedforward-Netzwerk: Expansion auf das 4-Fache der Embedding-Dimension → ReLU anwenden → wieder verkleinern
- Hier findet der Großteil des positionsweisen „Denkens“ statt
- Anders als Attention ist dies zu Zeitpunkt t eine vollständig lokale Berechnung
- Transformer wechseln Kommunikation (Attention) und Berechnung (MLP) ab
- Residual-Verbindungen: Sowohl Attention- als auch MLP-Block addieren ihre Ausgabe wieder auf die Eingabe
- Dadurch können Gradienten direkt durch das Netz fließen und tiefe Modelle trainierbar machen
- Ausgabe: Der finale Hidden State wird mit
lm_headauf die Vokabulargröße projiziert, um pro Token ein Logit zu erzeugen (hier 27 Zahlen); hohes Logit = hohe Wahrscheinlichkeit, dass dieses Token als Nächstes kommt - Besonderheit des KV-Caches: Die Nutzung eines KV-Caches während des Trainings ist selten, aber weil microgpt jeweils nur ein Token auf einmal verarbeitet, wird er explizit aufgebaut; die zwischengespeicherten Keys und Values sind lebende Value-Knoten im Rechengraphen und damit Ziel der Backpropagation
- Embeddings: Token-ID und Positions-ID verweisen jeweils auf Zeilen in den Embedding-Tabellen (
Trainingsschleife
- Die Trainingsschleife wiederholt fortlaufend: (1) Dokument auswählen → (2) Modell-Forward-Pass über die Token ausführen → (3) Verlust berechnen → (4) per Backpropagation Gradienten erhalten → (5) Parameter aktualisieren
-
Tokenisierung
- In jedem Trainingsschritt wird ein Dokument ausgewählt und auf beiden Seiten mit BOS umschlossen: „emma“ →
[BOS, e, m, m, a, BOS] - Das Ziel des Modells ist es, bei gegebenen vorherigen Token jedes nächste Token vorherzusagen
- In jedem Trainingsschritt wird ein Dokument ausgewählt und auf beiden Seiten mit BOS umschlossen: „emma“ →
-
Forward-Pass und Verlust
- Die Token werden dem Modell nacheinander einzeln zugeführt, wobei der KV-Cache aufgebaut wird
- An jeder Position gibt das Modell 27 Logits aus, die per softmax in Wahrscheinlichkeiten umgewandelt werden
- Der Verlust an jeder Position ist die negative Log-Wahrscheinlichkeit des korrekten nächsten Tokens: −log p(target); das nennt man Cross-Entropy-Loss
- Der Verlust misst, wie überrascht das Modell von dem tatsächlich folgenden Token ist: Bei Wahrscheinlichkeit 1.0 ist der Verlust 0, bei einer Wahrscheinlichkeit nahe 0 ist der Verlust +∞
- Die positionsweisen Verluste über das gesamte Dokument werden gemittelt, um einen einzelnen skalaren Verlust zu erhalten
-
Backward-Pass
- Ein einziger Aufruf von
loss.backward()führt die Backpropagation über den gesamten Rechengraphen aus - Danach zeigt
.gradjedes Parameters, wie er verändert werden muss, um den Verlust zu senken
- Ein einziger Aufruf von
-
Adam-Optimierer
- Statt einfachem Gradientenabstieg (
p.data -= lr * p.grad) wird Adam verwendet - Für jeden Parameter werden zwei gleitende Mittelwerte geführt:
m: Mittelwert der jüngsten Gradienten (Momentum)v: Mittelwert der quadrierten jüngsten Gradienten (Anpassung der Lernrate pro Parameter)
m_hatundv_hatsind die Bias-korrigierten Varianten der bei 0 initialisierten Werte m und v- Die Lernrate wird während des Trainings linear abgesenkt
- Nach dem Update wird mit
.grad = 0zurückgesetzt
- Statt einfachem Gradientenabstieg (
-
Trainingsergebnis
- Über 1.000 Schritte sinkt der Verlust von etwa 3.3 (zufälliges Raten unter 27 Token: −log(1/27)≈3.3) auf etwa 2.37
- Niedriger ist besser und das Minimum ist 0 (perfekte Vorhersage), also gibt es noch Verbesserungspotenzial, aber das Modell lernt klar die statistischen Muster von Namen
Inferenz
- Nach Abschluss des Trainings können mit dem Modell neue Namen gesampelt werden; bei fixierten Parametern wird der Forward-Pass in einer Schleife ausgeführt, und jedes erzeugte Token wird als nächste Eingabe zurückgeführt
-
Sampling-Prozess
- Jedes Sample beginnt mit einem BOS-Token („neuen Namen beginnen“)
- Das Modell erzeugt 27 Logits → Umwandlung in Wahrscheinlichkeiten → zufälliges Sampling eines Tokens entsprechend dieser Wahrscheinlichkeiten
- Dieses Token wird als nächste Eingabe zurückgeführt; wiederholt wird, bis das Modell erneut BOS („fertig“) erzeugt oder die maximale Sequenzlänge erreicht ist
-
Temperatur (Temperature)
- Vor softmax werden die Logits durch die Temperatur geteilt
- Temperatur 1.0: direktes Sampling aus der vom Modell gelernten Verteilung
- Niedrige Temperatur (z. B. 0.5): macht die Verteilung schärfer, sodass das Modell eher konservative Top-Auswahlen trifft
- Temperatur nahe 0: wählt immer das einzelne Token mit der höchsten Wahrscheinlichkeit (greedy decoding)
- Hohe Temperatur: macht die Verteilung flacher und die Ausgaben vielfältiger, aber weniger konsistent
Ausführung
- Nur Python erforderlich (kein
pip install, keine Abhängigkeiten):python train.py - Auf einem MacBook dauert es etwa 1 Minute
- In jedem Schritt wird der Verlust ausgegeben: von ~3.3 (zufällig) auf ~2.37 fallend
- Nach dem Training werden halluzinierte neue Namen erzeugt: „kamon“, „ann“, „karai“ usw.
- Läuft auch in einem Google-Colab-Notebook, Fragen können an Gemini gestellt werden
- Andere Datensätze ausprobieren, mit höherem
num_stepslänger trainieren, mit größerem Modell bessere Ergebnisse erzielen
Entwicklungsschritte des Codes
| Datei | Hinzugefügter Inhalt |
|---|---|
train0.py |
Bigramm-Zähltabelle — kein neuronales Netz, keine Gradienten |
train1.py |
MLP + manuelle Gradienten (numerisch & analytisch) + SGD |
train2.py |
Autograd (Value-Klasse) — ersetzt manuelle Gradienten |
train3.py |
Positions-Embeddings + Single-Head-Attention + rmsnorm + Residual |
train4.py |
Multi-Head-Attention + Layer-Schleife — vollständige GPT-Architektur |
train5.py |
Adam-Optimierer — das ist train.py |
- In den Revisions des Gists build_microgpt.py lassen sich alle Versionen und die Diffs zwischen den einzelnen Schritten ansehen
Unterschiede zu produktiven LLMs
- microgpt enthält das vollständige algorithmische Wesen von GPT-Training und -Ausführung; der Unterschied zu produktiven LLMs wie ChatGPT verändert nicht den Kernalgorithmus, sondern betrifft die Dinge, die ihn im großen Maßstab funktionsfähig machen
-
Daten
- Statt 32K kurzer Namen wird mit Billionen von Internet-Text-Token trainiert (Webseiten, Bücher, Code usw.)
- Deduplizierung der Daten, Qualitätsfilterung und sorgfältige Mischung über verschiedene Domänen hinweg
-
Tokenizer
- Statt einzelner Zeichen wird ein Subword-Tokenizer wie BPE (Byte Pair Encoding) verwendet
- Häufig gemeinsam auftretende Zeichenfolgen werden zu einem einzelnen Token zusammengeführt; gängige Wörter wie "the" sind ein einzelnes Token, seltene Wörter werden in Teile zerlegt
- Ein Vokabular von ~100K Token ist deutlich effizienter, weil pro Position mehr Inhalt gesehen wird
-
Autograd
- Statt eines skalaren Value-Objekts in reinem Python werden Tensoren (große mehrdimensionale Zahlenarrays) verwendet, die auf GPU/TPU laufen und Milliarden von Fließkommaoperationen pro Sekunde ausführen
- PyTorch übernimmt das Autograd für Tensoren, und CUDA-Kernel wie FlashAttention fusionieren mehrere Operationen
- Die Mathematik ist dieselbe, nur werden viele Skalare parallel verarbeitet
-
Architektur
- microgpt: 4.192 Parameter, ein Modell auf GPT-4-Niveau: Hunderte Milliarden
- Insgesamt ein sehr ähnliches Transformer-Netzwerk, aber viel breiter (Einbettungsdimension 10.000+) und viel tiefer (100+ Layer)
- Zusätzliche Lego-Block-Typen und geänderte Reihenfolge:
- RoPE (rotary positional embeddings) — statt gelernter Positionseinbettungen
- GQA (grouped query attention) — reduziert die Größe des KV-Cache
- gated linear activations — statt ReLU
- MoE-Layer (Mixture of Experts)
- Die Kernstruktur mit Attention (Kommunikation) und MLP (Berechnung), die sich über dem Residual Stream abwechseln, bleibt weitgehend erhalten
-
Training
- Statt eines Dokuments pro Schritt werden große Batches verwendet (Millionen Token pro Schritt), dazu Gradientenakkumulation, Mixed Precision (float16/bfloat16) und sorgfältiges Hyperparameter-Tuning
- Für das Training von Frontier-Modellen laufen Tausende GPUs über Monate hinweg
-
Optimierung
- microgpt: Adam + einfacher linearer Lernratenabfall
- Im großen Maßstab ist Optimierung ein eigenes Fachgebiet: reduzierte Präzision (bfloat16, fp8), Training auf großen GPU-Clustern
- Optimizer-Einstellungen (Lernrate, Weight Decay, Beta-Parameter, Warmup-/Decay-Schedules) müssen präzise abgestimmt werden; die richtigen Werte hängen von Modellgröße, Batch-Größe und Datensatz-Zusammensetzung ab
- Scaling Laws (z. B. Chinchilla) geben vor, wie ein festes Compute-Budget zwischen Modellgröße und Anzahl der Trainingstoken aufgeteilt werden sollte
- Werden diese Details im großen Maßstab falsch gesetzt, kann das Computing im Wert von Millionen Dollar verschwenden; Teams führen daher vor einem vollständigen Trainingslauf umfangreiche kleine Experimente durch
-
Post-Training
- Das aus dem Training kommende Basismodell (ein „vortrainiertes“ Modell) ist ein Dokument-Vervollständiger, kein Chatbot
- Der Weg zu ChatGPT besteht aus zwei Schritten:
- SFT (Supervised Fine-Tuning): Dokumente werden durch kuratierte Gespräche ersetzt und das Training fortgesetzt, ohne algorithmische Änderung
- RL (Reinforcement Learning): Das Modell erzeugt eine Antwort → es wird eine Bewertung vergeben (Menschen, „Judge“-Modelle, Algorithmen) → aus dem Feedback wird gelernt
- Grundsätzlich wird immer noch auf Dokumenten trainiert, aber nun bestehen die Dokumente aus vom Modell selbst erzeugten Token
-
Inferenz
- Um das Modell an Millionen Nutzer auszuliefern, ist ein eigener Engineering-Stack nötig: Request-Batching, Verwaltung und Paging des KV-Cache (z. B. vLLM), spekulatives Decoding für Geschwindigkeit, Quantisierung zur Speicherreduktion (Ausführung in int8/int4), Verteilung des Modells über mehrere GPUs
- Im Kern wird weiterhin das nächste Token einer Sequenz vorhergesagt, aber es fließt viel Aufwand in das Engineering, um das schneller zu machen
FAQ
-
Versteht das Modell etwas wirklich?
- Eine philosophische Frage, aber mechanisch betrachtet: Es geschieht nichts Magisches
- Das Modell ist eine große mathematische Funktion, die Eingabetoken auf eine Wahrscheinlichkeitsverteilung für das nächste Token abbildet
- Während des Trainings werden die Parameter so angepasst, dass das korrekte nächste Token wahrscheinlicher wird
- Ob das „Verstehen“ darstellt, ist Ansichtssache, aber der Mechanismus steckt vollständig in diesen 200 Zeilen
-
Warum funktioniert das?
- Das Modell hat Tausende anpassbare Parameter, und der Optimizer verschiebt sie bei jedem Schritt ein wenig so, dass der Loss sinkt
- Über viele Schritte stabilisieren sich die Parameter auf Werten, die statistische Regelmäßigkeiten in den Daten erfassen
- Bei Namen etwa: Sie beginnen oft mit einem Konsonanten, „qu“ tritt häufig zusammen auf, drei Konsonanten in Folge sind selten usw.
- Das Modell lernt keine expliziten Regeln, sondern eine Wahrscheinlichkeitsverteilung, die dies widerspiegelt
-
Was hat das mit ChatGPT zu tun?
- ChatGPT skaliert genau diese gleiche Kernschleife (Vorhersage des nächsten Tokens, Sampling, Wiederholen) massiv hoch und ergänzt Post-Training, um sie dialogfähig zu machen
- Beim Chatten sind System-Prompt, Nutzernachricht und Antwort allesamt einfach Token einer Sequenz
- Das Modell vervollständigt ein Dokument genauso wie microgpt Namen vervollständigt, nämlich ein Token nach dem anderen
-
Was sind „Halluzinationen“?
- Das Modell erzeugt Token, indem es aus einer Wahrscheinlichkeitsverteilung sampelt
- Es hat keinen Begriff von Wahrheit, sondern kennt nur Sequenzen, die im Licht der Trainingsdaten statistisch plausibel sind
- Wenn microgpt einen Namen wie „karia“ „halluziniert“, ist das dasselbe Phänomen wie wenn ChatGPT selbstbewusst falsche Fakten nennt
- Beides sind plausibel klingende Vervollständigungen, die nicht real sein müssen
-
Warum ist es so langsam?
- microgpt verarbeitet in reinem Python jeweils nur ein Skalar, und ein einzelner Trainingsschritt dauert mehrere Sekunden
- Auf GPUs läuft dieselbe Mathematik über Millionen Skalare parallel und dadurch um Größenordnungen schneller
-
Kann man es bessere Namen erzeugen lassen?
- Ja: länger trainieren (
num_stepserhöhen), das Modell größer machen (n_embd,n_layer,n_head), einen größeren Datensatz verwenden - Das sind dieselben Stellhebel, die auch im großen Maßstab wichtig sind
- Ja: länger trainieren (
-
Was passiert, wenn man den Datensatz ändert?
- Das Modell lernt jedes Muster, das in den Daten steckt
- Ersetzt man ihn durch Städtenamen, Pokémon-Namen, englische Wörter oder kurze Gedichte, lernt es stattdessen, genau diese zu erzeugen
- Der übrige Code muss nicht geändert werden
Noch keine Kommentare.