PaddleNLP MBart 分词器深度解析:MBartTokenizer 与 MBart50Tokenizer 的架构原理与实战使用

原创2026-09-23 21:19:061,229 阅读
文章标签:人工智能大模型预训练微调LoRARLHF强化学习分布式训练模型推理服务推理引擎模型量化模型压缩本地部署NLP

PaddleNLP MBart 分词器深度解析:MBartTokenizer 与 MBart50Tokenizer 的架构原理与实战使用

MBart 是面向多语言序列到序列任务的预训练模型,其分词器的核心挑战在于:在一个共享的 SentencePiece 词表之上叠加"语言代码"(language code)特殊 Token,从而让同一个模型支撑多语言翻译。本文以 PaddleNLP 仓库中 paddlenlp.transformers.mbart.tokenizer 模块为对象,系统讲解 MBartTokenizerMBart50Tokenizer 两个分词器的词表布局、fairseq 偏移机制、语言代码管理、特殊 Token 拼接规则与完整 API 用法,并给出基于 mbart-large-en-rombart-large-50-one-to-many-mmt 的翻译数据预处理实战示例。读完本文,你将能够独立完成 MBart 系列模型的文本编码、语言切换、decoder 输入构建与分词器持久化。

一、模块定位:MBart 分词器在模型家族中的角色

PaddleNLP 将 MBart 的实现拆分为三个文件,共同构成完整的多语言翻译能力:

  • configuration.pyMBartConfig,定义模型超参数(层数、注意力头数、词表大小等)与各预训练权重的下载地址;
  • modeling.pyMBartModel 及条件生成、问答、序列分类等变体,以及构建 decoder 输入的关键工具函数 shift_tokens_right
  • tokenizer.pyMBartTokenizerMBart50Tokenizer,负责将多语言文本切分为 subword 并注入语言代码。

其中 tokenizer.py 通过 __all__ = ["MBartTokenizer", "MBart50Tokenizer"] 对外暴露两个类,并在模块级声明了预训练模型的位置编码长度映射:

MBART_PRETRAINED_POSITIONAL_EMBEDDINGS_SIZES = {
    "mbart-large-cc25": 1024,
    "mbart-large-en-ro": 1024,
}
MBART50_PRETRAINED_POSITIONAL_EMBEDDINGS_SIZES = {
    "mbart-large-50-one-to-many-mmt": 1024,
    "mbart-large-50-many-to-one-mmt": 1024,
    "mbart-large-50-many-to-many-mmt": 1024,
}

也就是说,两个分词器共覆盖 5 个官方 checkpoint:mbart-large-cc25(多语言去噪预训练)、mbart-large-en-ro(英语-罗马尼亚语微调)、以及三个 MBart-50 多语言机器翻译模型。分词器资源文件统一为 sentencepiece.bpe.model,通过 pretrained_resource_files_map 声明,例如 mbart-large-en-ro 对应的词表即下载自该映射中登记的地址(见 tokenizer.py)。

二、核心机制:SentencePiece 词表 + fairseq 偏移的特殊词表布局

MBart 分词器没有为语言代码单独维护一张词表,而是在 SentencePiece 模型之外"追加"一段虚拟词表区间。理解这段布局,是理解 MBart 分词器一切行为的前提。

__init__ 中(见 tokenizer.py),初始化顺序如下:

  1. 加载 SentencePiece 模型 self.sp_model = spm.SentencePieceProcessor(...)
  2. 定义 self.fairseq_offset = 1,这是与 fairseq 保持兼容的关键偏移量;
  3. 定义基础特殊 Token 的固定 id:{"<s>": 0, "<pad>": 1, "</s>": 2, "<unk>": 3}
  4. 计算语言代码的 id:code_id = sp_model_size + i + fairseq_offset,其中 i 是语言代码在 FAIRSEQ_LANGUAGE_CODES 中的下标;
  5. <mask> 的 id 被放在语言代码之后:len(sp_model) + len(lang_code_to_id) + fairseq_offset

mbart-large-en-ro(SentencePiece 词表 250000 条)为例,可以精确推导出测试用例中出现的数字:

Token 计算公式 结果
ar_AR 250000 + 0 + 1 250001
en_XX 250000 + 3 + 1 250004
ro_RO 250000 + 19 + 1 250020
<mask> 250000 + 25 + 1 250026

这些数值与 tests/transformers/mbart/test_tokenizer.py 中的断言完全吻合。对于 MBart-50(52 个语言代码),<mask> 为 250053,mr_IN 为 250038,同样可以在 tests/transformers/mbart50/test_tokenizer.py 中找到对应断言。

2.1 普通 token 的 id 转换

