AI 快讯 编译自 marktechpost #模型发布#工具评测#性能优化

Perplexity AI 开源 Unigram Tokenizer,p50 延迟比 Hugging Face 低 5 倍,CPU 利用率降 5-6 倍

Perplexity AI 开源了用 Rust 重写的 Unigram tokenizer,针对 XLM-RoBERTa 模型优化,p50 延迟比 Hugging Face tokenizers crate 低约 5 倍,比 SentencePiece 低约 2 倍,生产环境 CPU 利用率降低 5-6 倍。本文解析其三大优化:双数组 Trie、位图内联打包和大页内存,并探讨对中文开发者…

编译发布 2026/05/28 原文发布 2026/05/28

一句话看懂

Perplexity AI 开源了用 Rust 重写的 Unigram tokenizer,p50 延迟比 Hugging Face 低 5 倍,生产环境 CPU 利用率降低 5-6 倍,专为 XLM-RoBERTa 等中小模型优化。

详细发生了什么

Perplexity AI 的研究团队用 Rust 从零重写了 Unigram tokenizer,并在其推理技术仓库 pplx-garden 中开源。在生产输入长度下,新编码器的 p50 延迟比 Hugging Face tokenizers crate 低约 5 倍,比 SentencePiece (C++) 低约 2 倍,比 IREE 的 tokenizer (C) 低约 1.5 倍,且稳态零堆分配。在生产中,它将 Perplexity 推理栈的 CPU 利用率降低了 5-6 倍,并将 reranker 延迟减少了数十毫秒。

为什么 tokenization 会成为瓶颈?LLM 推理成本通常集中在 GPU 工作(KV cache、attention kernel、expert routing),但嵌入模型、分类器和 reranker 等小模型比前沿 transformer 小两到三个数量级。以 reranker 为例,每个请求需对数百个候选文档评分,GPU 计算常可在个位数毫秒内完成,但每个输入必须先经过 CPU 端的 tokenization。当 batch size 较大时,tokenization 成为总请求延迟的显著部分。Perplexity 的工作针对 XLM-RoBERTa,该模型拥有 250K token 的 Unigram 词汇表(用 SentencePiece 训练)。微调后的 RoBERTa 系列编码器常用于排序、检索和相似度任务。

Unigram tokenization 由 Kudo 于 2018 年提出,在 SentencePiece 中实现。它将分词视为最可能路径问题:每个词汇 token 有一个学习的对数概率,tokenizer 选择 token 分数总和最高的分割。寻找最优路径的算法是 Viterbi 算法(1967 年提出的动态规划技术)。字节位置形成图层次,词汇 token 是跨越连续字节范围的边。DP 递推遍历字节位置,在每个位置更新最佳路径。外层循环相对于输入长度线性运行,内层循环在每个字节位置遍历词汇 trie(前缀树结构)。在 16K token 输入上,内层循环执行数十万次 trie 转换,这是热点路径。

Hugging Face tokenizers crate 是大多数团队默认使用的 Rust tokenizer。Perplexity 将其作为基准参考。在 514 token(512 + BOS/EOS 注入)下,参考实现有三个代价高昂的模式:每次匹配时分配 String::from_utf8 和 AHashMap 查找(514 token 时 7,295 次分配,16K 时 299,171 次);每个字节步骤的 AHashMap 指针追逐(每个字节步骤 4 个依赖加载);以及 DP 表和输出缓冲区的每次调用重新分配(L2 未命中率从 128 token 的 8% 升至 16K 的 50%)。

Perplexity 首先隔离了不必要工作的成本:他们做了一个零分配版本的参考实现(相同 HashMap trie,但使用调用者拥有的可重用 scratch 结构,并将 token ID 直接存储在 trie 节点中)。此基线已将 p50 延迟从 326 µs 降至 155 µs(514 token),指令退休数降低 2.4 倍。剩余成本是 HashMap 指针追逐本身。

