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}