Startseite / Artikel

Von der Vorhersage des nächsten Tokens zur Befolgung von Anweisungen: Feinabstimmung eines GPT-Modells

Wie ein vortrainiertes GPT lernt, Anweisungen zu befolgen: Formatierung von Datensätzen, Etikettverschiebung, Padding-Masken, der Trainingszyklus, Bewertungsgrenzen und der Schritt hin zu DPO-Präferenzen.

Verlag: REACTAPP.TOP9 Min. Lesezeit2112 Wörter

Quellen: From Next-Token Prediction to ChatGPT <> Build an LLM From Scratch [6]

Das Vortrainieren verleiht einem Sprachmodell bereits die Fähigkeit zu schreiben. Was ihm fehlt, ist ein Verständnis dafür, was der Benutzer erreichen möchte. Die Anweisungsoptimierung liefert das. Geben Sie zwei Modellen denselben Auftrag – beispielsweise, Rekursion einem Zehnjährigen zu erklären –, dann ist der Unterschied offensichtlich. Das Basismodell neigt dazu, den Auftrag nur widerzuspiegeln und in eine lehrbuchartige Einführung in Informatikkurse abzuschweifen. Es antwortet überhaupt nicht, sondern erweitert lediglich den Text. Das optimierte Modell hingegen zeigt ein Bild: eine Kiste, die eine kleinere Kiste enthält, die wiederum eine noch kleinere Kiste enthält, wobei Rekursion als ein Problem beschrieben wird, das sich ständig eine kleinere Version gibt, bis die kleinste leicht lösbar ist.

Im Grunde tun beide Modelle dasselbe – sie prognostizieren das nächste Token – doch ihr Verhalten unterscheidet sich stark. Chat-Produkte lassen das Befolgen von Anweisungen natürlich erscheinen, wodurch die Lücke verdeckt wird. Fragen werden beantwortet, Codeanfragen erzeugen Code und Zusammenfassungen werden verfasst. All das entsteht nicht kostenlos durch das Lesen einer riesigen Menge an Text. Das Vortrainieren vermittelt etwas Grundlegenderes: Angesichts dessen, was bisher bekannt ist, welches Token ist als Nächstes wahrscheinlich? Darin steht nichts davon, dass eine Anfrage eines Benutzers ausgeführt werden sollte. Die Überbrückung dieser Lücke ist das Thema hier.

Akt 1: Das Vortrainieren lehrt die Erfüllung von Aufgaben, nicht Gehorsam

Die Aufgabe beim Vortrainieren besteht lediglich in der Prognose des nächsten Tokens. Allein das reicht aus, um Grammatik, Fakten, Codesyntax, Schreibstile sowie einige überraschend komplexe Muster zu erfassen – doch das Befolgen von Anweisungen gehört nicht dazu. Geben Sie einem Basismodell folgenden Prompt ein:

Explain why the sky appears blue.

und es könnte mit folgendem weitergehen:

Explain why the sky appears blue.
The following questions are commonly asked in introductory physics...

Das ist nicht unbedingt ein defektes Modell. Es tut genau das, was das Vortraining verlangt hat, nämlich den Text fortzusetzen. Das gewünschte Verhalten sieht eher so aus:

The sky appears blue because Earth's atmosphere scatters
shorter wavelengths of sunlight more strongly than longer
wavelengths...

Das zugrunde liegende Modell ist dasselbe, doch das erwartete Verhalten unterscheidet sich. Die Frage ändert sich wie folgt:

What text probably comes next?

zu diesem:

When the text looks like an instruction,
what kind of continuation should I produce?

Das Anlernen des Modells, diese zweite Frage zu beantworten, ist der Zweck der Anweisungsfeinabstimmung.

Akt 2: Formatierung der Anweisungsdaten

Die direkteste Methode, dem Modell das Befolgen von Anweisungen beizubringen, besteht darin, ihm viele Anweisungen zusammen mit guten Antworten zu zeigen – es ist tatsächlich so einfach. Ein Trainingsdatensatz könnte wie folgt aussehen:

example = {
    "instruction": "Convert the following sentence to passive voice.",
    "input": "The developer fixed the bug.",
    "output": "The bug was fixed by the developer."
}

Ein Transformer kann keine Python-Dictionarys verarbeiten, daher wird die Zeile zunächst in reinen Text serialisiert, ungefähr so:

Below is an instruction that describes a task.

### Instruction:
Convert the following sentence to passive voice.

### Input:
The developer fixed the bug.

