#[cfg(feature = "grammar")]
use crate::grammar::{Fsm, FsmError, TokenTrie};
pub fn apply_temperature(logits: &mut [f32], temperature: f32) {
if temperature < f32::EPSILON {
return;
}
let inv_t = 1.0 / temperature;
for l in logits.iter_mut() {
*l *= inv_t;
}
}
pub fn top_k_filter(logits: &mut [f32], k: usize) {
if k == 0 || k >= logits.len() {
return;
}
let mut vals: Vec<f32> = logits.to_vec();
let kth = k - 1;
vals.select_nth_unstable_by(kth, |a, b| {
b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal)
});
let threshold = vals[kth];
for l in logits.iter_mut() {
if *l < threshold {
*l = f32::NEG_INFINITY;
}
}
}
pub fn top_p_filter(logits: &mut [f32], p: f32) {
if p >= 1.0 {
return;
}
let mut probs = logits.to_vec();
softmax_inplace(&mut probs);
let mut indexed: Vec<(usize, f32)> = probs.iter().copied().enumerate().collect();
indexed.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
let mut cumsum = 0.0f32;
let mut cutoff_idx = indexed.len();
for (rank, &(_, prob)) in indexed.iter().enumerate() {
cumsum += prob;
if cumsum > p {
cutoff_idx = rank + 1;
break;
}
}
let keep: std::collections::HashSet<usize> =
indexed[..cutoff_idx].iter().map(|&(i, _)| i).collect();
for (i, l) in logits.iter_mut().enumerate() {
if !keep.contains(&i) {
*l = f32::NEG_INFINITY;
}
}
}
pub fn softmax_inplace(v: &mut [f32]) {
if v.is_empty() {
return;
}
let max_val = v.iter().copied().fold(f32::NEG_INFINITY, f32::max);
let mut sum = 0.0f32;
for x in v.iter_mut() {
*x = (*x - max_val).exp();
sum += *x;
}
if sum > 0.0 {
for x in v.iter_mut() {
*x /= sum;
}
}
}
#[must_use]
pub fn softmax(logits: &[f32]) -> Vec<f32> {
let mut v = logits.to_vec();
softmax_inplace(&mut v);
v
}
#[must_use]
pub fn sample_argmax(logits: &[f32]) -> usize {
logits
.iter()
.enumerate()
.max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
.map_or(0, |(i, _)| i)
}
#[must_use]
pub fn sample_with_random(probs: &[f32], rand_val: f32) -> usize {
let mut cumsum = 0.0f32;
for (i, &p) in probs.iter().enumerate() {
cumsum += p;
if rand_val < cumsum {
return i;
}
}
probs.len().saturating_sub(1)
}
#[cfg(feature = "grammar")]
pub trait GrammarTokenizer {
fn text_of(&self, id: u32) -> String;
fn eos_id(&self) -> u32;
}
#[cfg(feature = "grammar")]
impl GrammarTokenizer for crate::gguf::GgufTokenizer {
fn text_of(&self, id: u32) -> String {
self.decode(&[id])
}
fn eos_id(&self) -> u32 {
self.eos_id
}
}
#[cfg(feature = "grammar")]
pub fn mask_logits_by_grammar<T: GrammarTokenizer + ?Sized>(
fsm: &Fsm<'_>,
tokenizer: &T,
logits: &mut [f32],
) {
let eos = tokenizer.eos_id();
let final_state = fsm.is_final();
for (id, logit) in logits.iter_mut().enumerate() {
if !logit.is_finite() {
continue;
}
let id_u32 = u32::try_from(id).unwrap_or(u32::MAX);
if id_u32 == eos {
if !final_state {
*logit = f32::NEG_INFINITY;
}
continue;
}
let text = tokenizer.text_of(id_u32);
if text.is_empty() {
*logit = f32::NEG_INFINITY;
continue;
}
if !fsm.accepts_str(&text) {
*logit = f32::NEG_INFINITY;
}
}
}
#[cfg(feature = "grammar")]
pub fn mask_logits_by_grammar_trie<T: GrammarTokenizer + ?Sized>(
fsm: &Fsm<'_>,
trie: &TokenTrie,
tokenizer: &T,
logits: &mut [f32],
scratch: &mut Vec<u32>,
) -> usize {
assert_eq!(
logits.len(),
trie.vocab_size(),
"logits length must match the trie vocabulary"
);
let advances = trie.allowed_tokens(fsm, scratch);
let mut allowed = vec![false; logits.len()];
for &id in scratch.iter() {
allowed[id as usize] = true;
}
let eos = tokenizer.eos_id() as usize;
let final_state = fsm.is_final();
for (id, logit) in logits.iter_mut().enumerate() {
if !logit.is_finite() {
continue;
}
if id == eos {
if !final_state {
*logit = f32::NEG_INFINITY;
}
continue;
}
if !allowed[id] {
*logit = f32::NEG_INFINITY;
}
}
advances
}
#[cfg(feature = "grammar")]
pub fn advance_fsm_on_emit<T: GrammarTokenizer + ?Sized>(
fsm: &mut Fsm<'_>,
tokenizer: &T,
token_id: u32,
) -> Result<(), FsmError> {
if token_id == tokenizer.eos_id() {
return Ok(());
}
let text = tokenizer.text_of(token_id);
for ch in text.chars() {
fsm.advance(ch)?;
}
Ok(())
}
#[cfg(all(test, feature = "grammar"))]
mod grammar_tests {
use super::{advance_fsm_on_emit, mask_logits_by_grammar, GrammarTokenizer};
use crate::grammar::{parse_gbnf, Fsm};
struct MockTokenizer {
tokens: Vec<String>,
eos: u32,
}
impl MockTokenizer {
fn new(tokens: &[&str], eos: u32) -> Self {
Self {
tokens: tokens.iter().map(|s| (*s).to_string()).collect(),
eos,
}
}
}
impl GrammarTokenizer for MockTokenizer {
fn text_of(&self, id: u32) -> String {
self.tokens.get(id as usize).cloned().unwrap_or_default()
}
fn eos_id(&self) -> u32 {
self.eos
}
}
#[test]
fn masks_non_matching_prefix() {
let tok = MockTokenizer::new(&["", "y", "n", "hello"], 0);
let grammar = parse_gbnf(r#"root ::= "yes""#).unwrap();
let fsm = Fsm::start(&grammar).unwrap();
let mut logits = vec![0.0f32; 4];
mask_logits_by_grammar(&fsm, &tok, &mut logits);
assert!(logits[0].is_infinite() && logits[0] < 0.0); assert!(logits[1].is_finite()); assert!(logits[2].is_infinite() && logits[2] < 0.0); assert!(logits[3].is_infinite() && logits[3] < 0.0); }
#[test]
fn allows_eos_when_final() {
let tok = MockTokenizer::new(&["", "a", "b"], 0);
let grammar = parse_gbnf(r#"root ::= "a"?"#).unwrap();
let fsm = Fsm::start(&grammar).unwrap();
assert!(fsm.is_final());
let mut logits = vec![0.0f32; 3];
mask_logits_by_grammar(&fsm, &tok, &mut logits);
assert!(logits[0].is_finite()); assert!(logits[1].is_finite()); assert!(logits[2].is_infinite()); }
#[test]
fn masks_eos_when_not_final() {
let tok = MockTokenizer::new(&["", "a"], 0);
let grammar = parse_gbnf(r#"root ::= "a""#).unwrap();
let fsm = Fsm::start(&grammar).unwrap();
assert!(!fsm.is_final());
let mut logits = vec![0.0f32; 2];
mask_logits_by_grammar(&fsm, &tok, &mut logits);
assert!(logits[0].is_infinite() && logits[0] < 0.0);
assert!(logits[1].is_finite());
}
#[test]
fn masks_control_tokens_with_empty_text() {
let tok = MockTokenizer::new(&["", "y", ""], 0);
let grammar = parse_gbnf(r#"root ::= "yes""#).unwrap();
let fsm = Fsm::start(&grammar).unwrap();
let mut logits = vec![0.0f32; 3];
mask_logits_by_grammar(&fsm, &tok, &mut logits);
assert!(logits[0].is_infinite()); assert!(logits[1].is_finite()); assert!(logits[2].is_infinite()); }
#[test]
fn skips_already_masked_logits() {
let tok = MockTokenizer::new(&["", "y"], 0);
let grammar = parse_gbnf(r#"root ::= "y""#).unwrap();
let fsm = Fsm::start(&grammar).unwrap();
let mut logits = vec![f32::NEG_INFINITY; 2];
mask_logits_by_grammar(&fsm, &tok, &mut logits);
assert!(logits[0].is_infinite());
assert!(logits[1].is_infinite());
}
#[test]
fn advance_on_emit_advances_state() {
let tok = MockTokenizer::new(&["", "y", "es"], 0);
let grammar = parse_gbnf(r#"root ::= "yes""#).unwrap();
let mut fsm = Fsm::start(&grammar).unwrap();
advance_fsm_on_emit(&mut fsm, &tok, 1).unwrap();
assert!(!fsm.is_final());
advance_fsm_on_emit(&mut fsm, &tok, 2).unwrap();
assert!(fsm.is_final());
}
#[test]
fn advance_on_emit_eos_is_noop() {
let tok = MockTokenizer::new(&["", "y"], 0);
let grammar = parse_gbnf(r#"root ::= "y""#).unwrap();
let mut fsm = Fsm::start(&grammar).unwrap();
advance_fsm_on_emit(&mut fsm, &tok, 0).unwrap();
assert!(!fsm.is_final());
}
#[test]
fn advance_on_emit_rejects_invalid_token() {
let tok = MockTokenizer::new(&["", "x"], 0);
let grammar = parse_gbnf(r#"root ::= "y""#).unwrap();
let mut fsm = Fsm::start(&grammar).unwrap();
assert!(advance_fsm_on_emit(&mut fsm, &tok, 1).is_err());
}
#[test]
fn utf8_multibyte_token_ok() {
let tok = MockTokenizer::new(&["", "あ"], 0);
let grammar = parse_gbnf(r#"root ::= "あ""#).unwrap();
let fsm = Fsm::start(&grammar).unwrap();
let mut logits = vec![0.0f32; 2];
mask_logits_by_grammar(&fsm, &tok, &mut logits);
assert!(logits[0].is_infinite()); assert!(logits[1].is_finite()); let mut fsm2 = Fsm::start(&grammar).unwrap();
advance_fsm_on_emit(&mut fsm2, &tok, 1).unwrap();
assert!(fsm2.is_final());
}
}