The mask — legal tokens live, illegal tokens die
Here's the whole trick in one picture. The model has produced a probability for each of 12 candidate next-tokens. A grammar (right now: "a number") marks each as legal or illegal. Constrained decoding sets every illegal probability to zero and renormalizes — so the model samples only from what's allowed, even if its favorite token was forbidden. Switch the active rule and watch a different set of tokens survive.
The 12 candidate probabilities are fixed; the mask is what changes. "Renormalize" means the surviving probabilities are rescaled to sum to 1 — the model's relative preferences among legal tokens are preserved.
The JSON automaton — a bouncer with a checklist
Where does the mask come from? A state machine that tracks where you are in the grammar. After { it expects a key or }; after a key it demands :; after a value it wants , or }. Generate a JSON object token-by-token below: at each step the automaton lights up only the legal next-tokens. Try to force it into an invalid state — you can't, the illegal buttons are disabled.
This is a real (small) pushdown-style automaton for a JSON subset, running live — the enabled/disabled buttons are computed from the current state, and the output is guaranteed parseable at every step.
Prayer vs lock — run 500 requests each
The empirical showdown. Send the same "give me JSON" task 500 times three ways: bare prompt, prompt with threats and examples ("ONLY JSON, no markdown, I will be fired"), and grammar-constrained. Read the valid-JSON rate. Prompting gets you most of the way — and "most of the way" is exactly the problem when a downstream parser needs 100%.
Failure modes sampled realistically: bare prompt leaks markdown fences, prose preambles ("Sure! Here's..."), trailing commas; the threatening prompt fixes most but not the rare ones. Constrained can't fail the parse — that's the definition.
The tokenizer trap — where naive masking corrupts the model
The subtle bug that makes constrained decoding hard to implement well. The grammar thinks in characters; the model thinks in tokens, and one token can straddle a boundary — the token " then true might only exist as a single merged token "true. Naive char-level masking forbids that merged token and forces the model down a rare, low-probability spelling path — quietly wrecking output quality. Toggle token-aware masking and watch the "model surprise" meter drop.
"Surprise" = average negative log-prob of the tokens the model was forced onto vs. the tokens it would have chosen freely (both still valid). The gap is the hidden cost of getting the token boundaries wrong.
The reasoning tax — when the cage hurts the bird
The counterintuitive finding. If you force the model into JSON immediately, it can't do its usual trick — think out loud first, then answer. Compare three protocols on a set of word problems: free-text reasoning (best accuracy, unparseable), JSON-only from token one (parseable, dumber), and the fix — reason freely, then constrain only the final answer field. Drag the "how early do we lock" slider and watch accuracy fall as the cage closes sooner.
Accuracy vs lock-point measured over 300 simulated word problems (reasoning tokens genuinely help until a plateau); the "valid JSON" guarantee holds at every setting — only accuracy moves.