分词器¶
分词器是任何大型语言模型(LLM)的关键组成部分。它们将原始文本转换为 token ID,token ID 索引到模型能够理解的嵌入向量中。
在 torchtune 中,分词器负责将 Message
对象转换为 token ID 以及任何必要的模型特定特殊 token。
from torchtune.data import Message
from torchtune.models.phi3 import phi3_mini_tokenizer
sample = {
"input": "user prompt",
"output": "model response",
}
msgs = [
Message(role="user", content=sample["input"]),
Message(role="assistant", content=sample["output"])
]
p_tokenizer = phi3_mini_tokenizer("/tmp/Phi-3-mini-4k-instruct/tokenizer.model")
tokens, mask = p_tokenizer.tokenize_messages(msgs)
print(tokens)
# [1, 32010, 29871, 13, 1792, 9508, 32007, 29871, 13, 32001, 29871, 13, 4299, 2933, 32007, 29871, 13]
print(p_tokenizer.decode(tokens))
# '\nuser prompt \n \nmodel response \n'
模型分词器通常基于底层的字节对编码算法,例如 SentencePiece 或 TikToken,torchtune 都支持这两种算法。
从 Hugging Face 下载分词器¶
托管在 Hugging Face 上的模型也附带了用于训练它们的分词器。使用 tune download
时,这些分词器会与模型权重一起自动下载。例如,此命令会下载 Mistral-7B 模型权重和分词器:
tune download mistralai/Mistral-7B-v0.1 --output-dir /tmp/Mistral-7B-v0.1 --hf-token <HF_TOKEN>
cd /tmp/Mistral-7B-v0.1/
ls tokenizer.model
# tokenizer.model
从文件加载分词器¶
下载分词器文件后,可以通过在配置文件或构造函数中指定分词器模型的路径,将其加载到相应的分词器类中。如果您已将其下载到其他位置,也可以传入自定义文件路径。
# In code
from torchtune.models.mistral import mistral_tokenizer
m_tokenizer = mistral_tokenizer("/tmp/Mistral-7B-v0.1/tokenizer.model")
type(m_tokenizer)
# <class 'torchtune.models.mistral._tokenizer.MistralTokenizer'>
# In config
tokenizer:
_component_: torchtune.models.mistral.mistral_tokenizer
path: /tmp/Mistral-7B-v0.1/tokenizer.model
设置最大序列长度¶
设置最大序列长度可以帮助您控制内存使用,并符合模型规范。
# In code
from torchtune.models.mistral import mistral_tokenizer
m_tokenizer = mistral_tokenizer("/tmp/Mistral-7B-v0.1/tokenizer.model", max_seq_len=8192)
# Set an arbitrarily small seq len for demonstration
from torchtune.data import Message
m_tokenizer = mistral_tokenizer("/tmp/Mistral-7B-v0.1/tokenizer.model", max_seq_len=7)
msg = Message(role="user", content="hello world")
tokens, mask = m_tokenizer.tokenize_messages([msg])
print(len(tokens))
# 7
print(tokens)
# [1, 733, 16289, 28793, 6312, 28709, 2]
print(m_tokenizer.decode(tokens))
# '[INST] hello'
# In config
tokenizer:
_component_: torchtune.models.mistral.mistral_tokenizer
path: /tmp/Mistral-7B-v0.1/tokenizer.model
max_seq_len: 8192
Prompt 模板¶
通过将 Prompt 模板传入任何模型分词器来启用它。有关更多详细信息,请参阅Prompt 模板。
特殊 token¶
特殊 token 是模型特定的标记,用于对模型进行 Prompt。它们与 Prompt 模板不同,因为它们被分配了自己唯一的 token ID。关于特殊 token 和 Prompt 模板区别的详细讨论,请参阅Prompt 模板。
模型分词器会自动将特殊 token 添加到您的数据中,无需您进行额外配置。您还可以通过传入包含新特殊 token 映射的 JSON 文件路径来定制特殊 token 以进行实验。请注意,这不会修改底层的 tokenizer.model
来支持新的特殊 token ID - 您有责任确保分词器文件正确编码这些 token。另请注意,某些模型为了正常使用需要某些特殊 token 的存在,例如 Llama3 Instruct 中的 "<|eot_id|>"
。
例如,这里我们改变 Llama3 Instruct 中的 "<|begin_of_text|>"
和 "<|end_of_text|>"
token ID:
# tokenizer/special_tokens.json
{
"added_tokens": [
{
"id": 128257,
"content": "<|begin_of_text|>",
},
{
"id": 128258,
"content": "<|end_of_text|>",
},
# Remaining required special tokens
...
]
}
# In code
from torchtune.models.llama3 import llama3_tokenizer
tokenizer = llama3_tokenizer(
path="/tmp/Meta-Llama-3-8B-Instruct/original/tokenizer.model",
special_tokens_path="tokenizer/special_tokens.json",
)
print(tokenizer.special_tokens)
# {'<|begin_of_text|>': 128257, '<|end_of_text|>': 128258, ...}
# In config
tokenizer:
_component_: torchtune.models.llama3.llama3_tokenizer
path: /tmp/Meta-Llama-3-8B-Instruct/original/tokenizer.model
special_tokens_path: tokenizer/special_tokens.json
基础分词器¶
BaseTokenizer
是底层的字节对编码模块,执行原始字符串到 token ID 的实际转换及逆转换。在 torchtune 中,它们需要实现 encode
和 decode
方法,这些方法由模型分词器调用,用于在原始文本和 token ID 之间进行转换。
class BaseTokenizer(Protocol):
def encode(self, text: str, **kwargs: Dict[str, Any]) -> List[int]:
"""
Given a string, return the encoded list of token ids.
Args:
text (str): The text to encode.
**kwargs (Dict[str, Any]): kwargs.
Returns:
List[int]: The encoded list of token ids.
"""
pass
def decode(self, token_ids: List[int], **kwargs: Dict[str, Any]) -> str:
"""
Given a list of token ids, return the decoded text, optionally including special tokens.
Args:
token_ids (List[int]): The list of token ids to decode.
**kwargs (Dict[str, Any]): kwargs.
Returns:
str: The decoded text.
"""
pass
如果您加载任何模型分词器,您会发现它调用其底层的BaseTokenizer
来执行实际的编码和解码。
from torchtune.models.mistral import mistral_tokenizer
from torchtune.modules.transforms.tokenizers import SentencePieceBaseTokenizer
m_tokenizer = mistral_tokenizer("/tmp/Mistral-7B-v0.1/tokenizer.model")
# Mistral uses SentencePiece for its underlying BPE
sp_tokenizer = SentencePieceBaseTokenizer("/tmp/Mistral-7B-v0.1/tokenizer.model")
text = "hello world"
print(m_tokenizer.encode(text))
# [1, 6312, 28709, 1526, 2]
print(sp_tokenizer.encode(text))
# [1, 6312, 28709, 1526, 2]
使用 Hugging Face 分词器¶
有时,托管在 Hugging Face 上的分词器不包含与 torchtune 现有分词器类兼容的文件。在这种情况下,我们提供了 HuggingFaceBaseTokenizer
,用于解析 Hugging Face 的 tokenizer.json
文件,并定义与 torchtune 其他 BaseTokenizer
类相匹配的正确 encode
和 decode
方法。您还应传入 tokenizer_config.json
或 generation_config.json
的路径,这将使 torchtune 能够推断出 BOS 和 EOS token。继续以 Mistral 为例:
hf_tokenizer = HuggingFaceBaseTokenizer(
tokenizer_json_path="/tmp/Mistral-7B-v0.1/tokenizer.json",
tokenizer_config_json_path="/tmp/Mistral-7B-v0.1/tokenizer_config.json",
)
text = "hello world"
print(hf_tokenizer.encode(text))
# [1, 6312, 28709, 1526, 2]
模型分词器¶
ModelTokenizer
特定于某个模型。它们需要实现 tokenize_messages
方法,该方法将消息列表转换为 token ID 列表。
class ModelTokenizer(Protocol):
special_tokens: Dict[str, int]
max_seq_len: Optional[int]
def tokenize_messages(
self, messages: List[Message], **kwargs: Dict[str, Any]
) -> Tuple[List[int], List[bool]]:
"""
Given a list of messages, return a list of tokens and list of masks for
the concatenated and formatted messages.
Args:
messages (List[Message]): The list of messages to tokenize.
**kwargs (Dict[str, Any]): kwargs.
Returns:
Tuple[List[int], List[bool]]: The list of token ids and the list of masks.
"""
pass
它们之所以是模型特定的并与基础分词器不同,是因为它们添加了对模型进行 Prompt 所需的所有必要的特殊 token 或 Prompt 模板。
from torchtune.models.mistral import mistral_tokenizer
from torchtune.modules.transforms.tokenizers import SentencePieceBaseTokenizer
from torchtune.data import Message
m_tokenizer = mistral_tokenizer("/tmp/Mistral-7B-v0.1/tokenizer.model")
# Mistral uses SentencePiece for its underlying BPE
sp_tokenizer = SentencePieceBaseTokenizer("/tmp/Mistral-7B-v0.1/tokenizer.model")
text = "hello world"
msg = Message(role="user", content=text)
tokens, mask = m_tokenizer.tokenize_messages([msg])
print(tokens)
# [1, 733, 16289, 28793, 6312, 28709, 1526, 28705, 733, 28748, 16289, 28793]
print(sp_tokenizer.encode(text))
# [1, 6312, 28709, 1526, 2]
print(m_tokenizer.decode(tokens))
# [INST] hello world [/INST]
print(sp_tokenizer.decode(sp_tokenizer.encode(text)))
# hello world