Overview
GLRMask is a grammar-constrained decoding library for high-throughput LLM serving. It is optimized for fast mask computation with extremely low tail latency, even for complex grammars.
GLRMask supports general context-free grammars, including ambiguous ones. It accepts JSON schemas and Lark grammars. GLRM is its internal grammar format.
Benchmarks
Care about compile times? Try dynamic mode.
GLRMask’s dynamic mode trades some mask-generation speed for lower cold-start latency. It is heavily based on llguidance.
Time between masks
Statistics
Lower is better.
Time between masks
· µs| Statistic | GLRMaskstatic | llguidance |
|---|---|---|
| mean | 3.0 | 25.9 |
| 2.5 | 10.6 | |
| 5.1 | 40.7 | |
| 12.9 | 289.9 | |
| 27.5 | 1092.6 | |
| 40.0 | 3549.0 | |
| 77.0 | 6931.5 |
Time to first mask
· ms| Statistic | GLRMaskstatic | llguidance |
|---|---|---|
| mean | 35.5 | 1.2 |
| p50 | 13.5 | 0.8 |
| p90 | 81.7 | 1.8 |
| p99 | 312.9 | 11.1 |
| max | 1390.1 | 231.0 |
Measurement details
Measured on AWS c8i.8xlarge (Intel Xeon 6975P-C, Linux) with the Llama 3.1 8B Instruct vocabulary. TTFM covers 8,326 schemas with successful builds and timings across all four engines. TBM covers 11,277 complete valid trajectories accepted by all four engines (1,734,930 token positions); failed, incorrect and incomplete trajectories are excluded. Timings use one wall-clock pass per engine; memory limits differed. See methodology and source data.
Full-run outcomes
| Mode | Build OK | Build errors | Runtime-error examples |
|---|---|---|---|
| GLRMask | 9325 | 233 | 0 |
| GLRMask dynamic | 9326 | 232 | 0 |
| GLRMask Balanced | 9326 | 232 | 0 |
| llg 1.8.0 | 8332 | 1226 | 6 |
Time between masks
Statistics
Lower is better.
Time between masks
· µs| Statistic | GLRMaskstatic | llguidance |
|---|---|---|
| mean | 16.9 | 1052.2 |
| 18.2 | 856.6 | |
| 30.4 | 2521.7 | |
| p95 | 35.7 | 2727.0 |
| 47.9 | 3097.0 | |
| 71.1 | 3597.1 | |
| 77.4 | 3848.2 | |
| 77.4 | 3867.9 |
Time to first mask
· ms| Statistic | GLRMaskstatic | llguidance |
|---|---|---|
| Value | 5089.4 | 8.0 |
Measurement details
Measured on AWS c8i.8xlarge (Intel Xeon 6975P-C, Linux) with the Llama 3.1 8B Instruct vocabulary. TTFM averages 55 case records of one shared grammar; TBM covers 28 complete valid trajectories accepted by all four engines (3,805 token positions). Timings use one wall-clock pass per engine. Some fixtures fail semantic checks, and the companion grammars differ in policy. See methodology and source data.
How it works
Getting started
Installation
python -m pip install glrmask
Python quickstart
import glrmask
vocab = glrmask.Vocab.from_id_to_bytes({0: b"hello", 1: b" ", 2: b"world"})
grammar = glrmask.Grammar.from_ebnf('start ::= "hello" " " "world"')
constraint = grammar.compile(vocab)
state = constraint.start()
for token_id, expected in [(0, [True, False, False]),
(1, [False, True, False]),
(2, [False, False, True])]:
assert state.mask().tolist() == expected
state.commit_token(token_id)
assert state.is_accepting()
Python masks are NumPy Boolean arrays indexed by original model token ID.
Rust quickstart
use glrmask::{Grammar, Vocab};
fn main() -> glrmask::Result<()> {
let vocab = Vocab::new(vec![
(0, b"hello".to_vec()), (1, b" ".to_vec()), (2, b"world".to_vec()),
]);
let grammar = Grammar::from_ebnf(r#"start ::= "hello" " " "world""#);
let constraint = grammar.compile(&vocab)?;
let mut state = constraint.start();
for (token_id, expected) in [(0, 1), (1, 2), (2, 4)] {
assert_eq!(state.mask()[0] & 7, expected);
state.commit_token(token_id)?;
}
assert!(state.is_accepting());
Ok(())
}
Rust masks are packed u32 bitsets. Bit token_id % 32 of word token_id / 32 indicates whether that token is allowed.
Core concepts
GLRMask has four core types:
Grammar: source grammar semantics and bindings.PreparedGrammar: reusable vocabulary-specific compiled machinery, which can still have unresolved bindings.Constraint: a closed, runnable grammar compiled against a vocabulary.ConstraintState: the mutable state for one generated sequence.
Compile a Grammar directly, or use prepare() and link() for reusable components. Create a state with start(), then compute the next-token mask, sample an allowed token, and commit it.
Grammar and PreparedGrammar keep reusable definitions with mutable bindings. bind() fills one declared slot on the handle. Clones and previously attached children retain independent binding snapshots; a failed bind leaves the handle unchanged.