2 Punkte von GN⁺ 2023-09-24 | 1 Kommentare | Auf WhatsApp teilen
  • Ein Experiment, das einen decoder-only Transformer ähnlich wie GPT-2 erstellt, indem die Gewichte ohne Training direkt von Hand festgelegt werden, damit er das Muster (aab)* vorhersagt
  • Die Vorhersage von "aabaabaabaab..." muss die zwei vorherigen Tokens betrachten und macht das Verhalten von Attention dadurch besser sichtbar als ein einfaches alternierendes Muster
  • Das Modell ist klein mit N_CTX=5, N_VOCAB=2, N_EMBED=8, verwendet die Tokenisierung a=0, b=1 und One-Hot-Embeddings
  • Ein einzelner Attention-Head verteilt je 0,5 Aufmerksamkeit auf die letzten zwei Tokens und berechnet das nächste Token mithilfe der Aufhebung in der Kodierung a=1, b=-1
  • In nicht mehrdeutigen Kontexten erreicht es 100,0 % (27/27) Genauigkeit, benötigt aber für eine Vorhersage mit 5-Token-Kontext etwa 4.000 FLOPs und ist damit weit ineffizienter als eine direkt implementierte Regel

Mini-GPT-2 mit manuell gesetzten Gewichten ohne Training

  • Das Ziel ist, intuitiv zu verstehen, was die einzelnen Bestandteile von Transformer und Attention tatsächlich tun
  • Das Modell wird nicht trainiert und nutzt auch keine vortrainierten Gewichte, sondern entsteht dadurch, dass an einem Abend jedes Gewicht direkt von Hand festgelegt wird
  • Die Struktur ist ein decoder-only Transformer ähnlich wie GPT-2, und die Implementierung ist eine vereinfachte Variante auf Basis der picoGPT-Implementierung von jaymody
    • Layer Normalization entfernt
    • ein einzelner Head statt Multi-Head-Attention
    • die mlp-Feed-Forward-Layer des Transformer-Blocks entfernt

Warum die Sequenz (aab)* gewählt wurde

  • Zunächst sollte eine Sequenz wie "ababababab" vorhergesagt werden, aber weil der Transformer eine verschobene Sequenz vorhersagt, wäre das zu einfach gewesen
    • Wenn a, dann b, sonst a vorhersagen reicht aus, ohne Positions-Embeddings zu benötigen
  • Die endgültige Aufgabe wurde die Vorhersage von "aabaabaabaab...", also der Sequenz (aab)*
    • Wenn die beiden vorherigen Tokens ab oder ba sind, ist das nächste Token a
    • Wenn die beiden vorherigen Tokens aa sind, ist das nächste Token b
    • bb wird als Fall außerhalb des Aufgabenbereichs behandelt
  • Die Tokenisierung ist einfach und behandelt nur zwei Symbole
    • a ist 0
    • b ist 1

Modelldimensionen und Rechenablauf

  • Es wurden drei Modellparameter gewählt
    • N_CTX = 5: maximale Kontextlänge, die das Modell auf einmal sieht
    • N_VOCAB = 2: die zwei Tokens a und b
    • N_EMBED = 8: Embedding-Größe für Token, Position und Rechenraum
  • Für die eigentliche Aufgabe werden zwar nur die zwei vorherigen Tokens benötigt, aber N_CTX=5 schließt absichtlich den Fall ein, irrelevante Tokens ignorieren zu müssen
  • Die Funktion gpt arbeitet in folgender Reihenfolge
    • Sie addiert Token-Embedding und Positions-Embedding mit wte[inputs] + wpe[range(len(inputs))]
    • Sie durchläuft einen Transformer-Block
    • Am Ende erzeugt x @ wte.T die Logits im Vokabularraum

Embeddings mit One-Hot für Position und Token

  • wpe ist das Positions-Embedding; die ersten 5 Embedding-Dimensionen werden als One-Hot-Positionen verwendet
    • Position 0 ist [1, 0, 0, 0, 0]
    • Position 4 ist [0, 0, 0, 0, 1]
  • wte ist das Token-Embedding; die nächsten 2 Dimensionen werden als One-Hot-Token verwendet
    • Token a ist in den Token-Dimensionen [1, 0]
    • Token b ist [0, 1]
  • Die 8. Embedding-Position wird anfangs nicht benutzt und dient innerhalb des Transformer-Blocks als Scratch Space
  • Zum Beispiel wird "aabaa" als 5 x 8-Embedding-Matrix dargestellt, wobei jede Zeile One-Hot für Position und Token gemeinsam enthält

