metrale_grammar/compiler/
coalesce.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2//
3// Coalescence — forced-token fast-path analysis.
4//
5// Implements the "Coalescence" optimization from dottxt.ai
6// (<https://blog.dottxt.ai/coalescence.html>): in a constrained
7// grammar many parser states admit *exactly one* legal continuation.
8// After `{` in a JSON object the next character is forced; while
9// spelling a literal object key only one byte sequence is legal. When
10// the grammar admits exactly one token, the model sampling step is
11// redundant — the token is fully determined by the grammar.
12//
13// This module is the pure, side-effect-free analysis half. It answers
14// one question, authoritatively:
15//
16//     "Given the next-token accept bitmask the matcher just computed,
17//      is the continuation FORCED — i.e. is there exactly one legal
18//      token?"
19//
20// The matcher (`src/matcher/`) owns the stateful half: it computes the
21// bitmask via the normal `compute_partitions` path, hands it here, and
22// — when this reports a forced token — exposes it through
23// `GrammarMatcher::forced_token` / `next_forced_tokens` so Metrale Engine's
24// scheduler can skip the model sample for those positions.
25//
26// CORRECTNESS
27// -----------
28// A token is reported "forced" ONLY when the *final* next-token
29// bitmask — the exact same mask the normal sampling path applies to
30// the logits — has exactly one bit set. That bitmask already folds in:
31//
32//   * the accepted-union across every live Earley state;
33//   * the rejected-intersection;
34//   * every uncertain token, resolved by trial advance;
35//   * stop tokens (added iff the root rule can complete);
36//   * special tokens (always cleared).
37//
38// So "exactly one bit set" is definitionally equivalent to "exactly
39// one grammar-legal token". There is no separate, weaker heuristic
40// that could disagree with the sampling path: the forced token is read
41// off the very mask sampling would have used. If zero or two-or-more
42// bits are set the continuation is NOT forced and the caller falls
43// through to the normal mask path. This is the conservative contract
44// the task demands — when in doubt, "not forced".
45//
46// Note the deliberate distinction from `find_jump_forward_string`,
47// which detects a forced *byte* sequence. A single forced byte path is
48// necessary but NOT sufficient for a single forced *token*: several
49// tokens may share that byte prefix (tokenizer ambiguity — dottxt's
50// "name" example), and conversely one forced token may not correspond
51// to a fully forced byte path once its own bytes are consumed. Token
52// forcedness is strictly the bitmask-cardinality question handled here.
53
54use crate::matcher::BitmaskSlice;
55
56/// The result of a forced-token analysis over a next-token bitmask.
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub enum Forced {
59    /// The grammar admits exactly one legal token — its id is carried.
60    /// The caller may emit it directly and skip the model sample.
61    Token(i32),
62    /// The grammar admits zero legal tokens. The matcher is stuck
63    /// (a dead state) — the caller must fall through; sampling will
64    /// also find an all-reject mask.
65    Dead,
66    /// Two or more tokens are legal — the continuation is genuinely a
67    /// choice. The caller must run the normal sample step.
68    NotForced,
69}
70
71impl Forced {
72    /// The forced token id, if (and only if) the continuation is a
73    /// single determined token.
74    #[must_use]
75    pub fn token(self) -> Option<i32> {
76        match self {
77            Forced::Token(id) => Some(id),
78            Forced::Dead | Forced::NotForced => None,
79        }
80    }
81}
82
83/// Number of bits packed per `i32` mask word.
84const BITS_PER_WORD: usize = 32;
85
86/// Analyze a fully-computed next-token bitmask for the forced-token
87/// condition.
88///
89/// `mask` MUST be the final mask produced by the matcher's normal
90/// partition path (accepted ∪, rejected ∩, uncertain resolved, stop
91/// tokens folded, special tokens cleared) — i.e. exactly the mask the
92/// sampling step would apply to the logits. Given that, this returns:
93///
94///   * [`Forced::Token`] when exactly one bit is set — the unique
95///     legal token; the caller may emit it without sampling;
96///   * [`Forced::Dead`] when no bit is set;
97///   * [`Forced::NotForced`] when two or more bits are set.
98///
99/// The scan works word-by-word over the packed mask using a hardware
100/// popcount, and stops as soon as a word pushes the running set-bit
101/// count past one. A genuine choice point — the common case — is
102/// therefore rejected within the first non-empty word; it never walks
103/// the whole vocabulary bit by bit.
104#[must_use]
105pub fn analyze_bitmask(mask: &BitmaskSlice<'_>) -> Forced {
106    let vocab = mask.vocab_size();
107    let words = mask.words();
108    if words.is_empty() || vocab == 0 {
109        return Forced::Dead;
110    }
111    // The final word may carry padding bits beyond `vocab`; mask them
112    // off so a stray set padding bit can never be mis-read as a token.
113    let last = words.len() - 1;
114    let tail = vocab % BITS_PER_WORD;
115    let tail_keep: u32 = if tail == 0 {
116        u32::MAX
117    } else {
118        (1u32 << tail) - 1
119    };
120
121    let mut found_word: Option<usize> = None;
122    for (wi, &raw) in words.iter().enumerate() {
123        let mut bits = raw as u32;
124        if wi == last {
125            bits &= tail_keep;
126        }
127        match bits.count_ones() {
128            0 => {}
129            1 => {
130                if found_word.is_some() {
131                    // A set bit in an earlier word plus one here — two
132                    // distinct legal tokens.
133                    return Forced::NotForced;
134                }
135                found_word = Some(wi);
136            }
137            // Two or more bits in a single word — a choice point.
138            _ => return Forced::NotForced,
139        }
140    }
141    match found_word {
142        Some(wi) => {
143            let bit = (words[wi] as u32 & if wi == last { tail_keep } else { u32::MAX })
144                .trailing_zeros() as usize;
145            Forced::Token((wi * BITS_PER_WORD + bit) as i32)
146        }
147        None => Forced::Dead,
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154    use crate::matcher::{BitmaskSlice, bitmask_size};
155
156    /// Build a `BitmaskSlice`-backed buffer with `set_ids` accepted.
157    fn mask_with(vocab: usize, set_ids: &[usize]) -> Vec<i32> {
158        let mut buf = vec![0i32; bitmask_size(vocab)];
159        {
160            let mut view = BitmaskSlice::new(&mut buf, vocab).expect("slice");
161            view.clear();
162            for &id in set_ids {
163                view.set(id, true);
164            }
165        }
166        buf
167    }
168
169    #[test]
170    fn single_set_bit_is_forced() {
171        let vocab = 100;
172        let mut buf = mask_with(vocab, &[42]);
173        let view = BitmaskSlice::new(&mut buf, vocab).expect("slice");
174        assert_eq!(analyze_bitmask(&view), Forced::Token(42));
175        assert_eq!(analyze_bitmask(&view).token(), Some(42));
176    }
177
178    #[test]
179    fn two_set_bits_is_not_forced() {
180        let vocab = 100;
181        let mut buf = mask_with(vocab, &[7, 88]);
182        let view = BitmaskSlice::new(&mut buf, vocab).expect("slice");
183        assert_eq!(analyze_bitmask(&view), Forced::NotForced);
184        assert_eq!(analyze_bitmask(&view).token(), None);
185    }
186
187    #[test]
188    fn no_set_bit_is_dead() {
189        let vocab = 100;
190        let mut buf = mask_with(vocab, &[]);
191        let view = BitmaskSlice::new(&mut buf, vocab).expect("slice");
192        assert_eq!(analyze_bitmask(&view), Forced::Dead);
193        assert_eq!(analyze_bitmask(&view).token(), None);
194    }
195
196    #[test]
197    fn forced_bit_at_vocab_boundary() {
198        // The single set bit being the last logical token id exercises
199        // the final (partial) word of the packed mask.
200        let vocab = 65; // 3 words, 1 used bit in word 2.
201        let mut buf = mask_with(vocab, &[64]);
202        let view = BitmaskSlice::new(&mut buf, vocab).expect("slice");
203        assert_eq!(analyze_bitmask(&view), Forced::Token(64));
204    }
205
206    #[test]
207    fn all_set_is_not_forced() {
208        let vocab = 40;
209        let mut buf = vec![0i32; bitmask_size(vocab)];
210        {
211            let mut view = BitmaskSlice::new(&mut buf, vocab).expect("slice");
212            view.fill_all();
213        }
214        let view = BitmaskSlice::new(&mut buf, vocab).expect("slice");
215        assert_eq!(analyze_bitmask(&view), Forced::NotForced);
216    }
217}