11. Tokenizer 和 Embedding 有什么区别?BPE 的基本原理是什么?

整理 Tokenizer 与 Embedding 的区别,以及 BPE 的基本原理。

简单回答

Tokenizer 负责把原始文本切分成 token 序列(本质是字符串到整数 ID 的映射),Embedding 负责把每个 token ID 映射为一个稠密向量。两者是串行关系:先 Tokenize 再 Embed。BPE(Byte Pair Encoding)是最主流的分词算法,核心思想是从字符级开始,反复合并出现频率最高的相邻 token 对,直到词表达到预设大小。

详细解释

Tokenizer 和 Embedding 的关系

整个流程是:原始文本 → Tokenizer → token ID 序列 → Embedding 层 → 向量序列。Tokenizer 是纯规则/统计的预处理步骤,不包含可学习参数(词表本身是训练好的,但推理时是固定的查表操作)。Embedding 层是模型的第一层,是一个可学习的查找表(lookup table),维度是 vocab_size × d_model。

BPE 的原理

BPE 的训练过程很直觉:

第一步,从最小粒度开始。最初词表只有基本字符(或 byte 级别的 256 个单元)。第二步,统计整个训练语料中所有相邻 token 对的出现频率。第三步,把频率最高的那一对合并成一个新 token,加入词表。第四步,重复这个过程,直到词表大小达到预设值(比如 32K、64K、128K)。

举个例子,如果语料中 "t" 和 "h" 经常挨着出现,就合并成 "th";后续 "th" 和 "e" 又经常一起出现,就合并成 "the"。

Byte-level BPE

现代大模型(GPT、LLaMA 等)普遍用的是 Byte-level BPE,也就是初始词表是 256 个 byte 而不是 Unicode 字符。好处是永远不会遇到 OOV(out-of-vocabulary)问题,任何输入都能被编码。

词表大小的权衡

词表太小,常见词要拆成很多 token,序列变长,推理变慢,也浪费上下文窗口。词表太大,Embedding 层参数膨胀,低频 token 训练不充分。主流模型词表大小在 32K-150K 之间,LLaMA 用 32K,Qwen 2 用 150K+(主要为了更好的多语言覆盖和代码效率)。

常见混淆

Tokenizer 不是分词器(中文 NLP 的"分词"是 word segmentation),BPE 切出来的不一定是完整的词,可能是 subword 片段。另外,Embedding 层和模型最后的 output projection(lm_head)在很多模型里是权重共享的(weight tying),但也有模型选择不共享。

面试时可以这样答

Tokenizer 和 Embedding 是两个阶段的事情。Tokenizer 把原始文本切成 token 序列,输出的是整数 ID;Embedding 层把每个 ID 查表映射成一个稠密向量,这个向量才是模型真正处理的输入。

现在主流的分词算法是 BPE,Byte Pair Encoding。它的训练过程其实很简单:从最小单元开始,反复找语料中频率最高的相邻 token 对,合并成新 token,直到词表大小达到目标。现代大模型基本都用 Byte-level BPE,初始单元是 256 个 byte,好处是不会有 OOV 问题。

词表大小的选择是个工程权衡。太小的话序列变长,浪费上下文窗口和推理时间;太大的话 Embedding 参数多,低频 token 训不充分。LLaMA 用 32K,Qwen 2 用了 15 万级别的大词表,主要是为了多语言和代码场景的编码效率。

还有一个细节,很多模型的 Embedding 层和最后的 lm_head 是共享权重的,叫 weight tying,但也有模型选择不共享,这个选择会影响参数量和效果。

常见追问

  1. 词表大小对中文大模型的影响特别大,为什么?LLaMA 处理中文效率低的根本原因是什么?
  2. SentencePiece 和 HuggingFace Tokenizers 在实现上有什么区别?
  3. 如果要给一个已有模型扩展词表(比如加中文 token),具体怎么做?有什么坑?