首页
/ Transformers 中 CPMAnt 模型的接入、推理生成与架构解析:基于 cpmant 源码的完整实践指南

Transformers 中 CPMAnt 模型的接入、推理生成与架构解析:基于 cpmant 源码的完整实践指南

2026-09-06 19:27:16作者:伍希望

导读

本文围绕本仓库官方模型文档 docs/source/en/model_doc/cpmant.md 展开,系统讲解 CPMAnt——OpenBMB CPM-Live 开源训练项目的第一个里程碑模型——在 🤗 Transformers 中的使用方式与底层实现。你将掌握两种文本生成调用方式(Pipeline 与 CpmAntForCausalLM)、CpmAntConfig 全部超参数的语义与默认值、基于 rjieba + WordPiece 的中文分词细节,以及源码层面 RMSNorm、门控 FFN、分段位置编码与 prompt 前缀注入等架构机理。仓库中的实现文件与测试用例会作为每一条结论的落点依据。


1. CPMAnt 模型速览

CPMAnt 是一个约 10B 参数的开源中文预训练语言模型,由 OpenBMB 的 CPM-Live 开源持续训练项目发布,属于该项目产出的第一个里程碑模型。根据 cpmant.md 文档说明,该模型具有以下特点:

  • 中文为主:面向中文文本生成设计;
  • CUGE 基准表现:配合 delta tuning(增量微调)在 CUGE 中文评测基准上取得较好结果;
  • 可压缩部署:官方还提供了适配不同硬件配置的压缩变体,供在资源受限环境下使用;
  • 2023-04-12 由社区贡献接入 Transformers

模型架构遵循“Pre-Norm + 残差”的 Transformer 解码器结构,但在位置编码、前馈网络与归一化上采用了更具现代性的设计(RMSNorm、门控 FFN、分段桶式相对位置编码),这些细节我们会在第 5 节结合源码逐一展开。

auto 模型映射 中,cpmant 同时注册了基础模型 CpmAntModel(映射关系见 modeling_auto.py)与自回归生成头模型 CpmAntForCausalLM,说明它可以被 AutoModelForCausalLM 等自动 API 以 "cpmant" 类型名识别加载。而本仓库内 cpmant 相关实现只包含四个文件,全部位于 src/transformers/models/cpmant/,即:

文件 职责
configuration_cpmant.py CpmAntConfig,全部超参数定义
modeling_cpmant.py PyTorch 模型实现(CpmAntModelCpmAntForCausalLM 等)
tokenization_cpmant.py CpmAntTokenizer 分词实现
init.py 惰性模块导出

对应测试位于 tests/models/cpmant/test_modeling_cpmant.pytests/models/cpmant/test_tokenization_cpmant.py


2. 快速上手:中文文本生成

原文档给出了两种完全对等的调用路径,二者都指向 Hugging Face Hub 上官方发布的检查点 openbmb/cpm-ant-10b

2.1 方式一:通过 Pipeline 一行完成

pipeline 抽象了 tokenizer、模型、设备分配与解码细节,适合快速验证模型效果:

from transformers import pipeline


pipe = pipeline(
    task="text-generation",
    model="openbmb/cpm-ant-10b",
)
pipe("今天天气很好,")

task="text-generation" 会触发 TextGenerationPipeline,底层自动装配 CpmAntTokenizerCpmAntForCausalLM

2.2 方式二:通过 CpmAntForCausalLM 精细控制

需要显式控制设备映射、采样参数或反复解码时,建议直接使用模型类:

from transformers import CpmAntForCausalLM, CpmAntTokenizer


tokenizer = CpmAntTokenizer.from_pretrained("openbmb/cpm-ant-10b")
model = CpmAntForCausalLM.from_pretrained(
    "openbmb/cpm-ant-10b",
    device_map="auto",
)
input_ids = tokenizer("今天天气很好,", return_tensors="pt").to(model.device)

output = model.generate(**input_ids, max_new_tokens=50)
print(tokenizer.decode(output[0], skip_special_tokens=True))

要点说明:

  • device_map="auto" 依赖 accelerate,可在多 GPU / CPU offload 环境下自动分片放置这 10B 参数;
  • tokenizer 直接接受中文原始文本,内部先经结巴分词再走 WordPiece(详见第 4 节);
  • model.generate(**input_ids, max_new_tokens=50) 表示在已有输入基础上续写最多 50 个新 token。模型会在解码时自动拼接并剥离内部使用的 prompt 前缀,用户侧无需关心。

