返回博客

2026.10.16

README 说快 1000 倍,我的机器上测出 0.07 倍到 26 倍:换一个 tokenizer 到底能快多少?

一个新出的高速 tokenizer 宣传比 HuggingFace 快约 1000 倍。我在一台 6 核的 Ryzen 上跑了六种负载,倍数从 0.07 到 26.33 不等。这篇拆开 README 自己的表,说明这种倍数主要由分母决定,再给一个除法,算你的文本量要多大,换 tokenizer 省下的时间才够一次模型调用。

LLMbenchmarking性能

README 第一行写着:比 HuggingFace 的 tokenizer 快约 1000 倍。

我在自己一台 6 核的 Ryzen 上跑了六种负载。最快那次 26.33 倍,最慢那次 0.07 倍,比原来的还慢十几倍。

同一个库、同一个 tokenizer 文件、同一台机器,倍数能差两个多数量级。这篇想把这件事说清楚:一个「快 N 倍」的数字,大半是分母说了算。 而且我自己测出的那个 20 倍,回头看也有水分,第 4 章会讲。

只想要那个除法的,直接看第 3 章;想知道我的数字哪里不能信,看第 4 章。

1. 那个 1000 倍是怎么来的

先说这是个什么东西。tokenizer 就是把文字切成模型认识的编号(token)的那一步,模型吃的是编号,不是字。这个新库叫 GigaToken,Rust 写的,主要招数是用 SIMD 手写了切词前那步「预切分」,替掉通用正则引擎,再把见过的词的切法缓存起来。它只管切字,模型本身一个字都不会因此算得更快。

README 里有三台机器的整表,测的都是同一个 11.9 GB 的英文网页语料。我把 Qwen 3.5 / 3.6 那一行抄出来:

机器 GigaToken HF tokenizers 倍数
Apple M4 Max(16 核) 6.31 GB/s 6.3 MB/s 994×
双路 EPYC 9565(144 核) 15.49 GB/s 27.7 MB/s 558×
Ryzen 7 9800X3D(16 线程) 5.22 GB/s 51.6 MB/s 101×

同一行,从 994 倍掉到 101 倍。差在哪?

拆开算一下就清楚了。M4 Max 和 9800X3D 比:

分子(GigaToken)变化:6.31 ÷ 5.22 = 1.21 倍
分母(HF)变化:      51.6 ÷ 6.3  = 8.19 倍
倍数变化:            994 ÷ 101  ≈ 9.8 倍 ≈ 1.21 × 8.19

倍数差了快 10 倍,其中新库自己只贡献了 1.21,剩下 8 倍是 HF 在 M4 Max 上特别慢。说白了,「1000 倍」这个标题,是挑了旧实现最拉胯的那台机器当分母。

这事 README 也没藏着掖着,它在细节里写了:HF 只吃了前 100 MB、而且事先按文档切好;GigaToken 吃的是整个文件。还写了兼容 HF 接口的那个模式「达不到 1000 倍」。字都在,只是没人往下翻。

倍数 = 新的吞吐 ÷ 旧的吞吐。看到一个大倍数,先去找分母是在什么条件下测的。 这不需要任何领域知识,就是一个除法。

2. 搬到我的 6 核机器上

我的机器是 Ryzen 5 5600,6 核 12 线程。用的是 GigaToken 0.9.0(还是 Beta),对照 HF tokenizers 0.23.1,tokenizer 文件是本地一个 Qwen3.5-27B 的。每组都做了逐 token 对账,输出和 HF 一致才算数。

负载 GigaToken HF 倍数
自带 CLI 测一个十几 KB 的小文件 0.12 MB/s 1.67 MB/s 0.07×
一条 171 字节的短文本 24.63 MB/s 4.08 MB/s 6.04×
100 条短文本(17 KB) 113.52 MB/s 23.91 MB/s 4.75×
1 万条短文本(1.75 MB) 82.00 MB/s 31.11 MB/s 2.64×
256 MB 文件,按文档分隔 344.85 MB/s 17.20 MB/s 20.05×
一条 1.71 MB 的长文 159.43 MB/s 6.06 MB/s 26.33×

短文本那几行用的是中文句子 + 一小段 JSON + 一行 Python + 一个 emoji,模拟平时塞给模型的那种杂文本。

几个一眼能看出来的东西:

小文件走 CLI 反而慢。 0.12 对 1.67,慢了十几倍。我没拆它慢在哪,大概率是一次性的初始化开销,文件太小摊不开。对「每次请求切一条 prompt」这种用法来说,这一行比那个 1000 倍更接近真实。

短文本越多,倍数越小。 1 条 6.04,100 条 4.75,1 万条 2.64。看 HF 那一列:从 4.08 涨到 31.11,涨了 7 倍多。HF 的批量接口会把多条文档分给多个线程,条数一多它就跑起来了,分母变大,倍数自然往下掉。

