Download engine/python/curriculum_tokenizer/tokenizer.py from Qvinfor/BloatTokenizer32K: direct link, hf CLI and curl.
- Browser
- Download file 9.13 kB
-
https://huggingface.co/Qvinfor/BloatTokenizer32K/resolve/main/engine/python/curriculum_tokenizer/tokenizer.py
- Command line
-
hf download hf://Qvinfor/BloatTokenizer32K/engine/python/curriculum_tokenizer/tokenizer.py
-
curl -L -o tokenizer.py https://huggingface.co/Qvinfor/BloatTokenizer32K/resolve/main/engine/python/curriculum_tokenizer/tokenizer.py
9.13 kB
| """ | |
| Python wrapper around the C++ tokenizer with Hugging Face PreTrainedTokenizer compatibility. | |
| """ | |
| import json | |
| import os | |
| import shutil | |
| from pathlib import Path | |
| from typing import List, Union, Optional, Dict, Any | |
| try: | |
| from ._cpp_tokenizer import Tokenizer as _CTokenizer | |
| except ImportError: | |
| raise ImportError( | |
| "C++ extension not built. Run: " | |
| "pip install pybind11 && python setup.py build_ext --inplace" | |
| ) | |
| class CurriculumTokenizer: | |
| """ | |
| Fast BPE tokenizer with curriculum training support. | |
| Compatible with Hugging Face Transformers tokenization interface. | |
| """ | |
| def __init__(self, vocab_file: str, merges_file: str, **kwargs): | |
| """ | |
| Args: | |
| vocab_file: Path to vocab.json | |
| merges_file: Path to merges.txt | |
| **kwargs: Additional config (special_tokens, etc.) | |
| """ | |
| self._tok = _CTokenizer() | |
| vocab_path = os.path.abspath(vocab_file) | |
| merges_path = os.path.abspath(merges_file) | |
| self._vocab_path = vocab_path | |
| self._merges_path = merges_path | |
| self._config = dict(kwargs) | |
| if not os.path.exists(vocab_path): | |
| raise FileNotFoundError(f"Vocab file not found: {vocab_path}") | |
| if not os.path.exists(merges_path): | |
| raise FileNotFoundError(f"Merges file not found: {merges_path}") | |
| ok = self._tok.load(vocab_path, merges_path) | |
| if not ok: | |
| raise RuntimeError("Failed to load tokenizer. Check vocab.json and merges.txt format.") | |
| native_vocab_size = self._tok.vocab_size() | |
| config_vocab_size = kwargs.get("vocab_size") | |
| self.special_tokens = list(kwargs.get("special_tokens", ["<|endoftext|>"])) | |
| if len(set(self.special_tokens)) != len(self.special_tokens): | |
| raise ValueError("Duplicate special-token names") | |
| declared_ids = kwargs.get("special_token_ids", kwargs.get("reserved_token_ids", {})) | |
| if set(declared_ids) - set(self.special_tokens): | |
| raise ValueError("Configured control IDs contain an undeclared special token") | |
| self._special_token_ids = { | |
| token: declared_ids.get(token, self._tok.token_to_id(token)) | |
| for token in self.special_tokens | |
| } | |
| self._special_aliases = {} | |
| for token, token_id in kwargs.get("compatibility_reserved_ids", {}).items(): | |
| if token_id in self._special_aliases: | |
| raise ValueError("Duplicate decode-only alias ID") | |
| self._special_aliases[token_id] = token | |
| inferred_size = max([native_vocab_size, *[i + 1 for i in self._special_token_ids.values()], *[i + 1 for i in self._special_aliases]]) | |
| expected_size = int(config_vocab_size) if config_vocab_size is not None else inferred_size | |
| self._tok.configure_special_tokens(self._special_token_ids, self._special_aliases, expected_size) | |
| self.vocab_size = self._tok.vocab_size() | |
| self._skip_special_ids = set(self._special_token_ids.values()) | set(self._special_aliases) | |
| # Build vocab mapping for Python access | |
| self._vocab = {} | |
| self._id_to_token = {} | |
| for i in range(self.vocab_size): | |
| token = self._tok.id_to_token(i) | |
| if token: | |
| if i not in self._special_aliases: | |
| self._vocab[token] = i | |
| self._id_to_token[i] = token | |
| # Special token IDs | |
| self.eos_token = kwargs.get("eos_token", "<|endoftext|>") | |
| eos_id = self._tok.token_to_id(self.eos_token) if self.eos_token is not None else -1 | |
| self.eos_token_id = eos_id if eos_id >= 0 else None | |
| self.pad_token = kwargs.get("pad_token", "<|endoftext|>") | |
| pad_id = self._tok.token_to_id(self.pad_token) if self.pad_token is not None else -1 | |
| self.pad_token_id = pad_id if pad_id >= 0 else None | |
| def from_pretrained(cls, model_dir: str, **kwargs): | |
| """Load tokenizer from a directory containing vocab.json and merges.txt.""" | |
| model_dir = Path(model_dir) | |
| vocab_file = model_dir / "vocab.json" | |
| merges_file = model_dir / "merges.txt" | |
| # Load config if present | |
| config_file = model_dir / "tokenizer_config.json" | |
| if config_file.exists(): | |
| with open(config_file, encoding="utf-8") as f: | |
| config = json.load(f) | |
| kwargs = {**config, **kwargs} | |
| return cls(str(vocab_file), str(merges_file), **kwargs) | |
| def encode( | |
| self, | |
| text: Union[str, List[str]], | |
| add_special_tokens: bool = True, | |
| **kwargs | |
| ) -> Union[List[int], Dict[str, List[int]]]: | |
| """ | |
| Encode text to token IDs. | |
| Args: | |
| text: String or list of strings to encode | |
| add_special_tokens: Whether to add EOS token | |
| Returns: | |
| List of token IDs, or dict with "input_ids" key (HF style) | |
| """ | |
| if isinstance(text, list): | |
| return self.encode_batch(text, add_special_tokens=add_special_tokens, **kwargs) | |
| ids = self._tok.encode(text) | |
| if add_special_tokens and self.eos_token_id is not None: | |
| ids.append(self.eos_token_id) | |
| return ids | |
| def encode_batch( | |
| self, | |
| texts: List[str], | |
| add_special_tokens: bool = True, | |
| num_threads: int = 0, | |
| **kwargs | |
| ) -> Dict[str, List[List[int]]]: | |
| """Encode in input order; 0 selects up to four native workers for large batches.""" | |
| results = self._tok.encode_batch(texts, num_threads=num_threads) | |
| if add_special_tokens and self.eos_token_id is not None: | |
| for ids in results: | |
| ids.append(self.eos_token_id) | |
| return {"input_ids": results} | |
| def set_cache_capacity(self, capacity: int) -> None: | |
| """Bound cached exact pieces; zero disables caching.""" | |
| self._tok.set_cache_capacity(capacity) | |
| def clear_cache(self) -> None: | |
| """Clear cached encodings and reset hit/miss counters.""" | |
| self._tok.clear_cache() | |
| def cache_info(self) -> Dict[str, int]: | |
| """Return piece-cache statistics; payload bytes exclude container overhead.""" | |
| return self._tok.cache_info() | |
| def decode( | |
| self, | |
| ids: Union[List[int], List[List[int]]], | |
| skip_special_tokens: bool = True, | |
| **kwargs | |
| ) -> Union[str, List[str]]: | |
| """ | |
| Decode token IDs to text. | |
| Args: | |
| ids: List of token IDs or batch of lists | |
| skip_special_tokens: Whether to remove special tokens | |
| Returns: | |
| Decoded string or list of strings | |
| """ | |
| if ids and isinstance(ids[0], list): | |
| return [self.decode_single(seq, skip_special_tokens) for seq in ids] | |
| return self.decode_single(ids, skip_special_tokens) | |
| def decode_single(self, ids: List[int], skip_special_tokens: bool) -> str: | |
| """Decode a single sequence.""" | |
| if skip_special_tokens and self._skip_special_ids: | |
| ids = [id for id in ids if id not in self._skip_special_ids] | |
| return self._tok.decode(ids) | |
| def __call__(self, text: Union[str, List[str]], **kwargs): | |
| """Hugging Face style __call__ method.""" | |
| result = self.encode(text, **kwargs) | |
| if isinstance(result, dict): | |
| return result | |
| return {"input_ids": result} | |
| def convert_tokens_to_ids(self, tokens: Union[str, List[str]]) -> Union[int, List[int]]: | |
| """Convert token strings to IDs.""" | |
| fallback = self.eos_token_id if self.eos_token_id is not None else 0 | |
| if isinstance(tokens, str): | |
| native_id = self._tok.token_to_id(tokens) | |
| if native_id >= 0: | |
| return native_id | |
| return self._vocab.get(tokens, fallback) | |
| return [self.convert_tokens_to_ids(token) for token in tokens] | |
| def convert_ids_to_tokens(self, ids: Union[int, List[int]]) -> Union[str, List[str]]: | |
| """Convert IDs to token strings.""" | |
| if isinstance(ids, int): | |
| return self._id_to_token.get(ids, "") | |
| return [self._id_to_token.get(i, "") for i in ids] | |
| def save_pretrained(self, save_dir: str): | |
| """Save tokenizer files to a directory.""" | |
| os.makedirs(save_dir, exist_ok=True) | |
| config = {**self._config, | |
| "vocab_size": self.vocab_size, | |
| "special_tokens": self.special_tokens, | |
| "special_token_ids": self._special_token_ids, | |
| "eos_token": self.eos_token, | |
| "pad_token": self.pad_token, | |
| } | |
| with open(os.path.join(save_dir, "tokenizer_config.json"), "w", encoding="utf-8") as f: | |
| json.dump(config, f, indent=2) | |
| for source, name in [(self._vocab_path, "vocab.json"), (self._merges_path, "merges.txt")]: | |
| destination = os.path.abspath(os.path.join(save_dir, name)) | |
| if source != destination: | |
| shutil.copyfile(source, destination) | |
| def __len__(self): | |
| return self.vocab_size | |
| def __repr__(self): | |
| return f"CurriculumTokenizer(vocab_size={self.vocab_size})" | |