别再把 40 万 Context 当 40 万字!一文搞懂 Token/Tokenizer(BPE)与字数换算
很多同学第一次看到“大模型支持 40 万 context window”,会下意识理解成“能装下 40 万字/词”。但这里的单位不是字,也不是词,而是 Token。要把 Token 搞懂,就必须把 Tokenizer(分词器) 一起搞明白。
这篇文章用“翻译官 + 压缩机”的直觉,把 Token 从概念、训练(BPE)、使用(编码/解码)到“Token≈多少字”完整串起来。
⸻
附:主流模型上下文窗口(参考)
- GPT‑5.2:约 400,000 tokens(API 标注 400K;产品端可能有策略限制)
- DeepSeek:V3/Chat 常见 64K;R1/Reasoner 64K–128K(随快照/托管端差异)
- 豆包(火山引擎):1.5/1.6 系列最高 256K(端内可能分区计费与策略下调)
说明:上下文窗口以 Token 计;输入+输出总和受限。不同平台的应用端可能设置额外的“服务端策略”(截断/上限),与模型本身能力有区分。
⸻
1)Token 到底是什么?为什么模型只认 Token?
大模型本质上是一个巨大的数学函数,它只能处理数字,不懂“文字”。
所以我们需要一个“翻译官”——Tokenizer:
- 编码(encode):把文字 → 切分成 Token → 映射成 Token id(数字)
- 解码(decode):把 Token id(数字) → 映射回 Token → 拼回文字
可以把模型输入输出想成这样一条流水线:
文本 → Tokenizer 编码 → Token id 序列(数字) → 大模型计算 → Token id 序列(数字) → Tokenizer 解码 → 文本
一个直观例子
输入一句话:小明喜欢人工智能吗
Tokenizer 会做两件事:
- 切分:把字符串拆成一段段 Token(注意:Token 不一定等于“字”或“词”)
- 编号:每个 Token 对应一个整数 id,例如:
“小明” -> 32018
“喜欢” -> 10923
“人工智能” -> 58791
“吗” -> 2345
模型真正“看到”的只是 [32018, 10923, 58791, 2345] 这样的数字序列。
⸻
2)Tokenizer 不靠“人写规则”,而是训练出来的
很多人以为 Tokenizer 是“写死的切词词典”。更准确的说法是:Tokenizer 通常是在语料上训练出来的,它会学习“哪些片段经常一起出现,合起来更划算”。
常见训练思路里,BPE(Byte Pair Encoding)非常典型(视频也重点讲了它)。核心目标就一句话:
让高频的相邻片段合并成更大的 Token,从而减少 Token 数量。
⸻
3)BPE 训练过程:从“单字表”到“合并规则”
BPE 可以理解成一个“不断合并最常见相邻对”的算法。
3.1 初始化:先从最小单位开始
- 准备训练语料(大量文本)
- 初始词表通常从字符/字节级开始:
- 每个字符(或字节)都有一个 Token id
- 合并规则(merge rules)一开始为空
3.2 反复迭代:统计、合并、记录
循环做这几步(直到达到目标词表大小或合并次数):
- 扫描语料,统计“相邻片段对”出现频率
- 找到最高频的一对,比如:人 + 工
- 合并成新 Token:人工
- 把 人工 加入词表,并记录一条合并规则:人 工 -> 人工
- 注意:新 Token 还能继续参与后续合并,比如 人工 + 智能 -> 人工智能
训练完以后,一个 BPE Tokenizer 的核心产物就是两样东西:
- 词表(vocab):Token → Token id 的映射
- 合并规则(merges):告诉你“哪些相邻片段要优先合并”
⸻
4)Tokenizer 使用过程:编码与解码到底干了啥?
4.1 编码(Encode):先拆再合,最后编号
典型 BPE 编码大致是:
- 先把输入切到最细粒度(字符/字节)
- 按照训练得到的合并规则,从前到后不断合并
- 得到最终 Token 序列
- 查词表,把 Token 变成 Token id
4.2 解码(Decode):查表拼回去
解码更简单:
把模型输出的 Token id 逐个查词表反向映射成 Token,然后拼接成字符串即可。
这也解释了为什么模型会输出一些看起来“奇怪的半个词/奇怪空格”:那可能正好是某个 Token 的文本片段。
工程补充:半词与空格的由来与处理
- 本质:模型按 Token 输出,一个 Token 可能只是词的一部分或包含前置空格。
- 典型场景:
- 半个词:例如“人工智能”被切成 Token “人工” + “智能”。如果生成或截断停在“人工”,解码后就是一个“半词”。
- 奇怪空格:很多分词器把“前导空格”一起编码成 Token(例如
' 世界'这个 Token 自带一个空格)。拼接时可能出现看起来多余或不规则的空格。
- 工程建议:
- 以 Token 为单位做长度控制或截断,再整体解码;必要时在文本层做词边界/标点边界的二次裁剪。
- 流式展示时缓冲到最近的空格/标点/换行再刷新 UI,减少半词与怪空格的感知。
- 统一提示词格式与空格规范,降低不必要的前导空格 Token 出现概率。
- 实用提醒:依赖分词器的“解码”来还原文本,不要手工拼接 Token 字符串。
⸻
5)为什么 Token 数 ≠ 字数?因为 Tokenizer 还是“压缩机”
Context window(上下文窗口)限制的是 Token 数量,不是字数。
差异的根源在于:Tokenizer 在做“翻译”的同时,也在做“压缩”——它会把高频组合合并成更长的 Token,从而让同样的文本占用更少 Token。
所以:
- 常见、规律的文本(高频片段多)→ 更“省 Token”
- 生僻词、混杂符号、乱码、少见组合 → 更“费 Token”
- 中文、英文、代码的 Token 密度也不一样
⸻
6)Token 和字数怎么换算?给你一个工程上够用的估算
视频给了一个非常常用的经验换算(注意这是近似,不同模型/Tokenizer 会有偏差):
- 1 Token ≈ 1.5 ~ 2 个汉字
- 1 Token ≈ 4 个英文字母
- 1 Token ≈ 0.75 个英文单词
用它可以快速估算“某个 context window 大约能装多少内容”。
举个例子:如果是 40 万 Token 的窗口(以视频举例的 GPT5.2 级别设定):
- 中文:约 60 万 ~ 80 万汉字(40万 × 1.5~2)
- 英文单词:约 30 万英文单词(40万 × 0.75)
但再次强调:这只是估算。真实值会随着文本类型(自然语言/代码/表格)、语言、符号密度、专有名词而显著波动。
⸻
7)写给实战同学的 3 个小提醒
- 别用“字数”规划上下文:用“Token 预算”更靠谱(尤其做 RAG、长文摘要、对话记忆时)。
- 同一段内容,换个表达 Token 可能差很多:更规范的格式、更常见的词汇,往往更省 Token。
- 代码/日志通常更费 Token:符号、路径、hash、时间戳会导致 Tokenizer 很难“合并压缩”。
⸻
总结
- Token 是大模型处理文本的“基本计量单位”,不是字也不是词。
- Tokenizer 负责把文字 ↔ 数字互转;在 BPE 等算法下,它不仅是翻译官,还是压缩机。
- Context window 的容量是 Token 数,字数只能估算:
1 Token ≈ 1.5~2 汉字 / 0.75 英文单词 / 4 英文字母。