modeling_cpmant.pyCpmAntForCausalLM.forward 文档字符串中还给出了一个实际生成示例:以 "今天天气不错," 为前缀,模型能自然续写出一段连贯的中文叙事,并正确使用换行 token </n> 作为句段分隔。这表明该模型对中文日常语料的续写质量可直接用于交互演示。

兼容性注意:模型文档与部分 docstring 中出现过 CPMAntTokenizer 命名,本仓库当前实际导出的类名为 CpmAntTokenizer,请以 CpmAntTokenizer 为准(见 tokenization_cpmant.py__all__)。


3. CpmAntConfig:模型结构与超参数总览

CpmAntConfig 定义于 configuration_cpmant.py,类型标记为 model_type = "cpmant"openbmb/cpm-ant-10b 即采用默认值构建。下表整理全部超参数、默认值与语义,来自源码中的默认值字段:

参数 默认值 语义
vocab_size 30720 词表大小
hidden_size 4096 隐藏层维度
num_attention_heads 32 注意力头数
dim_head 128 每个注意力头的维度(num_heads * dim_head = hidden_size
dim_ff 10240 FFN 中间层维度
num_hidden_layers 48 Transformer 层数
dropout_p 0.0 注意力与 FFN 内 dropout 概率
position_bias_num_buckets 512 相对位置 bias 的桶(bucket)数量
position_bias_max_distance 2048 模型可能遇到的最大序列长度(桶编码的上界)
eps 1e-6 RMSNorm 的数值稳定项
init_std 1.0 分段位置编码等参数初始化标准差
prompt_types 32 prompt 的类型数
prompt_length 32 内部 prompt 前缀的长度
segment_types 32 分段(segment)类型数
use_cache True 是否缓存 KV,加速增量解码
tie_word_embeddings True 是否绑定词嵌入权重

3.1 用配置初始化随机权重模型

CpmAntConfig 可以直接配合 CpmAntModel 构造一个未加载预训练权重的模型实例,常用于结构测试或从头训练:

from transformers import CpmAntModel, CpmAntConfig


# Initializing a CPMAnt cpm-ant-10b style configuration
configuration = CpmAntConfig()

# Initializing a model from the cpm-ant-10b style configuration
model = CpmAntModel(configuration)

# Accessing the model configuration
configuration = model.config

注意 CpmAntConfig 声明时带有 @strict(来自 huggingface_hub.dataclasses)修饰,即配置类采用严格数据类语义,传入未声明字段会报错,避免了拼写错误导致的静默失效。

3.2 几个超参数的源码级解读

  • prompt_types / prompt_length / segment_types:这三个参数不是普通的位置或 dropout 参数,而是直接决定嵌入表尺寸的“结构性”参数。由 modeling_cpmant.py 可见,输入嵌入层被定义为 nn.Embedding(config.vocab_size + config.prompt_types * config.prompt_length, config.hidden_size)(即默认 30720 + 1024 = 31744 行),分段嵌入层为 nn.Embedding(config.segment_types, config.hidden_size),并额外创建了 CpmAntSegmentPositionEmbedding 用于注入位置/分段信息。也就是说,只要修改这些配置,就必须配套重新训练或使用结构匹配的检查点。
  • position_bias_num_buckets / position_bias_max_distance:控制相对位置“分桶”的粒度与覆盖范围。长距离位置被对数压缩到有限个桶中(算法见第 5.4 节),这使模型在 2048 默认上界内无需维护稠密的位置编码表。
  • tie_word_embeddings=True_tied_weights_keys = {}:虽然默认绑定词嵌入,但源码注释明确说明:lm_head 其实是 input_embedding.weight 的一个“词表大小切片”(因为输入嵌入表多了 prompt 占位行),整张张量绑定会形状不匹配,因此检查点单独携带 lm_head.weightmodeling_cpmant.py 中把 _tied_weights_keys 置空并由 nn.Linear(config.hidden_size, config.vocab_size, bias=False) 独立承载语言模型头。

4. CpmAntTokenizer:中文分词细节

CPMAnt 的词表面向中文,直接对中文字符做子词切分效率不高,因此 tokenization_cpmant.py 实现的 CpmAntTokenizer 采用“结巴分词(rjieba)→ 逐词 WordPiece”两级切分策略。

4.1 初始化与特殊 token

构造时所需文件为词表文件 vocab.txtVOCAB_FILES_NAMES = {"vocab_file": "vocab.txt"}),可配置的特殊 token 如下(默认值均来自源码):

