Constrained Decoding: LLMs als schnelle Entscheidungsmodelle
Constrained Decoding macht aus einem LLM einen schnellen Klassifikator. Erfahre, wie Logit-Masking, Kalibrierung und Temperature Scaling Entscheidungsmodelle zuverlässig machen.

Ein simpler Trick, der stillschweigend sehr nützlich ist: Wenn dich nur das erste Token interessiert, das ein LLM ausgeben würde, kannst du ihm eine Multiple-Choice-Frage stellen und die Antwort in einem einzigen Forward Pass bekommen. Constrained Decoding – also das Maskieren aller Tokens im Vokabular außer den wenigen, die du akzeptieren willst – macht aus einem generativen Modell etwas, das stark nach Klassifikator aussieht. Kein JSON-Parsing, keine Retry-Schleifen, keine elf autoregressiven Schritte für elf Zeichen. Ein Durchlauf, ein Softmax, eine Antwort mit einer Wahrscheinlichkeit für jede Option.
Die Idee kursiert unter Namen wie „Entscheidungsmodelle“ oder „System-1“-Inferenz. Vor Kurzem hat sie auf Hacker News richtig eingeschlagen, mit einer Anleitung, wie man aus einem 1,7-Milliarden-Parameter-Qwen-Modell in gut vierzig Zeilen Python einen solchen Klassifikator baut. Die Machine-Learning-Veteranen in den Kommentaren sind erwartungsgemäß an die Decke gegangen: Das ist ein Klassifikator, die gibt es seit dem Perzeptron. Sie haben recht und verfehlen den Punkt trotzdem ein Stück weit. Neu ist nicht das Konzept, sondern dass man aus einem Allzweck-Sprachmodell ohne jegliches Training einen Zero-Shot-Klassifikator bekommt. Die spannende technische Frage ist, wann dieser eingeschränkte Ansatz besser ist, als das Modell einfach reden zu lassen, und wann er dich stillschweigend mit überselbstsicheren Wahrscheinlichkeiten belügt.
Zwei Wege, eine Antwort aus einem LLM zu holen
Der Standardmodus beim Reden mit einem Sprachmodell ist die Generierung. Du stellst eine Frage, das Modell gibt Tokens nacheinander aus, und irgendwo weiter unten parst du das Ergebnis. Brauchst du strukturierte Ausgaben, kommt ein Schema obendrauf: JSON-Modus, grammatikbeschränktes Sampling, Outlines-artige Finite-State-Machines. Das funktioniert, aber das Modell läuft trotzdem Token für Token durch die ganze Antwort. Eine triviale Multiple-Choice-Antwort kann elf Dekodierschritte brauchen, und jeder Schritt kostet einen vollen Forward Pass über Milliarden Parameter.
Die Alternative: Man lässt es gar nicht erst laufen. Nachdem der Prompt verarbeitet ist, schaust du dir die Logits über das Vokabular an der letzten Position an, verwirfst alles außer den Token-IDs deiner Optionen – etwa „A“, „B“, „C“, „D“, „E“ – und wendest Softmax nur darauf an. Das Argmax ist deine Vorhersage, die Softmax-Werte sind die Scores. Gesamtkosten: ein Forward Pass, genau das Prefill, das du ohnehin bezahlst. Hier ist der Kern, angelehnt an den Qwen-basierten Ansatz, der gerade die Runde macht:
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
model_name = "Qwen/Qwen3-1.7B"
options = ["A", "B", "C", "D", "E"]
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(
model_name, torch_dtype="auto", device_map="auto"
)
# First token the model would emit for each option
option_token_ids = [
tokenizer.encode(opt, add_special_tokens=False)[0] for opt in options
]
prompt = (
"What color is the sky?\n"
"A. Red\nB. Blue\nC. Green\nD. Purple\nE. I don't know\nAnswer:"
)
messages = [{"role": "user", "content": prompt}]
text = tokenizer.apply_chat_template(
messages, tokenize=False, add_generation_prompt=True,
enable_thinking=False
)
inputs = tokenizer(text, return_tensors="pt").to(model.device)
with torch.no_grad():
logits = model(**inputs).logits[0, -1]
# Constrained decoding: softmax over only the option tokens
probs = torch.softmax(logits[option_token_ids].float(), dim=-1)
print(options[probs.argmax().item()]) # -> B
Das ist die ganze Engine. Das Vokabular umfasst über 150.000 Tokens, und wir haben die Entscheidung auf fünf Zahlen reduziert. Keine Sampling-Temperatur-Spielereien zur Generierungszeit, kein Parser, der fehlschlagen kann, kein Weg für das Modell, eine Option zu halluzinieren, die nicht auf der Liste steht. Der Ausgaberaum ist per Konstruktion geschlossen.
Es ist ein Klassifikator, und das ist in Ordnung
Geben wir den Skeptikern ihr Recht. Was wir gebaut haben, ist ein diskriminativer Klassifikator über ein festes Label-Set – ein Nachfahre der Ideen, die auf Rosenblatts Perzeptron in den 1950ern zurückgehen, über die logistische Regression bis zu jedem softmax-gekrönten neuronalen Netz der Deep-Learning-Ära. Wenn du genau hinschaust, ist Constrained Decoding nur ein lineares Auslesen des letzten versteckten Zustands, so wie es ein Klassifikationskopf schon immer war. Der ML-Engineer, der seit Jahren darum bettelt, einfach einen Klassifikator trainieren zu dürfen, hat jedes Recht, beim Anblick dieses Rebrandings als „Entscheidungsmodell“ ein wenig die Nerven zu verlieren.
Die Geschichte zeigt aber auch, warum die neue Variante wichtig ist. Alte Klassifikatoren waren eng: Du hast gelabelte Daten gesammelt, trainiert und bekamst ein Modell, das eine Aufgabe kannte und sonst nichts. Der Grund, warum LLM-basierte Systeme immer wieder Aufgaben übernehmen, die „eigentlich“ einen richtigen Klassifikator bräuchten, ist die Zero-Shot-Eigenschaft: Das Basismodell hat bereits genug von der Welt aufgesogen, dass ein Prompt die Trainingsdaten ersetzt. Als ich so ein Setup gegen ein CommonsenseQA-Holdout getestet habe, landete das 1,7-Milliarden-Modell bei etwa 59 % Genauigkeit ganz ohne Fine-Tuning und kletterte nach einer kurzen Anpassung am Trainingssplit auf rund 62 %. Das ist nicht State of the Art, hat aber einen Nachmittag gekostet und keine eigenen gelabelten Daten erfordert. Ein eigens gebauter Klassifikator könnte besser sein, bräuchte dafür aber eine Pipeline, einen Datensatz und eine Strategie zum Neutrainieren, sobald sich die Labels ändern.
Hilfreich ist hier die alte Debatte zwischen generativen und diskriminativen Klassifikatoren. Generative Klassifikatoren modellieren die gesamte Verteilung, sind verschwenderisch, aber flexibel; diskriminative modellieren die Entscheidungsgrenze, sind effizient, aber starr. Constrained Decoding ist ein eigenartiger Hybrid: ein generatives Modell, das zur Inferenzzeit in den diskriminativen Dienst gezwungen wird. Du bekommst die Effizienz des diskriminativen Auslesens mit der Breite des generativen Pretrainings. Diese Kombination war so vorher nicht verfügbar, auch wenn die Einzelteile uralt sind.
Wo Constrained Decoding gewinnt
- Latenz. Ein Forward Pass statt N autoregressiver Schritte. Bei kleinen Modellen ist das der Unterschied zwischen „schnell genug für einen Request-Pfad“ und „braucht eine Warteschlange“. Leute, die reine Entscheidungsmodelle im Browser betreiben, berichten von Antworten unter 200 ms – versuch das mal mit generiertem JSON.
- Strukturelle Korrektheit. Das Modell kann buchstäblich keine Ausgabe außerhalb der erlaubten Menge erzeugen. Kein fehlerhaftes JSON, kein „Die Antwort ist wahrscheinlich B, weil…“-Geschwafel, keine Guardrail-Schicht zum Abfangen von Formatverletzungen.
- Durchsatz. Da jede Anfrage ein Durchlauf gleicher Form ist, ist Batching trivial und vorhersagbar. Variable lange Antworten ruinieren deine Batching-Effizienz.
- Ein Score pro Option. Du bekommst die gesamte Verteilung, nicht nur den Gewinner. Das öffnet die Tür für Abstinenzlogik: Liegt die Top-Wahrscheinlichkeit unter einem Schwellenwert, geht der Fall an einen Menschen oder ein größeres Modell.
Der Punkt mit der Abstinenz verdient Betonung. Eine generative Antwort ist ein einzelnes Artefakt, dem du entweder vertraust oder nicht. Eine Wahrscheinlichkeitsverteilung über Optionen erlaubt dir Routing: Fälle mit hoher Sicherheit laufen automatisch durch, unsichere werden eskaliert. Das ist das Muster hinter vielen Triage-Systemen in der Produktion – Routing von Support-Tickets, Vorprüfung bei der Moderation, Intent-Erkennung – und genau dort verdient sich diese Technik ihren Platz. Wenn sich deine Aufgabe natürlich in „wähle eines von K Labels und sag mir, wie sicher du dir bist“ zerlegen lässt, ist Constrained Decoding fast sicher das richtige Werkzeug.
Wo generative Ausgaben gewinnen
Nun zur anderen Seite. Sobald deine Aufgabe nicht in ein festes Label-Set passt, zerfällt Constrained Decoding. Ist die Antwort eine freie Entität, eine Zahl, ein Stück Code oder etwas Kompositionales, brauchst du Generierung – gegebenenfalls mit strukturellen Ausgabe-Constraints, aber dennoch Generierung. Es gibt außerdem einen subtileren Verlust: das Schlussfolgern. Wenn ein Modell vor der Antwort eine Gedankenkette erzeugt, schneidet es bei schweren Fragen oft deutlich besser ab. Der Ein-Pass-Entscheidungskopf bietet keinen Notizzettel. Du verlangst also Denken im Stil von System 1, schnell und intuitiv, und bekommst genau das – inklusive der typischen Fehler.
Dazu kommt das Problem der Formulierungsempfindlichkeit. Ein constrained Klassifikator über „A/B/C/D/E“ misst eigentlich die Vorliebe des Modells für den Text jeder Option an ihrer Position. Formuliere Option C leicht um, ordne die Liste neu oder ersetze „Answer:“ durch „The best answer is“, und die Scores können sich verschieben. Generative Antworten mit Begründung sind robuster gegenüber Oberflächenstörungen, weil das Modell sich auf Inhalte festlegen muss und nicht nur auf ein Token. Wenn du einen der beiden Ansätze evaluierst, störe die Präsentation und schau, was kaputtgeht – das ist ein billiger und zugleich unbequemer Robustheitstest.

