深入浅出 Tokenization & Serialization:基于 data_engineering_book吃透LLM 开发中数据处理的核心环节
在大语言模型(LLM)的开发和应用链路中,Tokenization(分词) 和 Serialization(序列化) 是贯穿数据预处理、模型输入、结果存储的核心环节——前者决定了文本如何被模型“读懂”,后者则保障了数据在存储、传输、复用过程中的一致性。
本文将基于《Data Engineering Book》核心内容,拆解这两个核心概念,并结合 Hugging Face 生态完成实操演练,帮你吃透 LLM 数据处理的关键细节。
GitHub地址: github.com/datascale-a…
一、核心概念:为什么这两个环节不可替代?
1. Tokenization:文本到模型语言的“翻译官”
LLM 无法直接理解自然语言文本,必须将文本转换为离散的、可计算的 Token(标记)(本质是整数ID),这个转换过程就是 Tokenization。
核心特点 & 常见算法
- 核心目标:平衡“词汇表大小”和“文本压缩率”,让模型用最少的 Token 覆盖最多的语义。
- 主流算法:
- BPE(字节对编码):GPT/LLaMA 系列核心算法,从字节层面迭代合并高频字符对;
- WordPiece:BERT 系列使用,基于贪心算法拆分单词;
- Unigram:T5 系列使用,基于概率模型选择最优 Token 组合。
- 关键组件:
- 词汇表(Vocab):Token 与整数 ID 的映射表;
- 特殊 Token:
[PAD](填充)、[CLS](句子起始)、[SEP](句子分隔)、[UNK](未知字符)等。
2. Serialization:数据的“打包与解包”工具
Serialization 是将内存中的数据(比如分词后的 Token 列表、模型参数、处理后的数据集)转换为可存储、可传输的格式(如 JSON、Pickle、Protobuf、Arrow)的过程;反之则为 反序列化(Deserialization)。
在 LLM 场景中的核心应用
- 保存分词后的 Token 结果,避免重复预处理;
- 跨设备/跨框架传输数据集(比如 PyTorch ↔ TensorFlow);
- 持久化模型训练的中间状态(Checkpoint)。
二、实战演练:基于 Hugging Face 完成 Tokenization & Serialization
Hugging Face 的 transformers + tokenizers 库是 LLM 分词的事实标准,下面以 LLaMA-2 分词器为例,完成全流程实操。
1. 环境准备
# 安装依赖
!pip install transformers tokenizers datasets --quiet
# 导入核心库
from transformers import AutoTokenizer
import json
import pickle
from datasets import Dataset
2. 基础分词(Tokenization)实操
步骤1:加载预训练分词器
以 LLaMA-2 7B 分词器为例(需提前同意 Meta 授权,或替换为公开可访问的分词器如 bert-base-chinese):
# 加载分词器(替换为自己的路径/模型名)
tokenizer = AutoTokenizer.from_pretrained(
"meta-llama/Llama-2-7b-hf",
padding_side="right", # 右侧填充(LLaMA 推荐)
use_fast=True # 使用 Rust 加速的 fast tokenizer
)
# 补充特殊 Token(如果需要)
tokenizer.add_special_tokens({"pad_token": "[PAD]"})
步骤2:文本编码(分词核心操作)
# 待处理文本
texts = [
"LLM 分词和序列化是数据处理的核心环节!",
"你好,世界!Hello, World!"
]
# 编码(返回 Tensor/列表,支持批量处理)
encoded = tokenizer(
texts,
truncation=True, # 截断超长文本
padding="max_length", # 填充到最大长度
max_length=32, # 最大 Token 长度
return_tensors="pt" # 返回 PyTorch Tensor,可选 "tf"/"np"/None
)
# 查看结果
print("Token ID 列表:", encoded["input_ids"])
print("注意力掩码(Attention Mask):", encoded["attention_mask"])
print("解码回文本:", tokenizer.decode(encoded["input_ids"][0]))
关键参数说明
truncation=True:超过max_length的文本自动截断;padding:可选max_length(填充到指定长度)、longest(填充到批量中最长文本长度);return_tensors:指定返回格式,适配不同框架。
3. 分词结果的序列化 & 反序列化
方式1:JSON 序列化(通用、易读)
JSON 是跨语言的轻量级格式,适合存储简单的分词结果:
# 序列化:将 Token ID 转换为 JSON 字符串
serialized_json = json.dumps({
"input_ids": encoded["input_ids"].tolist(), # Tensor → 列表
"attention_mask": encoded["attention_mask"].tolist()
})
# 保存到文件
with open("tokenized_result.json", "w", encoding="utf-8") as f:
f.write(serialized_json)
# 反序列化:从 JSON 文件恢复
with open("tokenized_result.json", "r", encoding="utf-8") as f:
deserialized_json = json.load(f)
print("JSON 反序列化结果:", deserialized_json["input_ids"][0])
方式2:Pickle 序列化(高效、支持复杂对象)
Pickle 是 Python 原生格式,支持序列化任意 Python 对象(如分词器实例、批量处理的数据集),但仅支持 Python 环境:
# 序列化分词器实例(避免重复加载)
with open("llama_tokenizer.pkl", "wb") as f:
pickle.dump(tokenizer, f)
# 反序列化分词器
with open("llama_tokenizer.pkl", "rb") as f:
loaded_tokenizer = pickle.load(f)
# 验证:用反序列化的分词器解码
print("Pickle 加载后解码:", loaded_tokenizer.decode(deserialized_json["input_ids"][0]))
方式3:Dataset 序列化(大规模数据场景)
针对海量文本的分词结果,推荐用 Hugging Face datasets 库的 Arrow 格式(高效、可分片):
# 构造数据集
data = {"text": texts, "input_ids": encoded["input_ids"].tolist()}
dataset = Dataset.from_dict(data)
# 序列化保存到本地
dataset.save_to_disk("tokenized_dataset")
# 反序列化加载
loaded_dataset = Dataset.load_from_disk("tokenized_dataset")
print("Arrow 数据集加载结果:", loaded_dataset[0]["input_ids"])
4. 自定义分词器的序列化
如果训练了自定义分词器(比如基于自有语料扩展 LLaMA 词汇表),可直接保存/加载:
# 保存自定义分词器
tokenizer.save_pretrained("./custom_llama_tokenizer")
# 加载自定义分词器
custom_tokenizer = AutoTokenizer.from_pretrained("./custom_llama_tokenizer")
三、避坑指南 & 最佳实践
1. Tokenization 常见坑
- ❌ 词汇表不匹配:训练和推理用不同版本的分词器 → 导致 Token ID 映射错乱;
✅ 解决方案:将分词器与模型绑定发布,用
save_pretrained统一保存; - ❌ 特殊 Token 处理不当:比如 LLaMA 原生无
[PAD]Token,直接填充会报错; ✅ 解决方案:通过add_special_tokens补充,并更新分词器的词汇表; - ❌ 截断/填充方向错误:比如中文文本左侧填充导致语义错乱;
✅ 解决方案:根据模型要求设置
padding_side(LLaMA 用 right,BERT 用 left)。
2. Serialization 常见坑
- ❌ 用 Pickle 存储不可信数据:Pickle 存在安全风险,可能执行恶意代码; ✅ 解决方案:对外提供的数据集用 JSON/Arrow/Protobuf,仅内部临时存储用 Pickle;
- ❌ 序列化后数据体积过大:比如直接存储 Tensor → 占用大量磁盘;
✅ 解决方案:转换为列表后序列化,或用
datasets的压缩格式(如 zstd); - ❌ 跨框架序列化不兼容:比如 PyTorch Tensor 直接序列化后无法在 TensorFlow 加载; ✅ 解决方案:先转换为 numpy 数组,再序列化。
3. 最佳实践
- 缓存分词结果:对海量文本,预处理后序列化保存,避免训练时重复分词(节省算力);
- 统一分词器版本:在
requirements.txt中固定transformers/tokenizers版本; - 优先选择高效格式:大规模数据集用 Arrow/Parquet,小数据用 JSON,Python 内部用 Pickle;
- 验证序列化结果:反序列化后通过
decode验证 Token 与原文的一致性。
四、总结与延伸
Tokenization 是 LLM 理解文本的“第一道门槛”,其质量直接影响模型效果;Serialization 则是数据工程的“基建”,决定了数据处理的效率和可复用性。
延伸方向
- 自定义分词器训练:基于
tokenizers库训练适配自有场景的 BPE 分词器; - 分布式训练序列化:用
torch.distributed实现跨节点的数据集序列化传输; - 轻量级序列化:尝试 Protobuf 替代 JSON,进一步降低数据体积和解析耗时。
掌握 Tokenization 和 Serialization 的核心逻辑,不仅能提升 LLM 数据处理的效率,更能避免训练/推理过程中的“隐性 Bug”。希望本文能帮你夯实 LLM 开发的基础环节,欢迎访问 github.com/datascale-a… 获取完整代码和实战文档,也欢迎在仓库中交流经验, 觉得有帮助的朋友,欢迎点个 Star ⭐️ 支持一下!