词表布局的特殊性同样体现在 token 与 id 的互转上(tokenizer.py):

  • _convert_token_to_id:先在 fairseq_tokens_to_ids 中查找(覆盖 <s><pad></s><unk><mask> 与全部语言代码);否则调用 sp_model.PieceToId(token) 得到原始 id 后 加上 fairseq_offset(即 +1);若 Piece 不存在则返回 unk_token_id
  • _convert_id_to_token:反向操作,普通 id 需 减去 fairseq_offset 后再交给 sp_model.IdToPiece

这也是测试中 convert_tokens_to_ids(<a href="https://link.gitcode.com/i/f20d811bd9e0ac9c8d403ebf36f3e4be" target="_blank">"<mask>", "ar_AR"]) 得到 [250026, 250001] 的原因。vocab_size 属性因此定义为 len(sp_model) + len(lang_code_to_id) + fairseq_offset + 1([tokenizer.py),即 SentencePiece 词表、语言代码、<mask> 与偏移量之和。

2.2 与模型配置的词表一致性

分词器 id 布局必须与模型 embedding 矩阵大小严格对应。configuration.pymbart-large-cc25mbart-large-en-rovocab_size 为 250027,MBart-50 系列为 250054。以 MBart-50 为例:250000(SentencePiece)+ 52(语言代码)+ 1(偏移)+ 1(<mask>)= 250054,两边完全吻合,保证了 convert_ids_to_tokens 得到的每个 id 都能落在 embedding 的有效区间内。

三、MBartTokenizer 与 MBart50Tokenizer:语言覆盖差异

两个分词器类结构几乎完全一致,唯一实质区别在于 FAIRSEQ_LANGUAGE_CODES 的规模:

  • MBartTokenizer 支持 25 种语言代码(tokenizer.py):ar_AR, cs_CZ, de_DE, en_XX, es_XX, et_EE, fi_FI, fr_XX, gu_IN, hi_IN, it_IT, ja_XX, kk_KZ, ko_KR, lt_LT, lv_LV, my_MM, ne_NP, nl_XX, ro_RO, ru_RU, si_LK, tr_TR, vi_VN, zh_CN,服务于 mbart-large-cc25mbart-large-en-ro
  • MBart50Tokenizer 在前 25 种之上追加 27 种(tokenizer.py):af_ZA, az_AZ, bn_IN, fa_IR, he_IL, hr_HR, id_ID, ka_GE, km_KH, mk_MK, ml_IN, mn_MN, mr_IN, pl_PL, ps_AF, pt_XX, sv_SE, sw_KE, ta_IN, te_IN, th_TH, tl_XX, uk_UA, ur_PK, xh_ZA, gl_ES, sl_SI,合计 52 种,服务于三个 mbart-large-50-* 翻译模型。

由于语言代码 id 由列表下标决定,MBart50Tokenizer 中前 25 种语言的 id 与 MBartTokenizer 保持一致,后 27 种从 250026 开始依次编号。需要特别注意的是:语言代码在类内部以 lang_code_to_id 字典保存,并整体注入 _additional_special_tokenstokenizer.py),因此所有语言代码都被视为 special token,解码时可通过 skip_special_tokens=True 自动剔除。

四、构造函数与参数详解

两个类的 __init__ 签名完全一致(tokenizer.py):

参数 默认值 说明
vocab_file 必填 sentencepiece.bpe.model 的本地路径或可加载路径
src_lang None(回退 en_XX 源语言代码,决定编码时追加到序列末尾的语言代码
tgt_lang None 目标语言代码,供翻译流程与解码使用
bos_token "<s>" 序列起始标记,MBart 实际不使用 BOS
eos_token "</s>" 序列结束标记,id 为 2
sep_token "</s>" 分隔标记,与 eos 共用
cls_token "<s>" 分类标记,与 bos 共用
unk_token "<unk>" 未知词标记,id 为 3
pad_token "<pad>" 填充标记,id 为 1
mask_token "<mask>" 掩码标记,构造时包装为 AddedToken(mask_token, lstrip=True, rstrip=False),即掩码 Token 左侧不留空格
sp_model_kwargs None 透传给 sentencepiece.SentencePieceProcessor 的额外关键字参数(如 keep_accents 等)
additional_special_tokens None 额外的特殊 Token 列表,仅在不在既有集合中时追加

构造完成后立即执行 set_src_lang_special_tokens(self.src_lang)tokenizer.py),因此 prefix_tokenssuffix_tokens 在创建时即按源语言配置完毕。

五、文本编码调用:call 的参数体系与旧参数兼容

MBartTokenizer.__call__tokenizer.py)在转发给基类 PretrainedTokenizer 之前,做了三层旧参数兼容处理,方便从旧版 PaddleNLP 代码平滑迁移:

  1. pad_to_max_seq_len=Truepadding="max_length",否则 padding=False
  2. max_seq_lenmax_length
  3. truncation_strategy(非 "longest_first" 时)→ truncation