Wie der Attention-Head die letzten zwei Tokens auswählt

  • Der Transformer-Block besteht aus einem Attention-Head und einer linearen c_proj-Schicht, die das Attention-Ergebnis zurück in den Embedding-Raum projiziert
  • c_attn ist eine lineare Schicht der Größe embed_size x (embed_size * 3), die das Eingabe-Embedding in eine qkv-Matrix umwandelt und dann in q, k, v aufteilt
  • k trennt die Positions-Embeddings heraus und repräsentiert damit die Positionsinformation jedes Tokens
  • q beschreibt für jede Position den gesuchten Positionsbereich; mit q @ k.T entsteht die Matrix der Attention-Scores
  • Nach Softmax und Causal Mask hat die Attention-Matrix folgende Eigenschaften
    • Die erste Zeile richtet 100 % Attention nur auf das erste Token
    • Die folgenden Zeilen richten jeweils 0.5 Attention auf die zwei zuletzt erreichbaren Tokens
  • Die Causal Mask fügt an zukünftigen Token-Positionen einen sehr kleinen Wert hinzu, im eigentlichen Code etwas wie -1e10, sodass keine zukünftigen Tokens gesehen werden können
    • Dieses handgebaute Modell ist zwar nicht darauf ausgelegt, in die Zukunft zu spähen, aber die Maske bleibt erhalten, um näher an der GPT-2-Struktur zu bleiben
  • Das Scaling durch Division mit np.sqrt(q.shape[-1]) hilft im echten Training bei besseren Gradienten, hat auf dieses handgefertigte Modell aber keinen Einfluss

Vorhersage durch v-Kodierung und additive Aufhebung

  • v wandelt das One-Hot-Token in die Kodierung a=1, b=-1 um
  • Weil das Attention-Ergebnis die letzten zwei Tokens mit je 0,5 mittelt, berechnet diese Kodierung die folgende Regel
    • a, b0.5 * 1 + 0.5 * (-1) = 0
    • b, a0.5 * (-1) + 0.5 * 1 = 0
    • a, a0.5 * 1 + 0.5 * 1 = 1
  • Dadurch entsteht an der 7. Position jeder Zeile der folgende Wert
    • 0, wenn a vorhergesagt werden soll
    • 1, wenn b vorhergesagt werden soll
  • Beim Eingabekontext "aabaa" kann die erste Zeile wegen fehlender Information eine Vorhersage b erzeugen, aber die folgenden Vorhersagen stimmen mit der Regel (aab)* überein

Die Vorhersage zurück in den Vokabularraum schicken

  • c_proj wandelt den Wert an der 7. Position des Attention-Ergebnisses zurück in das One-Hot-Format der Tokens
  • Es erzeugt nicht einfach [..., 1, 0, ...] oder [..., 0, 1, ...], sondern ein mit 1024 skaliertes One-Hot
    • embedding[row, 5] = 1024 + (-1024) * prediction
    • embedding[row, 6] = 0 + 1024 * prediction
  • Der Transformer-Block hat eine Residual Connection, also x = x + causal_self_attention(...), wodurch das ursprüngliche Embedding addiert wird
  • Weil dieses Residualsignal unnötig erhalten bleibt, wird die Skalierung 1024 verwendet, um es zu überdecken
  • Schließlich werden mit x @ wte.T die Logits berechnet und Softmax angewendet
    • Im Kontext "aabaa" zeigt die letzte Vorhersagezeile auf b
    • Während des Trainings sind Vorhersagen für alle Zeilen nützlich, bei der Inferenz wird aber nur die letzte Zeile benötigt

