AnyLearn
All lessons
AIadvanced

From Schema to Mask: The Automaton Index

The naive mask costs 64 million validity checks per response. The trick that made constrained decoding practical is to notice that the answer depends only on the automaton's current state, so it can be computed once per state and looked up thereafter. This lesson builds that idea: regex to DFA, why the state is a sufficient summary, how JSON Schema compiles down, and what the index costs.

Updated · AI-authored, review-gated · how lessons are made

Not signed in: your progress and quiz score won't be saved.
Progress1 / 10

Regular languages and the machine that recognises them

Start with the simplest useful class of formats: those describable by a regular expression. Phone numbers, dates, identifiers, enumerations, numeric literals, and a surprising share of the fields inside a JSON schema.

Every regular expression can be converted mechanically into a finite state machine. The construction is standard: the expression becomes a nondeterministic automaton, which is then converted to a deterministic one by the subset construction, and optionally minimised.

A deterministic finite automaton has a set of states, one start state, a set of accepting states, and a transition function taking a state and a character to exactly one next state.

The property that matters for us is determinism. From a given state, reading a given character leads to exactly one place. There is no search, no backtracking, and no ambiguity about where you are.

That is what turns validation into something cheap. The automaton's current state is a complete summary of everything the prefix so far implies about what may come next.

Full lesson text

All 10 steps on one page, for reading, reference, and search.

Show

1. Regular languages and the machine that recognises them

Start with the simplest useful class of formats: those describable by a regular expression. Phone numbers, dates, identifiers, enumerations, numeric literals, and a surprising share of the fields inside a JSON schema.

Every regular expression can be converted mechanically into a finite state machine. The construction is standard: the expression becomes a nondeterministic automaton, which is then converted to a deterministic one by the subset construction, and optionally minimised.

A deterministic finite automaton has a set of states, one start state, a set of accepting states, and a transition function taking a state and a character to exactly one next state.

The property that matters for us is determinism. From a given state, reading a given character leads to exactly one place. There is no search, no backtracking, and no ambiguity about where you are.

That is what turns validation into something cheap. The automaton's current state is a complete summary of everything the prefix so far implies about what may come next.

2. The state is a sufficient summary

This is the observation the whole technique rests on, and it is worth stating carefully because everything else is an implementation of it.

Two different strings that lead the automaton to the same state are, from that point onward, completely interchangeable. Whatever completions are valid after one are valid after the other. The history is irrelevant; only the state matters.

So the question which tokens may be generated next does not actually depend on the text generated so far. It depends only on which state that text landed in.

That converts an expensive per-step question into a cheap per-state one. Instead of asking, at every generation step, which of 128,000 tokens keeps this particular string valid, you ask once per state which of 128,000 tokens are legal from that state, and record the answer.

The number of states is a property of the format, not of the traffic. A schema might compile to a few hundred or a few thousand states. Whether you serve ten requests or ten million, the work of answering the question is done that many times, once.

3. Building the index

The construction, from Willard and Louf's work on guided generation, is direct once the insight is in place.

For each state of the automaton, walk the entire vocabulary. For each token, feed its characters through the automaton starting from that state. If every character has a defined transition and the token does not lead into a dead state, the token is permissible, and you record both that fact and the state it lands in.

The result is a map from state to a set of allowed tokens, together with the resulting state for each. That map is the index.

At generation time the work collapses. You hold the current state, look up its entry, and apply the stored mask. When a token is sampled, you read off the next state from the same entry. No string manipulation, no parsing, no scanning of the vocabulary.

The 64 million validity checks per response from the previous lesson become 500 dictionary lookups.

The cost has not vanished; it has moved. It is now paid once, when the schema is compiled, rather than repeatedly at every generation step.

4. Compile once, look up many times

The pipeline splits cleanly into a compile phase and a serve phase, and knowing which side a cost lands on answers most performance questions about this technique.

Compile time runs once per schema. The JSON Schema or regex is converted into an automaton, the automaton is determinised, and then the vocabulary is walked for every state to build the index. This is the expensive part, and it is proportional to the number of states times the vocabulary size.

Serve time runs once per generated token. Look up the current state, apply the stored mask to the logits, sample, and follow the stored transition to the next state. Constant work, independent of both schema complexity and output length.

The practical consequence is a caching question rather than an algorithmic one. If your service uses a fixed set of schemas, compile them at startup and the per-request cost is nil. If callers supply arbitrary schemas at request time, the compile lands on the first request that uses one, so you want a compiled-grammar cache keyed by schema hash, and you should expect a slow first call.

