数据截至 (上游 commit d5827816baed)
Tokenizers — 架构与原理
30 秒导读: Hugging Face
tokenizers是整个 HF 生态(transformers 的 fast tokenizer、绝大多数 Hub 模型的tokenizer.json)背后那个「真正干活的」分词库。它把分词拆成四段可自由组合的流水线——归一化(Normalizer)→ 预分词(PreTokenizer)→ 模型切分(Model:BPE / WordPiece / Unigram / WordLevel)→ 后处理(PostProcessor:插特殊 token)——每段都是一个 trait,换一段就换一种分词器。核心用 Rust 写成,靠一张「归一化字符串每个字节对应原串哪几个字节」的对齐表,让最终每个 token 都能精确指回原文;再用 PyO3 薄壳暴露给 Python,批编码时释放 GIL 并多线程并行。读它你能回答:为什么 fast tokenizer 比 Python 写的快一个数量级、offset 映射是怎么穿过小写化/Unicode 归一化还活着的、BPE 的「合并」在生产实现里到底怎么算的。
1. 这是什么(零基础也能懂)
一句话定义
tokenizers 是一个生产级文本分词库:给它一段文本,它输出一串 token id(外加每个 token 在原文里的位置);给它一批语料,它能从零训练出一个 BPE / WordPiece / Unigram 词表。
它要解决谁的什么问题
大模型只认数字。文本进模型前必须切成 token 并映射成 id。这件事听起来像「按空格 split」,实际上坑很多:
- 归一化会改变长度:小写化、Unicode 归一化(NFC/NFKC)、去重音之后,字符串变长了或变短了,「第 5 个 token 对应原文哪几个字」就说不清了。
- 不同模型切法不同:BERT 用 WordPiece,GPT-2 用字节级 BPE,T5/LLaMA 用 Unigram/SentencePiece。每换一个模型就要换一套实现,没法组合。
- 特殊 token 要精确插入:
[CLS] x [SEP] y [SEP]的位置和 type_id 每个模型都不一样。 - 速度:预训练语料是 TB 级,Python 逐字符实现会成为整个训练管线的瓶颈。
tokenizers 就是把这四件事一次性解决掉的那一层。 下游(transformers、文本嵌入服务、推理引擎)只面对一个 tokenizer.json 文件和一个 encode() 调用。