File size: 8,129 Bytes
8e1984a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
import os
import shutil
from typing import List, Optional, Tuple

import sentencepiece as spm
from transformers import PreTrainedTokenizer


class UnModelTokenizer(PreTrainedTokenizer):
    """SentencePiece tokenizer for the SAGE POND UnModel encoder.

    The special-token arguments are explicit constructor parameters. This is
    important for Transformers releases whose PythonBackend receives tokenizer
    configuration values through ``from_pretrained``: values such as
    ``bos_token`` must not also remain in ``kwargs`` when passed to ``super``.
    """

    vocab_files_names = {"vocab_file": "new_bpe.model"}
    model_input_names = ["input_ids", "attention_mask"]

    def __init__(
        self,
        vocab_file,
        add_bos_token=False,
        add_eos_token=False,
        model_max_length=600,
        pad_token=None,
        bos_token=None,
        eos_token=None,
        unk_token=None,
        clean_up_tokenization_spaces=False,
        **kwargs,
    ):
        if not vocab_file or not os.path.isfile(vocab_file):
            raise FileNotFoundError(
                "The tokenizer requires the SentencePiece model file 'new_bpe.model'."
            )

        self.vocab_file = os.path.abspath(vocab_file)
        self.sp_model = spm.SentencePieceProcessor(model_file=self.vocab_file)
        self.add_bos_token = bool(add_bos_token)
        self.add_eos_token = bool(add_eos_token)

        vocab_size = self.sp_model.get_piece_size()
        if vocab_size != 40_000:
            raise ValueError(
                f"Expected a 40,000-piece SentencePiece model, got {vocab_size:,}."
            )

        def special_id(method_name, fallback):
            value = getattr(self.sp_model, method_name)()
            return value if value >= 0 else fallback

        # These fallbacks match the IDs used by the original training script.
        self._sp_pad_id = special_id("pad_id", 0)
        self._sp_bos_id = special_id("bos_id", 2)
        self._sp_eos_id = special_id("eos_id", 3)
        self._sp_unk_id = special_id("unk_id", 1)

        self._sp_special_tokens = {
            "pad_token": self.sp_model.id_to_piece(self._sp_pad_id),
            "bos_token": self.sp_model.id_to_piece(self._sp_bos_id),
            "eos_token": self.sp_model.id_to_piece(self._sp_eos_id),
            "unk_token": self.sp_model.id_to_piece(self._sp_unk_id),
        }

        for token_name, token_id in (
            ("pad_token", self._sp_pad_id),
            ("bos_token", self._sp_bos_id),
            ("eos_token", self._sp_eos_id),
            ("unk_token", self._sp_unk_id),
        ):
            if token_id < 0 or token_id >= vocab_size:
                raise ValueError(
                    f"{token_name} ID {token_id} is outside vocabulary size {vocab_size}."
                )

        # Validate values restored from tokenizer_config.json against the
        # SentencePiece model. Keeping the same strings guarantees that token
        # IDs remain aligned with the model's embedding table.
        supplied_tokens = {
            "pad_token": pad_token,
            "bos_token": bos_token,
            "eos_token": eos_token,
            "unk_token": unk_token,
        }
        resolved_tokens = {}
        for name, supplied in supplied_tokens.items():
            expected = self._sp_special_tokens[name]
            if supplied is None:
                resolved_tokens[name] = expected
                continue

            supplied_content = getattr(supplied, "content", str(supplied))
            if supplied_content != expected:
                raise ValueError(
                    f"Configured {name}={supplied_content!r} does not match the "
                    f"SentencePiece token {expected!r}. The tokenizer config and "
                    "new_bpe.model must come from the same training run."
                )
            resolved_tokens[name] = supplied

        super().__init__(
            pad_token=resolved_tokens["pad_token"],
            bos_token=resolved_tokens["bos_token"],
            eos_token=resolved_tokens["eos_token"],
            unk_token=resolved_tokens["unk_token"],
            model_max_length=model_max_length,
            clean_up_tokenization_spaces=clean_up_tokenization_spaces,
            **kwargs,
        )

        # Ensure save_pretrained writes the dynamic AutoTokenizer mapping and
        # the options needed when the tokenizer is reconstructed from the Hub.
        self.init_kwargs["auto_map"] = {
            "AutoTokenizer": ["tokenization_un_model.UnModelTokenizer", None]
        }
        self.init_kwargs["tokenizer_class"] = self.__class__.__name__
        self.init_kwargs["add_bos_token"] = self.add_bos_token
        self.init_kwargs["add_eos_token"] = self.add_eos_token

    @property
    def vocab_size(self):
        return self.sp_model.get_piece_size()

    def get_vocab(self):
        vocab = {
            self.sp_model.id_to_piece(token_id): token_id
            for token_id in range(self.vocab_size)
        }
        vocab.update(self.added_tokens_encoder)
        return vocab

    def _tokenize(self, text, **kwargs):
        return self.sp_model.encode(text, out_type=str)

    def _convert_token_to_id(self, token):
        token_id = self.sp_model.piece_to_id(token)
        if token_id < 0 or token_id >= self.vocab_size:
            return self._sp_unk_id
        return token_id

    def _convert_id_to_token(self, index):
        if index < 0 or index >= self.vocab_size:
            return self.unk_token
        return self.sp_model.id_to_piece(index)

    def convert_tokens_to_string(self, tokens: List[str]) -> str:
        return self.sp_model.decode_pieces(tokens)

    def build_inputs_with_special_tokens(
        self,
        token_ids_0: List[int],
        token_ids_1: Optional[List[int]] = None,
    ) -> List[int]:
        output = list(token_ids_0)
        if self.add_bos_token:
            output.insert(0, self.bos_token_id)
        if self.add_eos_token:
            output.append(self.eos_token_id)

        if token_ids_1 is not None:
            second = list(token_ids_1)
            if self.add_bos_token:
                second.insert(0, self.bos_token_id)
            if self.add_eos_token:
                second.append(self.eos_token_id)
            output.extend(second)
        return output

    def get_special_tokens_mask(
        self,
        token_ids_0: List[int],
        token_ids_1: Optional[List[int]] = None,
        already_has_special_tokens: bool = False,
    ) -> List[int]:
        if already_has_special_tokens:
            special_ids = set(self.all_special_ids)
            return [1 if token_id in special_ids else 0 for token_id in token_ids_0]

        mask = []
        if self.add_bos_token:
            mask.append(1)
        mask.extend([0] * len(token_ids_0))
        if self.add_eos_token:
            mask.append(1)

        if token_ids_1 is not None:
            if self.add_bos_token:
                mask.append(1)
            mask.extend([0] * len(token_ids_1))
            if self.add_eos_token:
                mask.append(1)
        return mask

    def create_token_type_ids_from_sequences(
        self,
        token_ids_0: List[int],
        token_ids_1: Optional[List[int]] = None,
    ) -> List[int]:
        combined = self.build_inputs_with_special_tokens(token_ids_0, token_ids_1)
        return [0] * len(combined)

    def save_vocabulary(
        self,
        save_directory,
        filename_prefix: Optional[str] = None,
    ) -> Tuple[str, ...]:
        if not os.path.isdir(save_directory):
            raise ValueError(f"Vocabulary save directory does not exist: {save_directory}")

        filename = (
            f"{filename_prefix}-new_bpe.model"
            if filename_prefix
            else "new_bpe.model"
        )
        destination = os.path.join(save_directory, filename)
        if os.path.abspath(self.vocab_file) != os.path.abspath(destination):
            shutil.copyfile(self.vocab_file, destination)
        return (destination,)


UnModelTokenizer.register_for_auto_class("AutoTokenizer")