Token 与分词
你打开 ChatGPT 输入"今天天气怎么样",你以为它看到的是这七个汉字。其实它一个汉字都没看见。在这句话进入模型之前,它已经被一台叫"分词器"的机器切成了若干块碎片,每块碎片换成一个整数编号——模型从头到尾看到的,只是一串数字。这一节要讲的就是这台切碎机:它怎么切、为什么这么切、切出来的碎片怎么变成你账单上的钱,以及为什么"一个汉字等于两个 token"这句流传最广的说法,其实已经过时好几年了。
你从菜市场买回一整颗白菜、一根整萝卜,直接扔进锅里能煮吗?不行——锅装不下,火也进不去。你得先在厨房的案板上把它们切开。
关键是:切多大?切成大块,省事,但炒不透、不好入味;切成肉末那么细,倒是好熟,可切的功夫太多,而且一炒就烂、认不出这是白菜还是萝卜。有经验的厨师会切成"不大不小"的段——刚好能下锅,又刚好还能看出是什么菜。
分词器干的活儿,就是这位厨师手里那把刀。它把一句话切成"不大不小"的碎片:不是整句(太大,模型学不动),也不是单个字符(太细,一句话切出上百块,算不起)。那个"不大不小"的碎片,就叫 Token。
先把"Token"这个词彻底说清
Token 这个词听着玄,其实就是"一小块"。它在英语里的日常意思是"代币、筹码"——游戏厅里那种圆铁片,你拿钱换筹码,机器只认筹码不认钱。这个比喻好得不能再好:模型就是那台游戏机,它不认汉字、不认字母,只认筹码上的编号。
整个流程分三步,中间没有任何魔法:
你输入的原文 → 切碎 → 查表换编号 → 送进模型
"今天天气怎么样"
↓ 分词器切碎(Tokenization)
["今天", "天气", "怎么", "样"] ← 4 块碎片(示意,实际切法看词表)
↓ 查词表,每块换成一个整数 ID
[37046, 104651, 100678, 33014] ← 模型真正看到的东西
↓ 每个 ID 查一张巨大的表,换成一个向量
[[0.21, -1.33, ...], [...], ...] ← 进入 Transformer 的输入
注意第三步——每个整数 ID 会去查一张叫 Embedding 表(嵌入表,可以理解成"编号 → 一串数字"的字典)的东西,换成一串浮点数。这一步我们在第 2 章讲过,这里不重复。本节只关心第一步和第二步:怎么切、切成几块。
为什么这两步值得单开一节?因为token 是整个 LLM 世界的公制单位。上下文窗口按 token 算,API 价格按 token 算,生成速度按"每秒多少 token"算,限流按"每分钟多少 token"算。你要是不知道自己那份 8000 字的文档到底是多少 token,你就没法估算它能不能塞进窗口、要花多少钱。这就跟不知道一斤是多少两,就没法在菜市场算账一样。
为什么不干脆按字切、或者按词切
最容易想到的切法有两种极端,而它们都被试过、都失败了。这段历史值得讲清,因为它解释了今天这套看起来奇怪的切法是怎么来的。
极端一:按单个字符切(Character-level)。英文就是 26 个字母加标点,词表小到只有几十个,永远不会遇到"不认识的词"。听起来很美,问题是切得太碎——"international"一个词变成 13 块。一篇 1000 词的英文文章切出六七千块碎片,而注意力的计算量是碎片数的平方(第 7 章讲过),碎片数翻五倍,计算量就翻二十五倍。更糟的是,单个字母 "t" 本身没有任何语义,模型得从零开始学"t-h-e 连起来是个冠词"这种事,学习效率极低。
极端二:按完整单词切(Word-level)。一个词一块,语义最完整,碎片数最少。问题是词表爆炸——英文常用词就有几十万,加上人名、地名、拼写错误、新造词,根本列不完。而且一旦遇到词表里没有的词(叫 OOV,Out-Of-Vocabulary,"词表外"),模型只能吐一个 <unk>(unknown,未知)敷衍过去,信息全丢。你写"ChatGPT",它看到的是"未知",这怎么可能好用。中文更麻烦——中文词与词之间没有空格,"研究生命起源"到底是"研究生/命/起源"还是"研究/生命/起源"?这本身就是一个几十年没彻底解决的难题。
| 切法 | 词表大小 | 一句话切几块 | 致命问题 |
|---|---|---|---|
| 按字符切 | 几十到几千 | 很多(碎) | 序列太长,注意力算不起;单字符无语义 |
| 按单词切 | 几十万甚至无穷 | 很少 | 词表爆炸;遇生词只能吐"未知";中文没法切 |
| 按子词切(BPE) | 约 10 万 / 20 万 | 适中 | 切法不直观,会切出奇怪碎片——但可接受 |
于是人们走了中间路线,叫 Subword(子词,"比词小、比字母大"的碎片)。常用词整块保留,生僻词拆成几块常见零件。这就是今天所有主流 LLM 的做法。
想象你要买一盒乐高,要求是"能拼出世界上任何东西"。
方案 A:盒里只装一种最小的 1×1 颗粒。理论上什么都能拼,但拼一辆车要三千块颗粒,拼到手断。
方案 B:盒里装的全是成品——一整辆车、一整栎房子、一整艘船。拼起来倒是一秒钟的事,可你想拼一只从没出过的恐龙,盒里没有,就彻底拼不出来了。
方案 C(现实中乐高真正的做法):盒里装中等大小的零件——车轮、车门、窗框、屋顶斜块。常见的东西有现成件,一装就成;没见过的东西,用基础块慢慢搭也搭得出来。
BPE 分词就是方案 C。"the""ing""国家""因为"这种高频组合,词表里有现成的整块;碰到"Schadenfreude"这种冷门德语词,就拆成 "Sch"+"aden"+"fre"+"ude" 几个零件拼出来。既不至于碎得拼不动,又保证世界上任何字符串都拼得出来——这就是子词分词的全部智慧。
BPE:一套"统计谁老在一起"的笨办法
那些"中等大小的零件"是谁定的?答案是:没人定,是数出来的。主流方法叫 BPE(Byte Pair Encoding,字节对编码)。这名字听着玄,它干的活儿其实笨得可爱——就是反复找出"最常挨在一起出现的两个东西",把它们粘成一个新东西。
用一个具体例子走一遍,你会发现它完全不需要智商:
假设我们的全部语料只有这几个词(数字是出现次数):
low × 5 lower × 2 newest × 6 widest × 3
第 0 步:先拆到最细,每个字符一块
l o w l o w e r n e w e s t w i d e s t
第 1 步:数一数哪两个相邻块最常一起出现
"e"+"s" 出现了 6+3 = 9 次 ← 最多!粘起来
→ 新零件 "es" 进词表
n e w es t w i d es t
第 2 步:再数
"es"+"t" 出现 9 次 ← 最多,粘起来
→ 新零件 "est" 进词表
n e w est w i d est
第 3 步:再数
"l"+"o" 出现 5+2 = 7 次 ← 最多,粘起来
→ 新零件 "lo" 进词表
……一直重复,直到词表凑够 10 万个零件为止。
最后词表里会有什么?
高频整词: "low" "the" "and"
高频词尾: "est" "ing" "ed" "tion"
单个字符: "a" "b" "c" ...(保底,永远拆得下去)
就这么简单。没有语法学家参与,没人告诉它 "est" 是英语最高级词尾——它纯粹是因为"这两个字符老在一起"才被粘出来的。而结果恰好和语言学高度吻合,这件事本身就挺让人感慨。
名字里那个 Byte(字节)也值得解释一句。现代 LLM 用的是 Byte-level BPE——它不是在"字符"上做统计,而是在字节上做。一个字节就是 0~255 这 256 个可能值,任何文字(中文、日文、阿拉伯文、emoji、乱码)在计算机里都是一串字节。这一招保证了世界上不存在"切不出来"的输入——最坏情况下退化成一个字节一块,但永远不会吐出"未知"。你发一个从没见过的 emoji、一段火星文、一串乱码,它都能老老实实编码回去,这就叫"无损"。
词表有多大:cl100k_base 与 o200k_base
OpenAI 开源了自家的分词库,叫 tiktoken。里面有几套不同的编码方案,名字看起来像密码,其实规律很清楚——名字里的数字就是词表大小:
| 编码名 | 词表规模 | 用于哪些模型 | 中文表现 |
|---|---|---|---|
cl100k_base | 约 10 万 | GPT-3.5 / GPT-4 系列 | 一般,中文明显吃亏 |
o200k_base | 约 20 万 | GPT-4o 及之后的模型 | 明显改善,多语言优化 |
这里必须严谨一句:"约 10 万""约 20 万"是从名字推出来的量级,官方并没有明文声明一个精确到个位的数字。网上流传的那些精确数值(比如某个具体的六位数)多半来自第三方读取词表文件后的统计,不能当官方口径引用。所以本节一律写"约 10 万 / 约 20 万"。在技术科普里,"知道自己不知道多精确"和"知道数字"一样重要。
那么词表从 10 万扩到 20 万,换来了什么?换来了同一段文字碎片数量的下降。词表大,就意味着更多常见组合能整块塞进去,不必拆开。这对中文、日文、阿拉伯文这些"非英语"语言的收益最大——因为老词表的零件是照着英语语料统计出来的,中文的高频组合根本没进榜。换成大白话:老词表是照着英国人的菜谱配的刀具,切中餐自然不顺手;新词表把中式菜刀也配上了。
★ 一个汉字等于几个 Token:一笔很多人算错的账
这是本节最值得记住的一条,也是网上错误信息最集中的一条。
先给可以直接引用的官方口径。DeepSeek 官方文档给出的换算是:
- 1 个英文字符≈ 0.3 个 token(也就是说,大约 3~4 个英文字母才凑成 1 个 token)
- 1 个中文字符≈ 0.6 个 token(也就是说,不到 1 个 token!两个汉字才勉强凑成一个多 token)
- 官方同时声明不同模型的分词器有差异,这只是估算;实际用量一律以 API 返回的 usage 字段为准。
看清楚这个数字:1 个中文字符约 0.6 个 token,而不是 2 个。
可网上到处都在说"一个汉字等于两个 token"。这句话不是凭空编的,它曾经是对的——在 GPT-3、GPT-3.5 那个年代,用的是 cl100k_base 及更早的词表,那些词表几乎完全按英语语料统计出来,一个汉字在 UTF-8 编码里占 3 个字节,词表里又没有对应的中文整块零件,只能拆成 2~3 个字节块。所以"一个汉字 2 个 token"在当年是实测结论。
但从 GPT-4o 的 o200k_base 开始,以及国内厂商自研的分词器(它们的训练语料本来就中文占大头),中文被大幅优化——常见的双字词、三字词、四字成语都进了词表整块。于是同一句中文,碎片数直接腰斩甚至更多。
| 说法 | 成立范围 | 结论 |
|---|---|---|
| 1 个汉字 = 2 个 token | GPT-3 / GPT-3.5 及更早(cl100k 一代及以前) | 已过时,别再拿来估算新模型 |
| 1 个汉字 ≈ 0.6 个 token | DeepSeek 官方给出的估算口径 | 可引用,但仍是估算 |
| 1 个汉字通常 < 1 个 token | 针对中文优化过的新词表 | 当前最稳妥的直觉 |
| 1 token ≈ 4 个英文字符 | 没找到现行官方出处 | 业界经验值,估算够用,别当权威数据引 |
这件事的实际影响很大。打个比方,你要把一份 5 万汉字的合同扔给模型:按过时说法算是 10 万 token,你可能会觉得"完了,太长了塞不进去,得先分段";按真实口径算只有约 3 万 token,绝大多数现代模型一口就吃下了。一个过时的常识,能让你白做半天工程。
下面这个小工具让你亲眼看看一句话是怎么被切开的。输入中文、英文、代码、emoji 各试一次,看看碎片数怎么变——这比读十遍文字描述都直观。
用 Python 亲手数一遍
估算终究是估算。要拿到确切的 token 数,最靠得住的办法是本地跑一遍分词器。OpenAI 的 tiktoken 库可以离线做这件事,不消耗任何 API 额度:
# 安装: pip install tiktoken
import tiktoken
# 取两套编码对比:老词表 vs 新词表
old = tiktoken.get_encoding("cl100k_base") # GPT-3.5 / GPT-4 时代,约 10 万词表
new = tiktoken.get_encoding("o200k_base") # GPT-4o 之后,约 20 万词表
samples = [
"今天天气怎么样",
"Hello, how is the weather today?",
"def add(a, b): return a + b",
"面朝大海,春暖花开。",
"🍜🥢",
]
print(f"{'原文':<36} {'字符数':>5} {'cl100k':>7} {'o200k':>6}")
print("-" * 60)
for s in samples:
a = len(old.encode(s))
b = len(new.encode(s))
print(f"{s:<36} {len(s):>5} {a:>7} {b:>6}")
# 想看清到底切成了哪几块,就把每块解码回来看
ids = new.encode("研究生命起源")
for i in ids:
print(i, "→", repr(new.decode([i])))
这段代码里最值得你亲手跑一遍的是最后那个循环。它会把每一块碎片单独打印出来,你会看到一些很反直觉的切法——比如英文单词前面那个空格常常被算进碎片里(" the" 和 "the" 是两个不同的 token!),比如同一个词大写和小写是两个不同编号,比如换行符自己占一个 token。
另外还有一件工程上很重要的事:API 返回的 usage 字段才是最终账单依据。你自己数出来的数和账单可能有几十个 token 的差距,因为对话接口会自动加上角色标记、消息分隔符这类"看不见的 token"(后面会讲)。自己数是为了估算和优化,对账要看 usage。
Token 就是钱:把价格表摊开看
现在来算钱。所有主流 LLM 的 API 都按 token 计费,而且输入和输出两套价。下面是查询日期 2026 年 8 月 6 日的一组官方标价,单位是"每百万 token 多少美元":
| 模型 | 输入(每百万 token) | 输出(每百万 token) | 输出 / 输入 倍数 |
|---|---|---|---|
| GPT-5.6 Sol | $5 | $30 | 6 倍 |
| Claude Opus 4.7 | $5 | $25 | 5 倍 |
| Claude Sonnet 4.6 | $3 | $15 | 5 倍 |
| DeepSeek-V4-Flash | $0.14(缓存命中 $0.0028) | $0.28 | 2 倍 |
三件事值得盯一下。
第一,输出永远比输入贵,而且贵好几倍。这不是厂商乱定价,背后是硬件层面的硬道理——我们在 §8.2 会把这笔账彻底讲透(关键词是 prefill 和 decode 两个阶段)。这里先给结论:读你的问题可以整段并行算,写答案只能一个一个挤出来,后者对硬件的折磨程度完全不同。
第二,缓存命中价便宜得离谱。DeepSeek 缓存命中的输入价是 $0.0028,是原价 $0.14 的五十分之一;Claude 的缓存命中价是输入价的 0.1 倍。所谓缓存命中,说白了就是:你这次的开头一大段和上次一模一样(比如同一份长系统提示词、同一份长文档),服务端把上次算过的中间结果留着没扔,这次直接接着用,不必重算。这就像餐厅后厨提前熬好的一大锅高汤——每来一位客人不用重新熬八小时,舀一勺就上菜。工程上的启示很直接:把不变的长内容放在提示词最前面,把变化的部分放后面,缓存命中率会显著提高,账单能砍掉一大截。
第三,不同厂商的价差是几十倍量级。DeepSeek-V4-Flash 的输入价 $0.14 对比 GPT-5.6 Sol 的 $5,差了三十五倍。把这个数字换算成可感知的量:假设你要处理一本 100 万汉字的长篇小说(约 60 万 token),只算输入,用前者花约 8 美分(人民币六毛钱),用后者花约 3 美元(人民币二十多块)。同样一件事,一顿早饭钱和一杯咖啡钱的差别。选型时先想清楚任务需要多强的模型,别一律用最贵的。
分词带来的那些"AI 怎么这么蠢"的怪事
知道了分词机制,你就能解释一大批看起来智商为负的 AI 行为。这些不是模型笨,是它压根看不到你看到的东西。
- 数不清一个词里有几个字母经典问题:"strawberry 里有几个 r?"模型答错过很多次。因为它眼里这个词可能只是
str+aw+berry三块碎片,它从没见过里面的单个字母长什么样。好比你只拿到三个封好的快递包裹,不拆箱就要你数出总共有几颗螺丝——数不出来不是你笨。 - 把字符串倒着写会写错同理。倒序需要逐字符操作,而它的操作单位是碎片,不是字符。
- 大数字算术容易错"1234567" 可能被切成
123+456+7,也可能切成1234+567。同一个数字在不同上下文里被切成不同碎片,进位关系被切断了,算错就不奇怪。这也是为什么让它调用计算器工具远比让它硬算靠谱。 - 押韵、藏头诗做不好押韵关心的是最后一个音节,藏头诗关心的是每行第一个字——都是字符级任务,而它的视野是碎片级。
- 同一个词大小写被当成两个东西
"Apple"和"apple"是两个不同编号。它们的含义要靠模型在训练中自己学会关联,而不是天生就知道。
一句话总结:凡是"需要看清单个字符"的任务,LLM 天生弱,这是分词层面的先天限制,不是训练数据不够。知道这一点,你就不会拿这类问题去证明"AI 不行",也不会指望换个更大的模型就解决——该用工具的地方就得用工具。
看不见的 Token:特殊标记与对话模板
还有一批 token 你从来没输入过,但它们确实占了你的额度。它们叫特殊 token(Special Tokens),是模型内部的"控制符",负责告诉模型"这段话是谁说的""到这儿该停了"。
一次对话请求,模型实际看到的东西大致长这样(不同厂商格式不同):
<|im_start|>system
你是一个乐于助人的助手。
<|im_end|>
<|im_start|>user
今天天气怎么样
<|im_end|>
<|im_start|>assistant
← 模型从这里开始往下写
那几个 <|im_start|> / <|im_end|> 就是特殊 token。
im = "instant message"(一条消息)
它们各占 1 个 token,不属于你输入的内容,但计费
常见特殊 token 的职责:
BOS (Beginning Of Sequence) 一段文本的开头
EOS (End Of Sequence) 一段文本的结尾
PAD (Padding) 批处理时把短句垫齐用的填充
UNK (Unknown) 未知内容(Byte-level BPE 基本不需要它了)
其中 EOS 尤其重要——模型什么时候停下来不说话,靠的就是它自己吐出一个 EOS token。不是有个程序在旁边掐着表喊停,是它自己写下了"句号"。这一点在 §8.2 会再提一次。
实用启示:多轮对话的成本增长比你以为的快。每一轮你都要把之前所有轮次连同这些控制符一起重发一遍(因为模型没有记忆,这是 §8.3 的主题),聊到第二十轮时,单次请求的输入 token 可能已经是第一轮的几十倍。这就像每次去银行办业务,柜员都要求你把之前所有办过的业务凭证一起重新交一遍——越办到后面,那摞纸越厚,每次都得重新过一遍。
中文吃亏吗?多语言公平性的现实
既然分词器的零件是从语料里统计出来的,那语料里哪种语言多,哪种语言就占便宜。这是个客观事实,也带来了一个不太被注意的公平问题。
在英语主导的老词表里,同样表达一个意思,英语用的 token 最少,中文次之,而一些小语种(比如缅甸语、泰米尔语)可能要多花好几倍的 token。这意味着三件很实在的不公平:同一份预算,小语种能处理的内容更少;同样的窗口,小语种能塞进去的信息更少;同样一句话,小语种要跑更多次前向,回答更慢。
好消息是这件事在快速改善。词表从约 10 万扩到约 20 万,扩出来的容量主要就是给非英语语言的。国产模型的分词器因为训练语料本来中文占大头,中文效率通常比国际模型还要好一些。换成大白话:这就相当于原来超市只备了做西餐的调料,现在把八角、桂皮、豆瓣酱也上架了——做中餐的人不用再拿三样西式香料勉强凑一个味儿。
顺手记一个实用结论:如果你在做成本敏感的应用,同一段意思用中文写提示词往往比英文更省 token(中文信息密度高,一个汉字承载的意思比一个字母多得多)。这跟"用英文写提示词效果更好"的传言是两个维度的事,别混为一谈——效果和成本要分开算。
分词器家族:BPE 之外还有谁
BPE 不是唯一的方案,历史上有三条主要路线,今天各有各的地盘。知道它们的区别,主要是为了在读论文和读模型配置文件时不至于一头雾水。
| 方法 | 合并零件的依据 | 代表模型 | 特点 |
|---|---|---|---|
| BPE | 谁和谁挨着出现得最多(纯频次) | GPT 全系、Llama、DeepSeek | 简单、快、可预测,事实上的行业标准 |
| WordPiece | 合并之后能让语料的可能性变得最大(概率增益) | BERT 系列 | 比 BPE 多算一步,切分略更"讲道理" |
| Unigram | 反着来:先备一大堆候选零件,再一批批删掉最没用的 | T5、mT5 | 同一句话可以有多种切法,训练时能随机换切法当数据增强 |
另外你一定会碰到一个词:SentencePiece。它不是第四种算法,而是 Google 开源的一个工具库,里面同时实现了 BPE 和 Unigram。它最大的贡献是彻底不预设"词是用空格分开的"这件事——它把空格本身也当成一个普通字符(用一个特殊符号 ▁ 表示)。这一步对中文、日文、泰文至关重要,因为这些语言压根没有空格分词的概念。换成大白话:以前的工具要求你先把菜按份摆好再送进厨房,SentencePiece 说"整堆送进来就行,我自己会分"。
顺便说一个容易搞混的点:分词器是要"训练"的,但它的训练跟模型训练完全是两回事。分词器的训练只是在语料上数频次、跑合并,几小时就能跑完,产出是一张静态的词表文件(几兆到几十兆),之后永远不变。模型训练则要几万张显卡跑几个月。而且词表一旦定下来,就再也改不了了——因为 Embedding 表的每一行都对应一个 token 编号,改词表等于把模型的输入层全部推翻,只能重新预训练。这就像一栋楼的门牌号系统:楼盖好之后你想把"3 号房"改叫"7 号房",所有住户的钥匙、水电账户、快递地址全得跟着改,代价大到不如重盖。
词表大小的取舍:不是越大越好
既然大词表能让碎片数变少,那为什么不把词表做到一千万,让每个常见句子都成为一个 token?因为这里有一笔清清楚楚的三方拉锯。
- 词表变大的好处同一段文字切出的碎片更少 → 序列更短 → 注意力的平方级开销骤降 → 又快又省钱,而且相当于变相扩大了上下文窗口(同样 1M token 能装更多实际内容)。
- 词表变大的代价一 · 参数暴涨Embedding 表的行数就是词表大小。词表从 10 万涨到 20 万,输入层和输出层的参数直接翻倍。而输出层每一步都要对全词表算一遍概率——词表翻倍,每生成一个 token 的最后那一步计算量就翻倍。
- 词表变大的代价二 · 长尾学不动词表里排在后面那几万个零件,在训练语料里可能只出现过几十次。它们对应的 Embedding 行几乎没被训练过,等于占着位置不干活。好比仓库里买了一整排货架专门放冷门零件,一年动不了一次,租金照付。
- 现实中的甜点区所以主流选择落在 3 万到 25 万之间:早期英文模型 3~5 万,GPT-3.5/4 一代约 10 万,GPT-4o 之后约 20 万。再往上,收益开始被参数成本吃掉。
把这个取舍换算成可感知的量:假设向量维度是 4096,词表每多 1 万个 token,Embedding 表就要多 4096 万个参数。10 万个参数如果印在一张 A4 纸上大约能印满一页,那 4096 万个参数就是四百多页——相当于一本厚辞典,而这只是"词表多了 1 万条"的代价。词表大小从来不是随便拍的数。
常见误解一次澄清
| 常听到的说法 | 实际情况 |
|---|---|
| "一个汉字就是 2 个 token" | 过时。只对 GPT-3/3.5 那代旧词表成立;针对中文优化后的新词表通常 < 1 |
| "token 就是单词" | 不对。常用词可能是一整块,生僻词会被拆成几块,空格和换行也各自占块 |
| "所有模型的 token 数一样" | 不对。每家分词器不同,同一句话在不同模型上的 token 数能差三成以上 |
| "我数一下字符除以 4 就是 token 数" | 只是英文的业界经验值,中文完全不适用,且无现行官方出处 |
| "输入输出应该一个价" | 不对,输出通常贵 5~6 倍,原因是硬件层面的(见 §8.2) |
| "缓存只是省点小钱" | 不对。缓存命中价可低至原价的十分之一甚至五十分之一,是长提示词场景最大的省钱杠杆 |
| "模型能看见每个字母" | 不对。它看见的是碎片编号,字符级任务(数字母、倒序、押韵)天生弱 |
写给要动手的人:五条实操建议
- 上线前先测真实 token 数别靠经验公式估。用
tiktoken或厂商提供的接口跑一遍你自己的真实数据,成本预估误差能从 50% 降到 5% 以内。 - 长而不变的内容放最前面系统提示词、知识库文档、few-shot 示例——这些每次都一样的东西放开头,才能吃到前缀缓存的折扣。变量放最后。
- 对账一律看 usageAPI 返回里的
usage.prompt_tokens/usage.completion_tokens是唯一权威。本地统计只用于优化决策。 - 该用工具就别硬算数字符、精确算术、字符串反转这类任务,交给函数调用/代码执行,不要指望模型硬扛分词的先天限制。
- 给输出设上限输出是最贵的那一段。
max_tokens设一个合理值,既防止跑飞,也防止账单意外。
模型眼里没有汉字、没有字母,只有一串编号。把文字切成编号的那台机器叫分词器,切出来的每一块叫 Token——它是整个 LLM 世界的公制单位:窗口按它算、价格按它算、速度按它算。
切法走的是中间路线:不按字符(太碎,注意力算不起),不按单词(词表爆炸、遇生词就哑),而是用 BPE 反复合并"最常挨在一起的两个东西",统计出约 10 万(cl100k_base)或约 20 万(o200k_base)个中等大小的零件。最该更新的一条常识是:「一个汉字 = 2 个 token」已经过时,新词表下通常不到 1 个,DeepSeek 官方估算是约 0.6 个。
知道分词机制,你就同时解释了两类现象:为什么它数不清 strawberry 里有几个 r(它看不见单个字母),以及为什么输出比输入贵五六倍(下一节的主题)。
下一节我们进入运行时——为什么它非得一个字一个字往外冒。答案会顺带解开"输入便宜输出贵"这笔账。