随后他们应用了三大优化:

  1. 双数组 Trie:用双数组 trie 替换 HashMap trie。双数组 trie 将整个 trie 编码为两个扁平整数数组 base 和 check,子节点查找只需两次数组读取、一次整数加法和一次比较,无需哈希和指针追逐。对于 XLM-RoBERTa 的 250K 词汇表,整个 trie 占用约 9 MB 连续内存,每次编码的热工作集约 100 KB,适合 L2 缓存。Perplexity 将 trie 直接内联到 Viterbi 循环中,去除了 SentencePiece 和 IREE 的通用库开销。结果:514 token 下 p50 从 155 µs 降至 68 µs,相比原始参考延迟降低 4.8 倍。

  2. 位图与内联打包:用每个节点的位图(四个 64 位字,32 字节)替换 check 数组,位图查找编译为单个位测试。同时将所有四个每节点字段(位图、base、token ID、分数)打包到一个 64 字节的 cache line 中。一次 trie 步骤只需加载一个 cache line。权衡:trie 大小从约 9 MB 增至约 50 MB(780K 节点 × 64 字节),但热工作集仍约 100 KB。结果:514 token 下额外减少 4.5% 墙钟时间,L2 访问从 4.6K 降至 1.8K。

  3. 大页内存:50 MB 的 trie 在默认 4 KB 页的 Linux 系统上跨越约 12,000 个虚拟页,而 Intel Sapphire Rapids 的一级数据 TLB 仅 96 项。Perplexity 使用 mmap 的 MAP_HUGETLB 标志将 trie 置于 2 MB 大页上,50 MB 仅需 25 页。结果:墙钟时间减少 3-12%,在 4,098 token 时增益最大(-12.0%)。

最终基准测试(单线程,Intel Xeon Platinum 8488C,10,000 次迭代):514 token 下,Hugging Face tokenizers crate 的 p50 延迟为 349 µs,SentencePiece 为 128 µs,IREE 为 112 µs,Perplexity 最终版本约为 63 µs,指令数从 3.60M 降至 1.04M,分配数为 0。

中文圈视角

这个开源项目对中文开发者有直接价值。首先,XLM-RoBERTa 等模型在中文 NLP 任务(如文本分类、语义相似度、信息检索)中广泛使用,而 tokenization 性能瓶颈在中文场景同样存在。Perplexity 的优化思路——双数组 Trie、位图内联、大页——可以迁移到其他基于 Unigram 的中文 tokenizer(如基于 SentencePiece 训练的模型)。

其次,国内用户无需梯子即可直接使用 pplx-garden 仓库中的代码(MIT 许可证),但需注意依赖 Rust 工具链。对于使用 Hugging Face tokenizers 的团队,替换为 Perplexity 的实现可显著降低 CPU 开销,尤其适合高并发推理服务。

与国产同类工具对比:目前国内主流 tokenizer 仍依赖 Hugging Face 或 SentencePiece,鲜有针对特定模型深度优化的开源实现。Perplexity 的工作展示了 Rust 在 tokenizer 性能优化上的潜力,国内团队可借鉴其方法,例如为中文 BERT 类模型定制 tokenizer。

一个中文圈尚未讨论的盲点:tokenizer 性能优化对边缘部署(如手机端、IoT 设备)尤为重要,因为 CPU 资源有限。Perplexity 的零分配设计和 L2 缓存友好特性,使其非常适合资源受限环境。

几条值得记住的细节

  • 最终版本在 514 token 下 p50 延迟约 63 µs,指令数 1.04M,零分配。
  • 双数组 Trie 将整个词汇表编码为两个整数数组,查找只需两次数组读取和一次加法。
  • 位图内联将每节点字段打包到单个 64 字节 cache line,L2 访问降低 60%。
  • 大页内存将 TLB 未命中从 12,000 页降至 25 页,最长输入下延迟降低 12%。
  • 代码已开源在 pplx-garden 仓库,MIT 许可证,可直接集成。

一句话总结

Perplexity 开源的 Unigram tokenizer 为中文开发者提供了一套经过生产验证的高性能分词方案,尤其适合中小模型的推理加速。