其余参数语义如下:

参数 默认值 说明
text / text_pair 必填 / None 单条文本或文本对;MBart 通常只处理单条序列,文本对按无分隔符方式拼接
max_length None 序列最大长度(配合 truncation 使用)
stride 0 溢出窗口的步长,配合 return_overflowing_tokens 使用
is_split_into_words False 输入是否已按词切分
padding None(最终解析为 False True / "longest" / "max_length"
truncation "longest_first" 截断策略,默认从最长序列截断
return_position_ids / return_token_type_ids False 是否返回位置 id / token 类型 id,MBart 默认不返回
return_attention_mask True 是否返回注意力掩码
return_length / return_overflowing_tokens / return_special_tokens_mask False 附加返回项

model_input_names = <a href="https://link.gitcode.com/i/fb796bdc3dcd220e2cdf171044ed1cb3" target="_blank">"input_ids"]([tokenizer.py)表明该分词器的标准模型输入为 input_ids。典型调用方式:

from paddlenlp.transformers import MBartTokenizer

tokenizer = MBartTokenizer.from_pretrained("mbart-large-en-ro", src_lang="en_XX", tgt_lang="ro_RO")
inputs = tokenizer(
    ["UN Chief Says There Is No Military Solution in Syria"],
    max_length=64,
    padding=True,
    truncation=True,
    return_tensors="pd",
)
print(inputs["input_ids"].shape)

六、特殊 Token 拼接:两种语言代码摆放格式

MBart 与 MBart-50 系列在特殊 Token 的摆放位置上存在重要差异,这是两个类最核心的行为分叉,分别实现在各自的 set_src_lang_special_tokens / set_tgt_lang_special_tokens 中:

MBartTokenizer(eos + 语言代码后缀)tokenizer.py):

self.prefix_tokens = []
self.suffix_tokens = [self.eos_token_id, self.cur_lang_code_id]

即编码器输入格式为 X <a href="https://link.gitcode.com/i/f937a4376f1a8f7e5c12e6d130d605c6" target="_blank">eos, src_lang_code],语言代码放在 eos 之后。测试中的 expected_src_tokens 末尾为 [..., 2, EN_CODE]([tests/transformers/mbart/test_tokenizer.py)正是这一格式的直接证据。

MBart50Tokenizer(语言代码前缀 + eos 后缀)tokenizer.py):

self.prefix_tokens = [self.cur_lang_code_id]
self.suffix_tokens = [self.eos_token_id]

即编码器输入格式为 <a href="https://link.gitcode.com/i/4acce65f0d9acb8611f09b261ee1a0cf" target="_blank">src_lang_code] X [eos],语言代码放在 序列最前面。对应测试断言 expected_src_tokens = [EN_CODE, 8274, ..., 2]([tests/transformers/mbart50/test_tokenizer.py)。

两种格式统一由 build_inputs_with_special_tokens 完成拼接(tokenizer.py):单序列为 prefix_tokens + token_ids_0 + suffix_tokens;文本对则无分隔符地拼接为 prefix_tokens + token_ids_0 + token_ids_1 + suffix_tokens。MBart 系列 从不使用 BOSget_special_tokens_mask 与之对应,将前缀与后缀位置标记为 1、正文标记为 0(tokenizer.py),build_offset_mapping_with_special_tokens 则为特殊 Token 位置填充 (0, 0) 偏移(tokenizer.py)。

在翻译任务中切换语言只需调用对应方法:

tokenizer.set_src_lang_special_tokens("en_XX")
tokenizer.set_tgt_lang_special_tokens("ro_RO")

七、常用 API 速览

除上述方法外,分词器还提供一组可直接调用的基础能力:

  • tokenize(text):底层由 _tokenize 调用 sp_model.encode(text, out_type=str) 完成 SentencePiece 切分(tokenizer.py),返回带 前缀的 subword 列表;
  • convert_tokens_to_ids(tokens) / convert_ids_to_tokens(ids):按第二节介绍的偏移规则互转;
  • convert_tokens_to_string(tokens) / convert_ids_to_string(ids):将 subword 序列还原为字符串,核心逻辑是 "".join(tokens).replace("▁", " ").strip()tokenizer.py),即把 SentencePiece 的空格标记 还原为真实空格并去除首尾空白;
  • get_vocab():返回 {token: id} 全量词表,并在末尾合并 added_tokens_encodertokenizer.py);
  • vocab_size:属性,返回含语言代码与 <mask> 的总词表大小;
  • decode(ids, skip_special_tokens=True):继承自基类,skip_special_tokens=True 时可自动剔除语言代码与 eos 等特殊 Token,得到纯净译文文本——集成测试 test_enro_tokenizer_decode_ignores_language_codestests/transformers/mbart/test_tokenizer.py)专门验证了这一点。

