LLM

minbpe:最小的字节对编码实现

LLM标记化中常用的字节对编码(BPE)算法的最小、干净的代码。

2024年2月26日
分享

LLM 标记化中常用的(字节级)字节对编码 (BPE) 算法的最小、干净的代码。BPE 算法是“字节级”的,因为它在 UTF-8 编码的字符串上运行。

该算法通过 GPT-2 论文和 OpenAI 发布的相关 GPT-2代码在 LLM 中得到推广。[森里奇等人,2015 年]被引用为 BPE 在 NLP 应用中使用的原始参考文献。如今,所有现代 LLM(例如 GPT、Llama、Mistral)都使用此算法来训练他们的标记器。

该存储库中有两个 Tokenizer,它们都可以执行 Tokenizer 的 3 个主要功能:1)训练 tokenizer 词汇并合并给定文本,2)从文本编码到令牌,3)从令牌解码到文本。存储库的文件如下:

  1. minbpe/base.py:实现该类Tokenizer,该类是基类。它包含trainencode、 和decode存根、保存/加载功能,还有一些常见的实用功能。这个类不应该直接使用,而是要继承。
  2. minbpe/basic.py:实现BasicTokenizer,直接在文本上运行的 BPE 算法的最简单实现。
  3. minbpe/regex.py:实现RegexTokenizer,通过正则表达式模式进一步分割输入文本,这是一个预处理阶段,在标记化之前按类别(例如:字母、数字、标点符号)分割输入文本。这确保不会发生跨类别边界的合并。这是在 GPT-2 论文中引入的,并从 GPT-4 开始继续使用。此类还处理特殊标记(如果有)。
  4. minbpe/gpt4.py:实现GPT4Tokenizer,该类是RegexTokenizer的一个轻量级包装器,它精确地再现了tiktoken库中 GPT-4 的标记化。包装处理有关恢复标记生成器中的精确合并的一些细节,以及处理一些不幸的(并且可能是历史的?)1 字节标记排列。

最后,脚本train.py在输入文本tests/taylorswift.txt(这是她的维基百科条目)上训练两个主要分词器,并将词汇保存到磁盘以进行可视化。该脚本在我的 (M1) MacBook 上运行大约需要 25 秒。

上面的所有文件都非常短且注释详尽,并且在文件底部还包含使用示例。

快速开始

作为最简单的例子,我们可以复制维基百科关于 BPE 的文章如下:

from minbpe import BasicTokenizer
tokenizer = BasicTokenizer()
text = "aaabdaaabac"
tokenizer.train(text, 256 + 3) # 256 are the byte tokens, then do 3 merges
print(tokenizer.encode(text))
# [258, 100, 258, 97, 99]
print(tokenizer.decode([258, 100, 258, 97, 99]))
# aaabdaaabac
tokenizer.save("toy")
# writes two files: toy.model (for loading) and toy.vocab (for viewing)

根据 Wikipedia,对输入字符串“aaabdaaabac”运行 bpe 进行 3 次合并会生成字符串:“XdXac”,其中 X=ZY、Y=ab 和 Z=aa。需要注意的棘手之处在于,minbpe 始终将 256 个单独字节分配为令牌,然后根据需要合并字节。所以对于我们来说 a=97、b=98、c=99、d=100(它们的ASCII值)。然后,当 (a,a) 合并到 Z 时,Z 将变为 256。同样,Y 将变为 257,X 变为 258。因此,我们从 256 字节开始,进行 3 次合并以获得上面的结果,预期输出为[258、100、258、97、99]。

推理:GPT-4比较

我们可以验证RegexTokenizertiktoken的 GPT-4 分词器具有相同的功能,如下所示:

text = "hello123!!!? (안녕하세요!) 😉"

# tiktoken
import tiktoken
enc = tiktoken.get_encoding("cl100k_base")
print(enc.encode(text))
# [15339, 4513, 12340, 30, 320, 31495, 230, 75265, 243, 92245, 16715, 57037]

# ours
from minbpe import GPT4Tokenizer
tokenizer = GPT4Tokenizer()
print(tokenizer.encode(text))
# [15339, 4513, 12340, 30, 320, 31495, 230, 75265, 243, 92245, 16715, 57037]

(你必须执行pip install tiktoken才能运行)。在底层,GPT4Tokenizer只是RegexTokenizer的一个轻量级的包装器,传递 GPT-4 的合并和特殊标记。我们还可以确保特殊令牌得到正确处理:

text = "<|endoftext|>hello world"

# tiktoken
import tiktoken
enc = tiktoken.get_encoding("cl100k_base")
print(enc.encode(text, allowed_special="all"))
# [100257, 15339, 1917]

# ours
from minbpe import GPT4Tokenizer
tokenizer = GPT4Tokenizer()
print(tokenizer.encode(text, allowed_special="all"))
# [100257, 15339, 1917]

请注意,就像 tiktoken 一样,我们必须在编码调用中显式声明使用和解析特殊令牌的意图。否则,这可能会成为一个主要的枪口,无意中用特殊令牌标记攻击者控制的数据(例如用户提示)。allowed_special参数可以设置为“all”、“none”或允许的特殊标记列表。

训练

与 tiktoken 不同,此代码允许您训练自己的标记器。原则上,据我所知,如果您在词汇量为 100K 的大型数据集上进行RegexTokenizer训练,您将重现 GPT-4 分词器。

您可以遵循两条路径。首先,您不希望使用正则表达式模式分割和预处理文本的复杂性,并且您也不关心特殊标记。在这种情况下,请访问BasicTokenizer。您可以对其进行训练,然后进行编码和解码,例如如下所示:

from minbpe import BasicTokenizer
tokenizer = BasicTokenizer()
tokenizer.train(very_long_training_string, vocab_size=4096)
tokenizer.encode("hello world") # string -> tokens
tokenizer.decode([1000, 2000, 3000]) # tokens -> string
tokenizer.save("mymodel") # writes mymodel.model and mymodel.vocab
tokenizer.load("mymodel.model") # loads the model back, the vocab is just for vis

如果您想效仿 OpenAI 的文本标记器,最好采用他们使用正则表达式模式按类别分割文本的方法。GPT-4 模式默认带RegexTokenizer,因此您只需执行以下操作即可:

from minbpe import RegexTokenizer
tokenizer = RegexTokenizer()
tokenizer.train(very_long_training_string, vocab_size=32768)
tokenizer.encode("hello world") # string -> tokens
tokenizer.decode([1000, 2000, 3000]) # tokens -> string
tokenizer.save("tok32k") # writes tok32k.model and tok32k.vocab
tokenizer.load("tok32k.model") # loads the model back from disk

当然,您希望根据数据集的大小来更改词汇表大小。

特殊令牌。最后,您可能希望向标记生成器添加特殊标记。使用register_special_tokens函数注册它们。例如,如果您使用 vocab_size 为 32768 进行训练,则前 256 个标记是原始字节标记,接下来的 32768-256 个标记是合并标记,在这些标记之后您可以添加特殊标记。最后一个“真实”合并令牌的 id 为 32767 (vocab_size - 1),因此您的第一个特殊令牌应该紧随其后,其 id 恰好为 32768。所以:

from minbpe import RegexTokenizer
tokenizer = RegexTokenizer()
tokenizer.train(very_long_training_string, vocab_size=32768)
tokenizer.register_special_tokens({"<|endoftext|>": 32768})
tokenizer.encode("<|endoftext|>hello world", allowed_special="all")

当然,您也可以根据需要添加更多令牌。最后,我想强调,我努力保持代码本身的干净、可读和可修改。您不应该害怕阅读代码并理解它是如何工作的。这些测试也是寻找更多使用示例的好地方。

测试

我们使用 pytest 库进行测试。它们全部位于该tests/目录中。如果您还没有安装 pytest,首先执行pip install pytest,然后:

$ pytest -v .

运行测试。(-v 是详细的版本,稍微漂亮一些)。

练习

对于那些尝试学习 BPE 的人,这里是建议的进阶练习,帮助您逐步构建自己的 minbpe。请参阅exercise.md

讲座

我在这个 YouTube 视频的存储库中构建了代码。您还可以在lecture.md中找到本讲座的文本形式。

来自:https://github.com/karpathy/minbpe

更多文章

Git分支管理工具,从头开始构建,适合现代工作流程。

2024年2月23日 · git
sc
Stability AI出品的新一代文生图模型。
2024年2月22日 · AIGC AI
nanogpt
OpenAI前研究员Karpathy的极简版GPT。
2024年2月20日 · GPT
maybe
完全透明、开源、社区支持的个人理财应用程序。
2024年2月19日 · 财务 个人理财