use lattice_inference::grammar::engine::{last_build_profile, probe_reachable_states};
use lattice_inference::grammar::trie::ByteTrie;
use lattice_inference::grammar::vocab_partition::MAX_GRAMMAR_STATES;
use lattice_inference::grammar::{GrammarEngine, GrammarSpec};
use std::time::Instant;
const DEFAULT_VOCAB_SIZE: usize = 248_320;
const DEFAULT_REPS: usize = 10;
const DEFAULT_PROBE_CAP: usize = 512;
fn env_usize(name: &str, default: usize) -> usize {
std::env::var(name)
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(default)
}
fn simple_schema() -> serde_json::Value {
serde_json::json!({"type": "string", "enum": ["yes", "no", "maybe"]})
}
fn deep_schema() -> serde_json::Value {
serde_json::json!({
"type": "object",
"properties": {
"level1": {
"type": "object",
"properties": {
"level2": {
"type": "object",
"properties": {
"level3": {
"type": "object",
"properties": {
"level4": {
"type": "object",
"properties": {
"status": {"type": "string", "enum": ["active", "inactive", "pending", "archived", "deleted", "draft"]},
"value": {"type": "integer"}
},
"required": ["status", "value"]
}
},
"required": ["level4"]
},
"category": {"type": "string", "enum": ["alpha", "beta", "gamma", "delta", "epsilon", "zeta"]}
},
"required": ["level3", "category"]
},
"tags": {"type": "array", "items": {"type": "string"}}
},
"required": ["level2", "tags"]
},
"items": {"type": "array", "items": {"type": "integer"}},
"flags": {"type": "array", "items": {"type": "boolean"}},
"priority": {"type": "string", "enum": ["low", "medium", "high", "urgent", "critical", "none"]},
"region": {"type": "string", "enum": ["us", "eu", "apac", "latam", "mea", "other"]},
"mode": {"type": "string", "enum": ["sync", "async", "batch", "stream", "manual", "auto"]},
"role": {"type": "string", "enum": ["admin", "user", "guest", "owner", "viewer", "editor"]}
},
"required": ["level1", "items", "flags", "priority", "region", "mode", "role"]
})
}
struct Xorshift64(u64);
impl Xorshift64 {
fn next_u64(&mut self) -> u64 {
let mut x = self.0;
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
self.0 = x;
x
}
}
fn synthetic_vocab(n: usize) -> Vec<Vec<u8>> {
let mut v: Vec<Vec<u8>> = Vec::with_capacity(n);
for b in 0u32..256 {
v.push(vec![b as u8]);
}
const LITERALS: &[&str] = &[
"{", "}", "[", "]", ":", ",", "\"", ": ", ", ", "\": ", "\",", "\"}", "{\"", "level1",
"level2", "level3", "level4", "status", "value", "category", "tags", "items", "flags",
"priority", "region", "mode", "role", "active", "inactive", "pending", "archived",
"deleted", "draft", "alpha", "beta", "gamma", "delta", "epsilon", "zeta", "low", "medium",
"high", "urgent", "critical", "none", "us", "eu", "apac", "latam", "mea", "other", "sync",
"async", "batch", "stream", "manual", "auto", "admin", "user", "guest", "owner", "viewer",
"editor", "true", "false", "null",
];
for lit in LITERALS {
v.push(lit.as_bytes().to_vec());
}
let mut rng = Xorshift64(0x9E3779B97F4A7C15);
while v.len() < n {
let r = rng.next_u64();
let bucket = r % 100;
let len: usize = if bucket < 40 {
1 + (r >> 8) as usize % 2 } else if bucket < 70 {
3 + (r >> 8) as usize % 2 } else if bucket < 90 {
5 + (r >> 8) as usize % 4 } else {
9 + (r >> 8) as usize % 12 };
let mut bytes = Vec::with_capacity(len);
for _ in 0..len {
let byte_r = rng.next_u64();
bytes.push(33u8 + (byte_r % 94) as u8);
}
v.push(bytes);
}
v.truncate(n);
v
}
fn stats(mut xs: Vec<u64>) -> (u64, u64, f64, u64) {
xs.sort_unstable();
let min = xs[0];
let max = xs[xs.len() - 1];
let mean = xs.iter().sum::<u64>() as f64 / xs.len() as f64;
let median = xs[xs.len() / 2];
(min, max, mean, median)
}
fn measure_schema(
label: &str,
schema: serde_json::Value,
vocab: &[Vec<u8>],
reps: usize,
probe_cap: usize,
) {
let spec = GrammarSpec::JsonSchema(schema);
let probe_t0 = Instant::now();
let probed_states =
probe_reachable_states(&spec, vocab, probe_cap).expect("schema must compile");
let probe_ns = probe_t0.elapsed().as_nanos() as u64;
let probe_truncated = probed_states >= probe_cap;
if std::env::var("GRAMTIME_PROBE_ONLY").is_ok() {
println!(
"RESULT kind=probe_only label={label} probed_states={probed_states} \
probe_truncated={probe_truncated} probe_ns={probe_ns}"
);
return;
}
let mut partition_ns = Vec::with_capacity(reps);
let mut bfs_ns = Vec::with_capacity(reps);
let mut reachable_raw = 0usize;
let mut exceeds_budget = false;
let harness_t0 = Instant::now();
for rep in 0..reps {
let engine =
GrammarEngine::new(&spec, vocab.to_vec()).expect("schema must build an engine");
let bp = last_build_profile();
partition_ns.push(bp.partition_build_ns);
bfs_ns.push(bp.bfs_ns);
reachable_raw = bp.reachable_states;
exceeds_budget = engine.exceeds_state_budget();
println!(
"RESULT kind=schema_rep label={label} rep={rep} \
elapsed_since_harness_start_ns={} partition_build_ns={} bfs_ns={}",
harness_t0.elapsed().as_nanos() as u64,
bp.partition_build_ns,
bp.bfs_ns,
);
}
let effective_states = reachable_raw.min(MAX_GRAMMAR_STATES);
let (p_min, p_max, p_mean, p_median) = stats(partition_ns);
let (b_min, b_max, b_mean, b_median) = stats(bfs_ns);
let ns_per_pair = p_mean / (effective_states as f64 * vocab.len() as f64);
println!(
"RESULT kind=schema label={label} probed_states={probed_states} \
probe_truncated={probe_truncated} probe_ns={probe_ns} \
reachable_states_raw={reachable_raw} effective_states={effective_states} \
exceeds_state_budget={exceeds_budget} \
vocab_size={} reps={reps} \
partition_build_ns_min={p_min} partition_build_ns_max={p_max} \
partition_build_ns_mean={p_mean:.1} partition_build_ns_median={p_median} \
bfs_ns_min={b_min} bfs_ns_max={b_max} bfs_ns_mean={b_mean:.1} bfs_ns_median={b_median} \
ns_per_state_token_pair={ns_per_pair:.6}",
vocab.len(),
);
}
fn default_tokenizer_path() -> Option<std::path::PathBuf> {
let home = std::env::var("HOME").ok()?;
Some(std::path::PathBuf::from(home).join(".lattice/models/qwen3.5-0.8b/tokenizer.json"))
}
fn load_vocab(vocab_size: usize) -> (Vec<Vec<u8>>, &'static str, String) {
let path = match std::env::var("GRAMTIME_TOKENIZER_JSON") {
Ok(p) if p.is_empty() => None,
Ok(p) => Some(std::path::PathBuf::from(p)),
Err(_) => default_tokenizer_path(),
};
if let Some(path) = path
&& path.is_file()
{
match lattice_inference::BpeTokenizer::from_tokenizer_json(&path) {
Ok(tok) => match tok.vocab_bytes(vocab_size) {
Ok(vocab) => {
return (vocab, "real", path.display().to_string());
}
Err(e) => {
eprintln!(
"[gramtime] real tokenizer at {} could not fill vocab_size={vocab_size}: {e} — falling back to synthetic",
path.display()
);
}
},
Err(e) => {
eprintln!(
"[gramtime] failed to load tokenizer at {}: {e} — falling back to synthetic",
path.display()
);
}
}
} else {
eprintln!("[gramtime] no real tokenizer found — falling back to synthetic");
}
(synthetic_vocab(vocab_size), "synthetic", String::new())
}
fn main() {
let vocab_size = env_usize("GRAMTIME_VOCAB_SIZE", DEFAULT_VOCAB_SIZE);
let reps = env_usize("GRAMTIME_REPS", DEFAULT_REPS);
let probe_cap = env_usize("GRAMTIME_PROBE_CAP", DEFAULT_PROBE_CAP);
eprintln!("[gramtime] loading vocab: {vocab_size} tokens");
let (vocab, real_or_synthetic, source) = load_vocab(vocab_size);
println!(
"RESULT kind=vocab vocab_size={} real_or_synthetic={real_or_synthetic} source={source:?}",
vocab.len()
);
measure_schema("simple", simple_schema(), &vocab, reps, probe_cap);
measure_schema("deep", deep_schema(), &vocab, reps, probe_cap);
let mut trie_ns = Vec::with_capacity(reps);
for rep in 0..reps {
let t0 = Instant::now();
let trie = ByteTrie::build(&vocab);
let ns = t0.elapsed().as_nanos() as u64;
trie_ns.push(ns);
std::hint::black_box(&trie);
println!("RESULT kind=trie_build_rep rep={rep} trie_build_ns={ns}");
}
let (t_min, t_max, t_mean, t_median) = stats(trie_ns);
println!(
"RESULT kind=trie_build vocab_size={} reps={reps} \
trie_build_ns_min={t_min} trie_build_ns_max={t_max} \
trie_build_ns_mean={t_mean:.1} trie_build_ns_median={t_median}",
vocab.len(),
);
}