| | |
| | import os |
| | from typing import Collection, List, Optional, Dict, Set, Tuple, Union |
| |
|
| | from functools import cached_property |
| |
|
| | import base64 |
| | import requests |
| |
|
| | from transformers import PreTrainedTokenizer, AddedToken, AutoConfig |
| | from transformers.models.auto.tokenization_auto import get_tokenizer_config |
| | import tiktoken |
| |
|
| |
|
| | """ |
| | This tokenizer is almost identical to tiktoken.get_encoding("cl100k_base") |
| | with a few additional special tokens to support the ChatML format. |
| | |
| | TODO(bapatra): Right now, I do not save the special tokens to the vocab file. |
| | Maybe in the future, that would be useful? Can add that support later. |
| | |
| | """ |
| |
|
| | def _load_tiktoken_bpe(tiktoken_bpe_file: str) -> Dict[bytes, int]: |
| | with open(tiktoken_bpe_file, "rb") as f: |
| | contents = f.read() |
| | return { |
| | base64.b64decode(token): int(rank) |
| | for token, rank in (line.split() for line in contents.splitlines() if line) |
| | } |
| |
|
| | |
| | |
| | |
| |
|
| | EFFECTIVE_PADDED_VOCAB_SIZE = 100352 |
| | ACTUAL_VOCAB_SIZE = 100276 |
| |
|
| |
|
| | DUMMY_TOKENS = { |
| | f"<|dummy_id_{11 + offset}|>": 100276 + offset |
| | for offset in range(1, EFFECTIVE_PADDED_VOCAB_SIZE - ACTUAL_VOCAB_SIZE) |
| | } |
| |
|
| | SPECIAL_TOKENS = { |
| | |
| | '<|endoftext|>': 100257, |
| | '<|fim_prefix|>': 100258, |
| | '<|fim_middle|>': 100259, |
| | '<|fim_suffix|>': 100260, |
| | |
| | "<|system|>": 100261, |
| | "<|user|>": 100262, |
| | "<|assistant|>": 100263, |
| | |
| | "<|dummy_id_0|>": 100264, |
| | "<|dummy_id_1|>": 100265, |
| | |
| | "<|end|>": 100266, |
| | |
| | |
| | |
| | "<|dummy_id_2|>": 100256, |
| | |
| | "<|dummy_id_3|>": 100267, |
| | "<|dummy_id_4|>": 100268, |
| | "<|dummy_id_5|>": 100269, |
| | "<|dummy_id_6|>": 100270, |
| | "<|dummy_id_7|>": 100271, |
| | "<|dummy_id_8|>": 100272, |
| | "<|dummy_id_9|>": 100273, |
| | "<|dummy_id_10|>": 100274, |
| | "<|dummy_id_11|>": 100275, |
| | |
| | |
| | '<|endofprompt|>': 100276, |
| | |
| | |
| | **DUMMY_TOKENS |
| | } |
| |
|
| | class Phi3SmallTokenizer(PreTrainedTokenizer): |
| | vocab_files_names = { |
| | "vocab_file": "cl100k_base.tiktoken" |
| | } |
| |
|
| | model_input_names: List[str] = ["input_ids", "attention_mask"] |
| | padding_side = "left" |
| |
|
| | def __init__( |
| | self, |
| | vocab_file: Optional[str] = None, |
| | errors: str = "replace", |
| | **kwargs |
| | ) -> None: |
| | |
| | |
| | |
| | |
| | |
| | self.special_tokens = SPECIAL_TOKENS |
| |
|
| | super().__init__(**kwargs) |
| | self.errors = errors |
| |
|
| | try: |
| | base = tiktoken.get_encoding("cl100k_base") |
| | |
| | |
| | |
| | except requests.RequestException: |
| | import hashlib |
| | from transformers.utils import cached_file |
| | cached_tokenizer_path = cached_file( |
| | "microsoft/Phi-3-small-8k-instruct", |
| | "cl100k_base.tiktoken", |
| | _raise_exceptions_for_gated_repo=False, |
| | _raise_exceptions_for_missing_entries=False, |
| | _raise_exceptions_for_connection_errors=False |
| | ) |
| | tiktoken_cache_dir = os.path.dirname(cached_tokenizer_path) |
| | tiktoken_cache_path = os.path.join( |
| | tiktoken_cache_dir, |
| | hashlib.sha1("https://openaipublic.blob.core.windows.net/encodings/cl100k_base.tiktoken".encode()).hexdigest() |
| | ) |
| | if not os.path.exists(tiktoken_cache_path): |
| | os.rename(cached_tokenizer_path, tiktoken_cache_path) |
| | os.environ["TIKTOKEN_CACHE_DIR"] = tiktoken_cache_dir |
| | base = tiktoken.get_encoding("cl100k_base") |
| | |
| | if vocab_file is None: |
| | self.mergeable_ranks: Dict[bytes, int] = base._mergeable_ranks |
| | else: |
| | self.mergeable_ranks = _load_tiktoken_bpe(vocab_file) |
| |
|
| | self.pat_str = base._pat_str |
| | |
| | enc = tiktoken.Encoding( |
| | name="phi3small", |
| | pat_str=self.pat_str, |
| | mergeable_ranks=self.mergeable_ranks, |
| | special_tokens=self.special_tokens, |
| | ) |
| | self.tokenizer = enc |
| |
|
| | self.decoder: Dict[int, bytes] = { |
| | v: k for k, v in self.mergeable_ranks.items() |
| | } |
| | self.decoder.update({v: k for k, v in self.special_tokens.items()}) |
| | |
| | self.eod_id = self.tokenizer.eot_token |
| | self._eos_token = self._convert_id_to_token(self.eod_id) |
| |
|
| | |
| | |
| | |
| | self._bos_token = self._eos_token |
| |
|
| | |
| | self.system_id = self.special_tokens["<|system|>"] |
| | self.user_id = self.special_tokens["<|user|>"] |
| | self.assistant_id = self.special_tokens["<|assistant|>"] |
| | self.end_id = self.special_tokens["<|end|>"] |
| | |
| | @cached_property |
| | def dummy_token_indices(self) -> List[int]: |
| | |
| | |
| | additional_tokens = [ |
| | "<|fim_prefix|>", |
| | "<|fim_middle|>", |
| | "<|fim_suffix|>", |
| | "<|endofprompt|>" |
| | ] |
| | dummy_token_indices = [index for token, index in self.special_tokens.items() if "dummy_id" in token] |
| | dummy_token_indices.extend([self.special_tokens[token] for token in additional_tokens]) |
| | return sorted(dummy_token_indices) |
| |
|
| | def __getstate__(self): |
| | state = self.__dict__.copy() |
| | del state["tokenizer"] |
| | return state |
| | |
| | def __setstate__(self, state): |
| | self.__dict__ = state |
| | enc = tiktoken.Encoding( |
| | name="cl100k_im", |
| | pat_str=self.pat_str, |
| | mergeable_ranks=self.mergeable_ranks, |
| | special_tokens=self.special_tokens, |
| | ) |
| | self.tokenizer = enc |
| | |
| | def __len__(self): |
| | return self.tokenizer.n_vocab |
| | |
| | @classmethod |
| | def from_pretrained( |
| | cls, |
| | pretrained_model_name_or_path: Union[str, os.PathLike], |
| | *init_inputs, |
| | **kwargs, |
| | ): |
| | cls_kwargs = kwargs |
| | |
| | tokenization_config = get_tokenizer_config(pretrained_model_name_or_path, **kwargs) |
| | if tokenization_config: |
| | cls_kwargs = { |
| | **tokenization_config, |
| | **cls_kwargs |
| | } |
| | else: |
| | config = AutoConfig.from_pretrained(pretrained_model_name_or_path, trust_remote_code=True) |
| | cls_kwargs["model_max_length"] = config.max_position_embeddings |
| | return cls(**cls_kwargs) |
| |
|
| | def get_vocab(self) -> Dict[Union[str, bytes], int]: |
| | return {**self.mergeable_ranks, **self.special_tokens} |
| | |
| | def convert_tokens_to_ids( |
| | self, |
| | tokens: Union[bytes, str, List[Union[bytes, str]]] |
| | ) -> Union[int, List[int]]: |
| | ids = [] |
| | if isinstance(tokens, (str, bytes)): |
| | if tokens in self.special_tokens: |
| | return self.special_tokens[tokens] |
| | else: |
| | return self.mergeable_ranks.get(tokens) |
| | ids: List[int] = [] |
| | for token in tokens: |
| | ids.append(self.convert_tokens_to_ids(token)) |
| | return ids |
| |
|
| | def _add_tokens( |
| | self, |
| | new_tokens: Union[List[str], List[AddedToken]], |
| | special_tokens: bool = False, |
| | ) -> int: |
| | if not special_tokens and new_tokens: |
| | raise ValueError("Only special tokens can be added to this tokenizer") |
| | for token in new_tokens: |
| | surface_form = token.content if isinstance(token, AddedToken) else token |
| | if surface_form not in self.special_tokens: |
| | raise ValueError( |
| | "For now, we do not support unknown special tokens\n" |
| | "In the future, if there is a need for this, we can add special tokens to the tokenizer\n" |
| | "starting from rank 100261 - 100263 and then 100266 - 100275.\n" |
| | "And finally, we can re-construct the enc object back\n" |
| | ) |
| | return 0 |
| |
|
| | def save_vocabulary(self, save_directory: str, **kwargs) -> Tuple[str]: |
| | file_path = os.path.join(save_directory, "cl100k_base.tiktoken") |
| | with open(file_path, "w") as f: |
| | for token, rank in self.mergeable_ranks.items(): |
| | line = base64.b64encode(token).decode("utf-8") + " " + str(rank) + "\n" |
| | f.write(line) |
| | return (file_path,) |
| |
|
| | def tokenize( |
| | self, |
| | text: str, |
| | allowed_special: Union[Set, str] = "all", |
| | disallowed_special: Union[Collection, str] = (), |
| | **kwargs |
| | ) -> List[Union[bytes, str]]: |
| | tokens: List[Union[bytes, str]] = [] |
| | for token_id in self.tokenizer.encode( |
| | text, allowed_special=allowed_special, disallowed_special=disallowed_special |
| | ): |
| | tokens.append(self.decoder[token_id]) |
| | return tokens |
| |
|
| | def convert_tokens_to_string(self, tokens: List[Union[bytes, str]]) -> str: |
| | """ |
| | Converts a sequence of tokens in a single string. |
| | """ |
| | text = "" |
| | temp = b"" |
| | for t in tokens: |
| | if isinstance(t, str): |
| | if temp: |
| | text += temp.decode("utf-8", errors=self.errors) |
| | temp = b"" |
| | text += t |
| | elif isinstance(t, bytes): |
| | temp += t |
| | else: |
| | raise TypeError("token should only be of type types or str") |
| | if temp: |
| | text += temp.decode("utf-8", errors=self.errors) |
| | return text |
| |
|
| | @property |
| | def vocab_size(self): |
| | return self.tokenizer.n_vocab |
| |
|
| | @property |
| | def eos_token_id(self) -> int: |
| | return self.eod_id |
| |
|
| | def _convert_id_to_token(self, index: int) -> Union[bytes, str]: |
| | """Converts an id to a token, special tokens included""" |
| | if index in self.decoder: |
| | return self.decoder[index] |
| | raise ValueError("unknown ids") |
| |
|
| | def _convert_token_to_id(self, token: Union[bytes, str]) -> int: |
| | """Converts a token to an id using the vocab, special tokens included""" |
| | if token in self.special_tokens: |
| | return self.special_tokens[token] |
| | if token in self.mergeable_ranks: |
| | return self.mergeable_ranks[token] |
| | raise ValueError("unknown token") |
| |
|
| | def _tokenize(self, text: str, **kwargs): |
| | """ |
| | Converts a string in a sequence of tokens (string), using the tokenizer. Split in words for word-based |
| | vocabulary or sub-words for sub-word-based vocabularies (BPE/SentencePieces/WordPieces). |
| | Do NOT take care of added tokens. |
| | """ |
| | raise NotImplementedError |
| |
|
| | def _decode( |
| | self, |
| | token_ids: Union[int, List[int]], |
| | skip_special_tokens: bool = False, |
| | errors: str = None, |
| | **kwargs, |
| | ) -> str: |
| | if isinstance(token_ids, int): |
| | token_ids = [token_ids] |
| | if skip_special_tokens: |
| | token_ids = [i for i in token_ids if i < self.eod_id] |
| | return self.tokenizer.decode(token_ids, errors=errors or self.errors) |
| |
|
| |
|
| |
|