EADST

Transformers Llama 分词器代码中文注释 tokenization_llama.py

# coding=utf-8
# Copyright 2022 EleutherAI and the HuggingFace Inc. team. All rights reserved.
#
# 此代码基于EleutherAI的GPT-NeoX库以及此库中的GPT-NeoX和OPT实现。
# 它已从原始形式修改,以适应与Meta AI团队训练模型时使用的GPT-NeoX和OPT相比的
# 微小架构差异。
#
# 根据Apache许可证2.0版("许可证")获得许可;
# 除非符合许可证,否则不得使用此文件。
# 您可以在以下位置获取许可证副本:
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# 除非适用法律要求或书面同意,否则依据许可证分发的软件
# 是基于"按原样"分发的,没有任何明示或暗示的担保或条件。
# 有关许可证下特定语言的权限和限制,请参阅许可证。
# 中文代码注释 XD 因为markdown格式和代码中的特殊字符有冲突,所以特殊字符略作删减。
# 源码请参考 https://github.com/huggingface/transformers/blob/main/src/transformers/models/llama/tokenization_llama.py
"""LLaMA的分词器类。"""

import os
from shutil import copyfile
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple

import sentencepiece as spm

from ...convert_slow_tokenizer import import_protobuf
from ...tokenization_utils import AddedToken, PreTrainedTokenizer
from ...utils import logging
from ...utils.import_utils import requires


if TYPE_CHECKING:
    from ...tokenization_utils_base import TextInput

logger = logging.get_logger(__name__)

# 词汇文件名称
VOCAB_FILES_NAMES = {"vocab_file": "tokenizer.model"}

# SentencePiece中的下划线字符,表示一个词的开始
SPIECE_UNDERLINE = "▁"

# 指令开始和结束标记
B_INST, E_INST = "[INST]", "[/INST]"
# 系统提示开始和结束标记
B_SYS, E_SYS = "<>\n", "\n<>\n\n"

# 默认系统提示文本
DEFAULT_SYSTEM_PROMPT = """You are a helpful, respectful and honest assistant. Always answer as helpfully as possible, while being safe. Your \
answers should not include any harmful, unethical, racist, sexist, toxic, dangerous, or illegal content. Please ensure\
 that your responses are socially unbiased and positive in nature.

If a question does not make any sense, or is not factually coherent, explain why instead of answering something not \
correct. If you don't know the answer to a question, please don't share false information."""  # fmt: skip