单条长文倍数最高。 26.33 倍里,HF 只有 6.06 MB/s。同样是 1.7 MB 左右的量,拆成 1 万条它能跑 31.11,一整条它只剩 6.06。一条文档没法在文档之间并行,HF 在这儿最吃亏。

又是分母。新库的数字在 0.12 到 345 之间晃,旧库的数字在 1.67 到 31.11 之间晃,你挑哪一行当标题,就能写出哪个倍数。

3. 省下的时间够不够一次模型调用

倍数再大,真正要问的是:省下多少秒,这几秒在整件事里占多少。

这是个老公式(Amdahl 定律),换成大白话:

省下的时间 = 切词耗时 × (1 − 1 ÷ 倍数)

先代一条 prompt 进去。171 字节,HF 切完要 0.042 毫秒,GigaToken 要 0.007 毫秒,省 0.035 毫秒。

我之前在自己的环境里测过,对一个本地模型发一次最短的请求、预热之后,往返最快也要 79 毫秒。0.035 ÷ 79,省下的不到这次调用的千分之一。换句话说,给聊天、给 agent 的每次请求换 tokenizer,你量都量不出来。

再反过来算:文本要多大,省下的时间才够得上一次模型调用?

需要的文本量 = 一次往返时间 × HF 吞吐 ÷ (1 − 1 ÷ 倍数)
             = 0.079 秒 × 17.20 MB/s ÷ (1 − 1 ÷ 20)
             ≈ 1.43 MB

也就是说,一次要切 1.4 MB 以上的文本,换库省下的时间才刚够一次模型调用。日常一条 prompt 几 KB 到几十 KB,差了好几十倍。

大语料就是另一回事了。拿我那组 256 MB 的吞吐粗算 100 GB:HF 是 100,000 ÷ 17.20 ≈ 5,814 秒,大约 97 分钟;GigaToken 是 100,000 ÷ 344.85 ≈ 290 秒,不到 5 分钟。这是外推,没真跑 100 GB,而且下一章会说 344.85 这个数本身偏乐观。但量级上,做语料预处理、统计 token 数、打包训练数据,这个库是真香。

所以我最后的判断很简单:工具箱里收着,日常请求链路一行不改,哪天真有几百 GB 文本要切再拿出来。

4. 我那组数字的水分

这一章决定你要不要信第 2 章。

256 MB 那个文件是我拿 README 自己拼出来的。 把一份 README 首尾相接重复,切到 256 MB。问题在于 GigaToken 的一大招就是缓存「见过的词」,而这个文件里几乎每个词都见过上万遍。真实语料的词分布是长尾的,缓存命中率不可能这么高。所以 20.05 倍应该当上界看。

短文本那几组也一样偷懒了。 100 条和 1 万条,是同一句话后面拼一个序号;1.71 MB 的长文,是那 171 字节重复一万遍。26.33 倍这个最高值,恰好是重复度最高的那组。看到这里我自己都有点脸红。

我的绝对值和 README 对不上,原因没查清。 README 里 9800X3D 跑 Qwen 3.5 是 5.22 GB/s,我这台 5600 只有 344.85 MB/s,差了 15 倍。核数差一些,但解释不了 15 倍;测法也不完全一样(我走的是它的 CLI,HF 只比前 32 MB)。这个缺口我没追,所以我的绝对数字只用来跟同机的 HF 比,不拿去跟别人的机器比。

一致性只验了我测的东西。 逐 token 对账在这几组数据上全过,另外手挑了中文、组合字符、代码 emoji、对话特殊标记四个样例也都一致。但当时它的仓库里就挂着一个 Qwen 2.5 在某些 Unicode 组合字符上 token 顺序不一致的 issue。没撞上,不代表没有。

没测端到端。 我测的全是「只切词」这一步。真实流水线里还有读盘、解压、清洗、写出,切词占多大比例我没量,第 3 章的公式里那个「切词耗时」得你自己去 profile。

CLI 那个 0.07 倍没拆。 一次初始化、还是每次都有的固定开销,我没区分。

5. 下次再看到「快 N 倍」,按这个顺序问

  1. 分母是谁、在哪台机器上测的。换一台机器,旧实现的吞吐能差 8 倍。
  2. 两边吃的是不是同一份数据、同样的切法。整文件对前 100 MB,就已经不是同一道题了。
  3. 测试数据有没有重复。带缓存的实现,在重复数据上会好看得离谱。
  4. 你的负载长什么样。一条一条的小请求、上万条短文本、单条长文,倍数能从 0.07 跑到 26。
  5. 用 省下的时间 = 那一步耗时 × (1 − 1 ÷ 倍数) 算一下绝对值,再跟整件事里最贵的那一步比。
  6. 换之前在自己的真实数据上逐 token 对账,保留旧实现当兜底。

我花了一下午测一个 tokenizer,结论是平时用不上。等哪天真有 100 GB 要切,它能还我一个半小时。