Generierungsergebnisse und Genauigkeit

  • Die Funktion complete gibt die letzten maximal 5 Tokens in das Modell und wählt das nächste Token mit argmax aus der letzten Zeile des Softmax-Ergebnisses
  • Beispielhafte Generierungsergebnisse sind
    • complete("a")a :: baabaabaab
    • complete("ba")ba :: abaabaabaa
    • complete("abaab")abaab :: aabaabaaba
  • Auch bei Eingaben außerhalb des Aufgabenbereichs stellt sich teils wieder ein Wiederholmuster ein
    • complete("ababa")ababa :: abaabaabaa
    • complete("bbbbb")bbbbb :: aabaabaaba
  • Im Test mit "aab" * 10 beträgt die Genauigkeit 100,0 % (27/27), wenn nur nicht mehrdeutige Kontexte bewertet werden

4.000 FLOPs versus 8 Instruktionen

  • Wenn der gesamte 5-Token-Kontext genutzt wird, benötigt dieses Modell für die Vorhersage eines einzelnen Tokens etwa 4.000 Floating-Point-Operationen
    • Der Großteil entfällt auf die Attention-Berechnung
    • Das lässt sich durch ein kleineres Kontextfenster, fused multiply-add, KV-Caching usw. reduzieren
    • Trotzdem werden für die Vorhersage eines einzelnen Tokens noch Hunderte Maschineninstruktionen benötigt
  • Dieselbe (aab)*-Regel kann in handgeschriebenem x64-Assembler das nächste Token mit 8 Instruktionen berechnen
  • Offen bleibt die Frage, ob sich für natürliche Sprachgenerierung ein Sprachmodell trainieren lässt, das 1000-mal effizienter ist als heutige Modelle

