首页
/ Transformers 中的 CPM 中文预训练语言模型:GPT-2 架构与 Jieba-RS + SentencePiece 分词器解析

Transformers 中的 CPM 中文预训练语言模型:GPT-2 架构与 Jieba-RS + SentencePiece 分词器解析

2026-09-09 14:04:13作者:董宙帆

CPM(Chinese Pre-trained Language Model)是清华大学团队发布的面向中文的生成式预训练语言模型,本文基于本仓库的 CPM 模型文档(对应 英文版),结合源码实现,系统讲解 CPM 的模型背景、与 GPT-2 的关系,以及 CpmTokenizer / CpmTokenizerFast 两套分词器的设计原理、构造参数与实际用法。读完本文,你将掌握 CPM 分词器的完整工作流程,并能在中文生成任务中正确加载与使用它。

CPM 模型概述:面向中文的生成式预训练语言模型

CPM 模型由 Zhengyan Zhang、Xu Han、Hao Zhou、Pei Ke、Yuxian Gu、Deming Ye、Yujia Qin 等学者在论文 CPM: A Large-scale Generative Chinese Pre-trained Language Model 中提出(技术报告,2020-12-01 发布于 HF Papers,2021-04-10 贡献给 Hugging Face Transformers)。该模型由社区贡献者 canwenxu 提供,原始实现位于 TsinghuaAI/CPM-Generate。

论文摘要的核心事实如下:

  • 背景:GPT-3 凭借 1750 亿参数和 570GB 训练数据展现出强大的 few-shot(甚至 zero-shot)学习能力,但其训练语料以英文为主,且参数并未公开,难以直接应用于中文 NLP 任务。
  • CPM 的定位:CPM 在大规模中文训练数据上进行生成式预训练,拥有 26 亿参数100GB 中文训练数据,在当时是最大的中文预训练语言模型。
  • 能力:可支撑对话、作文生成、完形填空、语言理解等多种下游中文 NLP 任务;实验表明其在 few-shot(甚至 zero-shot)设定下表现良好。

需要说明的是,上述数字(26 亿参数、100GB 数据)来自论文摘要原文,属于论文披露的客观信息;是否"最大"仅指论文发表时的状况,不宜延伸为当前结论。

架构要点:与 GPT-2 相同的解码器架构

文档中的 Tip 明确说明:

CPM 的架构与 GPT-2 相同,区别仅在于分词(tokenization)方法。API 参考信息请参见 GPT-2 文档。

这意味着 CPM 采用 GPT-2 式的自回归解码器(causal LM)结构,使用方式上与 GPT-2 一脉相承。值得注意的是,当前仓库中 CPM 模块只实现了分词器,并未实现独立的 CpmModel 模型类——这一点可以从测试文件 test_tokenization_cpm.py 中的注释 "There is no CpmModel" 得到印证。因此在实际使用时,分词由 CpmTokenizer / CpmTokenizerFast 负责,而模型权重加载与生成推理则遵循 GPT-2 的用法(将 CPM 视为 GPT-2 架构的中文版本)。

仓库中 CPM 模块的文件结构如下:

CpmTokenizer:慢速分词器源码解析

