CoolFace
Modelpublic

aoiandroid/IndexTTS-Rust

sourceHugging Facemitupdated 4mo agoView on Hugging Face
0likes1downloads
tokenizer.rs317 linesDownload Raw Back to text
1//! Text tokenization for TTS2//!3//! Uses SentencePiece BPE tokenization for converting text to tokens4 5use crate::{Error, Result};6use std::collections::HashMap;7use std::path::Path;8 9/// Tokenizer configuration10#[derive(Debug, Clone)]11pub struct TokenizerConfig {12    /// Path to BPE model13    pub model_path: String,14    /// Vocabulary size15    pub vocab_size: usize,16    /// Start of text token ID17    pub bos_id: i64,18    /// End of text token ID19    pub eos_id: i64,20    /// Unknown token ID21    pub unk_id: i64,22    /// Padding token ID23    pub pad_id: i64,24}25 26impl Default for TokenizerConfig {27    fn default() -> Self {28        Self {29            model_path: "models/bpe.model".to_string(),30            vocab_size: 6681,31            bos_id: 1,32            eos_id: 2,33            unk_id: 0,34            pad_id: 3,35        }36    }37}38 39/// Text tokenizer using BPE (Byte Pair Encoding)40#[derive(Debug)]41pub struct TextTokenizer {42    /// Configuration43    config: TokenizerConfig,44    /// Token to ID mapping45    token_to_id: HashMap<String, i64>,46    /// ID to token mapping47    id_to_token: HashMap<i64, String>,48    /// Character-level fallback vocabulary49    char_vocab: HashMap<char, i64>,50}51 52impl TextTokenizer {53    /// Create new tokenizer with default vocabulary54    pub fn new(config: TokenizerConfig) -> Result<Self> {55        let mut token_to_id = HashMap::new();56        let mut id_to_token = HashMap::new();57        let mut char_vocab = HashMap::new();58 59        // Add special tokens60        token_to_id.insert("<unk>".to_string(), config.unk_id);61        token_to_id.insert("<s>".to_string(), config.bos_id);62        token_to_id.insert("</s>".to_string(), config.eos_id);63        token_to_id.insert("<pad>".to_string(), config.pad_id);64 65        id_to_token.insert(config.unk_id, "<unk>".to_string());66        id_to_token.insert(config.bos_id, "<s>".to_string());67        id_to_token.insert(config.eos_id, "</s>".to_string());68        id_to_token.insert(config.pad_id, "<pad>".to_string());69 70        // Add basic ASCII characters71        let mut next_id = 4i64;72        for c in ' '..='~' {73            char_vocab.insert(c, next_id);74            token_to_id.insert(c.to_string(), next_id);75            id_to_token.insert(next_id, c.to_string());76            next_id += 1;77        }78 79        // Add Chinese character range (simplified approach)80        // In production, this would load from the actual BPE model81        for code_point in 0x4E00u32..=0x9FFF {82            if let Some(c) = char::from_u32(code_point) {83                char_vocab.insert(c, next_id);84                token_to_id.insert(c.to_string(), next_id);85                id_to_token.insert(next_id, c.to_string());86                next_id += 1;87 88                if next_id >= config.vocab_size as i64 {89                    break;90                }91            }92        }93 94        Ok(Self {95            config,96            token_to_id,97            id_to_token,98            char_vocab,99        })100    }101 102    /// Load tokenizer from model file103    pub fn load<P: AsRef<Path>>(path: P) -> Result<Self> {104        let path = path.as_ref();105        if !path.exists() {106            return Err(Error::FileNotFound(path.display().to_string()));107        }108 109        // In production, this would load the actual SentencePiece model110        // For now, create a character-level tokenizer111        let config = TokenizerConfig {112            model_path: path.display().to_string(),113            ..Default::default()114        };115 116        Self::new(config)117    }118 119    /// Encode text to token IDs120    pub fn encode(&self, text: &str) -> Result<Vec<i64>> {121        let mut tokens = Vec::new();122 123        // Add BOS token124        tokens.push(self.config.bos_id);125 126        // Tokenize character by character (simplified)127        // In production, this would use BPE merging128        for ch in text.chars() {129            if let Some(&id) = self.char_vocab.get(&ch) {130                tokens.push(id);131            } else if let Some(&id) = self.token_to_id.get(&ch.to_string()) {132                tokens.push(id);133            } else {134                // Unknown token135                tokens.push(self.config.unk_id);136            }137        }138 139        // Add EOS token140        tokens.push(self.config.eos_id);141 142        Ok(tokens)143    }144 145    /// Encode text without special tokens146    pub fn encode_without_special(&self, text: &str) -> Result<Vec<i64>> {147        let mut tokens = Vec::new();148 149        for ch in text.chars() {150            if let Some(&id) = self.char_vocab.get(&ch) {151                tokens.push(id);152            } else if let Some(&id) = self.token_to_id.get(&ch.to_string()) {153                tokens.push(id);154            } else {155                tokens.push(self.config.unk_id);156            }157        }158 159        Ok(tokens)160    }161 162    /// Decode token IDs to text163    pub fn decode(&self, tokens: &[i64]) -> Result<String> {164        let mut text = String::new();165 166        for &token_id in tokens {167            // Skip special tokens168            if token_id == self.config.bos_id169                || token_id == self.config.eos_id170                || token_id == self.config.pad_id171            {172                continue;173            }174 175            if let Some(token) = self.id_to_token.get(&token_id) {176                text.push_str(token);177            } else {178                // Unknown token placeholder179                text.push('?');180            }181        }182 183        Ok(text)184    }185 186    /// Get vocabulary size187    pub fn vocab_size(&self) -> usize {188        self.config.vocab_size189    }190 191    /// Get BOS token ID192    pub fn bos_id(&self) -> i64 {193        self.config.bos_id194    }195 196    /// Get EOS token ID197    pub fn eos_id(&self) -> i64 {198        self.config.eos_id199    }200 201    /// Get UNK token ID202    pub fn unk_id(&self) -> i64 {203        self.config.unk_id204    }205 206    /// Get PAD token ID207    pub fn pad_id(&self) -> i64 {208        self.config.pad_id209    }210 211    /// Pad sequences to same length212    pub fn pad_sequences(&self, sequences: &[Vec<i64>], max_len: Option<usize>) -> Vec<Vec<i64>> {213        let max_length = max_len.unwrap_or_else(|| sequences.iter().map(|s| s.len()).max().unwrap_or(0));214 215        sequences216            .iter()217            .map(|seq| {218                let mut padded = seq.clone();219                while padded.len() < max_length {220                    padded.push(self.config.pad_id);221                }222                padded.truncate(max_length);223                padded224            })225            .collect()226    }227 228    /// Create attention mask (1 for real tokens, 0 for padding)229    pub fn create_attention_mask(&self, tokens: &[i64]) -> Vec<i64> {230        tokens231            .iter()232            .map(|&t| if t == self.config.pad_id { 0 } else { 1 })233            .collect()234    }235 236    /// Batch encode multiple texts237    pub fn batch_encode(&self, texts: &[&str]) -> Result<Vec<Vec<i64>>> {238        texts.iter().map(|text| self.encode(text)).collect()239    }240 241    /// Batch encode and pad242    pub fn batch_encode_padded(243        &self,244        texts: &[&str],245        max_len: Option<usize>,246    ) -> Result<Vec<Vec<i64>>> {247        let encoded: Vec<Vec<i64>> = self.batch_encode(texts)?;248        Ok(self.pad_sequences(&encoded, max_len))249    }250}251 252#[cfg(test)]253mod tests {254    use super::*;255 256    #[test]257    fn test_tokenizer_creation() {258        let config = TokenizerConfig::default();259        let tokenizer = TextTokenizer::new(config).unwrap();260        assert!(tokenizer.vocab_size() > 0);261    }262 263    #[test]264    fn test_encode_decode() {265        let config = TokenizerConfig::default();266        let tokenizer = TextTokenizer::new(config).unwrap();267 268        let text = "Hello world";269        let tokens = tokenizer.encode(text).unwrap();270 271        // Should start with BOS and end with EOS272        assert_eq!(tokens[0], tokenizer.bos_id());273        assert_eq!(*tokens.last().unwrap(), tokenizer.eos_id());274 275        let decoded = tokenizer.decode(&tokens).unwrap();276        assert_eq!(decoded, text);277    }278 279    #[test]280    fn test_encode_chinese() {281        let config = TokenizerConfig::default();282        let tokenizer = TextTokenizer::new(config).unwrap();283 284        let text = "你好";285        let tokens = tokenizer.encode(text).unwrap();286 287        // Should have BOS + 2 chars + EOS = 4 tokens288        assert_eq!(tokens.len(), 4);289    }290 291    #[test]292    fn test_pad_sequences() {293        let config = TokenizerConfig::default();294        let tokenizer = TextTokenizer::new(config).unwrap();295 296        let seq1 = vec![1, 2, 3];297        let seq2 = vec![1, 2, 3, 4, 5];298 299        let padded = tokenizer.pad_sequences(&[seq1, seq2], None);300 301        assert_eq!(padded[0].len(), 5);302        assert_eq!(padded[1].len(), 5);303        assert_eq!(padded[0][3], tokenizer.pad_id());304    }305 306    #[test]307    fn test_attention_mask() {308        let config = TokenizerConfig::default();309        let tokenizer = TextTokenizer::new(config).unwrap();310 311        let tokens = vec![1, 2, tokenizer.pad_id(), tokenizer.pad_id()];312        let mask = tokenizer.create_attention_mask(&tokens);313 314        assert_eq!(mask, vec![1, 1, 0, 0]);315    }316}317