课程
分词(Tokenization)是 HuggingFace Transformer 技术栈中我们常常不太在意的一环,直到它开始“泄漏”进您的输出。一个简单的空白字符怪癖就可能改变分词边界,解码可能生成意料之外的字符串,而在模板化时聊天提示也可能无声崩溃。
Transformers v5 通过分词重构来解决这些痛点:在不同实现间标准化行为、简化 API 表面,并在需要调试时让分词器内部更易检查。
在本教程中,我将先讲解分词会产生什么以及流水线如何工作,然后从务实、迁移为主的角度带您逐步了解 v5 实际发生了哪些变化。您将看到编码、解码、聊天模板和序列化的 v4 与 v5 并排代码模式,便于更新您的代码库。
如果您刚接触分词,建议先看看 Python 自然语言处理入门课程。
什么是分词(Tokenization)?
一种非常常见的 分词 定义是将文本转换为 token ID,这没错但不完整。在生产环境中,更好的理解是把分词看作一份契约。给定原始文本,分词器必须产出模型可以安全消费的结构化编码。
至少,这份契约包括:
input_ids,即整型 token ID,用于索引模型的嵌入表。- 还包括
attention_mask,用于指示哪些位置是真实 token,哪些是填充。 - 偏移/对齐(如可用)将 token 跨度映射回原文中的字符或词级跨度,这在高亮、命名实体识别(NER)和归因中是必须的。
- 必须保留特殊 token 的语义,即
BOS/EOS/PAD/UNK的放置和 ID 要与模型训练时的约定一致。 - 对于聊天模型,聊天格式也是契约的一部分,因此我们通常在运行分词前应用模型的聊天模板。
在 Transformers 中,这些职责位于分词器层(不在模型的前向传递内部),这就是为什么 AutoTokenizer 是标准入口。如下是一个简单示例:
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("HuggingFaceTB/SmolLM3-3B")
enc = tokenizer("Hello world")
print(enc["input_ids"])
print(tokenizer.convert_ids_to_tokens(enc["input_ids"]))
预期输出如下:
[9906, 1917]
['Hello', 'Ġworld']
这里,enc 是一个 BatchEncoding 对象(类字典容器),用于保存分词器生成的、可直接输入模型的字段(如 input_ids,以及常见的 attention_mask)。输出返回 [9906, 1917],即输入字符串对应的整型 token ID,同时还有 ['Hello', 'Ġworld'],展示了对应的 token 字符串。
注意: Ġ 前缀是一个常见约定,表示该 token 包含前导空格,因此 Ġworld 表示的是“ world”而非不含空白的“world”。
将分词视为流水线:v5 如何让它可调试
分词问题通常表现为一些小毛病,比如多余的空格、奇怪的 Unicode 切分、token 数量突然暴涨,或者聊天提示未按角色正确组织。
调试这些问题的最简单方式是把分词当作由多个阶段组成的流水线。一旦明确各阶段各司其职,您就能快速定位问题来源,而不是盲目猜测。请记住以下要点:
分词是可组合的流水线
首先要打破“分词器只是一个 WordPiece”的常见误解。实际上,它是一连串独立的变换阶段,包括规范化、预分词、子词建模、后处理和解码。每个阶段控制最终行为的不同部分。
可调试的心智模型
将分词拆分为多个阶段为我们提供了可靠的排障地图:
- 空白字符的怪癖通常来自预分词器或解码器
- Unicode 问题通常起源于规范化器
- token“爆炸”往往由子词模型及其词表/合并规则引起
- 聊天格式错误主要来自模板或后处理
天生显式且可检查
Transformers v5 让分词器更易于检查,尤其是由 Rust tokenizers 引擎驱动的分词器。
当使用 Rust 后端时,我们可以通过 tokenizer._tokenizer 查看底层组件(规范化器、预分词器、模型、后处理器、解码器)。我们不需要每天都用 _tokenizer,但在 v5 发布时它非常有用,因为在需要调试时可以让内部实现“看得见”。
这也自然引出了引擎与包装器的区分:
- 引擎(
tokenizers):低级的 Rust 实现,执行规范化、分割、子词切分等核心机制,并生成 token ID(以及在可用时生成偏移)。 - 包装器(
transformers): 面向模型的层,将这些机制转化为模型可用的 输入,执行诸如特殊 token 处理、聊天模板、填充/截断规则以及特定框架张量输出等约定。
Transformers v5 有哪些变化?
Transformers v5 是一次重要的标准化版本,分词是其中对用户可见的变化领域之一。该版本减少了重复实现,使分词器行为在不同模型之间更一致,并让分词器更易检查,某些情况下也更易从零训练。以下是对日常用户影响最大的变化。
每个模型仅保留一个分词器实现
在此前的版本中,许多模型同时拥有:
- 一个慢速的 Python 分词器
- 一个基于 Rust 的快速分词器实现
在 v5 中,Transformers 合并为每个模型仅一个分词器文件,并优先选择基于 Rust 的路径。为什么基于 Rust 的分词器行之有效:
- 去除了慢速与快速实现的重复。
- 减少了由这些实现间细微行为差异导致的一致性缺陷。
- 该分词器实现缩小并简化了测试面,因为只有一个主要代码路径需要验证。
- 同时为跨模型的分词器行为建立了唯一的“事实来源”。
编码 API 迁移
v4 的 encode_plus() API 已弃用,推荐直接调用分词器。本例展示 v4 与 v5 的差异:
v4 版本
enc = tokenizer.encode_plus(
text,
truncation=True,
padding="max_length",
max_length=128,
return_tensors="pt",
)
v5 版本
enc = tokenizer(
text,
truncation=True,
padding="max_length",
max_length=128,
return_tensors="pt",
)
从 encode_plus() 迁移到单一的 tokenizer() 入口可以减少生产代码中真实会遇到的失败模式。
在 v4 中,存在多种编码方法,意味着填充、截断、批处理或张量转换上可能出现细微差异,取决于您使用的路径,且往往只在从单样本切换到批量工作负载时才暴露出来。
v5 将一切标准化为一个始终返回 BatchEncoding 的编码路径,使行为更可预测,既减少边界情况的 Bug,也减少保持分词流水线一致所需的代码量。
解码 API 迁移
v5 统一了解码,使 decode() 同时处理单样本与批处理输入,使解码行为与编码保持一致。
v4 版本
texts = tokenizer.batch_decode(batch_ids, skip_special_tokens=True)
v5 版本
texts = tokenizer.decode(batch_ids, skip_special_tokens=True)
在 v5 中,解码被设计为镜像编码。就像 tokenizer() 可接受单个字符串或字符串批,decode() 也可接受单个 token ID 序列或一批序列。
关键在于返回类型:如果您传入一个序列,会返回一个字符串;如果传入一批(如 list[list[int]]),则返回字符串列表。整体上这让 API 更简洁,但也意味着任何假设 decode() 总是返回字符串的代码都应更新,以处理两种情况。
聊天模板
在 v4 中,apply_chat_template 为了向后兼容会返回原始的 input_ids。在 v5 中,它会像其他分词器方法一样返回一个 BatchEncoding。
v4 版本
input_ids = tokenizer.apply_chat_template(messages, return_tensors="pt")
v5 版本
enc = tokenizer.apply_chat_template(messages, return_tensors="pt")
input_ids = enc["input_ids"]
attention_mask = enc["attention_mask"]
在 v5 中,apply_chat_template() 被视为一个正式的分词器入口点,而非特殊的辅助函数。它不再只返回 input_ids,而是返回完整的 BatchEncoding 对象(包含 input_ids 与 attention_mask 等字段)。
这很重要,因为聊天格式的输入现在能接入与常规分词相同的批处理、填充、截断以及张量返回工作流。我们无需额外代码去重建 mask 或在送入模型前手动对齐格式。
分词器序列化
在 v5 中,分词器序列化被简化:将特殊 token 与新增 token 的元数据整合为更少的工件,同时仍支持旧文件以保持向后兼容。这降低了多个“事实来源”文件不同步的风险,并让分词器资产更易在 Transformers 之外的下游工具中加载与复用。
将分词器视为可配置的架构
在 v5 中,关键转变在于:分词器被视为一个定义好的流水线与已学习工件的组合,而非只是从序列化文件加载的某个东西。
- 分词器类定义了文本如何被规范化、拆分为片段、转换为子词 token,并最终解码回文本。
- 词表与合并规则是已学习的工件,即它们是经过训练得到的部分,决定了存在哪些 token 以及文本如何被压缩为 token ID。
- 这种分离支持“架构优先”的工作流。对于受支持的分词器,您可以实例化一个遵循某模型设计的分词器,然后单独加载或训练词表/合并规则,这让定制与再训练更为直接。
总结思考
Transformers v5 的分词在标准化与简化方面迈出了重要一步。现在有了更规范的行为路径(减少慢/快实现分歧)、更简化的 API 表面、聊天模板也会像常规分词一样返回结构化的 BatchEncoding 对象,并通过整合序列化降低了脆弱的文件级假设。
同时,它也推动了“架构优先”的心智模型,使分词器更易检查,并在受支持的场景下可从零训练。要顺利从 v4 迁移:用 tokenizer() 替换 encode_plus(),用 decode() 替换 batch_decode(),更新 apply_chat_template() 的调用以配合 BatchEncoding,在保存时避免硬编码旧的分词器文件名,并预期慢速分词器的一些怪癖会向基于 tokenizers 的行为收敛。
如果您想进一步了解近期发布,这里有 发布说明。我也推荐 Hugging Face 基础技能路线来进行实操练习。
Tokenization v5 常见问答
我还需要在“Fast”(Rust)与“Slow”(Python)分词器类之间做选择吗?
不需要。Transformers v5 将这些整合为每个模型一个实现。库现在默认对所有受支持的模型使用高度优化的 Rust 后端(tokenizers 库),无需手动在 LlamaTokenizerFast 与 LlamaTokenizer 之间选择。
我现有的 encode_plus 代码会马上失效吗?
不会立刻停止,但它已被弃用。尽管 v5 目前可能仍支持带警告的 encode_plus,强烈建议切换到直接的 tokenizer(__call__) 方法。这样可以获得统一的新批处理行为,并让您的代码更具前瞻性。
为什么 apply_chat_template 现在返回 BatchEncoding 字典而不是字符串?
是为了将聊天输入当作普通模型输入来处理。通过返回一个字典(包含 input_ids 和 attention_mask),v5 允许聊天格式数据无需第二次分词即可直接批处理、填充与截断。
我还能使用 return_tensors="tf" 或 return_tensors="jax" 吗?
不能。作为 v5 架构变更的一部分,Transformers 已弃用对 TensorFlow 与 Flax/JAX 后端的官方支持,专注于 PyTorch。您需要依赖 PyTorch 后端,或将模型导出为 ONNX/GGUF 等格式以供其他框架使用。
如何在 v5 中调试“无声”的分词错误?
使用可检查的内部结构。在 v5 中,您可以通过 tokenizer._tokenizer 访问底层 Rust 流水线。这使您可以单独检查特定阶段——如规范化器或预分词器——以准确查看字符串是在何处被意外拆分或修改的。
我是一名 Google Developers 机器学习(生成式 AI)领域的专家、Kaggle 三项专家,以及 Women Techmakers 大使,拥有 3 年以上的技术从业经验。2020 年我共同创办了一家健康科技初创公司,目前在佐治亚理工学院攻读计算机科学硕士,专攻机器学习。