# 使用requires装饰器确保sentencepiece后端可用
@requires(backends=("sentencepiece",))
class LlamaTokenizer(PreTrainedTokenizer):
    """
    构建Llama分词器。基于字节级的字节对编码(BPE)。原始模型中没有填充token,因此默认的填充token未设置。

    参数:
        vocab_file (`str`):
            词汇表文件的路径。
        unk_token (`str` 或 `tokenizers.AddedToken`, *可选*, 默认为 `"unk"`):
            未知token。词汇表中不存在的token无法转换为ID,会使用此token代替。
        bos_token (`str` 或 `tokenizers.AddedToken`, *可选*, 默认为 `"s"`):
            序列开始token,在预训练期间使用。可以用作序列分类器token。
        eos_token (`str` 或 `tokenizers.AddedToken`, *可选*, 默认为 `"/s"`):
            序列结束token。
        pad_token (`str` 或 `tokenizers.AddedToken`, *可选*):
            用于使token数组大小相同以进行批处理的特殊token。之后会被注意力机制或损失计算忽略。
        sp_model_kwargs (`Dict[str, Any]`, `Optional`, *可选*):
            将传递给`SentencePieceProcessor.__init__()`方法。
            [SentencePiece的Python包装器](https://github.com/google/sentencepiece/tree/master/python)可以用于设置:

            - `enable_sampling`: 启用子词正则化。
            - `nbest_size`: 一元模型的采样参数。对BPE-Dropout无效。

              - `nbest_size = {0,1}`: 不执行采样。
              - `nbest_size > 1`: 从nbest_size个结果中采样。
              - `nbest_size < 0`: 假设nbest_size是无限的,使用前向过滤和后向采样算法从所有假设(lattice)中采样。

            - `alpha`: 一元采样的平滑参数,和BPE-dropout的合并操作的dropout概率。

        add_bos_token (`bool`, *可选*, 默认为 `True`):
            是否在序列开始处添加`bos_token`。
        add_eos_token (`bool`, *可选*, 默认为 `False`):
            是否在序列结束处添加`eos_token`。
        clean_up_tokenization_spaces (`bool`, *可选*, 默认为 `False`):
            解码后是否清理空格,清理包括移除可能的人工产物,如额外的空格。
        use_default_system_prompt (`bool`, *可选*, 默认为 `False`):
            是否使用Llama的默认系统提示。
        spaces_between_special_tokens (`bool`, *可选*, 默认为 `False`):
            是否在特殊token之间添加空格。
        legacy (`bool`, *可选*):
            是否使用分词器的`legacy`行为。Legacy是在合并#24622和#25224之前的行为,
            包括修复正确处理出现在特殊token之后的token的问题。
            确保同时将`from_slow`设置为`True`。
            简单示例:

            - `legacy=True`:
            ```python
            >>> from transformers import LlamaTokenizerFast

            >>> tokenizer = LlamaTokenizerFast.from_pretrained("huggyllama/llama-7b", legacy=True, from_slow=True)
            >>> tokenizer.encode("Hello  s .") # 869 is ' .'
            [1, 15043, 29871, 1, 869]
            ```
            - `legacy=False`:
            ```python
            >>> from transformers import LlamaTokenizerFast

            >>> tokenizer = LlamaTokenizerFast.from_pretrained("huggyllama/llama-7b", legacy=False, from_slow=True)
            >>> tokenizer.encode("Hello s.")  # 29889 is '.'
            [1, 15043, 29871, 1, 29889]
            ```
            查看[pull request](https://github.com/huggingface/transformers/pull/24565)获取更多详情。
        add_prefix_space (`bool`, *可选*, 默认为 `True`):
            是否在输入前添加初始空格。这允许将首个词与其他词同等对待。
            同样,这应与`from_slow=True`一起设置,确保其被考虑。
    """

    # 词汇文件名称
    vocab_files_names = VOCAB_FILES_NAMES
    # 模型输入名称
    model_input_names = ["input_ids", "attention_mask"]

    def __init__(
        self,
        vocab_file,
        unk_token="",
        bos_token=" < s >",
        eos_token=" < /s >",
        pad_token=None,
        sp_model_kwargs: Optional[Dict[str, Any]] = None,
        add_bos_token=True,
        add_eos_token=False,
        clean_up_tokenization_spaces=False,
        use_default_system_prompt=False,
        spaces_between_special_tokens=False,
        legacy=None,
        add_prefix_space=True,
        **kwargs,
    ):
        # 初始化SentencePiece模型参数
        self.sp_model_kwargs = {} if sp_model_kwargs is None else sp_model_kwargs

        # 将字符串token转换为AddedToken对象
        bos_token = AddedToken(bos_token, normalized=False, special=True) if isinstance(bos_token, str) else bos_token
        eos_token = AddedToken(eos_token, normalized=False, special=True) if isinstance(eos_token, str) else eos_token
        unk_token = AddedToken(unk_token, normalized=False, special=True) if isinstance(unk_token, str) else unk_token
        pad_token = AddedToken(pad_token, normalized=False, special=True) if isinstance(pad_token, str) else pad_token

        # 处理legacy参数
        if legacy is None:
            logger.warning_once(
                f"您正在使用{self.__class__}的默认legacy行为。这是预期的,"
                "只是意味着将使用`legacy`(以前的)行为,所以对您来说没有变化。"
                "如果您想使用新行为,请设置`legacy=False`。这应该只在您理解它的"
                "含义并在https://github.com/huggingface/transformers/pull/24565中"
                "详细阅读了添加此功能的原因后设置 - 如果您从GGUF文件加载了llama分词器,"
                "可以忽略此消息"
            )
            legacy = True

        # 保存实例变量
        self.legacy = legacy
        self.vocab_file = vocab_file
        self.add_bos_token = add_bos_token
        self.add_eos_token = add_eos_token
        self.use_default_system_prompt = use_default_system_prompt
        # 获取SentencePiece处理器
        self.sp_model = self.get_spm_processor(kwargs.pop("from_slow", False))
        self.add_prefix_space = add_prefix_space

        # 调用父类初始化
        super().__init__(
            bos_token=bos_token,
            eos_token=eos_token,
            unk_token=unk_token,
            pad_token=pad_token,
            add_bos_token=add_bos_token,
            add_eos_token=add_eos_token,
            sp_model_kwargs=self.sp_model_kwargs,
            clean_up_tokenization_spaces=clean_up_tokenization_spaces,
            use_default_system_prompt=use_default_system_prompt,
            spaces_between_special_tokens=spaces_between_special_tokens,
            legacy=legacy,
            add_prefix_space=add_prefix_space,
            **kwargs,
        )

    @property
    def unk_token_length(self):
        """获取未知token的长度"""
        return len(self.sp_model.encode(str(self.unk_token)))

    # 从transformers.models.t5.tokenization_t5.T5Tokenizer.get_spm_processor复制
    def get_spm_processor(self, from_slow=False):
        """获取SentencePiece处理器"""
        # 初始化分词器
        tokenizer = spm.SentencePieceProcessor(**self.sp_model_kwargs)
        # 如果使用legacy模式或从slow转换,直接加载模型
        if self.legacy or from_slow:  # 不依赖protobuf
            tokenizer.Load(self.vocab_file)
            return tokenizer

        # 新行为:修改模型配置
        with open(self.vocab_file, "rb") as f:
            sp_model = f.read()
            model_pb2 = import_protobuf(f"{self.__class__.__name__}的新行为(使用`self.legacy = False`)")
            model = model_pb2.ModelProto.FromString(sp_model)
            normalizer_spec = model_pb2.NormalizerSpec()
            normalizer_spec.add_dummy_prefix = False
            model.normalizer_spec.MergeFrom(normalizer_spec)
            sp_model = model.SerializeToString()
            tokenizer.LoadFromSerializedProto(sp_model)
        return tokenizer

    def __getstate__(self):
        """获取对象状态,用于序列化"""
        state = self.__dict__.copy()
        state["sp_model"] = None
        state["sp_model_proto"] = self.sp_model.serialized_model_proto()
        return state

    def __setstate__(self, d):
        """设置对象状态,用于反序列化"""
        self.__dict__.update(d)
        self.sp_model = spm.SentencePieceProcessor(**self.sp_model_kwargs)
        self.sp_model.LoadFromSerializedProto(self.sp_model_proto)

    @property
    def vocab_size(self):
        """返回词汇表大小"""
        return self.sp_model.get_piece_size()

    def get_vocab(self):
        """以字典形式返回词汇表"""
        vocab = {self.convert_ids_to_tokens(i): i for i in range(self.vocab_size)}
        vocab.update(self.added_tokens_encoder)
        return vocab

    # 从transformers.models.t5.tokenization_t5.T5Tokenizer.tokenize复制
    def tokenize(self, text: "TextInput", **kwargs) -> List[str]:
        """
        将字符串转换为token列表。如果`self.legacy`设置为`False`,则添加前缀token,
        除非第一个token是特殊token。
        """
        if self.legacy or len(text) == 0:
            return super().tokenize(text, **kwargs)

        # 将SentencePiece下划线替换为空格
        text = text.replace(SPIECE_UNDERLINE, " ")
        if self.add_prefix_space:
            text = SPIECE_UNDERLINE + text

        # 调用父类的tokenize方法
        tokens = super().tokenize(text, **kwargs)

        # 处理特殊情况:如果第一个token是下划线,第二个是特殊token
        if len(tokens) > 1 and tokens[0] == SPIECE_UNDERLINE and tokens[1] in self.all_special_tokens:
            tokens = tokens[1:]
        return tokens

    # 从transformers.models.t5.tokenization_t5.T5Tokenizer._tokenize复制
    def _tokenize(self, text, **kwargs):
        """
        返回分词后的字符串。

        我们禁用了`add_dummy_prefix`选项,因此sentencepiece内部总是会去除任何
        SPIECE_UNDERLINE。例如:`self.sp_model.encode(f"{SPIECE_UNDERLINE}Hey", out_type = str)`
        会给出`['H', 'e', 'y']`而不是`['▁He', 'y']`。因此我们总是编码`f"{unk_token}text"`
        并去除`unk_token`。这里是一个例子,`unk_token = ""`且`unk_token_length = 4`。
        `self.tokenizer.sp_model.encode(" Hey", out_type = str)[4:]`。
        """
        if self.legacy or not text.startswith((SPIECE_UNDERLINE, " ")):
            return self.sp_model.encode(text, out_type=str)

        # 1. 编码字符串+前缀,例如:" Hey"
        tokens = self.sp_model.encode(self.unk_token + text, out_type=str)
        # 2. 从['<','unk','>', '▁Hey']中移除self.unk_token
        return tokens[self.unk_token_length :] if len(tokens) >= self.unk_token_length else tokens

    def _convert_token_to_id(self, token):
        """使用词汇表将token(str)转换为id。"""
        return self.sp_model.piece_to_id(token)

    def _convert_id_to_token(self, index):
        """使用词汇表将索引(整数)转换为token(str)。"""
        token = self.sp_model.IdToPiece(index)
        return token

    def convert_tokens_to_string(self, tokens):
        """将token序列(字符串)转换为单个字符串。"""
        # 由于我们手动添加前缀空格,解码时需要移除它
        if tokens[0].startswith(SPIECE_UNDERLINE) and self.add_prefix_space:
            tokens[0] = tokens[0][1:]

        current_sub_tokens = []
        out_string = ""
        prev_is_special = False
        for i, token in enumerate(tokens):
            # 确保不使用sentencepiece模型解码特殊token
            if token in self.all_special_tokens:
                if not prev_is_special and i != 0 and self.legacy:
                    out_string += " "
                out_string += self.sp_model.decode(current_sub_tokens) + token
                prev_is_special = True
                current_sub_tokens = []
            else:
                if prev_is_special and i == 1 and self.add_prefix_space and not token.startswith(SPIECE_UNDERLINE):
                    out_string += " "
                current_sub_tokens.append(token)
                prev_is_special = False
        out_string += self.sp_model.decode(current_sub_tokens)
        return out_string

    def save_vocabulary(self, save_directory, filename_prefix: Optional[str] = None) -> Tuple[str]:
        """
        将词汇表和特殊token文件保存到目录。

        参数:
            save_directory (`str`):
                保存词汇表的目录。

        返回:
            `Tuple(str)`: 保存的文件路径。
        """
        if not os.path.isdir(save_directory):
            logger.error(f"词汇表路径({save_directory})应该是一个目录")
            return
        # 构建输出词汇文件路径
        out_vocab_file = os.path.join(
            save_directory, (filename_prefix + "-" if filename_prefix else "") + VOCAB_FILES_NAMES["vocab_file"]
        )

        # 如果路径不同且原始文件存在,复制文件
        if os.path.abspath(self.vocab_file) != os.path.abspath(out_vocab_file) and os.path.isfile(self.vocab_file):
            copyfile(self.vocab_file, out_vocab_file)
        # 如果原始文件不存在,创建新文件
        elif not os.path.isfile(self.vocab_file):
            with open(out_vocab_file, "wb") as fi:
                content_spiece_model = self.sp_model.serialized_model_proto()
                fi.write(content_spiece_model)

        return (out_vocab_file,)

    def build_inputs_with_special_tokens(self, token_ids_0, token_ids_1=None):
        """构建包含特殊token的输入"""
        # 根据配置添加BOS和EOS token
        bos_token_id = [self.bos_token_id] if self.add_bos_token else []
        eos_token_id = [self.eos_token_id] if self.add_eos_token else []

        # 构建输出序列
        output = bos_token_id + token_ids_0 + eos_token_id

        # 如果有第二个序列,也添加到输出中
        if token_ids_1 is not None:
            output = output + bos_token_id + token_ids_1 + eos_token_id

        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]:
        """
        从没有添加特殊token的token列表中检索序列ID。当使用分词器的`prepare_for_model`方法添加
        特殊token时调用此方法。

        参数:
            token_ids_0 (`List[int]`):
                ID列表。
            token_ids_1 (`List[int]`, *可选*):
                序列对的可选第二个ID列表。
            already_has_special_tokens (`bool`, *可选*, 默认为 `False`):
                token列表是否已经使用特殊token为模型格式化。

        返回:
            `List[int]`: 范围为[0, 1]的整数列表:1表示特殊token,0表示序列token。
        """
        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
            )

        # 根据配置确定BOS和EOS标记
        bos_token_id = [1] if self.add_bos_token else []
        eos_token_id = [1] if self.add_eos_token else []

        # 构建掩码:特殊token为1,序列token为0
        if token_ids_1 is None:
            return bos_token_id + ([0] * len(token_ids_0)) + eos_token_id
        return (
            bos_token_id
            + ([0] * len(token_ids_0))
            + eos_token_id
            + bos_token_id
            + ([0] * len(token_ids_1))
            + eos_token_id
        )

    def create_token_type_ids_from_sequences(
        self, token_ids_0: List[int], token_ids_1: Optional[List[int]] = None
    ) -> List[int]:
        """
        从传递的两个序列创建掩码,用于序列对分类任务。ALBERT序列对掩码具有以下格式:

        ```
        0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1
        | first sequence    | second sequence |
        ```

        如果token_ids_1为None,则仅返回掩码的第一部分(0)。

        参数:
            token_ids_0 (`List[int]`):
                ID列表。
            token_ids_1 (`List[int]`, *可选*):
                序列对的可选第二个ID列表。

        返回:
            `List[int]`: 根据给定序列返回的[token类型ID](../glossary#token-type-ids)列表。
        """
        # 根据配置确定BOS和EOS标记
        bos_token_id = [self.bos_token_id] if self.add_bos_token else []
        eos_token_id = [self.eos_token_id] if self.add_eos_token else []

        # 第一个序列的token类型ID都为0
        output = [0] * len(bos_token_id + token_ids_0 + eos_token_id)

        # 如果有第二个序列,其token类型ID都为1
        if token_ids_1 is not None:
            output += [1] * len(bos_token_id + token_ids_1 + eos_token_id)

        return output