Das Kalibrierungsproblem: Deine Konfidenzwerte lügen
Hier liegt die Falle, in die jeder tappt, der so etwas baut. Du bekommst Wahrscheinlichkeiten aus einem Softmax, also müssen es Wahrscheinlichkeiten sein, oder? Sind sie nicht. Es sind die Konfidenzen des Modells, dass ein bestimmtes Token als Nächstes kommt – eine Aussage über Sprache, nicht über Richtigkeit. Frag das Modell „Wo würdest du am ehesten eine Fledermaus finden?“ mit den Optionen „Höhle“ und „Baseballspiel“, und es wird „Höhle“ etwa 0,998 zuweisen – eine mehrdeutige Frage ohne vertretbar sichere Antwort, beantwortet mit nahezu totaler Gewissheit.
Sortierst du die Vorhersagen bei einer echten Auswertung nach Konfidenz, wird das Bild noch schlechter. Bei einem CommonsenseQA-Lauf war der Bereich 0,9–1,0 nur etwa zu 70 % korrekt, der Bereich 0,8–0,9 kam kaum auf 40 %. Ein gut kalibriertes Modell sollte bei 0,9 in rund 90 % der Fälle richtig liegen. Dieses ist systematisch überselbstsicher – was, wenn man darüber nachdenkt, die alte Beobachtung widerspiegelt, dass moderne tiefe Netze generell überselbstsicher sind. Guo et al. haben schon 2017 gezeigt, dass die Softmax-Ausgaben eines einfachen ResNet im Vergleich zu den flachen Netzen der 1990er stark fehlkalibriert sind. Alles Alte ist wieder neu; wir haben das Problem nur bei 1,7 Milliarden Parametern neu erfunden.
Die Lösung ist zum Glück auch alt und fast peinlich simpel: Temperature Scaling. Du teilst die Logits vor dem Softmax durch einen einzelnen gelernten Skalar T:
def scaled_probs(logits, option_ids, temperature):
selected = logits[option_ids].float() / temperature
return torch.softmax(selected, dim=-1)
# Fit T on a validation set by minimizing negative log-likelihood
# of the correct option. For the CommonsenseQA run above, T ~= 3.8
temperature = 3.7973
probs = scaled_probs(logits, option_token_ids, temperature)
T größer als 1 glättet die Verteilung, T kleiner als 1 schärft sie. Das Anpassen einer einzigen Zahl an einem Held-out-Datensatz verwandelte meine miserable Kalibrierungstabelle in etwas Ehrliches: Der Bereich 0,9–1,0 liegt nun bei etwa 95 % Genauigkeit, der Bereich 0,5–0,6 bei etwa 55 %. Die Genauigkeit ändert sich dabei überhaupt nicht – Argmax ist invariant gegenüber monotoner Skalierung –, aber die Scores bedeuten jetzt, was du dir darunter vorgestellt hast. Wenn du auf diesen Wahrscheinlichkeiten routen willst, kalibriere zuerst oder erhebe sie gar nicht erst.
Ist das Tuning der Temperatur an einem Benchmark Schummelei?
Eine berechtigte Frage aus der Diskussion: Ist es nicht ein bisschen p-Hacking, T so zu fitten, dass das Modell auf einem Benchmark kalibriert wirkt? Wäre es das, wenn du auf dem Testset fittest. Richtig gemacht – T auf einem Validierungssplit fitten, Kalibrierung auf einem separaten Testsplit berichten – ist es nur eine einparametrige Regression und in der Literatur genau aus diesem Grund der Standard. Wichtig ist dieselbe Disziplin wie überall im ML: saubere Splits halten und misstrauisch sein gegenüber jeder Kalibrierungsaussage, die auf Daten gemessen wurde, die beim Fitten dabei waren. Driftet deine Einsatzdomäne von deiner Evaluationsdomäne weg, driftet auch dein T, also gehört das Nachkalibrieren in deine Wartungsroutine wie alles andere.
Das ist im Grunde eine Ausprägung eines größeren Übels: der Lücke zwischen dem, was ein System laut Spezifikation bedeuten soll, und dem, was es tatsächlich berechnet – eine Lücke, in der, wie ich schon früher argumentiert habe, Bugs leben. „Konfidenz“ ist als Korrektheitswahrscheinlichkeit spezifiziert; die Implementierung liefert plausibilität des nächsten Tokens. Temperature Scaling ist ein Pflaster über dieser Lücke, keine Lösung. Behalte diese Unterscheidung im Kopf, jedes Mal wenn du versucht bist, Softmax-Ausgaben an einen Alarm-Schwellenwert zu hängen.
Ein praktisches Entscheidungsverfahren
Wenn ich heute zwischen den beiden Modi wähle, gehe ich eine kurze Checkliste durch:
- Ist die Ausgabe eine feste Menge von Labels? Wenn ja, kommt Constrained Decoding infrage. Wenn nein, generiere.
- Brauche ich Schlussfolgerungen, um eine akzeptable Genauigkeit zu erreichen? Prototypisiere beides. Hinkt der Ein-Pass-Kopf der Gedankenkette um mehr hinterher, als du tolerieren kannst, gewinnt die Generierung trotz der Latenz.
- Brauche ich Scores pro Option für Routing oder Abstinenz? Wenn ja, sind Constrained Decoding plus Temperature Scaling nahezu kostenlos, und Generierung liefert nichts Vergleichbares.
- Wie groß ist das Label-Set? Fünf Optionen sind trivial, fünftausend sind Retrieval-Terrain. Bei großen Label-Räumen bettest du die Labels ein, erstellst mit Vektorsuche eine Vorauswahl und lässt das Modell erst dann unter den Finalisten wählen – der Trick der Klassifikator-Skalierung, der seit der Ära der Empfehlungssysteme Standard ist.
- Ändern sich die Labels oft? Die Zero-Shot-Eigenschaft ist der ganze Sinn der Sache. Wenn Labels wöchentlich wechseln, ist ein neu trainierter Klassifikator ein Wartungsklotz; eine Prompt-Änderung ist keiner.
Der Softmax des Modells ist eine Aussage darüber, welches Token als Nächstes kommt, nicht darüber, was wahr ist. Kalibriere ihn, oder vertrau ihm nicht.
Ein weiterer Gesichtspunkt: Die Modellgröße beeinflusst die Wahl. Der Ein-Pass-Kopf eines kleinen Modells ist günstig genug, um bei jeder Anfrage zu laufen, sogar clientseitig; Leute liefern bereits Entscheidungsmodelle aus, die im Browser mit Antworten unter 200 ms laufen. Ein kaskadiertes Design – ein kleines constrained Modell für die einfachen 80 %, ein großes generatives Modell für den schweren Rest – schlägt oft beide Extreme, sowohl bei Kosten als auch bei Genauigkeit. Es ist derselbe Instinkt wie beim Speculative Decoding, nur auf Systemebene statt auf Token-Ebene angewendet.
Die Empfehlung
Meine Position: Ist deine Aufgabe wirklich eine Entscheidung zwischen festen Optionen – Routing, Triage, Intent, Multiple-Choice-Auswertung –, dann baue den constrained Entscheidungskopf und schau nicht zurück. Allein der Latenzgewinn rechtfertigt ihn, die strukturellen Garantien beseitigen eine ganze Klasse von Parsing-Fehlern, und die Scores pro Option liefern eine Routing-Logik, die Generierung nicht erreicht. Behandle den rohen Softmax aber als unkalibriertes Messinstrument. Feintune auf deiner eigentlichen Aufgabe, wenn du auch nur einen bescheidenen Datensatz zusammenbekommst, fitte eine Temperatur auf einem sauberen Validierungssplit und prüfe die Kalibrierung auf Daten, die beim Fitten nie gesehen wurden.
Generative Ausgaben – mit strukturellen Constraints, wo nötig – solltest du für Aufgaben aufsparen, die wirklich kompositional sind oder von sichtbarem Schlussfolgern profitieren. Und lass dir kein „Entscheidungsmodell“ als neue Kategorie verkaufen: Es ist ein Klassifikator im Mantel eines LLM, abstammend von sechzig Jahren diskriminativer Modellierung, und am nützlichsten ist er genau dann, wenn du dieser Abstammung so viel Respekt zollst, dass du die Kalibrierungsarbeit erledigst, auf die die alten Hasen immer bestanden haben. Die Werkzeuge sind neu. Die Disziplin nicht.


