use std::collections::BTreeSet;
use crate::sampling::Sampler;
pub struct DecodeState<'a> {
pub generated: &'a [u32],
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AllowedSet {
Only(BTreeSet<u32>),
Mask(Vec<bool>),
}
impl AllowedSet {
pub fn contains(&self, id: u32) -> bool {
match self {
AllowedSet::Only(set) => set.contains(&id),
AllowedSet::Mask(mask) => mask.get(id as usize).copied().unwrap_or(false),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConstraintError {
NoTokenAllowed,
EmptyConstraint,
}
impl std::fmt::Display for ConstraintError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ConstraintError::NoTokenAllowed => {
write!(f, "constraint masked every token: no valid next token to sample")
}
ConstraintError::EmptyConstraint => {
write!(f, "constraint constructed with an empty allowed-token set")
}
}
}
}
impl std::error::Error for ConstraintError {}
pub trait TokenConstraint {
fn allowed(&self, state: &DecodeState<'_>) -> AllowedSet;
}
pub fn mask_logits(logits: &mut [f32], allowed: &AllowedSet) -> Result<(), ConstraintError> {
let mut any_allowed = false;
for (idx, logit) in logits.iter_mut().enumerate() {
if allowed.contains(idx as u32) {
any_allowed = true;
} else {
*logit = f32::NEG_INFINITY;
}
}
if any_allowed {
Ok(())
} else {
Err(ConstraintError::NoTokenAllowed)
}
}
#[derive(Debug, Clone)]
pub struct AllowedTokens {
ids: BTreeSet<u32>,
}
impl AllowedTokens {
pub fn new(ids: impl IntoIterator<Item = u32>) -> Result<Self, ConstraintError> {
let ids: BTreeSet<u32> = ids.into_iter().collect();
if ids.is_empty() {
return Err(ConstraintError::EmptyConstraint);
}
Ok(Self { ids })
}
}
impl TokenConstraint for AllowedTokens {
fn allowed(&self, _state: &DecodeState<'_>) -> AllowedSet {
AllowedSet::Only(self.ids.clone())
}
}
pub trait TokenVocab {
fn token_count(&self) -> usize;
fn token_bytes(&self, id: u32) -> Option<&[u8]>;
}
#[derive(Debug, Clone)]
pub struct SliceVocab {
tokens: Vec<Vec<u8>>,
}
impl SliceVocab {
pub fn new(tokens: Vec<Vec<u8>>) -> Self {
Self { tokens }
}
}
impl TokenVocab for SliceVocab {
fn token_count(&self) -> usize {
self.tokens.len()
}
fn token_bytes(&self, id: u32) -> Option<&[u8]> {
self.tokens.get(id as usize).map(Vec::as_slice)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Container {
Object,
Array,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Pos {
ValueStart,
ArrayStart,
ObjectStart,
KeyStart,
Colon,
AfterValue,
InString { escape: bool, is_key: bool },
InScalar,
Done,
}
#[derive(Debug, Clone)]
struct JsonMachine {
stack: Vec<Container>,
pos: Pos,
}
impl JsonMachine {
fn new() -> Self {
Self { stack: Vec::new(), pos: Pos::ValueStart }
}
fn step(&mut self, b: u8) -> bool {
loop {
match self.pos {
Pos::InString { .. } => return self.string_step(b),
Pos::InScalar => {
if is_scalar_cont(b) {
return true; }
self.complete_value();
continue;
}
_ => return self.consume(b),
}
}
}
fn consume(&mut self, b: u8) -> bool {
if is_ws(b) {
return true;
}
match self.pos {
Pos::ValueStart => self.begin_value(b),
Pos::ArrayStart => {
if b == b']' {
self.close(Container::Array)
} else {
self.begin_value(b)
}
}
Pos::ObjectStart => match b {
b'"' => {
self.pos = Pos::InString { escape: false, is_key: true };
true
}
b'}' => self.close(Container::Object),
_ => false,
},
Pos::KeyStart => match b {
b'"' => {
self.pos = Pos::InString { escape: false, is_key: true };
true
}
_ => false,
},
Pos::Colon => {
if b == b':' {
self.pos = Pos::ValueStart;
true
} else {
false
}
}
Pos::AfterValue => match b {
b',' => match self.stack.last() {
Some(Container::Object) => {
self.pos = Pos::KeyStart;
true
}
Some(Container::Array) => {
self.pos = Pos::ValueStart;
true
}
None => false, },
b'}' => self.close(Container::Object),
b']' => self.close(Container::Array),
_ => false,
},
Pos::Done => false, Pos::InString { .. } | Pos::InScalar => unreachable!("handled in step()"),
}
}
fn begin_value(&mut self, b: u8) -> bool {
match b {
b'{' => {
self.stack.push(Container::Object);
self.pos = Pos::ObjectStart;
true
}
b'[' => {
self.stack.push(Container::Array);
self.pos = Pos::ArrayStart;
true
}
b'"' => {
self.pos = Pos::InString { escape: false, is_key: false };
true
}
_ if is_scalar_start(b) => {
self.pos = Pos::InScalar;
true
}
_ => false,
}
}
fn close(&mut self, want: Container) -> bool {
match self.stack.last() {
Some(&top) if top == want => {
self.stack.pop();
self.complete_value();
true
}
_ => false,
}
}
fn complete_value(&mut self) {
self.pos = if self.stack.is_empty() { Pos::Done } else { Pos::AfterValue };
}
fn string_step(&mut self, b: u8) -> bool {
let Pos::InString { escape, is_key } = self.pos else {
unreachable!("string_step called outside a string");
};
if escape {
self.pos = Pos::InString { escape: false, is_key };
return true;
}
match b {
b'\\' => {
self.pos = Pos::InString { escape: true, is_key };
true
}
b'"' => {
if is_key {
self.pos = Pos::Colon;
} else {
self.complete_value();
}
true
}
b'\n' | b'\r' => false,
_ => true, }
}
}
fn is_ws(b: u8) -> bool {
matches!(b, b' ' | b'\t' | b'\n' | b'\r')
}
fn is_scalar_start(b: u8) -> bool {
b == b'-' || b.is_ascii_digit() || b.is_ascii_alphabetic()
}
fn is_scalar_cont(b: u8) -> bool {
b.is_ascii_alphanumeric() || matches!(b, b'.' | b'-' | b'+')
}
pub struct JsonStructure<V: TokenVocab> {
vocab: V,
always_allowed: BTreeSet<u32>,
}
impl<V: TokenVocab> JsonStructure<V> {
pub fn new(vocab: V) -> Self {
Self { vocab, always_allowed: BTreeSet::new() }
}
pub fn with_always_allowed(vocab: V, ids: impl IntoIterator<Item = u32>) -> Self {
Self { vocab, always_allowed: ids.into_iter().collect() }
}
fn replay(&self, generated: &[u32]) -> JsonMachine {
let mut machine = JsonMachine::new();
for &id in generated {
if let Some(bytes) = self.vocab.token_bytes(id) {
for &b in bytes {
machine.step(b);
}
}
}
machine
}
}
impl<V: TokenVocab> TokenConstraint for JsonStructure<V> {
fn allowed(&self, state: &DecodeState<'_>) -> AllowedSet {
let machine = self.replay(state.generated);
let count = self.vocab.token_count();
let mut mask = vec![false; count];
for id in 0..count as u32 {
if self.always_allowed.contains(&id) {
mask[id as usize] = true;
continue;
}
let Some(bytes) = self.vocab.token_bytes(id) else { continue };
if bytes.is_empty() {
continue; }
let mut probe = machine.clone();
let mut ok = true;
for &b in bytes {
if !probe.step(b) {
ok = false;
break;
}
}
mask[id as usize] = ok;
}
AllowedSet::Mask(mask)
}
}
pub struct ConstrainedSampler<C: TokenConstraint, S: Sampler> {
constraint: C,
inner: S,
generated: Vec<u32>,
}
impl<C: TokenConstraint, S: Sampler> ConstrainedSampler<C, S> {
pub fn new(constraint: C, inner: S) -> Self {
Self { constraint, inner, generated: Vec::new() }
}
pub fn generated(&self) -> &[u32] {
&self.generated
}
pub fn reset(&mut self) {
self.generated.clear();
}
pub fn try_sample(&mut self, logits: &[f32]) -> Result<u32, ConstraintError> {
let allowed = {
let state = DecodeState { generated: &self.generated };
self.constraint.allowed(&state)
};
let mut work = logits.to_vec();
mask_logits(&mut work, &allowed)?;
let chosen = self.inner.sample(&work);
debug_assert!(
allowed.contains(chosen),
"inner sampler returned masked-out token {chosen}; mask-before-sample invariant broken"
);
self.generated.push(chosen);
Ok(chosen)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sampling::{greedy_argmax, GreedySampler, SamplingConfig, StochasticSampler};
#[test]
fn allowed_set_only_contains_listed_ids() {
let set = AllowedSet::Only([1, 3, 5].into_iter().collect());
assert!(set.contains(3));
assert!(!set.contains(2));
}
#[test]
fn allowed_set_mask_treats_out_of_range_ids_as_not_allowed() {
let set = AllowedSet::Mask(vec![true, false, true]);
assert!(set.contains(0));
assert!(!set.contains(1));
assert!(set.contains(2));
assert!(!set.contains(9), "an id past the mask length must not be allowed");
}
#[test]
fn mask_forces_only_allowed_ids_to_survive_as_neg_inf() {
let allowed = AllowedSet::Only([1, 4].into_iter().collect());
let mut logits = vec![10.0, 2.0, 9.0, 8.0, 1.0];
mask_logits(&mut logits, &allowed).unwrap();
assert!(logits[0].is_infinite() && logits[0] < 0.0, "disallowed id 0 must be -inf");
assert_eq!(logits[1], 2.0, "allowed id 1 must be untouched");
assert!(logits[2].is_infinite() && logits[2] < 0.0, "disallowed id 2 must be -inf");
assert!(logits[3].is_infinite() && logits[3] < 0.0, "disallowed id 3 must be -inf");
assert_eq!(logits[4], 1.0, "allowed id 4 must be untouched");
}
#[test]
fn masked_argmax_is_always_in_the_allowed_set() {
let allowed = AllowedSet::Only([2, 3].into_iter().collect());
let mut logits = vec![100.0, 50.0, 4.0, 9.0, 7.0];
mask_logits(&mut logits, &allowed).unwrap();
let picked = greedy_argmax(&logits);
assert_eq!(picked, 3, "argmax must land on the highest *allowed* logit, not the masked-out max");
assert!(allowed.contains(picked));
}
#[test]
fn mask_survives_temperature_scaling_stays_neg_inf() {
let allowed = AllowedSet::Only([1].into_iter().collect());
let mut logits = vec![5.0, 5.0];
mask_logits(&mut logits, &allowed).unwrap();
let scaled = logits[0] / 0.7;
assert!(scaled.is_infinite() && scaled < 0.0, "-inf must survive temperature division");
}
#[test]
fn empty_allowed_set_is_an_honest_error_not_a_panic() {
let allowed = AllowedSet::Only([99].into_iter().collect());
let mut logits = vec![1.0, 2.0, 3.0];
assert_eq!(mask_logits(&mut logits, &allowed), Err(ConstraintError::NoTokenAllowed));
}
#[test]
fn allowed_tokens_rejects_an_empty_set_at_construction() {
assert_eq!(AllowedTokens::new([]).unwrap_err(), ConstraintError::EmptyConstraint);
}
#[test]
fn allowed_tokens_returns_its_fixed_set_regardless_of_state() {
let c = AllowedTokens::new([2, 7]).unwrap();
let a = c.allowed(&DecodeState { generated: &[] });
let b = c.allowed(&DecodeState { generated: &[2, 2, 2] });
assert_eq!(a, b, "a fixed-set constraint must ignore decode state");
assert!(a.contains(2) && a.contains(7) && !a.contains(3));
}
#[test]
fn constrained_greedy_sampler_only_ever_emits_allowed_ids() {
let constraint = AllowedTokens::new([2, 4]).unwrap();
let mut sampler = ConstrainedSampler::new(constraint, GreedySampler);
for _ in 0..10 {
let logits = vec![100.0, 90.0, 3.0, 80.0, 5.0];
let id = sampler.try_sample(&logits).unwrap();
assert!(id == 2 || id == 4, "constrained greedy emitted disallowed id {id}");
}
}
#[test]
fn constrained_stochastic_sampler_only_ever_emits_allowed_ids() {
let constraint = AllowedTokens::new([1, 3]).unwrap();
let inner = StochasticSampler::new(SamplingConfig {
temperature: 1.2,
top_k: Some(4),
top_p: Some(0.9),
seed: 123,
..SamplingConfig::default()
});
let mut sampler = ConstrainedSampler::new(constraint, inner);
let mut rng_seed = 7u64;
for _ in 0..200 {
rng_seed = rng_seed.wrapping_mul(6364136223846793005).wrapping_add(1);
let logits: Vec<f32> = (0..5).map(|k| ((rng_seed >> (k * 8)) & 0xff) as f32 / 25.0).collect();
let id = sampler.try_sample(&logits).unwrap();
assert!(id == 1 || id == 3, "constrained stochastic emitted disallowed id {id}");
}
}
#[test]
fn constrained_sampler_records_its_generated_prefix() {
let constraint = AllowedTokens::new([2]).unwrap();
let mut sampler = ConstrainedSampler::new(constraint, GreedySampler);
sampler.try_sample(&[0.0, 0.0, 1.0]).unwrap();
sampler.try_sample(&[0.0, 0.0, 1.0]).unwrap();
assert_eq!(sampler.generated(), &[2, 2]);
sampler.reset();
assert_eq!(sampler.generated(), &[] as &[u32]);
}
#[test]
fn constrained_sampler_surfaces_no_token_allowed_as_err() {
let constraint = AllowedTokens::new([9]).unwrap();
let mut sampler = ConstrainedSampler::new(constraint, GreedySampler);
assert_eq!(sampler.try_sample(&[1.0, 2.0, 3.0]), Err(ConstraintError::NoTokenAllowed));
assert_eq!(sampler.generated(), &[] as &[u32], "a failed step must not record a token");
}
fn json_test_vocab() -> SliceVocab {
SliceVocab::new(vec![
b"{".to_vec(), b"}".to_vec(), b"[".to_vec(), b"]".to_vec(), b":".to_vec(), b",".to_vec(), b"\"".to_vec(), b"\"k\"".to_vec(), b"1".to_vec(), b" ".to_vec(), b"\"v\"".to_vec(), ])
}
fn allowed_ids(set: &AllowedSet, count: usize) -> Vec<u32> {
(0..count as u32).filter(|&id| set.contains(id)).collect()
}
#[test]
fn json_document_start_allows_a_value_opener_not_a_close() {
let c = JsonStructure::new(json_test_vocab());
let a = c.allowed(&DecodeState { generated: &[] });
assert!(a.contains(0) && a.contains(2), "must allow '{{' and '['");
assert!(a.contains(8), "must allow a scalar start");
assert!(!a.contains(1) && !a.contains(3), "must not allow a close bracket at start");
assert!(!a.contains(4) && !a.contains(5), "must not allow ':' or ',' at start");
}
#[test]
fn json_after_open_brace_forces_a_key_or_close_only() {
let c = JsonStructure::new(json_test_vocab());
let a = c.allowed(&DecodeState { generated: &[0] });
assert!(a.contains(7), "after '{{' a \"key\" string is allowed");
assert!(a.contains(6), "after '{{' a bare quote (start of a key) is allowed");
assert!(a.contains(1), "after '{{' the object may immediately close with '}}'");
assert!(!a.contains(0), "after '{{' another '{{' (a non-string value) is NOT a valid key");
assert!(!a.contains(8), "after '{{' a bare scalar is NOT a valid key");
assert!(!a.contains(5), "after '{{' a ',' is not valid");
}
#[test]
fn json_after_key_forces_a_colon() {
let c = JsonStructure::new(json_test_vocab());
let a = c.allowed(&DecodeState { generated: &[0, 7] });
let non_ws: Vec<u32> = allowed_ids(&a, 11).into_iter().filter(|&id| id != 9).collect();
assert_eq!(non_ws, vec![4], "after a key only ':' (plus whitespace) is allowed");
}
#[test]
fn json_after_colon_expects_a_value() {
let c = JsonStructure::new(json_test_vocab());
let a = c.allowed(&DecodeState { generated: &[0, 7, 4] });
assert!(a.contains(10), "after ':' a string value is allowed");
assert!(a.contains(8), "after ':' a scalar value is allowed");
assert!(a.contains(0) && a.contains(2), "after ':' a nested object/array is allowed");
assert!(!a.contains(1) && !a.contains(4), "after ':' a '}}' or ':' is not allowed");
}
#[test]
fn json_after_value_in_object_allows_comma_or_close_only() {
let c = JsonStructure::new(json_test_vocab());
let a = c.allowed(&DecodeState { generated: &[0, 7, 4, 10] });
assert!(a.contains(5), "after a value, ',' is allowed");
assert!(a.contains(1), "after a value, '}}' (close object) is allowed");
assert!(!a.contains(3), "a ']' cannot close an object");
assert!(!a.contains(10), "a bare second value without a separator is not allowed");
assert!(!a.contains(8), "a bare scalar value without a separator is not allowed");
}
#[test]
fn json_mid_scalar_may_continue_or_be_separated() {
let c = JsonStructure::new(json_test_vocab());
let a = c.allowed(&DecodeState { generated: &[0, 7, 4, 8] });
assert!(a.contains(8), "mid-scalar, another digit continues the number");
assert!(a.contains(5), "mid-scalar, ',' terminates the scalar and separates");
assert!(a.contains(1), "mid-scalar, '}}' terminates the scalar and closes the object");
assert!(!a.contains(3), "mid-scalar, ']' still cannot close an *object*");
}
#[test]
fn json_balanced_close_returns_to_done() {
let c = JsonStructure::new(json_test_vocab());
let a = c.allowed(&DecodeState { generated: &[0, 7, 4, 8, 1] });
assert!(a.contains(9), "whitespace may follow a complete document");
assert!(
!a.contains(5) && !a.contains(0) && !a.contains(1),
"nothing structural may follow a complete document"
);
}
#[test]
fn json_empty_array_may_close_immediately() {
let c = JsonStructure::new(json_test_vocab());
let a = c.allowed(&DecodeState { generated: &[2] });
assert!(a.contains(3), "an empty array '[]' must be allowed to close");
assert!(a.contains(8), "an array may also start with a value");
assert!(!a.contains(5), "a leading ',' inside a fresh array is not valid");
}
#[test]
fn json_always_allowed_ids_bypass_the_grammar() {
let c = JsonStructure::with_always_allowed(json_test_vocab(), [3]);
let a = c.allowed(&DecodeState { generated: &[] });
assert!(a.contains(3), "an always-allowed id must bypass the structural check");
}
#[test]
fn json_machine_accepts_a_full_nested_document() {
let mut m = JsonMachine::new();
for &b in b"{\"k\":[1,\"v\"]}" {
assert!(m.step(b), "byte {:?} rejected in a valid document", b as char);
}
assert_eq!(m.pos, Pos::Done, "a balanced document must land in Done");
}
#[test]
fn json_machine_rejects_a_mismatched_close() {
let mut m = JsonMachine::new();
assert!(m.step(b'{'));
assert!(!m.step(b']'));
}
}