use crate::grammar::pda::{CompiledGrammar, GrammarState, StepResult, advance_byte};
thread_local! {
static MASK_SCRATCH: std::cell::RefCell<Vec<u64>> = const { std::cell::RefCell::new(Vec::new()) };
}
struct TrieNode {
children: Vec<(u8, u32)>,
terminals: Vec<u32>,
}
pub struct ByteTrie {
nodes: Vec<TrieNode>,
}
impl ByteTrie {
pub fn build(vocab_bytes: &[Vec<u8>]) -> Self {
let mut nodes = vec![TrieNode {
children: Vec::new(),
terminals: Vec::new(),
}];
for (token_id, bytes) in vocab_bytes.iter().enumerate() {
if bytes.is_empty() {
continue;
}
let mut cur = 0u32;
for &b in bytes {
cur = match nodes[cur as usize]
.children
.iter()
.find(|&&(cb, _)| cb == b)
{
Some(&(_, child)) => child,
None => {
let new_idx = nodes.len() as u32;
nodes.push(TrieNode {
children: Vec::new(),
terminals: Vec::new(),
});
nodes[cur as usize].children.push((b, new_idx));
new_idx
}
};
}
nodes[cur as usize].terminals.push(token_id as u32);
}
Self { nodes }
}
pub fn mask(
&self,
state: &GrammarState,
grammar: &CompiledGrammar,
vocab_size: usize,
logits: &mut [f32],
) {
let mask_stride = vocab_size.div_ceil(64);
MASK_SCRATCH.with(|scratch| {
let mut allowed = scratch.borrow_mut();
allowed.clear();
allowed.resize(mask_stride, 0u64);
mark_allowed(
&self.nodes,
0,
walk_root_state(state),
grammar,
&mut allowed,
);
apply_allowed_mask(&allowed, vocab_size, logits);
});
}
}
fn walk_root_state(state: &GrammarState) -> GrammarState {
GrammarState {
stack: state.stack.clone(),
partial_token_bytes: Vec::new(),
complete: state.complete,
}
}
fn mark_allowed(
nodes: &[TrieNode],
node_idx: u32,
state: GrammarState,
grammar: &CompiledGrammar,
allowed: &mut [u64],
) {
let node = &nodes[node_idx as usize];
for &token_id in &node.terminals {
let idx = token_id as usize;
allowed[idx / 64] |= 1u64 << (idx % 64);
}
let children = &node.children;
let Some((&last, rest)) = children.split_last() else {
return;
};
for &(byte, child_idx) in rest {
let mut child_state = state.clone();
if advance_byte(&mut child_state, grammar, byte) == StepResult::Accepted {
mark_allowed(nodes, child_idx, child_state, grammar, allowed);
}
}
let (byte, child_idx) = last;
let mut last_state = state;
if advance_byte(&mut last_state, grammar, byte) == StepResult::Accepted {
mark_allowed(nodes, child_idx, last_state, grammar, allowed);
}
}
fn apply_allowed_mask(allowed: &[u64], vocab_size: usize, logits: &mut [f32]) {
let mask_stride = allowed.len();
for word_idx in 0..mask_stride {
let word = allowed[word_idx];
let base_token = word_idx * 64;
if word == u64::MAX {
continue;
}
if word == 0 {
let end = (base_token + 64).min(vocab_size);
for l in logits[base_token..end].iter_mut() {
*l = f32::NEG_INFINITY;
}
continue;
}
for bit in 0..64u32 {
let token_idx = base_token + bit as usize;
if token_idx >= vocab_size {
break;
}
if word & (1u64 << bit) == 0 {
logits[token_idx] = f32::NEG_INFINITY;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::grammar::pda::{GrammarBuilder, Symbol};
fn or_grammar() -> CompiledGrammar {
let mut b = GrammarBuilder::new();
b.add_rule(
"root",
vec![vec![Symbol::Terminal(b'a')], vec![Symbol::Terminal(b'b')]],
);
b.build()
}
#[test]
fn trie_build_shares_prefixes() {
let vocab = vec![b"ab".to_vec(), b"ac".to_vec(), b"b".to_vec()];
let trie = ByteTrie::build(&vocab);
assert_eq!(trie.nodes.len(), 5);
}
#[test]
fn trie_mask_matches_simple_grammar() {
let vocab = vec![b"a".to_vec(), b"b".to_vec(), b"c".to_vec()];
let grammar = or_grammar();
let trie = ByteTrie::build(&vocab);
let state = GrammarState::initial();
let mut logits = vec![1.0f32, 2.0f32, 3.0f32];
trie.mask(&state, &grammar, vocab.len(), &mut logits);
assert!(logits[0] > f32::NEG_INFINITY, "'a' allowed");
assert!(logits[1] > f32::NEG_INFINITY, "'b' allowed");
assert_eq!(logits[2], f32::NEG_INFINITY, "'c' blocked");
}
#[test]
fn trie_mask_empty_token_blocked() {
let vocab = vec![b"a".to_vec(), vec![]];
let grammar = or_grammar();
let trie = ByteTrie::build(&vocab);
let state = GrammarState::initial();
let mut logits = vec![1.0f32, 1.0f32];
trie.mask(&state, &grammar, vocab.len(), &mut logits);
assert!(logits[0] > f32::NEG_INFINITY);
assert_eq!(logits[1], f32::NEG_INFINITY, "empty token always blocked");
}
#[test]
fn trie_walk_root_state_drops_partial_token_bytes() {
let mut state = GrammarState::initial();
state.partial_token_bytes = vec![b'x'; 64 * 1024];
let root = walk_root_state(&state);
assert!(
root.partial_token_bytes.is_empty(),
"walk root must start with empty partial_token_bytes regardless \
of the live state's history length (got {} bytes) — a full \
state.clone() here reintroduces O(generation-so-far) DFS clones",
root.partial_token_bytes.len()
);
assert_eq!(root.stack, state.stack, "walk root must preserve stack");
assert_eq!(
root.complete, state.complete,
"walk root must preserve the complete flag"
);
}
#[test]
fn trie_mask_byte_identical_regardless_of_partial_token_bytes_history() {
let vocab = vec![b"a".to_vec(), b"b".to_vec(), b"c".to_vec()];
let grammar = or_grammar();
let trie = ByteTrie::build(&vocab);
let clean_state = GrammarState::initial();
let mut clean_logits = vec![1.0f32, 2.0f32, 3.0f32];
trie.mask(&clean_state, &grammar, vocab.len(), &mut clean_logits);
let mut heavy_state = GrammarState::initial();
heavy_state.partial_token_bytes = vec![b'x'; 64 * 1024];
let mut heavy_logits = vec![1.0f32, 2.0f32, 3.0f32];
trie.mask(&heavy_state, &grammar, vocab.len(), &mut heavy_logits);
assert_eq!(
clean_logits, heavy_logits,
"mask output must be independent of partial_token_bytes history"
);
}
}