一、引言

小屌丝:鱼哥,大模型时代了,为什么还要单独开一篇讲“分词器”?直接调 API 不就好了吗?
小鱼:不是“直接调 API 就好了”,是你们连 API 背后的分词逻辑都没搞清楚,就敢线上用,出问题全靠玄学排查。更有甚者,训练时用 A,推理时换成 B,还奇怪为什么效果崩了。所以这第二篇,我专门把“分词器”拎出来讲透,这是整个 NLP 管线里,最容易踩、也最容易被低估的一环。
小屌丝:行吧,你说得对,我确实没认真研究过 tiktoken/SentencePiece 之类的,感觉会用就完事了。
小鱼:会用 ≠ 能用好。下面直接给你一张“为什么分词值得单开一篇”的清单:

  • 大模型(LLM)本质就是在做“token sequence 的建模”,你喂什么 token、切多细,直接影响:
    • 上下文长度(context window)的真实利用率
    • 训练和推理速度
    • 多语言/Emoji 的鲁棒性
  • RAG/Embedding 场景,如果:
    • 索引时和查询时的分词器不一致,检索质量会“阴跌”
  • 工程侧:
    • 生产环境升级分词器,会导致存量用户的历史 prompt / 向量失效
    • 不了解特殊 token([CLS]、[SEP]、<|im_start|> 等),模型输出会很“抽风”

所以:分词不是“切个词”,而是你要和模型、管线、业务长期兼容的关键接口。搞清楚之后,你看 transformers/sentencepiece/tiktoken 文档会像开挂一样。

在这里插入图片描述

二、分词到底在干什么?

为避免后面“你说的 token、id、词表是不是一个东西”,我们先统一术语:

  • Vocabulary(词表):所有可能的 token 的集合,比如 32k/64k/128k。

  • Token:子词/字符/标点等“切分单元”,模型看到的是它的 id。

  • id:每个 token 在词表里的整数下标。

  • special tokens:[CLS]、[SEP]、<|im_start|>、<|im_end|> 等模型约定符号。

  • pre-tokenization(预分词):在真正“子词分词”之前先做粗分割(比如按空格/标点拆成“词”)。
    核心要解决的三个问题:

  • 1、把文本拆成子词序列(tokenization)

    • 既不要太碎(序列太长),又不要太粗(词表太大、OOV 多)。
    • 要能处理罕见词、新词、多语言和 Emoji。
  • 2、子词 ⇔ id 互相转换(encode / decode)

    • 训练时:text → ids。
    • 推理/服务时:ids → text(还原展示)。
  • 3、和模型/训练/工程链路保持一致

    • 微调/预训练、推理、RAG/Embedding、评估/监控,都用同一套分词器。

后面所有的工具与选型,都是围绕这三点来展开。

三、现代分词的三大流派(BPE / WordPiece / Unigram)

3.1 BPE:从字符合并到子词

思路:

  • 把所有文本视为字符序列(或字节序列)。
  • 每次合并“出现频次最高”的字符对,形成新的子词单元。
  • 重复合并,直到词表达到预设大小。

特点:

  • 从小到大“生长”;可逆、无损。
  • 对“常见前缀/后缀”友好,比如 “ing”“tion” 会优先被合并。
  • tiktoken、GPT 系列、很多开源模型都基于 BPE。

3.2 WordPiece:BERT 的御用刀法

思路:

  • 也是从字符开始,迭代合并“让语言模型似然提升最大”的相邻符号。
  • 选择标准不是“频次”,而是“合并后整句得分的增量”。

特点:

  • Google 为 BERT 开发的分词算法,BERT 系模型大量复用。
  • 和 BPE 相比,对“低频但重要”的组合更敏感。

3.3 Unigram:从大词表“砍”到小词表(配合 SentencePiece)

思路:

  • 先准备一个“超大初始词表”(比如基于所有字符/字节、常见子串)。
  • 为每个 token 评估:去掉它以后,整体损失上升多少。
  • 周期性删掉“对损失影响最小”的那批 token,直到词表达到目标大小。