参数 默认值 含义
bod_token <d> 文档起始(begin of document)
eod_token </d> 文档结束(end of document)
bos_token <s> 序列起始
eos_token </s> 序列结束
pad_token <pad> 填充
unk_token <unk> 未知 token
line_token </n> 换行
space_token </_> 空格

分词器的 padding_side 默认是 "left"(左侧填充),与自回归解码时“右侧续写”的语义吻合;model_input_names = ["input_ids", "attention_mask"] 表明它只输出这两类模型输入。

硬性依赖__init__ 中调用 requires_backends(self, ["rjieba"]),即必须安装 rjieba 才能实例化该 tokenizer。rjieba 是 C++ 结巴分词的高性能 Python 绑定,缺失时会抛出带明确提示的 ImportError。安装方式为常规 pip 安装 rjieba 后重启解释器即可。

4.2 两级分词流程

def _tokenize(self, text):
    """Tokenize a string."""
    output_tokens = []
    for x in rjieba.cut(text, False):
        output_tokens.extend(self.wordpiece_tokenizer.tokenize(x))
    return output_tokens

rjieba.cut(text, False) 先把中文文本切分为词/词组,随后内嵌的 WordpieceTokenizertokenization_cpmant.py)对每个词执行最长匹配子词切分:

  • 若整词在词表中,直接作为一个 token;
  • 否则逐步缩短尾部前缀(end -= 1 循环)寻找最长可匹配子串;
  • 单字长度超过 max_input_chars_per_word(默认 200)或无可匹配子串时回退为 <unk>

4.3 特殊 token 的“透明化”处理

一个易被忽略的实现细节:加载词表后,tokenizer 把词表中的 </_>(空格)与 </n>(换行)这两个特殊词条的 id 直接复用给普通字符 " ""\n"(见 __init__self.encoder[" "] = self.encoder[space_token] 一段),随后从词表中删除这两个特殊 token 并将它们从 added_tokens 中弹出。效果是:用户在显式控制分词时看到的空格/换行就是普通 token 而非特殊 token,解码时也不会残留多余特殊标记。save_vocabulary 保存时会做逆变换,把 " "/"\n" 还原为 </_>/</n> 写回文件,保证可往返。

另外,_decode 在解码前会过滤掉 pad_token_ideos_token_idbos_token_id,避免生成结果尾部出现大量填充与结束标记——这也是 generate 后需要配合 skip_special_tokens=True 的一个内在原因。

4.4 分词正确性的测试证据

tests/models/cpmant/test_tokenization_cpmant.py 中有针对中文分词的精确断言,例如给定 "今天天气真好!" 期望得到 rjieba 分词结果 ["今天", "天气", "真", "好", "!"],并验证了 token-id 互转与文本重建的一致性。这些测试用 @require_rjieba 标记,说明整条测试链路都建立在 rjieba 可用之上。


5. 架构与源码级原理:CpmAntModel 内部分解

模型实现集中在 modeling_cpmant.py(共 783 行)。它并非简单复刻某一经典结构,而是融合了 RMSNorm、门控 FFN、分段相对位置偏置与 prompt 前缀机制。逐层自底向上看:

5.1 RMSNorm:无偏置的层归一化

CpmAntLayerNormmodeling_cpmant.py)实现的是 RMS Layer Normalization:计算隐藏状态的均方根作为归一化分母(torch.rsqrt(variance + eps)),只在 float32 下统计方差以避免低精度溢出,权重 weight 初始化为 1(由 _init_weightsinit.ones_ 完成)。相比带偏置的 LayerNorm,RMSNorm 参数更少且训练更稳定,这也是现代 LLM 的常见选择。

5.2 带残差的 Pre-LN 块

每个 CpmAntTransformerBlockmodeling_cpmant.py)由自注意力子块与 FFN 子块串联,且均采用 Post-Residual / Pre-LN 形式:

  • CpmAntSelfAttentionBlock:先对输入做 layernorm_before_attention,注意力输出经 dropout 后加回原输入(残差);
  • CpmAntFFNBlock:同样先 layernorm_before_ffn,再进入 FFN 并残差相加。

