大模型实战指南(1)——Token到底是什么?搞懂大模型的“最小货币单位”
大模型实战指南(1)——Token到底是什么?搞懂大模型的“最小货币单位”
你发给 DeepSeek 的每句话,在它“理解”之前,先被剁成碎片、贴上编号、变成一串整数。碎片叫 Token,切碎片叫分词。Token 不是大模型的附属概念——计费按它算、上下文窗口按它量、推理速度被它拖、输出长度被它卡。搞不懂 Token,你连 API 账单都看不懂。
0. 前言
先说清楚这个系列要干什么。
网上大模型教程分两种:一种太学术,开口就是“自注意力机制的多头缩放点积”,小白直接劝退;另一种太浅,教你怎么注册 ChatGPT 账号,看完还是不懂大模型到底咋运作的。
这个系列走中间路线:用人话讲透每个核心概念,配代码和图,讲完你马上能用。 从 Token 开始,一路讲到 Transformer、微调、RAG、Agent,15 篇读完,你对大模型的认知会从“会打字的魔法盒子”变成“一套可拆解可操控的工程系统”。
为什么第一篇讲 Token?因为 Token 是整个大模型技术栈的地基。你往后学的每一个概念——上下文窗口、Embedding、注意力机制、推理优化——全都以 Token 为基本单位。地基打不牢,后面全是空中楼阁。
不废话,直接开始。
1. 大模型不认字,只认数字
很多人以为大模型像人一样“读”文字——它没有。
你发给 DeepSeek 一句“你好世界”,它接收到的不是四个汉字,而是一串类似这样的整数:
[10236, 3827]
“你好世界”四个字,在 DeepSeek 的分词器里可能只被切成 2 个 Token。换成 GPT-4o,可能变成 4 个甚至 6 个。
同一句话,不同模型切碎的方式完全不同——因为每个模型有自己的“刀法”(分词器)。
如果你觉得抽象,打个比方:把大模型想象成一家餐厅,你的话是客人点的菜。
- 后厨只认标准化的食材包(Token),不认散装的原材料(原始文字)
- 切菜师傅(分词器)负责把五花八门的原材料,切成后厨认识的标准食材包
- 同一棵白菜(同一句话),中餐师傅切成“片”(DeepSeek 切法),西餐师傅切成“丝”(GPT 切法),端出来的菜完全不同
这就是为什么同一句话,不同模型“理解”得不一样——它们在入口处看到的食材形态就不同。

