Structured Decoding: Token Masks From Schema Automata

How a schema automaton or parser state turns next-token logits into constraint-valid generation by masking invalid continuations, while leaving truth and task success outside the formal guarantee.

Advanced · Undergraduate mathematics · about 22 minutes

Reading map and next steps

Intuition

The idea in plain words, before the symbols.

Decoding and sampling turn next-token logits into text. Structured decoding adds one more piece to the loop: the state of a small parser or automaton that tracks where the output is in a schema or grammar (Willard and Louf, 2023).

At each step, the model still proposes ordinary logits over tokens. The schema state asks a stricter question: which next tokens can still lead to a valid completion? Invalid tokens are masked to probability zero. The remaining tokens are renormalized, then greedy decoding, sampling, beam search, or tree search can choose among them.

The guarantee is narrow but useful. If the automaton is correct, every emitted token follows its mask, and decoding stops in an accepting state, then the output belongs to the formal language. That does not mean the source is true, the tool should be called, the answer is safe, or the selected enum is semantically right.

This page studies that finite mechanism, not product JSON modes, tool calling, retries, validators run after generation, or full JSON Schema.

The demo uses toy tokens: real tokenizers can split or merge pieces like "tool" or {, so production masks are built over actual token IDs. The schema here fixes the order of the fields and lists every allowed value; real JSON Schema can admit many equivalent serializations. The naive and cached check counts in the demo show how much mask-checking work is repeated at each decoding step, not a latency model.

Further reading: Scholak et al., "PICARD" (EMNLP 2021), which rejects invalid tokens with an incremental parser; Beurer-Kellner et al., "LMQL" (PLDI 2023); Beurer-Kellner, Fischer and Vechev, "DOMINO" (ICML 2024), on aligning constraints with subword tokens; Dong et al., "XGrammar" (MLSys 2025), on fast grammar masks; and the documentation for the JSON Schema object keyword, Outlines, OpenAI's Structured Outputs and vLLM structured outputs.

Study prompts for this section: Intuition
Study prompts

Each button copies a prompt about this section to your clipboard, for an AI assistant you already use. Nothing is sent by this site.

Mathematics

Definitions, assumptions and the derivation.

Let V\mathcal V be a fixed finite token vocabulary, including an end token <eos>\texttt{<eos>}.

Represent the schema as a deterministic finite automaton

A=(Q,V,δ,q0,F),δ:Q×V⇀Q.A=(Q,\mathcal V,\delta,q_0,F),\qquad \delta:Q\times\mathcal V\rightharpoonup Q.

For a prefix y1:ty_{1:t}, the schema state is qt=δ∗(q0,y1:t)q_t=\delta^*(q_0,y_{1:t}) when every transition is defined.

The allowed-token mask should include only tokens that can still reach an accepting state. Given model logits ℓt(v)\ell_t(v) and temperature τ\tau, constrained decoding uses

M(qt)={v∈V:δ(qt,v) is defined and can reach F},pA(v∣y1:t)=exp⁡(ℓt(v)/τ)1[v∈M(qt)]∑u∈M(qt)exp⁡(ℓt(u)/τ).M(q_t)= \{v\in\mathcal V:\delta(q_t,v)\ \mathrm{is\ defined\ and\ can\ reach}\ F\}, \qquad p_A(v\mid y_{1:t})= \frac{\exp(\ell_t(v)/\tau)\mathbf 1[v\in M(q_t)]} {\sum_{u\in M(q_t)}\exp(\ell_t(u)/\tau)}.

The "can reach FF" condition matters: a token whose transition leads into a dead end, a state from which no accepting state can be reached, must not be allowed even though the transition itself is defined.

Then a decoder selects or samples yt+1y_{t+1} from pAp_A and updates

qt+1=δ(qt,yt+1).q_{t+1}=\delta(q_t,y_{t+1}).

Stopping is accepted only when qt+1∈Fq_{t+1}\in F, usually after an explicit <eos>\texttt{<eos>} token.

If top-pp is used, the hard schema mask should be applied first, then top-pp truncates and renormalizes inside the valid set. Applying top-pp first can throw away every valid token when the raw model strongly prefers invalid ones.

The guarantee is modest:

If every emitted token is sampled from M(qt)M(q_t) and the run stops in FF, then the token sequence is in L(A)L(A). Nothing follows about whether the content is true, useful, task-correct, or semantically correct; safety is likewise outside a formal syntax guarantee.

Real tokenizers: characters first, then tokens

The automaton above reads whole tokens, which suits the toy vocabulary. A real schema is written over characters, and one real token can stand for several of them, such as {" or "tool. Then write the automaton over a character alphabet Σ\Sigma, with transition δΣ\delta_\Sigma and its extension δΣ∗\delta_\Sigma^* to strings, and let decode⁡(v)\operatorname{decode}(v) be the string that token vv stands for. A token moves the automaton by its whole string:

δV(q,v)=δΣ∗(q,decode⁡(v)),\delta_{\mathcal V}(q,v)=\delta_\Sigma^*\big(q,\operatorname{decode}(v)\big),

defined only when every character is accepted in turn. The mask M(qt)M(q_t) is then built from δV\delta_{\mathcal V}, with the same can-reach-FF condition. A token that stops partway through a keyword is allowed when the keyword can still be completed. This is why practical systems compute masks over the tokenizer's real vocabulary, often by precomputing, for each automaton state, which tokens' strings it can consume (Willard and Louf, 2023).

Worked example: mask, then renormalize

At the state after {"tool":, suppose the model's raw probabilities over five tokens are 0.200.20 for "retrieve" and 0.100.10 for "lookup", the two allowed tokens, and 0.500.50 for "DROP", 0.150.15 for "extra" and 0.050.05 for true, which the mask removes. The surviving mass is C=0.20+0.10=0.30C=0.20+0.10=0.30, so

pA("retrieve")=0.200.30=23,pA("lookup")=0.100.30=13.p_A(\texttt{"retrieve"})=\frac{0.20}{0.30}=\frac23,\qquad p_A(\texttt{"lookup"})=\frac{0.10}{0.30}=\frac13.

Greedy decoding picks "retrieve", although "DROP" had the highest raw probability; sampling still writes "lookup" one time in three. Renormalizing keeps the ratio 2:12:1 between the allowed tokens: the mask removes probability, it does not reorder what is left. Only when C=0C=0, with no allowed token given any probability, is the constrained distribution undefined; any positive CC, however small, defines it.

The order of the mask and top-pp matters. Apply top-pp with p=0.4p=0.4 to the raw distribution first, and the nucleus is "DROP" alone, since its 0.500.50 already reaches 0.40.4; the mask then leaves nothing to sample. Apply it after the mask, and top-pp keeps "retrieve", whose 2/32/3 reaches 0.40.4.

A valid output is not a correct one. At the state after "source":, let the raw probabilities be 0.100.10 for "docs" and 0.600.60 for "tickets", with the other 0.300.30 on masked tokens. Both sources are allowed, so the constrained probabilities are 1/71/7 and 6/76/7 and greedy decoding writes "tickets". If the task needs the documentation, the output follows the schema and is still wrong.

Study prompts for this section: Mathematics
Study prompts

Each button copies a prompt about this section to your clipboard, for an AI assistant you already use. Nothing is sent by this site.

Code

The same calculation as runnable code, in the notation of the derivation.

This code implements the same automaton as the demo: a token-level automaton for a tiny retrieval-call schema with fields in a fixed order. The unconstrained decoder can choose an invalid token immediately. The constrained decoder produces schema-valid output. A semantic-mismatch profile stays schema-valid while choosing the wrong source for the task.

from collections import defaultdict
from math import exp

V = [
    "{", "}", ":", ",",
    '"tool"', '"source"', '"k"',
    '"retrieve"', '"lookup"', '"docs"', '"tickets"',
    "1", "2",
    '"DROP"', '"extra"', "true", "<eos>",
]

D = {
    0: {"{": 1},
    1: {'"tool"': 2},
    2: {":": 3},
    3: {'"retrieve"': 4, '"lookup"': 4},
    4: {",": 5},
    5: {'"source"': 6},
    6: {":": 7},
    7: {'"docs"': 8, '"tickets"': 8},
    8: {",": 9},
    9: {'"k"': 10},
    10: {":": 11},
    11: {"1": 12, "2": 12},
    12: {"}": 13},
    13: {"<eos>": 14},
}

ACCEPT = {14}

VALID_PROFILES = {"schema-friendly", "format-confused", "semantic-mismatch"}

def good_states():
    rev = defaultdict(set)
    for q, arcs in D.items():
        for tok, r in arcs.items():
            rev[r].add(q)
    good = set(ACCEPT)
    stack = list(ACCEPT)
    while stack:
        r = stack.pop()
        for q in rev[r]:
            if q not in good:
                good.add(q)
                stack.append(q)
    return good

GOOD = good_states()

def allowed(q):
    return [tok for tok in V if D.get(q, {}).get(tok) in GOOD]

def step(q, tok):
    return D.get(q, {}).get(tok)

def base_logits(q, profile):
    if profile not in VALID_PROFILES:
        raise ValueError(f"unknown profile: {profile}")

    z = {tok: -2.0 for tok in V}
    for tok in allowed(q):
        z[tok] = 1.0

    preferred = {3: '"retrieve"', 7: '"docs"', 11: "2"}
    if q in preferred:
        z[preferred[q]] = 2.0

    if profile == "format-confused":
        z['"DROP"'] = 5.0
        z['"extra"'] = 4.0

    if profile == "semantic-mismatch" and q == 7:
        z['"tickets"'] = 5.0
        z['"docs"'] = 1.0

    return z

def softmax(z, toks):
    m = max(z[t] for t in toks)
    weights = {t: exp(z[t] - m) for t in toks}
    total = sum(weights.values())
    return {t: weights.get(t, 0.0) / total for t in V}

def masked_probs(z, q):
    return softmax(z, allowed(q))

def rejection_mass(z, q):
    raw = softmax(z, V)
    return 1.0 - sum(raw[t] for t in allowed(q))

def decode(profile="format-confused", constrained=True, max_steps=32):
    q = 0
    toks = []
    states = [q]

    for _ in range(max_steps):
        z = base_logits(q, profile)
        p = masked_probs(z, q) if constrained else softmax(z, V)
        tok = max(V, key=lambda t: (p[t], -V.index(t)))
        toks.append(tok)
        q_next = step(q, tok)

        if q_next is None:
            return {"tokens": toks, "states": states, "accepted": False}

        q = q_next
        states.append(q)

        if q in ACCEPT:
            return {"tokens": toks, "states": states, "accepted": True}

    return {"tokens": toks, "states": states, "accepted": False}

def parse_source(tokens):
    i = tokens.index('"source"')
    return tokens[i + 2].strip('"')

assert allowed(0) == ["{"]
assert set(allowed(3)) == {'"retrieve"', '"lookup"'}
assert allowed(13) == ["<eos>"]

bad = decode("format-confused", constrained=False)
assert bad["accepted"] is False
assert bad["tokens"][0] == '"DROP"'

good = decode("format-confused", constrained=True)
assert good["accepted"] is True
assert good["tokens"] == [
    "{", '"tool"', ":", '"retrieve"', ",",
    '"source"', ":", '"docs"', ",",
    '"k"', ":", "2", "}", "<eos>",
]

z = base_logits(3, "format-confused")
p = masked_probs(z, 3)
assert p['"DROP"'] == 0.0
assert rejection_mass(z, 3) > 0.90

sem = decode("semantic-mismatch", constrained=True)
assert sem["accepted"] is True
assert parse_source(sem["tokens"]) == "tickets"

Mask, renormalize, then top-p

This standard-library Python code does the worked example above: the masked and renormalized distribution, both orders of the mask and top-pp, and the second state.

def mask_and_renormalize(raw, mask):
    kept = [p * m for p, m in zip(raw, mask)]
    surviving = sum(kept)
    if surviving == 0:                       # any positive mass is renormalized
        raise ValueError("no allowed token has positive probability")
    return [p / surviving for p in kept], surviving

def top_p(dist, threshold):
    order = sorted(range(len(dist)), key=lambda i: (-dist[i], i))
    keep, mass = set(), 0.0
    for i in order:
        keep.add(i)
        mass += dist[i]
        if mass >= threshold:                # stop once the kept mass reaches p
            break
    return [x / mass if i in keep else 0.0 for i, x in enumerate(dist)]

TOKENS = ['"retrieve"', '"lookup"', '"DROP"', '"extra"', "true"]
RAW = [0.20, 0.10, 0.50, 0.15, 0.05]
MASK = [1, 1, 0, 0, 0]

constrained, surviving = mask_and_renormalize(RAW, MASK)
print("surviving mass:", round(surviving, 12), "constrained:", [round(x, 6) for x in constrained])

raw_first = top_p(RAW, 0.4)                  # keeps "DROP" alone
print("top-p first, then mask:", sum(x * m for x, m in zip(raw_first, MASK)))
print("mask first, then top-p:", top_p(constrained, 0.4))

source, _ = mask_and_renormalize([0.10, 0.60, 0.15, 0.10, 0.05], MASK)
print('"docs", "tickets":', [round(x, 6) for x in source[:2]])

assert mask_and_renormalize([1e-10, 1 - 1e-10], [1, 0])[0] == [1.0, 0.0]
Study prompts for this section: Code
Study prompts

Each button copies a prompt about this section to your clipboard, for an AI assistant you already use. Nothing is sent by this site.

Demo

Change an input, predict the result, and see which quantities respond.

ExploreChange one control at a time and name what responds.An optional observation guide follows the demo.

Explore Structured Decoding: Token Masks From Schema Automata

Loading the interactive demo…

Observation guide

Optional and ungraded. Opening a guide saves nothing.

Choose what to inspect in Structured Decoding: Token Masks From Schema Automata, then open its guide.

Use the Schema Mask Explorer to test a prediction. At the current automaton state, inspect the raw logits and predict whether the raw highest-logit token survives the schema mask, gets replaced by the highest valid token, or has no valid continuation.

After reveal, compare raw probability mass with the masked-and-renormalized distribution. Then turn the mask off to see a high-logit invalid token break the prefix, or use the semantic-mismatch profile to see the key limitation: schema-valid output can still choose the wrong source for the task.

The practice after the demo works one step of this by hand: the surviving mass, the renormalized distribution, the order of the mask and top-pp, and a valid but wrong choice.

Study prompts for this section: Demo
Study prompts

Each button copies a prompt about this section to your clipboard, for an AI assistant you already use. Nothing is sent by this site.

Practice

Renormalize the probability the schema allows, see why top-p must come after the mask, then meet a valid token that is the wrong one.

Loading the exercise…
In focus

Concept: Structured Decoding: Token Masks From Schema Automata

What is the smallest example of Structured Decoding you can work through by hand, and what does it show?

BeforeDecoding & Sampling: Temperature, Top-p & Inference-Time ControlNextMoE Serving & Scheduling: Token Dispatch, All-to-All, Disaggregated Parallelism
DetailsLLM Systems

After the first pass

Go deeper

Optional material for a second reading. Sources are always shown; the other panels open from their headings or from these links, and closing one keeps what you wrote or revealed.
What to look for in each sectionChoose a section and what you expect to see in it, then read short advice.

Section by section

Choose what to look for in each section

How a schema automaton or parser state turns next-token logits into constraint-valid generation by masking invalid continuations, while leaving truth and task success outside the formal guarantee.

Choose what to look for (no demo yet)01 / Intuition
Illustration of an automaton gating valid token paths.
What to look for

Start with the picture, metaphor, or geometric mechanism.

Choose first

Choose what you expect to see in this section of Structured Decoding: Token Masks From Schema Automata.

Questions to ask of the pictureChoose a part of the picture and what you expect, then read short advice.

Looking at the picture

Questions to ask of the picture

How a schema automaton or parser state turns next-token logits into constraint-valid generation by masking invalid continuations, while leaving truth and task success outside the formal guarantee.

4 of 4 sections writtenNo live demo yet
Question

Which part of the picture should you look at first?

Choose first

Pick the part of the picture you expect to explain Structured Decoding: Token Masks From Schema Automata.

Sources

References for this page

A reference shows where an idea comes from; it does not vouch for every step on this page.
paper · 2023Efficient Guided Generation for Large Language ModelsWillard and Louf
Why it is cited

Recasts constrained generation as transitions of a finite-state machine or parser, masks tokens that would leave the allowed language, and precomputes an index from states to allowed tokens so masking is cheap (arXiv preprint).

Open source
paper · 2025JSONSchemaBench: A Rigorous Benchmark of Structured Outputs for Language ModelsGeng et al.
Why it is cited

Benchmarks constrained-decoding frameworks on about 10,000 real-world JSON schemas, measuring schema compliance separately from coverage of schema features, efficiency and the quality of the generated output (arXiv preprint).

Open source
What each claim rests on1 claim, each with its sources, where it appears on this page, and its caveats.

Claims and sources

How a schema automaton or parser state turns next-token logits into constraint-valid generation by masking invalid continuations, while leaving truth and task success outside the formal guarantee.

Checked against the cited passages

The checks were made by this site, not by an independent reviewer.

Structured decoding can turn a schema or grammar state into an allowed-token mask, forcing generation to stay inside the formal constraint while leaving semantic truth and task success outside the guarantee.
What the sources support

Willard and Louf support FSM/parser state plus token-logit masks that zero invalid vocabulary continuations. Geng et al. support constrained decoding as invalid-token masking from constraints and prefix toke...

Where to see it on this page
Equation 1
A=(Q,V,δ,q0,F),δ:Q×V⇀Q.A=(Q,\mathcal V,\delta,q_0,F),\qquad \delta:Q\times\mathcal V\rightharpoonup Q.
Equation 2
M(qt)={v∈V:δ(qt,v) is defined and can reach F},pA(v∣y1:t)=exp⁡(ℓt(v)/τ)1[v∈M(qt)]∑u∈M(qt)exp⁡(ℓt(u)/τ).M(q_t)= \{v\in\mathcal V:\delta(q_t,v)\ \mathrm{is\ defined\ and\ can\ reach}\ F\}, \qquad p_A(v\mid y_{1:t})= \frac{\exp(\ell_t(v)/\tau)\mathbf 1[v\in M(q_t)]} {\sum_{u\in M(q_t)}\exp(\ell_t(u)/\tau)}.
Caveat

This covers the formal constraint, not whether the output is true or useful. Real JSON Schema guarantees also depend on tokenization, supported schema features and implementation. The advice to apply top-p a...

StatusChecked against the cited passages.

Willard and Louf support the mask mechanism: guided generation uses the state of a finite-state machine or parser to find the valid next tokens and gives invalid tokens zero probability. Geng et al. support treating schema compliance separately from coverage, efficiency and output quality. The page's math, code and demo work at the level of a few toy tokens; validity there is formal, not a guarantee of truth or task success.

Explain it without the pageWrite your own explanation before asking for feedback. Your draft and any hints stay while this page is open.

Practice · Structured Decoding: Token Masks From Schema Automata

Try the idea in your own words

How a schema automaton or parser state turns next-token logits into constraint-valid generation by masking invalid continuations, while leaving truth and task success outside the formal guarantee.

Concept · Selected for practice

Structured Decoding: Token Masks From Schema Automata

What it rests on: Sources: Efficient Guided Generation for Large Language Models; JSONSchemaBench: A Rigorous Benchmark of Structured Outputs for Language Models

Context and links
Choose a task

Explain the mechanism

For Structured Decoding: Token Masks From Schema Automata: What is the smallest example of Structured Decoding you can work through by hand, and what does it show? Explain your answer, including what changes, why, and which assumption matters.

No answer yet

A rough first thought is enough. Your draft stays when you change tasks.

Your draft stays on this page and is cleared when you leave or reload.

A little help · Explain

Open one hint at a time. These are suggestions, not your answer or a grade.

0 of 3 hints shown for this question.

    Clearing your answer keeps this help record. Outside help is your own report; this page cannot check it.

    Where am I stuck? (optional)
    Your own description, not an automatic diagnosis

    Choose one, or leave this unspecified. Select it again to clear it.

    Take your draft to a feedback conversation

    No AI feedback runs here. You can copy a prompt to use elsewhere; nothing is sent automatically. Review the text before sharing, and leave out private information.

    Write an attempt before copying a feedback prompt.

    A draft, or an AI reply to it, is not a test of what you have learned. To check that, try a different case later without help.

    Ask about this pagePick one item on this page and copy a prompt about it into an AI assistant you already use. Notes you write here stay in this browser.
    Ask about this pageClose
    ConceptStructured Decoding: Token Masks From Schema AutomataSources: Efficient Guided Generation for Large Language Models; JSONSchemaBench: A Rigorous Benchmark of Structured Outputs for Language Modelsassert allowed(0) == ["{"]

    Your question

    Choose what your question is about

    Pick the idea, equation, source, code, claim, misconception or demo state you want to ask about. The prompt you copy includes it, so the answer can stay on that item.
    Next local actionNo local draft saved yet

    Open the draft below to save one note and next action in this browser.

    conceptLLM Systems

    Structured Decoding: Token Masks From Schema Automata

    Starting question

    What is the smallest example of Structured Decoding you can work through by hand, and what does it show?

    SourcesCheck the 2 cited sources listed under Sources.Notes can be saved for this item
    Points of view on this item

    Fixed prompts generated from this item. They are not comments from people or independent reviews.

    Learner’s first stepAsk what would make "Structured Decoding: Token Masks From Schema Automata" feel predictable rather than familiar.
    Assumption

    The 2 cited sources must support this exact item, not just the surrounding topic.

    Source-checking summary

    Connect the definition to one equation, piece of code or demo before widening the discussion.

    Proposed experiment

    Change one input, then check that the same relationship holds in the mathematics, the code and the demo.

    Next action

    You can state the mechanism in your own words

    Evidence4 checks
    ObservationChecking for a saved observation
    ActionReady for one action
    PromptLearner prompt ready to copy
    Go to this item
    01ObservationChecking what this browser has saved
    02EvidenceChecking for a saved observation
    03SourcesCheck the 2 cited sources listed under Sources.
    04Next stepSave one next action
    Local action draftNo local draft saved yetOpen when you are ready to write one next action
    Local action draft

    This draft stays in this browser and is attached to this item only.

    No local draft saved.
    Evidence to inspect
    • What the 2 cited sources say about this exact item
    • The definition, its prerequisites, and a contrasting concept
    • The equation or code that makes the concept concrete
    • One demo state that shows what stays the same as the inputs change
    What would resolve this
    • You can state the mechanism in your own words
    • You can name the prerequisite that would clear up the confusion
    • You can predict how the result changes when one input changes
    Reference idconcept/concept-notebook/llm-systems/structured-decoding concept:llm-systems/structured-decoding