pagesift_ocr/formula/
tokenizer.rs1use std::path::Path;
8
9pub struct Tokenizer {
11 bytes: Vec<Option<Vec<u8>>>,
13}
14
15impl Tokenizer {
16 pub fn load(dir: &Path) -> Result<Tokenizer, String> {
18 Tokenizer::from_json(&crate::read_json(dir, "tokenizer.json")?)
19 }
20
21 pub fn from_json(j: &serde_json::Value) -> Result<Tokenizer, String> {
22 let decoder = j["decoder"]["type"].as_str().unwrap_or("");
23 if decoder != "ByteLevel" {
24 return Err(format!(
25 "unsupported decoder {decoder:?}, only ByteLevel is supported"
26 ));
27 }
28 let model = j["model"]["type"].as_str().unwrap_or("");
29 if model != "BPE" {
30 return Err(format!(
31 "unsupported tokenizer model {model:?}, only BPE is supported"
32 ));
33 }
34 let vocab = j["model"]["vocab"]
35 .as_object()
36 .ok_or("tokenizer.json has no model.vocab")?;
37 let table = byte_table();
38 let mut bytes: Vec<Option<Vec<u8>>> = Vec::new();
39 let mut put = |id: usize, v: Option<Vec<u8>>| {
40 if bytes.len() <= id {
41 bytes.resize(id + 1, None);
42 }
43 bytes[id] = v;
44 };
45 for (tok, id) in vocab {
46 let id = id.as_u64().ok_or("a vocabulary id is not an integer")? as usize;
47 put(id, Some(token_bytes(tok, &table)));
48 }
49 for a in j["added_tokens"].as_array().into_iter().flatten() {
51 let id = a["id"]
52 .as_u64()
53 .ok_or("an added_tokens id is not an integer")? as usize;
54 let content = a["content"]
55 .as_str()
56 .ok_or("an added_tokens entry has no content")?;
57 let special = a["special"].as_bool().unwrap_or(false);
58 put(id, (!special).then(|| token_bytes(content, &table)));
59 }
60 Ok(Tokenizer { bytes })
61 }
62
63 pub fn len(&self) -> usize {
65 self.bytes.len()
66 }
67
68 pub fn is_empty(&self) -> bool {
69 self.bytes.is_empty()
70 }
71
72 pub fn decode(&self, ids: &[u32]) -> String {
74 let mut out = Vec::new();
75 for &id in ids {
76 if let Some(Some(b)) = self.bytes.get(id as usize) {
77 out.extend_from_slice(b);
78 }
79 }
80 String::from_utf8_lossy(&out).into_owned()
81 }
82}
83
84fn token_bytes(tok: &str, table: &[Option<u8>]) -> Vec<u8> {
86 tok.chars()
87 .map(|c| table.get(c as usize).copied().flatten())
88 .collect::<Option<Vec<u8>>>()
89 .unwrap_or_else(|| tok.as_bytes().to_vec())
90}
91
92fn byte_table() -> Vec<Option<u8>> {
96 let visible = |b: u8| (33..=126).contains(&b) || (161..=172).contains(&b) || b >= 174;
98 const SHIFT: usize = 256;
100 let mut table = vec![None; SHIFT * 2];
101 let mut n = 0;
102 for b in 0..=u8::MAX {
103 if visible(b) {
104 table[usize::from(b)] = Some(b);
105 } else {
106 table[SHIFT + n] = Some(b);
107 n += 1;
108 }
109 }
110 table.truncate(SHIFT + n);
111 table
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 fn mini() -> Tokenizer {
120 let j = serde_json::json!({
121 "decoder": {"type": "ByteLevel", "add_prefix_space": true},
122 "model": {"type": "BPE", "vocab": {
123 "<s>": 0, "<pad>": 1, "</s>": 2, "(": 30, "d": 90, "\\": 82,
124 "ĉ": 220, "Ċ": 221, "ĠĠ": 304, "ĠâĪĴ": 612, "âĢĵ": 853, "âĪĴ": 1059,
125 "Ġμ": 1173, "ä": 183, "¸": 139, "Ń": 278, "ä¸": 19598, "Ġown": 4158
126 }},
127 "added_tokens": [
128 {"id": 0, "content": "<s>", "special": true},
129 {"id": 1, "content": "<pad>", "special": true},
130 {"id": 2, "content": "</s>", "special": true}
131 ]
132 });
133 Tokenizer::from_json(&j).unwrap()
134 }
135
136 #[test]
137 fn decodes_byte_level_tokens() {
138 let t = mini();
139 assert_eq!(t.decode(&[612]), " −");
140 assert_eq!(t.decode(&[853, 1059]), "–−");
141 assert_eq!(t.decode(&[1173]), " μ");
142 assert_eq!(t.decode(&[304, 220, 221]), " \t\n");
143 assert_eq!(t.decode(&[82, 90, 30, 4158]), "\\d( own");
144 assert_eq!(t.decode(&[183, 139, 278]), "中");
146 assert_eq!(t.decode(&[19598]), "\u{fffd}");
148 }
149
150 #[test]
151 fn skips_special_and_unknown() {
152 let t = mini();
153 assert_eq!(t.decode(&[2, 0, 90, 1, 2, 99_999]), "d");
154 }
155
156 #[test]
157 fn byte_table_is_a_bijection() {
158 let t = byte_table();
159 let mut seen = [false; 256];
160 for b in t.iter().flatten() {
161 assert!(!seen[usize::from(*b)]);
162 seen[usize::from(*b)] = true;
163 }
164 assert!(seen.iter().all(|&s| s));
165 assert_eq!(t['Ġ' as usize], Some(b' '));
167 assert_eq!(t['Ċ' as usize], Some(b'\n'));
168 }
169
170 #[test]
172 fn real_tokenizer_if_present() {
173 let Some(dir) = crate::models::cache_root()
174 .map(|r| r.join("PP-FormulaNet_plus-L_safetensors"))
175 .filter(|d| d.join("tokenizer.json").exists())
176 else {
177 return;
178 };
179 let t = Tokenizer::load(&dir).unwrap();
180 assert_eq!(t.len(), 50_000);
181 assert_eq!(t.decode(&[612, 1059, 1173, 4158]), " −− μ own");
182 assert_eq!(t.decode(&[183, 139, 278]), "中");
183 assert_eq!(t.decode(&[0, 1, 2, 3, 22]), "");
184 }
185}