Skip to main content

logicaffeine_compile/model/hf/
encode.rs

1//! A Hugging Face `tokenizer.json` byte-pair tokenizer, run as the `tokenizers` library's `encode` runs it: the added
2//! tokens the normalizer leaves alone split out of the raw text, the normalizer over what remains, the normalized added
3//! tokens split out of that, the pre-tokenizer's splits, the byte-pair model over each split, and the post-processor's
4//! special tokens around the whole. Patterns run on [`onig`](super::onig) and the normal forms on the generated Unicode
5//! data, as the library runs them on Oniguruma and the Unicode Character Database.
6//!
7//! A component the port does not reproduce is refused by name when the tokenizer is read, never approximated.
8
9use std::collections::{BinaryHeap, HashMap};
10use std::io;
11use std::path::Path;
12
13use serde_json::Value;
14
15use super::invalid;
16use super::onig::{self, Regex};
17use super::unicode_data::{CANONICAL_DECOMPOSITIONS, COMBINING_CLASSES, PRIMARY_COMPOSITES};
18
19/// What a pattern matches in a text: a literal, a regular expression, or one codepoint by a predicate.
20enum Pattern {
21    Text(Vec<char>),
22    Re(Regex),
23    Char(fn(char) -> bool),
24}
25
26/// Covering segments of a text, `(start, end, is_match)` in codepoint offsets: `find_matches` of `tokenizers`.
27fn segments(pattern: &Pattern, s: &[char]) -> Vec<(usize, usize, bool)> {
28    match pattern {
29        Pattern::Text(t) if t.is_empty() => vec![(0, s.len(), false)],
30        Pattern::Text(t) => {
31            let mut out = Vec::new();
32            let (mut prev, mut i) = (0, 0);
33            while i + t.len() <= s.len() {
34                if s[i..i + t.len()] == t[..] {
35                    if prev != i {
36                        out.push((prev, i, false));
37                    }
38                    out.push((i, i + t.len(), true));
39                    i += t.len();
40                    prev = i;
41                } else {
42                    i += 1;
43                }
44            }
45            if prev != s.len() || s.is_empty() {
46                out.push((prev, s.len(), false));
47            }
48            out
49        }
50        Pattern::Re(re) => {
51            if s.is_empty() {
52                return vec![(0, 0, false)];
53            }
54            let cps: Vec<u32> = s.iter().map(|&c| c as u32).collect();
55            let mut out = Vec::new();
56            let mut prev = 0;
57            for (start, end) in re.find_iter(&cps) {
58                if prev != start {
59                    out.push((prev, start, false));
60                }
61                out.push((start, end, true));
62                prev = end;
63            }
64            if prev != s.len() {
65                out.push((prev, s.len(), false));
66            }
67            out
68        }
69        Pattern::Char(f) => {
70            if s.is_empty() {
71                return vec![(0, 0, false)];
72            }
73            let mut out = Vec::new();
74            let mut last = 0;
75            for (i, &c) in s.iter().enumerate() {
76                if f(c) {
77                    if last < i {
78                        out.push((last, i, false));
79                    }
80                    out.push((i, i + 1, true));
81                    last = i + 1;
82                }
83            }
84            if s.len() > last {
85                out.push((last, s.len(), false));
86            }
87            out
88        }
89    }
90}
91
92/// How a split treats the pieces a pattern matches.
93#[derive(Clone, Copy, Debug, PartialEq, Eq)]
94enum Behavior {
95    Removed,
96    Isolated,
97    MergedWithPrevious,
98    MergedWithNext,
99    Contiguous,
100}
101
102impl Behavior {
103    fn of(v: &Value) -> io::Result<Behavior> {
104        Ok(match v.as_str() {
105            Some("Removed") => Behavior::Removed,
106            Some("Isolated") => Behavior::Isolated,
107            Some("MergedWithPrevious") => Behavior::MergedWithPrevious,
108            Some("MergedWithNext") => Behavior::MergedWithNext,
109            Some("Contiguous") => Behavior::Contiguous,
110            other => return Err(invalid(format!("tokenizer.json: split behavior {other:?}"))),
111        })
112    }
113}
114
115/// `NormalizedString::split`: the pieces of `s` the pattern and behavior leave, empty ones dropped.
116fn split(s: &str, pattern: &Pattern, behavior: Behavior, invert: bool) -> Vec<String> {
117    let chars: Vec<char> = s.chars().collect();
118    let segs: Vec<(usize, usize, bool)> =
119        segments(pattern, &chars).into_iter().map(|(a, b, m)| (a, b, m != invert)).collect();
120    // `(start, end, removed)`.
121    let pieces: Vec<(usize, usize, bool)> = match behavior {
122        Behavior::Isolated => segs.iter().map(|&(a, b, _)| (a, b, false)).collect(),
123        Behavior::Removed => segs.clone(),
124        Behavior::Contiguous => {
125            let mut acc: Vec<(usize, usize, bool)> = Vec::new();
126            let mut previous = false;
127            for &(a, b, m) in &segs {
128                match acc.last_mut() {
129                    Some(last) if m == previous => last.1 = b,
130                    _ => acc.push((a, b, false)),
131                }
132                previous = m;
133            }
134            acc
135        }
136        Behavior::MergedWithPrevious => {
137            let mut acc: Vec<(usize, usize, bool)> = Vec::new();
138            let mut previous = false;
139            for &(a, b, m) in &segs {
140                match acc.last_mut() {
141                    Some(last) if m && !previous => last.1 = b,
142                    _ => acc.push((a, b, false)),
143                }
144                previous = m;
145            }
146            acc
147        }
148        Behavior::MergedWithNext => {
149            let mut acc: Vec<(usize, usize, bool)> = Vec::new();
150            let mut previous = false;
151            for &(a, b, m) in segs.iter().rev() {
152                match acc.last_mut() {
153                    Some(last) if m && !previous => last.0 = a,
154                    _ => acc.push((a, b, false)),
155                }
156                previous = m;
157            }
158            acc.reverse();
159            acc
160        }
161    };
162    pieces
163        .into_iter()
164        .filter(|&(a, b, removed)| !removed && a != b)
165        .map(|(a, b, _)| chars[a..b].iter().collect())
166        .collect()
167}
168
169/// `replace` of a normalized string: every match of the pattern replaced by `content`.
170fn replace(s: &str, pattern: &Pattern, content: &str) -> String {
171    let chars: Vec<char> = s.chars().collect();
172    let mut out = String::new();
173    for (a, b, m) in segments(pattern, &chars) {
174        if m {
175            out.push_str(content);
176        } else {
177            out.extend(&chars[a..b]);
178        }
179    }
180    out
181}
182
183fn pattern_of(v: &Value) -> io::Result<Pattern> {
184    if let Some(s) = v.get("String").and_then(Value::as_str) {
185        return Ok(Pattern::Text(s.chars().collect()));
186    }
187    if let Some(r) = v.get("Regex").and_then(Value::as_str) {
188        return Ok(Pattern::Re(Regex::new(r).map_err(invalid)?));
189    }
190    Err(invalid(format!("tokenizer.json: a pattern {v}")))
191}
192
193fn ccc(c: u32) -> u8 {
194    let at = COMBINING_CLASSES.partition_point(|&(first, _, _)| first <= c);
195    match at.checked_sub(1).map(|i| COMBINING_CLASSES[i]) {
196        Some((first, last, class)) if (first..=last).contains(&c) => class,
197        _ => 0,
198    }
199}
200
201const S_BASE: u32 = 0xAC00;
202const L_BASE: u32 = 0x1100;
203const V_BASE: u32 = 0x1161;
204const T_BASE: u32 = 0x11A7;
205const L_COUNT: u32 = 19;
206const V_COUNT: u32 = 21;
207const T_COUNT: u32 = 28;
208const N_COUNT: u32 = V_COUNT * T_COUNT;
209const S_COUNT: u32 = L_COUNT * N_COUNT;
210
211fn decompose_into(c: u32, out: &mut Vec<u32>) {
212    if (S_BASE..S_BASE + S_COUNT).contains(&c) {
213        let s = c - S_BASE;
214        out.push(L_BASE + s / N_COUNT);
215        out.push(V_BASE + (s % N_COUNT) / T_COUNT);
216        if s % T_COUNT != 0 {
217            out.push(T_BASE + s % T_COUNT);
218        }
219        return;
220    }
221    match CANONICAL_DECOMPOSITIONS.binary_search_by_key(&c, |&(cp, _, _)| cp) {
222        Ok(i) => {
223            let (_, a, b) = CANONICAL_DECOMPOSITIONS[i];
224            decompose_into(a, out);
225            if b != 0 {
226                decompose_into(b, out);
227            }
228        }
229        Err(_) => out.push(c),
230    }
231}
232
233/// NFD: the full canonical decomposition, combining marks put in canonical order.
234fn nfd(s: &str) -> Vec<u32> {
235    let mut out = Vec::with_capacity(s.len());
236    for c in s.chars() {
237        decompose_into(c as u32, &mut out);
238    }
239    let mut i = 0;
240    while i < out.len() {
241        if ccc(out[i]) == 0 {
242            i += 1;
243            continue;
244        }
245        let start = i;
246        while i < out.len() && ccc(out[i]) != 0 {
247            i += 1;
248        }
249        out[start..i].sort_by_key(|&c| ccc(c));
250    }
251    out
252}
253
254fn compose_pair(a: u32, b: u32) -> Option<u32> {
255    if (L_BASE..L_BASE + L_COUNT).contains(&a) && (V_BASE..V_BASE + V_COUNT).contains(&b) {
256        return Some(S_BASE + ((a - L_BASE) * V_COUNT + (b - V_BASE)) * T_COUNT);
257    }
258    if (S_BASE..S_BASE + S_COUNT).contains(&a) && (a - S_BASE) % T_COUNT == 0 && (T_BASE + 1..T_BASE + T_COUNT).contains(&b) {
259        return Some(a + (b - T_BASE));
260    }
261    PRIMARY_COMPOSITES.binary_search_by_key(&(a, b), |&(x, y, _)| (x, y)).ok().map(|i| PRIMARY_COMPOSITES[i].2)
262}
263
264/// NFC: the canonical decomposition, recomposed by the canonical composition algorithm.
265pub fn nfc(s: &str) -> String {
266    let d = nfd(s);
267    let mut out: Vec<u32> = Vec::with_capacity(d.len());
268    let mut starter: Option<usize> = None;
269    let mut last_class: i32 = -1;
270    for c in d {
271        let class = ccc(c) as i32;
272        if let Some(st) = starter {
273            let blocked = last_class != -1 && (last_class == 0 || last_class >= class);
274            if !blocked {
275                if let Some(comp) = compose_pair(out[st], c) {
276                    out[st] = comp;
277                    continue;
278                }
279            }
280        }
281        if class == 0 {
282            starter = Some(out.len());
283            last_class = -1;
284        } else {
285            last_class = class;
286        }
287        out.push(c);
288    }
289    out.into_iter().filter_map(char::from_u32).collect()
290}
291
292/// A normalizer of `tokenizer.json`.
293enum Normalizer {
294    Nfc,
295    Nfd,
296    Lowercase,
297    Replace(Pattern, String),
298    Prepend(String),
299    Strip(bool, bool),
300    StripAccents,
301    Sequence(Vec<Normalizer>),
302}
303
304impl Normalizer {
305    fn of(v: &Value) -> io::Result<Option<Normalizer>> {
306        if v.is_null() {
307            return Ok(None);
308        }
309        Ok(Some(match v["type"].as_str() {
310            Some("NFC") => Normalizer::Nfc,
311            Some("NFD") => Normalizer::Nfd,
312            Some("Lowercase") => Normalizer::Lowercase,
313            Some("Replace") => {
314                Normalizer::Replace(pattern_of(&v["pattern"])?, v["content"].as_str().unwrap_or_default().to_string())
315            }
316            Some("Prepend") => Normalizer::Prepend(v["prepend"].as_str().unwrap_or_default().to_string()),
317            Some("Strip") => Normalizer::Strip(
318                v["strip_left"].as_bool().unwrap_or(true),
319                v["strip_right"].as_bool().unwrap_or(true),
320            ),
321            Some("StripAccents") => Normalizer::StripAccents,
322            Some("Sequence") => Normalizer::Sequence(
323                v["normalizers"]
324                    .as_array()
325                    .into_iter()
326                    .flatten()
327                    .map(|n| Normalizer::of(n).map(|o| o.unwrap_or(Normalizer::Sequence(Vec::new()))))
328                    .collect::<io::Result<_>>()?,
329            ),
330            other => return Err(invalid(format!("tokenizer.json: the {other:?} normalizer, which this port does not run"))),
331        }))
332    }
333
334    fn run(&self, s: String) -> String {
335        match self {
336            Normalizer::Nfc => nfc(&s),
337            Normalizer::Nfd => nfd(&s).into_iter().filter_map(char::from_u32).collect(),
338            Normalizer::Lowercase => s.chars().flat_map(char::to_lowercase).collect(),
339            Normalizer::Replace(p, content) => replace(&s, p, content),
340            Normalizer::Prepend(p) if !s.is_empty() => format!("{p}{s}"),
341            Normalizer::Prepend(_) => s,
342            Normalizer::Strip(left, right) => {
343                let t = if *left { s.trim_start() } else { &s };
344                (if *right { t.trim_end() } else { t }).to_string()
345            }
346            Normalizer::StripAccents => s.chars().filter(|&c| !matches!(onig::category_name(c as u32), "Mn" | "Mc" | "Me")).collect(),
347            Normalizer::Sequence(v) => v.iter().fold(s, |s, n| n.run(s)),
348        }
349    }
350}
351
352/// `Metaspace`'s scheme for the replacement it puts first.
353#[derive(Clone, Copy, PartialEq, Eq)]
354enum PrependScheme {
355    Always,
356    First,
357    Never,
358}
359
360/// A pre-tokenizer of `tokenizer.json`.
361enum PreTokenizer {
362    Sequence(Vec<PreTokenizer>),
363    Split { pattern: Pattern, behavior: Behavior, invert: bool },
364    ByteLevel { add_prefix_space: bool, use_regex: bool },
365    Digits(bool),
366    Whitespace,
367    WhitespaceSplit,
368    Punctuation(Behavior),
369    Metaspace { replacement: char, scheme: PrependScheme, split: bool },
370}
371
372/// The pattern `tokenizers`' byte-level pre-tokenizer splits by when `use_regex` is set (GPT-2's).
373const BYTE_LEVEL_PATTERN: &str = r"'s|'t|'re|'ve|'m|'ll|'d| ?\p{L}+| ?\p{N}+| ?[^\s\p{L}\p{N}]+|\s+(?!\S)|\s+";
374
375/// The pattern `Whitespace` keeps.
376const WHITESPACE_PATTERN: &str = r"\w+|[^\w\s]+";
377
378/// A split of the text: its content and whether it begins at the original text's first codepoint.
379struct Piece {
380    text: String,
381    at_origin: bool,
382}
383
384impl PreTokenizer {
385    fn of(v: &Value) -> io::Result<Option<PreTokenizer>> {
386        if v.is_null() {
387            return Ok(None);
388        }
389        Ok(Some(match v["type"].as_str() {
390            Some("Sequence") => PreTokenizer::Sequence(
391                v["pretokenizers"]
392                    .as_array()
393                    .into_iter()
394                    .flatten()
395                    .filter_map(|p| PreTokenizer::of(p).transpose())
396                    .collect::<io::Result<_>>()?,
397            ),
398            Some("Split") => PreTokenizer::Split {
399                pattern: pattern_of(&v["pattern"])?,
400                behavior: Behavior::of(&v["behavior"])?,
401                invert: v["invert"].as_bool().unwrap_or(false),
402            },
403            Some("ByteLevel") => PreTokenizer::ByteLevel {
404                add_prefix_space: v["add_prefix_space"].as_bool().unwrap_or(true),
405                use_regex: v["use_regex"].as_bool().unwrap_or(true),
406            },
407            Some("Digits") => PreTokenizer::Digits(v["individual_digits"].as_bool().unwrap_or(false)),
408            Some("Whitespace") => PreTokenizer::Whitespace,
409            Some("WhitespaceSplit") => PreTokenizer::WhitespaceSplit,
410            Some("Punctuation") => {
411                PreTokenizer::Punctuation(if v["behavior"].is_null() { Behavior::Isolated } else { Behavior::of(&v["behavior"])? })
412            }
413            Some("Metaspace") => PreTokenizer::Metaspace {
414                replacement: v["replacement"].as_str().and_then(|s| s.chars().next()).unwrap_or('\u{2581}'),
415                scheme: match v["prepend_scheme"].as_str() {
416                    Some("always") | None => PrependScheme::Always,
417                    Some("first") => PrependScheme::First,
418                    Some("never") => PrependScheme::Never,
419                    Some(other) => return Err(invalid(format!("tokenizer.json: Metaspace prepend scheme `{other}`"))),
420                },
421                split: v["split"].as_bool().unwrap_or(true),
422            },
423            other => return Err(invalid(format!("tokenizer.json: the {other:?} pre-tokenizer, which this port does not run"))),
424        }))
425    }
426
427    /// Each piece split further, as `PreTokenizedString::split` does: empty pieces dropped, the first sub-piece of a
428    /// piece keeping its place at the origin.
429    fn split_each(pieces: Vec<Piece>, f: impl Fn(&Piece) -> Vec<String>) -> Vec<Piece> {
430        let mut out = Vec::new();
431        for p in pieces {
432            for (i, text) in f(&p).into_iter().enumerate() {
433                if !text.is_empty() {
434                    out.push(Piece { text, at_origin: p.at_origin && i == 0 });
435                }
436            }
437        }
438        out
439    }
440
441    fn run(&self, pieces: Vec<Piece>) -> Vec<Piece> {
442        match self {
443            PreTokenizer::Sequence(v) => v.iter().fold(pieces, |p, t| t.run(p)),
444            PreTokenizer::Split { pattern, behavior, invert } => {
445                Self::split_each(pieces, |p| split(&p.text, pattern, *behavior, *invert))
446            }
447            PreTokenizer::ByteLevel { add_prefix_space, use_regex } => {
448                let re = use_regex.then(|| Pattern::Re(Regex::new(BYTE_LEVEL_PATTERN).expect("the byte-level pattern compiles")));
449                let split = Self::split_each(pieces, |p| {
450                    let text = if *add_prefix_space && !p.text.starts_with(' ') { format!(" {}", p.text) } else { p.text.clone() };
451                    match &re {
452                        Some(re) => split(&text, re, Behavior::Isolated, false),
453                        None => vec![text],
454                    }
455                });
456                split.into_iter().map(|p| Piece { text: byte_level(&p.text), at_origin: p.at_origin }).collect()
457            }
458            PreTokenizer::Digits(individual) => {
459                let b = if *individual { Behavior::Isolated } else { Behavior::Contiguous };
460                Self::split_each(pieces, |p| split(&p.text, &Pattern::Char(char::is_numeric), b, false))
461            }
462            PreTokenizer::Whitespace => {
463                let re = Pattern::Re(Regex::new(WHITESPACE_PATTERN).expect("the whitespace pattern compiles"));
464                Self::split_each(pieces, |p| split(&p.text, &re, Behavior::Removed, true))
465            }
466            PreTokenizer::WhitespaceSplit => {
467                Self::split_each(pieces, |p| split(&p.text, &Pattern::Char(char::is_whitespace), Behavior::Removed, false))
468            }
469            PreTokenizer::Punctuation(b) => Self::split_each(pieces, |p| {
470                split(&p.text, &Pattern::Char(|c| c.is_ascii_punctuation() || onig::category_name(c as u32).starts_with('P')), *b, false)
471            }),
472            PreTokenizer::Metaspace { replacement, scheme, split: do_split } => {
473                let rep = replacement.to_string();
474                Self::split_each(pieces, |p| {
475                    let mut text = p.text.replace(' ', &rep);
476                    let prepend = match scheme {
477                        PrependScheme::Always => !text.starts_with(*replacement),
478                        PrependScheme::First => !text.starts_with(*replacement) && p.at_origin,
479                        PrependScheme::Never => false,
480                    };
481                    if prepend {
482                        text = format!("{rep}{text}");
483                    }
484                    if *do_split {
485                        split(&text, &Pattern::Text(vec![*replacement]), Behavior::MergedWithNext, false)
486                    } else {
487                        vec![text]
488                    }
489                })
490            }
491        }
492    }
493}
494
495/// GPT-2's byte alphabet: each byte of `s` written as the codepoint that stands for it.
496fn byte_level(s: &str) -> String {
497    s.bytes().map(byte_char).collect()
498}
499
500pub(super) fn byte_char(b: u8) -> char {
501    let printable = |b: u8| matches!(b, 33..=126 | 161..=172 | 174..=255);
502    if printable(b) {
503        return b as char;
504    }
505    let n = (0..b).filter(|&x| !printable(x)).count() as u32;
506    char::from_u32(256 + n).unwrap()
507}
508
509/// The byte-pair model of `tokenizer.json`.
510struct Bpe {
511    vocab: HashMap<String, u32>,
512    /// `(left, right) -> (rank, merged)`.
513    merges: HashMap<(u32, u32), (u32, u32)>,
514    unk: Option<u32>,
515    fuse_unk: bool,
516    byte_fallback: bool,
517    prefix: Option<String>,
518    suffix: Option<String>,
519    ignore_merges: bool,
520}
521
522#[derive(Clone, Copy)]
523struct Symbol {
524    id: u32,
525    prev: isize,
526    next: isize,
527    len: usize,
528}
529
530#[derive(PartialEq, Eq)]
531struct Merge {
532    pos: usize,
533    rank: u32,
534    new_id: u32,
535}
536
537impl Ord for Merge {
538    fn cmp(&self, o: &Self) -> std::cmp::Ordering {
539        o.rank.cmp(&self.rank).then(o.pos.cmp(&self.pos))
540    }
541}
542
543impl PartialOrd for Merge {
544    fn partial_cmp(&self, o: &Self) -> Option<std::cmp::Ordering> {
545        Some(self.cmp(o))
546    }
547}
548
549impl Bpe {
550    fn of(m: &Value) -> io::Result<Bpe> {
551        if m["type"].as_str() != Some("BPE") {
552            return Err(invalid(format!("tokenizer.json: a `{}` model, which this port does not run", m["type"])));
553        }
554        if m["dropout"].as_f64().is_some_and(|d| d > 0.0) {
555            return Err(invalid("tokenizer.json: a byte-pair model with dropout, whose output is random"));
556        }
557        let mut vocab = HashMap::new();
558        for (t, id) in m["vocab"].as_object().into_iter().flatten() {
559            vocab.insert(t.clone(), id.as_u64().ok_or_else(|| invalid(format!("tokenizer.json: `{t}` has no id")))? as u32);
560        }
561        let prefix = m["continuing_subword_prefix"].as_str().map(str::to_string);
562        let prefix_len = prefix.as_ref().map_or(0, String::len);
563        let mut merges = HashMap::new();
564        for (rank, entry) in m["merges"].as_array().into_iter().flatten().enumerate() {
565            let (a, b) = match entry {
566                Value::String(s) => {
567                    let parts: Vec<&str> = s.split(' ').collect();
568                    if parts.len() != 2 {
569                        return Err(invalid(format!("tokenizer.json: merge `{s}` is not two parts")));
570                    }
571                    (parts[0].to_string(), parts[1].to_string())
572                }
573                Value::Array(p) if p.len() == 2 => (
574                    p[0].as_str().unwrap_or_default().to_string(),
575                    p[1].as_str().unwrap_or_default().to_string(),
576                ),
577                other => return Err(invalid(format!("tokenizer.json: merge {other}"))),
578            };
579            let id = |t: &str| vocab.get(t).copied().ok_or_else(|| invalid(format!("tokenizer.json: merge token `{t}` is not in the vocabulary")));
580            let merged = format!("{a}{}", b.get(prefix_len..).unwrap_or_default());
581            merges.insert((id(&a)?, id(&b)?), (rank as u32, id(&merged)?));
582        }
583        let unk = match m["unk_token"].as_str() {
584            Some(u) => Some(*vocab.get(u).ok_or_else(|| invalid(format!("tokenizer.json: unknown token `{u}` is not in the vocabulary")))?),
585            None => None,
586        };
587        Ok(Bpe {
588            vocab,
589            merges,
590            unk,
591            fuse_unk: m["fuse_unk"].as_bool().unwrap_or(false),
592            byte_fallback: m["byte_fallback"].as_bool().unwrap_or(false),
593            prefix,
594            suffix: m["end_of_word_suffix"].as_str().map(str::to_string),
595            ignore_merges: m["ignore_merges"].as_bool().unwrap_or(false),
596        })
597    }
598
599    /// The ids of one pre-tokenized split (`BPE::tokenize`).
600    fn tokenize(&self, w: &str, out: &mut Vec<u32>) {
601        if w.is_empty() {
602            return;
603        }
604        if self.ignore_merges {
605            if let Some(&id) = self.vocab.get(w) {
606                out.push(id);
607                return;
608            }
609        }
610        let mut syms: Vec<Symbol> = Vec::new();
611        let add = |syms: &mut Vec<Symbol>, id: u32, len: usize| {
612            let n = syms.len() as isize;
613            if let Some(last) = syms.last_mut() {
614                last.next = n;
615            }
616            syms.push(Symbol { id, prev: n - 1, next: -1, len });
617        };
618        let mut unk: Option<(u32, usize)> = None;
619        let chars: Vec<(usize, char)> = w.char_indices().collect();
620        for (k, &(_, c)) in chars.iter().enumerate() {
621            let (first, last) = (k == 0, k + 1 == chars.len());
622            let byte_len = c.len_utf8();
623            let mut s = c.to_string();
624            if !first {
625                if let Some(p) = &self.prefix {
626                    s = format!("{p}{s}");
627                }
628            }
629            if last {
630                if let Some(x) = &self.suffix {
631                    s = format!("{s}{x}");
632                }
633            }
634            if let Some(&id) = self.vocab.get(&s) {
635                if let Some((u, l)) = unk.take() {
636                    add(&mut syms, u, l);
637                }
638                add(&mut syms, id, byte_len);
639                continue;
640            }
641            if self.byte_fallback {
642                let ids: Option<Vec<u32>> = s.bytes().map(|b| self.vocab.get(&format!("<0x{b:02X}>")).copied()).collect();
643                if let Some(ids) = ids {
644                    for id in ids {
645                        add(&mut syms, id, 1);
646                    }
647                    continue;
648                }
649            }
650            if let Some(u) = self.unk {
651                unk = match (unk, self.fuse_unk) {
652                    (Some((id, l)), true) => Some((id, l + byte_len)),
653                    (Some((id, l)), false) => {
654                        add(&mut syms, id, l);
655                        Some((u, byte_len))
656                    }
657                    (None, _) => Some((u, byte_len)),
658                };
659            }
660        }
661        if let Some((u, l)) = unk {
662            add(&mut syms, u, l);
663        }
664        let mut queue: BinaryHeap<Merge> = syms
665            .windows(2)
666            .enumerate()
667            .filter_map(|(pos, w)| self.merges.get(&(w[0].id, w[1].id)).map(|&(rank, new_id)| Merge { pos, rank, new_id }))
668            .collect();
669        while let Some(top) = queue.pop() {
670            if syms[top.pos].len == 0 || syms[top.pos].next == -1 {
671                continue;
672            }
673            let next = syms[top.pos].next as usize;
674            let right = syms[next];
675            if self.merges.get(&(syms[top.pos].id, right.id)).is_none_or(|&(_, id)| id != top.new_id) {
676                continue;
677            }
678            syms[top.pos].id = top.new_id;
679            syms[top.pos].len += right.len;
680            syms[top.pos].next = right.next;
681            syms[next].len = 0;
682            if right.next > -1 && (right.next as usize) < syms.len() {
683                syms[right.next as usize].prev = top.pos as isize;
684            }
685            let cur = syms[top.pos];
686            if cur.prev >= 0 {
687                let prev = cur.prev as usize;
688                if let Some(&(rank, new_id)) = self.merges.get(&(syms[prev].id, cur.id)) {
689                    queue.push(Merge { pos: prev, rank, new_id });
690                }
691            }
692            if cur.next >= 0 && (cur.next as usize) < syms.len() {
693                if let Some(&(rank, new_id)) = self.merges.get(&(cur.id, syms[cur.next as usize].id)) {
694                    queue.push(Merge { pos: top.pos, rank, new_id });
695                }
696            }
697        }
698        out.extend(syms.iter().filter(|s| s.len != 0).map(|s| s.id));
699    }
700}
701
702/// An added token: its id, its text (normalized, for a token matched on the normalized text) and its flags.
703struct AddedToken {
704    id: u32,
705    content: Vec<char>,
706    single_word: bool,
707    lstrip: bool,
708    rstrip: bool,
709}
710
711/// One pass of the added-token split: the tokens matched leftmost-longest.
712struct AddedSet {
713    tokens: Vec<AddedToken>,
714    /// Token indices by first codepoint, longest first.
715    by_first: HashMap<char, Vec<usize>>,
716}
717
718impl AddedSet {
719    fn new(tokens: Vec<AddedToken>) -> AddedSet {
720        let mut by_first: HashMap<char, Vec<usize>> = HashMap::new();
721        for (i, t) in tokens.iter().enumerate() {
722            if let Some(&c) = t.content.first() {
723                by_first.entry(c).or_default().push(i);
724            }
725        }
726        for v in by_first.values_mut() {
727            v.sort_by(|&a, &b| tokens[b].content.len().cmp(&tokens[a].content.len()).then(a.cmp(&b)));
728        }
729        AddedSet { tokens, by_first }
730    }
731
732    /// `find_matches` of the added vocabulary: `(token, start, end)` and gaps (`None`), over `s`.
733    fn split(&self, s: &[char]) -> Vec<(Option<u32>, usize, usize)> {
734        if s.is_empty() {
735            return vec![(None, 0, 0)];
736        }
737        let word = |c: char| c.is_alphanumeric() || c == '_';
738        let mut out = Vec::new();
739        let mut start_offset = 0;
740        let mut i = 0;
741        while i < s.len() {
742            let hit = self.by_first.get(&s[i]).and_then(|cands| {
743                cands.iter().copied().find(|&t| s[i..].starts_with(&self.tokens[t].content))
744            });
745            let Some(t) = hit else {
746                i += 1;
747                continue;
748            };
749            let tok = &self.tokens[t];
750            let (mut start, mut stop) = (i, i + tok.content.len());
751            i = stop;
752            if tok.single_word {
753                let start_space = start == 0 || !word(s[start - 1]);
754                let stop_space = stop == s.len() || !word(s[stop]);
755                if !start_space || !stop_space {
756                    continue;
757                }
758            }
759            if tok.lstrip {
760                let mut l = start;
761                while l > 0 && s[l - 1].is_whitespace() {
762                    l -= 1;
763                }
764                start = l.max(start_offset);
765            }
766            if tok.rstrip {
767                while stop < s.len() && s[stop].is_whitespace() {
768                    stop += 1;
769                }
770            }
771            if start_offset < start {
772                out.push((None, start_offset, start));
773            }
774            out.push((Some(tok.id), start, stop));
775            start_offset = stop;
776        }
777        if start_offset != s.len() {
778            out.push((None, start_offset, s.len()));
779        }
780        out
781    }
782}
783
784/// The special tokens a post-processor puts around a single sequence.
785fn post_processor_wrap(pp: &Value, before: &mut Vec<u32>, after: &mut Vec<u32>) -> io::Result<()> {
786    let id_of = |v: &Value, what: &str| -> io::Result<u32> {
787        v.as_u64().map(|x| x as u32).ok_or_else(|| invalid(format!("tokenizer.json: the post-processor's {what} has no id")))
788    };
789    match pp["type"].as_str() {
790        None if pp.is_null() => {}
791        Some("Sequence") => {
792            for p in pp["processors"].as_array().into_iter().flatten() {
793                post_processor_wrap(p, before, after)?;
794            }
795        }
796        Some("ByteLevel") => {}
797        Some("TemplateProcessing") => {
798            let single = pp["single"].as_array().cloned().unwrap_or_default();
799            let at = single.iter().position(|i| i.get("Sequence").is_some()).unwrap_or(single.len());
800            let ids = |items: &[Value]| -> io::Result<Vec<u32>> {
801                let mut out = Vec::new();
802                for i in items {
803                    if let Some(name) = i["SpecialToken"]["id"].as_str() {
804                        for id in pp["special_tokens"][name]["ids"].as_array().into_iter().flatten() {
805                            out.push(id_of(id, name)?);
806                        }
807                    }
808                }
809                Ok(out)
810            };
811            let mut head = ids(&single[..at])?;
812            head.append(before);
813            *before = head;
814            after.extend(ids(&single[(at + 1).min(single.len())..])?);
815        }
816        Some("RobertaProcessing" | "BertProcessing") => {
817            before.insert(0, id_of(&pp["cls"][1], "cls")?);
818            after.push(id_of(&pp["sep"][1], "sep")?);
819        }
820        Some(other) => return Err(invalid(format!("tokenizer.json: the `{other}` post-processor, which this port does not run"))),
821        None => return Err(invalid("tokenizer.json: a post-processor without a type")),
822    }
823    Ok(())
824}
825
826/// A `tokenizer.json` pre-tokenizer alone, as `tokenizers`' `pre_tokenize_str` runs one.
827pub struct HfPreTokenizer(PreTokenizer);
828
829impl HfPreTokenizer {
830    pub fn from_json(v: &Value) -> io::Result<HfPreTokenizer> {
831        PreTokenizer::of(v)?.map(HfPreTokenizer).ok_or_else(|| invalid("tokenizer.json: no pre-tokenizer"))
832    }
833
834    /// The pieces the pre-tokenizer splits `text` into.
835    pub fn pre_tokenize_str(&self, text: &str) -> Vec<String> {
836        self.0.run(vec![Piece { text: text.to_string(), at_origin: true }]).into_iter().map(|p| p.text).collect()
837    }
838}
839
840/// A `tokenizer.json` byte-pair tokenizer.
841pub struct HfTokenizer {
842    raw: AddedSet,
843    normalized: AddedSet,
844    normalizer: Option<Normalizer>,
845    pre: Option<PreTokenizer>,
846    model: Bpe,
847    before: Vec<u32>,
848    after: Vec<u32>,
849}
850
851impl HfTokenizer {
852    pub fn from_file(path: &Path) -> io::Result<HfTokenizer> {
853        let text = std::fs::read_to_string(path).map_err(|e| io::Error::new(e.kind(), format!("{}: {e}", path.display())))?;
854        let json: Value = serde_json::from_str(&text).map_err(|e| invalid(format!("{}: {e}", path.display())))?;
855        HfTokenizer::from_json(&json).map_err(|e| invalid(format!("{}: {e}", path.display())))
856    }
857
858    pub fn from_json(tj: &Value) -> io::Result<HfTokenizer> {
859        let normalizer = Normalizer::of(&tj["normalizer"])?;
860        let mut raw = Vec::new();
861        let mut normalized = Vec::new();
862        for a in tj["added_tokens"].as_array().into_iter().flatten() {
863            let content = a["content"].as_str().unwrap_or_default().to_string();
864            let id = a["id"].as_u64().ok_or_else(|| invalid(format!("tokenizer.json: added token `{content}` has no id")))? as u32;
865            let flag = |k: &str| a[k].as_bool().unwrap_or(false);
866            let is_normalized = flag("normalized");
867            let text = match (&normalizer, is_normalized) {
868                (Some(n), true) => n.run(content),
869                _ => content,
870            };
871            let t = AddedToken { id, content: text.chars().collect(), single_word: flag("single_word"), lstrip: flag("lstrip"), rstrip: flag("rstrip") };
872            if is_normalized {
873                normalized.push(t);
874            } else {
875                raw.push(t);
876            }
877        }
878        let (mut before, mut after) = (Vec::new(), Vec::new());
879        post_processor_wrap(&tj["post_processor"], &mut before, &mut after)?;
880        Ok(HfTokenizer {
881            raw: AddedSet::new(raw),
882            normalized: AddedSet::new(normalized),
883            normalizer,
884            pre: PreTokenizer::of(&tj["pre_tokenizer"])?,
885            model: Bpe::of(&tj["model"])?,
886            before,
887            after,
888        })
889    }
890
891    /// The ids of `text` (`Tokenizer::encode`), with the post-processor's special tokens when `add_special_tokens`.
892    pub fn encode(&self, text: &str, add_special_tokens: bool) -> Vec<u32> {
893        // A split is an added token's id, or text with whether it begins at the original text's first codepoint.
894        enum Part {
895            Token(u32),
896            Text(String, bool),
897        }
898        let chars: Vec<char> = text.chars().collect();
899        let mut parts: Vec<Part> = self
900            .raw
901            .split(&chars)
902            .into_iter()
903            .filter(|&(id, a, b)| id.is_some() || a != b)
904            .map(|(id, a, b)| match id {
905                Some(id) => Part::Token(id),
906                None => Part::Text(chars[a..b].iter().collect(), a == 0),
907            })
908            .collect();
909        let mut second = Vec::new();
910        for p in parts.drain(..) {
911            match p {
912                Part::Token(id) => second.push(Part::Token(id)),
913                Part::Text(s, at_origin) => {
914                    let s = match &self.normalizer {
915                        Some(n) => n.run(s),
916                        None => s,
917                    };
918                    let cs: Vec<char> = s.chars().collect();
919                    for (id, a, b) in self.normalized.split(&cs) {
920                        match id {
921                            Some(id) => second.push(Part::Token(id)),
922                            None if a != b => second.push(Part::Text(cs[a..b].iter().collect(), at_origin && a == 0)),
923                            None => {}
924                        }
925                    }
926                }
927            }
928        }
929        let mut ids = Vec::new();
930        if add_special_tokens {
931            ids.extend(&self.before);
932        }
933        for p in second {
934            match p {
935                Part::Token(id) => ids.push(id),
936                Part::Text(text, at_origin) => {
937                    let pieces = vec![Piece { text, at_origin }];
938                    let pieces = match &self.pre {
939                        Some(pre) => pre.run(pieces),
940                        None => pieces,
941                    };
942                    for piece in pieces {
943                        self.model.tokenize(&piece.text, &mut ids);
944                    }
945                }
946            }
947        }
948        if add_special_tokens {
949            ids.extend(&self.after);
950        }
951        ids
952    }
953}