logicaffeine_compile/model/hf/
tiktoken.rs1use super::encode::byte_char;
16use super::onig::Regex;
17use serde_json::Value;
18use std::collections::HashMap;
19use std::io;
20use std::path::Path;
21
22fn invalid(why: impl Into<String>) -> io::Error {
23 io::Error::new(io::ErrorKind::InvalidData, why.into())
24}
25
26const MAX_ENCODE_CHARS: usize = 400_000;
28const MAX_RUN_CHARS: usize = 25_000;
30
31pub struct Tiktoken {
33 tokens: Vec<(Vec<u8>, u32)>,
35 ranks: HashMap<Vec<u8>, u32>,
36 pattern: Regex,
37 specials: Vec<(String, u32)>,
39}
40
41impl Tiktoken {
42 pub fn open(dir: &Path) -> io::Result<Tiktoken> {
45 let config = read_json(&dir.join("tokenizer_config.json"))?
46 .ok_or_else(|| invalid(format!("{}: no tokenizer_config.json", dir.display())))?;
47 let module = config
48 .get("auto_map")
49 .and_then(|m| m.get("AutoTokenizer"))
50 .and_then(|a| a.get(0).or(Some(a)))
51 .and_then(Value::as_str)
52 .and_then(|s| s.split_once('.'))
53 .map(|(module, _)| module.to_string())
54 .ok_or_else(|| invalid(format!("{}: tokenizer_config.json names no tokenizer code (auto_map)", dir.display())))?;
55 let code_path = dir.join(format!("{module}.py"));
56 let code = std::fs::read_to_string(&code_path).map_err(|e| invalid(format!("{}: {e}", code_path.display())))?;
57 let (pattern, reserved) = pattern_of(&code).map_err(|e| invalid(format!("{}: {e}", code_path.display())))?;
58 let pattern = Regex::tiktoken(&pattern).map_err(|e| invalid(format!("{}: {e}", code_path.display())))?;
59
60 let model_path = dir.join("tiktoken.model");
61 let text = std::fs::read_to_string(&model_path).map_err(|e| invalid(format!("{}: {e}", model_path.display())))?;
62 let mut tokens = Vec::new();
63 for (n, line) in text.lines().enumerate().filter(|(_, l)| !l.is_empty()) {
64 let mut fields = line.split_whitespace();
65 let (Some(b64), Some(rank), None) = (fields.next(), fields.next(), fields.next()) else {
66 return Err(invalid(format!("{}: line {} is not `<base64> <rank>`", model_path.display(), n + 1)));
67 };
68 let bytes = base64(b64).ok_or_else(|| invalid(format!("{}: line {}: bad base64", model_path.display(), n + 1)))?;
69 let rank = rank.parse().map_err(|_| invalid(format!("{}: line {}: bad rank", model_path.display(), n + 1)))?;
70 tokens.push((bytes, rank));
71 }
72 let mut ranks: HashMap<Vec<u8>, u32> = HashMap::new();
74 let mut order: Vec<Vec<u8>> = Vec::new();
75 for (bytes, rank) in tokens {
76 if ranks.insert(bytes.clone(), rank).is_none() {
77 order.push(bytes);
78 }
79 }
80 let tokens: Vec<(Vec<u8>, u32)> = order
81 .into_iter()
82 .map(|b| {
83 let rank = ranks[&b];
84 (b, rank)
85 })
86 .collect();
87
88 let named: HashMap<u32, String> = match config.get("added_tokens_decoder") {
89 Some(Value::Object(d)) => d
90 .iter()
91 .filter_map(|(id, t)| Some((id.parse().ok()?, t.get("content")?.as_str()?.to_string())))
92 .collect(),
93 _ => HashMap::new(),
94 };
95 let base = tokens.len() as u32;
96 let specials: Vec<(String, u32)> = (base..base + reserved)
97 .map(|id| (named.get(&id).cloned().unwrap_or_else(|| format!("<|reserved_token_{id}|>")), id))
98 .collect();
99 Ok(Tiktoken { tokens, ranks, pattern, specials })
100 }
101
102 pub fn tokens(&self) -> &[(Vec<u8>, u32)] {
104 &self.tokens
105 }
106
107 pub fn specials(&self) -> &[(String, u32)] {
109 &self.specials
110 }
111
112 pub fn encode(&self, text: &str) -> Vec<u32> {
114 let chars: Vec<char> = text.chars().collect();
115 let mut out = Vec::new();
116 for chunk in chars.chunks(MAX_ENCODE_CHARS) {
117 for piece in runs_split(chunk, MAX_RUN_CHARS) {
118 self.encode_with_specials(&piece.iter().collect::<String>(), &mut out);
119 }
120 }
121 out
122 }
123
124 fn encode_with_specials(&self, text: &str, out: &mut Vec<u32>) {
127 let mut start = 0;
128 while start <= text.len() {
129 let next = self
130 .specials
131 .iter()
132 .filter_map(|(s, id)| text[start..].find(s.as_str()).map(|at| (start + at, s.len(), *id)))
133 .min_by_key(|&(at, len, _)| (at, std::cmp::Reverse(len)));
134 let end = next.map_or(text.len(), |(at, _, _)| at);
135 self.encode_ordinary(&text[start..end], out);
136 match next {
137 Some((at, len, id)) => {
138 out.push(id);
139 start = at + len;
140 }
141 None => break,
142 }
143 }
144 }
145
146 fn encode_ordinary(&self, text: &str, out: &mut Vec<u32>) {
148 let cps: Vec<u32> = text.chars().map(u32::from).collect();
149 for (lo, hi) in self.pattern.find_iter(&cps) {
150 let piece: String = cps[lo..hi].iter().filter_map(|&c| char::from_u32(c)).collect();
151 match self.ranks.get(piece.as_bytes()) {
152 Some(&rank) => out.push(rank),
153 None => out.extend(byte_pair_encode(piece.as_bytes(), &self.ranks)),
154 }
155 }
156 }
157
158 pub fn merge_of(&self, token: &[u8], rank: u32) -> Option<(Vec<u8>, Vec<u8>)> {
161 let mut parts: Vec<Vec<u8>> = token.iter().map(|&b| vec![b]).collect();
162 loop {
163 let mut best: Option<(usize, u32)> = None;
164 for i in 0..parts.len().saturating_sub(1) {
165 let pair = [parts[i].as_slice(), parts[i + 1].as_slice()].concat();
166 if let Some(&r) = self.ranks.get(&pair) {
167 if best.is_none_or(|(_, b)| r < b) {
168 best = Some((i, r));
169 }
170 }
171 }
172 match best {
173 Some((i, r)) if r < rank => {
174 let right = parts.remove(i + 1);
175 parts[i].extend(right);
176 }
177 _ => break,
178 }
179 }
180 (parts.len() == 2).then(|| (parts[0].clone(), parts[1].clone()))
181 }
182}
183
184pub fn split(pattern: &str, text: &str) -> Result<Vec<String>, String> {
186 let re = Regex::tiktoken(pattern)?;
187 let cps: Vec<u32> = text.chars().map(u32::from).collect();
188 Ok(re.find_iter(&cps).into_iter().map(|(lo, hi)| cps[lo..hi].iter().filter_map(|&c| char::from_u32(c)).collect()).collect())
189}
190
191pub fn byte_level(bytes: &[u8]) -> String {
193 bytes.iter().map(|&b| byte_char(b)).collect()
194}
195
196fn byte_pair_encode(piece: &[u8], ranks: &HashMap<Vec<u8>, u32>) -> Vec<u32> {
199 if piece.len() == 1 {
200 return vec![ranks[piece]];
201 }
202 let rank_of = |a: usize, b: usize| ranks.get(&piece[a..b]).copied().unwrap_or(u32::MAX);
203 let mut parts: Vec<(usize, u32)> = (0..piece.len() - 1).map(|i| (i, rank_of(i, i + 2))).collect();
205 parts.push((piece.len() - 1, u32::MAX));
206 parts.push((piece.len(), u32::MAX));
207 loop {
208 let (mut min, mut at) = (u32::MAX, usize::MAX);
209 for (i, &(_, r)) in parts[..parts.len() - 1].iter().enumerate() {
210 if r < min {
211 (min, at) = (r, i);
212 }
213 }
214 if min == u32::MAX {
215 break;
216 }
217 let pair_rank = |parts: &[(usize, u32)], i: usize| {
218 if i + 3 < parts.len() {
219 rank_of(parts[i].0, parts[i + 3].0)
220 } else {
221 u32::MAX
222 }
223 };
224 if at > 0 {
225 parts[at - 1].1 = pair_rank(&parts, at - 1);
226 }
227 parts[at].1 = pair_rank(&parts, at);
228 parts.remove(at + 1);
229 }
230 parts.windows(2).map(|w| ranks[&piece[w[0].0..w[1].0]]).collect()
231}
232
233fn runs_split(s: &[char], max: usize) -> Vec<&[char]> {
236 let mut out = Vec::new();
237 let mut run = 0;
238 let mut in_space = s.first().is_some_and(|&c| python_space(c));
239 let mut start = 0;
240 for (i, &c) in s.iter().enumerate() {
241 let space = python_space(c);
242 if in_space != space {
243 run = 1;
244 in_space = space;
245 } else {
246 run += 1;
247 if run > max {
248 out.push(&s[start..i]);
249 start = i;
250 run = 1;
251 }
252 }
253 }
254 out.push(&s[start..]);
255 out
256}
257
258fn python_space(c: char) -> bool {
260 matches!(
261 c as u32,
262 0x09..=0x0D | 0x1C..=0x20 | 0x85 | 0xA0 | 0x1680 | 0x2000..=0x200A | 0x2028 | 0x2029 | 0x202F | 0x205F | 0x3000
263 )
264}
265
266fn pattern_of(code: &str) -> Result<(String, u32), String> {
269 let start = code.find("pat_str = \"|\".join(").ok_or("no `pat_str = \"|\".join(` in the tokenizer code")?;
270 let body = &code[start..];
271 let end = body.find("])").ok_or("the pattern's list is not closed")?;
272 let mut alternatives = Vec::new();
273 let mut rest = &body[..end];
274 while let Some(open) = rest.find("r\"\"\"") {
275 let after = &rest[open + 4..];
276 let close = after.find("\"\"\"").ok_or("an unclosed raw string in the pattern")?;
277 alternatives.push(&after[..close]);
278 rest = &after[close + 3..];
279 }
280 if alternatives.is_empty() {
281 return Err("the pattern's list holds no raw strings".into());
282 }
283 let reserved = code
284 .lines()
285 .find_map(|l| l.trim().strip_prefix("num_reserved_special_tokens = "))
286 .ok_or("no `num_reserved_special_tokens` in the tokenizer code")?
287 .trim()
288 .parse()
289 .map_err(|_| "num_reserved_special_tokens is not a count")?;
290 Ok((alternatives.join("|"), reserved))
291}
292
293fn base64(s: &str) -> Option<Vec<u8>> {
295 let value = |c: u8| -> Option<u32> {
296 Some(match c {
297 b'A'..=b'Z' => c - b'A',
298 b'a'..=b'z' => c - b'a' + 26,
299 b'0'..=b'9' => c - b'0' + 52,
300 b'+' => 62,
301 b'/' => 63,
302 _ => return None,
303 } as u32)
304 };
305 let digits: Vec<u8> = s.bytes().filter(|&c| c != b'=').collect();
306 if digits.len() % 4 == 1 {
307 return None;
308 }
309 let mut out = Vec::with_capacity(digits.len() * 3 / 4);
310 for group in digits.chunks(4) {
311 let mut acc = 0u32;
312 for (i, &c) in group.iter().enumerate() {
313 acc |= value(c)? << (18 - 6 * i);
314 }
315 out.extend_from_slice(&acc.to_be_bytes()[1..group.len()]);
316 }
317 Some(out)
318}
319
320fn read_json(path: &Path) -> io::Result<Option<Value>> {
321 match std::fs::read_to_string(path) {
322 Ok(s) => serde_json::from_str(&s).map(Some).map_err(|e| invalid(format!("{}: {e}", path.display()))),
323 Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(None),
324 Err(e) => Err(e),
325 }
326}