注意力实现 CpmAntAttention 中 Q/K/V 均使用 bias=False 的线性投影,点积注意力分数除以 sqrt(dim_head)直接加上 position_bias(而非常见的旋转位置编码或绝对位置加和),并用 Bool 型 attention mask 将无效位置填充为 -inf(softmax 前)与 0(softmax 后)。当 past_key_values 非空时调用 past_key_values.update(...) 追加新 KV,len_k 随之增长,从而支持增量解码。

5.3 门控 GELU 前馈网络

FFN 并非单一线性层,而是 CpmAntDenseGatedACT + 输出投影的两段式:

gate_score = self.act(self.w_0(hidden_states))   # GELU(w_0 x)
hidden_states = self.w_1(hidden_states)          # w_1 x
hidden_states = gate_score * hidden_states       # 逐元素门控相乘

hidden = GELU(x·W0) ⊙ (x·W1),随后经 w_out 投影回 hidden_size。这种“门控线性单元”结构(类 GLU 变体)相比普通 ReLU FFN 有更强的特征选择能力。CpmAntDenseGatedACTCpmAntIntermediateCpmAntOutput 中虽有部分类名与 BERT 一致,但真正被 TransformerBlock 串联使用的是上述 self_att + ffn(注意 CpmAntIntermediateCpmAntOutput 仅以 Copied from ... Bert 注释保留,实际不参与 cpmant 主路径计算)。

5.4 分段 + 相对位置的“双桶”编码:CpmAntSegmentPositionEmbedding

这是 CPMAnt 区别于一般 Transformer 最核心的设计,实现在 modeling_cpmant.py

  1. 分段桶(segment bucket)_segment_relative_position_bucket 计算 query_segment * num_segments + key_segment,即用 (q 段, k 段) 组合编号描述跨段关系,再整体平移 +num_buckets
  2. 绝对位置桶(absolute bucket)_position_bucket 采用 T5 式分桶——短距离线性精确映射(max_exact = num_buckets // 4 内),长距离取绝对值后用对数函数压缩映射到剩余桶,符号方向仅保留正负两档(源码注释 always bidirectional in CPMAnt),因此同一表格可复用前向/后向;
  3. 合并规则:当 key_segment == query_segment(同段内)时选用绝对位置桶,跨段时选用分段桶——torch.where 完成二选一;
  4. 可学习偏置表 relative_attention_bias 形状为 (segment_types^2 + position_bias_num_buckets, num_attention_heads)(默认 (32*32+512, 32)),按桶号用 F.embedding 查表得到 (batch, num_heads, len_q, len_k) 的偏置张量,并在注意力内与点积分数相加。

由于偏置表行数 = segment_types * segment_types + position_bias_num_buckets,这解释了为何 position_bias_num_buckets 默认 512、segment_types 默认 32:二者共同决定位置编码参数量。

5.5 CpmAntModel.forward:prompt 前缀的自动拼接与剥离

解码器入口(modeling_cpmant.py)的 forward 做了几件对用户“透明”的事:

  • 自动判断分段segment = torch.where(input_ids != 0, 2, 0),非零 token 归为段 2,pad 归为段 0;
  • 自动拼接 prompt:在真实输入头部拼上 prompt_length(默认 32)个 id 位于 [vocab_size + 2*prompt_length, vocab_size + 3*prompt_length) 区间的“提示 token”,并将这些位置的 segment 置 0,作为固定引导前缀;
  • 构造自定义注意力掩码_prepare_attention_maskcontext[:, None, :] | (context[:, :, None].logical_not() & directional_mask_2d) 实现“前序上下文可见 + 因果三角”的复合掩码,再约束 span 相等并对左侧 padding 做遮蔽;directional_mask_2d 即下三角因果掩码;
  • 缓存分支use_cache 为真时创建 DynamicCache;当存在 past_length(第二轮起)时,只取每轮最新的 1 个位置的 segment/hidden 参与计算,并在切片后通过 attention_mask[:, past_length:]position_bias[:, :, past_length:] 对齐;
  • 输出剥离 prompt:首次前向结束后,将 hidden states、attentions、hidden_states 序列的前 prompt_length 位裁掉(hidden_states[:, self.prompt_length:, :]),保证对外暴露的 last_hidden_state 与输入严格对齐,用户无需感知内部 prompt。

对外返回标准的 BaseModelOutputWithPastlast_hidden_statepast_key_valueshidden_statesattentions 四件套),与其余 Transformers 解码器模型接口一致。


