Training

Ein Transformer, trainiert an einem Dienstag

Nimm den Laptop, den du vor fünf Jahren gekauft hast. Öffne einen Tab. Trainiere darauf einen Transformer — nicht feintunen: trainieren, von zufällig initialisierten Gewichten bis zu Text, der meistens richtig buchstabiert, auf der GPU der Maschine selbst, das WLAN die meiste Zeit aus. Ein Zeichen-Modell, sechs Layer, rund 4,8 Millionen Parameter. Gehen wir den Abend gemeinsam durch, Fehler inklusive — denn an den Fehlern zeigt eine Plattform ihren Charakter.

Ein durchgerechnetes Beispiel

Das hier ist ein Walkthrough, kein Laborbericht. Scellis ist noch nicht für Nutzer geöffnet (wo wir stehen), diesen Abend hat also noch niemand erlebt — auch der Autor nicht. Die Rechnungen unten sind exakt: Parameterzahlen, Speicherbudgets, der Cross-Entropy-Startwert. Das Verhalten der Plattform ist das, wofür die Engine gebaut und verifiziert ist. Die Zeitangaben sind Schätzungen aus dem Design und stehen als solche da — gemessene Zahlen kommen, sobald es ein Release zu messen gibt.

Wenn du je eine Trainingsumgebung aufgesetzt hast — Treiber-Roulette, Dependency-Pinning, „läuft bei mir“ — dann ist die Pointe dieses Wegs, dass nichts davon passiert. Du öffnest einen Tab; die Engine spricht über WebGPU mit der GPU, und WebGPU bringt der Browser schon mit. Das war das Setup.

Den Stack skizzieren#

Ein Doppelklick auf einen Modell-Knoten öffnet Model View: einen Vollbild-Editor mit drei synchronisierten Projektionen desselben Modells — ein Graph zum Zeichnen, ein Formular zum Tippen und eine Code-Ansicht in SML, einer deklarativen, pythonisch angehauchten Schreibweise derselben Struktur. Änderst du eine, ziehen die anderen beiden sofort nach. Zeichne im Graph — Token-Embedding, gelernte Positionen, sechs Pre-Norm-Blöcke, ein gekoppelter Output-Head — und justiere die Breiten im Formular. Die Code-Ansicht schreibt sich darunter laufend neu, und um neun Uhr abends ist das seltsam gute Gesellschaft.

sml
# Code-Ansicht des Stacks (gekürzt)
model CharTransformer(context=256, vocab=96):
  d = 256; heads = 8; layers = 6

  x = token_embed(vocab, d)(tokens) + learned_pos(context, d)
  repeat layers:
    x = x + attention(norm(x), heads=heads, causal=true, dropout=0.1)
    x = x + mlp(norm(x), hidden=4 * d, activation="gelu", dropout=0.1)
  logits = head(norm(x), tied=token_embed)

Das context=256 ist die Zahl eines Überlebenden — der erste Entwurf sagt 512. Dazu kommen wir noch.

Der Draht wird rot, bevor du loslässt#

Der erste echte Fehler ist ein Klassiker, also machen wir ihn. Tippe die Hidden-Breite des MLP, 1024, auch in seine Output-Projektion — der Block will jetzt [B, T, 1024] an eine Residual-Addition zurückgeben, die [B, T, 256] erwartet. In einem Skript ist das ein Stacktrace beim ersten Run, zwanzig Minuten später. Hier wird der Draht rot, bevor du die Maus losgelassen hast. Die Validierung in Model View ist live; die Diagnose nennt beide Ports und beide Shapes und bietet die Reparatur als Ein-Klick-Quick-Fix an: Projektion zurück auf d_model. Du klickst — leicht beleidigt, überwiegend dankbar.

Das ist die stille These der ganzen Oberfläche: Ein Modell ist hier ein strukturiertes Dokument, das der Editor versteht, keine Textdatei, die erst zur Laufzeit scheitert. Shape-Fehler fliegen beim Erstellen auf, weil Shapes Teil des Vertrags sind — keine Überraschung mitten im Forward-Pass.

Serviettenrechnung: 4,8 Millionen Parameter#

Transformer-Parameterzahlen passen auf eine Serviette. Jeder Block trägt Attention (4d² für Q, K, V und die Output-Projektion) plus MLP (8d², für d→4d und zurück): zwölf d² pro Layer. Mit d = 256 sind das 786.432 Parameter pro Layer; sechs Layer ergeben 4.718.592. Das Token-Embedding addiert 96 × 256 ≈ 25k — mit dem Output-Head gekoppelt, also nur einmal gezählt —, gelernte Positionen weitere 256 × 256 = 65.536, Normen und Biases ein paar Tausend obendrauf. Rund 4,8 Millionen.

In f32 sind das etwa 19 MB Gewichte. Die Gewichte waren nie das Problem. Die Aktivierungen sind es.

Der Plan sagt Nein#

Verlange Kontext 512 und Batch 64, denn Ambition kostet nichts — bis kompiliert wird. Bevor irgendetwas läuft, plant die Engine den Run und beziffert seinen Speicher — Parameter, Gradienten, Optimizer-State, Aktivierungen, Arbeitsspeicher — und lehnt schlicht ab, wenn es nicht passt. Der Plan kommt rot zurück. Die Aufschlüsselung ist unmissverständlich: Aktivierungen dominieren, und darin die Attention-Puffer, die mit dem Quadrat des Kontexts wachsen. Bei Batch 64 und Kontext 512 sind allein die Attention-Matrizen 64 × 8 × 512 × 512 Floats pro Layer — ein halbes Gigabyte, mal sechs Layer, noch bevor irgendetwas für den Backward-Pass gesichert ist. Peak: über 6 GB. Das Budget auf so einer Maschine: rund 4.