图1:你输入的文字,先被分词器切成 Token,再查表变成整数 ID,最后变成模型能处理的向量。整条流水线不可逆地决定了模型“看到”什么。
整条流水线分四步:
- 原始文本:你输入的自然语言文字
- 分词器切分:BPE 算法把文字切成 Token 碎片
- 查表映射:每个 Token 查词表,变成一个整数 ID
- Embedding:整数 ID 映射为一个高维向量(比如 4096 维),模型开始处理
这四步不可逆地决定了模型“看到”什么。同样的文字,换个分词器,模型接收到的信息就变了。
这把“刀”是怎么工作的?为什么“你好”能合在一起、有的字会被单独切开?背后是一套叫 BPE 的算法,我们第 3 节细讲。
但别急,先搞懂一个更基础的问题:为什么不直接用字符?
1.1 Token 和字符到底是什么关系?
很多人搞混 Token 和字符。区分一下:
| 概念 | 定义 | 例子 |
|---|---|---|
| 字符(Character) | 文本的最小显示单位 | “你”“好”“a”“B”“!”“。” |
| 字节(Byte) | 计算机存储的最小单位 | 一个英文字符 = 1 字节,一个中文字符 = 3 字节(UTF-8) |
| 词(Word) | 语言中有意义的最小单位 | “自然语言处理”是一个词 |
| Token | 分词器切分后的最小单元 | 可能是 1 个字、半个词、或一整个短语 |
Token 介于字符和词之间——有时等于一个字,有时等于一个词,有时是半个词。具体等于什么,取决于分词器怎么切。
一个粗略的经验换算:
- 英文:1 个 Token ≈ 0.75 个单词(4 个字母)→ 100 个单词 ≈ 130 个 Token
- 中文(GPT-3.5):1 个汉字 ≈ 1.5-2 个 Token → 100 个汉字 ≈ 150-200 个 Token
- 中文(DeepSeek/Qwen):1 个汉字 ≈ 0.5-0.7 个 Token → 100 个汉字 ≈ 50-70 个 Token
- 代码:1 个 Token ≈ 3-4 个字符 → 100 行代码 ≈ 800-1200 个 Token
注意这些只是经验估值,实际 Token 数必须用分词器测。不同文本内容差异很大——常见词多的文本 Token 数少,生僻字多的文本 Token 数多。
2. 为什么不按“字”切?为什么不按“词”切?
直觉上,按字切最简单——“你好世界”切成“你”“好”“世”“界”四个字,每个字一个编号,完事。
问题在于:按字切,模型看不到“词”。
“银行”和“行走”里都有“行”字,但意思完全不同。如果按字切,模型只能看到孤零零的“行”,它得自己猜这个字是什么意思。而你把“银行”当一个整体给模型,模型一上来就知道这是个词,省了大量推理功夫。
更关键的是,按字切会让序列变得很长。一段 1000 字的中文,按字切就是 1000 个 Token。模型处理 1000 个 Token 和处理 500 个 Token,计算量差一倍——注意力机制的计算复杂度是 Token 数的平方,差得更多。
这里有个直觉:如果一个字的序列是 1000,平方就是 100 万次计算;如果压缩到 500,平方就是 25 万次,计算量直接降到四分之一。所以“把文字压缩成更少的 Token”不光是省钱,更是让模型能处理更长文本的关键。
那按词切呢?“自然语言处理”切成一个词,“我喜欢吃苹果”切成五个词——听起来完美。
问题来了:词的组合是无限的。
“自然语言处理工程师”是一个词还是两个词?“反内卷”算不算一个词?“YYDS”呢?按词切,你的词表会膨胀到几百万,模型的嵌入层(每个 Token 对应一个向量)参数直接爆炸,训练根本扛不住。
而且按词切有个致命缺陷:遇到新词就抓瞎。 训练时没见过的词(人名、新术语、拼写错误),分词器不认识,只能返回一个 <UNK>(unknown)标记,模型完全不知道这个词是什么。你发一句“今天 ChatGPT 发布了 GPT-5o”,分词器不认识“GPT-5o”,全变成 <UNK>,模型看到的就是“今天 <UNK> 发布了 <UNK>”——信息全丢了。
你可能会说:那不断往词表里加新词不就行了?问题是语言是活的——每天都有新词诞生(“打卡”“内卷”“多巴胺穿搭”),词表永远追不上。而且每加一个词,模型的嵌入层就要重训,成本极高。
BPE 的思路是在“字”和“词”之间找个平衡点:高频组合合并成短 Token,低频文本拆成碎片拼。 这样词表大小可控(几万到十几万),又能覆盖几乎所有文本。最重要的是——遇到没见过的词,BPE 把它拆成已知的子词碎片,永远不会出现 <UNK>。
比如“tokenization”这个词,如果训练语料里没见过,BPE 会把它拆成“token”+“ization”两个子词。模型虽然没见过“tokenization”整体,但见过“token”和“ization”,能推断出大概意思。这就是子词分词的核心优势:开放词汇表——任何文本都能处理,永远不会抓瞎。
反过来,高频词会被合并成一个短 Token。比如“the”在英文语料里出现频率极高,BPE 几轮合并后就会把它变成一个独立 Token。这样一来,模型处理“the”只需要一个步骤,而不是三个(“t”+“h”+“e”),效率大幅提升。OpenAI 官方 README 里的原话:“It attempts to let the model see common subwords. For instance, ‘ing’ is a common subword in English, so BPE encodings will often split ‘encoding’ into tokens like ‘encod’ and ‘ing’.” 常见组成部分被独立成 Token,模型在不同上下文反复见到它,有助于泛化和理解语法。
3. BPE:大模型的“切词刀法”
BPE 全称 Byte Pair Encoding(字节对编码),2015 年由 Sennrich 等人在论文《Neural Machine Translation of Rare Words with Subword Units》中提出,最初用于机器翻译,后来被 GPT-2 采用,如今是主流大模型分词的事实标准。
3.1 一句话讲清 BPE 的核心逻辑
统计训练语料里所有相邻字符对的出现频率,把频率最高的组合合并成新 Token,重复这个过程直到词表达到目标大小。
就像压缩文件——你发现“th”这两个字母老一块出现,就把它们合成一个单元;又发现“the”老一块出现,再合成一个更大的单元。合并到最后,常见的词变成了一个 Token,罕见的词被拆成几个子词碎片。
3.2 用一个例子走一遍
假设训练语料里有这些词(后面的数字是出现次数):
low: 5次, lower: 2次, newest: 6次, widest: 3次
初始状态,每个词被拆成单个字符:
l o w: 5
l o w e r: 2
n e w e s t: 6
w i d e s t: 3
第 1 轮:统计所有相邻字符对,发现 e 和 s 一起出现了 9 次(newest 6 次 + widest 3 次),频率最高。合并 e + s → es:
l o w: 5
l o w e r: 2
n e w es t: 6
w i d es t: 3
第 2 轮:现在 es 和 t 一起出现了 9 次,合并 → est:
l o w: 5
l o w e r: 2
n e w est: 6
w i d est: 3
第 3 轮:l 和 o 一起出现了 7 次(low 5 次 + lower 2 次),合并 → lo:
lo w: 5
lo w e r: 2
n e w est: 6
w i d est: 3
第 4 轮:lo 和 w 一起出现了 7 次,合并 → low:
low: 5
low e r: 2
n e w est: 6
w i d est: 3
继续合并下去,low + e + r 会变成 lower,n + e + w + est 会变成 newest……

