Gigatoken 把 BPE 分词推到 GB/s:我跑了兼容性校验,也找到了 1000 倍数字的边界

一、为什么一个分词器会登上 HN 首页

7 月 22 日,Marcel Rød 发布了 Gigatoken 0.9.0:一个用 Rust 编写、通过 PyO3 暴露 Python API 的 BPE 分词器。项目在 HN 得到 300 多分,GitHub 仓库在我核验时有 1,097 stars、43 forks,采用 MIT 协议。

最醒目的数字是“比 Hugging Face tokenizers 快约 1000 倍”。这个数字并非所有机器、所有文本、所有调用方式都成立。官方的峰值来自 11.9 GB OpenWebText 样本、双路 AMD EPYC 9565 共 144 核、Gigatoken 原生文件 API:GPT-2 达到 24.53 GB/s,Hugging Face 是 24.8 MB/s,差距 989 倍。换到 Ryzen 7 9800X3D 的 8 核桌面机,同一项变成 6.27 GB/s 对 59.0 MB/s,约 106 倍。

我更关心两个问题:它究竟优化了哪里;普通 Python 项目把 tokenizers 换掉后,数字还能剩多少。

二、我先按项目提供的方式跑了一遍

项目要求 Python 3.10 以上,PyPI 当前版本是 0.9.0。无需克隆仓库,可以用 uvx 直接执行。我先检查 CLI,再用仓库测试夹具里的 GPT-2 tokenizer.json 跑校验:

uvx gigatoken --help
uvx --with tokenizers gigatoken bench \
  /tmp/gpt2_tokenizer.json /tmp/sample.txt \
  --validate --comparison-limit none

--validate 不只是计时,它还会让 Hugging Face 对同一批文档编码,然后逐项比对 token id。我的 14.6 KB README 样本得到:

cpu: Intel Xeon Gold 6267C, 1 core
gigatoken: 0.080 s | 0.18 MB/s | 0.07 Mtok/s
hf:        0.023 s | 0.63 MB/s | 0.23 Mtok/s
gigatoken is 0.29x slower than hf
validation OK: 1 documents match

这个结果很重要:只有一篇小文档时,初始化、动态库加载和 Python/Rust 边界成本压过了 SIMD 收益,Gigatoken 反而慢约 3.4 倍。随后我把同一文本重复到 32 MiB,只跑原生编码路径,得到 0.905 秒、37.08 MB/s、13.58 Mtok/s。Hugging Face 校验在这个只有 1 个大文档、容器仅暴露 1 个物理核的环境里超过 300 秒,未能形成公平的完整对照,所以我不会拿本机数字宣称“复现了 1000 倍”。

在数据管道里,建议先用自己的数据做同样的正确性门禁:

uvx --with tokenizers gigatoken bench \
  'Qwen/Qwen3-8B' ./train.jsonl \
  --validate --comparison-limit 100MB

生产代码若已经依赖 Hugging Face,可先走兼容模式,改动很小:

import gigatoken as gt

hf_tokenizer = ...
tokenizer = gt.Tokenizer(hf_tokenizer).as_hf()
ids = tokenizer.encode_batch([
    "第一条训练样本",
    "第二条训练样本",
])

追求吞吐时则要让 Rust 直接读文件,少在 Python 里制造字符串列表:

import gigatoken as gt

tokenizer = gt.Tokenizer("Qwen/Qwen3-8B")
source = gt.TextFileSource(
    ["owt_train.txt"],
    separator=b"<|endoftext|>",
)
tokens = tokenizer.encode_files(source)

作者在 HN 明确说,兼容模式仍要创建 Python list、把字符串转成 bytes,预期更现实的提升约为 200~300 倍;1000 倍对应的是原生 API 和足够大的批量任务。

三、速度主要来自四处,不只是“改用 Rust”

1. 用专用状态机替代通用正则

BPE 在合并 token 前通常先做 pre-tokenization。主流实现会把这一步交给正则引擎,Gigatoken 为 GPT-2、Qwen、Llama、DeepSeek、Kimi 等 tokenizer family 写了专用实现,并使用 AVX-512、AVX2、NEON 等 SIMD 路径。README 给出的单线程预分词速度超过 2 GB/s。

2. 缓存已经见过的 pretoken

自然语言词频是长尾分布,但高频词会重复。项目用并发缓存保存“字节片段 → token 序列”的映射,再结合 cache hierarchy 降低重复 BPE merge 的成本。作者也承认缓存会快速增长,这不是无上限的免费加速。

3. 尽量不穿过 Python 边界

