PaddleNLP MPNet 模型全解析:架构原理、配置参数与实战调用指南

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

PaddleNLP MPNet 模型全解析:架构原理、配置参数与实战调用指南

MPNet(Masked and Permuted Language Modeling)是由微软提出的预训练语言模型,它在掩码语言建模(MLM)与排列语言建模(PLM)之间找到了统一的折中方案,弥补了 BERT 依赖独立性假设、XLNet 忽略 token 绝对位置的缺陷。本指南以 paddlenlp.transformers.mpnet.rst 对应的 API 参考为主线,结合 modeling.py、configuration.py、tokenizer.py 的实现细节,系统讲解 MPNet 在 PaddleNLP 中的配置体系、模型架构、各任务头封装与分词器用法。读完本文,你将掌握 MPNet 系列模型从加载、配置、前向推理到下游任务微调的完整调用方法,并能对照源码理解其相对位置编码、池化策略等核心机制。

一、模块总览:PaddleNLP 中的 MPNet 家族

在 PaddleNLP 中,MPNet 的完整实现集中在 paddlenlp/transformers/mpnet 目录下,由四个文件构成:

文件 职责
configuration.py 定义 MPNetConfig 与预训练配置常量
modeling.py 定义 MPNetModel、MPNetPretrainedModel 及五个下游任务模型
tokenizer.py 定义 MPNetTokenizer
__init__.py 汇总导出 modeling 与 tokenizer 的公开符号

对应地,中文 API 文档在 docs/zh/source 下拆分为三个 RST 节点:paddlenlp.transformers.mpnet.rst(模块入口)、paddlenlp.transformers.mpnet.modeling.rst 与 paddlenlp.transformers.mpnet.tokenizer.rst,分别由 automodule 指令自动抽取源码 docstring 生成。

modeling.py 中公开的类名如下(见 __all__ 列表):MPNetModel、MPNetPretrainedModel、MPNetForMaskedLM、MPNetForSequenceClassification、MPNetForMultipleChoice、MPNetForTokenClassification、MPNetForQuestionAnswering。其中 MPNetModel 通过 @register_base_model 装饰器注册为基座模型,MPNetPretrainedModel 是抽象预训练基类,负责权重下载与加载。

二、MPNetConfig:模型架构的 12 个核心参数

MPNetConfig 继承自 PretrainedConfig(定义于 configuration_utils.py),用于在实例化模型时完整定义架构。其默认值与 mpnet-base 保持一致,源码中的完整签名如下:

def __init__(
    self,
    vocab_size: int = 30527,
    hidden_size: int = 768,
    num_hidden_layers: int = 12,
    num_attention_heads: int = 12,
    intermediate_size: int = 3072,
    hidden_act: str = "gelu",
    hidden_dropout_prob: float = 0.1,
    attention_probs_dropout_prob: float = 0.1,
    max_position_embeddings: int = 514,
    initializer_range: float = 0.02,
    layer_norm_eps: float = 1e-5,
    relative_attention_num_buckets: int = 32,
    pad_token_id: int = 1,
    bos_token_id: int = 0,
    eos_token_id: int = 2,
    **kwargs
):

各参数的含义与取值说明如下:

参数 默认值 作用说明
vocab_size 30527 词表大小,决定 input_ids 可表示的 token 数量
hidden_size 768 编码器层与池化层的隐层维度
num_hidden_layers 12 Transformer 编码器层数
num_attention_heads 12 每层注意力头数,须能整除 hidden_size
intermediate_size 3072 前馈网络(FFN)中间层维度
hidden_act "gelu" 编码器与池化层的非线性激活函数,支持 "gelu"、"relu"、"silu"、"gelu_new"
hidden_dropout_prob 0.1 嵌入层、编码器与池化层全连接层的 dropout 概率
attention_probs_dropout_prob 0.1 注意力概率矩阵的 dropout 比例
max_position_embeddings 514 最大序列长度(含特殊 token),mpnet-base 为 514
initializer_range 0.02 权重矩阵截断正态初始化的标准差
layer_norm_eps 1e-5 LayerNorm 的 epsilon
relative_attention_num_buckets 32 相对位置编码的分桶数量

MPNetConfig 还声明了 model_type = "mpnet",并建立了 attribute_map = {"num_classes": "num_labels"},即旧属性名 num_classes 会自动映射到新属性 num_labels。特殊 token 的默认约定为:pad_token_id=1、bos_token_id=0、eos_token_id=2。

从源码结构看,MPNET_PRETRAINED_INIT_CONFIGURATION 目前为空字典,预训练权重信息直接维护在 MPNetPretrainedModel.pretrained_resource_files_map 中,目前内置的官方权重为 mpnet-base,其 model_state.pdparams 权重文件托管于百度 BOS。

三、MPNetModel:双任务统一模型的 Paddle 实现

MPNetModel 由三部分组成:MPNetEmbeddings、MPNetEncoder 与 MPNetPooler。前向传播的返回值为二元组 (sequence_output, pooled_output)。

3.1 嵌入层:位置感知的"非 padding 累计"编码

MPNetEmbeddings 包含词嵌入、位置嵌入、LayerNorm 与 dropout。与 BERT 直接使用预置 position_ids 不同,MPNet 的位置 id 由输入动态推导:

def create_position_ids_from_input_ids(input_ids, padding_idx=1):
    mask = (input_ids != padding_idx).astype(paddle.int64)
    incremental_indices = paddle.cumsum(mask, axis=1).astype(mask.dtype) * mask
    return incremental_indices.astype(paddle.int64) + padding_idx

该函数借鉴了 fairseq 的 make_positions:非 padding 符号按其在有效 token 序列中的顺序编号(从 padding_idx + 1 起),padding 符号则被屏蔽忽略。这保证了即使样本被 padding 到不同长度,模型接收到的位置信息仍然与真实语序严格对应——这是 MPNet 能在预训练中同时保留绝对位置信号的关键。position_ids 可通过前向接口手动传入,缺省时自动按上述规则生成。

3.2 编码器:相对位置分桶 + 前置/后置 LayerNorm

MPNetEncoder 由 num_hidden_layers 个 MPNetLayer 堆叠而成(nn.LayerList 深拷贝同一层),并额外维护一个 relative_attention_bias 嵌入表,尺寸为 (relative_attention_num_buckets, num_attention_heads)。

MPNet 的核心创新在于注意力计算同时融合了绝对位置(通过位置嵌入)与相对位置(通过分桶偏置):

def compute_position_bias(self, x, position_ids=None, num_buckets=32):
    bsz, qlen, klen = x.shape[0], x.shape[1], x.shape[1]
    context_position = paddle.arange(qlen).unsqueeze(1)
    memory_position = paddle.arange(klen).unsqueeze(0)
    relative_position = memory_position - context_position
    rp_bucket = self.relative_position_bucket(relative_position, num_buckets=num_buckets)
    values = self.relative_attention_bias(rp_bucket)
    ...

相对位置按 T5 风格进行分桶映射:relative_position_bucket 先将位置差取负号判断正负(负向偏移计入一半桶数),再对绝对值做对数分桶,max_distance=128 以内的精确距离用小桶表示,超出部分通过对数缩放压缩到大桶中。最终生成的 position_bias 形状为 [batch, num_heads, qlen, klen],在 MPNetAttention 中直接加到注意力得分上。

注意力层(MPNetAttention)的实现在结构上遵循"前置 LayerNorm"(Pre-LN)设计:Q/K/V 线性投影后按头拆分并乘以 scale = attention_head_size ** -0.5,加上 position_bias 与 attention_mask 后 softmax、dropout,与 V 相乘得到上下文向量,经输出投影和 dropout 后,再与残差连接一起过 LayerNorm。MPNetLayer 中的 FFN 同样采用残差 + 后 LayerNorm 结构。此外,每层都会返回 layer_att(注意力得分)与所有中间层输出,便于分析注意力模式或做中间层特征提取。

3.3 池化层:首 token 池化

MPNetPooler 取序列第一个 token(<s>)的隐状态,经 nn.Linear(hidden_size, hidden_size) 与 nn.Tanh 得到 [batch_size, hidden_size] 的 pooled_output,供分类类任务使用。

3.4 前向接口与注意力掩码约定

MPNetModel.forward(input_ids, position_ids=None, attention_mask=None) 的约定如下:

  • input_ids:int64 型,形状 [batch_size, sequence_length];
  • position_ids:可选,范围 [0, max_position_embeddings - 1];
  • attention_mask:可选。缺省时自动以 input_ids != pad_token_id 生成;当 mask 为二维时,会扩展为 [batch, 1, 1, seq_len] 并转换为 (1.0 - mask) * -10000.0 的加法掩码形式,1 表示可见、0 表示被屏蔽。

官方示例(取自 docstring,可直接运行):

import paddle
from paddlenlp.transformers import MPNetModel, MPNetTokenizer

tokenizer = MPNetTokenizer.from_pretrained('mpnet-base')
model = MPNetModel.from_pretrained('mpnet-base')

inputs = tokenizer("Welcome to use PaddlePaddle and PaddleNLP!")
inputs = {k: paddle.to_tensor([v]) for (k, v) in inputs.items()}
outputs = model(**inputs)
# outputs[0]: sequence_output, [1, seq_len, 768]
# outputs[1]: pooled_output,  [1, 768]

四、五个下游任务模型:一行代码接入分类、抽取与问答

modeling.py 在基座模型之上封装了五个带任务头的模型,均通过 from_pretrained 加载预训练权重后直接微调。

4.1 MPNetForMaskedLM —— 掩码语言建模

结构为 MPNetModel + MPNetLMHead。MPNetLMHead 先做 dense → 激活 → LayerNorm,再与词嵌入权重共享的解码矩阵做转置矩阵乘法并加偏置,输出 [batch, seq_len, vocab_size] 的 prediction_scores。labels 中值为 -100 的位置将被忽略(不参与损失计算),仅对有效标签位置计算 CrossEntropyLoss。传入 labels 时返回三元组 (masked_lm_loss, prediction_scores, sequence_output),否则返回 (prediction_scores, sequence_output)。

4.2 MPNetForSequenceClassification —— 文本分类 / 回归

在 pooled_output 之上接 Dropout + Linear(hidden_size, num_labels),适用于 GLUE 类任务。分类器的 dropout 优先使用 config.classifier_dropout,未设置时回退到 hidden_dropout_prob。返回 [batch_size, num_classes] 的 logits。

4.3 MPNetForMultipleChoice —— 多项选择

将 [batch, num_choice, seq_len] 的输入展平为 [batch * num_choice, seq_len] 过基座模型,池化后经单输出分类头得到 [batch * num_choice, 1],再 reshape 回 [batch, num_choice]。适用于 RocStories、SWAG 等多项选择任务,构造时默认 num_choices=2。

4.4 MPNetForTokenClassification —— 序列标注 / NER

直接对 sequence_output 逐 token 做 Dropout + Linear(hidden_size, num_labels),返回 [batch, seq_len, num_labels] 的 logits,适用于命名实体识别等任务。

4.5 MPNetForQuestionAnswering —— 抽取式问答

在 sequence_output 上接 Linear(hidden_size, 2),经转置与 paddle.unstack 拆出 start_logits 与 end_logits(形状均为 [batch, seq_len]),用于 SQuAD 类答案区间抽取。

五、MPNetTokenizer:与 BERT 几乎一致的分词方案

MPNetTokenizer 继承自 BertTokenizer(实现在 bert/tokenizer.py),因此完整继承了 BasicTokenizer(小写化、标点切分、中文字符切分、Unicode 归一化)与 WordpieceTokenizer 的分词管线,但特殊 token 体系针对 MPNet 做了定制:

  • 预训练配置:mpnet-base 默认 do_lower_case=True;
  • 特殊 token:bos_token="<s>"、eos_token="</s>"、unk_token="[UNK]"、sep_token="</s>"、pad_token="<pad>"、cls_token="<s>"、mask_token="<mask>";
  • mask_token 以 lstrip=True 注册为 AddedToken,使其像普通词一样保留前置空格;
  • 词表文件名为 vocab.txt,官方权重对应资源地址见 pretrained_resource_files_map。

