1bitLLM/bitnet_b1_58-large
1292.7k
1# coding=utf-82# Copyright 2022 EleutherAI and the HuggingFace Inc. team. All rights reserved.3#4# This code is based on EleutherAI's GPT-NeoX library and the GPT-NeoX5# and OPT implementations in this library. It has been modified from its6# original forms to accommodate minor architectural differences compared7# to GPT-NeoX and OPT used by the Meta AI team that trained the model.8#9# Licensed under the Apache License, Version 2.0 (the "License");10# you may not use this file except in compliance with the License.11# You may obtain a copy of the License at12#13# http://www.apache.org/licenses/LICENSE-2.014#15# Unless required by applicable law or agreed to in writing, software16# distributed under the License is distributed on an "AS IS" BASIS,17# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.18# See the License for the specific language governing permissions and19# limitations under the License.20 21"""Tokenization classes for LLaMA."""22import os23from shutil import copyfile24from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple25 26import sentencepiece as spm27 28from transformers.convert_slow_tokenizer import import_protobuf29from transformers.tokenization_utils import AddedToken, PreTrainedTokenizer30from transformers.utils import logging31 32 33if TYPE_CHECKING:34 from transformers.tokenization_utils_base import TextInput35 36logger = logging.get_logger(__name__)37 38VOCAB_FILES_NAMES = {"vocab_file": "tokenizer.model"}39 40PRETRAINED_VOCAB_FILES_MAP = {41 "vocab_file": {42 "hf-internal-testing/llama-tokenizer": "https://huggingface.co/hf-internal-testing/llama-tokenizer/resolve/main/tokenizer.model",43 },44 "tokenizer_file": {45 "hf-internal-testing/llama-tokenizer": "https://huggingface.co/hf-internal-testing/llama-tokenizer/resolve/main/tokenizer_config.json",46 },47}48PRETRAINED_POSITIONAL_EMBEDDINGS_SIZES = {49 "hf-internal-testing/llama-tokenizer": 2048,50}51SPIECE_UNDERLINE = "โ"52 53B_INST, E_INST = "[INST]", "[/INST]"54B_SYS, E_SYS = "<<SYS>>\n", "\n<</SYS>>\n\n"55 56# fmt: off57DEFAULT_SYSTEM_PROMPT = """You are a helpful, respectful and honest assistant. Always answer as helpfully as possible, while being safe. Your \58answers should not include any harmful, unethical, racist, sexist, toxic, dangerous, or illegal content. Please ensure\59 that your responses are socially unbiased and positive in nature.60 61If a question does not make any sense, or is not factually coherent, explain why instead of answering something not \62correct. If you don't know the answer to a question, please don't share false information."""63# fmt: on64 65 66class BitnetTokenizer(PreTrainedTokenizer):67 """68 Construct a Bitnet tokenizer. Based on byte-level Byte-Pair-Encoding. The default padding token is unset as there is69 no padding token in the original model.70 71 Args:72 vocab_file (`str`):73 Path to the vocabulary file.74 unk_token (`str` or `tokenizers.AddedToken`, *optional*, defaults to `"<unk>"`):75 The unknown token. A token that is not in the vocabulary cannot be converted to an ID and is set to be this76 token instead.77 bos_token (`str` or `tokenizers.AddedToken`, *optional*, defaults to `"<s>"`):78 The beginning of sequence token that was used during pretraining. Can be used a sequence classifier token.79 eos_token (`str` or `tokenizers.AddedToken`, *optional*, defaults to `"</s>"`):80 The end of sequence token.81 pad_token (`str` or `tokenizers.AddedToken`, *optional*):82 A special token used to make arrays of tokens the same size for batching purpose. Will then be ignored by83 attention mechanisms or loss computation.84 sp_model_kwargs (`Dict[str, Any]`, `Optional`, *optional*):85 Will be passed to the `SentencePieceProcessor.__init__()` method. The [Python wrapper for86 SentencePiece](https://github.com/google/sentencepiece/tree/master/python) can be used, among other things,87 to set:88 89 - `enable_sampling`: Enable subword regularization.90 - `nbest_size`: Sampling parameters for unigram. Invalid for BPE-Dropout.91 92 - `nbest_size = {0,1}`: No sampling is performed.93 - `nbest_size > 1`: samples from the nbest_size results.94 - `nbest_size < 0`: assuming that nbest_size is infinite and samples from the all hypothesis (lattice)95 using forward-filtering-and-backward-sampling algorithm.96 97 - `alpha`: Smoothing parameter for unigram sampling, and dropout probability of merge operations for98 BPE-dropout.99 100 add_bos_token (`bool`, *optional*, defaults to `True`):101 Whether or not to add an `bos_token` at the start of sequences.102 add_eos_token (`bool`, *optional*, defaults to `False`):103 Whether or not to add an `eos_token` at the end of sequences.104 clean_up_tokenization_spaces (`bool`, *optional*, defaults to `False`):105 Whether or not to cleanup spaces after decoding, cleanup consists in removing potential artifacts like106 extra spaces.107 use_default_system_prompt (`bool`, *optional*, defaults to `False`):108 Whether or not the default system prompt for Bitnet should be used.109 spaces_between_special_tokens (`bool`, *optional*, defaults to `False`):110 Whether or not to add spaces between special tokens.111 legacy (`bool`, *optional*):112 Whether or not the `legacy` behavior of the tokenizer should be used. Legacy is before the merge of #24622113 and #25224 which includes fixes to properly handle tokens that appear after special tokens. A simple114 example:115 116 - `legacy=True`:117 ```python118 >>> from transformers import T5Tokenizer119 120 >>> tokenizer = T5Tokenizer.from_pretrained("google-t5/t5-base", legacy=True)121 >>> tokenizer.encode("Hello <extra_id_0>.")122 [8774, 32099, 3, 5, 1]123 ```124 - `legacy=False`:125 ```python126 >>> from transformers import T5Tokenizer127 128 >>> tokenizer = T5Tokenizer.from_pretrained("google-t5/t5-base", legacy=False)129 >>> tokenizer.encode("Hello <extra_id_0>.") # the extra space `[3]` is no longer here130 [8774, 32099, 5, 1]131 ```132 Checkout the [pull request](https://github.com/huggingface/transformers/pull/24565) for more details.133 add_prefix_space (`bool`, *optional*, defaults to `True`):134 Whether or not to add an initial space to the input. This allows to treat the leading word just as any135 other word.136 137 """138 139 vocab_files_names = VOCAB_FILES_NAMES140 pretrained_vocab_files_map = PRETRAINED_VOCAB_FILES_MAP141 max_model_input_sizes = PRETRAINED_POSITIONAL_EMBEDDINGS_SIZES142 model_input_names = ["input_ids", "attention_mask"]143 144 def __init__(145 self,146 vocab_file,147 unk_token="<unk>",148 bos_token="<s>",149 eos_token="</s>",150 pad_token=None,151 sp_model_kwargs: Optional[Dict[str, Any]] = None,152 add_bos_token=True,153 add_eos_token=False,154 clean_up_tokenization_spaces=False,155 use_default_system_prompt=False,156 spaces_between_special_tokens=False,157 legacy=None,158 add_prefix_space=True,159 **kwargs,160 ):161 self.sp_model_kwargs = {} if sp_model_kwargs is None else sp_model_kwargs162 bos_token = AddedToken(bos_token, normalized=False, special=True) if isinstance(bos_token, str) else bos_token163 eos_token = AddedToken(eos_token, normalized=False, special=True) if isinstance(eos_token, str) else eos_token164 unk_token = AddedToken(unk_token, normalized=False, special=True) if isinstance(unk_token, str) else unk_token165 pad_token = AddedToken(pad_token, normalized=False, special=True) if isinstance(pad_token, str) else pad_token166 167 if legacy is None:168 logger.warning_once(169 f"You are using the default legacy behaviour of the {self.__class__}. This is"170 " expected, and simply means that the `legacy` (previous) behavior will be used so nothing changes for you."171 " If you want to use the new behaviour, set `legacy=False`. This should only be set if you understand what it"172 " means, and thoroughly read the reason why this was added as explained in"173 " https://github.com/huggingface/transformers/pull/24565"174 )175 legacy = True176 177 self.legacy = legacy178 self.vocab_file = vocab_file179 self.add_bos_token = add_bos_token180 self.add_eos_token = add_eos_token181 self.use_default_system_prompt = use_default_system_prompt182 self.sp_model = self.get_spm_processor(kwargs.pop("from_slow", False))183 self.add_prefix_space = add_prefix_space184 185 super().__init__(186 bos_token=bos_token,187 eos_token=eos_token,188 unk_token=unk_token,189 pad_token=pad_token,190 add_bos_token=add_bos_token,191 add_eos_token=add_eos_token,192 sp_model_kwargs=self.sp_model_kwargs,193 clean_up_tokenization_spaces=clean_up_tokenization_spaces,194 use_default_system_prompt=use_default_system_prompt,195 spaces_between_special_tokens=spaces_between_special_tokens,196 legacy=legacy,197 add_prefix_space=add_prefix_space,198 **kwargs,199 )200 201 @property202 def unk_token_length(self):203 return len(self.sp_model.encode(str(self.unk_token)))204 205 # Copied from transformers.models.t5.tokenization_t5.T5Tokenizer.get_spm_processor206 def get_spm_processor(self, from_slow=False):207 tokenizer = spm.SentencePieceProcessor(**self.sp_model_kwargs)208 if self.legacy or from_slow: # no dependency on protobuf209 tokenizer.Load(self.vocab_file)210 return tokenizer211 212 with open(self.vocab_file, "rb") as f:213 sp_model = f.read()214 model_pb2 = import_protobuf(f"The new behaviour of {self.__class__.__name__} (with `self.legacy = False`)")215 model = model_pb2.ModelProto.FromString(sp_model)216 normalizer_spec = model_pb2.NormalizerSpec()217 normalizer_spec.add_dummy_prefix = False218 model.normalizer_spec.MergeFrom(normalizer_spec)219 sp_model = model.SerializeToString()220 tokenizer.LoadFromSerializedProto(sp_model)221 return tokenizer222 223 def __getstate__(self):224 state = self.__dict__.copy()225 state["sp_model"] = None226 state["sp_model_proto"] = self.sp_model.serialized_model_proto()227 return state228 229 def __setstate__(self, d):230 self.__dict__ = d231 self.sp_model = spm.SentencePieceProcessor(**self.sp_model_kwargs)232 self.sp_model.LoadFromSerializedProto(self.sp_model_proto)233 234 @property235 def vocab_size(self):236 """Returns vocab size"""237 return self.sp_model.get_piece_size()238 239 def get_vocab(self):240 """Returns vocab as a dict"""241 vocab = {self.convert_ids_to_tokens(i): i for i in range(self.vocab_size)}242 vocab.update(self.added_tokens_encoder)243 return vocab244 245 # Copied from transformers.models.t5.tokenization_t5.T5Tokenizer.tokenize246 def tokenize(self, text: "TextInput", **kwargs) -> List[str]:247 """248 Converts a string to a list of tokens. If `self.legacy` is set to `False`, a prefix token is added unless the249 first token is special.250 """251 if self.legacy or len(text) == 0:252 return super().tokenize(text, **kwargs)253 254 text = text.replace(SPIECE_UNDERLINE, " ")255 if self.add_prefix_space:256 text = SPIECE_UNDERLINE + text257 258 tokens = super().tokenize(text, **kwargs)259 260 if len(tokens) > 1 and tokens[0] == SPIECE_UNDERLINE and tokens[1] in self.all_special_tokens:261 tokens = tokens[1:]262 return tokens263 264 # Copied from transformers.models.t5.tokenization_t5.T5Tokenizer._tokenize265 def _tokenize(self, text, **kwargs):266 """267 Returns a tokenized string.268 269 We de-activated the `add_dummy_prefix` option, thus the sentencepiece internals will always strip any270 SPIECE_UNDERLINE. For example: `self.sp_model.encode(f"{SPIECE_UNDERLINE}Hey", out_type = str)` will give271 `['H', 'e', 'y']` instead of `['โHe', 'y']`. Thus we always encode `f"{unk_token}text"` and strip the272 `unk_token`. Here is an example with `unk_token = "<unk>"` and `unk_token_length = 4`.273 `self.tokenizer.sp_model.encode("<unk> Hey", out_type = str)[4:]`.274 """275 tokens = self.sp_model.encode(text, out_type=str)276 if self.legacy or not text.startswith((SPIECE_UNDERLINE, " ")):277 return tokens278 279 # 1. Encode string + prefix ex: "<unk> Hey"280 tokens = self.sp_model.encode(self.unk_token + text, out_type=str)281 # 2. Remove self.unk_token from ['<','unk','>', 'โHey']282 return tokens[self.unk_token_length :] if len(tokens) >= self.unk_token_length else tokens283 284 def _convert_token_to_id(self, token):285 """Converts a token (str) in an id using the vocab."""286 return self.sp_model.piece_to_id(token)287 288 def _convert_id_to_token(self, index):289 """Converts an index (integer) in a token (str) using the vocab."""290 token = self.sp_model.IdToPiece(index)291 return token292 293 def convert_tokens_to_string(self, tokens):294 """Converts a sequence of tokens (string) in a single string."""295 # since we manually add the prefix space, we have to remove it when decoding296 if tokens[0].startswith(SPIECE_UNDERLINE) and self.add_prefix_space:297 tokens[0] = tokens[0][1:]298 299 current_sub_tokens = []300 out_string = ""301 prev_is_special = False302 for i, token in enumerate(tokens):303 # make sure that special tokens are not decoded using sentencepiece model304 if token in self.all_special_tokens:305 if not prev_is_special and i != 0 and self.legacy:306 out_string += " "307 out_string += self.sp_model.decode(current_sub_tokens) + token308 prev_is_special = True309 current_sub_tokens = []310 else:311 current_sub_tokens.append(token)312 prev_is_special = False313 out_string += self.sp_model.decode(current_sub_tokens)314 return out_string315 316 def save_vocabulary(self, save_directory, filename_prefix: Optional[str] = None) -> Tuple[str]:317 """318 Save the vocabulary and special tokens file to a directory.319 320 Args:321 save_directory (`str`):322 The directory in which to save the vocabulary.323 324 Returns:325 `Tuple(str)`: Paths to the files saved.326 """327 if not os.path.isdir(save_directory):328 logger.error(f"Vocabulary path ({save_directory}) should be a directory")329 return330 out_vocab_file = os.path.join(331 save_directory, (filename_prefix + "-" if filename_prefix else "") + VOCAB_FILES_NAMES["vocab_file"]332 )333 334 if os.path.abspath(self.vocab_file) != os.path.abspath(out_vocab_file) and os.path.isfile(self.vocab_file):335 copyfile(self.vocab_file, out_vocab_file)336 elif not os.path.isfile(self.vocab_file):337 with open(out_vocab_file, "wb") as fi:338 content_spiece_model = self.sp_model.serialized_model_proto()339 fi.write(content_spiece_model)340 341 return (out_vocab_file,)342 343 def build_inputs_with_special_tokens(self, token_ids_0, token_ids_1=None):344 bos_token_id = [self.bos_token_id] if self.add_bos_token else []345 eos_token_id = [self.eos_token_id] if self.add_eos_token else []346 347 output = bos_token_id + token_ids_0 + eos_token_id348 349 if token_ids_1 is not None:350 output = output + bos_token_id + token_ids_1 + eos_token_id351 352 return output353 354 def get_special_tokens_mask(355 self, token_ids_0: List[int], token_ids_1: Optional[List[int]] = None, already_has_special_tokens: bool = False356 ) -> List[int]:357 """358 Retrieve sequence ids from a token list that has no special tokens added. This method is called when adding359 special tokens using the tokenizer `prepare_for_model` method.360 361 Args:362 token_ids_0 (`List[int]`):363 List of IDs.364 token_ids_1 (`List[int]`, *optional*):365 Optional second list of IDs for sequence pairs.366 already_has_special_tokens (`bool`, *optional*, defaults to `False`):367 Whether or not the token list is already formatted with special tokens for the model.368 369 Returns:370 `List[int]`: A list of integers in the range [0, 1]: 1 for a special token, 0 for a sequence token.371 """372 if already_has_special_tokens:373 return super().get_special_tokens_mask(374 token_ids_0=token_ids_0, token_ids_1=token_ids_1, already_has_special_tokens=True375 )376 377 bos_token_id = [1] if self.add_bos_token else []378 eos_token_id = [1] if self.add_eos_token else []379 380 if token_ids_1 is None:381 return bos_token_id + ([0] * len(token_ids_0)) + eos_token_id382 return (383 bos_token_id384 + ([0] * len(token_ids_0))385 + eos_token_id386 + bos_token_id387 + ([0] * len(token_ids_1))388 + eos_token_id389 )390 391 def create_token_type_ids_from_sequences(392 self, token_ids_0: List[int], token_ids_1: Optional[List[int]] = None393 ) -> List[int]:394 """395 Creates a mask from the two sequences passed to be used in a sequence-pair classification task. An ALBERT396 sequence pair mask has the following format:397 398 ```399 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1400 | first sequence | second sequence |401 ```402 403 if token_ids_1 is None, only returns the first portion of the mask (0s).404 405 Args:406 token_ids_0 (`List[int]`):407 List of ids.408 token_ids_1 (`List[int]`, *optional*):409 Optional second list of IDs for sequence pairs.410 411 Returns:412 `List[int]`: List of [token type IDs](../glossary#token-type-ids) according to the given sequence(s).413 """414 bos_token_id = [self.bos_token_id] if self.add_bos_token else []415 eos_token_id = [self.eos_token_id] if self.add_eos_token else []416 417 output = [0] * len(bos_token_id + token_ids_0 + eos_token_id)418 419 if token_ids_1 is not None:420 output += [1] * len(bos_token_id + token_ids_1 + eos_token_id)421 422 return output423 424 @property425 def default_chat_template(self):426 """427 LLaMA uses [INST] and [/INST] to indicate user messages, and <<SYS>> and <</SYS>> to indicate system messages.428 Assistant messages do not have special tokens, because LLaMA chat models are generally trained with strict429 user/assistant/user/assistant message ordering, and so assistant messages can be identified from the ordering430 rather than needing special tokens. The system message is partly 'embedded' in the first user message, which431 results in an unusual token ordering when it is present. This template should definitely be changed if you wish432 to fine-tune a model with more flexible role ordering!433 434 The output should look something like:435 436 <bos>[INST] B_SYS SystemPrompt E_SYS Prompt [/INST] Answer <eos><bos>[INST] Prompt [/INST] Answer <eos>437 <bos>[INST] Prompt [/INST]438 439 The reference for this chat template is [this code440 snippet](https://github.com/facebookresearch/llama/blob/556949fdfb72da27c2f4a40b7f0e4cf0b8153a28/llama/generation.py#L320-L362)441 in the original repository.442 """443 logger.warning_once(444 "\nNo chat template is defined for this tokenizer - using the default template "445 f"for the {self.__class__.__name__} class. If the default is not appropriate for "446 "your model, please set `tokenizer.chat_template` to an appropriate template. "447 "See https://huggingface.co/docs/transformers/main/chat_templating for more information.\n"448 )449 template = (450 "{% if messages[0]['role'] == 'system' %}"451 "{% set loop_messages = messages[1:] %}" # Extract system message if it's present452 "{% set system_message = messages[0]['content'] %}"453 "{% elif USE_DEFAULT_PROMPT == true and not '<<SYS>>' in messages[0]['content'] %}"454 "{% set loop_messages = messages %}" # Or use the default system message if the flag is set455 "{% set system_message = 'DEFAULT_SYSTEM_MESSAGE' %}"456 "{% else %}"457 "{% set loop_messages = messages %}"458 "{% set system_message = false %}"459 "{% endif %}"460 "{% for message in loop_messages %}" # Loop over all non-system messages461 "{% if (message['role'] == 'user') != (loop.index0 % 2 == 0) %}"462 "{{ raise_exception('Conversation roles must alternate user/assistant/user/assistant/...') }}"463 "{% endif %}"464 "{% if loop.index0 == 0 and system_message != false %}" # Embed system message in first message465 "{% set content = '<<SYS>>\\n' + system_message + '\\n<</SYS>>\\n\\n' + message['content'] %}"466 "{% else %}"467 "{% set content = message['content'] %}"468 "{% endif %}"469 "{% if message['role'] == 'user' %}" # After all of that, handle messages/roles in a fairly normal way470 "{{ bos_token + '[INST] ' + content.strip() + ' [/INST]' }}"471 "{% elif message['role'] == 'system' %}"472 "{{ '<<SYS>>\\n' + content.strip() + '\\n<</SYS>>\\n\\n' }}"473 "{% elif message['role'] == 'assistant' %}"474 "{{ ' ' + content.strip() + ' ' + eos_token }}"475 "{% endif %}"476 "{% endfor %}"477 )478 template = template.replace("USE_DEFAULT_PROMPT", "true" if self.use_default_system_prompt else "false")479 default_message = DEFAULT_SYSTEM_PROMPT.replace("\n", "\\n").replace("'", "\\'")480 template = template.replace("DEFAULT_SYSTEM_MESSAGE", default_message)481 482 return template