woodchen7 commited on
Commit
a4749d6
1 Parent(s): 7e691d8

Upload tokenization_hy.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. tokenization_hy.py +354 -0
tokenization_hy.py ADDED
@@ -0,0 +1,354 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding=utf-8
2
+ # Copyright 2024 The Tencent Inc. HunYuan Team.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ import os
16
+ import base64
17
+ import logging
18
+ import tiktoken
19
+ import unicodedata
20
+ from transformers import PreTrainedTokenizer, AddedToken
21
+ from typing import Collection, Dict, List, Set, Tuple, Union
22
+
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ VOCAB_FILES_NAMES = {"vocab_file": "hy.tiktoken"}
28
+ PAT_STR = r"""(?i:'s|'t|'re|'ve|'m|'ll|'d)|""" \
29
+ r"""[^\r\n\p{L}\p{N}]?\p{L}+|""" \
30
+ r"""\p{N}|""" \
31
+ r""" ?[^\s\p{L}\p{N}]+[\r\n]*|""" \
32
+ r"""\s*[\r\n]+|""" \
33
+ r"""\s+(?!\S)|""" \
34
+ r"""\s+"""
35
+ # default eod_token and bod_token of our base model
36
+ ENDOFTEXT = "<|endoftext|>"
37
+ STARTOFTEXT = "<|startoftext|>"
38
+
39
+ # extra flag token for other training
40
+ BOSTOKEN = "<|bos|>"
41
+ EOSTOKEN = "<|eos|>"
42
+
43
+ PADTOKEN = "<|pad|>"
44
+
45
+ # extra special tokens for the tokenizer
46
+ # as the default behavior is changed to allow special tokens in
47
+ # regular texts, the surface forms of special tokens need to be
48
+ # as different as possible to minimize the impact
49
+ EXTRAS = tuple((f"<|extra_{i}|>" for i in range(204)))
50
+
51
+ SPECIAL_START_ID = 127957
52
+
53
+
54
+ def _load_tiktoken_bpe(tiktoken_bpe_file: str) -> Dict[bytes, int]:
55
+ dic = {}
56
+ rank = 0
57
+ for i, line in enumerate(open(tiktoken_bpe_file, "rb")):
58
+ if line:
59
+ token, _ = line.split()
60
+ # skip duplicated tokens, this should not happen
61
+ if base64.b64decode(token) in dic:
62
+ raise ValueError(f"!ERROR: duplicated token {token} in your vocab file")
63
+ dic[base64.b64decode(token)] = int(rank)
64
+ rank += 1
65
+ return dic
66
+
67
+
68
+ # special tokens for pretrain and finetune models
69
+ SPECIAL_TOKENS = tuple(
70
+ enumerate(
71
+ (
72
+ (
73
+ ENDOFTEXT,
74
+ STARTOFTEXT,
75
+ BOSTOKEN,
76
+ EOSTOKEN,
77
+ PADTOKEN,
78
+ )
79
+ + EXTRAS
80
+ ),
81
+ start=SPECIAL_START_ID,
82
+ )
83
+ )
84
+
85
+ SPECIAL_TOKENS_SET = set(t for i, t in SPECIAL_TOKENS)
86
+
87
+
88
+ class HYTokenizer(PreTrainedTokenizer):
89
+ """
90
+ HunYuan Tokenizer Initialization. We extend `tiktoken` vocab and
91
+ the default EOD & BOD special tokens are used for base model.
92
+
93
+ Args:
94
+ vocab_file (`str`):
95
+ Path to the vocabulary file.
96
+
97
+ errors (`str`):
98
+ How to handle errors in decoding UTF-8 byte sequences.
99
+ use ignore if you are in streaming inference
100
+
101
+ bod_token (`str` or `tokenizers.AddedToken`, *optional*, defaults to `""<|startoftext|>""`):
102
+ The beginning of document token that was used for training. can be modified by your task.
103
+ default to be `<|startoftext|>` for released base model.
104
+
105
+ eod_token (`str` or `tokenizers.AddedToken`, *optional*, defaults to `""<|endoftext|>""`):
106
+ The end of document token that was used for training. can be modified by your task.
107
+ default to be `<|endoftext|>` for released base model.
108
+
109
+ bos_token (`str` or `tokenizers.AddedToken`, *optional*, defaults to `None`):
110
+ The start or sep special token that was used for some training tasks.
111
+ default to be `<|startoftext|>` for released base model.
112
+ It can be set to `<|bos|>` when you training for some other task
113
+
114
+ eos_token (`str` or `tokenizers.AddedToken`, *optional*, defaults to `None`):
115
+ The end or sep special token that was used for some training tasks.
116
+ default to be `<|endoftext|>` for released base model.
117
+ It can be set to `<|eos|>` when you training for some other task
118
+
119
+ pad_token (`str` or `tokenizers.AddedToken`, *optional*):
120
+ A special token used to make arrays of tokens the same size for batching purpose. Will then be ignored by
121
+ attention mechanisms or loss computation.
122
+
123
+ special_vocab_file (str, *optional*):
124
+ Customed special extra vocab file, same format with hy.tiktoken.
125
+ **Be careful** to use the extra special vocab, it will may cause the model loading collapse.
126
+ The data line be like:
127
+ `PHxhYmN8Pg== 0`
128
+ the id followed `base64.encode(str)` is unused, we will reset them in case of collision
129
+
130
+ add_bod_token (`bool`, *optional*, defaults to `True`):
131
+ Whether or not to add an `bos_token` at the start of documents.
132
+ This will effect `build_inputs_with_special_tokens` method
133
+
134
+ add_eod_token (`bool`, *optional*, defaults to `False`):
135
+ Whether or not to add an `eos_token` at the end of documents.
136
+ This will effect `build_inputs_with_special_tokens` method
137
+
138
+ """
139
+ vocab_files_names = VOCAB_FILES_NAMES
140
+
141
+ def __init__(
142
+ self,
143
+ vocab_file,
144
+ errors="replace",
145
+ bod_token="<|startoftext|>",
146
+ eod_token="<|endoftext|>",
147
+ bos_token="<|startoftext|>",
148
+ eos_token="<|endoftext|>",
149
+ pad_token="<|pad|>",
150
+ add_bod_token=True,
151
+ add_eod_token=True,
152
+ **kwargs,
153
+ ):
154
+ super().__init__(**kwargs)
155
+
156
+ self.errors = errors
157
+ self.mergeable_ranks = _load_tiktoken_bpe(vocab_file) # type: Dict[bytes, int]
158
+ self.special_tokens = {
159
+ token: index
160
+ for index, token in SPECIAL_TOKENS
161
+ }
162
+
163
+ enc = tiktoken.Encoding(
164
+ "HunYuan",
165
+ pat_str=PAT_STR,
166
+ mergeable_ranks=self.mergeable_ranks,
167
+ special_tokens=self.special_tokens,
168
+ )
169
+ assert (
170
+ len(self.mergeable_ranks) + len(self.special_tokens) == enc.n_vocab
171
+ ), f"{len(self.mergeable_ranks)} + {len(self.special_tokens)} != {enc.n_vocab} in encoding"
172
+
173
+ self.decoder = {
174
+ v: k for k, v in self.mergeable_ranks.items()
175
+ } # type: dict[int, bytes|str]
176
+ self.decoder.update({v: k for k, v in self.special_tokens.items()})
177
+
178
+ self.tokenizer = enc
179
+
180
+ self.bod_token, self.bod_id = bod_token, self.special_tokens[bod_token]
181
+ self.eod_token, self.eod_id = eod_token, self.special_tokens[eod_token]
182
+ self.bos_token, self.bos_id = bos_token, self.special_tokens[bos_token]
183
+ self.eos_token, self.eos_id = eos_token, self.special_tokens[eos_token]
184
+ self.pad_token, self.pad_id = pad_token, self.special_tokens[pad_token]
185
+
186
+ self._num_special_token = len(self.special_tokens)
187
+
188
+ self.add_bod_token = add_bod_token
189
+ self.add_eod_token = add_eod_token
190
+
191
+ def __getstate__(self):
192
+ state = self.__dict__.copy()
193
+ del state["tokenizer"]
194
+ return state
195
+
196
+ def __setstate__(self, state):
197
+ self.__dict__.update(state)
198
+ enc = tiktoken.Encoding(
199
+ "HunYuan",
200
+ pat_str=PAT_STR,
201
+ mergeable_ranks=self.mergeable_ranks,
202
+ special_tokens=self.special_tokens,
203
+ )
204
+ self.tokenizer = enc
205
+
206
+ def __len__(self) -> int:
207
+ return self.tokenizer.n_vocab
208
+
209
+ def get_vocab(self) -> Dict[bytes, int]:
210
+ """Return the vocabulary as a dictionary, without special tokens."""
211
+ return self.mergeable_ranks
212
+
213
+ def convert_tokens_to_ids(
214
+ self, tokens: Union[bytes, str, List[Union[bytes, str]]]
215
+ ) -> List[int]:
216
+ ids = []
217
+ if isinstance(tokens, (str, bytes)):
218
+ if tokens in self.special_tokens:
219
+ return self.special_tokens[tokens]
220
+ else:
221
+ return self.mergeable_ranks.get(tokens)
222
+ for token in tokens:
223
+ if token in self.special_tokens:
224
+ ids.append(self.special_tokens[token])
225
+ else:
226
+ ids.append(self.mergeable_ranks.get(token))
227
+ return ids
228
+
229
+ def build_inputs_with_special_tokens(self, token_ids_0, token_ids_1=None):
230
+ bod_token_id = [self.bod_id] if self.add_bod_token else []
231
+ eod_token_id = [self.eod_id] if self.add_eod_token else []
232
+ output = bod_token_id + token_ids_0 + eod_token_id
233
+ if token_ids_1 is not None:
234
+ output = output + bod_token_id + token_ids_1 + eod_token_id
235
+ return output
236
+
237
+ def _add_tokens(
238
+ self,
239
+ new_tokens: Union[List[str], List[AddedToken]],
240
+ special_tokens: bool = False,
241
+ ) -> List[Tuple[int, str]]:
242
+ """do not support adding tokens"""
243
+ if not special_tokens and new_tokens:
244
+ raise ValueError("Adding regular tokens is not supported")
245
+ for token in new_tokens:
246
+ surface_form = token.content if isinstance(token, AddedToken) else token
247
+ if surface_form not in SPECIAL_TOKENS_SET:
248
+ raise ValueError("Adding unknown special tokens is not supported")
249
+ return 0
250
+
251
+ def save_vocabulary(self, save_directory: str, **kwargs) -> Tuple[str]:
252
+ """
253
+ Save only the vocabulary of the tokenizer (vocabulary).
254
+ Returns:
255
+ `Tuple(str)`: Paths to the files saved.
256
+ """
257
+ file_path = os.path.join(save_directory, "hy.tiktoken")
258
+ with open(file_path, "w", encoding="utf8") as w:
259
+ for k, v in self.mergeable_ranks.items():
260
+ line = base64.b64encode(k).decode("utf8") + " " + str(v) + "\n"
261
+ w.write(line)
262
+ return (file_path,)
263
+
264
+ def tokenize(
265
+ self,
266
+ text: str,
267
+ allowed_special: Union[Set, str] = "all",
268
+ disallowed_special: Union[Collection, str] = (),
269
+ **kwargs,
270
+ ) -> List[Union[bytes, str]]:
271
+ """
272
+ Converts a string in a sequence of tokens.
273
+ Args:
274
+ text (`str`):
275
+ The sequence to be encoded.
276
+ allowed_special (`Literal["all"]` or `set`):
277
+ The surface forms of the tokens to be encoded as special tokens in regular texts.
278
+ Default to "all".
279
+ disallowed_special (`Literal["all"]` or `Collection`):
280
+ The surface forms of the tokens that should not be in regular texts and trigger errors.
281
+ Default to an empty tuple.
282
+ kwargs (additional keyword arguments, *optional*):
283
+ Will be passed to the underlying model specific encode method.
284
+ Returns:
285
+ `List[bytes|str]`: The list of tokens.
286
+ """
287
+ tokens = []
288
+ text = unicodedata.normalize("NFC", text)
289
+
290
+ # this implementation takes a detour: text -> token id -> token surface forms
291
+ for t in self.tokenizer.encode(
292
+ text, allowed_special=allowed_special, disallowed_special=disallowed_special
293
+ ):
294
+ tokens.append(self.decoder[t])
295
+ return tokens
296
+
297
+ def convert_tokens_to_string(self, tokens: List[Union[bytes, str]]) -> str:
298
+ """
299
+ Converts a sequence of tokens in a single string.
300
+ """
301
+ text = ""
302
+ temp = b""
303
+ for t in tokens:
304
+ if isinstance(t, str):
305
+ if temp:
306
+ text += temp.decode("utf-8", errors=self.errors)
307
+ temp = b""
308
+ text += t
309
+ elif isinstance(t, bytes):
310
+ temp += t
311
+ else:
312
+ raise TypeError("token should only be of type types or str")
313
+ if temp:
314
+ text += temp.decode("utf-8", errors=self.errors)
315
+ return text
316
+
317
+ @property
318
+ def vocab_size(self):
319
+ return self.tokenizer.n_vocab
320
+
321
+ def _convert_id_to_token(self, index: int) -> Union[bytes, str]:
322
+ """Converts an id to a token, special tokens included"""
323
+ if index in self.decoder:
324
+ return self.decoder[index]
325
+ raise ValueError("unknown ids")
326
+
327
+ def _convert_token_to_id(self, token: Union[bytes, str]) -> int:
328
+ """Converts a token to an id using the vocab, special tokens included"""
329
+ if token in self.special_tokens:
330
+ return self.special_tokens[token]
331
+ if token in self.mergeable_ranks:
332
+ return self.mergeable_ranks[token]
333
+ raise ValueError("unknown token")
334
+
335
+ def _tokenize(self, text: str, **kwargs):
336
+ """
337
+ Converts a string in a sequence of tokens (string), using the tokenizer. Split in words for word-based
338
+ vocabulary or sub-words for sub-word-based vocabularies (BPE/SentencePieces/WordPieces).
339
+ Do NOT take care of added tokens.
340
+ """
341
+ raise NotImplementedError
342
+
343
+ def _decode(
344
+ self,
345
+ token_ids: Union[int, List[int]],
346
+ skip_special_tokens: bool = False,
347
+ errors: str = None,
348
+ **kwargs,
349
+ ) -> str:
350
+ if isinstance(token_ids, int):
351
+ token_ids = [token_ids]
352
+ if skip_special_tokens:
353
+ token_ids = [i for i in token_ids if i < self.eod_id]
354
+ return self.tokenizer.decode(token_ids, errors=errors or self.errors)