Die Absage kommt mit ihren eigenen Auswegen: Kontext kürzen, Batch verkleinern, Präzision wechseln. Nimm alle drei. Kontext 256 — das Quadrat bringt dich um, also viertelt die Halbierung genau diese Puffer. Batch 32. Und Mixed Precision: Aktivierungen und Matmuls in f16, Master-Gewichte in f32, dazu ein zustandsbehafteter GradScaler fürs Loss-Scaling, weil f16-Gradienten sonst unterlaufen. Neu geplant: grün, Peak knapp unter 2 GB. Wäre es rot geblieben, läge noch Activation-Checkpointing in Reserve — neu rechnen statt speichern. Was bleibt: Der Speicher ist nie ein Rätsel. Du weißt, woher jeder große Puffer kommen wird, bevor ein einziges Byte fließt.

Zusehen, wie der Loss fällt#

Die Trainingskonfiguration wohnt ebenfalls in Model View, direkt neben der Architektur statt über Skripte verstreut: AdamW mit 3e-4, WarmupCosineLR, CrossEntropy, Gradient-Clipping bei 1.0, Dropout 0.1, Seed 1337, Determinismus-Stufe prod — reproduzierbar innerhalb einer deklarierten Toleranz auf diesem Gerät, der ehrliche Default; debug würde bitweise festnageln, gegen Durchsatz.

Der erste Tipp eines Zeichenmodells ist die Gleichverteilung über das Vokabular: Bei 96 Zeichen startet die Cross-Entropy bei ln 96 ≈ 4,56 Nats. Von dort fällt die Kurve durch das Warmup steil, und der gesampelte Text läuft durch die Stadien, die jeder kennt, der so etwas einmal trainiert hat: Rauschen mit den richtigen Zeichenhäufigkeiten, dann wortförmige Gebilde, am Ende ganze Satzteile, die fast alles richtig schreiben und fast nichts bedeuten. Irgendwann mittendrin fällt dir auf, dass das WLAN seit dem Abendessen aus ist. Es hat niemanden interessiert. Modell, Daten, Compiler, Run — alles local-first, diesseits des Netzes.

Und jetzt der ehrliche Teil. 12.000 Schritte bei Batch 32 und Kontext 256 sind rund 98 Millionen Token — das ist Arithmetik. Was das auf einer fünf Jahre alten GPU an Minuten kostet, ist eine Schätzung, und die soll auch so heißen: ein Abend, keine Kaffeepause. Gemessene Zeiten veröffentlichen wir, sobald es ein Release zu messen gibt, und keinen Tag früher. Präzise sagen lässt sich heute die Gestalt der Kosten: Training läuft auf dem eager Autograd-Tape — der Gradient ist korrekt, aber nicht fusioniert (Kernel-Fusion bedient derzeit den Inferenz-Forward). Niemand behauptet, ein Tab auf einem alten Laptop überhole eine gemietete Datacenter-GPU. Die Behauptung ist eine andere: kein Environment, kein Upload, kein laufender Zähler — und die Maschine, die dir längst gehört, erledigt das Ganze, während du Tee kochst. Das Run-Panel bietet sogar einen Wake Lock an, damit der Laptop nicht mitten in der Epoche einnickt.

Checkpoint, Commit, Zitat#

Der Checkpoint am Ende ist keine Gewichtsdatei, sondern ein Bündel — Gewichte, Optimizer-State, RNG-State, Epoche, Schritt, Metriken — als eine Einheit content-addressed. Wiederaufnahme fällt aus dieser Struktur einfach heraus: Stirbt der Tab bei Schritt 9.000, wird der Run als „unterbrochen“ abgeglichen, mit Ein-Klick-Resume vom letzten Checkpoint — und weil der RNG-State mit drinsteckt, machen Shuffle und Dropout exakt dort weiter, wo sie waren.

Dann committest du den Run. Eine committete Version ist content-addressed — der Hash deckt Workflow, Modell, Datensatz-Version, Trainingskonfiguration und Checkpoint ab, bis hin zur Engine-Version — und die Snapshot-URL löst genau diese Bytes wieder auf, für jeden, ohne Login. Die präzise Aussage zur Reproduzierbarkeit lohnt die Sorgfalt, denn genau sie weigert sich diese Plattform aufzublasen: Die zitierten Bytes sind exakt, für immer; eine erneute Ausführung auf anderer Hardware stimmt innerhalb der deklarierten Toleranzen der prod-Stufe überein, nicht bitweise — Floats auf heterogenen GPUs können bitgenau nicht, und die Plattform sagt das, statt aufzurunden.

Das war der Abend. Ein Shape-Fehler, ein roter Plan, ein grüner, eine fallende Loss-Kurve — und am Ende eine URL, die das Experiment ist statt ein Bild davon. Denselben Weg, Schritt für Schritt und mit weniger dieser Fehler, führt der Guide unter Training im Browser. Ein Dienstag ist dafür nicht erforderlich.

  • training
  • transformer
  • webgpu
  • model-view
  • mixed-precision

← Alle Beiträge