metrale_grammar/matcher/
bitmask.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2//
3// TokenBitmask — the per-token accept/reject mask the matcher fills.
4//
5// Port of the C++ token-bitmask representation. The C++ side packs the
6// mask into a `DLTensor` of `int32`, one bit per vocab token, with the
7// buffer size given by `GetBitmaskSize(vocab_size)`. This module
8// reproduces the same packed layout in safe Rust:
9//   * `bitmask_size(vocab_size)` — number of `i32` words, identical to
10//     the C++ `DynamicBitset::GetBufferSize`;
11//   * [`TokenBitmask`] — an owned, vocab-sized packed mask;
12//   * helpers to view / fill a caller-supplied `&mut [i32]` slice so
13//     Metrale Engine's W7 shim can hand the matcher whatever buffer it owns.
14//
15// Bit `t` of the mask is set iff token id `t` is grammar-legal for the
16// next decode position. Word `t / 32`, bit `t % 32` (LSB-first), the
17// exact layout the C++ `DynamicBitset` uses so a bitmask produced here
18// is byte-compatible with the upstream CUDA `apply_token_bitmask`.
19
20/// Bits packed per `i32` word — fixed by the C++ `DynamicBitset`.
21pub const BITS_PER_WORD: usize = 32;
22
23/// Number of `i32` words needed to hold a bit-per-token mask for a
24/// vocabulary of `vocab_size` tokens.
25///
26/// Port of `GetBitmaskSize` / `DynamicBitset::GetBufferSize`:
27/// `ceil(vocab_size / 32)`.
28#[must_use]
29pub fn bitmask_size(vocab_size: usize) -> usize {
30    vocab_size.div_ceil(BITS_PER_WORD)
31}
32
33/// Set bit `token` in a packed `i32` mask slice.
34#[inline]
35fn set_bit(words: &mut [i32], token: usize, value: bool) {
36    let word = token / BITS_PER_WORD;
37    let bit = token % BITS_PER_WORD;
38    let mask = 1u32 << bit;
39    let cur = words[word] as u32;
40    words[word] = if value { cur | mask } else { cur & !mask } as i32;
41}
42
43/// Read bit `token` from a packed `i32` mask slice.
44#[inline]
45#[must_use]
46fn get_bit(words: &[i32], token: usize) -> bool {
47    let word = token / BITS_PER_WORD;
48    let bit = token % BITS_PER_WORD;
49    (words[word] as u32 >> bit) & 1 == 1
50}
51
52/// Fill every bit `0..vocab_size` of `words` with `value`, leaving the
53/// padding bits of the final word cleared (so `count`/`all` over the
54/// logical vocabulary are exact).
55fn fill(words: &mut [i32], vocab_size: usize, value: bool) {
56    for w in words.iter_mut() {
57        *w = if value { -1 } else { 0 };
58    }
59    if value {
60        // Clear padding bits beyond `vocab_size` in the last word.
61        let used = vocab_size % BITS_PER_WORD;
62        if used != 0 {
63            let last = bitmask_size(vocab_size) - 1;
64            let keep = (1u32 << used) - 1;
65            words[last] = (words[last] as u32 & keep) as i32;
66        }
67    }
68}
69
70/// A packed bit-per-token accept mask owned by a [`super::GrammarMatcher`].
71///
72/// This is the Rust equivalent of the C++ `DLTensor` int32 bitmask. It
73/// is `vocab_size` bits wide, stored LSB-first in `i32` words. The
74/// matcher fills it in place each decode step; callers read which
75/// tokens are legal via [`Self::is_set`] or apply it to logits.
76#[derive(Debug, Clone, PartialEq, Eq)]
77pub struct TokenBitmask {
78    words: Vec<i32>,
79    vocab_size: usize,
80}
81
82impl TokenBitmask {
83    /// Allocate an all-rejecting (all-zero) mask for `vocab_size` tokens.
84    #[must_use]
85    pub fn new(vocab_size: usize) -> Self {
86        Self {
87            words: vec![0; bitmask_size(vocab_size)],
88            vocab_size,
89        }
90    }
91
92    /// The vocabulary size this mask was sized for.
93    #[must_use]
94    pub fn vocab_size(&self) -> usize {
95        self.vocab_size
96    }
97
98    /// The packed `i32` words — the layout Metrale Engine hands to the kernel.
99    #[must_use]
100    pub fn as_words(&self) -> &[i32] {
101        &self.words
102    }
103
104    /// Mutable access to the packed words (for the W7 shim / kernels).
105    pub fn as_words_mut(&mut self) -> &mut [i32] {
106        &mut self.words
107    }
108
109    /// True if token id `token` is accepted (its bit is set).
110    ///
111    /// Panics if `token >= vocab_size`.
112    #[must_use]
113    pub fn is_set(&self, token: usize) -> bool {
114        assert!(token < self.vocab_size, "token id out of range");
115        get_bit(&self.words, token)
116    }
117
118    /// Set / clear token id `token`. Panics if `token >= vocab_size`.
119    pub fn set(&mut self, token: usize, value: bool) {
120        assert!(token < self.vocab_size, "token id out of range");
121        set_bit(&mut self.words, token, value);
122    }
123
124    /// Reject every token (clear all bits).
125    pub fn clear(&mut self) {
126        fill(&mut self.words, self.vocab_size, false);
127    }
128
129    /// Accept every token (set every logical bit).
130    pub fn fill_all(&mut self) {
131        fill(&mut self.words, self.vocab_size, true);
132    }
133
134    /// Number of accepted (set) tokens over the logical vocabulary.
135    #[must_use]
136    pub fn count_set(&self) -> usize {
137        (0..self.vocab_size)
138            .filter(|&t| get_bit(&self.words, t))
139            .count()
140    }
141
142    /// True if every logical token bit is set.
143    #[must_use]
144    pub fn all_set(&self) -> bool {
145        (0..self.vocab_size).all(|t| get_bit(&self.words, t))
146    }
147
148    /// Collect the rejected (cleared) token ids — the
149    /// `_DebugGetMaskedTokensFromBitmask` helper of the C++ matcher.
150    #[must_use]
151    pub fn rejected_tokens(&self) -> Vec<i32> {
152        (0..self.vocab_size)
153            .filter(|&t| !get_bit(&self.words, t))
154            .map(|t| t as i32)
155            .collect()
156    }
157}
158
159/// A mutable, vocab-sized view over a caller-owned `i32` slice.
160///
161/// `FillNextTokenBitmask` in the C++ takes a `DLTensor*`; Metrale Engine owns
162/// the backing buffer. This newtype lets the matcher fill that buffer
163/// directly without copying through a [`TokenBitmask`]. The slice must
164/// be at least `bitmask_size(vocab_size)` words long.
165pub struct BitmaskSlice<'a> {
166    words: &'a mut [i32],
167    vocab_size: usize,
168}
169
170impl<'a> BitmaskSlice<'a> {
171    /// Wrap `words` as a `vocab_size`-bit mask.
172    ///
173    /// Returns `None` if `words` is too short to hold the mask.
174    #[must_use]
175    pub fn new(words: &'a mut [i32], vocab_size: usize) -> Option<Self> {
176        if words.len() < bitmask_size(vocab_size) {
177            return None;
178        }
179        Some(Self { words, vocab_size })
180    }
181
182    /// The vocabulary size this view covers.
183    #[must_use]
184    pub fn vocab_size(&self) -> usize {
185        self.vocab_size
186    }
187
188    /// Set / clear token id `token`.
189    pub fn set(&mut self, token: usize, value: bool) {
190        debug_assert!(token < self.vocab_size);
191        set_bit(self.words, token, value);
192    }
193
194    /// Read token id `token`.
195    #[must_use]
196    pub fn is_set(&self, token: usize) -> bool {
197        debug_assert!(token < self.vocab_size);
198        get_bit(self.words, token)
199    }
200
201    /// The packed `i32` words backing this view — exactly
202    /// `bitmask_size(vocab_size)` of them.
203    ///
204    /// Padding bits beyond `vocab_size` in the final word are not
205    /// guaranteed cleared (only [`Self::fill_all`] / [`Self::clear`]
206    /// normalize them); callers that popcount must mask the tail or
207    /// only trust [`Self::vocab_size`] logical bits.
208    #[must_use]
209    pub fn words(&self) -> &[i32] {
210        self.words
211    }
212
213    /// Reject every token.
214    pub fn clear(&mut self) {
215        fill(self.words, self.vocab_size, false);
216    }
217
218    /// Accept every token.
219    pub fn fill_all(&mut self) {
220        fill(self.words, self.vocab_size, true);
221    }
222
223    /// True if every logical token bit is set.
224    #[must_use]
225    pub fn all_set(&self) -> bool {
226        (0..self.vocab_size).all(|t| get_bit(self.words, t))
227    }
228}
229
230impl<'a> From<&'a mut TokenBitmask> for BitmaskSlice<'a> {
231    fn from(b: &'a mut TokenBitmask) -> Self {
232        let vocab_size = b.vocab_size;
233        Self {
234            words: &mut b.words,
235            vocab_size,
236        }
237    }
238}