### Response:
The bug was fixed by the developer.

Die Formatierung ist wichtig, weil das Modell immer wieder dasselbe Muster sieht:

instruction
    ↓
optional context
    ↓
response

Kurz gesagt wird jede Eintragung zu formatiertem Text, anschließend zu Tokens und dann zu aufgefüllten Batch-Gruppen, wobei diese Batch-Gruppen das vortrainierte GPT-Modell feinabstimmen.

Die Tokenansicht

Nach der Tokenisierung wird das Beispiel zu einer Liste von IDs, ungefähr so:

[21106, 318, 281, 12064, ... 4435, 257, 3126, ...]

Diese Token-IDs bilden die Trainingssequenz. Bei einem autoregressiven Modell sind die Labels einfach die Sequenz, die um eine Position verschoben wurde:

Tokens:
[A, B, C, D, E]

Input:
[A, B, C, D]

Labels:
[B, C, D, E]

Durch das Anpassen der Anweisungen entsteht kein völlig neues Lernziel. Das Modell prognostiziert weiterhin den nächsten Token. Was sich ändert, ist die Verteilung der Texte, die es vorhergesagt werden sollen. Anstelle von überwiegend gewöhnlichen Dokumenten sieht es nun immer wieder diese Zuordnung:

instruction → useful response

Das ist die zentrale Idee.

Padding erfordert eine spezielle Behandlung

Die Trainingsbeispiele unterscheiden sich in der Länge, doch die Batchs benötigen Tensoren mit kompatiblen Formen. Hier sind zwei Beispiele:

Example 1:
[10, 20, 30, 40]

Example 2:
[11, 21]

Sie können mit Füllmaterial versehen werden:

Example 1:
[10, 20, 30, 40]

Example 2:
[11, 21, PAD, PAD]

Padding-Tokens sind keine sinnvollen Ziele. Das Modell sollte nicht dafür belohnt werden, dies zu lernen:

PAD → PAD → PAD → PAD

Daher werden diese Positionen in der Regel durch einen Ignorierwert ersetzt:

ignore_index = -100

Der Cross-Entropy-Verlust von PyTorch überspringt sie daher. Konzeptionell gesehen:

loss = cross_entropy(
    logits.reshape(-1, vocab_size),
    targets.reshape(-1),
    ignore_index=-100,
)

Die Details sind wichtig. Ohne Maskierung geht ein Teil des Trainingssignals an Artefakte verloren, die durch das Gruppieren der Daten entstehen, anstatt für die Sprache genutzt zu werden.

Akt 3: Das Verhalten feinabstimmen

Der Trainingszyklus an sich ist hier nichts Besonderes, da die Optimierung eines Next-Token-Modells bereits bekannt ist. Entscheidend ist, welche Beispiele in den Zyklus eingespeist werden. Vereinfacht ausgedrückt:

for batch in train_loader:
    optimizer.zero_grad()

    input_ids = batch[:, :-1]
    targets = batch[:, 1:]

    logits = model(input_ids)

    loss = cross_entropy(
        logits.flatten(0, 1),
        targets.flatten(),
        ignore_index=-100,
    )

    loss.backward()
    optimizer.step()

In diesem Code steht nichts, was so etwas besagt:

model.learn_to_be_helpful()

Das Verhalten ergibt sich aus den Daten. Angenommen, Tausende von Beispielen sehen so aus:

### Instruction:
Summarize this paragraph.

### Response:
<clear concise summary>

Gradienten-Updates sorgen dafür, dass das Modell nach solchen Eingaben stets zu Antworten dieser Art neigt. Nehmen wir an, andere Beispiele sehen so aus:

### Instruction:
Write Python code that reverses a string.

### Response:
def reverse_string(s):
    return s[::-1]

Es lernt eine weitere Zuordnung zwischen einem Befehlsmuster und einer passenden Fortsetzung. Auf Token-Ebene ist die Feinabstimmung von Befehlen weniger „magisch“: Der Kontext um die Vorhersagen wurde absichtlich verändert. Zuvor:

The CPU cache is...

Nach Formatierung der Anweisung:

### Instruction:
Explain CPU caching to a beginner.

### Response:
A CPU cache is...

Es wurde kein Ziel wie „Benutzer verstehen“ eingeführt. Stattdessen wurde die Verteilung des Trainingstextes so angepasst, dass hilfreiche Antworten nach einer Anweisung zu den statistisch bevorzugten Fortsetzungen werden. Diese Veränderung verursacht das neue Verhalten. Im angegebenen Beispiel wird ein vortrainiertes GPT-2-medium-Modell anhand von Anweisungen feinabgestimmt, es generiert Antworten und speichert das resultierende Modell.

