1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
//! Bounded, byte-preserving output pieces for the pinned GPT-2/Qwen35 vocabulary.
//!
//! This module only reverses the serialized byte alphabet. It does not tokenize
//! prompts, apply merges or chat templates, or decide which generated IDs stop a
//! request. A piece can end partway through a UTF-8 sequence; callers must retain
//! those bytes across token boundaries before interpreting text.
use bonsai_gguf::{Metadata, MetadataArray};
use crate::{Result, invalid};
/// Maximum admitted output vocabulary, independent of untrusted metadata counts.
pub const MAX_VOCABULARY_TOKENS: usize = 1_000_000;
/// Maximum retained decoded piece bytes, excluding the bounded entry table.
pub const MAX_VOCABULARY_BYTES: usize = 64 * 1024 * 1024;
/// Maximum decoded bytes in one output piece.
pub const MAX_PIECE_BYTES: usize = 1024 * 1024;
#[derive(Clone, Copy, Debug)]
enum TokenType {
Normal,
Unknown,
Control,
UserDefined,
Unused,
Byte,
}
impl TokenType {
fn from_i32(value: i32) -> Result<Self> {
match value {
1 => Ok(Self::Normal),
2 => Ok(Self::Unknown),
3 => Ok(Self::Control),
4 => Ok(Self::UserDefined),
5 => Ok(Self::Unused),
6 => Ok(Self::Byte),
_ => Err(invalid(format!(
"unsupported vocabulary token type {value}"
))),
}
}
}
#[derive(Debug)]
struct Entry {
end: usize,
kind: TokenType,
}
/// Validated output pieces with one bounded, contiguous byte allocation.
///
/// Normal pieces use the inverse GPT-2 byte alphabet. Control and user-defined
/// pieces retain literal UTF-8 bytes; byte pieces use strict `<0xHH>` notation.
/// Unknown and unused IDs are retained but cannot be rendered. Control pieces
/// are never silently omitted: [`Self::is_control`] lets the caller explicitly
/// choose its display policy without guessing from a token's spelling.
#[derive(Debug)]
pub struct Vocabulary {
entries: Vec<Entry>,
bytes: Vec<u8>,
}
/// Fully checked metadata, without allocating decoded output pieces.
///
/// The private slices have equal, bounded lengths and every renderable piece
/// obeys the same spelling and decoded-size contract as [`Vocabulary`].
pub(crate) struct ValidatedVocabulary<'a> {
tokens: &'a [String],
types: &'a [i32],
decoded_bytes: usize,
}
impl<'a> ValidatedVocabulary<'a> {
pub(crate) fn from_metadata(metadata: &'a Metadata, vocab_size: usize) -> Result<Self> {
for (key, expected) in [
("tokenizer.ggml.model", "gpt2"),
("tokenizer.ggml.pre", "qwen35"),
] {
if metadata.get(key).and_then(|value| value.as_str()) != Some(expected) {
return Err(invalid(format!("vocabulary requires {key}={expected}")));
}
}
if vocab_size == 0 || vocab_size > MAX_VOCABULARY_TOKENS {
return Err(invalid("vocabulary size exceeds supported bounds"));
}
let Some(MetadataArray::String(tokens)) = metadata
.get("tokenizer.ggml.tokens")
.and_then(|value| value.as_array())
else {
return Err(invalid("vocabulary requires a STRING token array"));
};
let Some(MetadataArray::I32(types)) = metadata
.get("tokenizer.ggml.token_type")
.and_then(|value| value.as_array())
else {
return Err(invalid("vocabulary requires an INT32 token type array"));
};
if tokens.len() != vocab_size || types.len() != vocab_size {
return Err(invalid("vocabulary counts do not match graph output size"));
}
let mut required = 0_usize;
for (token, &kind) in tokens.iter().zip(types) {
let length = piece_length(token, TokenType::from_i32(kind)?)?;
required = required
.checked_add(length)
.filter(|&bytes| bytes <= MAX_VOCABULARY_BYTES)
.ok_or_else(|| invalid("vocabulary exceeds decoded byte limit"))?;
}
Ok(Self {
tokens,
types,
decoded_bytes: required,
})
}
pub(crate) const fn len(&self) -> usize {
self.tokens.len()
}
pub(crate) fn entries(&self) -> impl ExactSizeIterator<Item = (&'a str, i32)> {
self.tokens
.iter()
.map(String::as_str)
.zip(self.types.iter().copied())
}
}
impl Vocabulary {
/// Validates the pinned `gpt2` / `qwen35` output vocabulary against the graph.
///
/// Requires string tokens and GGUF `INT32` token types with exactly
/// `vocab_size` elements each. Construction validates every renderable piece
/// before reserving decoded storage. At most [`MAX_VOCABULARY_TOKENS`]
/// entries, [`MAX_VOCABULARY_BYTES`] decoded bytes, and [`MAX_PIECE_BYTES`]
/// bytes per piece are admitted. The entry table additionally retains at
/// most `MAX_VOCABULARY_TOKENS * size_of::<Entry>()` bytes.
///
/// # Errors
/// Rejects unsupported tokenizer metadata, graph/count mismatches, invalid
/// token types, malformed byte alphabet or byte notation, empty renderable
/// pieces, resource limits, and allocation failures.
pub fn from_metadata(metadata: &Metadata, vocab_size: usize) -> Result<Self> {
let vocabulary = ValidatedVocabulary::from_metadata(metadata, vocab_size)?;
let mut entries = Vec::new();
entries.try_reserve_exact(vocabulary.len())?;
let mut bytes = Vec::new();
bytes.try_reserve_exact(vocabulary.decoded_bytes)?;
for (token, kind) in vocabulary.entries() {
let kind = TokenType::from_i32(kind)?;
match kind {
TokenType::Normal => {
for character in token.chars() {
bytes.push(decode_character(character)?);
}
}
TokenType::Control | TokenType::UserDefined => {
bytes.extend_from_slice(token.as_bytes());
}
TokenType::Byte => bytes.push(decode_byte(token)?),
TokenType::Unknown | TokenType::Unused => {}
}
entries.push(Entry {
end: bytes.len(),
kind,
});
}
Ok(Self { entries, bytes })
}
/// Number of output IDs, exactly matching the validated graph output size.
#[must_use]
pub const fn vocab_size(&self) -> usize {
self.entries.len()
}
/// Borrows an output piece without interpreting UTF-8 or omitting controls.
///
/// # Errors
/// Rejects out-of-range, unknown and unused IDs. A returned byte slice can
/// contain any byte, including NUL or an incomplete UTF-8 sequence.
pub fn piece(&self, id: u32) -> Result<&[u8]> {
let index = usize::try_from(id)?;
let entry = self.entry(index)?;
if matches!(entry.kind, TokenType::Unknown | TokenType::Unused) {
return Err(invalid(format!("vocabulary token {id} has no text piece")));
}
let start = index
.checked_sub(1)
.map_or(0, |previous| self.entries[previous].end);
Ok(&self.bytes[start..entry.end])
}
/// Reports the declared control type, without inspecting the token text.
///
/// # Errors
/// Rejects IDs outside the validated output vocabulary.
pub fn is_control(&self, id: u32) -> Result<bool> {
Ok(matches!(
self.entry(usize::try_from(id)?)?.kind,
TokenType::Control
))
}
fn entry(&self, index: usize) -> Result<&Entry> {
self.entries
.get(index)
.ok_or_else(|| invalid("output token ID exceeds validated vocabulary"))
}
}
fn piece_length(token: &str, kind: TokenType) -> Result<usize> {
let length = match kind {
TokenType::Normal => {
let mut length = 0_usize;
for character in token.chars() {
decode_character(character)?;
length += 1;
if length > MAX_PIECE_BYTES {
return Err(invalid("vocabulary piece exceeds decoded byte limit"));
}
}
length
}
TokenType::Control | TokenType::UserDefined => token.len(),
TokenType::Byte => {
decode_byte(token)?;
1
}
TokenType::Unknown | TokenType::Unused => return Ok(0),
};
if length == 0 || length > MAX_PIECE_BYTES {
return Err(invalid("vocabulary piece has invalid decoded length"));
}
Ok(length)
}
pub(crate) fn decode_character(character: char) -> Result<u8> {
let scalar = u32::from(character);
let byte = match scalar {
33..=126 | 161..=172 | 174..=255 => scalar,
256..=288 => scalar - 256,
289..=322 => scalar - 162,
323 => 173,
_ => return Err(invalid("normal token contains a non-GPT-2 byte character")),
};
Ok(u8::try_from(byte)?)
}
fn decode_byte(token: &str) -> Result<u8> {
let Some(hex) = token
.strip_prefix("<0x")
.and_then(|value| value.strip_suffix('>'))
.filter(|value| value.len() == 2 && value.bytes().all(|byte| byte.is_ascii_hexdigit()))
else {
return Err(invalid("byte token requires exact <0xHH> notation"));
};
Ok(u8::from_str_radix(hex, 16)?)
}