特点:

  • 从大到小“剪枝”;通常搭配 SentencePiece 使用。
  • T5、mBART、XLNet、BigBird 等模型采用 Unigram + SentencePiece。
  • 训练时支持子词正则化(subword regularization),通过采样不同切法提升鲁棒性。

选型建议(工程侧):

  • 想复现/兼容 OpenAI 系列模型(GPT-4o、o1、GPT-4 等):用 tiktoken(BPE)。
  • 想做预训练/微调或复现 BERT 系(DistilBERT 等):用 WordPiece(tokenizers 或 transformers 内置)。
  • 想做多语言/从零训自己的词表:优先考虑 SentencePiece + Unigram/BPE。

在这里插入图片描述

四、工具栈实战:三种“主流且能打”的分词实现

4.1 Hugging Face tokenizers(BPE/WordPiece/Unigram 一把梭)

官方:Rust 实现,Python 绑定,主打“快、稳、生产级”。
支持 BPE / WordPiece / Unigram,也支持预分词、特殊 token、截断与填充。

# 安装
# pip install tokenizers

from tokenizers import Tokenizer
from tokenizers.models import BPE, WordPiece, Unigram
from tokenizers.pre_tokenizers import Whitespace
from tokenizers.trainers import BpeTrainer, WordPieceTrainer

# 1) BPE 示例
tokenizer = Tokenizer(BPE(unk_token="[UNK]"))
tokenizer.pre_tokenizer = Whitespace()

trainer = BpeTrainer(special_tokens=["[UNK]", "[CLS]", "[SEP]", "[PAD]", "[MASK]"])
# 注意:训练前准备好文件列表(可以是 .txt / .jsonl)
tokenizer.train(files=["wiki.train.raw", "wiki.valid.raw", "wiki.test.raw"], trainer=trainer)

# 编码
output = tokenizer.encode("Hello, y'all! How are you \U0001f601 ?")
print(output.tokens)
# 示例输出(示意):
# ["Hello", ",", "y", "'", "all", "!", "How", "are", "you", "[UNK]", "?"]<span data-allow-html class='source-item source-aggregated' data-group-key='source-group-12' data-url='https://github.com/huggingface/tokenizers' data-id='turn2fetch0'><span data-allow-html class='source-item-num' data-group-key='source-group-12' data-id='turn2fetch0' data-url='https://github.com/huggingface/tokenizers'><span class='source-item-num-name' data-allow-html>github.com</span><span data-allow-html class='source-item-num-count'></span></span></span>

4.2 SentencePiece(T5/mBART/XLNet 等的标配)

Google 出品:支持 BPE/Unigram/char/word,无语言依赖,支持子词正则化。
常与 T5、mBART、XLNet、BigBird 等模型捆绑。
支持 CLI 训练和 Python API。

安装

pip install sentencepiece

代码示例

import sentencepiece as spm

# 1) 训练(通常在 CLI 做,这里示意 Python 接口)
# spm.SentencePieceTrainer.train(
#     input='corpus.txt',
#     model_prefix='spm',
#     vocab_size=32000,
#     model_type='bpe',  # 或 'unigram'
#     character_coverage=1.0,
#     input_sentence_size=10000000,
#     shuffle_input_sentence=True,
# )

# 2) 加载模型并分词
sp = spm.SentencePieceProcessor(model_file='spm.model')

# 编码为 id 列表或字符串列表
print(sp.encode('New York', out_type=str))         # ['▁New', '▁York']
print(sp.encode('New York', out_type=int))         # [1234, 5678]

# 采样模式(子词正则化)
for _ in range(3):
    print(sp.encode('New York', out_type=str, enable_sampling=True, alpha=0.1, nbest_size=-1))
    # 可能出现不同的切分结果,起到数据增强效果<span data-allow-html class='source-item source-aggregated' data-group-key='source-group-16' data-url='https://github.com/google/sentencepiece' data-id='turn0find1'><span data-allow-html class='source-item-num' data-group-key='source-group-16' data-id='turn0find1' data-url='https://github.com/google/sentencepiece'><span class='source-item-num-name' data-allow-html>github.com</span><span data-allow-html class='source-item-num-count'></span></span></span>

