从玩具数据到真实语料:内存优化、并行预分词、增量更新与时间-空间权衡

真实数据训练

在第二章,我们实现了基础 BPE 算法;在第三章,我们学习了 GPT 系列的预分词机制。现在,让我们把它们结合起来,在真实数据上训练一个 tokenizer。

本章我们将:

  1. 构建 baseline:结合第二章的 BPE 算法和第三章的预分词,支持文件输入
  2. 在 TinyStories 上测试:2GB 数据,32K 词表,看看 baseline 能否胜任
  3. 分析瓶颈:当数据量增大时,哪里会出问题?
  4. 逐步优化:分块预分词、增量更新、低频剪枝、检查点机制

1.1 Baseline 实现

结合前两章的内容,我们的 baseline 需要:

  1. 文件输入:读取大文件
  2. 预分词:使用第三章介绍的 GPT-2 风格正则分割
  3. 按 word 分组:利用重复的 words 加速训练

数据结构变化

与第二章不同,我们不再维护单一的 token 序列,而是记录每个 word 的频率:

# 第二章:单一序列
tokens = [72, 101, 108, 108, 111, ...]  # 整个文本
# 本章:按 word 分组,记录频率
byte_tokens_count = {
    (72, 101, 108, 108, 111): 1000,  # "Hello" 出现 1000 次
    (119, 111, 114, 108, 100): 500,  # "world" 出现 500 次
    ...
}

这样,当 “Hello” 在文本中出现 1000 次时,我们只需处理一次,用频率来加权。

频率加权

预分词后,相同的 word 会被合并,用计数表示频率。在统计 pair 频率时,需要考虑这个计数: