codingutf8 2020 microsoft and the huggingface inc team licensed under the apache license version 2 0 the license you may not use this file except in compliance with the license you may obtain a copy of the license at http www apache orglicenseslicense2 0 unless required by applicable law or agreed to in writing software distributed under the license is distributed on an as is basis without warranties or conditions of any kind either express or implied see the license for the specific language governing permissions and limitations under the license tokenization class for model deberta import os import unicodedata from typing import any dict list optional tuple import sentencepiece as sp from tokenizationutils import addedtoken pretrainedtokenizer from utils import logging logger logging getloggername pretrainedvocabfilesmap vocabfile microsoftdebertav2xlarge https huggingface comicrosoftdebertav2xlargeresolvemainspm model microsoftdebertav2xxlarge https huggingface comicrosoftdebertav2xxlargeresolvemainspm model microsoftdebertav2xlargemnli https huggingface comicrosoftdebertav2xlargemnliresolvemainspm model microsoftdebertav2xxlargemnli https huggingface comicrosoftdebertav2xxlargemnliresolvemainspm model pretrainedpositionalembeddingssizes microsoftdebertav2xlarge 512 microsoftdebertav2xxlarge 512 microsoftdebertav2xlargemnli 512 microsoftdebertav2xxlargemnli 512 pretrainedinitconfiguration microsoftdebertav2xlarge dolowercase false microsoftdebertav2xxlarge dolowercase false microsoftdebertav2xlargemnli dolowercase false microsoftdebertav2xxlargemnli dolowercase false vocabfilesnames vocabfile spm model class debertav2tokenizerpretrainedtokenizer r constructs a debertav2 tokenizer based on sentencepiecehttps github comgooglesentencepiece args vocabfile str sentencepiecehttps github comgooglesentencepiece file generally has a spm extension that contains the vocabulary necessary to instantiate a tokenizer dolowercase bool optional defaults to false whether or not to lowercase the input when tokenizing bostoken string optional defaults to cls the beginning of sequence token that was used during pretraining can be used a sequence classifier token when building a sequence using special tokens this is not the token that is used for the beginning of sequence the token used is the clstoken eostoken string optional defaults to sep the end of sequence token when building a sequence using special tokens this is not the token that is used for the end of sequence the token used is the septoken unktoken str optional defaults to unk the unknown token a token that is not in the vocabulary cannot be converted to an id and is set to be this token instead septoken str optional defaults to sep the separator token which is used when building a sequence from multiple sequences e g two sequences for sequence classification or for a text and a question for question answering it is also used as the last token of a sequence built with special tokens padtoken str optional defaults to pad the token used for padding for example when batching sequences of different lengths clstoken str optional defaults to cls the classifier token which is used when doing sequence classification classification of the whole sequence instead of pertoken classification it is the first token of the sequence when built with special tokens masktoken str optional defaults to mask the token used for masking values this is the token used when training this model with masked language modeling this is the token which the model will try to predict spmodelkwargs dict optional will be passed to the sentencepieceprocessor init method the python wrapper for sentencepiecehttps github comgooglesentencepiecetreemasterpython can be used among other things to set enablesampling enable subword regularization nbestsize sampling parameters for unigram invalid for bpedropout nbestsize 0 1 no sampling is performed nbestsize 1 samples from the nbestsize results nbestsize 0 assuming that nbestsize is infinite and samples from the all hypothesis lattice using forwardfilteringandbackwardsampling algorithm alpha smoothing parameter for unigram sampling and dropout probability of merge operations for bpedropout take as input a string and return a list of strings tokens for wordssubwords if self dolowercase text text lower return self tokenizer tokenizetext def converttokentoidself token converts an index integer in a token str using the vocab return self tokenizer spm idtopieceindex if index self vocabsize else self unktoken def converttokenstostringself tokens build model inputs from a sequence or a pair of sequence for sequence classification tasks by concatenating and adding special tokens a deberta sequence has the following format single sequence cls x sep pair of sequences cls a sep b sep args tokenids0 listint list of ids to which the special tokens will be added tokenids1 listint optional optional second list of ids for sequence pairs returns listint list of input ids glossaryinputids with the appropriate special tokens retrieves sequence ids from a token list that has no special tokens added this method is called when adding special tokens using the tokenizer prepareformodel or encodeplus methods args tokenids0 listint list of ids tokenids1 listint optional optional second list of ids for sequence pairs alreadyhasspecialtokens bool optional defaults to false whether or not the token list is already formatted with special tokens for the model returns listint a list of integers in the range 0 1 1 for a special token 0 for a sequence token create a mask from the two sequences passed to be used in a sequencepair classification task a deberta sequence pair mask has the following format 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 first sequence second sequence if tokenids1 is none this method only returns the first portion of the mask 0s args tokenids0 listint list of ids tokenids1 listint optional optional second list of ids for sequence pairs returns listint list of token type ids glossarytokentypeids according to the given sequences def init self vocabfile specialtokens splitbypunctfalse spmodelkwargs optionaldictstr any none self splitbypunct splitbypunct self vocabfile vocabfile self spmodelkwargs if spmodelkwargs is none else spmodelkwargs spm sp sentencepieceprocessorself spmodelkwargs if not os path existsvocabfile raise filenotfounderrorfvocabfile does not exist spm loadvocabfile bpevocabsize spm getpiecesize token map unk 01 s 11 s 21 self vocab spm idtopiecei i for i in rangebpevocabsize self idstotokens spm idtopiecei for i in rangebpevocabsize self vocab pad 0 self vocab cls 1 self vocab sep 2 self vocab unk 3 self spm spm self specialtokens specialtokens def getstateself state self dict copy statespm none return state def setstateself d self dict d for backward compatibility if not hasattrself spmodelkwargs self spmodelkwargs self spm sp sentencepieceprocessorself spmodelkwargs self spm loadself vocabfile def tokenizeself text return self encodeaspiecestext def convertidstotokensself ids tokens for i in ids tokens appendself idstotokensi return tokens def decodeself tokens start1 end1 rawtextnone if rawtext is none currentsubtokens outstring previsspecial false for token in tokens make sure that special tokens are not decoded using sentencepiece model if token in self specialtokens if not previsspecial outstring outstring self spm decodepiecescurrentsubtokens token previsspecial true currentsubtokens else currentsubtokens appendtoken previsspecial false outstring self spm decodepiecescurrentsubtokens return outstring strip else words self splittowordsrawtext wordtokens self tokenizew for w in words token2words 0 lentokens tid 0 for i w in enumeratewordtokens for k t in enumeratew token2wordstid i tid 1 wordstart token2wordsstart wordend token2wordsend if end lentokens else lenwords text joinwordswordstart wordend return text todo add a deprecation cycle as this can have different behaviour from our api def addspecialtokenself token if token not in self specialtokens self specialtokens appendtoken if token not in self vocab self vocabtoken lenself vocab 1 self idstotokens appendtoken return self idtoken def partofwholewordself token isbosfalse logger warningonce the debertatokenizer partofwholeword method is deprecated and will be removed in transformers4 35 if isbos return true if lentoken 1 and iswhitespacelisttoken0 or iscontrollisttoken0 or ispunctuationlisttoken0 or token in self specialtokens return false wordstart bxe2x96x81 decodeutf8 return not token startswithwordstart def padself return pad def bosself return cls def eosself return sep def unkself return unk def maskself return mask def symself id return self idstotokensid def idself sym logger warningonce the debertatokenizer id method is deprecated and will be removed in transformers4 35 return self vocabsym if sym in self vocab else 1 def encodeaspiecesself text text converttounicodetext if self splitbypunct words self runsplitonpunctext pieces self spm encodew outtypestr for w in words return p for w in pieces for p in w else return self spm encodetext outtypestr def splittowordsself text pieces self encodeaspiecestext wordstart bxe2x96x81 decodeutf8 words offset 0 prevend 0 for i p in enumeratepieces if p startswithwordstart if offset prevend words appendtextprevend offset prevend offset w p replacewordstart else w p try s text indexw offset pn k i 1 while k lenpieces pn piecesk replacewordstart if lenpn 0 break k 1 if lenpn 0 and pn in textoffset s offset offset 1 else offset s lenw except exception offset offset 1 if prevend offset words appendtextprevend offset return words def runsplitonpuncself text checks whether chars is a whitespace character t n and r are technically control characters but we treat them as whitespace since they are generally considered as such if char or char t or char n or char r return true cat unicodedata categorychar if cat zs return true return false def iscontrolchar these are technically control characters but we count them as whitespace characters checks whether chars is a punctuation character cp ordchar we treat all nonletternumber ascii as punctuation characters such as and are not in the unicode punctuation class but we treat them as punctuation anyways for consistency if cp 33 and cp 47 or cp 58 and cp 64 or cp 91 and cp 96 or cp 123 and cp 126 return true cat unicodedata categorychar if cat startswithp return true return false def converttounicodetext coding utf 8 2020 microsoft and the huggingface inc team licensed under the apache license version 2 0 the license you may not use this file except in compliance with the license you may obtain a copy of the license at http www apache org licenses license 2 0 unless required by applicable law or agreed to in writing software distributed under the license is distributed on an as is basis without warranties or conditions of any kind either express or implied see the license for the specific language governing permissions and limitations under the license tokenization class for model deberta constructs a deberta v2 tokenizer based on sentencepiece https github com google sentencepiece args vocab_file str sentencepiece https github com google sentencepiece file generally has a spm extension that contains the vocabulary necessary to instantiate a tokenizer do_lower_case bool optional defaults to false whether or not to lowercase the input when tokenizing bos_token string optional defaults to cls the beginning of sequence token that was used during pre training can be used a sequence classifier token when building a sequence using special tokens this is not the token that is used for the beginning of sequence the token used is the cls_token eos_token string optional defaults to sep the end of sequence token when building a sequence using special tokens this is not the token that is used for the end of sequence the token used is the sep_token unk_token str optional defaults to unk the unknown token a token that is not in the vocabulary cannot be converted to an id and is set to be this token instead sep_token str optional defaults to sep the separator token which is used when building a sequence from multiple sequences e g two sequences for sequence classification or for a text and a question for question answering it is also used as the last token of a sequence built with special tokens pad_token str optional defaults to pad the token used for padding for example when batching sequences of different lengths cls_token str optional defaults to cls the classifier token which is used when doing sequence classification classification of the whole sequence instead of per token classification it is the first token of the sequence when built with special tokens mask_token str optional defaults to mask the token used for masking values this is the token used when training this model with masked language modeling this is the token which the model will try to predict sp_model_kwargs dict optional will be passed to the sentencepieceprocessor __init__ method the python wrapper for sentencepiece https github com google sentencepiece tree master python can be used among other things to set enable_sampling enable subword regularization nbest_size sampling parameters for unigram invalid for bpe dropout nbest_size 0 1 no sampling is performed nbest_size 1 samples from the nbest_size results nbest_size 0 assuming that nbest_size is infinite and samples from the all hypothesis lattice using forward filtering and backward sampling algorithm alpha smoothing parameter for unigram sampling and dropout probability of merge operations for bpe dropout take as input a string and return a list of strings tokens for words sub words converts a token str in an id using the vocab converts an index integer in a token str using the vocab converts a sequence of tokens string in a single string build model inputs from a sequence or a pair of sequence for sequence classification tasks by concatenating and adding special tokens a deberta sequence has the following format single sequence cls x sep pair of sequences cls a sep b sep args token_ids_0 list int list of ids to which the special tokens will be added token_ids_1 list int optional optional second list of ids for sequence pairs returns list int list of input ids glossary input ids with the appropriate special tokens retrieves sequence ids from a token list that has no special tokens added this method is called when adding special tokens using the tokenizer prepare_for_model or encode_plus methods args token_ids_0 list int list of ids token_ids_1 list int optional optional second list of ids for sequence pairs already_has_special_tokens bool optional defaults to false whether or not the token list is already formatted with special tokens for the model returns list int a list of integers in the range 0 1 1 for a special token 0 for a sequence token create a mask from the two sequences passed to be used in a sequence pair classification task a deberta sequence pair mask has the following format 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 first sequence second sequence if token_ids_1 is none this method only returns the first portion of the mask 0s args token_ids_0 list int list of ids token_ids_1 list int optional optional second list of ids for sequence pairs returns list int list of token type ids glossary token type ids according to the given sequence s constructs a tokenizer based on sentencepiece https github com google sentencepiece args vocab_file str sentencepiece https github com google sentencepiece file generally has a spm extension that contains the vocabulary necessary to instantiate a tokenizer sp_model_kwargs dict optional will be passed to the sentencepieceprocessor __init__ method the python wrapper for sentencepiece https github com google sentencepiece tree master python can be used among other things to set enable_sampling enable subword regularization nbest_size sampling parameters for unigram invalid for bpe dropout nbest_size 0 1 no sampling is performed nbest_size 1 samples from the nbest_size results nbest_size 0 assuming that nbest_size is infinite and samples from the all hypothesis lattice using forward filtering and backward sampling algorithm alpha smoothing parameter for unigram sampling and dropout probability of merge operations for bpe dropout token map unk 0 1 s 1 1 s 2 1 self vocab pad 0 self vocab cls 1 self vocab sep 2 self vocab unk 3 for backward compatibility make sure that special tokens are not decoded using sentencepiece model todo add a deprecation cycle as this can have different behaviour from our api splits punctuation on a piece of text checks whether chars is a whitespace character t n and r are technically control characters but we treat them as whitespace since they are generally considered as such checks whether chars is a control character these are technically control characters but we count them as whitespace characters checks whether chars is a punctuation character we treat all non letter number ascii as punctuation characters such as and are not in the unicode punctuation class but we treat them as punctuation anyways for consistency converts text to unicode if it s not already assuming utf 8 input
import os
import unicodedata
from typing import Any, Dict, List, Optional, Tuple

import sentencepiece as sp

from ...tokenization_utils import AddedToken, PreTrainedTokenizer
from ...utils import logging


logger = logging.get_logger(__name__)

PRETRAINED_VOCAB_FILES_MAP = {
    "vocab_file": {
        "microsoft/deberta-v2-xlarge": "https://huggingface.co/microsoft/deberta-v2-xlarge/resolve/main/spm.model",
        "microsoft/deberta-v2-xxlarge": "https://huggingface.co/microsoft/deberta-v2-xxlarge/resolve/main/spm.model",
        "microsoft/deberta-v2-xlarge-mnli": (
            "https://huggingface.co/microsoft/deberta-v2-xlarge-mnli/resolve/main/spm.model"
        ),
        "microsoft/deberta-v2-xxlarge-mnli": (
            "https://huggingface.co/microsoft/deberta-v2-xxlarge-mnli/resolve/main/spm.model"
        ),
    }
}

PRETRAINED_POSITIONAL_EMBEDDINGS_SIZES = {
    "microsoft/deberta-v2-xlarge": 512,
    "microsoft/deberta-v2-xxlarge": 512,
    "microsoft/deberta-v2-xlarge-mnli": 512,
    "microsoft/deberta-v2-xxlarge-mnli": 512,
}

PRETRAINED_INIT_CONFIGURATION = {
    "microsoft/deberta-v2-xlarge": {"do_lower_case": False},
    "microsoft/deberta-v2-xxlarge": {"do_lower_case": False},
    "microsoft/deberta-v2-xlarge-mnli": {"do_lower_case": False},
    "microsoft/deberta-v2-xxlarge-mnli": {"do_lower_case": False},
}

VOCAB_FILES_NAMES = {"vocab_file": "spm.model"}


class DebertaV2Tokenizer(PreTrainedTokenizer):
    r

    vocab_files_names = VOCAB_FILES_NAMES
    pretrained_vocab_files_map = PRETRAINED_VOCAB_FILES_MAP
    pretrained_init_configuration = PRETRAINED_INIT_CONFIGURATION
    max_model_input_sizes = PRETRAINED_POSITIONAL_EMBEDDINGS_SIZES

    def __init__(
        self,
        vocab_file,
        do_lower_case=False,
        split_by_punct=False,
        bos_token="[CLS]",
        eos_token="[SEP]",
        unk_token="[UNK]",
        sep_token="[SEP]",
        pad_token="[PAD]",
        cls_token="[CLS]",
        mask_token="[MASK]",
        sp_model_kwargs: Optional[Dict[str, Any]] = None,
        **kwargs,
    ) -> None:
        self.sp_model_kwargs = {} if sp_model_kwargs is None else sp_model_kwargs

        if not os.path.isfile(vocab_file):
            raise ValueError(
                f"Can't find a vocabulary file at path '{vocab_file}'. To load the vocabulary from a Google pretrained"
                " model use `tokenizer = AutoTokenizer.from_pretrained(PRETRAINED_MODEL_NAME)`"
            )
        self.do_lower_case = do_lower_case
        self.split_by_punct = split_by_punct
        self.vocab_file = vocab_file
        self._tokenizer = SPMTokenizer(
            vocab_file, None, split_by_punct=split_by_punct, sp_model_kwargs=self.sp_model_kwargs
        )
        unk_token = AddedToken(unk_token, normalized=True, special=True) if isinstance(unk_token, str) else unk_token
        super().__init__(
            do_lower_case=do_lower_case,
            bos_token=bos_token,
            eos_token=eos_token,
            unk_token=unk_token,
            sep_token=sep_token,
            pad_token=pad_token,
            cls_token=cls_token,
            mask_token=mask_token,
            split_by_punct=split_by_punct,
            sp_model_kwargs=self.sp_model_kwargs,
            **kwargs,
        )
        self._tokenizer.special_tokens = self.all_special_tokens

    @property
    def vocab_size(self):
        return len(self.vocab)

    @property
    def vocab(self):
        return self._tokenizer.vocab

    def get_vocab(self):
        vocab = self.vocab.copy()
        vocab.update(self.get_added_vocab())
        return vocab

    def _tokenize(self, text: str) -> List[str]:
        
        if self.do_lower_case:
            text = text.lower()
        return self._tokenizer.tokenize(text)

    def _convert_token_to_id(self, token):
        
        return self._tokenizer.spm.PieceToId(token)

    def _convert_id_to_token(self, index):
        
        return self._tokenizer.spm.IdToPiece(index) if index < self.vocab_size else self.unk_token

    def convert_tokens_to_string(self, tokens):
        
        return self._tokenizer.decode(tokens)

    def build_inputs_with_special_tokens(self, token_ids_0, token_ids_1=None):
        

        if token_ids_1 is None:
            return [self.cls_token_id] + token_ids_0 + [self.sep_token_id]
        cls = [self.cls_token_id]
        sep = [self.sep_token_id]
        return cls + token_ids_0 + sep + token_ids_1 + sep

    def get_special_tokens_mask(self, token_ids_0, token_ids_1=None, already_has_special_tokens=False):
        

        if already_has_special_tokens:
            return super().get_special_tokens_mask(
                token_ids_0=token_ids_0, token_ids_1=token_ids_1, already_has_special_tokens=True
            )

        if token_ids_1 is not None:
            return [1] + ([0] * len(token_ids_0)) + [1] + ([0] * len(token_ids_1)) + [1]
        return [1] + ([0] * len(token_ids_0)) + [1]

    def create_token_type_ids_from_sequences(self, token_ids_0, token_ids_1=None):
        
        sep = [self.sep_token_id]
        cls = [self.cls_token_id]
        if token_ids_1 is None:
            return len(cls + token_ids_0 + sep) * [0]
        return len(cls + token_ids_0 + sep) * [0] + len(token_ids_1 + sep) * [1]

    def prepare_for_tokenization(self, text, is_split_into_words=False, **kwargs):
        add_prefix_space = kwargs.pop("add_prefix_space", False)
        if is_split_into_words or add_prefix_space:
            text = " " + text
        return (text, kwargs)

    def save_vocabulary(self, save_directory: str, filename_prefix: Optional[str] = None) -> Tuple[str]:
        return self._tokenizer.save_pretrained(save_directory, filename_prefix=filename_prefix)


