一、为什么同样是"500字",中英文花的token差这么多?
用过模型 API 的人,一定对 token 不陌生------计费按它算、上下文窗口按它算、报错"超出 max_tokens"也是因为它。但你有没有疑惑过:
- 英文一句话 "Hello world" 几个 token?中文一句"你好世界"又是几个?
- 为什么有人说中文比英文更"费"token?
- token 和"字数"到底是什么换算关系?
这篇文章把 token 这件事彻底讲明白:模型怎么把文字切成 token、BPE 分词怎么工作、中文为什么 token 贵,以及它对上下文窗口和计费的影响。全程 Python 可运行,看完你对 token 的理解会超出绝大多数人。
二、模型不吃文字,只吃数字:先有词表
大模型本质是"按概率预测下一个 token"的机器。但它面对的不是文字,而是数字 id------所以任何输入都得先翻译成一串整数,模型才看得懂。
这个翻译过程分两步:
- 分词(Tokenization):把原始文本切成一串 token(可以近似理解为"小块")
- 映射:查词表(vocabulary),把每个 token 变成它在词表里的编号(id)
每个模型有自己的词表------通常是几万到十几万个 token(GPT-4 系列约 10 万级别)。词表是训练时定死的,不会因为你换了文本而变。
拿到 tokenizer 之后,一切就透明了(以 OpenAI 官方 tiktoken 为例):
pip install tiktoken
python
import tiktoken
enc = tiktoken.get_encoding("cl100k_base")
tokens_en = enc.encode("Hello world, this is a test.")
tokens_zh = enc.encode("你好世界,这是一次测试。")
print("英文token数:", len(tokens_en), tokens_en)
print("中文token数:", len(tokens_zh), tokens_zh)
输出类似(id 具体值取决于词表):
英文token数: 8 [15496, 995, 11, 2023, 374, 257, 2566, 13]
中文token数: 14 [..., ..., ...]
有意思的来了:同样的一句话,"你好世界,这是一次测试。" 的 token 数明显多于 "Hello world, this is a test."------中文每个字常常对应 1 个甚至多个 token,而英文单词常由多个"更小单位"共享词表。这就是"中文更费 token"的来源。
三、token 不是字,也不是词:它是 BPE 的"子词"
你可能以为 token = 词。其实主流模型用的是子词(subword)分词 ,最著名的算法叫 BPE(Byte Pair Encoding,字节对编码)。
BPE 的核心思想:从字符出发,反复把"最常见的相邻片段"合并成一个新 token,直到词表大小达到目标。
看个直觉例子------假设语料里 "ing" 这个片段出现极多,BPE 就会把它合并成一个 token;于是:
- "running" →
["runn", "ing"](两个 token) - "talking" →
["talk", "ing"](两个 token)
"ing" 这一个 token 被所有以 ing 结尾的词共享------这就是子词方案能压缩词表、又比纯字符方案更省 token 的原因。
Python 里自己实现一个极简 BPE 训练,体会"合并最频繁片段"的过程:
python
def build_bpe(text, num_merges=10):
vocab = {"<w>": i for i, ch in enumerate(set(text))} # 先按字符切
tokens = list(text)
merges = {}
for _ in range(num_merges):
# 统计相邻 token 对
pairs = {}
for a, b in zip(tokens, tokens[1:]):
pairs[(a, b)] = pairs.get((a, b), 0) + 1
if not pairs:
break
best = max(pairs, key=pairs.get) # 最频繁的一对
merges[best] = True
# 替换文本里的这对
i, new = 0, []
while i < len(tokens):
if i < len(tokens) - 1 and (tokens[i], tokens[i+1]) == best:
new.append(best[0] + best[1]); i += 2
else:
new.append(tokens[i]); i += 1
tokens = new
return tokens, merges
tokens, merges = build_bpe("aaaaabbbbbaaaaacccc")
print("合并后token:", tokens)
print("合并规则数:", len(merges))
看到没有,BPE 就是不断把高频相邻片段"焊"成一个新 token。工业级实现还加上了字节级回退(遇到生僻字/emoji 时降到字节),所以理论上任何文本它都能切,不会碰到"词表外"的报错。
四、为什么中文 token 更"贵"?三个原因
同样长度的内容,中文 token 开销通常更大,主要是这几个原因:
- 中文字符多、组合复杂:汉字几万个常用字,英文只有 26 个字母,BPE 从字节/字符起步合并时,中文能形成的高频"子词"相对少,很多字只能单独成 token 甚至拆字节
- 词表共享效率低:英文 "ing"、"tion" 这种后缀能被大量词共享;中文没有天然的空格分隔和词形变化,一个字往往只能"独享"一个 token
- 标点与空格的代价:英文按空格天然分段,BPE 容易形成整词 token;中文句间没有空格,分词边界需要更细
实操验证一下中英 token 差异的直观比例:
python
text_en = "Artificial intelligence is transforming the way we work and live every single day."
text_zh = "人工智能正在改变我们每天工作和生活的方式。"
print("英文:", len(enc.encode(text_en)), "token")
print("中文:", len(enc.encode(text_zh)), "token")
print("中文/英文字符比:", len(text_zh) / len(text_en))
一般经验是:同样语义下,中文 token 大约是英文的 1.3~2 倍 (具体因模型 tokenizer 而异)。这意味着如果你在调多语言系统,中文提示词会更快填满上下文窗口、花更多钱------这不是玄学,是分词机制决定的。
五、Token 对工程实践的三个直接影响
理解了机制,下面这些"行业常识"你就能看透本质:
1. 上下文窗口 = 输入 token 上限(含历史)
模型"记得多少"是按 token 算的,不是按字。上下文窗口 128K 意味着输入 + 历史 + 输出加起来不能超过 128K token。中文用户尤其要注意:同样的窗口,能塞的中文文本量只有英文的一半多。
2. 计费按 token,不是按字/按条
所有 API 都按 token 计费(输入、输出单价常不同)。设计提示词时,把冗余修饰词去掉、用尽量少的 token 表达同样的指令,是实打实的省钱手段。
3. 长文本处理常先"按 token 切块"
RAG 的文档切片、上下文管理,业界都是按 token 阈值切(比如 800~1000 token 一段),而不是按固定字符数------因为模型能"看"的单位是 token。切块代码通常是这种:
python
def chunk_by_tokens(text, enc, chunk_size=800):
ids = enc.encode(text)
chunks = []
for i in range(0, len(ids), chunk_size):
chunks.append(enc.decode(ids[i:i + chunk_size]))
return chunks
print(len(chunk_by_tokens("你好世界。" * 200, enc, 100)))
按 token 切块的好处:每段都不会"撑爆"模型单次处理范围,也不会因为字符计数失真而浪费 token。
六、总结 + 实用清单
| 概念 | 一句话理解 | 对你的实际影响 |
|---|---|---|
| Token | 模型处理文本的"最小单位" | 计费与窗口的计算单位 |
| 词表 | token 与 id 的映射表 | 训练时定死,不随文本变 |
| BPE 分词 | 高频相邻片段合并成子词 | 英文省 token,中文相对费 |
| 上下文窗口 | 输入+历史+输出上限 | 决定单次能"看"多长 |
| token 计费 | 按输入+输出 token 算钱 | 精简提示词=省钱 |
| token 切块 | 长文按 token 切段 | RAG/长文档处理的标准做法 |
实用小贴士(可以直接抄):
- 估 token :拿不准就
len(tokenizer.encode(text)),别用字数估算,尤其中文 - 省 token:提示词去掉"请你帮我""好的呢"这类冗余,指令紧凑表达
- 看窗口:用中文做长文档问答前,先估一下整段会不会超出上下文,超了就分块
- 调中文系统:预留"中文 token 系数",缓存、计数都按 token 记,别按字符记
一句话收尾:token 是模型世界里唯一"看得懂"的文字单位,中文的 token 贵不是玄学,是 BPE 子词方案在无空格、高组合语言上的必然结果。 理解了 tokenizer,再看上下文窗口、计费、RAG 切块这些工程问题,就都串起来了。
本文为技术科普,示例基于 tiktoken(OpenAI 词表);不同模型词表不同,token 数会有差异,但 BPE 原理与"中文相对费 token"的结论通用。