1 Kommentare

 
GN⁺ 2023-09-24
Meinungen auf Hacker News
  • Als verwandte Arbeit gibt es „Thinking Like Transformers“.
    Darin wird eine primitive Programmiersprache namens RASP vorgestellt, die aus Operationen besteht, die sich mit Transformer-Komponenten modellieren lassen, und es wird gezeigt, dass man damit Programme wie Histogramme oder Sortierungen schreiben kann.
    Außerdem gibt es einen hervorragenden Blogbeitrag von Sasha Rush und Gail Weiss; Folgearbeiten haben gezeigt, dass sich RASP-artige Programme ohne Training in echte Modellgewichte kompilieren lassen.
    [1] https://arxiv.org/abs/2106.06981
    [2] https://srush.github.io/raspy/
    [3] https://arxiv.org/abs/2301.05062

    • Ich mag die RASP-Familie wirklich sehr.
      Wenn dieses Gebiet spannend klingt, lohnt sich vielleicht auch ein Blick auf meine Arbeit HandCrafted Transformers, bei der ich die Gewichte eines Transformer-Modells von Hand ausgewählt habe, damit es schriftliche Addition ausführt – ungefähr so, wie Menschen es in der Grundschule lernen.
      [1] https://colab.research.google.com/github/newhouseb/handcraft...
    • Für solche Arbeiten scheinen funktionale Sprachen wie Haskell gut geeignet zu sein.
      Auch die Richtung vom neuronalen Netz hin zu Code dürfte unter dem Aspekt der Erklärbarkeit sehr interessant sein.
  • Ich dachte, ich würde Transformer gut verstehen, hatte aber nie selbst einen implementiert.
    Als ich eines Tages selbst einen implementierte, funktionierte oder trainierte er nicht so gut wie der standardmäßige PyTorch Transformer; am Ende merkte ich, dass ich Dropout ignoriert hatte.
    Ich hatte ihn auf Zahlenaddition trainiert und nie dasselbe Paar zweimal gezeigt, also dachte ich, Overfitting sei unmöglich – aber die Rolle von Dropout war deutlich größer, als ich erwartet hatte.
    Kurz gesagt: Es lohnt sich, einfach selbst einen Transformer zu implementieren, und je mehr von Grund auf, desto besser.
    Alle, die das gemacht haben, haben etwas Unerwartetes gelernt; je nach Person reichte das von der Parallelisierung des tokenweisen Trainings bis dazu, wie Backpropagation tatsächlich funktioniert.

    • Ich frage mich, ob es Literaturhinweise gibt, die beim Einstieg in diese Arbeit helfen könnten.
  • Die Materialien von Karpathy sind auch gut, aber dieses Video hat bei mir den Knoten platzen lassen, was Transformer angeht: https://youtu.be/kWLed8o5M2Y?si=SJT5_lCJ0hSR7Z_k

  • Ich habe schon eine Weile über etwas Ähnliches nachgedacht.
    Könnte man eine intuitive Schnittstelle zu Modellgewichten bauen, über die Domain-Experten von Hand Anpassungen vornehmen und so das Training beschleunigen können?
    Zum Beispiel könnte man bei einem Vision-Modell zur Erkennung von Verkehrskegeln ein Bündel von Gewichten hochdrehen, das „Orangehaftigkeit“ entspricht.
    Dann könnte ein Mensch beschleunigend eingreifen, statt Tausende oder Millionen weiterer Beispiele zu verlangen, nur um „Orangehaftigkeit“ richtig zu kalibrieren.
    Die Schwierigkeit ist natürlich, dass diese Schnittstelle auf Gewichtsbündel mit unterschiedlichen Bedeutungen abgebildet werden müsste; ich frage mich, ob es technische Gründe gibt, warum das unmöglich ist.

    • Dass „Domain-Experten die Gewichte eines Modells von Hand anpassen“, klingt ähnlich wie die Bilderkennung vor dem Deep Learning.
      [1] https://www.youtube.com/watch?v=8SF_h3xF3cE&t=1358s
    • Der Grund, nach dem du suchst, heißt The Bitter Lesson.
      Kurz gesagt: Menschliche Hilfe bei KI ist fast immer weniger kosteneffizient, als einfach mehr Rechenleistung darauf loszulassen.
      Während ein Mensch eine Gewichtsschicht so kalibriert, dass orangefarbene Verkehrskegel erkannt werden, trainiert der GPU-Cluster die KI bereits darauf, Verkehrskegel, Ampeln, Bäume, andere Autos und sogar Verkehrskegel in leicht anderen Orangetönen zu erkennen.
    • Die Zahl der Schichten und Gewichte liegt nicht in einer Größenordnung, die Menschen manuell aktualisieren könnten; und selbst wenn das ginge, wären die nachgelagerten Effekte einer Gewichtsänderung viel zu schwer zu kontrollieren.
      Selbst wenn man das Modell so anpasst, dass es Orange besser sieht, kann man, wenn man nicht gleichzeitig die Genauigkeit bei allen anderen Farben überwacht, unbemerkt Probleme bei anderen Farben erzeugen.
    • Der technische Grund, warum das unmöglich oder sehr schwierig ist: Gewichte sind normalerweise sehr schwer zu interpretieren.
      Es ist nicht so, dass ein bestimmter Neuronen-Cluster einem bestimmten Konzept entspricht; vielmehr macht im Großen und Ganzen alles ein bisschen von allem.
    • Der Attention-Mechanismus von Transformern scheint sich nicht leicht auf für Menschen verständliche Semantik abbilden zu lassen.
      Es sind zu viele Parameter beteiligt.
  • Das Transformer-Paper war so technisch, dass ich es immer zumindest oberflächlich verstehen wollte, es aber schwierig fand.
    Dieser Artikel hat mir wirklich geholfen, die Funktionsweise zu verstehen, und zumindest die Beispiele waren sehr klar.
    Dadurch konnte ich mich auch wieder an die Matrizen erinnern, die ich an der Uni gelernt hatte.

  • Ist das nicht eine Art abstrakte Maschine, wie eine Turing-Maschine oder eine Maschine, die reguläre Ausdrücke parst?

    • Etwas vereinfacht: Es ist eine „Maschine“, die eine Eingabemenge auf eine Wahrscheinlichkeitsmenge für die nächste Ausgabe abbildet.
      Zuerst definiert man eine Liste von Token; sagen wir der Einfachheit halber zum Beispiel 24 Zeichen.
      Diese Maschine nimmt eine Eingabesequenz von Token, führt deterministische Matrixoperationen aus und gibt anschließend eine Wahrscheinlichkeitsliste für alle Token aus.
      „Training“ ist lediglich der Prozess, einige Zahlen in den Matrizen festzulegen, die bei diesen Operationen verwendet werden.
      Bemerkenswert ist, dass der finale Code nur eine einzige if-Anweisung enthält, und selbst die dient dazu, die Genauigkeit des Ergebnisses zu bewerten.
      Die gesamte „Logik“ entsteht aus dem Ergebnis von Matrixoperationen.
    • Es ist ziemlich schwierig, solche Dinge als Automaten in dem Sinn zu interpretieren, den man üblicherweise im Kopf hat.
      In neuronalen Netzen ist im Allgemeinen alles etwas unscharf, und Dinge wie if/else gibt es kaum, auch wenn in Beispielen wie Transformern Werte auf 0 oder -∞ „maskiert“ werden.
      Auch die Ausgabe ist fast immer ein Bündel von Scores oder Wahrscheinlichkeiten; wenn ein Modell, das Katzen- und Hundefotos unterscheidet, etwas wie dog:0.95 cat:0.05 ausgibt, sagt man, es habe einen Hund vorhergesagt, weil der Hund-Score höher ist.
      Der Attention-Mechanismus, der Kern des Transformers, basiert auf einer Art weicher Lookup-Operation.
      In einem nicht-unscharfen System würde man über jedes Token der Sequenz iterieren, prüfen, ob es mit dem aktuellen Token zusammenhängt, und bei Relevanz eine bestimmte Aktion ausführen; im Transformer ist Relevanz jedoch keine binäre Entscheidung.
      Stattdessen berechnet er kontinuierliche Relevanz-Scores zwischen allen Token-Paaren in der Sequenz und nutzt diese Scores für den nächsten Schritt.
      Allerdings lassen sich manche Dinge nicht direkt aus Systemen verallgemeinern, die auf binären Entscheidungen beruhen.
      Zum Beispiel werden solche Relevanz-Scores als Gewichte verwendet, um einen gewichteten Durchschnitt über Vokabular-Token zu berechnen, wodurch man für die aktuelle Position ein „Durchschnitts-Token“ erhält.
      Ich sehe keinen einfachen Weg, das als Erweiterung eines auf Verzweigungslogik basierenden Prozesses zu interpretieren.
    • Also ist das ein Haufen linearer Algebra, den der AllSpark berührt hat?
    • Genau.
      Dieses Paper, das erklärt, dass Linear Transformers eigentlich Fast Weight Programmers sind, ist dazu lesenswert: https://arxiv.org/abs/2102.11174
    • Neuronale Netze sind Turing-Maschinen.
      Wenn man die Gewichte sorgfältig setzt, kann man sie dazu bringen, beliebige Berechnungen auszuführen.
      Allerdings wäre ein Compiler schön, der nicht auf Approximation basiert.
  • Ich frage mich, wofür der Satz „man könnte Lust bekommen, selbst ein Modell zu bauen“ gedacht ist, außer als Lernübung zur Befriedigung der Neugier.
    Komplexe Machine-Learning-Modelle wirken langsam unrealistisch für jemanden, der zu Hause Blogbeiträge liest.

    • Bei nanoGPT erreicht ein auf Shakespeare vortrainiertes Modell in 3 Minuten auf dem Ausgangsmaterial eine Treue auf dem Niveau von Lewis Carrolls Jabberwocky.
      Es erzeugt viele plausibel wirkende altenglische Wörter und lernt die Grundlagen der englischen Grammatik sowie das Format von Theaterstücken usw.
      Ich fand es ziemlich erstaunlich, in so kurzer Zeit so weit zu kommen.
      Wenn man lokal mehrere Modelle auf ein Treue-Niveau von Shakespeare-from-Wish.com trainiert, dürfte das helfen zu beurteilen, ob man eine gute Architektur gefunden hat und ob es Zeit ist, sie zu skalieren.
    • Der Zweck steht im ersten Absatz des Artikels.
      Dort steht, dass der Autor Transformer und Attention besser verstehen wollte und zwar The Illustrated Transformer gelesen hatte, aber nicht intuitiv nachvollziehen konnte, was die einzelnen Teile von Attention tatsächlich tun.
      Etwa der Unterschied zwischen q und k, ganz zu schweigen von v.
    • Eine hervorragende Lernübung.
      Sie geht über das bloße Befriedigen von Neugier hinaus und hilft, Verständnis aufzubauen und zu vertiefen.
    • Vielleicht macht es jemandem einfach tatsächlich Spaß, an so einem Projekt herumzuhacken? Seltsam, aber möglich.
  • Es wäre gut, wenn im Titel ein Ausdruck wie neural network vorkäme.
    Hier geht es um die „Transformer“-Architektur im Machine Learning, nicht um ein Spulenbündel, das zwei Schaltungen elektromagnetisch koppelt.