图2:BPE 每一轮找出最高频的相邻组合,合并成新 Token。从单字符开始,一轮一轮“攒”出常见词,就像捏乐高——经常一起出现的积木先拼好,下次直接用。
这个过程的关键特征:
- 自底向上:从最小单元(字符)开始,逐步合并
- 频率驱动:只合并出现频率最高的组合,低频组合不动
- 可逆:任何 Token 序列都能无损还原成原始文本
- 开放词汇:没见过的词被拆成已知子词,不会出现
<UNK>
你可能注意到了一个细节:BPE 的合并顺序完全由训练语料决定。换一批训练语料,合并顺序就不同,最终词表也不同。这就是为什么不同公司的模型——即使都用 BPE——分词结果完全不一样:它们的分词器是在各自的训练语料上训练出来的。
还有一个容易混淆的点:BPE 的“训练”和模型的“预训练”是两回事。分词器的训练发生在模型预训练之前,是一个独立的、相对简单的过程(统计频率 + 贪心合并)。分词器训练好了,词表固定下来,然后才开始用这个分词器把全部训练语料转成 Token 序列,喂给模型做预训练。分词器一旦确定,在整个模型生命周期里不再改变——这也是为什么换分词器等于换模型。
3.3 中文怎么切?
BPE 原本是处理英文字符的,中文怎么办?两种主流方案:
方案一:字节级 BPE(Byte-level BPE,简称 BBPE)。把所有文本(不管中文英文 emoji)先转成 UTF-8 字节,再在字节层面做 BPE。一个中文字占 3 个字节,所以理论上可能被拆成 3 个碎片。好处是——不管什么语言什么字符,都能处理,永远不会出现“未知字符”。
DeepSeek-V3 用的就是这种方案,词表大小 128,100。GPT-4o 用的 o200k_base 也是字节级 BPE,词表 200,000。
方案二:SentencePiece。Google 开源的工具,直接在 Unicode 字符层面做分词,不依赖空格切分。LLaMA 1/2 用的就是 SentencePiece,词表只有 32,000。而 Qwen 系列虽然也用 BBPE,但词表更大(约 152,000),中文压缩率和 DeepSeek 接近。
你可能会问:为什么不直接在中文“字”的层面做 BPE?因为字节级方案有一个巨大优势——它不关心你的文本是什么语言。英文、中文、日文、韩文、emoji、甚至二进制数据,统统先变成字节,再统一处理。一个分词器通吃所有语言,这对多语言大模型非常重要。
还有一个小知识:BPE 不是唯一的子词分词算法。还有两种常见变体——WordPiece(BERT 用的,合并标准不是频率最高而是似然增益最大)和 Unigram(SentencePiece 的另一种模式,从大量候选词里逆向选出最优子词集)。但主流大语言模型(GPT 系列、DeepSeek、Qwen、LLaMA 3)基本都用 BPE 或 BBPE,所以这个系列只重点讲 BPE,其他两种你了解存在即可。
3.4 词表大小是什么意思?
词表(vocabulary)就是分词器认识的所有 Token 的列表。词表大小就是这张列表有多少行。
- 词表太小:很多常见词被拆成碎片,模型处理同样文本需要更多 Token,推理更慢,也浪费上下文窗口
- 词表太大:模型嵌入层参数暴增(每个 Token 对应一个几千维的向量),训练更贵,而且低频 Token 在训练数据中出现次数太少,学不好
主流大模型的词表大小:
| 模型 | 词表大小 | 分词算法 |
|---|---|---|
| GPT-3.5 / GPT-4 | 100,256(cl100k_base) | BPE |
| GPT-4o / o1 / o3 | 200,000(o200k_base) | BPE |
| DeepSeek-V3 | 128,100 | BBPE(字节级 BPE) |
| Qwen 系列 | ~152,000 | BBPE |
| LLaMA 3 | 128,256 | BBPE |

