深入浅出 Tokenization & Serialization | 基于 data_engineering_book吃透LLM 开发中数据处理的核心环节

16 阅读6分钟

深入浅出 Tokenization & Serialization:基于 data_engineering_book吃透LLM 开发中数据处理的核心环节

在大语言模型(LLM)的开发和应用链路中,Tokenization(分词)Serialization(序列化) 是贯穿数据预处理、模型输入、结果存储的核心环节——前者决定了文本如何被模型“读懂”,后者则保障了数据在存储、传输、复用过程中的一致性。

本文将基于《Data Engineering Book》核心内容,拆解这两个核心概念,并结合 Hugging Face 生态完成实操演练,帮你吃透 LLM 数据处理的关键细节。

图5_6_本章知识结构.png

GitHub地址: github.com/datascale-a…

在线链接:datascale-ai.github.io/

一、核心概念:为什么这两个环节不可替代?

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 ⭐️ 支持一下!