CpmTokenizer 继承自 PreTrainedTokenizertokenization_cpm.py),其核心设计是基于 Jieba-RS(结巴分词的 Rust 实现,Python 包 rjiebaSentencePiece 的两段式分词:先由 Jieba-RS 完成中文词语切分,再由 SentencePiece 的 BPE/Unigram 子词模型切出子词。类注释中明确写道:"Runs pre-tokenization with Jieba-RS segmentation tool. It is used in CPM models."

依赖与词表文件

  • 词表文件:VOCAB_FILES_NAMES = {"vocab_file": "spiece.model"},即一个 SentencePiece 模型文件(.spm)。
  • 硬依赖:rjieba。两个分词器在初始化时都会尝试 import rjieba,若未安装则抛出带提示的 ModuleNotFoundError:"You need to install rjieba to use CpmTokenizer or CpmTokenizerFast."(见 tokenization_cpm.py)。安装方式为 pip install rjieba sentencepiece

构造参数一览

CpmTokenizer 的构造参数(tokenization_cpm.py)如下:

参数 默认值 说明
vocab_file 必填 SentencePiece 词表文件(.spm),用于实例化词表
do_lower_case False 分词前是否将输入转为小写
remove_space True 分词前是否去除首尾空格并合并多余空白
keep_accents False 是否保留重音符号(为 False 时按 NFKD 规范化去除组合字符)
bos_token "<s>" 预训练时使用的序列起始 token;注意构建带特殊 token 的序列时,序列开头实际用的是 cls_token
eos_token "</s>" 序列结束 token;构建序列时序列末尾实际是 sep_token
unk_token "<unk>" 词表外 token
sep_token "<sep>" 分隔 token,用于拼接多个序列
pad_token "<pad>" 填充 token,用于 batch 内长度对齐
cls_token "<cls>" 分类 token,位于序列首位
mask_token "<mask>" 掩码 token;构造时会被包装为 AddedToken(mask_token, lstrip=True, rstrip=False),即像普通词一样保留其前的空格
additional_special_tokens ["<eop>", "<eod>"] 额外特殊 token(从命名看应分别表示段落结束与文档结束,代码中未展开解释)
sp_model_kwargs None 透传给 sentencepiece.SentencePieceProcessor 的额外关键字参数

注意:源码 docstring 中 do_lower_case 标注的默认值是 True,但函数签名与 super().__init__ 传递的实际默认值是 False,实际行为以代码签名为准。

另外,该分词器将 _pad_token_type_id 设置为 3tokenization_cpm.py),这是 padding 位置的 token type id,与下面将提到的 segment id(0/1/2)区分开。

文本预处理流水线:preprocess_text

每次分词前,文本会依次经过 preprocess_texttokenization_cpm.py)处理:

  1. remove_space=True,先 strip() 再按空白切分后重新拼接,压缩多余空格;
  2. `` 替换为 "'' 替换为 "(中文语境中的引号统一);
  3. keep_accents=False,按 Unicode NFKD 规范化并删除组合字符(combining marks);
  4. do_lower_case=True,转为小写。

分词主流程:_tokenize 与数字逗号回切

_tokenizetokenization_cpm.py)的实现为:

  1. 调用 preprocess_text 得到规范化文本;
  2. 通过 self.sp_model.encode(text, out_type=str) 得到 SentencePiece 子词序列;
  3. 数字千分位逗号回切:对每个子词,若 len(piece) > 1 且以逗号结尾、且逗号前一位是数字(例如 1,000 被切成一个子词时),则把逗号之前的部分(去掉句首空格标记 )用 EncodeAsPieces 重新切分,再在末尾补回逗号。这样保证数字串与逗号能按语义正确拆分。

分词结果中,SPIECE_UNDERLINE)是 SentencePiece 的空格标记。反向拼接时,convert_tokens_to_string 会把 还原为空格并 strip()tokenization_cpm.py)。

特殊 token 序列格式与 token type ids

build_inputs_with_special_tokenstokenization_cpm.py)定义了输入序列的组装格式:

  • 单序列:X <sep> <cls>
  • 双序列(如文本分类、问答):A <sep> B <sep> <cls>

对应的 get_special_tokens_maskcreate_token_type_ids_from_sequences 生成的 segment id 为:第一段为 0,第二段为 1,末尾 <cls> 段为 2;padding 位置则为 _pad_token_type_id = 3

解码:还原空格与换行

CpmTokenizer 定义了一个字符映射表 self.translator = str.maketrans(" \n", "\u2582\u2583"),即 空格 → (U+2582)、换行 → (U+2583)。在 _decode 中(tokenization_cpm.py)会先调用父类解码,再删除普通空格、把 还原为空格、 还原为换行。这种"先占位再还原"的机制确保了中英文混合文本中空格信息在 token 化过程中不丢失。

词表保存

save_vocabularytokenization_cpm.py)支持将当前词表以 spiece.model 保存到指定目录(若原词表文件不存在,则通过 serialized_model_proto() 序列化写出)。

