metrale_grammar/matcher/fill.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2//
3// GrammarMatcher — next-token bitmask fill and jump-forward search.
4//
5// Port of `FillNextTokenBitmask`, `SetTokenBitmask` and
6// `FindJumpForwardString` from `cpp/grammar_matcher.cc`.
7//
8// FILL ALGORITHM (faithful to the C++)
9// ------------------------------------
10// Each latest scanable Earley state has a precompiled `AdaptiveTokenMask`
11// partitioning the vocabulary into accepted / rejected / uncertain.
12// * Accepted union — a token accepted by *any* state is accepted.
13// * Rejected intersect — a token is rejected only if *every* state
14// rejects it; tracked as a running `tmp_rejected` interset that
15// starts as the universal set `{-1}`.
16// * Uncertain tokens — resolved by trial: a one-state probe is pushed
17// onto the parser, the token's bytes are advanced, and the parser's
18// verdict decides accept/reject. The probe is popped afterward, so
19// the matcher state is unchanged.
20// `set_token_bitmask` then turns the accepted-bitset + rejected-interset
21// into the packed `i32` mask, adding stop tokens when the root rule can
22// complete and clearing special tokens.
23
24use crate::compiler::StoreType;
25use crate::support::int_set::{intset_intersection, intset_union};
26
27use super::bitmask::BitmaskSlice;
28use super::matcher::{FillScratch, GrammarMatcher};
29
30impl GrammarMatcher {
31 /// Fill `bitmask` with the set of tokens acceptable for the next
32 /// decode position.
33 ///
34 /// `index` selects the matcher's slice when the caller batches
35 /// several matchers into one tensor; for a single matcher pass `0`.
36 /// `debug` is accepted for API parity (no effect here).
37 ///
38 /// Returns `Ok(true)` when the mask is *not* all-accepting (so it
39 /// must be applied to the logits), `Ok(false)` when every token is
40 /// legal. Returns `Err` if the matcher has terminated or the buffer
41 /// is too small. Port of `FillNextTokenBitmask`.
42 pub fn fill_next_token_bitmask(
43 &mut self,
44 bitmask: &mut [i32],
45 index: usize,
46 debug: bool,
47 ) -> Result<bool, FillError> {
48 let _ = debug;
49 if self.stop_token_accepted() {
50 return Err(FillError::Terminated);
51 }
52 let vocab_size = self.tokenizer_info().vocab_size();
53 let words = super::bitmask::bitmask_size(vocab_size);
54 let start = index.checked_mul(words).ok_or(FillError::BadIndex)?;
55 let slice = bitmask
56 .get_mut(start..start + words)
57 .ok_or(FillError::BufferTooSmall)?;
58 let mut view = BitmaskSlice::new(slice, vocab_size).ok_or(FillError::BufferTooSmall)?;
59
60 // Take the reusable scratch buffers for the duration of the
61 // fill (a `Default::default()` left in their place), so the hot
62 // path owns them outright with no per-token allocation. They
63 // are restored unconditionally before returning.
64 let mut scratch = std::mem::take(&mut self.scratch);
65 let can_reach_end = self.compute_partitions(&mut scratch);
66 self.set_token_bitmask(
67 &mut view,
68 &scratch.accepted,
69 &scratch.rejected,
70 can_reach_end,
71 );
72 self.scratch = scratch;
73 Ok(!view.all_set())
74 }
75
76 /// Populate `scratch.accepted` (accepted-bitset) and
77 /// `scratch.rejected` (rejected-interset) over every latest scanable
78 /// state, resolving uncertain tokens by trial. Returns
79 /// `can_reach_end`.
80 ///
81 /// All working buffers are reused from `scratch` — `.clear()`ed,
82 /// not reallocated — so this runs with zero heap traffic on the hot
83 /// path. The vocab-sized `sorted_decoded_vocab` and
84 /// `trie_subtree_nodes_range` are borrowed in place from
85 /// `self.compiled_grammar` (a field disjoint from `self.parser` and
86 /// `self.scratch`), never cloned.
87 fn compute_partitions(&mut self, scratch: &mut FillScratch) -> bool {
88 let vocab_size = self.tokenizer_info().vocab_size();
89 // `accepted[token_id]` — union of every state's accepted set.
90 scratch.accepted.clear();
91 scratch.accepted.resize(vocab_size, false);
92 // `rejected` — running intersection; `{-1}` is the universal set.
93 scratch.rejected.clear();
94 scratch.rejected.push(-1);
95
96 // Snapshot the parser's latest scanable states (`ParserState`
97 // is `Copy`) into the scratch buffer once, so the trial loop
98 // below can mutate `self.parser` freely without holding a
99 // borrow of `latest_scanable_states()`.
100 scratch.live_states.clear();
101 scratch
102 .live_states
103 .extend_from_slice(self.parser.latest_scanable_states());
104
105 // Resolve each live scanable state to the canonical
106 // `ParserState` the compiler keys its mask under (live states
107 // carry real positions; the mask cache is position-agnostic),
108 // then JIT-compile (or fetch from the lazy cache) its
109 // `AdaptiveTokenMask`. `get_or_compute_mask` returns an `Arc`.
110 let root_rule_id = self.compiled_grammar.grammar().root_rule_id();
111 scratch.states.clear();
112 for i in 0..scratch.live_states.len() {
113 let live = scratch.live_states[i];
114 let canon = self.canonical_mask_state(&live);
115 let is_root = canon.rule_id == root_rule_id;
116 let mask = self.compiled_grammar.get_or_compute_mask(canon, is_root);
117 scratch.states.push((live, mask));
118 }
119
120 // Vocab-sized tables borrowed in place — `self.compiled_grammar`
121 // is disjoint from `self.parser` and `scratch`.
122 let sorted = self
123 .compiled_grammar
124 .tokenizer_info()
125 .sorted_decoded_vocab();
126 let subtree = self
127 .compiled_grammar
128 .tokenizer_info()
129 .trie_subtree_nodes_range();
130
131 // Pass 1: seed `accepted` from every state's static accepted set.
132 for (_, mask) in &scratch.states {
133 let mask = mask.as_ref();
134 match mask.store_type {
135 StoreType::AcceptedBitset => {
136 for (tid, slot) in scratch.accepted.iter_mut().enumerate() {
137 if mask.accepted_bitset[tid] {
138 *slot = true;
139 }
140 }
141 }
142 StoreType::Accepted => {
143 for &idx in &mask.accepted_indices {
144 scratch.accepted[sorted[idx as usize].0 as usize] = true;
145 }
146 }
147 StoreType::Rejected => {}
148 }
149 }
150
151 // Pass 2: resolve uncertain tokens per state via trial advance.
152 // Iterate by index so `scratch.states` is not borrowed across
153 // the `self.parser` mutation; the `Arc<AdaptiveTokenMask>` is
154 // cheap to clone and keeps the mask alive without that borrow.
155 for si in 0..scratch.states.len() {
156 let live = scratch.states[si].0;
157 let mask = std::sync::Arc::clone(&scratch.states[si].1);
158 let mask = mask.as_ref();
159 scratch.rejected_delta.clear();
160 self.parser.push_one_state_to_check(live);
161
162 let mut prev_token: Option<&[u8]> = None;
163 let mut prev_matched = 0usize;
164 let mut last_rejected_range = 0i32;
165
166 for &cur_idx in &mask.uncertain_indices {
167 let (cur_id, ref cur_token) = sorted[cur_idx as usize];
168 if scratch.accepted[cur_id as usize] {
169 continue;
170 }
171 if cur_idx < last_rejected_range {
172 if mask.store_type == StoreType::Rejected {
173 scratch.rejected_delta.push(cur_idx);
174 }
175 continue;
176 }
177
178 let mut is_accepted = true;
179 // Reuse the longest common prefix already matched.
180 if let Some(prev) = prev_token {
181 let lcp = cur_token
182 .iter()
183 .zip(prev.iter())
184 .take_while(|(a, b)| a == b)
185 .count();
186 if lcp > prev_matched {
187 last_rejected_range = subtree[cur_idx as usize];
188 is_accepted = false;
189 } else if lcp < prev_matched {
190 self.parser.pop_last_states(prev_matched - lcp);
191 }
192 prev_matched = prev_matched.min(lcp);
193 }
194
195 // Advance the remaining bytes of this token.
196 if is_accepted {
197 for (j, &byte) in cur_token.iter().enumerate().skip(prev_matched) {
198 if !self.parser.advance(byte) {
199 last_rejected_range = subtree[cur_idx as usize];
200 is_accepted = false;
201 break;
202 }
203 prev_matched = j + 1;
204 }
205 }
206
207 match mask.store_type {
208 StoreType::AcceptedBitset | StoreType::Accepted => {
209 if is_accepted {
210 scratch.accepted[cur_id as usize] = true;
211 }
212 }
213 StoreType::Rejected => {
214 if !is_accepted {
215 scratch.rejected_delta.push(cur_idx);
216 }
217 }
218 }
219 prev_token = Some(cur_token);
220 }
221
222 // Pop the bytes advanced for the last uncertain token plus
223 // the one-state probe step itself.
224 self.parser.pop_last_states(prev_matched + 1);
225
226 if mask.store_type == StoreType::Rejected {
227 // rejected = intersect(rejected, mask.rejected ∪ delta)
228 intset_union(&mut scratch.rejected_delta, &mask.rejected_indices);
229 intset_intersection(&mut scratch.rejected, &scratch.rejected_delta);
230 }
231 }
232
233 self.parser.is_completed()
234 }
235
236 /// Turn the accepted-bitset + rejected-interset into the packed
237 /// mask. Port of `SetTokenBitmask`.
238 fn set_token_bitmask(
239 &self,
240 view: &mut BitmaskSlice<'_>,
241 accepted: &[bool],
242 rejected: &[i32],
243 can_reach_end: bool,
244 ) {
245 let sorted = self.tokenizer_info().sorted_decoded_vocab();
246 if rejected.len() == 1 && rejected[0] == -1 {
247 // Universal rejected set: accepted set is exactly `accepted`.
248 view.clear();
249 for (tid, &ok) in accepted.iter().enumerate() {
250 if ok {
251 view.set(tid, true);
252 }
253 }
254 if can_reach_end {
255 for &id in self.stop_token_ids() {
256 view.set(id as usize, true);
257 }
258 }
259 } else {
260 // Final rejected set is `rejected \ accepted`.
261 view.fill_all();
262 for &idx in rejected {
263 let id = sorted[idx as usize].0 as usize;
264 if !accepted[id] {
265 view.set(id, false);
266 }
267 }
268 for &id in self.tokenizer_info().special_token_ids() {
269 view.set(id as usize, false);
270 }
271 if !can_reach_end {
272 for &id in self.stop_token_ids() {
273 view.set(id as usize, false);
274 }
275 }
276 }
277 }
278
279 /// Find the jump-forward string: the longest prefix that is forced
280 /// by the grammar regardless of which token is sampled.
281 ///
282 /// The matcher state is left unchanged. Port of
283 /// `FindJumpForwardString`: at each step every latest scanable
284 /// state must agree on exactly one acceptable byte; the union of
285 /// acceptable bytes having size 1 is the equivalent condition.
286 #[must_use]
287 pub fn find_jump_forward_string(&mut self) -> Vec<u8> {
288 if self.stop_token_accepted() {
289 return Vec::new();
290 }
291 let mut result = Vec::new();
292 let mut advanced = 0usize;
293 loop {
294 if self.parser.is_completed() {
295 break;
296 }
297 let mask = self.parser.acceptable_byte_mask();
298 let mut only: Option<u8> = None;
299 let mut unique = true;
300 for (b, &ok) in mask.iter().enumerate() {
301 if ok {
302 if only.is_some() {
303 unique = false;
304 break;
305 }
306 only = Some(b as u8);
307 }
308 }
309 let next = match (unique, only) {
310 (true, Some(b)) => b,
311 _ => break,
312 };
313 if !self.parser.advance(next) {
314 break;
315 }
316 result.push(next);
317 advanced += 1;
318 }
319 if advanced > 0 {
320 self.parser.pop_last_states(advanced);
321 }
322 result
323 }
324
325 /// `find_jump_forward_string` as a UTF-8 `String`, lossily decoding
326 /// non-UTF-8 bytes. Convenience for callers that want text.
327 #[must_use]
328 pub fn find_jump_forward_string_lossy(&mut self) -> String {
329 String::from_utf8_lossy(&self.find_jump_forward_string()).into_owned()
330 }
331}
332
333/// Why [`GrammarMatcher::fill_next_token_bitmask`] could not fill.
334#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
335pub enum FillError {
336 /// The matcher already accepted a stop token and terminated.
337 #[error("matcher has terminated; cannot fill next-token bitmask")]
338 Terminated,
339 /// The supplied buffer is shorter than `bitmask_size(vocab_size)`.
340 #[error("bitmask buffer too small for the vocabulary")]
341 BufferTooSmall,
342 /// `index * bitmask_size` overflowed `usize`.
343 #[error("bitmask index out of range")]
344 BadIndex,
345}