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}