- Nur mit PostgreSQL und pgvector wurde der GPT-2-Inferenzablauf in SQL übertragen und von der Tokenisierung über Transformer-Blöcke bis zur Generierung des nächsten Tokens nachgebildet
- Generative LLMs sind eher eine deterministische Funktion, die für dieselbe Eingabe dieselben Wahrscheinlichkeiten für Kandidaten-Tokens liefert; unterschiedlich werden Antworten erst im probabilistischen Auswahlschritt für das nächste Token
- Die Implementierung bildet den BPE-Tokenizer von GPT-2, 50257 Tokens, 768-dimensionale Embeddings, 1024 Tokens Kontext, 12 Blöcke, 12 Attention-Heads und ein GELU-basiertes Feedforward-Netz mit SQL-Abfragen und Tabellen ab
- Wegen Einschränkungen wie fehlender Unicode-Eigenschaften in PostgreSQL-Regulärausdrücken und Grenzen von
EXPbei sehr kleinen Werten waren praxisnahe Workarounds für die Datenbankumgebung nötig - Im Beispiel wurden aus
"Happy New Year! I wish you"10 Tokens erzeugt, mit der Ausgabe"Happy New Year! I wish you all the best in your new year!"; in der Umgebung des Autors dauerte das 2 Minuten 44 Sekunden
Aufbau einer GPT-2-Inferenzpipeline in SQL
- ChatGPT antwortete, SQL sei nicht geeignet, um ein großes Sprachmodell zu implementieren, aber hier wurde eine GPT-2-Inferenzpipeline in PostgreSQL-SQL umgesetzt
- Als Referenz diente Jay Modys GPT in 60 Lines of NumPy; die gleichen Bausteine wurden in Datenbanktabellen und Abfragen übertragen
- Ein generatives LLM lässt sich wie eine Funktion der Form
llm(prompt: str) -> list[tuple[str, float]]betrachten- Die Eingabe ist ein Text-Prompt
- Die Ausgabe ist ein Array aus möglichen nächsten Strings und ihren Wahrscheinlichkeiten
- Wenn interne Mathematik und Parameter gleich sind, liefert dieselbe Eingabe dasselbe Ergebnis
- Dass Produkte wie ChatGPT auf dieselbe Frage unterschiedliche Antworten geben können, liegt weniger am Modell selbst als an der probabilistischen Auswahl des nächsten Tokens
Schleife zur Textgenerierung
- Im Generierungsprozess wird ein Prompt zuerst in ein Token-Array umgewandelt, dann das Modell wiederholt aufgerufen, das nächste Token gewählt und an den Prompt angehängt
- Der grundlegende Ablauf besteht aus folgenden Schritten
tokenize(prompt)wandelt den String in ein Array von Token-IDs umgpt2(tokens)berechnet Wahrscheinlichkeiten für 50257 Tokensselect_next_token(candidates)wählt das nächste Token aus- Das gewählte Token wird dem Array hinzugefügt
- Bei Bedingungen wie festgelegter Token-Zahl, Timeout oder Stopword wird abgebrochen
detokenize(tokens)stellt aus dem Token-Array wieder einen String her
- Die so aufaddierte Token-Sequenz kann zu natürlichem Text werden, der Eigenschaften wie Grammatik, Syntax, Bedeutung und scheinbares Schließen zeigt
BPE-Tokenizer in SQL implementieren
- Vor der Eingabe ins neuronale Netz muss Text in eine Zahlenliste umgewandelt werden; rohe Unicode-Codepoints wären dabei für Token-Raum und Länge ineffizient
- GPT-2 verwendet eine Variante von Byte pair encoding
- Das Token-Vokabular verwendet 50257 Codepoints
- Es enthält UTF-8-Byte-Sequenzen und ein „end of text“-Token
- Zunächst wird mit 256 Byte-Tokens begonnen, dann werden häufig auftretende benachbarte Token-Paare als neue Tokens hinzugefügt
- Diese Zusammenführung wird 50000-mal wiederholt, sodass 50256 Tokens entstehen; anschließend wird das End-of-Text-Token hinzugefügt
- Der GPT-2-Tokenizer hat eine zusätzliche Ebene, die Bytes auf String-Zeichen abbildet; diese Zuordnung ist in OpenAIs GPT-2-
encoder.pydefiniert - In der SQL-Implementierung wurde das von OpenAI heruntergeladene Token-Vokabular in die Tabelle
tokenizergeladen und die Byte-Zeichen-Zuordnung in der Tabelleencodergespeichert - Im Beispiel
"Mississippilessly"wird mit einer rekursiven CTE bei einzelnen Bytes begonnen und dann wiederholt das beste zusammenführbare Nachbarpaar verschmolzen- Dabei sinkt die Token-Zahl im Beispiel von 17 auf 5
- Statt des Unicode-Raums mit rund 150k Codepoints wird der GPT-2-Token-Raum mit rund 50k Tokens genutzt
- Bei mehreren Wörtern zerlegt GPT-2 den Text per Regulärem Ausdruck und führt die Merges innerhalb jedes Wortes aus
- PostgreSQL unterstützt in Regulären Ausdrücken keine Unicode-Zeicheneigenschaften, daher wurde der ursprüngliche GPT-2-RegEx teilweise angepasst
- Diese Änderung könnte die korrekte Unicode-Unterstützung beeinträchtigt haben
"PostgreSQL is great"wird im SQL-Tokenizer zu[6307, 47701, 318, 1049]- Die Token-Cluster sind
Post,greSQL,Ġis,Ġgreat Ġsteht für ein Leerzeichen
- Die Token-Cluster sind
Embeddings und Kontextfenster
- Token-IDs werden nicht direkt in den Modellberechnungen verwendet, sondern in Embedding-Vektoren umgewandelt
- GPT-2 verwendet getrennte Embeddings für Tokens und Positionen
WTEist das Word-Token-Embedding und eine 50257×768-MatrixWPEist das Word-Position-Embedding und eine 1024×768-Matrix
- Für jede Token-Position werden der
WTE-Vektor und derWPE-Vektor addiert und bilden so den Eingabevektor für den nächsten Schritt - Weil
WPEnur 1024 Positionen hat, können in einem GPT-2-Prompt maximal 1024 Tokens verwendet werden- Diese Zahl entspricht dem Kontextfenster des LLM
- Sie ist ein beim Modelldesign festgelegter Hyperparameter und ändert sich nicht durch Training
- Die SQL-Implementierung verwendet pgvector
- Vektoroperationen könnten auch direkt auf Arrays in reinem SQL definiert werden, wären dann aber langsamer
- Eine frühe Version funktionierte mit reinen SQL-Funktionen, war jedoch langsam
Self-Attention als SQL-Abfragen entfalten
- Der Kern des Transformers ist der Self-Attention-Mechanismus auf Basis der Arbeit Attention is all you need von 2017
- Attention sorgt dafür, dass Token-Vektoren einander beeinflussen, sodass Informationen vom Anfang des Prompts bis zum letzten Vektor weitergegeben werden
- Die GPT-2-Implementierung verwendet 12 Sätze von
Q-,K- undV-Matrizen- Jeder Satz ist ein Attention-Head
- Jeder Head ist 64-dimensional
c_attnist eine lineare Transformation 768×2304; das Ergebnis ist ein 2304-dimensionaler Vektor, in demQ,KundVhorizontal gestapelt sind- Gewichte und Biases liegen in den Tabellen
c_attn_wundc_attn_b
- Vor der Attention-Berechnung wird Layer Normalization angewendet
- Scale- und Shift-Parameter liegen in den Tabellen
ln_1_gundln_1_b
- Scale- und Shift-Parameter liegen in den Tabellen
- Bei der kausalen Self-Attention wird eine kausale Maske verwendet, damit spätere Tokens frühere Tokens nicht beeinflussen können
- Die Kandidaten für das nächste Token werden letztlich aus dem letzten Embedding bestimmt
- Der Informationsfluss muss in Richtung des letzten Vektors laufen, und Zwischenwerte des letzten Vektors dürfen frühere Vektoren nicht beeinflussen
- In der SQL-Implementierung werden bei der Softmax-Berechnung Eingaben kleiner als -745.13 als 0 behandelt, um zu vermeiden, dass PostgreSQL-
EXPbei sehr kleinen Zahlen scheitert - Dank der kausalen Maske ändern sich die Berechnungsergebnisse für frühere Tokens nicht, wenn ein neues Token an den Prompt angehängt wird
- Die originale GPT-2-Implementierung nutzt diese Eigenschaft aus
- Die SQL-Implementierung verzichtet der Einfachheit halber auf diese Wiederverwendung
Multi-Head-Attention und Residualverbindungen
- Die Attention-Ergebnisse der 12 Heads sind jeweils 64-dimensional; horizontal gestapelt ergeben sie wieder 768 Dimensionen
- Anschließend wird die Attention-Ausgabe mit der gelernten linearen Transformation aus
c_proj_wundc_proj_bprojiziert - Zum Ergebnis der Multi-Head-Attention wird die ursprüngliche Eingabe wieder addiert
- Diese Residualverbindung ist ein Verfahren aus dem ursprünglichen Transformer-Paper
- Sie wurde eingeführt, um beim Training Probleme wie Vanishing und Exploding Gradients abzumildern
Feedforward-Schritt und Transformer-Block
- Auf die Attention folgt ein Feedforward-Neuralnetz
- Der Feedforward-Schritt von GPT-2 besteht aus einem dreischichtigen Multi-Layer-Perceptron
- Die Dimensionen sind 768 → 3072 → 768
- Als Aktivierungsfunktion wird GELU verwendet
- Die Parameter der linearen Transformationen sind in folgenden Tabellen gespeichert
mlp_c_fc_w,mlp_c_fc_bmlp_c_proj_w,mlp_c_proj_b
- Auch die Feedforward-Eingabe wird zunächst mit den
ln_2-Parametern normalisiertln_2_gundln_2_bspeichern Scale und Shift
- Auch auf die Feedforward-Ausgabe wird eine Residualverbindung angewendet, bei der die Eingabe erneut addiert wird
- Diese Kombination aus Attention und Feedforward bildet einen Block, und GPT-2 verbindet 12 solcher Blöcke wie eine Pipeline
- Jeder Block besitzt seinen eigenen Satz gelernter Parameter
- In SQL werden die Blöcke mit einer rekursiven CTE verbunden
- Die Ausgabe des letzten Blocks wird erneut mit den
ln_f-Parametern normalisiert
Rückübersetzung zum nächsten Token
- In der finalen Ausgabe ist der 768-dimensionale Vektor an der letzten Position das Embedding des nächsten Tokens
- Um diesen Vektor wieder in ein Token zurückzuführen, wird erneut die
WTE-Matrix verwendet, die auch für das anfängliche Token-Embedding genutzt wurde - Eine exakte Rücktransformation ist meist nicht möglich
- Das vorhergesagte Embedding muss nicht exakt einer bestimmten Zeile in
WTEentsprechen - Deshalb wird das Dot Product mit jedem Token-Embedding berechnet, um nahe Tokens zu finden
- Das vorhergesagte Embedding muss nicht exakt einer bestimmten Zeile in
- Die Dot Products von
WTEund dem vorhergesagten Embedding ergeben 50257 Scores, also Logits - Diese Scores werden per Softmax in Wahrscheinlichkeiten umgewandelt
- Die Zahl der Top-Kandidaten ist
top_n - Der Wert zur Steuerung der Wahrscheinlichkeitsverteilung ist die Temperature
- Je höher die Temperature, desto wahrscheinlicher wird die Auswahl eines nicht erstplatzierten Tokens und desto weniger vorhersagbar wird die Inferenz
- Die Zahl der Top-Kandidaten ist
- Im Beispiel
"PostgreSQL is great"sind die Top-5-Kandidaten für das nächste Token:Ġfor,.ĠatĠto
- Wenn die Temperature auf 0.5, 1 oder 2 geändert wird, verändert sich die Softmax-Verteilung derselben Kandidaten entsprechend
Reale Inferenz-Ergebnisse und Code
- Das finale SQL wiederholt den Ablauf, Tokens entsprechend ihren Wahrscheinlichkeiten auszuwählen und an den Prompt anzuhängen
- Das Modell selbst ist deterministisch; der nichtdeterministische Anteil steckt nur im Zufall bei der Token-Auswahl
- Die Beispielkonfiguration ist wie folgt
- Prompt:
"Happy New Year! I wish you" - Anzahl generierter Tokens: 10
- Temperature: 2
- top_n: 1
- Verwendung von
SETSEED(0.20231231)
- Prompt:
- In der Umgebung des Autors lief die Abfrage 2 Minuten 44 Sekunden
- Die Ausgabe lautete
"Happy New Year! I wish you all the best in your new year!" - Die Query und der Installationscode stehen im GitHub-Repository quassnoi/explain-extended-2024
1 Kommentare
Meinungen auf Hacker News
Wunderschön. Ich habe mich selbst in einem ähnlichen Kaninchenbau mit SQLite vergraben, bin aber noch nicht so weit gekommen, neuronale Netze hineinzuziehen.
Inspiriert hat mich die makemore-Vorlesungsreihe[0]; nach etwa einer Stunde geht sie vom Counting-Ansatz zu neuronalen Netzen über, und ungefähr bis dorthin bin ich gekommen.
Das in ein relationales Modell zu zerlegen, ist eine wirklich gute Übung.
[0] https://www.youtube.com/watch?v=PaCmpygFfXo
Gute Demo, aber die Erklärung zu kausalem Masking scheint im Artikel Training und Inferenz zu vermischen.
Kausales Masking dient beim Training dazu, zu verhindern, dass das Modell zukünftige Tokens „erspäht“, und in GPT-artigen Architekturen auch dazu, bei der Inferenz die Autoregressivität zu erzwingen.
Bei der Inferenz wird ohnehin nur das letzte Token verwendet; dieses Token richtet seine Aufmerksamkeit auf die gesamte Eingabesequenz. Daher wird das nächste Token nicht nur aus dem Embedding des letzten Tokens bestimmt.
Ich frage mich, ob das die Driver-Loop von GPT korrekt abbildet: Es sieht nach einer Struktur aus, bei der der Prompt tokenisiert wird, man mit
gpt2(tokens)Wahrscheinlichkeiten für 50.257 Tokens erhält, das nächste Token auswählt, es an die Tokenliste anhängt, die Abbruchbedingung prüft und am Ende wieder detokenisiert.Aber das wirkt, als würde eine Zustandsmaschine den Shlemiel-the-painter-Algorithmus implementieren, und wirft Fragen zu den inhärenten Rechenkosten der Generierung auf.
Dieses Fenster ist ein Sliding Window.
gpt2.Es zeigt eher nur, wie die generierten Tokens zu einem Satz zusammengesetzt werden.
Verwandtes Material: A GPT in 60 Lines of NumPy - https://news.ycombinator.com/item?id=34726115 - Februar 2023, 146 Kommentare
In einem ähnlichen Kontext habe ich GPT vollständig mit Tabellenkalkulationsfunktionen implementiert und dazu auch ein begleitendes Video-Tutorial erstellt.
https://spreadsheets-are-all-you-need.ai/
Aus der Perspektive von jemandem, der LLMs ziemlich cool findet, aber beruflich nicht lernen musste, wie sie tatsächlich funktionieren, hat mir dieses 10-Minuten-Video mehr beigebracht als Jahre des Lesens schwer verständlicher HN-Kommentare und oberflächlicher Mainstream-Artikel.
Wenn man sieht, wie eine riesige Menge an Gleitkommazahlen darauf wartet, berechnet zu werden, versteht man auch viel intuitiver, warum diese Technik so viele GPUs verschlingt.
Wenn man für jedes Trainingsbeispiel die Ableitung jedes Parameters berechnet und explizit zeigt, wie sie auf den jeweiligen Parameter abgebildet wird, ließe sich wohl auch der Trainingsprozess gut erklären.
Schön. Was noch vor einem Jahr wie eine Art Magie wirkte, wird jetzt so gut erklärt, dass es fast sogar ein Kind nachvollziehen könnte.
Das im Artikel erklärte Modell ist GPT-2, das Anfang 2019 veröffentlicht wurde.
Um diesen Artikel richtig zu verstehen, braucht man solide Informatik-Grundkenntnisse, und schon der Titel selbst ist für 99 % der Menschheit wenig zugänglich.
Ich habe GPT und LLMs komplett gemieden, aber dieser Ansatz scheint zwar eine gewisse Sprachflüssigkeit bei Textausgaben erzeugen zu können, nicht jedoch die Fähigkeit, Fragen zu interpretieren und zu beantworten.
Ich frage mich, ob es einfache Blogbeiträge oder Kurse gibt, die erklären, wie das tatsächlich funktioniert, oder eine Spielzeug-Engine etwa in Python zeigen.
Die Lernmaterialien, die ich bisher gesehen habe, konzentrieren sich darauf, wie man Plattformen nutzt, und behandeln die interne Funktionsweise kaum.
Besonders [0], [1] und [2] sind gut.
[0] http://jalammar.github.io/illustrated-transformer/
[1] http://jalammar.github.io/illustrated-gpt2/
[2] https://jalammar.github.io/visualizing-neural-machine-transl...
Interessanterweise erfordert modernes Machine Learning keine Turing-Vollständigkeit.
Und dennoch wird die Möglichkeit von AGI in Betracht gezogen; es wäre also ziemlich interessant, wenn sich herausstellen sollte, dass Turing-Vollständigkeit nicht nötig ist.
Aus einem einfachen Grund: Ich kann im Kopf die Ausführung von Turing-vollständigem Code nachvollziehen.
Der Artikel war hervorragend; die Erklärungen der einzelnen Komponenten waren klar und ziemlich gründlich, daher gut zu lesen.
Allerdings habe ich versehentlich auf „+ expand source“ geklickt, und nachdem ich dieses erstaunliche Monster gesehen hatte, konnte ich ChatGPT zustimmen, wenn es sagt: „SQL ist für die Implementierung großer Sprachmodelle nicht geeignet.“
Die Aussage „normales Unicode passt nicht gut zu neuronalen Netzen“ stimmt nicht. Siehe zum Beispiel ByT5.
Was der Artikel „Alphabet“ nennt, heißt normalerweise Vokabular; wenn man UTF-8-Bytes als Vokabular verwendet, hat man nicht 149.186 Tokens, sondern 256.
Genau das macht ByT5.
Dass alle leistungsstärksten Modelle am Markt Tokenisierung verwenden, ist der Beleg dafür.
Es ist kein Geheimnis, dass Tokenisierung im Grunde eher ein Hack ist und man sie idealerweise irgendwann auf die eine oder andere Weise loswerden möchte (https://twitter.com/karpathy/status/1657949234535211009).
Grundsätzlich kann man die Schwächen der Byte-Level-Tokenisierung durch größere Modelle und größere Kontexte ausgleichen, aber in der Praxis braucht es viel mehr Ressourcen, um ein Modell mit demselben Intelligenzniveau zu trainieren.
Natürlich gibt es auch bestimmte Aufgaben, bei denen Tokenisierung der Intelligenz eher schadet, etwa beim Zählen der Buchstaben eines Wortes.