适用场景:

  • 多语言(包括中日韩)、需要自定义词表、从零训练。
  • 需要子词正则化/BPE-dropout 来提升模型鲁棒性

五、避坑指南

5.1 坑1:训练和推理的分词逻辑不一致

  • 症状:评估很好、线上效果崩;日志里看到“莫名奇怪的 token”。
  • 原因:
    • 训练时用 A(比如 SentencePiece),推理时换成 B(比如 BertTokenizerFast)。
    • 未正确加载 special tokens / truncation 设置不同。
  • 对策:
    • 始终保存分词器的配置与词表(tokenizer.save_pretrained / SentencePiece .model 文件)。
    • 把 tokenizer 当作“模型的一部分”进行版本管理。

5.2 坑2:中文/Emoji/多语言切得太碎

  • 症状:每个中文字都被切成 2~3 个 token,上下文窗口“被虚高”。
  • 原因:
    • 词表对中文/Emoji 覆盖不足;或 character_coverage 太低(SentencePiece)。
  • 对策:
    • 多语言模型建议 character_coverage=1.0(尤其是中日韩)。
    • 训练语料里保证足够的中文/Emoji 数据;必要时扩展词表。

5.3 坑3:离线训练 SentencePiece,模型加载不出来

  • 症状:本地跑得好,线上部署“找不到 spm.model”或版本不兼容。
  • 原因:
    • 训练环境与部署环境的 SentencePiece 版本不一致。
    • 路径/编码问题(Windows/Unix 换行符)。
  • 对策:
    • 将 .model 文件随模型一起打包(Docker/模型仓库)。
    • CI/CD 中增加单元测试:固定文本 + 固定切分结果作为“黄金样本”。

5.4 坑4:长文本截断、特殊 token 忘记加

  • 症状:模型输出乱码、首尾信息丢失、评分指标离谱。
  • 原因:
    • 没有统一配置 truncation / max_length。
    • [CLS]/[SEP]/<|im_start|> 等特殊 token 没有按要求添加。
  • 对策:
    • 在代码库里封装统一 tokenize 函数:
    • 统一 add_special_tokens
    • 统一 truncation 策略(左侧/右侧/是否保留首尾)
    • 为不同任务(分类/抽取/对话/Embedding)准备模板。

六、总结

回顾整篇文章,我们可以把分词器的核心脉络收敛成三句话:

  • 算法层面

    • 从 BPE 的“自底向上合并”,到 WordPiece 的“基于似然增益合并”,再到 Unigram 的“自顶向下剪枝”。
    • 这三大流派的本质,都是在“词表体积(显存/计算代价)”与“序列长度(上下文窗口利用率)”之间走钢丝。理解了这一点,你就不会被各种花哨的名词绕晕。
  • 工具层面

    • 追求与 OpenAI 生态及计费绝对对齐,闭眼上 tiktoken;
    • 搞多语言、做预训练从零训词表,SentencePiece 是底座;
    • 需要高并发、工业级性能且要灵活定制,Hugging Face tokenizers 是那把瑞士军刀。
    • 不要在 BERT 任务里硬套 tiktoken,也不要在对接 GPT-4o 时自己手搓 BPE,工具选错,后面全是屎山代码。
  • 工程层面

    • 无论你是做 RAG 检索、Agent 工具调用,还是模型微调,分词器工程的唯一心法就是“绝对一致”——训练与推理一致、索引与查询一致、升级前后兼容。
    • 在 LLM 时代,分词器早就不是一个独立的 NLP 模块,它是模型大脑的“感官神经”,神经一旦错乱,再牛的架构也只能输出幻觉。

我是小鱼

  • CSDN 博客专家
  • AIGC MVP专家
  • 阿里云 专家博主
  • 51CTO博客专家
  • 企业认证金牌面试官
  • 多个名企认证&特邀讲师等
  • 名企签约职场面试培训、职场规划师
  • 多个国内主流技术社区的认证专家博主
  • 多款主流产品(阿里云等)评测一等奖获得者

关注小鱼,学习【自然语言处理】最新最全的领域知识。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