1 个汉字算几个 token?实测中英文 prompt 与代码的编码差异

5 阅读7分钟

219 个汉字,在 cl100k_base 词表下要 291 个 token,平均一个字 1.33 个。

同样长度的英文,379 个字符只要 73 个 token,5 个字符才摊到一个。

换成 o200k_base,同一段中文从 291 掉到 188,省了三成半;可同一段 JavaScript 代码反而从 102 涨到 103。

图 1:中文样例载入后的计量台,左侧是输入框和字符、码点、字节统计,右侧是两套词表的 token 数与平均每 token 字符数

在线案例与源码:atomgit.com/deli007/dem… 生成物只有一个 index.html,分词在浏览器本地跑,文本不出本机。

一、为什么值得自己数一遍

按 token 计费的模型越来越多,估错一次不致命,估错一百万次就是账单。

网上流传的口诀是「1 个 token 约等于 4 个英文单词符、0.75 个英文单词」,对英文还算好用。

这句口诀碰到中文、emoji 和 JSON 就全不对了。

我不想背口诀,想要能自己复现的数:同一份文本,换个词表、换种语言,token 数到底差多少。

二、准备环境:进入码道 Web,把需求写成一段话

浏览器打开 devcloud.cn-north-4.huaweicloud.com/chat/home ,把要做的东西说清楚,就能拿到一个能跑的单文件应用。

本文这个计量台就是这么来的,不用装软件,登录华为云账号即可使用。

三、一个汉字 1.33 个 token,一个英文单词不到 1.5 个

我把 8 类文本一条条载进页面,读数直接摊在屏幕上。

中文那段 219 个字符,cl100k 编出 291 个 token;英文那段 379 个字符,只有 73 个 token。

换个指标看更清楚,也就是每个 token 平均覆盖几个字符:中文 1.16,英文 5.19。

差距来自词表的覆盖面,英文的常见词、词缀、空格组合大多有独立词条,中文常用词也有词条但覆盖稀疏,剩下的字退到字节级,一个汉字常常吃掉一到两个 token。

四、8 类文本实测对照

图 2:8 类内置样例的对照表,每行给出字符数、UTF-8 字节数、两套词表的 token 数和每 100 字符的 token 消耗

样例字符数UTF-8 字节cl100ko200k每 100 字符 token
中文叙述219657291188132.9
英文叙述379379737319.3
中英混排28046214311251.1
JSON32736113312940.7
JavaScript 代码37637610210327.1
URL225253848037.3
数字与日期21525815314171.2
emoji 与标点213646357259167.6

表格里最扎眼的是最后一列:中文 132.9,emoji 167.6,英文只有 19.3。

中间差了 7 倍。

数字那行也反直觉,215 个字符烧掉 153 个 token,比 327 个字符的 JSON 还费,因为每一位数字、每一个小数点都不容易和邻居合并成更长的词条。

字节数也不等于 token 数:中文 657 字节只对应 291 个 token,emoji 646 字节对应 357 个,两个方向都不一样。

五、o200k 省在哪、贵在哪

o200k 词表有 200006 个词条,cl100k 只有 100264 个,几乎翻了一倍。

中文样例从 291 降到 188,省 103 个,降幅 35.4%。

中英混排省 31 个,数字与日期省 12 个,emoji 省 98 个。

但 JavaScript 那行是 102 涨到 103,多了一个。

我本来以为新词表只会更省,实测告诉我不是这样,代码里的短符号序列在旧词表里恰好有更划算的合并方式。

想省钱不能看词表名气,得拿自己的真实文本去测。

六、emoji 是最贵的一类:一个家庭表情 18 个 token

图 3:emoji 样例载入后,切片条被拆成 357 个小方块,每个方块就是一个 token

emoji 样例只有 213 个字符,cl100k 编出 357 个 token,平均一个 token 只覆盖 0.87 个字符。

单个表情还好,😀 在 cl100k 下 2 个 token,在 o200k 下只要 1 个。

一上肤色和零宽连接符就崩了,👍🏻 是 6 个 token,👨‍👩‍👧‍👦 是 18 个 token。

