这里需要给最快下一个定语:“当前我所知的通用的 tokenizer”。在《tiktoken vs hf tokenizer:AI网关如何本地高效统计Token》里面,我比较了两个知名的 tokenizer 库,并得出结论:
- tiktoken 是专为 GPT model 实现的 tokenizer,而 hf tokenizer 则采取通用的流水线分层的方式来适配不同的 tokenizer config。尽管两者都采用一样的 fancy_regex 正则库,但是正因为 hf tokenizer 有着更泛用的分层,性能不如 tokenizer。作为交换,hf tokenizer 拥有更好的普适性。
- tokenization 的瓶颈在于 切分 token 的正则,而不是 bpe 算法本身。
dynamo 项目引入 fastokens 作为 hf tokenizer 和 tiktoken 之外的第三种选择。该项目宣称比 tiktoken 和 hf tokenizer 更快,而且它的适用性在 tiktoken 和 hf tokenizer 之间:虽然不能支持 hf tokenizer 的所有能力,但是和 tiktoken 这种专门实现不同,fastokens 可以支持主流模型的 tokenizer config。这个宣传看起来非常吸引人。虽然拓展性不如 hf tokenizer,但是只要能支持主流模型,大部份用户都不会在意是不是有足够灵活。大多数用户更关心性能,而在这一点上,fastokens 甚至能超过专有的 tiktoken。
为了验证它的说法,我在本地跑一个 benchmark,测试的 dataset 为 zai-org/LongBench-v2,模型为 openai/gpt-oss-120b。其中 tiktoken 使用 o200k_harmony 这个 tokenizer config。结果如下:
tokenizer total time throughput relative
HF tokenizers 24316.68 ms 6.33 MB/s 1x
tiktoken-rs 5368.49 ms 28.65 MB/s 4.53x
fastokens 2024.72 ms 75.97 MB/s 12.01x这个结果非常惊人。fastokens 和 hf tokenizers 对比,就像高铁对比公交车。即使是专门为 openai GPT 模型设计的 tiktoken,也只有 fastokens 的三分之一的速度。
(注意我跑了两次各个 tokenizer 的 benchmark。跑了第一次意识到 fastokens 使用 rayon 来做并行 tokenize,这个对于其他两个 tokenizers 是不公平的,因为它们只用到了单线程。尤其在真实用户场景里,用户可能直接就用通过绑核来提高吞吐量了,这时候不一定有多余的 CPU 资源。所以第二次我加了 RAYON_NUM_THREADS=1。表格里对比结果是第二次的,不过下面的火焰图是第一次的产物。)
fastokens 的火焰图如下:
抛开 rayon 部分不谈,fastokens 的瓶颈也是在正则表达式上。那为什么 fastokens 要比别家快那么多呢?
答案其实很简单,因为 fastokens 使用了性能更好的正则库。fastokens 使用 PCRE-JIT 来做解析。而 HF tokenizers 和 tiktoken-rs 用的是 fancy_regex。GPT tokenizer config 里面有 \s+(?!\S) 这种 lookaround 的表达式,原生的 Rust std regex 不支持,这时候 fancy_regex 会回退到自己较为低效的实现。而久经考验的 PCRE-JIT 毫无畏惧。
说到 PCRE-JIT,经验老到的读者会想到,如果 PCRE 不加资源限制的话,执行某些表达式时会出现 unbounded backtracing 的事故。2019年7月2日,Cloudflare 的工程师在更新 WAF 的规则时,部署了一条用于防御内联 JavaScript 恶意攻击的正则。该正则中包含了糟糕的嵌套贪婪匹配模式(形如 .*(?:.*=.*)),在面对特定类型的请求时,触发了 PCRE 引擎的灾难性 unbounded backtracing。由于 PCRE-JIT 在处理该回溯时耗尽了计算资源,导致 Cloudflare 全球网络节点的 CPU 利用率瞬间飙升至 100%。全球约 82% 的流量受到影响,包括 Discord、Shopify 等知名网站在内的大半个互联网直接陷入了瘫痪(返回 502 错误),持续了约 27 分钟。为了彻底根除这种因正则表达式缺陷导致的系统性风险,Cloudflare 在此事件后痛定思痛,逐步弃用了基于回溯机制的 PCRE 引擎,将其重构并全面迁移至用 Rust std regex。这也是为什么 Rust std regex 不支持 lookaround 的原因(因为它不是基于回溯机制的)。
那么 fastokens 有没有上述的安全风险呢?
答案是有的。至少我没找到 fastokens 在哪里设置了资源限制。虽然知名大模型的 tokenizer.json 不太可能有糟糕的正则,但如果你的业务允许用户上传未经审计的正则表达式(哪怕这个用户是贵团队内部的一名工程师),那么直接采用 fastokens 可能会有风险(参考 Cloudflare 的事故,WAF 规则替换成 tokenizer.json)。
总而言之,甘蔗没有两头甜。通过采用更高效的正则库,fastokens 的吞吐量比起其他 tokenizer 有成倍的提升。但里面隐含的风险不得不察。用户需要根据自己的业务场景进行 trade-off。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用。你还可以使用@来通知其他用户。