八、翻译数据预处理实战

8.1 用 shift_tokens_right 构建 decoder 输入

训练 MBart 条件生成时,decoder 的输入由标签右移一位得到。与普通 seq2seq 模型不同,MBart 的右移并非简单填充 pad id,而是把最后一列的非 pad Token(即目标语言代码 <LID>)搬运到序列首位,这正是 modeling.pyshift_tokens_right 的实现:

def shift_tokens_right(input_ids, pad_token_id):
    shifted_input_ids = input_ids.clone()
    ...
    index_of_eos = paddle.cast(shifted_input_ids != pad_token_id, dtype="int32").sum(axis=-1) - 1
    decoder_start_tokens = paddle.gather(input_flat, index + index_of_eos.astype(index.dtype))
    shifted_input_ids[:, 1:] = shifted_input_ids[:, :-1].clone()
    shifted_input_ids[:, 0] = decoder_start_tokens
    return shifted_input_ids

测试中 batch<a href="https://link.gitcode.com/i/e82c56c712f87724aba1755b67c60522" target="_blank">"decoder_input_ids"] = shift_tokens_right(labels, tokenizer.pad_token_id)([tests/transformers/mbart/test_tokenizer.py)演示了标准的训练数据组装流程:

from paddlenlp.transformers.mbart.modeling import shift_tokens_right

batch = tokenizer(src_text, padding=True, truncation=True, max_length=3, return_tensors="pd")
targets = tokenizer(tgt_text, padding=True, truncation=True, max_length=10, return_tensors="pd")
labels = targets["input_ids"]
batch["decoder_input_ids"] = shift_tokens_right(labels, tokenizer.pad_token_id)

8.2 用 _build_translation_inputs 准备推理输入

MBart50Tokenizer._build_translation_inputstokenizer.py)专为翻译推理设计,其行为被单测完整覆盖(tests/transformers/mbart50/test_tokenizer.py):

inputs = tokenizer._build_translation_inputs(
    "A test", return_tensors="pd", src_lang="en_XX", tgt_lang="ar_AR"
)
# input_ids: [[250004, 62, 3034, 2]]   (en_XX + 原文 + eos)
# forced_bos_token_id: 250001          (ar_AR)

它完成三件事:校验 src_lang/tgt_lang 必须提供;以 add_special_tokens=True 编码原文并附带源语言代码;将目标语言代码作为 forced_bos_token_id 写入返回结果,从而强制解码器从目标语言代码开始生成。

九、分词器持久化与序列化

MBart 分词器在 pickle 序列化与磁盘保存两方面都做了专门处理:

  • __getstate__ / __setstate__tokenizer.py):pickle 时把不可序列化的 SentencePieceProcessor 对象置空,改为保存其 serialized_model_proto() 二进制原型;反序列化时再通过 LoadFromSerializedProto 恢复,并兼容旧版本缺失 sp_model_kwargs 属性的情况;
  • save_resourcestokenizer.py):将 vocab_file 复制为 sentencepiece.bpe.model 写入保存目录;若原始词表文件已不存在,则直接写出序列化原型,保证分词器在任何情况下都能完整落盘。

测试 test_special_tokens_unaffacted_by_save_loadtests/transformers/mbart/test_tokenizer.py)验证了 save_pretrained 后再 from_pretrained 加载,fairseq_tokens_to_ids(含语言代码与 <mask> 映射)保持完全一致。

十、测试验证与集成要点

MBart 分词器在仓库中有完整的测试支撑:

实践中的关键集成要点可总结为:

  1. 语言代码必须显式指定src_lang 默认回退为 en_XX,训练/推理前务必通过构造参数或 set_src_lang_special_tokens 明确源语言;
  2. 两代模型格式不同mbart-large-*(cc25/en-ro)语言代码在序列尾部(X [eos, lang]),mbart-large-50-* 语言代码在序列头部([lang] X [eos]),切勿混用;
  3. id 对齐以配置为准:分词器的 vocab_size 必须与 MBartConfig.vocab_size 一致(250027 / 250054),自定义训练时两者需同步修改;
  4. decoder 输入用 shift_tokens_right:不要用普通 pad 填充做右移,否则会丢失目标语言代码导致生成质量下降。

通过上述机制,MBart 分词器把"多语言共享词表"与"语言条件控制"无缝融合,是 PaddleNLP 中实现多语言翻译、多语言去噪预训练等任务的基础组件。若需进一步了解模型侧行为,可继续阅读 paddlenlp/transformers/mbart/modeling.pypaddlenlp/transformers/mbart/configuration.py

登录后查看全文
PaddleNLP