use metrale_grammar::{
CompiledGrammar, GrammarMatcher, allocate_token_bitmask, reset_token_bitmask,
};
use super::engine::GrammarError;
use super::prewarm::Prewarm;
const FORCED_TOKEN_TOP_K: usize = 512;
pub struct GrammarState {
timing: std::sync::Arc<crate::scheduler::mtp_timing::RunTiming>,
matcher: GrammarMatcher,
prewarm: Prewarm,
bitmask_data: Box<[i32]>,
vocab_size: usize,
stop_tokens: Box<[u32]>,
bitmask_valid: bool,
bitmask_fill_result: bool,
}
impl GrammarState {
pub fn new(compiled: &CompiledGrammar, vocab_size: usize) -> Result<Self, GrammarError> {
Self::with_timing(compiled, vocab_size, std::sync::Arc::default())
}
pub fn new_with_hook(
compiled: &CompiledGrammar,
vocab_size: usize,
on_warm: Option<super::PrewarmHook>,
) -> Result<Self, GrammarError> {
Self::build_with(
compiled,
vocab_size,
None,
super::prewarm::overlap_enabled_for_serve(),
on_warm,
)
}
pub fn with_timing(
compiled: &CompiledGrammar,
vocab_size: usize,
timing: std::sync::Arc<crate::scheduler::mtp_timing::RunTiming>,
) -> Result<Self, GrammarError> {
let mut state = Self::build(compiled, vocab_size)?;
state.timing = timing;
Ok(state)
}
fn build(compiled: &CompiledGrammar, vocab_size: usize) -> Result<Self, GrammarError> {
Self::build_with(
compiled,
vocab_size,
None,
super::prewarm::overlap_enabled_for_serve(),
None,
)
}
pub(super) fn build_gated(
compiled: &CompiledGrammar,
vocab_size: usize,
gate: Option<super::prewarm::PrewarmGate>,
) -> Result<Self, GrammarError> {
Self::build_with(compiled, vocab_size, gate, true, None)
}
pub(super) fn build_with(
compiled: &CompiledGrammar,
vocab_size: usize,
gate: Option<super::prewarm::PrewarmGate>,
overlap: bool,
on_warm: Option<super::PrewarmHook>,
) -> Result<Self, GrammarError> {
let matcher = GrammarMatcher::new(
compiled, None, false, -1,
)
.map_err(GrammarError::Compilation)?;
let prewarm = Prewarm::start_with(compiled, FORCED_TOKEN_TOP_K, gate, overlap, on_warm);
let bitmask_data = allocate_token_bitmask(1, vocab_size);
Ok(Self {
timing: std::sync::Arc::default(),
matcher,
prewarm,
bitmask_data,
vocab_size,
stop_tokens: Box::new([]),
bitmask_valid: false,
bitmask_fill_result: false,
})
}
#[must_use]
pub fn with_stop_tokens(mut self, stop_tokens: &[u32]) -> Self {
self.stop_tokens = stop_tokens.to_vec().into_boxed_slice();
self
}
#[cfg(test)]
pub(super) fn prewarm_finished(&self) -> bool {
self.prewarm.is_finished()
}
fn await_masks(&mut self) {
let warmed = self.prewarm.wait();
if warmed > 0 {
tracing::debug!(
warmed,
requested = FORCED_TOKEN_TOP_K,
"Grammar: pre-warmed top-k token masks overlapped with prefill"
);
}
}
pub fn fill_bitmask(&mut self) -> bool {
if self.matcher.is_terminated() {
return false;
}
if self.bitmask_valid {
return self.bitmask_fill_result;
}
self.await_masks();
let t_fill = std::time::Instant::now();
reset_token_bitmask(&mut self.bitmask_data);
let filled = self
.matcher
.fill_next_token_bitmask(&mut self.bitmask_data, 0, false);
self.timing
.record(crate::scheduler::mtp_timing::Phase::GrammarFill, t_fill);
self.bitmask_valid = true;
self.bitmask_fill_result = filled;
filled
}
pub fn bitmask_data(&self) -> &[i32] {
&self.bitmask_data
}
pub fn is_token_allowed(&self, token_id: u32) -> bool {
let word = (token_id / 32) as usize;
let bit = token_id % 32;
if word >= self.bitmask_data.len() {
return false;
}
(self.bitmask_data[word] & (1i32 << bit)) != 0
}
pub fn accept_token(&mut self, token_id: u32) -> bool {
self.bitmask_valid = false;
if self.matcher.is_terminated() {
return true;
}
if self.stop_tokens.contains(&token_id) {
return true;
}
self.matcher.accept_token(token_id as i32)
}
pub fn forced_token(&mut self) -> Option<i32> {
if self.matcher.is_terminated() {
return None;
}
if !self.fill_bitmask() {
return None;
}
let t = std::time::Instant::now();
let forced = self.matcher.forced_from_bitmask(&mut self.bitmask_data, 0);
self.timing
.record(crate::scheduler::mtp_timing::Phase::ForcedTok, t);
forced
}
pub fn is_terminated(&self) -> bool {
self.matcher.is_terminated()
}
pub fn stop_legal(&mut self, eos_tokens: &[u32]) -> bool {
if self.matcher.is_terminated() {
return true;
}
if !self.fill_bitmask() {
return true;
}
eos_tokens.iter().any(|&e| self.is_token_allowed(e))
}
pub fn completion_token_ids(&mut self, max_bytes: usize) -> Option<Vec<i32>> {
self.await_masks();
self.matcher.find_completion_token_ids(max_bytes)
}
pub fn num_history_steps(&self) -> usize {
self.matcher.num_history_steps()
}
pub fn rollback(&mut self, n: usize) {
self.bitmask_valid = false;
self.matcher.rollback(n as i32);
}
pub fn reset(&mut self) {
self.bitmask_valid = false;
self.matcher.reset();
}
pub fn apply_bitmask_to_logits(&self, logits: &mut [f32]) {
let n = logits.len().min(self.vocab_size);
for token_id in 0..n {
let word = token_id / 32;
let bit = token_id % 32;
if word < self.bitmask_data.len() && (self.bitmask_data[word] & (1i32 << bit)) == 0 {
logits[token_id] = f32::NEG_INFINITY;
}
}
}
}
pub fn grammar_blocks_stop(gs: Option<&mut GrammarState>, eos_tokens: &[u32]) -> bool {
match gs {
None => false,
Some(gs) => !gs.is_terminated() && !gs.stop_legal(eos_tokens),
}
}