AI Research

02. Token 化:文本如何变成数字

02. Token 化:文本如何变成数字

模型不认识文字。它只认识数字。Token 化就是把人类的文本翻译成模型能处理的数字序列。


一、为什么不能直接用字符

最直觉的想法:给每个字符分配一个 ID。

'a' → 0,  'b' → 1,  ...,  'z' → 25
'A' → 26, 'B' → 27, ...,  'Z' → 51
'0' → 52, ..., '9' → 61
' ' → 62, '.' → 63, ...

问题:

  1. 词汇表太小,序列太长。“Transformer” 这个词有 11 个字符,模型需要处理 11 个步骤。文章越长,计算量就越大(Attention 的计算量和序列长度的平方成正比)。

  2. 单个字符没有语义。“t” 本身什么意思都没有,模型需要从底层拼凑出 “the”、“this” 等词的含义,学习效率低。

另一个极端——按完整单词分:

"the" → 0,  "cat" → 1,  "transformer" → 2, ...

问题:

  1. 词汇表爆炸。英语有几十万个单词,加上变形(run/runs/running/ran)、专有名词、各种语言的词汇——词汇表会达到数百万,embedding 矩阵太大。

  2. 未登录词 (OOV)。遇到词汇表外的新词(比如 “ChatGPT”),直接无法处理。

Token 化是两者的折中:比字符粒度大,比完整单词粒度小。


二、BPE:最主流的分词算法

BPE(Byte Pair Encoding,字节对编码)是目前 GPT 系列、LLaMA、GLM 等主流模型使用的分词算法。

2.1 训练过程(构建词汇表)

BPE 的训练不是在模型训练时发生的,而是预先在大量文本上统计出来的

第一步:初始化

从所有单字节(256 个)开始作为初始词汇表:

词汇表 = {
  所有 ASCII 字符: a, b, c, ..., z, A, B, ..., Z, 0-9, 空格, 标点, ...
  所有 UTF-8 字节: 0x00 到 0xFF (256 个)
}

此时,任何文本都可以被表示——最坏情况就是拆成单字节。

第二步:统计相邻 token 对的出现频率

假设训练语料(简化示例):

"the cat sat on the mat the cat"

初始时每个字符是一个 token:

[t, h, e, _, c, a, t, _, s, a, t, _, o, n, _, t, h, e, _, m, a, t, _, t, h, e, _, c, a, t]

统计所有相邻 token 对:

(t, h) → 3 次    ← 最高频!
(h, e) → 3 次
(e, _) → 3 次
(c, a) → 2 次
(a, t) → 4 次    ← 最高频!
...

选出现频率最高的一对(假设是 (a, t)),合并成新 token:

词汇表新增: "at"
语料变成: [t, h, e, _, c, at, _, s, at, _, o, n, _, t, h, e, _, m, at, _, t, h, e, _, c, at]

第三步:重复合并

再次统计:

(t, h) → 3 次    ← 最高频
(h, e) → 3 次
...

合并 (t, h) → “th”:

词汇表新增: "th"
语料变成: [th, e, _, c, at, _, s, at, _, o, n, _, th, e, _, m, at, _, th, e, _, c, at]

继续:合并 (th, e) → “the”:

词汇表新增: "the"
语料变成: [the, _, c, at, _, s, at, _, o, n, _, the, _, m, at, _, the, _, c, at]

继续:合并 (c, at) → “cat”:

词汇表新增: "cat"
语料变成: [the, _, cat, _, s, at, _, o, n, _, the, _, m, at, _, the, _, cat]

持续合并,直到词汇表达到目标大小

GPT-2 的词汇表大小:50,257 GPT-4 的词汇表大小:约 100,000 GLM 系列的词汇表大小:约 150,000

2.2 编码过程(使用训练好的词汇表)

训练好词汇表后,对新文本进行 Token 化:

输入: "unhappiness"

BPE 查找最长匹配:
  1. 检查 "unhappiness" 是否在词汇表中 → 不在
  2. 检查所有可能的拆分,找到词汇表中存在的最优拆分
  3. 结果: ["un", "happiness"]  或  ["un", "happi", "ness"]
     (具体取决于哪些子串在词汇表中)

每个 token 对应一个 ID:
  "un" → ID 348
  "happi" → ID 23991
  "ness" → ID 1108

2.3 实际 Token 化的例子

输入: "Hello, world! 你好世界"

GPT-4 tokenizer 的结果:
  ["Hello", ",", " world", "!", " 你", "好", "世", "界"]
  →  [9906, 11, 1917, 0, 220, 57668, 53901, 3574, 244, 41519, 230]