class SPMTokenizer:
    r

    def __init__(
        self, vocab_file, special_tokens, split_by_punct=False, sp_model_kwargs: Optional[Dict[str, Any]] = None
    ):
        self.split_by_punct = split_by_punct
        self.vocab_file = vocab_file
        self.sp_model_kwargs = {} if sp_model_kwargs is None else sp_model_kwargs
        spm = sp.SentencePieceProcessor(**self.sp_model_kwargs)
        if not os.path.exists(vocab_file):
            raise FileNotFoundError(f"{vocab_file} does not exist!")
        spm.load(vocab_file)
        bpe_vocab_size = spm.GetPieceSize()
        
        
        
        
        self.vocab = {spm.IdToPiece(i): i for i in range(bpe_vocab_size)}
        self.ids_to_tokens = [spm.IdToPiece(i) for i in range(bpe_vocab_size)]
        
        
        
        

        self.spm = spm
        self.special_tokens = special_tokens

    def __getstate__(self):
        state = self.__dict__.copy()
        state["spm"] = None
        return state

    def __setstate__(self, d):
        self.__dict__ = d

        
        if not hasattr(self, "sp_model_kwargs"):
            self.sp_model_kwargs = {}

        self.spm = sp.SentencePieceProcessor(**self.sp_model_kwargs)
        self.spm.Load(self.vocab_file)

    def tokenize(self, text):
        return self._encode_as_pieces(text)

    def convert_ids_to_tokens(self, ids):
        tokens = []
        for i in ids:
            tokens.append(self.ids_to_tokens[i])
        return tokens

    def decode(self, tokens, start=-1, end=-1, raw_text=None):
        if raw_text is None:
            current_sub_tokens = []
            out_string = ""
            prev_is_special = False
            for token in tokens:
                
                if token in self.special_tokens:
                    if not prev_is_special:
                        out_string += " "
                    out_string += self.spm.decode_pieces(current_sub_tokens) + token
                    prev_is_special = True
                    current_sub_tokens = []
                else:
                    current_sub_tokens.append(token)
                    prev_is_special = False
            out_string += self.spm.decode_pieces(current_sub_tokens)
            return out_string.strip()
        else:
            words = self.split_to_words(raw_text)
            word_tokens = [self.tokenize(w) for w in words]
            token2words = [0] * len(tokens)
            tid = 0
            for i, w in enumerate(word_tokens):
                for k, t in enumerate(w):
                    token2words[tid] = i
                    tid += 1
            word_start = token2words[start]
            word_end = token2words[end] if end < len(tokens) else len(words)
            text = "".join(words[word_start:word_end])
            return text

    
    def add_special_token(self, token):
        if token not in self.special_tokens:
            self.special_tokens.append(token)
            if token not in self.vocab:
                self.vocab[token] = len(self.vocab) - 1
                self.ids_to_tokens.append(token)
        return self.id(token)

    def part_of_whole_word(self, token, is_bos=False):
        logger.warning_once(
            "The `DebertaTokenizer.part_of_whole_word` method is deprecated and will be removed in `transformers==4.35`"
        )
        if is_bos:
            return True
        if (
            len(token) == 1
            and (_is_whitespace(list(token)[0]) or _is_control(list(token)[0]) or _is_punctuation(list(token)[0]))
        ) or token in self.special_tokens:
            return False

        word_start = b"\xe2\x96\x81".decode("utf-8")
        return not token.startswith(word_start)

    def pad(self):
        return "[PAD]"

    def bos(self):
        return "[CLS]"

    def eos(self):
        return "[SEP]"

    def unk(self):
        return "[UNK]"

    def mask(self):
        return "[MASK]"

    def sym(self, id):
        return self.ids_to_tokens[id]

    def id(self, sym):
        logger.warning_once(
            "The `DebertaTokenizer.id` method is deprecated and will be removed in `transformers==4.35`"
        )
        return self.vocab[sym] if sym in self.vocab else 1

    def _encode_as_pieces(self, text):
        text = convert_to_unicode(text)
        if self.split_by_punct:
            words = self._run_split_on_punc(text)
            pieces = [self.spm.encode(w, out_type=str) for w in words]
            return [p for w in pieces for p in w]
        else:
            return self.spm.encode(text, out_type=str)

    def split_to_words(self, text):
        pieces = self._encode_as_pieces(text)
        word_start = b"\xe2\x96\x81".decode("utf-8")
        words = []
        offset = 0
        prev_end = 0
        for i, p in enumerate(pieces):
            if p.startswith(word_start):
                if offset > prev_end:
                    words.append(text[prev_end:offset])
                prev_end = offset
                w = p.replace(word_start, "")
            else:
                w = p
            try:
                s = text.index(w, offset)
                pn = ""
                k = i + 1
                while k < len(pieces):
                    pn = pieces[k].replace(word_start, "")
                    if len(pn) > 0:
                        break
                    k += 1

                if len(pn) > 0 and pn in text[offset:s]:
                    offset = offset + 1
                else:
                    offset = s + len(w)
            except Exception:
                offset = offset + 1

        if prev_end < offset:
            words.append(text[prev_end:offset])

        return words

    def _run_split_on_punc(self, text):
        
        chars = list(text)
        i = 0
        start_new_word = True
        output = []
        while i < len(chars):
            char = chars[i]
            if _is_punctuation(char):
                output.append([char])
                start_new_word = True
            else:
                if start_new_word:
                    output.append([])
                start_new_word = False
                output[-1].append(char)
            i += 1

        return ["".join(x) for x in output]

    def save_pretrained(self, path: str, filename_prefix: str = None):
        filename = VOCAB_FILES_NAMES[list(VOCAB_FILES_NAMES.keys())[0]]
        if filename_prefix is not None:
            filename = filename_prefix + "-" + filename
        full_path = os.path.join(path, filename)
        with open(full_path, "wb") as fs:
            fs.write(self.spm.serialized_model_proto())
        return (full_path,)


def _is_whitespace(char):
    
    
    
    if char == " " or char == "\t" or char == "\n" or char == "\r":
        return True
    cat = unicodedata.category(char)
    if cat == "Zs":
        return True
    return False


def _is_control(char):
    
    
    
    if char == "\t" or char == "\n" or char == "\r":
        return False
    cat = unicodedata.category(char)
    if cat.startswith("C"):
        return True
    return False


def _is_punctuation(char):
    
    cp = ord(char)
    
    
    
    
    if (cp >= 33 and cp <= 47) or (cp >= 58 and cp <= 64) or (cp >= 91 and cp <= 96) or (cp >= 123 and cp <= 126):
        return True
    cat = unicodedata.category(char)
    if cat.startswith("P"):
        return True
    return False


def convert_to_unicode(text):
    
    if isinstance(text, str):
        return text
    elif isinstance(text, bytes):
        return text.decode("utf-8", "ignore")
    else:
        raise ValueError(f"Unsupported string type: {type(text)}")