# 定义模块中所有公开的类
__all__ = ["LlamaTokenizer"]

相关标签
About Me
XD
Goals determine what you are going to be.
Category
标签云
Markdown 搞笑 多线程 财报 FP16 Color Password Michelin GPTQ Hungarian Image2Text Plotly Paper NLTK Quantization Video SPIE Crawler InvalidArgumentError GGML TTS Python OpenAI COCO Baidu BTC Miniforge API 继承 git WebCrawler Input torchinfo XGBoost Magnet logger 报税 HaggingFace Data tqdm JSON 证件照 CEIR diffusers FP32 mmap Zip FP64 FP8 SQLite AI CLAP Template Animate Linux Bipartite ResNet-50 LLAMA Conda 阿里云 TensorRT Vim Claude 多进程 飞书 Transformers Permission PDB Bert OCR RGB Gemma UI MD5 Clash tar 公式 Statistics Github LaTeX Hotel Base64 Augmentation ChatGPT SQL transformers Nginx Windows RAR Attention Ptyhon Pytorch Cloudreve IndexTTS2 hf Qwen2 CTC Domain CV LLM Git SVR Interview PIP Algorithm DeepStream C++ 签证 VSCode CC Tracking Land Mixtral Firewall Pandas VPN PyCharm XML Jetson 音频 NameSilo Use Tiktoken Diagram BeautifulSoup Distillation Qwen2.5 YOLO Datetime Django ONNX uwsgi Proxy NLP Paddle GPT4 域名 LeetCode git-lfs uWSGI VGG-16 v2ray CSV Numpy llama.cpp OpenCV Website Math Pickle Ubuntu Pillow PyTorch Plate Dataset Sklearn CAM 关于博主 Bin HuggingFace TensorFlow Heatmap Anaconda Streamlit Shortcut Quantize Breakpoint Random v0.dev Google ModelScope FastAPI SAM Review Knowledge 版权 GIT printf QWEN Vmess BF16 GoogLeNet FlashAttention Llama 净利润 腾讯云 scipy Disk Card Hilton PDF Food DeepSeek TSV Excel Jupyter Bitcoin Web Translation CUDA Docker EXCEL UNIX Logo LoRA 算法题 Qwen WAN Tensor Safetensors Freesound
站点统计

本站现有博文311篇,共被浏览742118

本站已经建立2381天!

热门文章
文章归档
回到顶部