从零开始理解大模型(二):Token——大模型眼中的"字"长什么样

举报
AGENT魔方 发表于 2026/04/15 10:21:15 2026/04/15
【摘要】 本文是「从零开始理解大模型」系列第二篇,系列共十篇,从 “下一个词预测” 逐步搭建完整大模型心智模型,每篇均附带可运行代码。本篇聚焦 Token,带你了解大模型视角下 “字” 的本质与形态。

专栏1.png

欢迎阅读「从零开始理解大模型」系列 —— 本系列十篇文章,从"下一个词预测"到完整的大模型心智模型。每篇配可运行代码。

  • 第一篇:一切从"猜下一个词"开始
  • 第二篇:Token——大模型眼中的"字"长什么样(本文)
  • 第三篇:向量与 Embedding——把文字变成数学
  • 第四篇:Attention——大模型的"阅读理解"机制
  • 第五篇:Transformer 全景——积木怎么搭成大厦
  • 第六篇:训练——70 亿个参数是怎么"学"出来的
  • 第七篇:推理——你按下回车后的这一秒发生了什么
  • 第八篇:上下文窗口——大模型的"工作记忆"
  • 第九篇:Scaling Law——为什么"大力出奇迹"有效
  • 第十篇:从大模型到 Agent——下一个词预测如何长出手脚

* 本文实操配套代码附件,可在本公众号后台回复“大模型”获取。

作者:十一

上一篇我们看到,模型以 99.2% 的概率预测 "Thank you very" 后面接 "much"。代码里有一行被跳过了:

input_ids = tokenizer.encode("Thank you very", return_tensors="pt")
# 输出: [10449, 345, 845]

三个英文单词变成了三个数字。模型看到的不是 "Thank you very",而是 [10449, 345, 845]

这三个数字就是三个 token。 Token 是大模型的最小阅读单位——模型不认识字母,不认识汉字,它只认识 token。

本篇回答三个问题:token 到底是什么?怎么切出来的?为什么你需要关心它?

一、先说结论

1.png


句话总结:Token 是人类语言和模型之间的翻译层——人类写字,模型读 token。

二、Token 不是"词"

用第一篇的 tokenizer 来看几个例子:

tokenizer.encode("Hello")        # → [15496]             1 个 token
tokenizer.encode("hello")        # → [31373]             1 个 token,但 ID 不同!
tokenizer.encode("Kubernetes")   # → [42, 18478, 3262, 274]  4 个 token,被切碎了
tokenizer.encode("strawberry")   # → [301, 1831, 8396]    3 个 token
tokenizer.encode("你好")         # → [19526, 254, 25001, 121]  4 个 token!

几个反直觉的事实:

大小写不同 = 不同的 token。 "Hello"(15496)和 "hello"(31373)在模型眼里是两个完全不同的"字"。

空格被编进 token 里。" capital"(前面带空格)和 "capital" 是不同的 token。GPT 系列把空格粘在下一个词的前面。

专业术语被切碎。 "Kubernetes" 不在词表里,被切成了四段碎片(K-uber-net-es)。模型需要从碎片中"拼"出含义。

中文被切得更碎。 两个汉字 "你好" 竟然变成了 4 个 token——因为 GPT-2 的词表主要基于英文构建,中文字符被拆成了字节级碎片。

完整的分词实验代码见附件 tokenizer_demo.py[1],支持命令行输入任意文本测试。

三、BPE——Token 是怎么"切"出来的

为什么 "Hello" 是 1 个 token 而 "Kubernetes" 是 4 个?这由一个叫 BPE(Byte Pair Encoding,字节对编码) 的算法决定。

3.1 核心思路

你需要设计一个固定大小的"字典"(比如 50257 个 token),用来表示所有可能的文本。

极端方案一:收录所有词 → 词无穷多,不可行。 极端方案二:只收 256 个字节 → 任何文本都能表示,但效率极低。

BPE 的做法是:从单个字节开始,反复合并最频繁的相邻对,直到词表达到目标大小。 形式化表示:

初始词表 V₀ = {所有单字节}    // 256 个

重复以下步骤:
    (a, b) = argmax_{所有相邻对} count(a, b)    // 找出语料中出现次数最多的相邻 token 对
    合并 a + b → ab                              // 创建新 token
    V_{i+1} = V_i ∪ {ab}                        // 加入词表

直到 |V| = 目标大小(如 50257)

3.2 一个具体例子

训练语料:low low low low low lower lower newest newest newest widest

起点:每个字符是一个 token
  → {l, o, w, e, r, n, s, t, i, d}

轮次 1: (l, o) 出现 7 次 → 合并为 "lo"
轮次 2: (lo, w) 出现 7 次 → 合并为 "low"
轮次 3: (e, s) 出现 4 次 → 合并为 "es"
轮次 4: (es, t) 出现 4 次 → 合并为 "est"
轮次 5: (n, e) 出现 3 次 → 合并为 "ne"
轮次 6: (ne, w) 出现 3 次 → 合并为 "new"
轮次 7: (new, est) 出现 3 次 → 合并为 "newest"
...

训练完成后,用这些合并规则对新词分词:

"low"    → [low]          训练中高频,完整保留
"lowest" → [low, est]     两个已知子词拼接("lowest" 训练时没见过!)
"newest" → [newest]       训练中高频,完整保留
"widest" → [w, i, d, est] 低频词被切碎,但 "est" 被识别出来

3.3 BPE 的三个关键性质

纯统计过程 BPE 不懂语言学。"est" 被合并不是因为算法知道它是后缀,而是因为它在语料中频繁出现。结果恰好和语言学吻合——这是统计的副产品。