注意:
  - "Hello" 是一个完整 token(高频词)
  - " world" 包含前面的空格(BPE 会把空格编码进 token)
  - 中文通常一个字一个 token(中文字不像英文那么高频合并)

2.4 Token 数量直觉

英文:
  1 个 token ≈ 4 个字符 ≈ 0.75 个单词
  "The quick brown fox jumps over the lazy dog"
  → 9 个单词 → 大约 9-10 个 token

中文:
  1 个 token ≈ 1-2 个汉字
  "今天天气真好"
  → 6 个字 → 大约 6 个 token

代码:
  通常 token 更多(符号、缩进都是 token)
  "def hello():\n    print('hi')"
  → 大约 12-15 个 token

三、其他分词算法

3.1 WordPiece(BERT 使用)

和 BPE 类似,但合并策略不同。BPE 选频率最高的对合并,WordPiece 选能最大化训练数据似然的对合并。

BPE:      选 count(AB) 最大的
WordPiece: 选 count(AB) / (count(A) * count(B)) 最大的

WordPiece 更偏好合并那些”总是一起出现”的对,而不仅仅是”出现次数多”的对。

标记方式也不同——WordPiece 用 ## 标记非词首的子词:

"unhappiness" → ["un", "##happi", "##ness"]

3.2 SentencePiece(多语言友好)

不依赖预先的空格分词,直接在原始字节/Unicode 上训练。对中文、日文等没有空格分隔的语言更友好。

LLaMA、T5 等模型使用 SentencePiece。

3.3 对比

算法使用模型特点
BPEGPT 系列, GLM简单高效,频率驱动
WordPieceBERT似然驱动,学术经典
SentencePieceLLaMA, T5多语言友好,无需预分词

实际差异不大,核心思想都是:从小到大逐步合并高频子串


四、特殊 Token

除了文本 token,词汇表中还有一些特殊用途的 token:

<|endoftext|>   (ID: 50256)   → 文本结束标记
<|im_start|>                  → 对话开始
<|im_end|>                    → 对话结束
<PAD>                         → 填充(使 batch 内序列等长)
<UNK>                         → 未知 token(理论上 BPE 不需要)

对话格式示例(ChatML):

<|im_start|>system
你是一个有帮助的助手。<|im_end|>
<|im_start|>user
你好<|im_end|>
<|im_start|>assistant
你好!有什么可以帮助你的吗?<|im_end|>

这些特殊 token 告诉模型”这段是系统指令”、“这段是用户说的”、“这段是你该生成的回答”。


五、Token 化对模型的影响

5.1 词汇表大小的权衡

词汇表大 (100K+):
  ✅ 常见词/短语是单个 token,序列更短,推理更快
  ❌ Embedding 矩阵更大 (100K × d 个参数)
  ❌ 生成时 softmax 计算量更大

词汇表小 (10K):
  ✅ Embedding 矩阵小
  ❌ 文本被拆得很碎,序列很长,计算量大
  ❌ 每个 token 携带的信息少

5.2 “Token 税”

中文在以英文为主的 tokenizer 中通常效率较低:

英文: "Hello world"  → 2 tokens
中文: "你好世界"     → 4 tokens (同样的语义,token 数翻倍)

这意味着:

  • 中文的有效上下文窗口更短(同样 128K token 限制,能放的中文内容更少)
  • 中文的推理成本更高(按 token 计费时更贵)

这就是为什么一些专注中文的模型(如 GLM)会用更大的词汇表(150K),把更多中文字词组合纳入,减少中文的 token 数量。

5.3 Token 边界问题

模型只能看到 token,看不到字符。这会导致一些有趣的问题:

问: "strawberry 中有几个 r?"
模型可能答错,因为:
  "strawberry" → ["str", "aw", "berry"]
  模型看到的是 3 个 token,不是 10 个字母
  它不能直接"数"字母

这不是模型”不聪明”,而是它的输入粒度就是 token 级别的。


本章总结

  1. Token 是介于字符和单词之间的文本单位
  2. BPE 通过反复合并高频字节对来构建词汇表
  3. 高频词(如 “the”)是单个 token,罕见词被拆成多个子词
  4. 每个 token 有一个整数 ID,模型的输入就是 ID 序列
  5. 词汇表大小通常在 50K-150K,是效率和参数量的权衡
  6. Token 化对不同语言有不同效率(“token 税”)

下一篇:03. Embedding:从数字到向量空间 — Token ID 怎么变成模型能计算的向量