flowchart TD
A["JSON Schema or regex"] --> B["Convert to a finite automaton"]
B --> C["Determinise and minimise"]
C --> D["For each state, scan the vocabulary"]
D --> E["Index: state to allowed tokens and next states"]
F["Generation step: current state"] --> G["Look up mask in the index"]
G --> H["Apply to logits and sample"]
H --> I["Follow stored transition to next state"]
I --> F
E --> G

5. How JSON Schema gets there

JSON Schema is not a regular expression, so a translation step sits in front of the machinery.

The conversion is largely mechanical for the common subset. A string type with a pattern becomes that pattern. A string without one becomes the regex for a JSON string literal, including escape handling. An integer becomes an optional minus sign followed by digits. An enum becomes an alternation of its literal values.

An object becomes the concatenation of an opening brace, its properties in a fixed order, each as a quoted key, a colon, the recursively translated value regex, separated by commas, and a closing brace.

Optional properties turn each into an alternation between present and absent, which is where the state count grows quickly: n optional fields can produce combinatorially many orderings unless the implementation pins an order.

That detail explains a behaviour people notice. Constrained generation usually emits fields in schema order rather than in whatever order the model would prefer, because pinning the order keeps the automaton small.

And it explains why deeply nested or highly optional schemas compile slowly: the state count, not the text length, is what grows.

6. What the index costs to store

The index trades memory for speed, and it is worth knowing the shape of that trade before enabling it on a large schema.

The natural representation is one bitmask per state over the vocabulary. With 128,000 tokens that is 16 kilobytes per state. A thousand-state automaton therefore needs about 16 megabytes of masks, which is unremarkable next to model weights.

A pathological schema with a hundred thousand states would need 1.6 gigabytes, which is not.

Two properties keep this manageable in practice. Masks are extremely sparse in one direction or the other: in most states only a handful of tokens are legal, and inside a free-text string almost all are. Both extremes compress well, so implementations store token lists for restrictive states and complements for permissive ones.

And many states share identical masks. Every position inside an unconstrained string field permits the same set, so deduplicating masks by content collapses long runs of states onto one stored mask.

The number to watch when a schema behaves badly is state count, and the usual cause is optional fields or unbounded repetition rather than schema size on the page.

7. Tokens are not characters

There is a mismatch at the heart of this that the index construction quietly handles, and that has to be understood to reason about the failures.

The automaton is defined over characters. The model emits tokens, which are multi-character pieces chosen by a tokenizer for compression rather than for any structural reason. A single token can contain a quote, a colon and the start of a word.

The index construction accommodates this by feeding each token's whole character sequence through the automaton. A token is permissible only if every one of its characters has a valid transition, and the recorded next state is where the last character lands.

The consequence is that a token straddling a structural boundary is fine, provided the whole crossing is legal. What is excluded is a token whose first half is legal and whose second half is not.

This is why the set of permissible tokens is often much smaller than intuition suggests. At a point where the grammar requires a closing brace, only tokens that begin with a closing brace and continue legally survive, and a tokenizer optimised for English prose has few of those.

8. The tokenization mismatch

The deeper version of that mismatch causes a subtle quality problem worth recognising, because it is invisible in output that parses perfectly.

A given string usually has many possible tokenizations. The tokenizer, applied to complete text, produces one canonical segmentation, generally the one using the fewest, longest pieces. That canonical form is what the model saw throughout training.

Constrained decoding does not necessarily reproduce it. The mask permits any token that keeps the string valid, and sampling may pick a shorter piece where the canonical tokenization would have used a longer one. The resulting text is identical; the token sequence is not.

The model is then conditioning on a token sequence with a shape it rarely encountered in training. The text looks right, and the internal representation is slightly off-distribution.

Related is the token healing problem: when a prompt ends mid-word, the final prompt token may be a fragment the model would never have produced there, and continuing from it degrades quality. Implementations handle this by backing up over the last token and re-generating it under a constraint that it match the fragment.

Both are reasons to prefer a well-tested implementation over rolling your own.

9. Where the index approach runs out

Finite automata recognise exactly the regular languages, and that is a real ceiling rather than an implementation limit.

A finite automaton has finitely many states and no memory beyond which state it is in. That makes it incapable of counting without bound.