国旗同理,🇨🇳 在 cl100k 下 6 个,o200k 下 4 个。

我以前把表情当成「一个字符」,放到账单上它其实是最贵的一类。

七、费用换算:单价一填,账就出来了

页面右下角能把 token 数直接换算成钱,填每百万 token 的价格和每天调用次数,出来单次、每天、每月三个数。

价格默认给了 0.15,标注写着「示例默认值,可自行修改」,因为各家定价随时会变,我不想把一个会过期的数字写死在页面里。

按每天 1000 次调用算,219 字符那段中文在 cl100k 下每天 0.04 美元、每月 1.31 美元;换成 o200k 词表,每月降到 0.85 美元。

同一段文本换个词表就省三成半,这个比例比砍字数好使。

八、验证与踩坑:第一版点开是空白的

页面底部有 20 条断言,加载时自动跑,每条都把实测值渲染出来。

图 4:20 条断言逐条列出实测值,全部 PASS

码道交回来的第一版没法用,打开就是一片白,控制台里是 SyntaxError。

原因很小也很典型,标点那条测试用例里多写了一个单引号,字符串提前结束,整个 module 解析失败,输入框和按钮一个都没绑上事件。

我修好引号之后再跑,发现还有两条断言写错了对象:名字叫「空文本 cl100k token 数 = 0」,实际比的却是 hello world 的编码结果,所以实测值永远是 2。

另有一条断言名字和内容对不上,名字写「按字素切分 1 个字符」,比较的却是 2。

顺手还修了 4 个长度越界的样例,实测 198、477、415、187 个字符,要求是 200 到 400 之间;补了两条组合表情的断言;再让页面打开时自动载入第一个样例。

改完重跑一遍,20 条断言全过。

九、断网的时候它会说实话

图 5:把分词库的 CDN 域名拦掉再刷新,顶部给出降级提示,token 数加上 ≈ 前缀,断言如实显示失败

我把 CDN 域名拦掉再刷新,页面没有白屏,也没有假装自己还在真实分词。

顶部挂出红条,写明分词器连不上、已降级为按字符估算,token 数前面加上 ≈。

底部 20 条断言跟着全红,实测值那一栏显示 -1。

失败路径能自己说话,比一句「一切正常」有用得多。

十、实测数据汇总

项目实测值说明
中文每个字符的 token 数1.33219 字符对应 291 个 token
英文每个 token 的字符数5.19379 字符对应 73 个 token
o200k 对中文的降幅35.4%291 降到 188
o200k 对 JS 代码多 1 个102 涨到 103,新词表不是处处更省
最贵的一类167.6 个每 100 字符emoji 与标点
家庭表情 👨‍👩‍👧‍👦18 tokencl100k
带肤色手势 👍🏻6 tokencl100k
页面单次渲染1 到 5 毫秒页面自己报的数
219 字符编码 2000 次26.0 毫秒平均 13.0 微秒一次
页面自检20 / 20 通过加载时自动执行

十一、本地复现

生成物只有一个 index.html,纯前端,分词库走 CDN,第一次打开需要联网。

双击打开就能用,也可以起个静态服务看:python -m http.server 8000,然后访问 http://localhost:8000 。

想加自己的样例,改 SAMPLES 数组里的名字和文本就行,表格会自动重算。

十二、用码道做这个小工具的体会

码道有三种使用方式:WebUI(浏览器对话)、TUI(终端命令行)和桌面 IDE(IDE 插件)。本文用 WebUI 版演示。

这次最值钱的一条需求是「页面里要有自检脚本,把每条断言的实测值渲染出来」。

正因为有这一条,错误才藏不住,空文本那条断言的实测值写着 2,一眼就不对。

另一个体会是「要实测值,不要结论」:表格里同时给字符数、字节数和两套词表的 token 数,读者可以自己算比值,不用信我的总结。

不足也说一句,这里用的是两套公开词表,各家模型实际用的分词器并不完全一致,页面给的是同量级的参考值,不能当成账单的替代品;样例文本也是我写的,不能代表你的业务语料。

要算准自己项目的成本,最靠谱的办法还是把真实语料跑一遍。