Ein Forward ist noch kein Absatz
Ein Decoder liefert pro Inputposition Logits. Zum Generieren benutzen wir die Logits der letzten Position, weil sie den nächsten Token nach dem gesamten sichtbaren Präfix vorhersagen. Wir wählen eine ID, hängen sie an den Kontext und rechnen erneut.
with torch.inference_mode():
logits = model(ids[:, -cfg.context:])[:, -1, :]
next_id = torch.argmax(logits, dim=-1, keepdim=True)
ids = torch.cat([ids, next_id], dim=1)Die Gewichte ändern sich dabei nicht. Ohne KV-Cache wird der sichtbare Kontext in jedem Schritt erneut verarbeitet. Das ist langsam, aber für den Einstieg leicht zu überprüfen.
Greedy Decoding
Greedy wählt das Token mit dem höchsten Score. Bei demselben Modell, Präfix und derselben numerischen Umgebung ist diese Regel weitgehend deterministisch. Sie maximiert nicht automatisch die Wahrscheinlichkeit der gesamten zukünftigen Sequenz. Eine lokal beste Fortsetzung kann später zu einer ungünstigen Folge führen.
Greedy kann bei kleinen Modellen Wiederholungsschleifen verstärken. Zufall beim Sampling kann solche Schleifen manchmal unterbrechen, beseitigt aber keine strukturelle Modellschwäche. Wenn ein Modell „und und und“ hoch bewertet, solltest du nicht nur seine Temperatur verstellen.
Temperatur und Zufall
Teile die Logits durch eine positive Temperatur und wandle sie in Wahrscheinlichkeiten um. Ziehe eine ID mit torch.multinomial. Ein Zufallsseed erleichtert reproduzierbare Vergleiche. Ein anderer Seed erzeugt keinen anderen Wissensbestand, nur andere Ziehungen aus den berechneten Verteilungen.
logits = logits / temperature
probs = torch.softmax(logits, dim=-1)
next_id = torch.multinomial(probs, num_samples=1)Im Projekt bedeutet --temperature 0 Greedy; der Code führt dann keine Division durch null aus. Negative Temperaturen werden abgewiesen.
Top-k
Top-k lässt nur die k größten Logits zu. Alle anderen erhalten minus unendlich. Danach wird erneut normalisiert. Bei k=1 entspricht das der Wahl des größten Scores, abgesehen von Bindungsfragen. Ein kleines k kann unplausible Randtokens unterdrücken, begrenzt aber auch Vielfalt.
k = min(top_k, logits.size(-1))
threshold = torch.topk(logits, k).values[..., -1, None]
logits = logits.masked_fill(logits < threshold, float('-inf'))Bei exakt gebundenen Scores kann diese Schwellenform mehr als k Tokens behalten. Das Referenzprojekt benutzt die expliziten Top-k-Indizes, um wirklich die gewählte Anzahl zu behalten.
Top-p oder Nucleus Sampling
Sortiere die Tokens nach Wahrscheinlichkeit. Behalte die kleinste Anfangsmenge, deren aufsummierte Wahrscheinlichkeit mindestens p erreicht. Das überschreitende Token gehört dazu. Dadurch passt sich die Zahl zugelassener Tokens an die Konzentration der Verteilung an.
Für Wahrscheinlichkeiten [0.6,0.25,0.1,0.05] und p=0,8 behalten wir die ersten zwei Tokens. Bei p=0,95 sind es drei. Achte in der Implementierung auf Rundung und darauf, mindestens ein Token zu behalten. Top-k und Top-p nacheinander anzuwenden ergibt eine andere Verteilung als nur eines davon; dokumentiere die Reihenfolge.
EOS ist ein trainierter Stopp
Beim Pretraining sieht das Modell EOS nach Dokumenten. Beim SFT sieht es EOS nach einer Antwort. Während des Generierens stoppen wir, wenn EOS gewählt wurde, oder wenn ein maximales Tokenbudget erreicht ist. Ein nicht vorhandenes EOS-Training kann zu Ausgaben führen, die nie sinnvoll enden.
Reservierte Rollenmarker wollen wir nicht mitten im sichtbaren Antworttext erzeugen. Unser Sampler sperrt PAD, BOS und Rollenmarker bei der normalen Ausgabe, lässt EOS aber zu. Das ist eine Regel der Dekodierung, keine Behauptung, das Modell selbst würde diese Klassen niemals hoch bewerten.
Kontextfenster und abgeschnittene Erinnerung
Wenn der Kontext länger als die konfigurierte Länge ist, nimmt unser einfacher Sampler nur die letzten Tokens. Das Modell kann dann nur diese Tokens lesen. Ein früher Systemmarker oder eine Frage kann herausfallen. Eine lange Ausgabe bedeutet deshalb nicht, dass alle früheren Eingaben weiter berücksichtigt werden.
Im Lehrprojekt sollten Prompts und Antwortbudget kurz bleiben. Eine ernsthafte Chat-Anwendung braucht eine bewusste Strategie zum Kürzen von Nachrichten, nicht bloß ein blindes Fenster über die gesamte Unterhaltung.
So prüfst du Generierung
python sample.py --checkpoint runs/base/best.pt \
--prompt "Ein kleiner Hund" --tokens 100 \
--temperature 0.8 --top-k 40 --top-p 0.95 --seed 42Vergleiche dieselben Prompts mit Greedy und zwei Temperaturen. Prüfe Verständlichkeit, Wiederholung, Abbruch und Format. Veröffentliche nicht nur das schönste Sample. Bei einem Demonstrationskorpus ist Memorisation zu erwarten; bei einem größeren Korpus untersuche zusätzlich Ähnlichkeit zu Trainingstexten.
Das Experiment
Ziehe mehrfach ein Token aus einer dreiteiligen Verteilung. Die beobachteten Häufigkeiten müssen nach wenigen Ziehungen nicht exakt den theoretischen Wahrscheinlichkeiten entsprechen. Über viele Ziehungen sollten sie sich annähern. Diese Zufallsschwankung erklärt, weshalb einzelne Samples als Modellvergleich so unzuverlässig sind.