6. CpmAntForCausalLM:生成与训练

6.1 结构

CpmAntForCausalLMmodeling_cpmant.py)继承 CpmAntPreTrainedModelGenerationMixin

self.cpmant = CpmAntModel(config)
self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)

get_input_embeddings/set_input_embeddings 均代理到底层 self.cpmant.input_embedding,方便 LoRA、prefix tuning 等增量训练框架替换嵌入。prepare_inputs_for_generation 的注释指出:由于 forward 内部会自己裁掉已缓存的头部序列,生成时需要把完整input_ids 传入。

6.2 前向计算与训练损失

forward 依次完成:self.cpmant(...) 得到末层隐藏状态 → self.lm_head 计算 logits → 若传入 labels 则用 CrossEntropyLoss 计算语言建模损失。同时支持 logits_to_keep 参数,只对序列最后若干个位置计算 logits,从而大幅减少训练/评估时的显存占用。

一个完整的监督微调片段(构造 labels 对齐示例):

from transformers import CpmAntForCausalLM, CpmAntTokenizer

tokenizer = CpmAntTokenizer.from_pretrained("openbmb/cpm-ant-10b")
model = CpmAntForCausalLM.from_pretrained("openbmb/cpm-ant-10b")

texts = "今天天气不错,"
labels = "阳光明媚,我和妈妈一起去超市买东西。"

inputs = tokenizer(texts, return_tensors="pt")
label_ids = tokenizer(labels, return_tensors="pt").input_ids

# 把标签拼接在输入之后作为模型输入
full_ids = torch.cat([inputs.input_ids, label_ids], dim=-1)
attention_mask = torch.ones_like(full_ids)

outputs = model(
    input_ids=full_ids,
    attention_mask=attention_mask,
    labels=full_ids,          # 结合你自己的目标掩码使用
)
loss = outputs.loss

实际生产微调中通常配合 Trainer/Seq2SeqTrainer 与掩码策略(例如对前缀部分置 -100 不计算损失),此处仅演示前向与 loss 的接线方式。

6.3 测试覆盖

tests/models/cpmant/test_modeling_cpmant.pyCpmAntModelTester 为骨架,覆盖了:基础前向(test_cpmant_model)、LM head(test_cpmant_lm_head_model)、masked/因果两种推理一致性(test_inference_masked_lm / test_inference_causal)、单句与 batch 生成(test_simple_generation / test_batch_generation),以及保留梯度/注意力输出等通用行为。若想在本地改动源码后验证回归,可在仓库根目录运行:

pytest tests/models/cpmant/test_modeling_cpmant.py -q

(tokenizer 测试需先安装 rjieba,见上。)


7. 工程实践建议与边界提示

  • 显存规划openbmb/cpm-ant-10b 是约 10B 参数模型,直接加载默认权重将占用可观显存;文档建议的压缩变体与 device_map="auto" 是低资源环境的可行选项。具体量化/压缩后的指标以官方发布页与 Hub 检查点卡片为准。
  • 依赖清单:模型推理依赖 torch;分词依赖 rjieba(缺失时 CpmAntTokenizer(...) 直接报 ImportError);device_map="auto" 依赖 accelerate。多机多卡场景可复用仓库 examples/pytorch/language-modeling 下的训练脚本流程。
  • 上下文长度position_bias_max_distance 默认 2048,即默认检查点训练时相对位置桶覆盖到约 2048 的序列上界;超长输入需自行验证外推表现。
  • API 兼容:本文所有类名与导入路径均以当前仓库 models/cpmant 目录为准;跨版本升级(尤其 v5 前后命名差异)请参考仓库根目录 MIGRATION_GUIDE_V5.md

综上,CPMAnt 的接入点集中在 configuration_cpmant.pymodeling_cpmant.pytokenization_cpmant.py 三份实现之中——理解“分词两级切分、输入 prompt 前缀、双桶分段位置编码、Pre-LN 门控 FFN 解码器”这四条主线,即可自由地在推理、微调与二次开发场景中驾驭这个 10B 中文模型。

登录后查看全文
热门项目推荐
相关项目推荐