序列拼接格式与 BERT 有明显差异:

  • 单序列:<s> X </s>;
  • 序列对:<s> A </s></s> B </s>(两个 </s> 连用分隔两段)。

对应的 get_special_tokens_mask 会为双序列返回 [1] + 0... + [1, 1] + 0... + [1] 的掩码。由于 MPNet 不使用 token type id,create_token_type_ids_from_sequences 直接返回全 0。__call__ 支持 max_length、stride、padding、truncation、pad_to_multiple_of、return_offsets_mapping 等完整参数集,与基类保持一致。

典型调用:

from paddlenlp.transformers import MPNetTokenizer

tokenizer = MPNetTokenizer.from_pretrained('mpnet-base')
# 单序列
print(tokenizer("Welcome to use PaddlePaddle and PaddleNLP!"))
# 序列对:用于句对分类 / 匹配任务
print(tokenizer("sentence A", "sentence B"))

六、从配置到模型:四种标准加载方式

MPNetConfig 的 docstring 给出了标准用法,结合 PaddleNLP 的 from_pretrained 机制,共有四条加载路径:

from paddlenlp.transformers import MPNetModel, MPNetConfig

# 1. 仅用默认配置实例化(相当于 mpnet-base 风格架构)
configuration = MPNetConfig()
model = MPNetModel(configuration)

# 2. 自定义配置实例化(例如改为 6 层 6 头)
custom_config = MPNetConfig(hidden_size=384, num_hidden_layers=6, num_attention_heads=6)
model = MPNetModel(custom_config)

# 3. 直接加载官方预训练权重
model = MPNetModel.from_pretrained('mpnet-base')

# 4. 从模型对象反向获取配置
configuration = model.config

方式 2 适合从零训练或蒸馏小模型;方式 3 会从 pretrained_resource_files_map 指定的 BOS 地址自动下载 model_state.pdparams 权重。MPNetPretrainedModel._init_weights 使用 paddle.tensor.normal 以 initializer_range 为标准差初始化 nn.Linear 与 nn.Embedding 权重。

七、正确性与可用性验证:仓库测试与文档

PaddleNLP 为 MPNet 提供了完整的单测覆盖,可作为复现与验证的依据:

中文文档侧,除入口页 paddlenlp.transformers.mpnet.rst 外,还有 modeling 与 tokenizer 两个子页面,通过 automodule 与 :members:、:no-undoc-members:、:show-inheritance: 指令自动生成全部公开 API 的参考手册,所有 docstring 即上文各节引用的参数与返回说明的第一手来源。

八、实战小结与选型建议

综上,PaddleNLP 的 MPNet 实现具备三个鲜明特点:

  1. 位置编码双轨制:动态累计位置 id(绝对位置)+ 分桶相对位置偏置(相对位置),这正是 MPNet 论文提出的"掩码 + 排列"统一建模在工程上的落地形态,实现位于 modeling.py 的 MPNetEmbeddings 与 MPNetEncoder.compute_position_bias;
  2. 任务覆盖齐全:一个基座模型 + 五个任务头(MLM、分类/回归、多项选择、序列标注、抽取式问答),覆盖 NLP 主流下游场景,且任务头均共享 from_pretrained 加载机制;
  3. 分词即插即用:MPNetTokenizer 直接复用 BERT 分词能力,仅需注意其特殊的 <s> A </s></s> B </s> 句对格式与全零 token type id。

若你的场景需要比 BERT 更强的上下文建模(例如句子对匹配、抽取式问答、长文本表示),且不介意其预训练权重仅有 mpnet-base 一档,可优先选用 MPNet 系列。具体接入时,建议以"分词器 + 任务模型 + from_pretrained('mpnet-base')"的组合快速起步,再根据算力与精度诉求调整 hidden_size、num_hidden_layers 等配置项重新训练或蒸馏。

登录后查看全文
PaddleNLP