你有没有试过问 AI:「strawberry 里有几个 r?」
这么简单的问题,小学生一秒就能答对:3 个。但 GPT-4、Claude 这些顶流模型,当年在这个问题上翻车的截图满天飞。有人借此嘲讽 AI 不如小学生。但真正的原因,藏在一个大多数人从没听过的环节里:模型在读到你的文字之前,先要经过一个叫 tokenizer 的部件,把文字切成一块一块的「词牌」。
今天这篇就用一个真实实验,把 token 的上游环节 tokenizer 讲明白。看懂它,你就能理解 AI 的很多「怪毛病」:为什么它数不清字母、为什么中文比英文贵、为什么让它反转字符串会翻车。
先说结论:AI 读到的不是字母,是碎块
大模型没法直接读文字。它内部的计算全是数字运算,所以任何文本进模型之前,都得先变成一串数字。负责把文字变成数字的,就是 tokenizer(分词器)。
关键在于它切分的方式。它不是一个字母一个字母地切,也不是按词典里有没有这个词来切,而是按统计规律切出「高频碎块」。我在本机用 OpenAI 官方的分词工具 tiktoken(GPT-4o 用的 o200k_base 词表)跑了一下,「strawberry」这个词被切成了三块:
st / raw / berry
模型看到的输入就是这三块碎块对应的编号,原始字母 st、raw 之间的边界信息已经没了。它要数 r,就得在没有任何「字母表」参照的情况下,从碎块的编号里反推拼写。这不是推理能力差,是题目在一开始就被改写了:它拿到的根本不是拼好的单词。
tokenizer 是怎么工作的:从 256 个字节开始合并
现在主流大模型(GPT、Llama、Mistral 等)的 tokenizer,用的基本是同一个算法:BPE,Byte Pair Encoding,字节对编码。Karpathy 在他的 minbpe 项目里写得很直接:这个算法由 GPT-2 论文带火,如今所有现代大模型都用它来训练自己的分词器。
BPE 的思路不复杂,分三步:
第一步,把所有文本先用 UTF-8 编码拆成字节。任何语言都逃不出这 256 个基础字节,这是「保底」:再偏门的字符,最差也能被表示。
第二步,统计语料里哪两个相邻字节块出现的次数最多,把它合并成一个新块,词表里多一个条目。比如英文语料里 t 和 h 几乎总是一起出现,就合并成 th;th 和 e 又高频相邻,就合并成 the。
第三步,重复第二步,直到词表达到预设大小。
训练完成后,每个模型都有自己的「词表」。我用 tiktoken 实测了两个词表的大小:GPT-4o 在用的 o200k_base 有约 20 万个条目,GPT-4/3.5 的 cl100k_base 有约 10 万个。
这里有个容易误会的地方:训练 tokenizer 和训练模型是两码事。Hugging Face 官方课程里专门提醒过这一点:训练模型靠梯度下降,本质是随机的;训练 tokenizer 是纯统计,在同样的语料上跑同样的算法,结果永远一样。它不学「含义」,只数「谁和谁经常挨在一起」。
三个实验,看懂 AI 的怪毛病从哪来
光讲原理还是虚,我跑了几个真实切分,结果比预想的还有意思。
实验一:换个词表,切碎方式完全不同
同样是 strawberry,在 o200k_base 里切成 st / raw / berry 三块;而在 GPT-4 的 cl100k_base 词表里,切的是 str / aw / berry。又比如 tokenizer 这个词,o200k_base 切成 token / izer 两块,cl100k_base 干脆整词一个 token。
同一段文字,不同模型看到的「颗粒度」是不一样的。词表越大,常见词越容易被整词收录,切得越粗;词表小,就得多切几刀。所以严格说,AI 数不清字母不是模型笨,是它上游那个「翻译官」把词切成了字母顺序不可见的碎块,而且每家翻译官的切法还不一样。
实验二:中文为什么更「贵」
我把「为什么 AI 数不清 strawberry 里有两个 r」这句话分别丢给两个词表。o200k_base 切成 11 块,其中「为什么」三个字是完整一块;cl100k_base 切成 15 块,「为」和「么」各自成块,中间还夹着不完整的字节(解码时直接显示乱码,完整序列才能还原)。
原因在训练语料。BPE 的词表是从训练语料里统计出来的,英文语料占大头,高频英文片段都成了整块,中文的出现频率低很多,很多汉字只能退回字节级表示,一个汉字要占两三个 token。这就是「中文比英文贵」的底层原因:同样的意思,中文要花更多 token,API 按量计费,账单自然更长。我之前写API 计费那篇时提过一汉字约 0.6 个 token,源头就在这。
实验三:四个 a 是一块,「空格」是两块
还有几个零碎结果:aaaa 在两个词表里都是整块一个 token,说明高频重复序列也会被合并;而中文词「空格」被切成「空」和「格」两块。切分结果完全由语料统计决定,没有任何语言学规则参与,所以它经常不合直觉。
Karpathy 在 minbpe 的课程讲义里列过一串问题,答案全是同一个:为什么大模型拼不对单词?Tokenization。为什么连反转字符串这种最简单的活都干不好?Tokenization。为什么非英语语言表现更差?Tokenization。为什么简单算术老出错?还是 Tokenization。数字 1234 在词表里可能被切成 12 和 34 两块,模型看到的不是四个数位,做逐位进位计算时自然容易翻车(顺带一提,让模型「一步一步想」的思维链,对这类问题也有缓解作用)。
字节级 BPE 的一个好处,很多人没意识到
好处是兜底。因为词表的底层是 256 个字节,任何新词、错别字、emoji、生僻符号,最坏情况也能退回字节表示,不会因为词表里没收录就直接丢字符。这是 GPT-2 论文当年把 BPE 改成字节级的原因之一:让任意文本都有保底表示。
代价就是前面说的:低频语言的 token 效率低。中文、日文用户在实际使用里都会碰到:同样的内容,token 数就是比英文长。
这跟我有什么关系?三个实用点
如果你只是偶尔用用 AI 聊天,tokenizer 知道是怎么回事就够了。但如果你写提示词、调 API,有三件事值得记住。
别让 AI 硬数字母或数空格。它拿到的输入里没有可靠的字母边界,这类任务对它是「开卷考试但课本被撕碎了」。真要做字符级操作,让 AI 写段代码来跑,比它自己数靠谱得多。
控制成本先看 token。API 按 token 计费,中文内容 token 更密。砍提示词里的冗余,就是在砍账单,上下文缓存那篇里有更完整的省法。
token 数不等于字数。写有长度要求的文案时,「500 字」和「500 token」差得远,中文一般一个字 0.5 到 1 个 token。想让输出正好卡在某个长度,用字数去要求它,别用 token。
写在最后
回头再看 strawberry 那道题,答案已经不重要了。有意思的是它的反转:模型答错,不是因为不够聪明,而是因为我们在题目进它脑子之前,就让人把题面改写了。下一回你发现 AI 在某件「人类看来很蠢」的事上翻车,可以先想想:它的输入,到底长什么样?多数时候,答案就藏在输入里。
(本文切分实验用 OpenAI 官方 tiktoken 库在本机实测;BPE 出处与算法描述参考 Karpathy 的 minbpe 项目及课程讲义、Hugging Face 官方 NLP 课程第 6 章。)