Skip to content

Build grammar expressions directly in a shared finite-state machine - #727

Merged
Ubospica merged 8 commits into
mlc-ai:mainfrom
Ubospica:perf/fsm-sequence-build
Jul 28, 2026
Merged

Ubospica merged 8 commits into
mlc-ai:mainfrom
Ubospica:perf/fsm-sequence-build

Conversation

@Ubospica

@Ubospica Ubospica commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Changes

A finite-state machine (FSM) represents valid paths using states and transitions. Previously, the implementation built a small FSM for every sequence element and every choice branch, then copied those FSMs into a larger one. Large grammars therefore repeatedly created, copied, and destroyed short-lived objects.

This PR builds arbitrarily nested grammar expressions directly into a shared target FSM:

  • Byte strings, rule references, character classes, repetitions, token edges, and excluded-token edges can all be appended directly.
  • Sequence subexpressions reuse start and end states, while choice branches stream into the same target.
  • The construction order deliberately preserves the state IDs, edge insertion order, start state, and end state of the previous output.

Correctness

  • All 427 Python tests covering grammars, compilers, parsers, matchers, and the new FSM implementation pass.
  • All 61 C++ tests pass.
  • The main branch and this branch compiled the same set of 17 grammars; their printed FSMs and fully serialized grammars matched byte-for-byte across 177,330 output bytes.
  • Added 24 real behavior cases and 17 exact-layout cases covering nested expressions, multibyte text, recursive rule references, token edges, and consistency of the allowed-token set at every step.

Performance

Standalone FSM construction benchmarks:

  • A long sequence of 10,000 literal and character-class pairs: 12.656 -> 5.518 ms, a 56.4% reduction and 2.29x speedup.
  • A single choice with 8,000 branches: 74.397 -> 66.402 ms, a 10.7% reduction and 1.12x speedup.
  • Full construction of a cycle containing 8,000 rules: 46.813 -> 37.766 ms, a 19.3% reduction and 1.24x speedup.

Across four inputs, full grammar optimization time decreased by 10.2% to 20.2%. For a JSON Schema with 50,000 fields, the first full compilation dropped from 4440.5 ms to 4237.0 ms, a 4.6% reduction. This PR does not change allowed-token-set generation during decoding.

Ubospica added 3 commits July 23, 2026 04:46
Add behavior tests for sequence FSM construction: exhaustive comparisons
against reference regexes, UTF-8 byte strings, long mixed sequences,
recursive rule references, large repetition ranges, token edges, and
bitmask consistency with string acceptance.
Ubospica added 2 commits July 28, 2026 05:17
Stream sequence and choice fragments into one target FSM so element builders can reuse caller-provided start states without temporary FSM copies.
@Ubospica
Ubospica marked this pull request as ready for review July 28, 2026 10:32
Copilot AI review requested due to automatic review settings July 28, 2026 10:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors FSM construction during grammar compilation to stream sequence elements and choice branches directly into a single target FSM, reducing intermediate FSM allocations/copies and improving compile-time performance while preserving resulting FSM structure.

Changes:

  • Refactor C++ FSM builder to build sequences/choices by appending directly into a caller-owned target FSM and returning end-state ID sets.
  • Update character-class range building helpers to operate directly on FSM (instead of FSMWithStartEnd) to support in-place construction.
  • Add a new Python correctness test suite covering sequence construction across element types, recursion, UTF-8, and token/bitmask behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
tests/python/test_fsm_sequence_build.py Adds extensive correctness coverage for streamed sequence/choice FSM construction and token-bitmask behavior.
cpp/grammar_functor.cc Refactors sequence/choices FSM building to stream into a shared target FSM; adapts character-range helpers for in-place FSM mutation.
Comments suppressed due to low confidence (1)

tests/python/test_fsm_sequence_build.py:98

  • The return type annotation -> (str, str) is not a valid typing return type for mypy (it’s a runtime tuple value). Since the project supports Python 3.8 and uses mypy strict, prefer Tuple[str, str].
def _build_long_sequence_grammar(num_segments: int) -> (str, str):

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

import itertools
import re
import sys
from typing import List
Ubospica added 3 commits July 28, 2026 07:49
Require fragment builders to hold a valid target FSM and centralize per-rule dispatch, while preserving existing standalone builder entry points. Add golden structure checks that detect any change in state numbering, edge order, or complete-FSM layout.
Regenerate the JSON-pattern case with the explicit main native library so the golden digest tracks the intended baseline rather than an installed package.
Build rule state machines recursively so construction no longer depends on the normalized choice-sequence-element layout.
@Ubospica
Ubospica force-pushed the perf/fsm-sequence-build branch 2 times, most recently from d8a264f to 23746d7 Compare July 28, 2026 13:35
@Ubospica Ubospica changed the title perf: build sequence FSMs by streaming elements into one target FSM perf: build grammar expressions directly into a shared FSM Jul 28, 2026
@Ubospica
Ubospica merged commit 2b44e8c into mlc-ai:main Jul 28, 2026
18 of 57 checks passed
@Ubospica
Ubospica deleted the perf/fsm-sequence-build branch July 28, 2026 13:44
Ubospica added a commit that referenced this pull request Jul 28, 2026
## Summary

- consolidate the 14 EBNF exact-layout cases into the FSM
sequence-builder test suite added by #727
- remove two JSON Schema snapshots and one structural-tag snapshot whose
layouts change with unrelated converter implementations
- fix the long-sequence helper's Python 3.8-compatible tuple return
annotation

## Test plan

- [x] Build the native library from the latest `main`
- [x] Run all 38 consolidated FSM builder tests with that branch-native
library
- [x] Run Black, isort, and Ruff checks
- [x] Run repository pre-commit hooks
@Ubospica Ubospica changed the title perf: build grammar expressions directly into a shared FSM Build grammar expressions directly in a shared finite-state machine Jul 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants