跳转至

三:Tokenizer

来源:http://mp.weixin.qq.com/s?__biz=MzYyNTk3Njg1NA==&mid=2247483754&idx=1&sn=a7af13deb4c6fec2ad995778d5837ca1&chksm=f01eb213c7693b05d476cf6053c2d97f891d1196caf775e99a7936495af54ffb51012e3754ed#rd

1. 学习定位

Tokenizer 是大语言模型从文本世界进入数字世界的入口。模型不能直接处理字符串,必须先把文本切成 token,再把 token 映射成 token id,随后通过 embedding table 变成连续向量。 第三天重点是 subword-based tokenizer,也就是介于字符级和词级之间的子词分词。现代 LLM 大多使用某种子词 tokenizer,例如 BPE、byte-level BPE、WordPiece、Unigram/SentencePiece 或它们的变体。 本日需要建立的知识闭环是:

raw text
-> normalization
-> pre-tokenization
-> subword segmentation
-> token ids
-> special tokens / chat template
-> embeddings
面试中 Tokenizer 常被问到这些角度: - 为什么 LLM 不直接按词或字符建模。

  • BPE、WordPiece、Unigram 的核心思想和区别。

  • 子词 tokenizer 如何缓解 OOV 问题。

  • 中文、多语言、代码、emoji 为什么会影响 token 数。

  • tokenization 如何影响上下文长度、训练成本、推理成本和模型效果。

  • special tokens、BOS/EOS/PAD/UNK、chat template 的作用。

  • tokenizer 变更为什么会影响模型 embedding 和兼容性。

2. Tokenizer 在 LLM 流程中的位置

LLM 的文本处理流程通常是:

用户文本
  -> tokenizer.encode()
  -> input_ids: [B, T]
  -> embedding table lookup
  -> hidden states: [B, T, d_model]
  -> Transformer
  -> logits: [B, T, V]
  -> decoding
  -> output_ids
  -> tokenizer.decode()
  -> 输出文本
Tokenizer 的编码方向是从文本到 token id,解码方向是从 token id 回到文本。

encode: "hello world" -> [15339, 1917]
decode: [15339, 1917] -> "hello world"
不同模型的 tokenizer 不一定兼容。即使输入字符串相同,不同 tokenizer 也可能得到完全不同的 token 序列。模型训练时绑定了某个 tokenizer 和词表,因此推理和微调时必须使用匹配的 tokenizer。

3. Token、Vocabulary 与 Token ID

token 是 tokenizer 输出的基本单位,可以是字符、子词、完整单词、标点、空格模式、字节片段或特殊符号。 vocabulary 是 tokenizer 能识别的 token 集合。每个 token 对应一个整数 id。词表大小记为 V

vocab:
  "<pad>" -> 0
  "<bos>" -> 1
  "hello" -> 15339
  " world" -> 1917
模型的输入 embedding table 形状通常是:

[V, d_model]
LM head 输出 logits 的最后一维通常也是词表大小:

logits: [B, T, V]
因此 tokenizer 的词表大小会直接影响 embedding 参数量和输出层计算成本。

4. 字符级、词级与子词级

早期直觉上有三种粒度:

字符级 tokenizer:  NLP -> N, L, P
词级 tokenizer:    I love NLP -> I, love, NLP
子词级 tokenizer:  unbelievable -> un, believe, able
字符级优点是几乎没有 OOV,可以表示任何文本;缺点是序列很长,模型需要更多步才能建模长词和语义单元。 词级优点是 token 更接近自然词,序列更短;缺点是词表巨大,长尾词、拼写变化、新词、代码标识符、网址、罕见实体容易 OOV。 子词级折中两者。高频词可以作为完整 token,低频词可以拆成多个子词。它既能控制词表大小,又能处理未见词,因此成为现代 NLP 和 LLM 的主流方案。

5. Subword Tokenizer 的基本思想

Subword tokenizer 的核心思想是:

高频片段保留为较长 token,低频或未知词拆成更小片段。
例如:

unbelievable -> un + believable
believable -> believe + able
对模型来说,子词粒度可以让它共享形态信息。例如 playplayingplayed 之间有公共子词片段,模型可以更容易学习词形变化。 对工程来说,子词 tokenizer 可以在有限词表下覆盖开放词汇,缓解 OOV,同时保持序列长度不过分膨胀。

6. Tokenizer 训练与使用的两个阶段

Tokenizer 有训练阶段和使用阶段。 训练阶段根据语料构建词表和规则:

training corpus
-> normalization / pre-tokenization
-> learn vocab and merge/probability rules
-> tokenizer files
使用阶段用已经训练好的 tokenizer 对文本编码:

raw text
-> same normalization / pre-tokenization
-> apply learned rules
-> token ids
注意,训练好模型后,tokenizer 通常不能随意替换。更换 tokenizer 会改变 token id 序列,也会改变 embedding table 的语义对齐。除非进行专门的词表扩展、embedding 初始化和继续训练,否则会破坏模型兼容性。

7. Normalization

normalization 是对原始文本做规范化处理。常见操作包括: - Unicode 规范化,例如 NFC、NFKC。

  • 大小写转换,例如 lowercasing。

  • 去除或规范化重音符号。

  • 空白字符规范化。

  • 全角/半角转换。

  • 特殊符号清洗。

normalization 的目标是减少无意义变体,让相同语义的文本更可能映射到一致 token 序列。 但 normalization 也可能损失信息。例如大小写在代码、实体名、德语名词中可能有意义;全角/半角、emoji、特殊标点在某些任务中也可能承载信息。因此 tokenizer 的 normalization 策略必须与模型目标和语料匹配。

8. Pre-tokenization

pre-tokenization 是在正式子词切分前,先按空格、标点、数字、字母类别或语言规则做粗切分。它决定后续子词算法在哪些边界内工作。 英文常见 pre-tokenization 会利用空格和标点。中文没有天然空格分词边界,常需要不同策略。代码文本可能需要保留下划线、缩进、换行、括号等结构。 很多现代 tokenizer 会把空格作为 token 的一部分。例如 GPT 类 byte-level BPE 中,"hello"" hello" 可能是不同 token。这让 tokenizer 可以保留词前空格信息。 pre-tokenization 的边界会显著影响最终 tokenization。例如是否把 New YorkC++foo_bar2026-05-23、URL、emoji 当成连续片段,会影响 token 数和语义建模。

9. BPE 的核心思想

BPE 是 Byte Pair Encoding 的缩写。用于 NLP 时,它从较小的基本单位开始,不断合并语料中最频繁相邻 pair,直到达到目标词表大小或合并次数。 简化训练流程:

初始:把词拆成字符或字节
统计:统计相邻 token pair 频率
合并:选择频率最高的 pair 合成新 token
重复:继续统计和合并
输出:词表 + merge rules
示例:

l o w
l o w e r
n e w e r

如果 "l o" 最频繁,合并为 "lo"
如果 "lo w" 最频繁,合并为 "low"
使用阶段不是重新统计频率,而是按训练得到的 merge rules 对新文本进行合并。 BPE 的直觉是:高频相邻片段应该成为更长 token,低频词则保留为多个较短片段。

10. BPE 编码阶段

BPE tokenizer 训练后会得到 merge rule 的优先级列表。编码新文本时,一般流程是:

text
-> normalization
-> pre-tokenization
-> 初始拆成字符或字节单位
-> 按 merge priority 逐步合并可合并 pair
-> 得到 subword tokens
-> 查 vocab 得到 token ids
编码阶段的关键点是:BPE 使用训练好的合并规则,不会根据当前输入动态重新学习规则。 如果一个词没有作为完整 token 出现在词表中,它仍可以被拆成多个子词甚至字节级 token,从而避免传统词级 OOV。

11. Byte-level BPE

byte-level BPE 把初始单位设为字节,而不是 Unicode 字符。这样理论上任何文本都可以编码,因为任何字符串都可以表示为 UTF-8 字节序列。 byte-level BPE 的优点: - 覆盖能力强,几乎不需要 <unk>

  • 能处理多语言、emoji、乱码、罕见字符、代码符号。

  • 对开放互联网文本更鲁棒。

缺点: - 对某些非英文文本,一个字符可能对应多个字节,token 效率可能较低。

  • 人类可读的字符边界和 byte token 边界不一定一致。

GPT 系列和 OpenAI 的 tiktoken 使用的是 byte-level BPE 或相关变体。面试中需要知道:byte-level 的核心优势是开放字符覆盖,代价是某些语言下 token 数可能变多。

12. WordPiece

WordPiece 是 BERT 等模型常用的 subword tokenizer。它也会构建子词词表,但学习策略和 BPE 不完全相同。 BPE 通常直接合并频率最高的 pair。WordPiece 更强调选择能最大化训练语料似然或提升语言模型概率的子词。实际工程解释中,可以把 WordPiece 理解为一种基于统计评分选择子词的 greedy 子词分割方法。

img

img

WordPiece 常用 ## 标记词中续接子词:

unaffable -&gt; un + ##aff&nbsp;+ ##able
## 表示该 token 不是一个新词的开头,而是接在前一个 token 后面。这有助于解码和保持词边界信息。

13. Unigram 与 SentencePiece

Unigram tokenizer 的思路和 BPE/WordPiece 不同。它不是从小单位开始逐步合并,而是先准备一个较大的候选子词词表,并为每个子词学习概率,然后逐步删除对语料似然影响较小的 token,最终得到目标词表。 - 初始构建超大候选子词库,囊括字符、长短词组

  • 基于单词概率计算整体文本对数似然

  • 迭代移除对整体概率贡献最小的子词

  • 压缩到预设词表规模停止

  • 分词时动态选概率最高的拆分方案

img

img

编码时,Unigram 可以把一个字符串切成多种可能的子词序列,并选择概率最高或代价最低的分割。 SentencePiece 是常用 tokenizer 工具,支持 Unigram 和 BPE。它的重要特点是把文本视为原始 Unicode 字符序列处理,不依赖预先按空格分词,因此对日语、中文等无空格语言更友好。 SentencePiece 常用特殊符号 表示空格边界:

"Hello world" -&gt; ▁Hello ▁world
这让空格也成为可学习和可逆编码的一部分。

14. BPE、WordPiece、Unigram 对比

三者都属于 subword tokenizer,但训练思想不同:

BPE:
&nbsp; 从小单位开始,反复合并高频相邻 pair。
&nbsp; 直观、简单、常用于 GPT 类模型。

WordPiece:
&nbsp; 选择能提升语料概率的子词,常用 greedy longest-match 编码。
&nbsp; BERT 系列常用。

Unigram:
&nbsp; 从大候选词表开始,学习子词概率并删除低贡献 token。
&nbsp; SentencePiece 常用。
从面试角度,不需要把所有算法推导到极细,但必须能讲清: - BPE 是 merge-based。

  • WordPiece 是 likelihood/score-based 的子词选择。

  • Unigram 是概率模型和 pruning 思路。

  • 它们都在词级和字符级之间做折中。

15. Special Tokens

special tokens 是 tokenizer 词表中的控制符,不一定对应普通自然语言文本。常见 special tokens 包括:

&lt;bos&gt; / &lt;s&gt; &nbsp; &nbsp; &nbsp; 序列开始
&lt;eos&gt; / &lt;/s&gt; &nbsp; &nbsp; &nbsp;序列结束
&lt;pad&gt; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; padding
&lt;unk&gt; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; unknown token
&lt;mask&gt; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;mask language modeling
&lt;sep&gt; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; segment separator
&lt;cls&gt; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; classification token
&lt;|user|&gt; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;chat role token
&lt;|assistant|&gt; &nbsp; &nbsp; chat role token
特殊 token 的作用取决于模型架构和训练目标。BERT 需要 [CLS][SEP][MASK]。decoder-only chat model 通常需要 BOS/EOS、role tokens、turn separators 等。 special token 必须和模型训练时的格式一致。错误添加或遗漏 EOS、role token、chat template,可能导致模型输出异常、角色混乱、无法停止或格式不稳定。

16. Chat Template 与 Tokenizer

chat model 的输入不是简单把用户问题直接 tokenize,而是先通过 chat template 转换成模型训练时见过的对话格式。 示意:

messages = [
&nbsp; {"role": "system", "content": "You are helpful."},
&nbsp; {"role": "user", "content": "Explain BPE."}
]

template -&gt;
&lt;|system|&gt;
You are helpful.
&lt;|user|&gt;
Explain BPE.
&lt;|assistant|&gt;
再进行 tokenization。 chat template 的作用是把角色、轮次、边界、助手开始位置显式编码进 token 序列。不同模型的 chat template 不同,不能随意混用。

17. OOV、UNK 与开放词汇

OOV 是 out-of-vocabulary,指输入中出现了词表没有的词。词级 tokenizer 容易 OOV,因为新词、拼写错误、罕见实体、网址、代码标识符几乎无限。 subword tokenizer 缓解 OOV 的方式是把未知词拆成已知子词。例如:

ChatGPTization -&gt; Chat + GPT + ization
byte-level tokenizer 进一步把任何字符拆到字节层面,几乎可以避免 <unk>。 但“可编码”不等于“模型理解得好”。罕见词如果被拆成很多碎片,模型仍可能难以准确建模,且会消耗更多上下文长度。

18. 中文与多语言 Tokenization

中文没有英文那样的空格词边界。中文 tokenizer 可能按字、词、子词、字节或 SentencePiece 方式切分。 例如:

大语言模型
-&gt; 大 / 语言 / 模型
-&gt; 大 / 语 / 言 / 模 / 型
-&gt; 大语言 / 模型
不同切分会影响 token 数、语义粒度和训练效率。 多语言 tokenizer 还需要处理不同文字系统、重音符号、复合词、空格规则、emoji 和混合文本。词表容量有限时,如果英文占据太多高频 token,其他语言可能被切得更碎,导致非英文 token 效率较低。 这也是为什么同样字符数的中文、英文、代码、emoji,在不同模型上 token 数可能差异很大。

19. 代码、数字、URL 与结构化文本

代码和结构化文本对 tokenizer 很敏感。 代码中有大量符号:

def foo_bar(x):
&nbsp; &nbsp; return x + 1
下划线、缩进、换行、括号、运算符、驼峰命名都会影响 tokenization。一个代码友好的 tokenizer 应该尽量高效表示常见代码模式,否则会导致序列变长、上下文浪费和建模困难。 数字和日期也类似:

2026-05-23
3.1415926
不同 tokenizer 可能把它们切成完整数字、多个数字片段或单字符。数字切分方式会影响模型做精确复制、比较和计算的能力。 URL、路径、JSON、Markdown、LaTeX 等结构化文本也常出现大量符号和长尾片段,需要关注 token 数和边界保留。

20. Tokenizer 对上下文长度和成本的影响

LLM 的上下文窗口通常按 token 计数,而不是按字符或词计数。 如果一个文本被切成更多 token,它会: - 占用更多上下文窗口。

  • 增加 prefill 计算。

  • 增加 KV cache 显存。

  • 增加 API 计费成本。

  • 在训练中增加序列长度和训练成本。

因此 tokenizer 的 token efficiency 很重要。对同一段中文、英文、代码,不同 tokenizer 可能产生不同 token 数。面试中可以把 tokenizer 理解为模型成本和可用上下文长度的前置决定因素之一。

21. Tokenizer 与训练数据

Tokenizer 通常在大规模语料上训练。语料分布会影响词表内容。 如果训练语料以英文网页为主,英文常见词和空格模式会被高效编码;如果语料包含大量代码,代码符号和常见标识符片段可能进入词表;如果语料多语言均衡,词表会给不同语言分配更多容量。 训练 tokenizer 时需要考虑: - 语料代表性。

  • 语言分布。

  • 领域分布,例如代码、数学、医学、法律。

  • 词表大小。

  • special tokens 设计。

  • normalization 策略。

  • 是否 byte-level。

Tokenizer 不是模型训练后才随便选的工具,而是预训练体系的一部分。

22. Tokenizer 与 Embedding 兼容性

模型的 embedding table 行号与 tokenizer token id 一一对应。

tokenizer: "hello" -&gt; 15339
embedding_table[15339] -&gt; "hello" 的向量
如果更换 tokenizer,即使词表大小相同,id 到 token 的映射也可能不同。原来 15339 表示 "hello",新 tokenizer 中可能表示别的 token,embedding 语义会错位。 因此: - 不能随意替换预训练模型的 tokenizer。

  • 扩展词表后需要 resize embedding。

  • 新增 token 的 embedding 需要初始化并训练。

  • 微调和推理必须使用模型对应 tokenizer。

23. Offset Mapping 与标签对齐

在信息抽取、NER、QA 等任务中,常需要把 token 与原始字符位置对齐。tokenizer 通常可以返回 offset mapping:

token -&gt; (char_start, char_end)
子词切分会让一个原始词对应多个 token。做 token-level 标签时,需要决定: - 只给第一个 subword 打标签。

  • 所有 subword 复制同一个标签。

  • 后续 subword 标记为 ignore。

如果对齐处理错误,训练标签会错位,评测指标也会失真。中文和混合文本中,这个问题尤其常见。

24. Padding、Truncation 与 Attention Mask

Tokenizer 通常还负责构造模型输入所需的辅助张量:

input_ids
attention_mask
token_type_ids
labels
padding 把不同长度样本补齐到同一长度。truncation 把超过最大长度的样本截断。attention mask 标记哪些位置是真实 token,哪些位置是 padding。 常见问题: - 截断导致关键信息丢失。

  • padding token 参与 loss。

  • attention mask 与 input_ids 长度不一致。

  • left padding/right padding 与 decoder-only 推理缓存不匹配。

训练和推理时要保证 padding/truncation 策略与模型架构和任务匹配。

25. Fast Tokenizer 与 Slow Tokenizer

Hugging Face 中常见 fast tokenizer 和 slow tokenizer。fast tokenizer 通常基于 Rust tokenizers 库,速度更快,并支持更完整的 offset mapping 等功能。slow tokenizer 多为 Python 实现,便于理解和调试,但速度较慢。 在大规模训练、数据预处理和在线服务中,tokenization 速度可能成为瓶颈。需要关注批量编码、缓存、并行处理和 tokenizer 线程安全。

26. 常见工程问题

Tokenizer 工程中常见问题包括: - 推理时 tokenizer 与模型不匹配。

  • 忘记添加或错误添加 special tokens。

  • chat template 用错,导致模型角色混乱。

  • 训练 label 没有 mask 掉 prompt 或 padding。

  • 截断策略导致答案或关键上下文被截断。

  • 中文、emoji、代码 token 数异常膨胀。

  • 新增 token 后没有 resize embedding。

  • token-level 标注任务中 subword 标签对齐错误。

  • decode 时没有跳过 special tokens,输出包含控制符。

这些问题通常不会表现为语法错误,而是表现为效果差、loss 异常、输出格式错、成本升高或模型不停止。

27. 面试中的核心表达

Tokenizer 的面试表达可以压缩为:

Tokenizer 把原始文本映射为模型可处理的 token ids。
现代 LLM 多使用 subword tokenizer,在词级和字符级之间折中。
BPE 通过高频 pair 合并学习词表。
WordPiece 更强调基于似然或评分选择子词。
Unigram 从候选词表出发学习子词概率并裁剪。
byte-level tokenizer 能覆盖任意文本,但可能牺牲部分语言的 token 效率。
special tokens 和 chat template 是模型输入格式的一部分。
tokenizer 会影响上下文长度、成本、训练效率、OOV、标签对齐和模型兼容性。

28. 参考资料

  • Hugging Face Tokenizer Summary: https://huggingface.co/docs/transformers/v4.42.0/tokenizer_summary

  • Hugging Face Tokenizers Documentation: https://huggingface.co/docs/tokenizers/

  • Neural Machine Translation of Rare Words with Subword Units: https://arxiv.org/abs/1508.07909

  • SentencePiece: A simple and language independent subword tokenizer and detokenizer for Neural Text Processing: https://arxiv.org/abs/1808.06226

  • OpenAI tiktoken: https://github.com/openai/tiktoken

            预览时标签不可点
    

    <div class="