模型不读文字,它读整数。字符串和模型之间隔着一个分词器(tokenizer),它悄悄决定了你付多少钱、上下文窗口能装多少内容,以及模型为什么在数某个单词里有几个字母时表现得很糟。
我过去一直把它当成预处理环节,直到自己动手数了一遍。
分词器到底在做什么
它把文本切成固定词表里的碎片,词表通常有3万到20万个条目,每块碎片对应一个编号。常见词变成单个token,生僻内容被拆成更小的片段。
用tiktoken可以直接看到这个过程:常见词"tokenization"被切成"token"和"ization"两块;一个超长的生僻医学词,被拆成了17块。还有一个细节——前导空格会改变token的切法,这就是为什么空白不同的提示词,表现可能不一样。
词表怎么建出来的?现代分词器大多是BPE(字节对编码)的变体,思路简单到近乎无聊:从单个字符开始,找出最频繁的相邻字符对,合并成新符号,重复下去。在自造的小语料上跑一遍,前几次合并依次是s+t、e+st、o+w、l+ow、w+est、n+e。
把这个过程在巨大语料上重复几万次,词表就成型了。训练文本里常见的东西拿到短而便宜的token,不常见的拿不到。这一句就是整件事的核心。
四个分词器,同一批字符串
我拿同样的字符串跑了四个分词器:GPT-2、cl100k_base(GPT-4时代)、o200k_base(GPT-4o时代)和T5的SentencePiece,统一换算成每100字符的token数。
几个现象很扎眼:
- 英语是顺风路。各家表现差不多,因为词表基本都是拿英语建的。
- 代码和JSON在新旧编码之间直接减半。我的猜测是新词表训练时见了更多代码,连续空白和常见JSON标点被合并成了单个token——我没有翻词表文件去确认。
- 印地语是重灾区。同一句话,GPT-2要111个token,cl100k要78个,o200k只要24个。用老编码,成本是4倍多,上下文占用也是4倍多,质量我猜也更差,但没有实测。
- Emoji的表现则毫无规律。
印地语被拆到什么程度?一个词"मेरा"变成['म', 'े', 'र', 'ा']——一个字符或符号一个token。字节级分词器在没见过某种文字时,就是这么退化的。
这对智能体意味着什么
工具描述是每次调用都要付费的。我那个610字符的JSON schema大约160个token,二十个这样的工具,就是每一步循环3000多个token的固定开销。描述写短一点、每次少挂几个工具、保持稳定排序(让提示缓存生效),都能省。
格式化过的JSON比紧凑JSON贵,缩进也是token。工具返回结果可以试试json.dumps(obj, separators=(",", ":"))。
先数再猜。用你模型对应的真实分词器,别用"字符数除以4"——这个经验法则对英语还行,对印地语、代码和emoji很糟。
非英语用户付得更多,钱和上下文都多。如果你的产品服务他们,就拿他们的文本去测,别拿你自己的。
还有一类古怪的模型失败——数"strawberry"里有几个字母、对长数字做算术——部分原因就在分词:模型从没见过那些字母,只见过切好的块。
需要说明的是,我每个类别只测了一个样本、四个分词器,这是对效果的示意,不是一项研究。而且我只展示了token数量;一个模型在某种语言上表现好不好,取决于训练数据,不只是文本怎么被切。
想自己试的话,换成你的字符串、你的语言、你的工具schema。十分钟,你大概率会发现点意外的东西。我就发现了。
热门跟贴