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}