Akt 4: Die Bewertung wird schwierig

Die Klassifizierung bot eine einfache Bewertungsmethode. Bei der Spamerkennung:

Prediction: SPAM
Label:      SPAM

Correct.

Die Befolgung von Anweisungen bietet keinen solchen Luxus. Betrachten Sie die Anfrage, Rekursion einfach zu erklären. Eine mögliche Antwort:

Recursion is when a function solves a problem by calling
itself on a smaller version of the same problem.

Ein weiterer:

Recursion is a technique where a problem is reduced into
smaller instances of itself until a stopping condition is reached.

Sowohl können richtig sein, und keines muss mit einem Referenztext wortwörtlich übereinstimmen. Dadurch ist die Genauigkeit bei exakter Übereinstimmung für offene Antworten meist nutzlos.

Vergleich mit einem Referenztext

Eine Möglichkeit besteht darin, einen Referenztext bereitzustellen:

{
    "instruction": "Explain recursion simply.",
    "reference": "Recursion solves a problem by repeatedly reducing it...",
    "model_response": "A recursive function calls itself..."
}

und vergleicht die erzeugte Antwort damit. Doch Ähnlichkeit bedeutet nicht Qualität – ein Modell kann eine hervorragende Antwort in völlig anderen Worten liefern. Das führt zu einem anderen Ansatz.

Lassen Sie ein anderes LLM urteilen

Der Bewerter erhält die Anweisung, die Referenzantwort sowie die zu bewertende Antwort:

Instruction:
Explain recursion simply.

Reference answer:
...

Model answer:
...

Rate the model answer from 0 to 100 based on correctness,
relevance, and clarity.

Ein lokaler Richter kann Ollama nutzen, was praktisch ist, doch es führt zu einem neuen Problem: Der Richter selbst ist ein Modell. Seine Bewertung kann von der Bewertungsanfrage, der Wahl des Bewerters, der Reihenfolge der Antworten, der Ausführlichkeit, dem stilistischen Geschmack sowie den eigenen Fehlern des Richters abhängen. Einige Beispiele zeigen, dass ein lokaler Richter überraschend hohe Bewertungen für schlechte Antworten von Basismodellen vergeben kann – ein Hinweis darauf, dass die von LLMs als Richtern ermittelten Zahlen keine objektive Wahrheit darstellen.

Ein kleines Projekt kann drei Schichten kombinieren:

1. Inspect representative outputs manually
2. Compare against reference answers where useful
3. Use an LLM judge for aggregate comparison

Keines davon ist für sich genommen perfekt. Gemeinsam liefern sie ein klareres Bild als eine einzige Zahl, die als Wahrheit ausgegeben wird.

Akt 5: ein schwierigeres Problem – Präferenzen

Durch das Feintunen der Anweisungen wird dem Modell gezeigt, wie eine angemessene Antwort aussieht. Vergleichen wir nun zwei Antworten. Antwort A:

Recursion is when a function calls itself.

Antwort B:

Recursion is a technique where a function solves a problem
by calling itself on a smaller version of that problem.
The process stops when it reaches a base case.

Sowohl sind gültig, aber B kann bevorzugt werden. Das Trainingssignal ist nicht mehr folgendes:

correct vs incorrect

Es ist folgendes:

chosen vs rejected

Ein Präferenzdatensatz kann so aussehen:

{
    "prompt": "Explain recursion simply.",
    "chosen": """
    Recursion solves a problem by repeatedly reducing it
    until reaching a base case.
    """,
    "rejected": """
    Recursion is when recursion happens recursively.
    """
}

Das ist eine andere Art der Überwachung. Anstatt zu sagen „Hier ist eine Antwort zum Nachahmen“, heißt es „Angesichts dieser beiden Antworten soll man diese bevorzugen.“ Hier kommen Techniken wie die direkte Präferenzoptimierung (DPO) ins Spiel. Auf höherer Ebene:

Prompt
   ↓
Chosen response ─────┐
                     ├── preference objective
Rejected response ───┘
                     ↓
               model update

DPO verdient eine eigene Behandlung, weshalb seine Mechanismen außerhalb dieser Diskussion bleiben. Der nützliche Link zeigt den Gesamtfortschritt:

Pretraining
    ↓
learn language patterns

------------------------------

Instruction fine-tuning
    ↓