图3:GPT-4o 把词表从 10 万翻到 20 万,是为了压缩非英语文本的 Token 数。国产模型普遍 12-15 万,兼顾中文压缩率和词表效率。
GPT-4o 为什么要翻倍词表?因为 cl100k_base 对中文压缩率太差——一个汉字经常被切成 2-3 个 Token。o200k_base 扩大词表后,中文压缩率显著提升(OpenAI 官方发布说明的说法是“增加了 tokenizer 的压缩率”,但未公布具体倍数)。
注意一个细节:GPT-3.5 和 GPT-4 用的是同一套分词器(cl100k_base),但 GPT-4o 换了新的(o200k_base)。这意味着同一个 OpenAI API,换模型版本可能导致 Token 数变化,这是很多人没意识到的坑。
3.5 动手实现一个极简 BPE
理论看再多,不如自己跑一遍。下面用 Python 实现一个 20 行的极简 BPE(只保留核心逻辑),你跑完就彻底懂了。
from collections import Counter
def train_bpe(corpus, num_merges=20):
# 1. 初始化:每个词拆成字符序列(字符间加空格,便于按"对"合并)
words = {}
for w, freq in corpus.items():
words[w] = [' '.join(list(w)), freq]
vocab = set(''.join(corpus.keys())) # 初始字符集
for _ in range(num_merges):
# 2. 统计所有相邻字符对的频率
pair_freq = Counter()
for seq, freq in words.values():
symbols = seq.split()
for i in range(len(symbols) - 1):
pair_freq[(symbols[i], symbols[i+1])] += freq
if not pair_freq:
break
# 3. 找到频率最高的对,合并成新 Token
best_pair = max(pair_freq, key=pair_freq.get)
merged = ''.join(best_pair)
# 4. 把语料里所有出现该对的地方替换成新 Token
new_words = {}
for w, (seq, freq) in words.items():
new_seq = seq.replace(best_pair[0] + ' ' + best_pair[1], merged)
new_words[w] = [new_seq, freq]
words = new_words
vocab.add(merged)
return vocab
# 训练语料:词 -> 出现次数
corpus = {
"low": 5,
"lower": 2,
"newest": 6,
"widest": 3,
}
vocab = train_bpe(corpus)
print("训练出的词表:", vocab)
运行这段代码,你会看到“low”、“lower”、“est”、“newest”等词条逐渐被合并出来——和我们前面手推的 4 轮过程完全一致。这就是 BPE 的全部秘密。
真实世界的 BPE 比这复杂得多(还要处理字节级、特殊 Token、正则预分词等),但核心就是这个 20 行的贪心合并逻辑。Karpathy(OpenAI 创始成员)写过 minbpe 教学项目,有兴趣可以去看他的视频讲解。
4. 同一句话,不同模型 Token 数能差多少?
这是最容易踩的坑。来看实测对比——拿“人工智能正在改变世界”这句话测几个模型:
| 模型 | Token 数 | 你看到的 |
|---|---|---|
| GPT-3.5(cl100k_base) | 约 12-14 | 几乎每个字被拆成 1-2 个碎片 |
| GPT-4o(o200k_base) | 约 8-10 | 中文压缩率提升,但仍不如国产 |
| DeepSeek-V3 | 约 5-6 | 中文优化,常见词整块保留 |
| Qwen | 约 5-7 | 同样针对中文优化 |

