use crate::error::{Result, bail};
use crate::gguf::Gguf;
pub struct Tokenizer {
bytes: Vec<u8>,
ends: Vec<usize>,
}
impl Tokenizer {
pub fn new(g: &Gguf) -> Result<Self> {
g.check("tokenizer.ggml.model", &["gpt2"])?;
let mut byte = [None; 512];
let mut next = 256;
for b in 0..256usize {
let printable = matches!(b, 33..=126 | 161..=172 | 174..=255);
let c = if printable { b } else { (next, next += 1).0 };
byte[c] = Some(b as u8);
}
let (mut bytes, mut ends) = (Vec::new(), Vec::new());
for piece in g.array::<&str>("tokenizer.ggml.tokens")? {
for c in piece.chars() {
match byte.get(c as usize).copied().flatten() {
Some(b) => bytes.push(b),
None => bytes.extend_from_slice(c.encode_utf8(&mut [0; 4]).as_bytes()),
}
}
ends.push(bytes.len());
}
if ends.is_empty() {
bail!("empty vocabulary");
}
Ok(Self { bytes, ends })
}
pub fn len(&self) -> usize {
self.ends.len()
}
pub fn bytes(&self, id: usize) -> &[u8] {
&self.bytes[if id == 0 { 0 } else { self.ends[id - 1] }..self.ends[id]]
}
pub fn find(&self, s: &str) -> Option<usize> {
(0..self.len()).rev().find(|&i| self.bytes(i) == s.as_bytes())
}
}
pub fn pieces<'a>(bytes: impl Fn(usize) -> &'a [u8], ids: &[usize]) -> Vec<String> {
let mut pending = Vec::new();
let mut out: Vec<String> = Vec::with_capacity(ids.len());
for &id in ids {
pending.extend_from_slice(bytes(id));
let done = match std::str::from_utf8(&pending) {
Ok(_) => pending.len(),
Err(e) if e.error_len().is_none() => e.valid_up_to(),
Err(_) => pending.len(),
};
out.push(String::from_utf8_lossy(&pending[..done]).into_owned());
pending.drain(..done);
}
if let Some(last) = out.last_mut() {
*last += &String::from_utf8_lossy(&pending);
}
out
}
pub fn trim(s: &str) -> &str {
s.trim_matches([' ', '\t', '\n', '\r'])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn split_characters_go_to_the_token_that_completes_them() {
let raw: [&[u8]; 4] = [b"\xe6\x97", b"\xa5", b" a", b"\xff"];
let mut t = Tokenizer { bytes: Vec::new(), ends: Vec::new() };
for r in raw {
t.bytes.extend_from_slice(r);
t.ends.push(t.bytes.len());
}
let bytes = |id| t.bytes(id);
assert_eq!(pieces(bytes, &[0, 1, 2]), ["", "日", " a"]);
assert_eq!(pieces(bytes, &[2, 0]), [" a", "\u{fffd}"]);
assert_eq!(pieces(bytes, &[3, 2]), ["\u{fffd}", " a"]);
assert_eq!(t.find(" a"), Some(2));
assert_eq!(trim("\t x \n"), "x");
}
}