metrale_grammar/matcher/
coalesce.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2//
3// GrammarMatcher — Coalescence forced-token fast-path (Tier 3b).
4//
5// The stateful half of the dottxt.ai "Coalescence" optimization
6// (<https://blog.dottxt.ai/coalescence.html>). The pure analysis lives
7// in `crate::compiler::coalesce`; this file wires it onto the matcher.
8//
9// IDEA. In a constrained grammar many states admit exactly one legal
10// token — after `{` in a JSON object, while spelling a literal key,
11// inside a fixed enum value. When the grammar forces the token the
12// model sampling step is redundant: the token is determined. Metrale Engine's
13// scheduler can call `forced_token` / `next_forced_tokens`, emit the
14// forced token(s) directly, and skip the GPU sample for those
15// positions. dottxt reports up to 5x on nested structures.
16//
17// API (all ADDITIVE — the existing matcher API is untouched):
18//   * `forced_token()`        — the single forced token, if any.
19//   * `next_forced_tokens()`  — the maximal forced *chain* from here.
20//   * `fill_next_token_bitmask` early-out is handled in `fill.rs`.
21//
22// CORRECTNESS
23// -----------
24// `forced_token` computes the SAME next-token bitmask the normal
25// sampling path computes (`compute_partitions` + `set_token_bitmask`),
26// then asks `analyze_bitmask` whether exactly one bit is set. A token
27// is reported forced ONLY when it is the sole bit in that authoritative
28// mask — so it is, by construction, the only grammar-legal token.
29// Feeding it back through `accept_token` therefore lands the matcher in
30// exactly the state the normal path would (the normal path could only
31// have sampled that same token). When zero / two-or-more tokens are
32// legal, `forced_token` returns `None` and the caller falls through.
33//
34// `forced_token` and `next_forced_tokens` leave the matcher state
35// completely unchanged: the chain walk virtually accepts each forced
36// token to look ahead, then rolls every virtual accept back before
37// returning.
38
39use crate::compiler::analyze_bitmask;
40
41use super::bitmask::{BitmaskSlice, bitmask_size};
42use super::matcher::GrammarMatcher;
43
44impl GrammarMatcher {
45    /// The single grammar-forced next token, if the current state
46    /// admits exactly one legal token.
47    ///
48    /// Returns `Some(token_id)` when the constrained grammar leaves the
49    /// caller no choice — the token is fully determined, so the model
50    /// sampling step for this position can be skipped and `token_id`
51    /// emitted directly (then fed back through [`Self::accept_token`]).
52    /// Returns `None` when the continuation is a genuine choice (two or
53    /// more legal tokens), the state is dead (zero legal tokens), or the
54    /// matcher has already terminated.
55    ///
56    /// This does NOT mutate the matcher: it computes the next-token
57    /// bitmask exactly as [`Self::fill_next_token_bitmask`] would and
58    /// inspects its cardinality. The returned token is the unique set
59    /// bit of that authoritative mask, so accepting it is equivalent to
60    /// the normal sample-then-accept path.
61    ///
62    /// The packed-bitmask buffer is reused from the matcher's scratch
63    /// (`FillScratch::coalesce_bitmask`), so a repeated `forced_token`
64    /// call — e.g. inside the [`Self::next_forced_tokens`] chain walk —
65    /// costs no per-call heap allocation.
66    #[must_use]
67    pub fn forced_token(&mut self) -> Option<i32> {
68        if self.stop_token_accepted() {
69            return None;
70        }
71        let vocab = self.tokenizer_info().vocab_size();
72        let words = bitmask_size(vocab);
73        // Take the reusable buffer out of scratch for the duration of
74        // the fill (a `Default` left in its place), resize once, and
75        // restore it before returning — the same `mem::take` discipline
76        // `fill_next_token_bitmask` uses for its own scratch.
77        let mut buf = std::mem::take(&mut self.scratch.coalesce_bitmask);
78        if buf.len() < words {
79            buf.resize(words, 0);
80        }
81        // `fill_next_token_bitmask` cannot fail here: not terminated
82        // (checked above) and the buffer is exactly `bitmask_size`.
83        let forced = match self.fill_next_token_bitmask(&mut buf, 0, false) {
84            Ok(_) => {
85                BitmaskSlice::new(&mut buf, vocab).and_then(|view| analyze_bitmask(&view).token())
86            }
87            Err(_) => None,
88        };
89        self.scratch.coalesce_bitmask = buf;
90        forced
91    }
92
93    /// Inspect an *already-filled* next-token bitmask for the
94    /// forced-token condition — the zero-extra-work entry point.
95    ///
96    /// A caller that has just run [`Self::fill_next_token_bitmask`] for
97    /// the current decode step already holds the authoritative mask;
98    /// it can pass that buffer here to learn whether the step is forced
99    /// without a second partition computation. `bitmask` / `index` must
100    /// be the same buffer and slice index handed to the fill call.
101    ///
102    /// Returns `Some(token_id)` iff the mask has exactly one bit set
103    /// (the forced token), `None` otherwise. Behaves identically to
104    /// [`Self::forced_token`] but reuses the caller's mask, so the
105    /// per-token server loop pays the coalescence check for free:
106    ///
107    /// ```ignore
108    /// let nontrivial = matcher.fill_next_token_bitmask(&mut buf, 0, false);
109    /// if let Some(tok) = matcher.forced_from_bitmask(&buf, 0) {
110    ///     // skip the model sample — emit `tok` directly
111    /// }
112    /// ```
113    #[must_use]
114    pub fn forced_from_bitmask(&self, bitmask: &mut [i32], index: usize) -> Option<i32> {
115        let vocab = self.tokenizer_info().vocab_size();
116        let words = bitmask_size(vocab);
117        let start = index.checked_mul(words)?;
118        let slice = bitmask.get_mut(start..start.checked_add(words)?)?;
119        let view = BitmaskSlice::new(slice, vocab)?;
120        analyze_bitmask(&view).token()
121    }
122
123    /// The maximal *chain* of grammar-forced tokens starting at the
124    /// current state.
125    ///
126    /// Coalescence's forced-chain: after emitting a forced token the
127    /// next state may itself be forced. This walks that chain and
128    /// returns every forced token in order, so the caller can skip the
129    /// model sample for the whole run at once. The returned vector is
130    /// empty when the current state is not forced.
131    ///
132    /// The matcher state is left completely UNCHANGED: each forced
133    /// token is virtually accepted to peek at the next state, and every
134    /// such virtual accept is rolled back before returning. The caller
135    /// is responsible for feeding the returned tokens back through
136    /// [`Self::accept_token`] for real.
137    ///
138    /// The walk stops at the first non-forced state, at a dead state,
139    /// or once a forced token terminates the matcher (a forced stop
140    /// token is included as the final element — the matcher would
141    /// terminate there, so there is nothing further to force).
142    ///
143    /// A `max` cap bounds the chain length; pass [`usize::MAX`] for an
144    /// unbounded walk. A cap of `0` returns an empty vector.
145    #[must_use]
146    pub fn next_forced_tokens(&mut self, max: usize) -> Vec<i32> {
147        let mut chain = Vec::new();
148        if max == 0 || self.stop_token_accepted() {
149            return chain;
150        }
151        // Each iteration: detect a forced token, record it, then
152        // virtually accept it to advance to the next state. Every
153        // virtual accept is undone by the matched `rollback` below, so
154        // the matcher is byte-identical to its entry state on return.
155        let mut virtual_steps = 0i32;
156        while chain.len() < max {
157            let Some(tok) = self.forced_token() else {
158                break;
159            };
160            chain.push(tok);
161            // Virtually accept to peek at the next state. `accept_token`
162            // records one rollback step (a forced stop token records a
163            // zero-length step and terminates — handled by `rollback`).
164            if !self.accept_token(tok, false) {
165                // The forced token failed to apply. This cannot happen
166                // for a token read off the authoritative mask, but if
167                // it ever did, drop it from the chain rather than
168                // returning an unaccepted token to the caller.
169                chain.pop();
170                break;
171            }
172            virtual_steps += 1;
173            if self.is_terminated() {
174                // A forced stop token (or root completion under
175                // `terminate_without_stop_token`): nothing can follow.
176                break;
177            }
178        }
179        // Undo every virtual accept — restore the entry state exactly.
180        if virtual_steps > 0 {
181            self.rollback(virtual_steps);
182        }
183        chain
184    }
185
186    /// Detect the forced chain and ACCEPT it in place — the efficient
187    /// server primitive for Coalescence.
188    ///
189    /// Unlike [`Self::next_forced_tokens`], which peeks and rolls back
190    /// (leaving the caller to re-accept every token), this advances the
191    /// matcher *as it walks*: each forced token is detected and kept.
192    /// The matcher's parser is therefore traversed once, not twice — no
193    /// rollback, no caller re-accept. The returned vector lists the
194    /// tokens accepted, in order, so the caller can append them to its
195    /// output and skip the model sample for each.
196    ///
197    /// On return the matcher state has advanced past the whole forced
198    /// run — exactly as if the caller had sampled and accepted those
199    /// tokens one by one. The walk stops at the first non-forced state
200    /// (the next position is a genuine choice and must be sampled), at
201    /// a dead state, or once a forced token terminates the matcher.
202    ///
203    /// `max` caps the run length (`usize::MAX` for unbounded); `0`
204    /// accepts nothing and returns an empty vector.
205    pub fn accept_forced_chain(&mut self, max: usize) -> Vec<i32> {
206        let mut accepted = Vec::new();
207        if max == 0 || self.stop_token_accepted() {
208            return accepted;
209        }
210        while accepted.len() < max {
211            let Some(tok) = self.forced_token() else {
212                break;
213            };
214            // `forced_token` read `tok` off the authoritative mask, so
215            // the accept must succeed; guard defensively all the same.
216            if !self.accept_token(tok, false) {
217                break;
218            }
219            accepted.push(tok);
220            if self.is_terminated() {
221                break;
222            }
223        }
224        accepted
225    }
226}