图4:同一句中文,GPT-3.5 可能切 12+ 个 Token,DeepSeek 只要 5-6 个。这意味着同样 128K 上下文窗口,DeepSeek 实际能塞下的中文是 GPT-3.5 的 2 倍多。
为什么差这么多? 因为分词器是在模型预训练之前单独训练的。分词器的训练语料和模型预训练语料通常是一致的——DeepSeek 和 Qwen 的训练语料里中文比例高,BPE 合并时自然把高频中文组合攒成了 Token。GPT 系列训练语料以英文为主,中文组合频率低,没被合并成短 Token。
这个差异直接影响三个东西:费用、速度、上下文容量。下一节细说。
4.1 先记住一张“日常换算表”
不想每回都跑代码?先记住这张常用表(DeepSeek/Qwen 的估值):
| 常见场景 | 大约消耗 |
|---|---|
| 一句“你好” | 1-2 Token |
| 一条 20 字的微信消息 | 10-15 Token |
| 一页 A4 中文文档(约 500 字) | 300-350 Token |
| 一篇 2000 字公众号文章 | 1200-1400 Token |
| 一份 10000 字技术文档 | 6000-7000 Token |
| 一本 20 万字小说 | 12-14 万 Token |
注意这是**国产模型(DeepSeek/Qwen)**的换算。用 GPT-3.5 的话,所有数字直接翻 2-3 倍。
用这张表算一笔账:DeepSeek-V3 的上下文窗口是 128K Token。按一个汉字约 0.6 Token 算,大约能塞进 20 万+汉字——相当于 2000 字文章约 100 篇,或 500 页的厚书。这就是为什么很多人说“DeepSeek 能直接读整本书”。
5. Token 如何影响你的钱包和体验
5.1 计费:API 按 Token 收费
OpenAI、DeepSeek、阿里云——所有大模型 API 的计费单位都是 Token,不是字符,不是请求次数。
价格通常写成“每百万输入 Token 多少钱”和“每百万输出 Token 多少钱”(输出比输入贵,因为生成比理解难)。你写的 Prompt 是输入 Token,模型生成的回答是输出 Token。
这就意味着:同样一段中文,用 GPT-3.5 处理比用 DeepSeek 贵不止一倍——不是因为单价差,而是因为 GPT-3.5 对同样文本消耗的 Token 数是 DeepSeek 的 2 倍多。
举个例子:你有一篇 10000 字的中文文档要喂给模型做摘要。
- 用 DeepSeek:约 6000 个输入 Token
- 用 GPT-3.5:约 15000-18000 个输入 Token
如果 DeepSeek 每百万输入 Token 收 1 元,GPT-3.5 收 0.5 元,你会觉得 GPT 更便宜?算一下:DeepSeek 花 0.006 元,GPT-3.5 花 0.008 元。GPT 单价便宜一半,实际花费反而更高——因为 Token 消耗量翻了 2-3 倍。
比价时必须按等量文本换算,不能直接比“每百万 Token 单价”。
5.2 速度:生成 Token 是串行的
大模型生成文本时,是一个 Token 一个 Token 吐出来的(实际上有投机解码等优化,但本质是串行过程)。每秒能生成多少 Token,叫“输出速度”或“生成速度”。
DeepSeek-V3 的生成速度大约 30-60 Token/秒(取决于部署方式)。如果一段回答有 500 个 Token,大约需要 8-17 秒。
这就是为什么你问大模型一个问题,它要“思考”几秒才开始打字,然后一个字一个字蹦出来——它在逐个生成 Token。
注意:输入 Token 和输出 Token 的处理速度完全不同。 输入(Prompt 处理)可以并行计算,速度快得多——10000 个输入 Token 可能 1 秒就处理完了;输出必须逐个生成,500 个输出 Token 要 8-17 秒。所以 API 计费时输入和输出分开算,输出贵得多。
5.3 上下文窗口:Token 是“长度单位”
大模型能“记住”的对话长度有限,上限叫上下文窗口(Context Window),单位当然是 Token。
- DeepSeek-V3:128K Token
- GPT-4o:128K Token
- Claude 3.5:200K Token
- Gemini 1.5 Pro:1M Token
但 128K Token 能塞多少中文?取决于分词器。DeepSeek 大约能塞 20 万字中文,GPT-3.5 只能塞 7-8 万字。同一个“128K”,实际容量差 2-3 倍。
这就是为什么很多人感觉“模型记不住前面说的话”——不是模型记忆差,是对话历史已经超出了上下文窗口的 Token 上限,旧消息被自动截断了。
这里有个常见的误解需要澄清:上下文窗口不是“对话框能显示多少条消息”,而是“模型一次推理能处理的 Token 总量”。它包括系统提示词、历史对话、当前问题、以及模型要生成的回答。很多新手把对话历史全塞进去,结果留给输出的空间不够,模型回答到一半就断了。
实操建议:在构建大模型应用时,始终用代码计算当前对话的 Token 总量,在接近窗口上限时主动截断或摘要旧消息。主流的大模型 SDK 都提供 Token 计数功能,不要靠肉眼估计。
5.4 最大输出 Token:模型能“说”多少
除了上下文窗口(输入+输出总量),还有一个容易忽略的限制——最大输出 Token。模型一次能生成多少 Token 是有上限的:
- GPT-4o:最大输出 16,384 Token
- DeepSeek-V3:最大输出 8,192 Token(默认 4,096)
- Claude 3.5:最大输出 8,192 Token
这意味着什么?如果你让模型写一篇长文,它写到上限就会自动停止,不管写没写完。很多人遇到“模型回答到一半突然断了”就是踩了这个坑——不是模型不想说了,是它被限制不能再说更多了。
解法:需要长输出时,把任务拆成多轮,让模型分段生成,你在每轮之间传一个“继续”的指令。或者用流式输出(Streaming)配合后端拼接,在模型停止后自动发起续写请求。
实操代码:用 OpenAI SDK 处理长输出的标准姿势:
from openai import OpenAI
client = OpenAI()
full_text = ""
while True:
# 把已经生成的内容拼回消息里,让模型接着写
resp = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "写一篇 20000 字的中文技术文章"},
{"role": "user", "content": "开始写"},
{"role": "assistant", "content": full_text}, # 已生成的部分
{"role": "user", "content": "继续"},
],
max_tokens=4000, # 每次只生成一段
)
chunk = resp.choices[0].message.content
full_text += chunk
if "完" in chunk or len(full_text) > 20000:
break
这段代码用“接力写作”的方式突破单次最大输出限制——每次生成 4000 Token,把已生成的内容塞回对话让模型接着写,直到文章写完。这是所有“AI 写长文”应用的底层套路。
6. 动手实验:用 tiktoken 数 Token
理论讲完,动手跑一遍。这里用 OpenAI 开源的 tiktoken 库(GitHub 19.1k stars),它就是 GPT 系列分词器的官方实现。
tiktoken 官方 README 里有一段值得注意的说明:“On average, in practice, each token corresponds to about 4 bytes.”(平均每个 Token 对应约 4 字节)。这是英文场景的经验值——1 个英文单词平均 4-5 个字母,约 4-5 字节,大约 1 个 Token。但中文一个字就 3 字节,按这个算法应该接近 1 个 Token 对应 1 个字,实际却不是——因为分词器的训练语料以英文为主,中文没有被充分合并。
6.1 安装和基本用法
pip install tiktoken
import tiktoken
# 选择编码器
# cl100k_base 对应 GPT-4 / GPT-3.5-turbo
# o200k_base 对应 GPT-4o / o1 / o3 系列
enc = tiktoken.get_encoding("cl100k_base")
# 编码:文本 → Token ID 列表
tokens = enc.encode("你好世界,Hello World!")
print(tokens)
# 输出类似: [57668, 53901, 10236, 3827, ..., 3304, 4422, 0]
# 数 Token 数
print(f"Token 数: {len(tokens)}")
# 解码:Token ID → 文本(无损可逆)
text = enc.decode(tokens)
print(text) # 你好世界,Hello World!
# 也可以按模型名自动选择编码器
enc_4o = tiktoken.encoding_for_model("gpt-4o") # 自动用 o200k_base
6.2 对比不同模型的 Token 数
import tiktoken
text = "人工智能正在改变世界,大模型技术日新月异。"
# GPT-4 / GPT-3.5
enc_cl100k = tiktoken.get_encoding("cl100k_base")
print(f"GPT-4 (cl100k_base): {len(enc_cl100k.encode(text))} tokens")
# GPT-4o
enc_o200k = tiktoken.get_encoding("o200k_base")
print(f"GPT-4o (o200k_base): {len(enc_o200k.encode(text))} tokens")
# 对比英文
en_text = "Artificial intelligence is changing the world."
print(f"English (cl100k_base): {len(enc_cl100k.encode(en_text))} tokens")
print(f"English (o200k_base): {len(enc_o200k.encode(en_text))} tokens")
跑完你会发现两个现象:
- 同样一句中文,cl100k_base 切出的 Token 数明显多于 o200k_base
- 英文的 Token 数差异远小于中文——因为两套词表都是英文优先设计的
这也是 OpenAI 在 GPT-4o 里换新词表的原因:非英语用户的体验太差了,同样一段话比英语用户多花 2-3 倍费用。
6.3 用 HuggingFace 加载国产模型分词器
如果你用 DeepSeek 或 Qwen,可以用 HuggingFace 的 transformers 库加载对应模型的分词器:
from transformers import AutoTokenizer
# DeepSeek-V3 分词器(注意:DeepSeek-V3 需要 trust_remote_code=True,
# 因为它的分词器依赖自定义代码,这是官方示例的标准写法)
tokenizer = AutoTokenizer.from_pretrained(
"deepseek-ai/DeepSeek-V3", trust_remote_code=True
)
tokens = tokenizer.encode("你好世界")
print(f"DeepSeek-V3: {len(tokens)} tokens")
print(f"Token IDs: {tokens}")
# Qwen 分词器(无需 trust_remote_code)
qwen_tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-7B-Instruct")
qwen_tokens = qwen_tokenizer.encode("你好世界")
print(f"Qwen: {len(qwen_tokens)} tokens")
注意:第一次运行会从 HuggingFace 下载模型分词器文件(几十 MB),需要网络。如果下载慢,可以用 ModelScope(魔搭社区)的镜像源,把
from_pretrained的路径换成魔搭对应的仓库名即可。
6.4 别忘了特殊 Token
以上代码数的是“文本内容”的 Token 数,但实际 API 调用时,还有一类隐藏的 Token 消耗——特殊 Token(Special Tokens)。
每个模型在处理你的请求时,会自动在文本里插入一些控制标记:
<|im_start|>/<|im_end|>:标记一轮对话的开始和结束(ChatML 格式)<|system|>/<|user|>/<|assistant|>:标记角色- BOS(Beginning of Sequence)/ EOS(End of Sequence):序列首尾标记
这些特殊 Token 也算进 Token 总数,但你在界面上看不到它们。一轮对话可能多出 5-10 个 Token 的隐藏消耗。对话轮次越多,这部分开销越大。
# 查看 tiktoken 的特殊 Token
import tiktoken
enc = tiktoken.get_encoding("cl100k_base")
# cl100k_base 的特殊 Token
print(enc.special_tokens_set)
# 输出类似: {'<|endoftext|>', '<|fim_prefix|>', '<|fim_middle|>', '<|fim_suffix|>'}
所以在做成本预估时,给每轮对话额外预留 10-20 个 Token 的余量,别算得太紧。
7. 三个真坑,每个都付过费
坑 1:你以为的“免费额度”根本不够用
你看到某 API 送“100 万 Token 免费额度”,心想够用一个月。结果一跑——你用 GPT-3.5 的 cl100k_base 编码器,一篇 5000 字的中文文档大约消耗 8000-10000 Token。一篇就吃掉 1%。如果每篇还要让模型输出 2000 字,又多 3000-4000 Token。
算一下:每天处理 10 篇文档,每篇输入 8000 + 输出 3500 = 11500 Token,一天 115000 Token,9 天就花完 100 万免费额度。
解法:换模型前先算实际 Token 消耗。中文场景优先用 DeepSeek/Qwen,Token 压缩率高,同样额度能用更久。按上面的例子换 DeepSeek:同样 5000 字中文只消耗约 3000 Token,100 万额度能用 30 天以上。
坑 2:上下文窗口“虚标”
模型宣称 128K 上下文,你以为能塞 12.8 万字中文。实际呢?用 GPT-3.5 的 cl100k_base,一个汉字约 1.5-2 个 Token,128K 大约只能塞 6-8 万字。用 DeepSeek,一个汉字约 0.6 个 Token,能塞 20 万字+。
更坑的是,有些模型的“128K 上下文”是指输入+输出合计。你以为还能再让它输出 2 万字,实际上输入已经把窗口挤满了,输出吐了几个 Token 就被截断。
解法:用目标模型的分词器实际计算你的文本 Token 数,别信“1 Token ≈ 1 个字”这种粗略估算。给输出预留足够的 Token 空间,别把上下文窗口塞满。
坑 3:换模型 = 换刀法,Token 数会变
你用 DeepSeek 跑通了一个流程,Prompt 精心设计成 4000 Token 以内。换 GPT-4o 后一测——同样 Prompt 变成 6000+ Token,直接超出某些限制。
更隐蔽的情况:你从 GPT-4 换到 GPT-4o,以为同是 OpenAI 系列没事。结果 GPT-4 用 cl100k_base,GPT-4o 用 o200k_base,Token 数不一样。你的 Prompt 里如果有很多中文,GPT-4o 的 Token 数会减少(词表大了,压缩率高了);如果有很多英文,变化不大。
解法:切换模型前,用目标模型的分词器重新数一遍 Token。OpenAI 系用 tiktoken,HuggingFace 系用 transformers.AutoTokenizer,国产模型用官方 SDK。把它做成一个自动化检查脚本,切模型之前自动跑一遍。
8. 经验清单:带走这 5 条就够了
-
Token 是大模型的最小单位:计费、上下文窗口、输出长度、推理速度,全以 Token 计。搞不懂 Token,你连账单都看不懂。
-
不同模型对同一句话切出的 Token 数可能差 2-3 倍:国产模型中文压缩率比 GPT-3.5 高 2-3 倍。比价时必须按等量文本换算,不能直接比每百万 Token 单价。
-
BPE 的核心逻辑就一句话:高频组合合并成短 Token,低频文本拆成碎片拼——和 ZIP 压缩是同一个道理。词表大小是效率和覆盖率的折中。遇到没见过的词,BPE 拆成已知子词,永远不会抓瞎。
-
永远用分词器实际数 Token,不要用字符数估算:1 个中文字在 GPT-3.5 里是 1-2 个 Token,在 DeepSeek 里可能只有 0.6 个。估算误差能让你的成本预算翻倍。
-
切模型 = 换刀法:同一个 Prompt 在不同模型里 Token 数不同,即使同是 OpenAI 系列,GPT-4 和 GPT-4o 的分词器也不一样。迁移模型前,用目标模型的分词器重新数一遍 Token,防止上下文超限。
下篇预告
下一篇:《大模型实战指南(2)——上下文窗口:为什么聊着聊着模型就“失忆”了?》
1M 上下文到底能不能塞下一整本书?为什么模型说“忘了”前面的对话?KV Cache 怎么让长文本推理不死等?注意力机制的计算量为什么随 Token 数平方增长?
系列:大模型实战指南,篇篇连载,从 Token 到 Agent,带你从零搞懂大模型。关注不迷路。
本文所有数据基于 2026 年 8 月公开信息整理。词表大小和分词算法来自官方文档/技术报告:OpenAI tiktoken GitHub 仓库(github.com/openai/tiktoken,19.1k stars)、DeepSeek-V3 技术报告(arxiv.org/abs/2412.19437)、Qwen 官方文档。Token 换算比例为实测估值,具体数值取决于模型版本和分词器配置。
更多推荐


所有评论(0)