原生 encode_files 在 Rust 内读文件、找文档边界、并行编码,最后再交付结果。若先在 Python 中读成数百万个 str,大量对象分配和 ABI3 调用会吞掉收益。Cargo 配置使用 PyO3 0.29 的 abi3-py310;README 把“按 Python 小版本专门构建绑定”列为后续方向,并称早期实验可让边界受限场景再快约 2 倍。

4. 并行读取、切分和编码

源码同时用了 Rayon、DashMap、memmap2、Arrow/Parquet,并针对 JSONL、gzip、Zstandard 和普通文本提供输入路径。项目不是只优化一个 encode("hello"),而是在优化“把训练语料变成 token 流”的整条 CPU 管道。

四、1000 倍对哪些任务有意义

如果一次在线请求的分词只占总耗时 0.1%,按照 Amdahl 定律,即使这一段快 1000 倍,端到端也只改善约 0.1%。HN 评论区对此质疑得很合理。因此我不会建议普通聊天接口看到标题就替换依赖。

但以下三类任务不同:

场景 分词为何可能成为瓶颈 建议
预训练数据处理 TB 级语料要反复过滤、重排、按 token 切分 优先测试原生文件 API
路由与限流 网关在调用模型前要估算 token、分桶、拒绝超限请求 先做小批量 P99 延迟测试
小模型分类/Embedding 推理很快,分词占比可能超过 10%,甚至成为主要 CPU 时间 用真实短文本分布跑兼容模式

作者在 HN 说,他们处理 DCLM 一类数据时,分词会在大量 CPU 上运行数天;另一位工程师则提到 64M 参数 BERT 分类系统中,分词超过总运行时间的 10%。这两种情况才是 Gigatoken 最有价值的地方。

推理侧还有一个容易忽略的细节:vLLM、SGLang 的 prefix cache 通常按 token chunk 建索引。为了知道命中在哪个 token 边界结束,开源引擎往往仍需先分词,再查询 KV cache。长 system prompt、低延迟推理服务器可能因此改善 TTFT,但幅度必须结合具体缓存结构测量,不能直接套训练数据的 GB/s 数字。

五、目前的局限与待验证项

  • 小输入与冷启动(待验证):我的 14.6 KB 单文档测试中,Gigatoken 比 HF 慢 3.4 倍;需要补 1 KB~10 MB、不同 batch size 的延迟曲线。
  • 兼容模式基准(不足):作者给出 200~300 倍预期,但 README 尚未列独立表格,Python 对象形状会明显影响结果。
  • SentencePiece 与 WordPiece(坑点):SentencePiece 路径只有约 7~21 倍,WordPiece 当前不支持,BERT 老项目不能直接替换。
  • 缓存内存上限(还在调研):pretoken 分布长尾,仓库没有给 11.9 GB 数据跑完后的峰值 RSS 和缓存淘汰曲线。
  • Windows 支持(不足):官方建议优先使用 WSL,原生 Windows 测试还不充分。
  • 文件输出(待验证):原生 API 还没有 file sink,超大语料的 token 结果如何无复制落盘仍需工程处理。

此外,官方三组 benchmark 的比较方式并不完全对称:Gigatoken 处理完整 11.9 GB 文件并自行找边界,HF 只取前 100 MB,tiktoken 取前 1 GB;作者解释这些基线没有缓存,吞吐近似稳定。这个解释有合理性,但企业选型仍应在相同硬件、相同数据、相同 API 形状下重跑。

六、我的采用建议

如果团队正在做预训练语料、批量 embedding 或高吞吐 AI 网关,Gigatoken 值得进入基准测试清单:先用 --validate 锁住 token id 一致性,再逐步扩大到 1 GB 以上,记录吞吐、P99、峰值 RSS 和输出写盘成本。不要第一步就删除原实现,保留按 tokenizer family 回退到 Hugging Face 的开关。

如果只是普通 Web 应用每次编码几段 prompt,先用 profiler 证明分词占比。我的小样本已经说明,漂亮的 GB/s 峰值不能替代业务分布。这里最值得学习的不是一个 1000 倍标题,而是它把通用正则、缓存层级、Python 边界和文件管道逐层测量后,再针对真正的热点做专用实现。

参考资料

  1. Gigatoken GitHub README、benchmark 与 Known Issues:https://github.com/marcelroed/gigatoken
  2. Gigatoken pyproject.toml / Cargo.toml(版本、PyO3、Rayon、SIMD 依赖):https://github.com/marcelroed/gigatoken/tree/main
  3. HN 讨论与作者回复(训练数据、TTFT、兼容模式边界):https://news.ycombinator.com/item?id=49010167
  4. Stanford CS336 OpenWebText 样本:https://huggingface.co/datasets/stanford-cs336/owt-sample
posted @ 2026-07-23 07:24  Ninghg  阅读(66)  评论(0)    收藏  举报