在大语言模型(Large Language Model,LLM)的 API(应用程序编程接口)文档里,经常会看到几个说法:
- 这个模型支持 128K Token 上下文;
- 输入价格是每百万 Token 多少元;
- 输出价格比输入价格更贵;
- 提示词(Prompt)太长会导致请求失败。
这里的 Token 不是中文里的“字”,也不是英文里的“单词”。它更像大模型内部处理文本时使用的“信息块”:一段文本进入模型之前,会先被分词器(Tokenizer)切成若干 Token,再转换成数字编号,模型真正处理的是这些编号以及由编号映射出来的向量。
理解 Token,能解决三个很实际的问题:
- 为什么同样一句话,不同模型统计出来的 Token 数不一样;
- 为什么上下文窗口不是按字数计算,而是按 Token 计算;
- 为什么大模型 API 通常按照输入 Token 和输出 Token 计费。
Token 是大模型看到的文本单位
人看到的是文字,模型看到的是 Token。
例如一句话:
今天天气不错。
对人来说,它可以按字理解:
今 / 天 / 天 / 气 / 不 / 错 / 。
也可以按词理解:
今天 / 天气 / 不错 / 。
大模型不会直接用人类语言里的“词”作为唯一标准,而是使用分词器决定切分方式。切出来的 Token 可能是一个汉字、一个词、一个标点、一个英文单词,也可能只是英文单词的一部分。
| 文本片段 | 可能成为 Token 的形式 | 说明 |
|---|---|---|
鸡 | 一个汉字 | 单字很常见,可能单独成 Token |
苹果 | 一个词 | 高频组合可能被打包 |
孙悟空 | 一个名字 | 高频专有名词可能被打包 |
。 | 一个标点 | 标点通常也会参与编码 |
apple | 一个英文单词 | 常见英文词可能整体保留 |
ing | 单词后缀 | 高频子词可能单独成 Token |
“可能”这个词很重要。Token 的切分结果由具体模型使用的分词器决定,同一段文本换一个模型,Token 数量和切法都可能变化。
整个流程可以画成这样:
flowchart LR
A[原始文本] --> B[分词器 Tokenizer]
B --> C[Token 序列]
C --> D[Token ID 数字编号]
D --> E[向量表示 Embedding]
E --> F[大模型计算]
F --> G[预测下一个 Token]
G --> H[分词器解码为文字]
模型不是直接“读”汉字或英文字符,而是先把文本变成 Token ID。Token ID 再映射成向量,进入 Transformer 等模型结构里参与计算。
为什么不直接按字或按词处理
最直观的方案是按字处理中文、按单词处理英文,但实际会遇到很多麻烦。
| 切分方式 | 优点 | 问题 |
|---|---|---|
| 按字符切分 | 规则简单,任何文本都能处理 | 序列会变长,模型需要处理更多位置 |
| 按单词切分 | 符合部分语言习惯 | 遇到生僻词、新词、拼写变化会很麻烦 |
| 按子词切分 | 在长度和泛化之间折中 | 切法不一定符合人类直觉 |
现代大模型常用的是“子词”思路。它不会强行把所有内容都切成单字,也不会要求词表覆盖世界上所有单词,而是把高频片段打包,把低频片段拆开。
英文里常见的 ing、tion、pre 这类片段,可能成为 Token;中文里常见的词语、名字、短语,也可能成为 Token。这样做的好处是:常见表达可以用更少 Token 表示,不常见表达也能被拆成更小单元处理,不至于完全无法编码。
分词器怎样决定切哪里
分词器的核心工作是两件事:
- 建一个 Token 词表;
- 把输入文本映射成词表里的 Token ID。
很多分词器会使用 BPE(Byte Pair Encoding,字节对编码)、Unigram、WordPiece 或 SentencePiece 一类算法。不同算法细节不一样,但思想很接近:从大量语料中统计高频片段,把经常一起出现的字符或字节组合合并成更大的单元。
以 BPE 的简化过程为例,可以这样理解:
flowchart TD
A[准备大量训练文本] --> B[从字符或字节级别开始]
B --> C[统计相邻片段出现频率]
C --> D[合并最高频的相邻片段]
D --> E[更新词表]
E --> C
E --> F[达到词表大小后停止]
F --> G[得到分词器词表]
假设训练文本里反复出现这些内容:
人工智能
人工智能模型
人工智能应用
分词器可能先发现:
人 + 工
经常相邻出现,于是合并成:
人工
又发现:
智 + 能
经常相邻出现,于是合并成:
智能
如果 人工智能 作为整体出现频率非常高,它还可能继续被合并成一个 Token。
最终词表里可能出现这样的条目:
| Token | Token ID |
|---|---|
人 | 1024 |
工 | 2048 |
人工 | 8301 |
智能 | 9120 |
人工智能 | 15678 |
。 | 13 |
Token ID 只是词表编号,不代表大小关系。ID 为 15678 的 Token 并不比 ID 为 13 的 Token “更重要”,它们只是不同位置上的索引。
分词、编码和解码是一组配套动作
一次完整的大模型调用,大致可以拆成三个阶段。
sequenceDiagram
participant User as 用户
participant Tok as 分词器
participant Model as 大模型
User->>Tok: 输入文本
Tok->>Tok: 切分为 Token
Tok->>Model: Token ID 序列
Model->>Model: 计算下一个 Token 的概率
Model-->>Tok: 输出 Token ID
Tok-->>User: 解码成人类可读文本
输入阶段,分词器把文本变成 Token ID:
"今天天气不错。"
↓
[TokenA, TokenB, TokenC, TokenD]
↓
[1234, 5678, 9012, 13]
计算阶段,模型根据已有 Token 预测下一个 Token 的概率分布。比如在“今天天气”后面,模型可能认为:
| 候选 Token | 概率 |
|---|---|
不错 | 0.42 |
很好 | 0.21 |
很热 | 0.08 |
。 | 0.03 |
输出阶段,模型选出一个 Token,把它追加到已有序列后,再继续预测下一个 Token。大模型生成文字看起来像“一个字一个字打出来”,本质上通常是一个 Token 一个 Token 地生成,只是 Token 和可见字符不一定一一对应。
有时一个输出 Token 可能包含多个汉字,有时一个英文单词会被拆成多个 Token,所以流式输出时会出现“半个单词先出来”的感觉。
不同模型为什么切法不同
Token 不是统一标准,而是模型工程的一部分。不同模型的分词器可能差异很大,原因主要有几类。
| 影响因素 | 会造成什么差异 |
|---|---|
| 训练语料不同 | 高频词判断不同,常见片段会被更容易打包 |
| 词表大小不同 | 词表越大,越可能容纳更多长片段 |
| 分词算法不同 | BPE、Unigram、WordPiece 的切分习惯不一样 |
| 多语言比例不同 | 中文、英文、代码、日文等语言的 Token 效率不同 |
| 规范化规则不同 | 空格、大小写、标点、全角半角处理方式不同 |
| 特殊 Token 不同 | 系统提示、工具调用、图片占位等可能有专门 Token |
例如同样是中文词语:
| 文本 | 模型 A 可能的切法 | 模型 B 可能的切法 |
|---|---|---|
鸡蛋 | 鸡蛋 | 鸡 / 蛋 |
鸭蛋 | 鸭 / 蛋 | 鸭蛋 |
孙悟空 | 孙悟空 | 孙 / 悟空 |
哈哈哈哈哈 | 哈哈哈哈 / 哈 | 哈哈 / 哈哈哈 |
这些差异并不说明哪个模型“更懂”这些词,只说明它们的分词器词表和训练语料不同。判断某段文本到底消耗多少 Token,不能靠肉眼估算,应该使用对应模型的 tokenizer 或 API 返回的 usage 字段。
Token 和上下文窗口是什么关系
模型文档里的“上下文窗口”指一次请求中模型最多能处理多少 Token。这个数量通常包括:
- 系统提示词;
- 用户输入;
- 多轮对话历史;
- 工具调用描述;
- 检索到的参考资料;
- 模型即将生成的输出预算。
假设一个模型支持 128K Token 上下文,不代表你可以输入 128K Token 后再让它输出 10K Token。输入和输出往往共享同一个窗口,实际请求还要给输出预留空间。
flowchart LR
A[系统提示 1K] --> B[历史对话 20K]
B --> C[用户新问题 2K]
C --> D[检索资料 30K]
D --> E[预留输出 4K]
E --> F[总上下文 57K]
当上下文超过模型限制时,常见处理方式有三种:
| 方式 | 做法 | 适合场景 | 代价 |
|---|---|---|---|
| 截断 | 删除最早或不重要的内容 | 简单聊天、低风险任务 | 可能丢掉关键信 |