1use crate::types::*;
7use std::collections::HashMap;
8use std::path::Path;
9use tokenizers::Tokenizer as HfTokenizer;
10
11pub const BOS_TOKEN_ID: u32 = 1; pub const EOS_TOKEN_ID: u32 = 2; pub const UNK_TOKEN_ID: u32 = 0; pub const PAD_TOKEN_ID: u32 = 3; #[derive(Debug, Clone)]
19pub struct SpecialTokens {
20 pub bos_token_id: u32,
21 pub eos_token_id: u32,
22 pub unk_token_id: u32,
23 pub pad_token_id: u32,
24}
25
26impl Default for SpecialTokens {
27 fn default() -> Self {
28 Self {
29 bos_token_id: BOS_TOKEN_ID,
30 eos_token_id: EOS_TOKEN_ID,
31 unk_token_id: UNK_TOKEN_ID,
32 pad_token_id: PAD_TOKEN_ID,
33 }
34 }
35}
36
37pub struct Tokenizer {
39 inner: TokenizerBackend,
40 special_tokens: SpecialTokens,
41}
42
43enum TokenizerBackend {
44 HuggingFace(HfTokenizer),
45 Basic(BasicTokenizer),
46 GGUF(GGUFTokenizerImpl),
47}
48
49impl Tokenizer {
50 pub fn new() -> Self {
52 Self {
53 inner: TokenizerBackend::Basic(BasicTokenizer::new()),
54 special_tokens: SpecialTokens::default(),
55 }
56 }
57
58 pub fn from_file<P: AsRef<Path>>(path: P) -> ModelResult<Self> {
60 let hf_tokenizer = HfTokenizer::from_file(path.as_ref())
61 .map_err(|e| ModelError::InitializationFailed(format!("Failed to load tokenizer: {}", e)))?;
62
63 let special_tokens = Self::extract_special_tokens(&hf_tokenizer);
65
66 Ok(Self {
67 inner: TokenizerBackend::HuggingFace(hf_tokenizer),
68 special_tokens,
69 })
70 }
71
72 pub fn from_model_dir<P: AsRef<Path>>(model_dir: P) -> ModelResult<Self> {
74 let tokenizer_path = model_dir.as_ref().join("tokenizer.json");
75 if tokenizer_path.exists() {
76 Self::from_file(tokenizer_path)
77 } else {
78 Ok(Self::new())
80 }
81 }
82
83 pub fn from_gguf(gguf_tokenizer: &crate::weight_loader_core::GGUFTokenizer) -> ModelResult<Self> {
85 let impl_tokenizer = GGUFTokenizerImpl::new(gguf_tokenizer);
86 let special_tokens = impl_tokenizer.special_tokens.clone();
87
88 Ok(Self {
89 inner: TokenizerBackend::GGUF(impl_tokenizer),
90 special_tokens,
91 })
92 }
93
94 pub fn from_model_weights(weights: &crate::model_core::ModelWeights) -> ModelResult<Self> {
96 if let Some(ref gguf_tok) = weights.gguf_tokenizer {
98 return Self::from_gguf(gguf_tok);
99 }
100
101 Ok(Self::new())
103 }
104
105 pub fn from_pretrained(model_name: &str) -> ModelResult<Self> {
108 let cache_dir = dirs::cache_dir()
110 .unwrap_or_else(|| std::path::PathBuf::from("."))
111 .join("huggingface")
112 .join("hub");
113
114 let model_path = cache_dir.join(format!("models--{}", model_name.replace('/', "--")));
116 let tokenizer_path = model_path.join("snapshots").join("*").join("tokenizer.json");
117
118 if let Ok(entries) = glob::glob(tokenizer_path.to_str().unwrap_or("")) {
120 for entry in entries.flatten() {
121 if let Ok(tokenizer) = Self::from_file(entry) {
122 return Ok(tokenizer);
123 }
124 }
125 }
126
127 Ok(Self::new())
129 }
130
131 fn extract_special_tokens(hf_tokenizer: &HfTokenizer) -> SpecialTokens {
133 let mut special = SpecialTokens::default();
134
135 if let Some(id) = hf_tokenizer.token_to_id("<s>") {
137 special.bos_token_id = id;
138 } else if let Some(id) = hf_tokenizer.token_to_id("<bos>") {
139 special.bos_token_id = id;
140 }
141
142 if let Some(id) = hf_tokenizer.token_to_id("</s>") {
143 special.eos_token_id = id;
144 } else if let Some(id) = hf_tokenizer.token_to_id("<eos>") {
145 special.eos_token_id = id;
146 }
147
148 if let Some(id) = hf_tokenizer.token_to_id("<unk>") {
149 special.unk_token_id = id;
150 }
151
152 if let Some(id) = hf_tokenizer.token_to_id("<pad>") {
153 special.pad_token_id = id;
154 }
155
156 special
157 }
158
159 pub fn encode(&self, text: &str) -> Vec<u32> {
161 match &self.inner {
162 TokenizerBackend::HuggingFace(hf) => {
163 match hf.encode(text, false) {
164 Ok(encoding) => encoding.get_ids().to_vec(),
165 Err(_) => {
166 vec![self.special_tokens.bos_token_id, self.special_tokens.eos_token_id]
168 }
169 }
170 }
171 TokenizerBackend::Basic(basic) => basic.encode(text),
172 TokenizerBackend::GGUF(gguf) => gguf.encode(text),
173 }
174 }
175
176 pub fn encode_with_special_tokens(&self, text: &str, add_bos: bool, add_eos: bool) -> Vec<u32> {
178 match &self.inner {
179 TokenizerBackend::HuggingFace(hf) => {
180 match hf.encode(text, add_bos) {
181 Ok(encoding) => {
182 let mut ids = encoding.get_ids().to_vec();
183 if add_eos && !ids.ends_with(&[self.special_tokens.eos_token_id]) {
184 ids.push(self.special_tokens.eos_token_id);
185 }
186 ids
187 }
188 Err(_) => {
189 let mut ids = if add_bos {
190 vec![self.special_tokens.bos_token_id]
191 } else {
192 vec![]
193 };
194 if add_eos {
195 ids.push(self.special_tokens.eos_token_id);
196 }
197 ids
198 }
199 }
200 }
201 TokenizerBackend::Basic(basic) => {
202 let mut ids = basic.encode_raw(text);
203 if add_bos {
204 ids.insert(0, self.special_tokens.bos_token_id);
205 }
206 if add_eos {
207 ids.push(self.special_tokens.eos_token_id);
208 }
209 ids
210 }
211 TokenizerBackend::GGUF(gguf) => {
212 let mut ids = gguf.encode_raw(text);
213 if add_bos {
214 ids.insert(0, self.special_tokens.bos_token_id);
215 }
216 if add_eos {
217 ids.push(self.special_tokens.eos_token_id);
218 }
219 ids
220 }
221 }
222 }
223
224 pub fn decode(&self, token_ids: &[u32]) -> String {
226 match &self.inner {
227 TokenizerBackend::HuggingFace(hf) => {
228 hf.decode(token_ids, true).unwrap_or_default()
229 }
230 TokenizerBackend::Basic(basic) => basic.decode(token_ids),
231 TokenizerBackend::GGUF(gguf) => gguf.decode(token_ids),
232 }
233 }
234
235 pub fn decode_raw(&self, token_ids: &[u32]) -> String {
237 match &self.inner {
238 TokenizerBackend::HuggingFace(hf) => {
239 hf.decode(token_ids, false).unwrap_or_default()
240 }
241 TokenizerBackend::Basic(basic) => basic.decode(token_ids),
242 TokenizerBackend::GGUF(gguf) => gguf.decode_raw(token_ids),
243 }
244 }
245
246 pub fn vocab_size(&self) -> usize {
248 match &self.inner {
249 TokenizerBackend::HuggingFace(hf) => hf.get_vocab_size(true),
250 TokenizerBackend::Basic(basic) => basic.vocab_size(),
251 TokenizerBackend::GGUF(gguf) => gguf.vocab_size(),
252 }
253 }
254
255 pub fn special_tokens(&self) -> &SpecialTokens {
257 &self.special_tokens
258 }
259
260 pub fn bos_token_id(&self) -> u32 {
262 self.special_tokens.bos_token_id
263 }
264
265 pub fn eos_token_id(&self) -> u32 {
267 self.special_tokens.eos_token_id
268 }
269
270 pub fn pad_token_id(&self) -> u32 {
272 self.special_tokens.pad_token_id
273 }
274
275 pub fn unk_token_id(&self) -> u32 {
277 self.special_tokens.unk_token_id
278 }
279
280 pub fn contains_token(&self, token: &str) -> bool {
282 match &self.inner {
283 TokenizerBackend::HuggingFace(hf) => hf.token_to_id(token).is_some(),
284 TokenizerBackend::Basic(basic) => basic.contains_token(token),
285 TokenizerBackend::GGUF(gguf) => gguf.contains_token(token),
286 }
287 }
288
289 pub fn token_to_id(&self, token: &str) -> Option<u32> {
291 match &self.inner {
292 TokenizerBackend::HuggingFace(hf) => hf.token_to_id(token),
293 TokenizerBackend::Basic(basic) => basic.token_to_id(token),
294 TokenizerBackend::GGUF(gguf) => gguf.token_to_id(token),
295 }
296 }
297
298 pub fn id_to_token(&self, id: u32) -> Option<String> {
300 match &self.inner {
301 TokenizerBackend::HuggingFace(hf) => hf.id_to_token(id),
302 TokenizerBackend::Basic(basic) => basic.id_to_token(id).map(|s| s.to_string()),
303 TokenizerBackend::GGUF(gguf) => gguf.id_to_token(id).map(|s| s.to_string()),
304 }
305 }
306}
307
308impl Default for Tokenizer {
309 fn default() -> Self {
310 Self::new()
311 }
312}
313
314struct BasicTokenizer {
316 vocab: HashMap<String, u32>,
317 id_to_token: HashMap<u32, String>,
318 vocab_size: usize,
319}
320
321impl BasicTokenizer {
322 fn new() -> Self {
323 let mut vocab = HashMap::new();
324 let mut id_to_token = HashMap::new();
325
326 vocab.insert("<unk>".to_string(), UNK_TOKEN_ID);
328 vocab.insert("<s>".to_string(), BOS_TOKEN_ID);
329 vocab.insert("</s>".to_string(), EOS_TOKEN_ID);
330 vocab.insert("<pad>".to_string(), PAD_TOKEN_ID);
331
332 id_to_token.insert(UNK_TOKEN_ID, "<unk>".to_string());
333 id_to_token.insert(BOS_TOKEN_ID, "<s>".to_string());
334 id_to_token.insert(EOS_TOKEN_ID, "</s>".to_string());
335 id_to_token.insert(PAD_TOKEN_ID, "<pad>".to_string());
336
337 let mut next_id = 4;
338
339 let basic_vocab = vec![
341 "the", "a", "an", "and", "or", "but", "in", "on", "at", "to", "for", "of", "with", "by",
342 "is", "are", "was", "were", "be", "been", "have", "has", "had", "do", "does", "did",
343 "will", "would", "could", "should", "can", "may", "might", "must",
344 "I", "you", "he", "she", "it", "we", "they", "me", "him", "her", "us", "them",
345 "this", "that", "these", "those", "here", "there", "where", "when", "why", "how",
346 "what", "who", "which", "whose", "all", "some", "any", "no", "not", "yes",
347 "hello", "world", "test", "example", "text", "model", "language", "AI",
348 "quick", "brown", "fox", "dog", "cat", "house", "car", "tree", "book", "water",
349 ".", "!", "?", ",", ";", ":", "'", "\"", "-", "_", "(", ")", "[", "]", "{", "}",
350 "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
351 "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
352 "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
353 "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
354 "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
355 "ing", "ed", "er", "ly", "tion", "ment", "ness", "ity", "ous", "ful",
356 " ", "\n", "\t",
357 ];
358
359 for word in basic_vocab {
360 if !vocab.contains_key(word) {
361 vocab.insert(word.to_string(), next_id);
362 id_to_token.insert(next_id, word.to_string());
363 next_id += 1;
364 }
365 }
366
367 Self {
368 vocab,
369 id_to_token,
370 vocab_size: next_id as usize,
371 }
372 }
373
374 fn encode(&self, text: &str) -> Vec<u32> {
375 let mut tokens = vec![BOS_TOKEN_ID];
376 tokens.extend(self.encode_raw(text));
377 tokens.push(EOS_TOKEN_ID);
378 tokens
379 }
380
381 fn encode_raw(&self, text: &str) -> Vec<u32> {
382 let mut tokens = Vec::new();
383 let words = self.tokenize_text(text);
384
385 for word in words {
386 if let Some(&token_id) = self.vocab.get(&word) {
387 tokens.push(token_id);
388 } else {
389 for ch in word.chars() {
390 let ch_str = ch.to_string();
391 if let Some(&token_id) = self.vocab.get(&ch_str) {
392 tokens.push(token_id);
393 } else {
394 tokens.push(UNK_TOKEN_ID);
395 }
396 }
397 }
398 }
399 tokens
400 }
401
402 fn decode(&self, token_ids: &[u32]) -> String {
403 let mut result = String::new();
404 let mut last_was_char = false;
405
406 for &token_id in token_ids {
407 if token_id == BOS_TOKEN_ID || token_id == EOS_TOKEN_ID || token_id == PAD_TOKEN_ID {
408 continue;
409 }
410
411 if let Some(token) = self.id_to_token.get(&token_id) {
412 let is_single_char = token.len() == 1 && token.chars().next().unwrap().is_alphabetic();
413 let is_whitespace = token.chars().all(|c| c.is_whitespace());
414 let is_punctuation = token.chars().all(|c| c.is_ascii_punctuation());
415
416 if !result.is_empty() && !is_whitespace && !is_punctuation {
417 if !last_was_char || !is_single_char {
418 result.push(' ');
419 }
420 }
421
422 result.push_str(token);
423 last_was_char = is_single_char;
424 } else {
425 if !result.is_empty() {
426 result.push(' ');
427 }
428 result.push_str("<unk>");
429 last_was_char = false;
430 }
431 }
432
433 result.trim().to_string()
434 }
435
436 fn vocab_size(&self) -> usize {
437 self.vocab_size
438 }
439
440 fn contains_token(&self, token: &str) -> bool {
441 self.vocab.contains_key(token)
442 }
443
444 fn token_to_id(&self, token: &str) -> Option<u32> {
445 self.vocab.get(token).copied()
446 }
447
448 fn id_to_token(&self, id: u32) -> Option<&str> {
449 self.id_to_token.get(&id).map(|s| s.as_str())
450 }
451
452 fn tokenize_text(&self, text: &str) -> Vec<String> {
453 let mut tokens = Vec::new();
454 let mut current_word = String::new();
455
456 for ch in text.chars() {
457 if ch.is_whitespace() {
458 if !current_word.is_empty() {
459 tokens.push(current_word.clone());
460 current_word.clear();
461 }
462 tokens.push(ch.to_string());
463 } else if ch.is_ascii_punctuation() {
464 if !current_word.is_empty() {
465 tokens.push(current_word.clone());
466 current_word.clear();
467 }
468 tokens.push(ch.to_string());
469 } else {
470 current_word.push(ch);
471 }
472 }
473
474 if !current_word.is_empty() {
475 tokens.push(current_word);
476 }
477
478 tokens
479 }
480}
481
482struct GGUFTokenizerImpl {
484 tokens: Vec<String>,
485 token_to_id: HashMap<String, u32>,
486 id_to_token: HashMap<u32, String>,
487 special_tokens: SpecialTokens,
488 vocab_size: usize,
489}
490
491impl GGUFTokenizerImpl {
492 fn new(gguf_tokenizer: &crate::weight_loader_core::GGUFTokenizer) -> Self {
493 let mut token_to_id = HashMap::new();
494 let mut id_to_token = HashMap::new();
495
496 for (id, token) in gguf_tokenizer.tokens.iter().enumerate() {
497 let id = id as u32;
498 token_to_id.insert(token.clone(), id);
499 id_to_token.insert(id, token.clone());
500 }
501
502 let gguf_special = &gguf_tokenizer.special_tokens;
503 let special_tokens = SpecialTokens {
504 bos_token_id: gguf_special.bos_token_id.unwrap_or(BOS_TOKEN_ID),
505 eos_token_id: gguf_special.eos_token_id.unwrap_or(EOS_TOKEN_ID),
506 unk_token_id: gguf_special.unk_token_id.unwrap_or(UNK_TOKEN_ID),
507 pad_token_id: gguf_special.pad_token_id.unwrap_or(PAD_TOKEN_ID),
508 };
509
510 let vocab_size = gguf_tokenizer.tokens.len();
511
512 Self {
513 tokens: gguf_tokenizer.tokens.clone(),
514 token_to_id,
515 id_to_token,
516 special_tokens,
517 vocab_size,
518 }
519 }
520
521 fn encode(&self, text: &str) -> Vec<u32> {
522 let mut tokens = vec![self.special_tokens.bos_token_id];
523 tokens.extend(self.encode_raw(text));
524 tokens.push(self.special_tokens.eos_token_id);
525 tokens
526 }
527
528 fn encode_raw(&self, text: &str) -> Vec<u32> {
529 let processed = format!("▁{}", text.replace(' ', "▁"));
533
534 let mut result = Vec::new();
535 let bytes = processed.as_bytes();
536 let mut i = 0;
537
538 while i < bytes.len() {
539 let mut matched = false;
540
541 let max_len = std::cmp::min(bytes.len() - i, 32);
544
545 for len in (1..=max_len).rev() {
546 if let Ok(substr) = std::str::from_utf8(&bytes[i..i + len]) {
547 if let Some(&id) = self.token_to_id.get(substr) {
548 result.push(id);
549 i += len;
550 matched = true;
551 break;
552 }
553 }
554 }
555
556 if !matched {
557 let byte = bytes[i];
559
560 if let Ok(ch) = std::str::from_utf8(&bytes[i..i + 1]) {
562 if let Some(&id) = self.token_to_id.get(ch) {
563 result.push(id);
564 i += 1;
565 continue;
566 }
567 }
568
569 let byte_token = format!("<0x{:02X}>", byte);
571 if let Some(&id) = self.token_to_id.get(&byte_token) {
572 result.push(id);
573 } else {
574 result.push(self.special_tokens.unk_token_id);
576 }
577 i += 1;
578 }
579 }
580
581 result
582 }
583
584 fn decode(&self, token_ids: &[u32]) -> String {
585 let mut result = String::new();
586
587 for &id in token_ids {
588 if id == self.special_tokens.bos_token_id
590 || id == self.special_tokens.eos_token_id
591 || id == self.special_tokens.pad_token_id
592 {
593 continue;
594 }
595
596 if let Some(token) = self.id_to_token.get(&id) {
597 result.push_str(token);
598 }
599 }
600
601 result.replace('▁', " ").trim_start().to_string()
603 }
604
605 fn decode_raw(&self, token_ids: &[u32]) -> String {
606 let mut result = String::new();
607
608 for &id in token_ids {
609 if let Some(token) = self.id_to_token.get(&id) {
610 result.push_str(token);
611 }
612 }
613
614 result.replace('▁', " ").trim_start().to_string()
616 }
617
618 fn vocab_size(&self) -> usize {
619 self.vocab_size
620 }
621
622 fn contains_token(&self, token: &str) -> bool {
623 self.token_to_id.contains_key(token)
624 }
625
626 fn token_to_id(&self, token: &str) -> Option<u32> {
627 self.token_to_id.get(token).copied()
628 }
629
630 fn id_to_token(&self, id: u32) -> Option<&str> {
631 self.id_to_token.get(&id).map(|s| s.as_str())
632 }
633}
634
635pub struct BatchTokenizer {
637 tokenizer: Tokenizer,
638}
639
640impl BatchTokenizer {
641 pub fn new(tokenizer: Tokenizer) -> Self {
642 Self { tokenizer }
643 }
644
645 pub fn encode_batch(&self, texts: &[&str], max_length: Option<usize>) -> (Vec<Vec<u32>>, Vec<Vec<bool>>) {
647 let mut encoded_batch = Vec::new();
648 let mut attention_masks = Vec::new();
649
650 for text in texts {
651 let tokens = self.tokenizer.encode(text);
652 encoded_batch.push(tokens);
653 }
654
655 let max_len = max_length.unwrap_or_else(|| {
656 encoded_batch.iter().map(|tokens| tokens.len()).max().unwrap_or(0)
657 });
658
659 for tokens in &mut encoded_batch {
660 if tokens.len() > max_len {
661 tokens.truncate(max_len);
662 }
663
664 let mut attention_mask = vec![true; tokens.len()];
665
666 while tokens.len() < max_len {
667 tokens.push(self.tokenizer.pad_token_id());
668 attention_mask.push(false);
669 }
670
671 attention_masks.push(attention_mask);
672 }
673
674 (encoded_batch, attention_masks)
675 }
676
677 pub fn decode_batch(&self, token_sequences: &[Vec<u32>]) -> Vec<String> {
679 token_sequences.iter()
680 .map(|tokens| self.tokenizer.decode(tokens))
681 .collect()
682 }
683}
684
685#[cfg(test)]
686mod tests {
687 use super::*;
688
689 #[test]
690 fn test_tokenizer_creation() {
691 let tokenizer = Tokenizer::new();
692 assert!(tokenizer.vocab_size() > 100);
693 assert!(tokenizer.contains_token("the"));
694 assert!(tokenizer.contains_token("<s>"));
695 assert!(tokenizer.contains_token("</s>"));
696 }
697
698 #[test]
699 fn test_special_tokens() {
700 let tokenizer = Tokenizer::new();
701 assert_eq!(tokenizer.token_to_id("<unk>"), Some(UNK_TOKEN_ID));
702 assert_eq!(tokenizer.token_to_id("<s>"), Some(BOS_TOKEN_ID));
703 assert_eq!(tokenizer.token_to_id("</s>"), Some(EOS_TOKEN_ID));
704 assert_eq!(tokenizer.token_to_id("<pad>"), Some(PAD_TOKEN_ID));
705 }
706
707 #[test]
708 fn test_encode_decode_simple() {
709 let tokenizer = Tokenizer::new();
710 let text = "hello world";
711
712 let tokens = tokenizer.encode(text);
713 assert!(tokens.len() > 2);
714
715 let decoded = tokenizer.decode(&tokens);
716 assert!(decoded.contains("hello"));
717 assert!(decoded.contains("world"));
718 }
719
720 #[test]
721 fn test_special_token_ids() {
722 let tokenizer = Tokenizer::new();
723 assert_eq!(tokenizer.bos_token_id(), BOS_TOKEN_ID);
724 assert_eq!(tokenizer.eos_token_id(), EOS_TOKEN_ID);
725 assert_eq!(tokenizer.pad_token_id(), PAD_TOKEN_ID);
726 assert_eq!(tokenizer.unk_token_id(), UNK_TOKEN_ID);
727 }
728
729 #[test]
730 fn test_batch_tokenizer() {
731 let tokenizer = Tokenizer::new();
732 let batch_tokenizer = BatchTokenizer::new(tokenizer);
733
734 let texts = vec!["hello", "hello world", "hello world test"];
735 let (encoded_batch, attention_masks) = batch_tokenizer.encode_batch(&texts, Some(10));
736
737 assert_eq!(encoded_batch.len(), 3);
738 assert_eq!(attention_masks.len(), 3);
739
740 for tokens in &encoded_batch {
741 assert_eq!(tokens.len(), 10);
742 }
743 }
744}