高频完整、低频切碎 训练数据中出现越多的片段越容易被合并成完整 token。这就是 "Hello" 是 1 个 token 而 "Kubernetes" 是 4 个的原因——前者在互联网文本中远比后者常见。

能泛化到新词 "lowest" 没出现在训练语料中,但 "low" 和 "est" 都是已学到的 token,新词自动被切成 [low, est]。BPE 天然处理了"未见过的词"。

关键洞察:BPE 词表是在训练数据上学出来的。训练数据以英文为主 → 英文词表丰富、切得粗、效率高;中文作为"非主流语言" → 词表位置少、切得碎、效率低。这不是歧视,是统计分布的自然结果。

完整的 BPE 手写实现见附件 bpe_demo.py[2],纯 Python 无依赖,可以修改训练语料观察词表变化。

四、Token 效率——为什么你需要关心

Token 数量直接决定三件事:你花多少钱、等多久、能塞多少内容。

4.1 中英文效率对比

同一句话,中文版的 token 数通常是英文版的 2-3 倍(GPT-2 分词器)

2.png4.2 三个影响公式

API 费用 = token_count × price_per_token
生成延迟 ≈ output_tokens × time_per_token
可用上下文 ≈ context_window / avg_tokens_per_char

同样的内容用中文处理,费用翻倍、速度减半、能塞进上下文的内容更少。这就是为什么 DeepSeek、Qwen 等国产模型专门优化了中文词表

3.png

趋势明确:新一代模型都在扩大词表、增加多语言 token。 词表大了,常见中文词可以作为完整 token 存在,不需要被切成字节碎片。

完整的中英文效率对比和费用估算代码见附件 token_cost.py[3]

五、"strawberry 里有几个 r"——分词决定模型的视力

2024 年有一个著名的测试:问大模型 "How many r's in strawberry?",很多模型答错。原因不在"智力",而在"视力":

tokenizer.encode("strawberry")   # → ["st", "raw", "berry"]

模型看到的是三个 token,看不到单独的 "r"。要数 "r" 的数量,它需要在 token 内部做字符级推理——而这不是它被训练来做的事。

Token 是模型能看到的最小单位。 Token 内部的字符结构对模型来说是模糊的。很多"能力缺陷"——数不对字母、处理不好罕见语言、无法做字符级操作——根源都在分词层。

模型的智力受限于它的视力。

六、特殊 Token:模型的控制信号

除了表示文本的普通 token,还有一类特殊 token——不对应自然语言,用来给模型发"指令":

<|endoftext|>   → 文本结束
<|system|>      → 系统消息
<|user|>        → 用户消息
<|assistant|>   → "现在该我说了"

第一篇提到,当你问 "法国的首都是哪里?" 时,模型看到的输入其实是:

<|system|>你是一个有帮助的助手。<|end|>
<|user|>法国的首都是哪里?<|end|>
<|assistant|>

这些特殊 token 不来自 BPE,而是手动添加到词表中的。如果你读过 Agent 系列第一篇,Agent 的 messages 列表中的 role 字段,发送给模型前就会被转换成这些特殊 token。JSON 是给人看的,特殊 token 才是给模型看的。

模型生成 <|endoftext|> 时就意味着"我说完了"——这就是 Agent 循环中"任务结束"的底层信号。

七、Token 在完整链路中的位置

回顾第一篇的代码,现在你对每一步的理解都深了一层:

# 第一步:分词 —— 本篇的主题
input_ids = tokenizer.encode("Thank you very", return_tensors="pt")
# "Thank you very" → [10449, 345, 845]   ← 3 个 token ID

# 第二步:token ID → 向量(查 Embedding 表)→ 第三篇
# [10449, 345, 845] → 3 个 768 维的向量

# 第三步:向量经过 Attention 层层变换 → 第四、五篇
# 3 个向量 → 经过 12 层 Transformer → 3 个新向量

# 第四步:最后一个向量 → 50257 个概率 → 第一篇已讲过
# softmax(linear(最后一个向量)) → P("much") = 99.2%

Token 是这条链路的起点。它决定了模型看到什么、看不到什么、用户花多少钱、模型跑多快、上下文能装多少。

下一篇,我们打开第二个黑盒:token ID 是怎么变成向量的?为什么 "France"(ID 4881)和 "Paris"(ID 6342)编号差很远,模型却知道它们有关系?

八、结语

Token 是大模型和人类语言之间的翻译层。

这个翻译层看起来不起眼,但它决定了模型的视力边界、使用成本和处理效率。"数不对字母、中文比英文贵、长文本容易截断"——追到底,都能在 token 这一层找到原因。

"Between the human world and the model world, there is a thin layer of translation. That layer is tokenization."

理解了 token,你就理解了大模型的"入口"。


本文配套代码:tokenizer_demo.py[1](分词实验)、bpe_demo.py[2](手写 BPE)、token_cost.py[3](效率对比)。需要 Python 3.8+、transformers。


容器模仿.png

扫码回复“大模型

获取本系列文章配套代码(持续更新)


「从零开始理解大模型」是「从零开始理解 Agent」的姊妹系列。Agent 系列讲"四肢",本系列讲"大脑"。建议对照阅读。

相关链接

[1] tokenizer_demo.py: https://github.com/GitHubxsy/nanoAgent/blob/claude/organize-teaching-materials-4hnRP/llm-from-scratch/tokenizer_demo.py

[2] bpe_demo.py: https://github.com/GitHubxsy/nanoAgent/blob/claude/organize-teaching-materials-4hnRP/llm-from-scratch/bpe_demo.py

[3] token_cost.py: https://github.com/GitHubxsy/nanoAgent/blob/claude/organize-teaching-materials-4hnRP/llm-from-scratch/token_cost.py


【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。