use std::collections::HashMap;
use serde::Deserialize;
const METASPACE: char = '\u{2581}';
#[derive(Deserialize)]
struct File {
model: Model,
}
#[derive(Deserialize)]
struct Model {
vocab: HashMap<String, usize>,
merges: Vec<String>,
}
pub struct Tokenizer {
ids: HashMap<String, usize>,
tokens: Vec<String>,
ranks: HashMap<(String, String), usize>,
}
impl Tokenizer {
pub fn new(text: &str) -> Self {
let file: File = serde_json::from_str(text).expect("the tokenizer file parses");
let ids = file.model.vocab;
let mut tokens = vec![String::new(); ids.len()];
for (token, &id) in &ids {
tokens[id] = token.clone();
}
let mut ranks = HashMap::new();
for (rank, line) in file.model.merges.iter().enumerate() {
let (left, right) = line.split_once(' ').expect("merges pair per entry");
ranks.insert((left.to_string(), right.to_string()), rank);
}
Self { ids, tokens, ranks }
}
pub fn encode(&self, text: &str) -> Vec<usize> {
let normalized = format!("{METASPACE}{text}").replace(' ', "\u{2581}");
let symbols: Vec<String> = normalized
.chars()
.map(|character| character.to_string())
.collect();
let mut encoded = Vec::new();
for symbol in self.merged(symbols) {
match self.ids.get(&symbol) {
Some(&id) => encoded.push(id),
None => {
for byte in symbol.bytes() {
let name = format!("<0x{byte:02X}>");
encoded.push(
*self
.ids
.get(&name)
.expect("the byte tokens cover every byte"),
);
}
}
}
}
encoded
}
pub fn decode(&self, ids: &[usize]) -> String {
let mut bytes = Vec::new();
for &id in ids {
self.emit(id, &mut bytes);
}
let text = String::from_utf8_lossy(&bytes);
text.strip_prefix(' ').unwrap_or(&text).to_string()
}
pub fn piece(&self, id: usize) -> String {
let mut bytes = Vec::new();
self.emit(id, &mut bytes);
String::from_utf8_lossy(&bytes).into_owned()
}
fn emit(&self, id: usize, bytes: &mut Vec<u8>) {
let token = &self.tokens[id];
if let Some(byte) = fallback_byte(token) {
bytes.push(byte);
return;
}
for character in token.chars() {
if character == METASPACE {
bytes.push(b' ');
continue;
}
let mut encoded = [0u8; 4];
bytes.extend_from_slice(character.encode_utf8(&mut encoded).as_bytes());
}
}
fn merged(&self, mut symbols: Vec<String>) -> Vec<String> {
while symbols.len() > 1 {
let best = (0..symbols.len() - 1)
.filter_map(|at| {
self.ranks
.get(&(symbols[at].clone(), symbols[at + 1].clone()))
.map(|&rank| (rank, at))
})
.min();
let Some((_, at)) = best else {
break;
};
let joined = format!("{}{}", symbols[at], symbols[at + 1]);
symbols.splice(at..at + 2, [joined]);
}
symbols
}
}
fn fallback_byte(token: &str) -> Option<u8> {
let inner = token.strip_prefix("<0x")?.strip_suffix('>')?;
u8::from_str_radix(inner, 16).ok()
}