LLM 中的 Byte Pair Encoding

Byte Pair Encoding in LLMs

在这篇文章里,我们会学习 BPE(Byte Pair Encoding,字节对编码):大多数现代大型语言模型(LLM)都会使用这种分词算法,在处理文本之前先把文本拆成更小的片段。

我们会理解 BPE 是什么,为什么需要它,以及它如何通过一个简单例子一步步工作。

我是 Amit ShekharOutcome School 创始人。我教过并辅导过很多开发者,他们靠自己的努力拿到了高薪技术岗位;我也帮助过许多科技公司解决各自独特的问题,并创建过很多被顶级公司使用的开源库。我热衷于通过开源、博客和视频分享知识。

我在 Outcome School 教 AI and Machine Learning

我们开始吧。

什么是 Tokenization?

在理解 BPE 之前,我们必须先理解 tokenization

当我们输入一句话,比如 “I love teaching AI” 时,我们看到的是词。但模型不能直接理解词。它处理的是数字。所以第一步,是把文本拆成叫作 token 的小片段。然后每个 token 会被转换成一个数字。这个把文本拆成 token 的过程,就叫 tokenization

可以把它想成一块巧克力。完整的巧克力就是句子。你掰下来的每一小块就是一个 token。模型一次处理多个 token。

现在问题来了:我们怎么决定从哪里切?每个 token 应该多大或多小?

这就是不同 tokenization 方法登场的地方。

问题:如何把文本拆成 token?

把文本拆成 token 有不同方式。我们先看两个最简单的方法,并理解为什么它们并不好用。

方法 1:词级 tokenization

最简单的想法是把每个词当成一个 token。

对于句子 “I love teaching”,token 会是:“I”、“love”、“teaching”。

这看起来很容易。但这个方法的问题是,模型需要知道语言里的每一个词。想想看,光英语就有几十万个词。还有人名、技术术语、其他语言里的词、拼写错误,以及每天都在被创造出来的新词。

如果模型看到一个从未见过的词,比如 “ChatGPT” 或 “tokenization”,它就不知道该怎么办。它会把整个未知词当成一个单独的 “unknown” token,并丢失所有含义。

方法 2:字符级 tokenization

另一个极端是把每个字符都当成一个 token。

对于句子 “I love”,token 会是:“I”、“ ”、“l”、“o”、“v”、“e”。

现在模型可以处理任何词,因为每个词都是由字符组成的,而唯一字符的数量相比词的数量要小得多。比如英语只有 26 个字母。即使加上数字、标点和空格,总数仍然很小。不会再有任何词完全 “unknown”。

但这个方法的问题是 token 太小。同一段文本,模型必须处理更多 token。一个 10 个词的句子可能变成 50 多个 token。这会让训练非常慢,也让模型更难理解含义,因为像 “l” 这样的单个字符本身几乎没有什么含义。

我们需要一种两全其美的方案:不要太大,也不要太小。所以,BPE 来救场了。

什么是 BPE(Byte Pair Encoding)?

BPE(Byte Pair Encoding) 是一种 tokenization 算法,它会把文本拆成介于字符和词之间的片段。

它的工作方式是:反复寻找相邻 token 中最常见的一对,然后把这一对合并成一个 token。

所以,BPE 从单个字符开始,不断合并最频繁出现的字符对,直到构建出一套由常见片段组成的词表。这些片段叫作子词(subword)。子词可以是完整词(比如 “the”),也可以是词的一部分(比如 “ing”、“un”、“tion”),甚至也可以是单个字符。

可以把它想成学习速记。刚开始记笔记时,你会把每个字母都写下来。但时间久了,你会发现某些组合经常出现,比如 “ing” 或 “tion”。于是你会为它们创建快捷写法。BPE 会自动做同样的事。

BPE 如何工作:一步一步看

学习这个概念最好的方式,是看一个例子。

假设我们有下面这段文本,想用它来构建词表:

low low low low low
lower lower
newest newest newest newest newest newest
widest widest widest

为了便于理解,我们先统计词频:

  • “low” 出现 5 次
  • “lower” 出现 2 次
  • “newest” 出现 6 次
  • “widest” 出现 3 次

步骤 1:从字符开始

我们先把每个词拆成单个字符。我们还会在每个词末尾加一个特殊的词尾符号 “_”,这样模型就知道词在哪里结束。

l o w _        (frequency: 5)
l o w e r _    (frequency: 2)
n e w e s t _  (frequency: 6)
w i d e s t _  (frequency: 3)

初始词表就是所有单个字符:{l, o, w, e, r, n, s, t, i, d, _}

步骤 2:找到最频繁的一对

现在,我们查看整段文本中所有相邻 token 对,并统计每一对出现了多少次。

为了便于理解,我们统计几个重要的相邻对:

  • “e s” 出现在 “newest”(6 次)和 “widest”(3 次)中 = 9 次
  • “s t” 出现在 “newest”(6 次)和 “widest”(3 次)中 = 9 次
  • “t _” 出现在 “newest”(6 次)和 “widest”(3 次)中 = 9 次
  • “l o” 出现在 “low”(5 次)和 “lower”(2 次)中 = 7 次
  • “o w” 出现在 “low”(5 次)和 “lower”(2 次)中 = 7 次

最频繁的相邻对是 “e s”,出现了 9 次。我们把这一对合并成一个 token:“es”。

步骤 3:合并并更新

把 “e s” 合并成 “es” 之后,我们的词现在长这样:

l o w _         (frequency: 5)
l o w e r _     (frequency: 2)
n e w es t _    (frequency: 6)
w i d es t _    (frequency: 3)

现在的词表是:{l, o, w, e, r, n, s, t, i, d, _, es}

这里可以看到,凡是 “e” 和 “s” 连在一起出现的地方,都被合并成了 “es”。

步骤 4:重复

我们重复同样的过程,找到下一个最频繁的相邻对。现在 “es t” 出现了 9 次(6 次来自 “newest”,3 次来自 “widest”)。我们把它合并成 “est”。

l o w _         (frequency: 5)
l o w e r _     (frequency: 2)
n e w est _     (frequency: 6)
w i d est _     (frequency: 3)

词表:{l, o, w, e, r, n, s, t, i, d, _, es, est}

我们会不断重复这个过程。下一次合并可能是把 “est ” 合成 “est”。然后把 “l o” 合成 “lo”。再把 “lo w” 合成 “low”。以此类推。

每次合并,词表都会增加一个 token,而文本会用更少、更大的片段来表示。

步骤 5:达到目标后停止

我们会持续合并,直到达到想要的词表大小。这个大小是我们预先选择的一个数字。现代 LLM 通常使用 32,000 到 256,000 个 token 的词表。例如,LLaMA 3 使用大约 128K token 的词表,而 Gemma 使用大约 256K token。

最终词表会包含几类东西:

  • 像 “the”、“is”、“and” 这样的常见完整词
  • 像 “ing”、“tion”、“est”、“un” 这样的常见子词
  • 用于罕见组合的单个字符

这就是 BPE 构建词表的方式。

给你的一条简短提醒

无论你在哪个技术领域工作,都应该熟悉这些主题:

  • LLM
  • RAG
  • MCP
  • Agent
  • Fine-tuning
  • Quantization

我们在一个视频里把它们串了起来:

AI Engineering Explained: LLM, RAG, MCP, Agent, Fine-Tuning, and Quantization

不用停下来读,先收藏,等有时间再看。未来的你会感谢现在的你。

现在,我们回到主题。

BPE 如何处理新文本

BPE 训练完成后,它会得到两样东西:一个词表,以及一份有顺序的合并规则列表。顺序很重要。训练期间,BPE 按特定顺序学到了这些合并:先把 “e s” 合成 “es”,再把 “es t” 合成 “est”,再把 “l o” 合成 “lo”,以此类推。这个确切顺序会被保存下来。

现在,当 BPE 需要处理新文本时,它不是简单地去词表里查子词。相反,它会按照训练时学到的同一顺序,重新播放同一组合并规则

假设我们想把 “lowest” 这个词拆成 token。它的过程如下:

开始: 把这个词拆成单个字符:l o w e s t

应用合并规则 1(把 “e s” 合成 “es”):l o w es t

应用合并规则 2(把 “es t” 合成 “est”):l o w est

应用合并规则 3(把 “l o” 合成 “lo”):lo w est

应用合并规则 4(把 “lo w” 合成 “low”):low est

结果:“lowest” 被拆成 [“low”, “est”]。

类似地,对于 “newer” 这个词:

开始: n e w e r

合并规则会按顺序应用。这个词中匹配到相邻对的规则会一步步被应用,最终得到:[“new”, “er”]。

对于像 “widestness” 这样的罕见词:

开始: w i d e s t n e s s

合并规则会按顺序应用。像 “es” 和 “est” 这样的常见合并,只要匹配上就会触发。剩下的字符会保持为更小的片段。这个词会被拆成词表中已知的子词。

为什么顺序很重要? 因为不同的合并顺序可能产生不同结果。如果我们只是做贪心查找,也就是尝试寻找词表中最长的匹配子词,就可能得到和模型预期不同的分词结果。通过严格按照训练顺序重放合并规则,我们每次都能得到一致且正确的分词结果。

这就是 BPE 的妙处。它永远不会遇到完全未知的词。即使一个词从未见过,BPE 也可以通过应用合并规则把它拆成更小的片段。在最坏情况下,它会退回到单个字符,但对于常见语言,这种情况很少发生。问题解决了。

这就是 BPE 处理新文本的方式。

如果我们想深入学习 Tokenization 和 LLM 内部机制,并从零开始构建一个大型语言模型(LLM),可以看看 Outcome School 的 AI and Machine Learning Program

为什么现代 LLM 使用 BPE

BPE 是大多数现代 LLM 使用的 tokenization 算法。原因如下:

处理未知词: BPE 可以处理任何词,即使这个词从未见过,也能把它拆成已知的子词片段。没有任何词会完全 “unknown”。

词表大小高效: BPE 不需要为每一个可能出现的词准备数百万个条目,而是创建一个 32,000 到 256,000 个 token 的紧凑词表,就能表示任意文本。这让我们的工作轻松很多。

平衡含义和效率: 像 “the” 和 “is” 这样的常见词会保留为单个 token,处理快,也有明确含义。罕见词会被拆成子词片段,仍然可理解、可管理。这给了我们两全其美的效果。

跨语言工作: BPE 在字符层面工作,所以可以处理任何语言、任何文字系统和任何特殊字符。同一个算法可以用于英语、中文、阿拉伯语和代码,而且算法本身不需要任何改变。

模型使用的是 BPE 的某种变体,但核心思想保持不变:BPE 或它的变体,位于现代 LLM 读取和处理文本方式的核心。

现在,我们已经理解了 BPE(Byte Pair Encoding)以及它如何工作。

准备 AI Engineering 面试:AI Engineering Interview Questions

今天就到这里。

谢谢

Amit Shekhar Outcome School 创始人