CpmTokenizerFast:基于 Tokenizer 的快速分词器

CpmTokenizerFast 继承自 PreTrainedTokenizerFasttokenization_cpm_fast.py),同样基于 Jieba-RS 与 SentencePiece,核心差异在于:

  • 词表文件:除 spiece.model 外还支持 tokenizer.jsonVOCAB_FILES_NAMES = {"vocab_file": "spiece.model", "tokenizer_file": "tokenizer.json"}),可直接从预构建的 Tokenizer 文件恢复,速度更快。
  • 批量编码前置处理_batch_encode_plustokenization_cpm_fast.py)会对每个输入先执行 self.jieba.cut(text, False) 完成 Jieba-RS 中文分词,再用 translator 将结果中的空格与换行替换为 / 占位符,拼接为带空格分隔的字符串后交给底层 Tokenizer 编码。这正是慢速分词器"空格/换行占位"思路在快速路径中的落地。
  • 特殊 token 与 segment idbuild_inputs_with_special_tokenscreate_token_type_ids_from_sequences 与慢速版完全一致,_pad_token_type_id 同样为 3
  • 解码_decode 与慢速版相同,删除普通空格、还原 →空格、→换行。
  • 保存限制save_vocabulary 要求 can_save_slow_tokenizer 为真,否则抛错提示无法回存慢速分词器所需信息。

实践:安装、加载与分词验证

环境准备

使用 CPM 分词器前需要安装两个依赖:

pip install rjieba sentencepiece

rjieba(Jieba-RS)是必需项,缺失时会直接报错并给出安装提示;sentencepiece 用于加载 spiece.model

加载与分词示例

from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("TsinghuaAI/CPM-Generate")

text = "Hugging Face大法好,谁用谁知道。"
tokens = tokenizer.tokenize(text)
print(tokens)
# ['▁Hu', 'gg', 'ing', '▁', '▂', '▁F', 'ace', '▁大法', '▁好', '▁', ',', '▁谁', '▁用', '▁谁', '▁知', '道', '▁', '。']

ids = tokenizer.encode(text)
print(tokenizer.decode(ids))

上述示例中的期望输出直接取自仓库测试 test_tokenization_cpm.py,可观察到:

  • 中文词("大法""好""谁""用""知道")被 Jieba-RS 正确切分,再由 SentencePiece 拆出子词;
  • "Hugging" 与 "Face" 之间的空格在 token 序列中体现为独立的 token,解码后还原为空格,实现了中英混排文本的信息无损往返;
  • 全角逗号","被归一化为半角 ,(对应 preprocess_text 与测试中的 normalized_text)。

测试还验证了 token → id 的转换(例如 ▁Hu → 13789)以及 decode 能还原出 "Hugging Face大法好,谁用谁知道。<unk>" 的规范化文本。

AutoTokenizer 自动映射

在自动加载体系 tokenization_auto.py 中,"cpm" 架构被映射到 CpmTokenizer(在 tokenizers 可用时),因此上述 AutoTokenizer.from_pretrained 写法对 CPM 系列 checkpoint 是开箱即用的。

小结

CPM 是本仓库中一个"模型架构复用 GPT-2、分词完全定制"的典型案例:它以 26 亿参数和 100GB 中文语料进行生成式预训练,而仓库内为其提供的核心资产是 CpmTokenizerCpmTokenizerFast 两套基于 Jieba-RS + SentencePiece 的中文分词器。理解 preprocess_text 的规范化流程、_tokenize 的数字逗号回切逻辑、▂/▃ 空格换行占位机制,以及单序列 X <sep> <cls>、双序列 A <sep> B <sep> <cls> 的特殊 token 格式,是正确使用和微调 CPM 系列模型的关键。相关实现与验证均可直接在 tokenization_cpm.pytokenization_cpm_fast.pytest_tokenization_cpm.py 中查阅。

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

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
docsdocs
暂无描述
Markdown
899
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
924
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.84 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
599
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.03 K
525
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
394