learn instruction → response behavior

------------------------------

Preference tuning
    ↓
learn which acceptable responses we prefer

Die Erstellung von Präferenzdatensätzen mit Llama 3.1 über Ollama sowie die Implementierung von DPO von Grund auf sind Themen für später. Es gibt eine wichtige Einschränkung: Nur weil DPO als Zusatzmaterial verfügbar ist, bedeutet das noch nicht, dass es der richtige nächste Schritt für jedes Modell oder jeden Datensatz ist. Einige Experimente zeigen Grenzen bei den Ergebnissen, was genau der Skeptizismus ist, den man bei der Anpassung von Präferenzen einbringen sollte.

Was sich tatsächlich geändert hat

Der Ausgangspunkt war ein vorgetrainiertes Modell, das wusste, wie man Text fortführt. Der Transformer wurde nicht durch eine spezielle Assistentenarchitektur ersetzt. Die Beispiele zum Problem des nächsten Tokens änderten sich, wodurch ein anderes verhaltensbezogenes Vorauswissen entstand. Wenn das Modell nun Folgendes sieht:

### Instruction:

Es gab bereits ausreichend Fälle, in denen die Fortsetzung kein weiterer Absatz aus dem Internet war, sondern eine Antwort. Wenn man es so betrachtet, ist der Ablauf leichter nachvollziehbar:

Pretraining:
What token should come next?

Instruction tuning:
What does a good answer look like after an instruction?

Preference tuning:
Of several reasonable answers, which behavior should we prefer?

Architektur, Optimierer und Lernrate spielen alle eine Rolle, doch bei der Anweisungsoptimierung spielen die Daten eine besondere Funktion, da sie das zu imitierende Verhalten festlegen. Da es mehrere akzeptable Antworten gibt, ist eine gültige Ausgabe nicht mehr das einzige Ziel. Die Frage lautet stattdessen, welche akzeptable Antwort mehr Gewicht verdient – und genau das ist der Ansatz, den DPO bietet.

Das Vortrainieren lehrt, welcher Text in der Regel als Nächstes kommt. Die Anweisungsoptimierung bestimmt, welche Fortsetzungen als nützliche Antworten auf Anweisungen gelten. Die Präferenzoptimierung geht einen Schritt weiter und entscheidet, welche nützlichen Antworten bevorzugt werden sollen.

Verwandte Literatur

  • Wie ein Transformer den nächsten Token vorhersagt: Attention, Residuals und Softmax — Eine verständliche Erklärung des GPT-ähnlichen Transformers, von Tokens und Positionselementen bis hin zu kausaler Attention, Feed-Forward-Schichten, Logits und Softmax.
  • Von der Vorhersage des nächsten Tokens bis zum funktionsfähigen Code: Wie LLMs Python erzeugen — Tokens, Softmax, Temperature, Selbst-Attention und Feed-Forward-Schichten werden nacheinander erläutert, zusammen mit einem schrittweisen Sortierbeispiel sowie den daraus resultierenden Grenzen bei der Vorhersage.
  • Praktische Hinweise: Wie LLMs tatsächlich funktionieren – Tokens, Transformers und das nächste Token — Eine ausführliche Anleitung zu den Praktischen Hinweisen: Wie LLMs tatsächlich funktionieren – Tokens, Transformers und das nächste Token: Verträge, Überprüfungen sowie Code-Blöcke für Teams, die dieses Muster einsetzen.
  • Strategien zum Chunking für Produktionsumgebungen im RAG-Format – Festgrößen-, semantische und Eltern-Kind-Methoden — Eine praktische Übersicht über das Chunking im RAG-Format, von der Festgrößen- und rekursiven Aufteilung über Eltern-Kind-Strukturen, Propositionen, spätes Chunking, RAPTOR, Tabellen, Code bis hin zu Benchmarking.
  • Recursive Language Model Sandbox: Was behinderte die Entwicklung, was muss neu aufgebaut werden — Eine Rückschau auf eine Plattform für rekursive Sprachmodelle: Isolierschichten, Broker, CDR, Grenzen der Benutzerspace-Überwachung, Einschränkungen durch einen einzigen Host und warum Beschränkungen am wichtigsten waren.
  • Wie ein LLM funktioniert: Tokens, Attention und Vorhersage des nächsten Tokens — Eine einführende Erklärung der LLM-Architektur von Tokens und Embeddings über Attention, kausales Masking und Generierung sowie warum Training und Inferenz unterschiedlich sind.