JSON needs unbounded counting. An object may contain an array containing an object containing an array, to any depth, and every opening brace must eventually be matched by a closing brace. To validate that, you must remember how many are currently open, and a machine with a fixed number of states cannot remember an arbitrary number.

So JSON in full generality is not a regular language, and no finite automaton recognises it.

The practical escape is that a concrete schema usually is regular. If the schema fixes the nesting depth, and most do, the depth is bounded and a finite automaton suffices. That is why the index approach works so well for typical structured output.

It stops working when the format is genuinely recursive: arbitrary JSON, a programming language, a query dialect with nested expressions. For those you need a machine with a stack.

10. What to check when it misbehaves

Three symptoms come up repeatedly with index-based constrained decoding, and each maps to a specific cause.

The first request is slow and subsequent ones are fast. That is compilation, and it is expected. Cache compiled grammars by schema hash, and pre-warm the schemas you know about at startup.

Compilation is slow or memory use is high for a schema that looks small. Look at state count rather than schema length. Optional fields, unbounded repetition and deep nesting are the usual causes. Pinning field order and bounding array lengths often reduces the automaton dramatically.

Output parses but quality dropped. This is the interesting one, and it is rarely the index's fault. Check whether the schema forces the model into fields it cannot fill, whether it is being made to answer before reasoning, and whether the field ordering matches how the model would naturally derive the answer.

That third category is not an engineering problem at all, which is why the last lesson of this path is about it rather than about automata.

Check your understanding

The lesson ends with a 5-question quiz. Take it in the player above to see your score.

  1. What makes the automaton's current state a sufficient basis for computing the mask?
    • It stores the full text generated so far
    • Any two prefixes reaching the same state have identical sets of valid completions, so history beyond the state is irrelevant
    • It records which tokens the model has already used
    • It is recomputed from the schema at every step
  2. Where does the cost of index-based constrained decoding land?
    • Evenly across every generated token
    • On the sampler, which must be modified
    • At compile time, proportional to states times vocabulary size, with near-constant work per generated token
    • On the model forward pass, which runs twice
  3. A schema with several optional fields compiles slowly and uses a lot of memory. Why?
    • Optional fields require a separate model call each
    • The vocabulary must be re-tokenized per field
    • Optional fields are stored in 32-bit masks rather than bitmasks
    • Each optional field becomes an alternation, and unpinned field order can produce combinatorially many states
  4. Why can a finite automaton not validate arbitrary JSON?
    • Because JSON allows Unicode escapes
    • Because matching arbitrarily deep nesting requires unbounded counting, which a machine with finitely many states cannot do
    • Because JSON keys are unordered
    • Because tokenizers split braces inconsistently
  5. What is the tokenization mismatch problem in constrained decoding?
    • The mask may permit a non-canonical segmentation of the same text, so the model conditions on a token sequence it rarely saw in training
    • Tokens containing structural characters are always rejected
    • The automaton must be rebuilt for each tokenizer version
    • Constrained output uses more tokens than unconstrained output

Related lessons

AI
intermediate

What This Teaches About Measuring Anything

The exchange is a case study with transferable rules. A conclusion resting on failures needs a failure taxonomy. Every instance must be verified solvable before anyone is scored against it. Output format is a confound whenever answers get long. And when two explanations fit the same data, the productive move is to find the prediction on which they differ, then test it.

8 steps·~12 min
AI
intermediate

The Rebuttal: Three Ways to Score Zero Without Failing

The response disputed none of the data and argued the experiment measured something other than reasoning. Models had to print move lists exceeding their output limits, and said so in the transcripts. Some instances had no solution and were scored as failures anyway. And asking for a program instead of a move list produced high accuracy on instances reported as total collapse.

8 steps·~12 min
AI
intermediate

The Experiment: Puzzles With a Difficulty Dial

Apple researchers built an evaluation designed to fix a real problem with benchmarks: puzzles where difficulty turns up smoothly while the logic stays identical, and every step can be checked. They found accuracy collapsing to zero past a threshold, and not improving when the solution algorithm was handed to the model. This lesson covers the design and why it was a good one.

8 steps·~12 min
AI
intermediate

Ten Domains, and a Profile That Is Not Flat

The framework scores ten cognitive domains at ten percent each. Running it produces something more useful than the headline totals of 27 percent for GPT-4 and around 57 for GPT-5: a jagged profile, where a model is at or near full marks on some domains and at zero on others. This lesson walks the domains, reads both profiles column by column, and shows what the jaggedness explains.

8 steps·~12 min