Transformers 中的 CPM 中文预训练语言模型:GPT-2 架构与 Jieba-RS + SentencePiece 分词器解析
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 模块的文件结构如下:
- src/transformers/models/cpm/tokenization_cpm.py:慢速分词器
CpmTokenizer - src/transformers/models/cpm/tokenization_cpm_fast.py:快速分词器
CpmTokenizerFast - src/transformers/models/cpm/init.py:模块惰性加载入口(
_LazyModule)
CpmTokenizer:慢速分词器源码解析
CpmTokenizer 继承自 PreTrainedTokenizer(tokenization_cpm.py),其核心设计是基于 Jieba-RS(结巴分词的 Rust 实现,Python 包 rjieba) 与 SentencePiece 的两段式分词:先由 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 设置为 3(tokenization_cpm.py),这是 padding 位置的 token type id,与下面将提到的 segment id(0/1/2)区分开。
文本预处理流水线:preprocess_text
每次分词前,文本会依次经过 preprocess_text(tokenization_cpm.py)处理:
- 若
remove_space=True,先strip()再按空白切分后重新拼接,压缩多余空格; - 将
``替换为"、''替换为"(中文语境中的引号统一); - 若
keep_accents=False,按 Unicode NFKD 规范化并删除组合字符(combining marks); - 若
do_lower_case=True,转为小写。
分词主流程:_tokenize 与数字逗号回切
_tokenize(tokenization_cpm.py)的实现为:
- 调用
preprocess_text得到规范化文本; - 通过
self.sp_model.encode(text, out_type=str)得到 SentencePiece 子词序列; - 数字千分位逗号回切:对每个子词,若
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_tokens(tokenization_cpm.py)定义了输入序列的组装格式:
- 单序列:
X <sep> <cls> - 双序列(如文本分类、问答):
A <sep> B <sep> <cls>
对应的 get_special_tokens_mask 与 create_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_vocabulary(tokenization_cpm.py)支持将当前词表以 spiece.model 保存到指定目录(若原词表文件不存在,则通过 serialized_model_proto() 序列化写出)。
CpmTokenizerFast:基于 Tokenizer 的快速分词器
CpmTokenizerFast 继承自 PreTrainedTokenizerFast(tokenization_cpm_fast.py),同样基于 Jieba-RS 与 SentencePiece,核心差异在于:
- 词表文件:除
spiece.model外还支持tokenizer.json(VOCAB_FILES_NAMES = {"vocab_file": "spiece.model", "tokenizer_file": "tokenizer.json"}),可直接从预构建的 Tokenizer 文件恢复,速度更快。 - 批量编码前置处理:
_batch_encode_plus(tokenization_cpm_fast.py)会对每个输入先执行self.jieba.cut(text, False)完成 Jieba-RS 中文分词,再用translator将结果中的空格与换行替换为▂/▃占位符,拼接为带空格分隔的字符串后交给底层 Tokenizer 编码。这正是慢速分词器"空格/换行占位"思路在快速路径中的落地。 - 特殊 token 与 segment id:
build_inputs_with_special_tokens、create_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 中文语料进行生成式预训练,而仓库内为其提供的核心资产是 CpmTokenizer 与 CpmTokenizerFast 两套基于 Jieba-RS + SentencePiece 的中文分词器。理解 preprocess_text 的规范化流程、_tokenize 的数字逗号回切逻辑、▂/▃ 空格换行占位机制,以及单序列 X <sep> <cls>、双序列 A <sep> B <sep> <cls> 的特殊 token 格式,是正确使用和微调 CPM 系列模型的关键。相关实现与验证均可直接在 tokenization_cpm.py、tokenization_cpm_fast.py 与 test_tokenization_cpm.py 中查阅。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0631
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
video-shotcraftAI宣传片skill,使用 Remotion 制作电影级产品视频:提供106 张镜头配方卡和可复用的视频魔板。适用于 Claude Code 与 Codex以及所有其他智能体Markdown00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python09
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00