| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| use pyo3::prelude::*; |
| use rayon::prelude::*; |
| use ahash::AHashMap; |
| use std::sync::Arc; |
| use crate::errors::KernelError; |
|
|
| |
|
|
| |
| pub type Vocab = AHashMap<String, u32>; |
|
|
| |
| pub type MergePairs = AHashMap<(u32, u32), u32>; |
|
|
| |
| pub type TokenIds = Vec<u32>; |
|
|
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| #[pyclass] |
| pub struct RustTokenizer { |
| |
| vocab: Arc<Vocab>, |
| |
| id_to_token: Arc<Vec<String>>, |
| |
| merges: Arc<MergePairs>, |
| |
| unk_id: u32, |
| pad_id: u32, |
| eos_id: u32, |
| bos_id: u32, |
| |
| max_token_len: usize, |
| } |
|
|
| #[pymethods] |
| impl RustTokenizer { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| #[new] |
| #[pyo3(signature = (vocab, merges, unk_id=0, pad_id=1, eos_id=2, bos_id=3))] |
| pub fn new( |
| vocab: std::collections::HashMap<String, u32>, |
| merges: Vec<(String, String)>, |
| unk_id: u32, |
| pad_id: u32, |
| eos_id: u32, |
| bos_id: u32, |
| ) -> PyResult<Self> { |
| |
| let vocab_map: Vocab = vocab.into_iter().collect(); |
| let vocab_size = vocab_map.len(); |
|
|
| |
| let mut id_to_token_map: Vec<String> = vec!["<UNK>".to_string(); vocab_size + 16]; |
| for (token, id) in &vocab_map { |
| let id = *id as usize; |
| if id < id_to_token_map.len() { |
| id_to_token_map[id] = token.clone(); |
| } |
| } |
|
|
| |
| let mut merge_map: MergePairs = AHashMap::new(); |
| for (left_str, right_str) in &merges { |
| let merged = format!("{}{}", left_str, right_str); |
| if let (Some(&left_id), Some(&right_id), Some(&merged_id)) = ( |
| vocab_map.get(left_str.as_str()), |
| vocab_map.get(right_str.as_str()), |
| vocab_map.get(merged.as_str()), |
| ) { |
| merge_map.insert((left_id, right_id), merged_id); |
| } |
| } |
|
|
| Ok(RustTokenizer { |
| vocab: Arc::new(vocab_map), |
| id_to_token: Arc::new(id_to_token_map), |
| merges: Arc::new(merge_map), |
| unk_id, |
| pad_id, |
| eos_id, |
| bos_id, |
| max_token_len: 512, |
| }) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| #[pyo3(signature = (text, add_special_tokens=true, max_length=2048))] |
| pub fn encode( |
| &self, |
| py: Python<'_>, |
| text: String, |
| add_special_tokens: bool, |
| max_length: usize, |
| ) -> Vec<u32> { |
| let vocab = Arc::clone(&self.vocab); |
| let merges = Arc::clone(&self.merges); |
| let unk_id = self.unk_id; |
| let bos_id = self.bos_id; |
| let eos_id = self.eos_id; |
| let max_tok = self.max_token_len; |
|
|
| py.allow_threads(move || { |
| encode_bpe(&text, &vocab, &merges, unk_id, bos_id, eos_id, |
| add_special_tokens, max_length, max_tok) |
| }) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| #[pyo3(signature = (texts, add_special_tokens=true, max_length=2048))] |
| pub fn encode_batch( |
| &self, |
| py: Python<'_>, |
| texts: Vec<String>, |
| add_special_tokens: bool, |
| max_length: usize, |
| ) -> Vec<Vec<u32>> { |
| let vocab = Arc::clone(&self.vocab); |
| let merges = Arc::clone(&self.merges); |
| let unk_id = self.unk_id; |
| let bos_id = self.bos_id; |
| let eos_id = self.eos_id; |
| let max_tok = self.max_token_len; |
|
|
| py.allow_threads(move || { |
| texts.par_iter() |
| .map(|text| { |
| encode_bpe(text, &vocab, &merges, unk_id, bos_id, eos_id, |
| add_special_tokens, max_length, max_tok) |
| }) |
| .collect() |
| }) |
| } |
|
|
| |
| pub fn decode(&self, ids: Vec<u32>) -> String { |
| ids.iter() |
| .filter_map(|&id| self.id_to_token.get(id as usize)) |
| .cloned() |
| .collect::<Vec<_>>() |
| .join("") |
| } |
|
|
| |
| pub fn vocab_size(&self) -> usize { |
| self.vocab.len() |
| } |
| } |
|
|
| |
|
|
| |
| fn encode_bpe( |
| text: &str, |
| vocab: &Vocab, |
| merges: &MergePairs, |
| unk_id: u32, |
| bos_id: u32, |
| eos_id: u32, |
| add_special: bool, |
| max_length: usize, |
| max_token_len: usize, |
| ) -> TokenIds { |
| |
| let mut ids: TokenIds = Vec::with_capacity(text.len() / 3 + 4); |
|
|
| if add_special { |
| ids.push(bos_id); |
| } |
|
|
| |
| let chars: Vec<String> = text.chars().map(|c| c.to_string()).collect(); |
| let mut pieces: Vec<u32> = chars.iter() |
| .map(|c| vocab.get(c.as_str()).copied().unwrap_or(unk_id)) |
| .collect(); |
|
|
| |
| |
| let max_iters = max_token_len.min(pieces.len()); |
| for _ in 0..max_iters { |
| let mut best: Option<(usize, u32)> = None; |
|
|
| |
| for i in 0..pieces.len().saturating_sub(1) { |
| let pair = (pieces[i], pieces[i + 1]); |
| if let Some(&merged) = merges.get(&pair) { |
| |
| best = Some((i, merged)); |
| break; |
| } |
| } |
|
|
| match best { |
| None => break, |
| Some((pos, merged_id)) => { |
| |
| pieces[pos] = merged_id; |
| pieces.remove(pos + 1); |
| } |
| } |
| } |
|
|
| ids.extend_from_slice(&pieces); |
|
|
| if add_special { |
| ids.push(eos_id); |
| } |
|
|
| |
| ids.truncate(max_length); |
| ids |
| } |
|
|
| |
|
|
| #[cfg(test)] |
| mod tests { |
| use super::*; |
|
|
| fn make_simple_vocab() -> Vocab { |
| let mut v = AHashMap::new(); |
| v.insert("<PAD>".to_string(), 0u32); |
| v.insert("<UNK>".to_string(), 1u32); |
| v.insert("<BOS>".to_string(), 2u32); |
| v.insert("<EOS>".to_string(), 3u32); |
| v.insert("h".to_string(), 4u32); |
| v.insert("e".to_string(), 5u32); |
| v.insert("l".to_string(), 6u32); |
| v.insert("o".to_string(), 7u32); |
| v.insert("he".to_string(), 8u32); |
| v.insert("hel".to_string(), 9u32); |
| v |
| } |
|
|
| #[test] |
| fn test_encode_basic() { |
| let vocab = make_simple_vocab(); |
| let merges = AHashMap::new(); |
| let result = encode_bpe("hello", &vocab, &merges, 1, 2, 3, true, 128, 512); |
| assert_eq!(result[0], 2, "First token is BOS"); |
| assert_eq!(result[result.len() - 1], 3, "Last token is EOS"); |
| } |
|
|
| #[test] |
| fn test_unk_for_missing_chars() { |
| let vocab = make_simple_vocab(); |
| let merges = AHashMap::new(); |
| let result = encode_bpe("xyz", &vocab, &merges, 1, 2, 3, false, 128, 512); |
| assert!(result.iter().all(|&id| id == 1), "All unknown chars β UNK id"); |
| } |
|
|
| #[test] |
| fn test_truncation() { |
| let vocab = make_simple_vocab(); |
| let merges = AHashMap::new(); |
| let text = "hello".repeat(100); |
| let result = encode_bpe(&text, &vocab, &merges, 1, 2, 3, true, 10, 512); |
| assert!(result.len() <= 10, "Result is truncated to max_length"); |
| } |
|
|
| #[test] |
| fn test_empty_string() { |
| let vocab = make_simple_vocab(); |
| let merges = AHashMap::new(); |
| let result = encode_bpe("", &vocab, &merges, 1, 2, 3, true, 128, 512); |
| |
| assert_eq!(result, vec![2, 3]); |
| } |
| } |
|
|