第 3 讲:预训练数据的整理与分词

Large Language Models: Methods and Applications · Lecture 03。个人学习讲义与配套实验,按主题整理并持续修订。

课程源码 ZIP ↓

个人整理的学习笔记,非官方讲义;课程来源与整理说明见课程介绍。配套示例未逐一运行验证。

本篇目录

对应原课:CMU 11-667 Fall 2025 · Lecture 3 · Tue 2-Sep-2025 · Pre-training data curation and tokenization(Jaromir Savelka、Yubin Kim) 原课 slides:Pre-training Data Curation and Text Tokenization(课表上标为 pre-training data)。slides 的最后一节”预训练目标”已经放在第 2 讲 §6 阅读材料:Dodge et al. (2021), Documenting Large Webtext Corpora: A Case Study on the Colossal Clean Crawled Corpus;Longpre et al. (2024), Consent in Crisis: The Rapid Decline of the AI Data Commons;Overwijk et al. (2022), ClueWeb22: 10 Billion Web Documents with Visual and Semantic Information 配套代码:code/lecture03/(4 个模块 + 7 个演示脚本;最长的一个首次运行约 75 秒) 本讲的写法:骨架来自原课 slides;“小互联网”上的爬取、清洗、去重、去污染和训练实验是补充的。所有数字都来自配套代码的实际输出。


目录


0. 本讲导读

0.1 这一讲要回答的问题

第 2 讲 §5.2 有一个结论:我们的小 Transformer 离真实分布还差的那一截困惑度,74% 来自训练数据里刻意缺掉的 5 种搭配。结构决定模型能学什么,数据决定它实际学到什么。

真实的 LLM 用几万亿个 token 预训练。这些 token 从哪里来?slides 用一张图概括了”数据预处理”:把原始数据(不管它最初是什么格式)变成能喂给神经网络的形式。这一讲沿着这条管线走一遍:

 网上的内容 ──► 爬虫下载一部分 ──► 抽取文字 ──► 过滤与清洗 ──► 去重 / 去污染 ──► 分词 ──► 打包成批次 ──► 模型
   §2             §2               §3          §3              §4、§5           §8         §9

要回答的问题:

  1. 为什么几乎所有 LLM 都用网页数据?网页数据有哪些毛病?
  2. “清洗”具体删了什么?C4、Gopher 的规则、质量分类器分别是怎么回事?它们会误删什么、误删谁?
  3. 怎样在几十亿个页面里高效地找出近似重复?测试集泄露到训练数据里怎么办?
  4. 清洗到底值不值得?——花园世界让我们能便宜地回答这个通常非常昂贵的问题。
  5. 分词器是怎样从数据里训练出来的?词表多大合适?为什么有的语言”更贵”?
  6. 预训练论文里”批大小 400 万”指的是什么?

0.2 学完这一讲你应该能

  • 说出预训练数据的主要来源,以及网页数据在可得性、噪声、偏差、版权与授权上的问题;
  • 复述 C4 的 7 条过滤规则,解释 Gopher 规则、质量分类器、“被可信来源链接”这几类信号的思路和局限;
  • 解释 MinHash 为什么能估计 Jaccard 相似度、LSH 的 S 形曲线怎样由 bb、rr 决定;
  • 解释测试集污染的检测方法和它的两难;
  • 手算 BPE 的合并过程,说出词表大小的取舍、“分词税”和”从未训练过的 token”的来历;
  • 区分补齐与打包,知道文档掩码在解决什么问题,知道”批大小”通常以 token 计。

0.3 本讲的实验平台:一个”知道标准答案”的小互联网

在真实数据上很难回答”这个过滤器删对了多少、删错了多少”——没有人知道几十亿个页面里哪些”本来就该留下”。所以配套代码用第 1 讲的花园世界造了一个小互联网(web_world.py):191 个站点、3020 个页面、21,731 条链接,每个页面都带真实标签。

种类页面内容好文字?
百科 encyclopedia200干净的花园世界文字(相当于 Wikipedia)是
博客 blog900花园世界文字 + 大量网页样板(导航栏、cookie 提示、侧栏、页脚、页尾的”Thanks for reading…”)是
方言论坛 dialect225用花园方言写的好文字(见下)是
外语站 floral300用虚构语言 Floral 写的好文字(主-宾-谓语序,带 ø、å、é)是
镜像站 mirror150复制博客页面:一半原样复制,一半改掉一句是(但是副本)
题库站 leak20原样贴出第 1 讲测试集的前 200 篇文档是(但是泄露)
垃圾页 spam800关键词堆砌、大写感叹号、重复行,彼此大量互链否
商品列表 shop300一行一个商品和价格否
lorem / js / code各 25占位文字、“请启用 JavaScript”、带 CSS/JS 代码的教程否
骂人页 toxic50几句正常的话夹着用虚构屏蔽词骂人的句子否

花园方言是一个虚构的社区变体,语法完全规则,只是和”标准花园语”不同:第三人称单数动词不加 s(she pick the plum,很多真实英语方言都有这个特征),并用 grut 表示”很”(the grut sweet plum)。在这个虚构世界里,grut 在别处也被用来骂人,于是被收进了一份”屏蔽词表”(zorp、blick、snerk、grut,全部是虚构的占位词)。这个设定是为了在可控的条件下复现 Dodge et al. (2021) 在 C4 上的发现(§3.7)。


1. 预训练想要什么样的数据

1.1 预训练的目标

slides 把预训练的目标概括为三条:让语言模型

  1. 学会自然语言的结构;
  2. 学到训练数据里编码的人类对世界的理解;
  3. 成为训练数据的一个极好的模拟器。

第三条值得多想一下:语言模型的训练目标就是”模仿训练数据的分布”(第 1 讲的最大似然)。数据里有什么,它就模仿什么——包括错误、偏见、垃圾和重复。所以理想的预训练数据要大,要高质量、干净、多样:书、维基百科、新闻、科学论文……

1.2 理想与现实

干净、格式规整的文字确实存在:维基百科、书、为经典 NLP 任务整理的数据集,以及英语、法语这类”高资源语言”的文本。但 slides 说得很直接:在真实系统里,“文字都干净整齐”的完美世界是达不到的。原因是另一条经验规律:

数据几乎总是越多越好。 slides 引用了 RoBERTa(Liu et al. 2019)的消融:同样的 BERT-Large 结构,

预训练设置数据量批大小步数MNLI-mSQuAD 1.1 F1
Wiki + Books16 GB8K100K89.093.6
+ 额外数据160 GB8K100K89.394.0
+ 训练更多步160 GB8K300K90.094.4
+ 再多训练160 GB8K500K90.294.6

同样的模型、同样的步数,数据多十倍就更好;数据多了之后,训练更多步也继续有帮助。slides 的评价是:这类收益比很多”更花哨”的改进都大。第 12 讲的规模定律会把这条经验变成公式。

于是问题变成:去哪里找几万亿个 token? 实际上唯一可行的来源是网页:它是数字时代通用领域人类知识的首选去处,体量巨大,增长速度不太可能比算力慢,而且公开可得。可是高质量的数据终会用完,而网页数据很难打交道:

  • 版权和使用限制可能极其复杂(§7.2);
  • 数据有噪声、脏、有偏差;
  • 混着大量自动生成的文字——不只是 LLM 生成的,还有海量模板化的文字。

1.3 网页数据的三步

slides 把网页数据的流水线分成三步:

  1. 内容被发布到网上;
  2. 网络爬虫找到并下载其中一部分;
  3. 数据被过滤和清洗。

每一步都会让”最终的训练数据”和”人类真实的语言”之间多一层偏差。下面依次看。


2. 网上有什么,爬虫爬到什么

2.1 第一层偏差:网上有什么

slides 列了”内容被发布到网上”这一步本身的问题:

  • 网上有什么本身就有偏差:时间上偏向最近(recency bias)、人群上偏向能上网、爱写东西的人(demographic bias)、语言上偏向英语等少数几种语言;
  • 网页不是静态的 HTML:CSS、JavaScript、交互、响应式设计,一个页面常常牵涉二十多个次级 URL、iframe;
  • 什么才算”内容”? 广告、推荐、导航栏、图片、视频、表格、垃圾信息……

代码 01 §1 统计了小互联网里”正文占 HTML 的比例”:百科页面 66.0%,博客 46.7%,方言论坛 43.9%,商品列表 35.8%,“请启用 JavaScript”的壳页面只有 15.4%。即使是好页面,HTML 的一半左右也是脚本、样式、导航栏、cookie 提示、侧栏和页脚。全网 3020 个页面里,好文字页面 1795 个(59%)。

2.2 第二层偏差:爬虫爬到什么

爬虫的基本流程(slides):从一组种子 URL 出发,沿着页面上的超链接向外探索,系统地下载每个页面、抽取原始文字。难点有三:

  • 怎样高效地收集大量种子 URL;
  • 怎样挑出”高质量”的 URL、跳过”坏”的——有些一目了然(垃圾、不安全),有些很难判断或者有争议(例如某些偏见);
  • 怎样保持更新:每个月的算力预算是固定的,是爬新页面,还是重爬可能已经变化的旧页面?

Common Crawl 是这一步最重要的公共基础设施(slides):一个非营利组织,从 2008 年起每月(或每两月)发布一次大规模爬取,累计数 PB。它从一组受欢迎、高质量、可信的种子网站(政府、教育机构、热门域名)出发,每月发现约 5000 亿个链接,其中 250 亿个以上是不重复的;但每次只挑约 30 亿个 URL 真正下载——预算远远小于发现的 URL 数。挑哪些,经历过几个阶段:2008–2012 年用自己的 PageRank;2012–2015 年用搜索引擎 blekko 捐赠的 220 亿个页面的排名;2016 年起用 Alexa 等排名和零星捐赠的种子;2017 年至今用基于自己网页图的排名(PageRank 和中心性),用过去三个月的数据计算重要性来引导接下来三个月的爬取,并限制每个域名的 URL 数。

slides 对 Common Crawl 的评价:它是唯一一个这种规模的公开爬取(虽然仍远不及商业搜索引擎);多年的存档让人可以从中”攒出”低资源语言的数据。缺点是每次约 30 亿个文档,只覆盖了网络的一小部分;每月都从种子重新开始,规模长不上去;URL 的分布被排序策略、每域名上限、甚至爬虫机器(和人)的地理位置扭曲了。

代码 01 §2 在小互联网上模拟了这件事:从 5 个百科首页和 2 个博客首页出发,预算 1000 个页面(全网的 33%),比较五种”先爬谁”的策略。优先级只用已经爬到的页面里的链接来算——爬虫看不到还没下载的页面。

策略好文字垃圾站点数百科博客方言外语镜像
全网(参照)59.4%40.6%191200900225300150
广度优先 BFS83.4%16.6%161176538135845
按入链数94.2%5.8%901287980111
按入链站点数 + 每站 ≤ 10 页82.9%17.1%154505891413735
聚焦爬取(好页面的链接才算票)96.2%3.8%92114841052
聚焦爬取 + 每站 ≤ 10 页85.4%14.6%16650600793490

几点观察:

  • 爬取顺序显著改变数据集的组成。 即使最朴素的 BFS,好文字也从全网的 59% 提高到 83%——种子是好页面,好页面倾向于链接好页面。
  • 质量和多样性此消彼长。 按入链数和聚焦爬取把好文字的比例推到 94%~96%,代价是预算集中到 90 个左右被大量链接的站点上(主要是博客),方言论坛一页都没爬到。加上”每站上限”后,站点数回到 150 以上,方言和外语的覆盖回升,垃圾比例也回到 15%~17%。Common Crawl 限制每个域名的 URL 数,正是在用一点质量换多样性。
  • 一个没发生的现象。 原本以为”按入链数”会被互相链接的垃圾站(link farm)吸走——垃圾站之间的链接极多。实际上,好页面几乎不链接垃圾站,爬虫从好种子出发,就一直没进入那片区域。真实世界的 link farm 会想方设法从好页面获得链接(比如在评论区留链接),这正是搜索引擎和爬虫的长期对抗。
  • “网上有什么”和”爬到了什么”之间已经有一层偏差。 被链接得少的社区(方言论坛、外语站)在多数策略下都被低估。后面的过滤还会再叠加一层。

3. 过滤与清洗

3.1 为什么要清洗、清洗什么

slides 列了清洗的三个目的:

  1. 删掉嘈杂、垃圾、模板化、支离破碎的文字——它们缺少实现预训练目标所需的内容;
  2. 从海量候选里挑出质量更高的文字——预训练的算力有限,希望花在更好的文字上;
  3. 避开有毒和有偏见的内容——不适合工作场所的内容、带强烈偏见的文字。

识别高质量内容的方法有三类:基于规则的启发式(原课 HW1 要做的就是这个)、与已知高质量指标的接近程度(例如在 Reddit 上得到高赞的网站、被 Wikipedia 引用的网站)、以及分类器(例如用 Wikipedia 当正例、随机网页当负例训练一个质量分类器,毒性分类器也很常见)。

清洗本身也有难题:在什么粒度上过滤——词、句、段、还是整篇文档?什么才算”高质量”、“无毒”?过滤器对多种语言都有效吗?它们对所有人群一视同仁吗? 以及,消融预训练数据的决定非常昂贵(要回答”这一步值不值得”,只能训练模型来看)。下面在小互联网上把这些问题一个个变成数字。

3.2 抽取正文

Common Crawl 提供三种文件:WARC(原始的 HTTP 响应,含 HTML)、WAT(元数据)和 WET(抽取出来的纯文字)。WET 基本上就是”页面上的全部可见文字”,没有区分正文和样板。代码 02 §1 打印了一个博客页面的”全部可见文字”:

  My garden diary
  This site uses cookies to improve your experience. Accept
  Garden | Recipes | Home | Contact | Login
  My garden diary
✓ He grows the carrot in the morning. My friend grows the lemon. I grow the leek. ...
✓ ...(正文的几段)
✓ Thanks for reading. Leave a comment below. Share this post with your friends.
  Popular posts: Daisy care | Growing tulips | Hen houses
  Copyright 2024 blog-00.example. All rights reserved. Privacy Policy | Terms of Use

标 ✓ 的行属于 <main> 里的段落。博客类页面(博客、方言、镜像)的全部可见文字里,35.4% 的字符是样板。专门的正文抽取工具(trafilatura、resiliparse 等)能去掉大部分样板——但注意 Thanks for reading… 这一段也在 <main> 里,抽取工具认不出它是样板,后面要靠去重(§4)。slides 提到 ClueWeb22(Overwijk et al. 2022)的一大优势正是它用了商业搜索引擎级别的抽取工具(同时保留了页面的视觉信息和语义标注);做好正文抽取需要大量工程和资源,这本身就是一些闭源 LLM 的竞争优势。

3.3 语言识别

多数数据集先按语言分开。最常用的工具是 fastText 的语言识别模型:本质上是”字符 n-gram 特征 + 线性分类器”。配套代码实现了一个最小版本:字符三元组朴素贝叶斯,逐行判断语言,一页的”置信度”是主要语言的字符占比(cleaning.py 的 LangID)。FineWeb 只保留英语置信度 ≥ 0.65 的页面,这里用同样的阈值。代码 02 §2:

种类抽取方式判为英语判为 Floral平均置信度英语且 ≥ 0.65
博客全部文字90001.000900
方言全部文字22501.000225
Floral全部文字232770.6720
Floral只取正文03001.0000
lorem全部文字2500.97225
  • Floral 页面用”全部文字”时,英语的导航栏、cookie 提示、页脚占了三成左右的字符,23 个页面被判成了英语,但置信度都不高,0.65 的阈值把这些”混合”页面挡在了外面。阈值决定了混合语言页面的去留——在真实网络上,大量页面就是混合的(英语的界面、本地语言的内容),低资源语言的数据尤其容易在这一步丢失。
  • 方言被正确地判为英语。
  • lorem ipsum 也被判为英语——这个识别器只认识两种语言,没有”其他”选项。真实的识别器会把它判成拉丁语,但”不在任何已知语言里”的文字照样会被硬塞进某个类别。

3.4 C4 的规则

C4(Colossal Clean Crawled Corpus)是 Raffel et al. (2020) 为训练 T5 构建的数据集,基于 Common Crawl 2019 年 4 月的一次爬取,用启发式规则过滤后得到约 750 GB 相当干净的英语文字。slides 列出了它的规则:

  1. 从 Common Crawl 官方抽取的文字(WET)开始;
  2. 只保留以句末标点结尾的行;
  3. 丢掉少于 5 句的页面;
  4. 只保留至少 3 个词的行;
  5. 删掉含 “javascript” 一词的行;
  6. 删掉含有以下任何一项的页面:(a) 有毒词表里的任何一个词,(b) “lorem ipsum”,(c) ”{”;
  7. 在”三句话片段”的级别上去重。

代码 02 §3 对通过语言识别的 2720 个页面(全部可见文字)应用规则 2–6(规则 7 见 §4)。行级规则一共删掉了 20,009 行”没有句末标点”的行和 25 行含 javascript 的行。按页面种类:

种类页面保留整页删除的理由
百科200199少于 5 句 1
博客900900
方言22521屏蔽词 201,少于 5 句 3
镜像150150
题库2020
垃圾800636少于 5 句 164
商品3000少于 5 句 300
lorem250lorem ipsum 25
js250少于 5 句 25
代码250含 { 25
骂人500屏蔽词 50

几点观察:

  • “以句末标点结尾”这条看似粗糙的行级规则,其实是一个正文抽取器:导航栏、cookie 提示、标题、侧栏、页脚都不以句号结尾。好页面保留下来的文字里,100.0% 是正文。代价是它也会删掉列表、诗歌、表格、代码这类不以句号结尾的好内容(练习 8)。
  • 垃圾页有 636 页活了下来:Click here to win a free goat!!! 以感叹号结尾,词数也够。C4 的规则只看”格式像不像句子”,不看”内容是不是垃圾”。
  • 每条规则都有误删:好页面的正文字符只保留了 87.5%,其余随”少于 5 句”或屏蔽词整页删除。
  • 屏蔽词删掉的方言页面(201 页)比骂人页面(50 页)还多。 这是 §3.7 的主题。

3.5 Gopher 的规则,以及规则的”移植”

DeepMind 为 Gopher 构建 MassiveText 时(Rae et al. 2021)用了另一套质量规则,FineWeb 等后来的数据集大量沿用:词数在 50 到 10 万之间;平均词长在 3 到 10 个字符之间;”#“和”…”与词数之比不超过 0.1;以省略号结尾的行不超过 30%;至少 80% 的词含有字母;至少出现 2 个停用词(the、be、to、of、and、that、have、with);重复行、重复段落、高频 n-gram 不能太多。停用词规则的直觉是:自然语言里功能词无处不在,一页文字里连 the、and 都没有,多半是列表、代码或乱码。

代码 02 §5 对”只取正文”的文字应用这些规则,并且对比了两份停用词表:

种类页面原文的英语停用词表:通过按花园语重挑的功能词:通过
百科200149(停用词不足 45)188
博客900835835
方言225139(停用词不足 63)179
垃圾80000
商品30000
代码252525

(其余不通过的主要原因是”词数少于 50”。)

照搬英语停用词表的 Gopher 规则在花园语上误删了大量好页面:花园语几乎没有 and、to、of、that。更有意思的是博客为什么没被误删——它们页尾那段样板”… with your friends”里有个 with。一条规则的效果,可能取决于和质量毫不相关的特征。改用按花园语重挑的功能词(the、with、from、in、every、own)之后,误删基本消失。真实世界里,FineWeb-2 把流水线扩展到上千种语言时,就为每种语言重新估计了停用词和各项阈值。规则是按某种语言、某类网页调出来的,换一个领域就要重新校准。

另外注意:垃圾页在这里全军覆没(停用词不足 800、重复行过多 715),代码页却全部通过——Gopher 规则和 C4 规则擅长抓的东西不同,所以真实流水线会把几套规则叠起来用。

3.6 质量分类器与”被可信来源链接”

分类器。GPT-3 用 WebText、Wikipedia 和书当正例,未经过滤的 Common Crawl 当负例,训练了一个逻辑回归分类器;Llama、FineWeb-Edu 等也用过类似的做法(FineWeb-Edu 用 LLM 给网页的”教育价值”打分,再训练分类器)。代码 03 实现了最简单的版本:特征是小写后的 1-gram 和 2-gram,用 CRC32 哈希到 16384 维(“哈希技巧”,fastText 的质量分类器也是这个思路),模型是带 L2 正则的逻辑回归。正例:100 个百科页面;负例:随机抽 500 个网页——其中 61% 本来就是好页面(博客、外语、百科、镜像、方言),GPT-3 的负例也是这样”随便什么网页”。正例权重取 5,让两类同样重要。

在训练没用到的 2195 个英语页面上,分数区分”好 / 不好”(真实标签)的 AUC = 0.959。100 个正例就够了,这正是分类器流行的原因。但它到底学到了什么?

种类直接用在正文上:平均分用在规则过滤 + 去重之后:平均分(中位数)
百科0.7540.771(0.789)
博客0.2190.749(0.760)
方言0.6180.646(0.652)
镜像0.2140.278(0.267)
垃圾0.019—(已被规则删光)
骂人0.2470.311(0.304)

左边一列出乎意料:直接用在正文上时,博客的分数比方言论坛还低。权重最小的 10 个特征是 a、this、post、below、leave、leave a、below .、share、thanks、. thanks——全部来自博客页尾那段 Thanks for reading. Leave a comment below. Share this post with your friends.。它在负例里出现了上百次,在正例里一次也没有,于是成了”不像百科”最强的信号。分类器学到的是”像不像正例”,而样板文字恰好是最容易抓住的差别。

右边一列是更现实的用法:先用规则过滤和去重(§4 的三句片段去重会删掉这段重复的样板),再用分类器。这时博客回到了接近百科的分数,而方言论坛系统性地低一截。权重告诉我们原因:方言词 grut 的权重是 −0.893;二元组 she pick(方言)−0.189,she picks(标准)+0.454。这些特征和”质量”毫无关系,只和”正例里有没有”有关——正例里从来没有 grut,也没有不加 s 的第三人称动词。

阈值越严,差距越大(代码 03 §4,只统计训练没用到的页面):

种类阈值 0.50.70.75只留前 50%帕累托采样
百科100%88%66%66%21%
博客99%78%57%58%16%
方言96%27%3%5%10%

“Wikipedia 当正例”等于把某一类人写的某一种文字当成了质量的定义。GPT-3 没有用硬阈值,而是用帕累托采样:当 np.random.pareto(9) > 1 - 分数 时保留文档。分数越高越可能保留,低分文档也有一定机会留下——用一点质量换多样性。这里的分数都在 0.6~0.8 之间,所以整体保留比例不高、三类之间差得也没那么多。

“被可信来源链接”。GPT-2 的 WebText 只收集 Reddit 上获得至少 3 个 karma 的外链(“有人觉得值得分享”),4500 万个链接去重后剩约 800 万个页面。好处是容易从一个公共来源收集大量 URL,而且每个 URL 都经过了人的投票;坏处是规模有限、Reddit 本身不是互联网最干净的角落,而且 Reddit 后来禁止用它的数据预训练 LLM(slides)。代码 03 §5 的类比:被百科页面链接过的非百科页面有 452 个,其中 87.4% 是好页面;但它们只覆盖了非百科英语好页面的 30.5%,其中方言论坛只有 5 页。精度高、召回低,而且继承了”可信来源愿意链接谁”的偏好。

3.7 屏蔽词与”谁的英语被留下”

回到 §3.4 表格里最刺眼的一行。代码 02 §4:

种类因屏蔽词被整页删除
博客0 / 900(0%)
方言论坛201 / 225(89%)
骂人页50 / 50(100%)

方言用 grut 表示”很”,屏蔽词表却把 grut 当成骂人的话。被屏蔽词删掉的页面里,好页面比真正的骂人页面还多;而且误删不是随机的,而是集中在一个群体身上。

这正是本讲阅读材料 Dodge et al. (2021) 在真实的 C4 上发现的。他们第一次系统地”文档化”了 C4:

  • 数据从哪来:有大量意想不到的来源,比如专利和美国军方网站;
  • 数据里有什么:机器生成的文字(例如机器翻译系统的输出),以及其他 NLP 基准的评估样本(§5);
  • 过滤器删了什么:屏蔽词过滤不成比例地删掉了来自少数群体和关于少数群体的文字——例如提到 LGBTQ+ 身份的文档,以及用非裔美国人英语、西班牙语裔英语写的文字,被删掉的比例明显更高。

他们的建议(slides):报告网站的元数据;检查基准污染;检查潜在的偏见;不要用屏蔽词表过滤。更一般地说:大型数据集应该附带”datasheet”式的文档——数据从哪来、怎么处理的、删了什么、可能的偏差——让使用者知道自己在用什么。

代码 02 §6 把整条流水线(抽取 → 语言识别 → C4 → Gopher → 三句片段去重 → MinHash 去重)跑了两遍,一遍含屏蔽词、一遍不含:

最终页面好页面占比英语好页面的召回博客留下方言论坛留下骂人页留下
含屏蔽词1124100.0%75.2%93%4%0
不含屏蔽词131498.4%86.5%93%80%21

去掉屏蔽词之后,方言论坛回来了,骂人页面也回来了 21 页(其余的被 Gopher 的”少于 50 词”删掉)。过滤器在”删干净”和”删公平”之间要做取舍,这类取舍应该被记录下来。§6 会看到这个取舍在模型上的后果。


4. 去重

4.1 为什么要去重

网上的重复多得惊人:镜像站、转载、模板、同一篇新闻被几百个站点抄。几个真实的数字(slides 与本讲阅读):Toronto BookCorpus 最初报告有 11,038 本书,后来发现大量重复,不重复的只有 7,185 本;WebText 的 4500 万个链接去重后只剩约 800 万个页面;Lee et al. (2022) 在 C4 里发现一个 61 个词的英语句子重复了 6 万多次。

重复的害处:

  • 浪费算力:同样的文字学了很多遍;
  • 记忆:Lee et al. 发现,在未去重的数据上训练的模型,无提示生成的文字里超过 1% 是逐字照抄训练数据的;去重后,模型吐出记忆文字的频率降低了十倍,达到同样精度需要的训练步数也更少;
  • 评估失真:训练集和验证集的重叠会让分数虚高(他们发现标准数据集里超过 4% 的验证样本和训练集重叠,§5)。

4.2 精确去重和片段去重

最简单的是精确去重:把文字规范化(小写、去标点、合并空白)后算哈希,哈希相同的只留一份。它只能抓到一字不差的副本。

C4 的第 7 条规则是片段去重:任何”连续三句话”的片段,如果在整个数据集里出现了不止一次,只保留第一次出现。它擅长删掉跨页面重复的样板段落:代码 04 §4,900 个博客页尾的 Thanks for reading… 三句话,去重后只在 1 页里保留——这是页面级去重做不到的。

4.3 MinHash:用几十个整数估计相似度

近似重复(改了一句、换了日期、加了广告)需要一种”相似度”。常用的定义是把文档表示成 shingle 的集合(连续 kk 个词,这里 k=5k = 5),两篇文档的相似度是集合的 Jaccard 系数:

J(A,B)=∣A∩B∣∣A∪B∣J(A, B) = \frac{|A \cap B|}{|A \cup B|}

改掉一句话只影响包含那句话的少数几个 shingle,JJ 仍然很高:代码 04 §1 的例子,一个镜像页只换了一句,Jaccard 是 0.899,但精确哈希完全不同。

几十亿篇文档两两算 Jaccard 是不可能的。MinHash 的关键性质是:对一个随机的哈希函数 hh,

P[min⁡x∈Ah(x)=min⁡x∈Bh(x)]=J(A,B)P\left[\min_{x \in A} h(x) = \min_{x \in B} h(x)\right] = J(A, B)

(证明见练习 2。)所以用 kk 个独立的哈希函数,每篇文档只存 kk 个最小值(“签名”);两个签名里”相同位置取值相同”的比例就是 JJ 的无偏估计,标准差约 J(1−J)/k\sqrt{J(1-J)/k}。代码 04 §2 在 300 对相似度从 0 到 1 的文档上验证:

签名长度 kk平均绝对误差最大误差理论标准差(J=0.5J = 0.5)
160.0600.3330.125
640.0310.1660.062
2560.0150.0660.031

签名长度每增加到 4 倍,误差减半——和”抛 kk 次硬币估计一个概率”完全一样。

4.4 LSH:把”两两比较”变成”查桶”

有了签名,还是不能两两比较。**局部敏感哈希(LSH)**的”分带”技巧:把长 b×rb \times r 的签名切成 bb 段(band)、每段 rr 个数;只要有一段完全相同,两篇文档就成为”候选对”,只对候选对精确比较。Jaccard 为 ss 的一对成为候选的概率是

P(候选)=1−(1−sr)bP(\text{候选}) = 1 - (1 - s^r)^b

一条 S 形曲线,陡升的位置大约在 (1/b)1/r(1/b)^{1/r}。代码 04 §3 用实测验证:

真实 Jaccardb=16,r=8b=16, r=8 实测理论b=32,r=4b=32, r=4 实测理论
0.0~0.20.0%0.0%0.6%0.1%
0.4~0.62.4%5.6%85.4%86.1%
0.6~0.735.3%37.2%100%99.7%
0.7~0.876.9%81.7%100%100%
0.8~0.9100%98.2%100%100%

(理论值用区间内真实 Jaccard 的平均值代入,所以只能大致对上。)rr 越大,曲线越陡、阈值越高;bb 越大,阈值越低。调 (b,r)(b, r) 就是在漏报和误报之间选位置,而计算量只和”每篇文档落进几个桶”有关,与文档总数成线性关系。

4.5 在小互联网上去重

代码 04 §4 只看英语好页面,比较三种方法找到了多少镜像页(150 个镜像中,71 个原样复制、79 个改了一句):

方法删掉的页面其中镜像误删非镜像
精确去重71710
MinHash + LSH(J≥0.8J \ge 0.8)1351350
C4 三句片段去重71710
  • 精确去重只抓到原样复制的;MinHash 还抓到了大部分改过一句的近似副本,没有误删;
  • 三句片段去重处理整页副本时会留下碎片:79 个近似镜像没被整页删掉,而是各剩下了平均 1.0 句——被换掉的那一句,比如孤零零的 We grow the carrot from our own garden.。这种支离破碎的文字留在数据集里没什么好处;
  • 顺序很重要:先做 MinHash 页面级去重、再做片段去重,残留的镜像碎片从 79 个降到 15 个。

4.6 规模化的去重

真实数据集的去重在几十亿篇文档上进行:FineWeb 对每个 Common Crawl 快照分别做 MinHash 去重(他们发现跨快照的全局去重反而会让数据变差,因为它会删掉很多”老快照里被多次转载的好文字”,留下更多低质量的独特文字);RefinedWeb 叠加了 MinHash 和基于后缀数组的子串去重(Lee et al. 的 ExactSubstr);Dolma 用 Bloom 过滤器在 URL、文档、段落三个级别做精确去重。去重的粒度、阈值、顺序、范围都是要做的决定,而且它们和质量过滤相互影响。


5. 测试集污染

5.1 问题

如果评估用的测试题出现在预训练数据里,模型的分数就可能是”背出来的”。Dodge et al. 在 C4 里发现了两种污染(slides):输入和标签都出现了(模型直接见过答案)和只有输入出现了。网页上本来就有大量”题库”:基准数据集的原文、讨论帖、教程。

小互联网里的题库站贴出了第 1 讲测试集的前 200 篇文档。第 5 讲会对比”泄露的”和”干净的”测试文档;这里先看怎样检测。

5.2 用 n-gram 重叠检测

最常见的做法是看训练文字和测试样本有没有共同的长 n-gram。GPT-3 的定义:与训练数据有 13-gram 重叠的测试样本算”脏的”(短于 13 个词的样本,看整个样本是否出现在训练数据里),然后在”干净”的子集上重新评估。代码 04 §5 检查哪些网页和泄露的 200 篇文档共享 nn-gram:

nn能被检测的泄露文档(≥ nn 词)查出的题库页误报的其他页面
8168 / 200(84%)20 / 201167
13132 / 200(66%)20 / 2022
2086 / 200(43%)20 / 200

两难:nn 太小,花园语这门”单调”的语言里短 n-gram 到处都有,误报大量好页面;nn 太大,短的测试文档根本凑不出一个 nn-gram,泄露了也查不出来。

从测试集的角度看(GPT-3 的规则):泄露的 200 篇全部被判为”脏”;但干净的 800 篇里也有 220 篇(28%)被判为”脏”——其中不到 13 个词的 262 篇短文档里有 168 篇。它们并没有泄露,只是太常见,整篇碰巧出现在了网上。“脏”的标签只是一个近似,第 5 讲比较两个子集时要记住这一点。真实语言比花园语丰富得多,误报会少很多,但同样的两难依然存在;而换个说法、换个格式的”语义泄露”(把题目改写一下再发到网上),n-gram 根本查不出来(第 16 讲)。


6. 数据决定模型:同样的计算量,不同的数据

6.1 实验设置

slides 说消融预训练数据的决定非常昂贵。在小互联网上它很便宜:代码 05 在流水线各个阶段的输出上各训练一个第 2 讲的小 Transformer(106,560 个参数),计算量完全相同:600 步 × 每步 32 个 × 64 个 token = 1,228,800 个 token。数据首尾相接打包成定长块(§9),词表是花园世界的词加上 grut,样板、垃圾页里的词和外语词都变成 <unk>。

训练数据页面token<unk> 占比相当于几遍
原始:全部页面的全部可见文字3020472,55552.8%2.6
+ 语言识别(只留英语)2720426,44649.2%2.9
+ C4 与 Gopher 规则(含屏蔽词)1192144,4516.7%8.5
+ 去重(完整流水线,含屏蔽词)1124115,1450.0%10.7
完整流水线,但不用屏蔽词1314136,5680.3%9.0
不用屏蔽词 + 去污染(13-gram)1207121,2030.4%10.1

6.2 结果

三个测试集:干净的英语测试文档(第 1 讲测试集里没被泄露的 800 篇)、被泄露的 200 篇、花园方言的 500 篇。

训练数据干净英语泄露的测试方言
原始5.6035.8056.178
+ 语言识别5.4525.6496.078
+ 规则(含屏蔽词)4.2134.3217.752
+ 去重(含屏蔽词)3.9504.0637.447
不用屏蔽词3.9914.0844.283
不用屏蔽词 + 去污染4.0014.1204.274
真实分布(下界)3.6443.701—

(方言是一种改写规则,没有可以精确计算的真实分布。)

清洗的价值:同样的计算量,干净英语上的困惑度从 5.603 一路降到 3.950。原始数据里一半以上的 token 是样板、垃圾和外语,模型把算力花在了预测它们上。注意清洗后的数据只有原来的四分之一,模型要重复看十遍——在计算受限时,宁可把好数据多看几遍,也比看一遍垃圾强(第 12、23 讲会讨论”数据用完了怎么办”)。

屏蔽词的代价:含屏蔽词的流水线训练出的模型,在方言上的困惑度是 7.447,比原始数据训练的模型(6.178)还差;不用屏蔽词时是 4.283。而在标准英语上,两者几乎一样(3.950 vs 3.991)。这个代价只落在方言使用者身上,只看标准测试集是看不出来的。 一个只在”主流”测试集上评估的团队会得出”屏蔽词没有副作用”的结论。

6.3 泄露的剂量

表里还有一个”没看出来”的东西:泄露。前 200 篇本身就比后 800 篇略难(连真实分布的比值都是 3.701 / 3.644 = 1.016;用原始数据训练时是 1.036),所以要看比值”泄露/干净”的变化:训练数据含题库站时 1.023,去污染后 1.030。泄露一份(20 个页面,约 4% 的训练 token)对一个 10 万参数、训练十遍左右的模型影响很小。

代码 05 §3 做了一个”剂量”实验:在去污染的数据里额外混入 kk 份泄露的 200 篇测试文档。

泄露的份数占训练 token干净英语泄露的测试泄露 / 干净
00%4.0014.1201.030
13.9%3.9474.0391.023
1028.6%3.8853.8000.978
5066.7%4.0643.3700.829

同一批考题重复出现几十次,模型就把它们背下来了:泄露的测试文档困惑度降到 3.370,远低于干净文档——而且低于这 200 篇文档在真实分布下的困惑度 3.701。一个模型在测试集上明显”超过”了真实分布(在约 5000 个 token 上低这么多,不是统计波动能解释的),唯一的解释就是它见过答案。 Carlini et al. (2023) 发现记忆随三件事对数线性地增长:模型越大、一段文字被重复的次数越多、提示给的上下文越长,就越容易被逐字背出。这也是去重(§4)的另一个理由。

另一个细节:13-gram 去污染删掉了 107 个页面,其中真正的题库页只有 20 个,其余都是碰巧与测试文档共享 13-gram 的好页面——§5.2 的误报在这里变成了真实的数据损失。


7. 几个著名的数据集与”数据公地”的衰退

7.1 数据集

slides 介绍了几个有代表性的数据集:

  • Wikipedia:按语言切分、清理掉标记和无关章节的文章,覆盖约 350 种语言;字段是 id、url、标题、正文,在 HuggingFace 上可以直接用。干净,但量小。
  • Toronto BookCorpus:从独立电子书平台 Smashwords 抓取的自出版书籍,最初报告 11,038 本(Zhu, Kiros et al. 2015),后来发现大量重复,不重复的只有 7,185 本(Bandy & Vincent 2021);约 9.85 亿词,用于训练最早的 GPT 和 BERT。因为法律问题,官方版本已不再提供。
  • WebText:GPT-2 的预训练语料(§3.6),没有公开,社区复刻了 OpenWebTextCorpus。
  • Common Crawl(§2.2)。
  • C4(§3.4):Common Crawl 一个月的快照,约 750 GB 英语,T5 的训练数据。
  • ClueWeb22(Overwijk et al. 2022,本讲阅读):100 亿个网页,带视觉信息和语义标注,用商业搜索引擎级的工具做了正文抽取(§3.2)。它的分布和 Common Crawl 不同:更接近”搜索引擎用户真正访问的网页”。
  • FineWeb(Penedo et al. 2024):15 万亿 token、44 TB,来自 96 个 Common Crawl 快照,流程和本讲几乎一一对应(抽取、语言识别、Gopher / C4 风格的规则、每个快照内的 MinHash 去重),并且每一步都用训练小模型的方式做了消融。
  • Dolma(Soldaini et al. 2024):3 万亿 token,OLMo 的训练数据,混合了网页、代码、论文、书、百科、社交媒体,完整公开了处理代码。

7.2 数据公地的衰退

本讲的另一篇阅读,Longpre et al. (2024) 审计了 C4、RefinedWeb、Dolma 背后的 14,000 个网络域名的”同意协议”:robots.txt 和服务条款。他们发现:

  • 专门针对 AI 的限制条款在激增;对不同 AI 开发者的限制差别很大;网站服务条款里表达的意愿和 robots.txt 常常不一致——这些协议当初不是为”把整个互联网拿去训练 AI”设计的;
  • 仅在 2023–2024 一年里,限制迅速增加:C4 中约 5% 以上的 token、以及 28% 以上最活跃维护的关键来源,已经被 robots.txt 完全禁止抓取;按服务条款算,C4 的 45% 已受限;
  • 如果这些限制被尊重或强制执行,会迅速影响通用 AI 系统的多样性、新鲜度和规模定律。

这意味着”网上的公开文字”作为数据来源,正在变得更小、更偏(愿意开放的网站和不愿开放的网站,内容分布不一样)。数据的获取和授权,正在成为和算法同样重要的问题。


8. 分词:从文字到整数

8.1 从干净的文字到张量

文字清洗干净之后,还要变成神经网络能吃的整数张量(slides):先分词(tokenization)——把文字切成离散的 token,每个 token 对应词表里的一个 id;再组批(batching,§9)。词表就是所有可用 token 的列表。

第 1 讲 §5.1 比较过三种切法:对 A hippopotamus ate my homework.,字符级 31 个 token、子词级 9 个、词级 6 个。词级分词(按空格和标点切)有开放词表问题:很多词在训练数据里从没出现过,只能变成 [UNK];在德语这类会把词拼在一起的语言里更严重;拼错的词也会变成 [UNK]。子词分词让词表”动态”形成:常见词有自己的 token,罕见词被拆成几个子词。

8.2 BPE 的训练

**字节对编码(Byte Pair Encoding,BPE)**的思路来自压缩算法:从字符出发,反复合并最常见的相邻对。slides 给的训练步骤:

  1. 用字符初始化词表(例如英语的字母、数字、标点);
  2. 把语料里出现最多的相邻 token 对合并成一个新 token(词表 +1);
  3. 用新 token 重新切分语料;
  4. 重复 2、3,直到词表达到目标大小。

高效的实现方式是:先用规则把语料切成词,统计每个词的频率,然后只在”词 → 频率”表上操作。代码 06 §1 复现了 slides 的例子,词频 (hug, 10)、(pug, 5)、(pun, 12)、(bun, 4)、(hugs, 5),初始词表 b g h n p s u:

第 1 次:合并 'u' + 'g'(出现 20 次)→ (h ug, 10),(p ug, 5),(p u n, 12),(b u n, 4),(h ug s, 5)
第 2 次:合并 'u' + 'n'(出现 16 次)→ (h ug, 10),(p ug, 5),(p un, 12),(b un, 4),(h ug s, 5)
第 3 次:合并 'h' + 'ug'(出现 15 次)→ (hug, 10),(p ug, 5),(p un, 12),(b un, 4),(hug s, 5)

ug 出现 10 + 5 + 5 = 20 次,最先被合并。常见的 hug 成了一个 token,hugs 被切成 hug + s——词根和后缀自然地分开了。这就是子词分词的好处(slides):词表大小是可控的超参数;常见词完整保留,罕见词拆成子词,子词得到更多的观察、还能利用构词信息;这些单位很适合神经网络——Transformer 很容易学会常见的组合,神经表示也能平滑罕见的组合。

编码新文字时,按学到的先后顺序应用合并规则:每次在当前序列里找最早学到的那一对合并,直到没有可合并的(第 1 讲 toy_tokenizers.BPETokenizer)。

8.3 三个”进一步的难题”

slides 列了 BPE 的三个难题和解决办法:

  • 基本单位是字符时,仍然可能有未知 token:如果基本单位是 [A-Za-z],中文字符就切不了。办法:字节级 BPE,用原始字节(UTF-8 的 256 个字节)作为基本字符集,任何文字都能编码(GPT-2 起的做法)。
  • 算法需要先做一次词级切分,这可能很棘手:中文和代码里按空格切分就不管用。办法:更聪明的规则化预分词(GPT-2 的正则表达式把空格算在后一个词的开头;GPT-4 的预分词规则会把数字按三位一组切开);或者像 SentencePiece 那样把空格当成普通字符 ▁、直接在原始文字上训练。
  • 找最常见的一对,朴素实现很慢:每次合并都要把所有相邻对重新数一遍。办法:缓存出现次数,增量更新。

最后一条,代码 06 §2 实现了增量计数的 FastBPE(bpe.py):合并 (a,b)(a, b) 时,只有含有相邻 a ba\,b 的词会改变;对这些词先减掉旧的相邻对、合并、再加上新的相邻对,同时维护”每一对出现在哪些词里”的索引。在本机 Python 标准库的文档字符串(真实英语)的前 30 万个字符上,它和第 1 讲的朴素实现学出的合并规则逐条相同;在作者机器上,100 次合并快约 8 倍、300 次快约 9 倍(计时因机器而异)。合并越多,差距越大;工业实现(HuggingFace tokenizers)还会用堆来找最大的一对,并用 Rust 并行。

另外两种子词算法,原课只提到名字,这里给一句话的直觉:

  • WordPiece(BERT):也是自底向上地合并,但不选”出现最多的一对”,而选”合并后让语料的似然增加最多的一对”,即 count(ab)count(a) count(b)\frac{\text{count}(ab)}{\text{count}(a)\,\text{count}(b)} 最大的一对——更偏好”总是一起出现”的组合。它用 ## 标记”接在前一个片段后面”(slides 里的 hip ##pop ##ota ##mus)。
  • Unigram LM(Kudo 2018,SentencePiece 的默认算法):反过来,从一个很大的候选词表出发,假设每个 token 独立出现,反复删掉”删了之后语料似然下降最少”的 token,直到词表缩到目标大小。编码时可以给出多种切分及其概率,训练时随机采样切分(subword regularization)能增强鲁棒性。

8.4 词表多大合适

代码 06 §3 在约 120 万个字符的文档字符串上训练 8000 次合并,再取前 mm 条合并规则得到不同大小的词表,在另外 1/5 的文档字符串上测量:

词表大小测试集 token 数字节 / tokentoken / 词
256(纯字节)225,9801.006.41
506114,3581.983.24
125681,4992.772.31
425662,3063.631.77
825657,6423.921.63

例句 The tokenizer splits uncommonly long identifiers into reusable pieces. 在 4256 的词表下切成 The|▁token|izer|▁s|pl|its|▁un|comm|only|▁long|▁identif|i|ers|▁into|▁re…。

词表从 256 增大到 1256,序列缩短了 64%;之后每多一倍词表,收益越来越小。词表大小的取舍:

  • 词表大:序列短(同样的上下文窗口装下更多内容、注意力计算更省、生成同样的文字步数更少);
  • 词表小:嵌入矩阵和输出层小(V×dV \times d,P3 §3);每个 token 的训练样本多,罕见 token 学得好(§8.6)。

真实模型的词表从 3 万(Llama 2)到 12.8 万(Llama 3)到 20 万(GPT-4o)不等,多语言模型的词表通常更大。

8.5 分词税:同样的内容,不同语言要付的 token 不一样

代码 06 §4 训练了两个各做 300 次合并的字节级 BPE:A 只用清洗后的英语网页训练;B 再加上等量级的 Floral 文字。

测试文字字节 / 字符A:token / 词B:token / 词
标准花园语1.001.211.21
花园方言1.001.201.20
Floral(外语)1.105.392.70

同样一句 Floral,A 切成 53 个 token(F|el|o|▁q|um|i|▁k|a|m|i|v|||…),B 切成 32 个。Floral 的 ø 在 UTF-8 里占 2 个字节,A 从没见过它,连这一个字母都要拆成两个字节 token。真实世界里,这种”分词税”(Petrov et al. 2023 称之为分词器带来的语言间不公平)意味着:对训练数据里占比少的语言,同样的上下文窗口装下的内容更少、按 token 计费的 API 更贵、生成同样的内容要多走好几步,模型在这些语言上的表现往往也更差。中文、印地语、缅甸语等非拉丁文字在 UTF-8 里每个字符占 3 个字节,问题更突出。B 的结果说明,把这门语言加进分词器的训练数据,税就降下来了——分词器的训练数据和模型的训练数据一样,决定了谁被”优待”。

方言几乎不受影响(1.20 vs 1.21):它和标准花园语的字母、词几乎相同。

8.6 从未被训练过的 token

代码 06 §5:分词器用未清洗的全部网页文字训练(600 次合并),语言模型却用清洗后的数据训练。600 个合并出来的 token 里:

  • 187 个是 BPE 的中间产物:比如先合出 he,后来又被合进更长的 token,在最终的编码里本来就很少单独出现——这是正常的;
  • 另有 283 个在网页原文里经常出现,在语言模型的训练数据里却一次都没出现:▁|(导航栏的分隔符,19,740 次)、▁cheap(9,815 次)、!!!、ø、é、▁runelo(一个 Floral 词)、▁Terms、▁experience、Popular……它们来自导航栏、页脚、垃圾页、外语页——全被清洗掉了。

这些 token 的输入嵌入在语言模型训练中从来没被更新过,停留在随机初始化的状态(输出层里它们只被不断压低概率)。模型偶尔在输入里遇到它们时,行为就无法预料。GPT-2/GPT-3 词表里著名的 ▁SolidGoldMagikarp 就是这样一个 token:它来自某个论坛的用户名,在分词器的训练数据里很常见,在模型的训练数据里几乎没有;模型被要求重复这个词时会说出莫名其妙的东西。教训:分词器和模型最好用同一份(或同分布的)数据训练;发布前可以检查嵌入范数异常、在训练数据里从未出现过的 token。


9. 批次:从整数到张量

9.1 “批大小”指的是什么

slides 问了一个看似简单的问题:批大小(batch size)是什么的个数?样本数?句子 / 段落数?文档数?token 数?

  • 样本数:在计算机视觉里有意义,因为图片都是同样的形状;对语言模型没有意义。
  • 句子 / 段落数:需要先解析文字,而且长度照样不一样。
  • 文档数:可以一个批放若干篇文档,但补齐很浪费(§9.2)。
  • token 数:预训练时人们说”批大小”,几乎总是指这个。

代码 07 §4 列了几个公开的数字:

模型每批 token 数序列长度相当于多少条序列
T565,536512128
GPT-3 175B320 万2048约 1560
LLaMA-1400 万2048约 1950
OLMo4,194,30420482048

一块 GPU 一次也许只放得下 8 条 × 2048 = 16,384 个 token;要凑够 400 万个 token,需要约 244 份——由很多块 GPU 的数据并行加上每块 GPU 上的梯度累积共同完成(第 13 讲)。slides 也提醒,批大小和多卡并行之间还有很多复杂之处。

9.2 补齐还是打包

slides 用三篇长度为 5、12、4 的文档、每行 8 个位置的批举例。代码 07 §1:

  • 方式 1:一行一篇,短的补 0、长的截断:24 个位置里 7 个是补齐(29%),第二篇还被截掉了 4 个 token;
  • 方式 2:首尾相接打包,文档之间放一个分隔符:24 个位置正好装下全部 21 个 token 和 3 个分隔符,没有补齐,也没有截断。代价是第二行同时装着第一篇的结尾和第二篇的开头。

在花园世界的 8000 篇训练文档上(含开头的 <bos>,平均长 26.2、中位数 20、最长 208),代码 07 §2:

组批方式补齐占比
随机 32 篇一批,补齐到批内最长70.9%
按长度排序后 32 篇一批(第 2 讲的做法)1.9%
打包成长 64 的定长块0.02%
打包成长 1024 的定长块0.32%

随机组批时,一篇 200 个 token 的长文档会让同批的 31 篇短文档都补齐到 200——七成的计算花在了补齐上。按长度分桶能省掉大部分,但会让每批的长度分布不一样(短文档扎堆);打包几乎不浪费。预训练几乎都用打包;微调和推理时(样本长度差异大、需要逐条处理)才更常见到补齐。

9.3 打包之后的跨文档注意力

打包之后,因果注意力里位置 ii 能看见位置 0..i0..i,其中一部分属于别的文档。代码 07 §3:

块长 TT每块平均文档数跨文档的注意力对
643.555.3%
25610.884.3%
102440.195.8%
4096157.698.9%

块越长(相对文档长度),绝大多数注意力对都落在”别的文档”上。花园世界的文档彼此独立,这些注意力既浪费计算,也可能让模型学到”上一篇和这一篇有关”的错误线索。两种做法:

  1. 不管它,只在文档之间放一个分隔符(end-of-text token),让模型自己学会”分隔符之前的内容与我无关”。GPT-3 就是这样做的;§6 的实验也是这样训练的。
  2. 文档掩码:把因果掩码改成按文档分块的下三角,同一块里不同文档之间互相看不见(见下图,3 篇文档打包在长 10 的块里)。Llama 3 预训练时用了文档掩码;他们报告这在标准长度的预训练里影响不大,在超长序列的继续预训练里很重要(第 21 讲)。
■ · · · · · · · · ·
■ ■ · · · · · · · ·
■ ■ ■ · · · · · · ·
· · · ■ · · · · · ·
· · · ■ ■ · · · · ·
· · · ■ ■ ■ · · · ·
· · · ■ ■ ■ ■ · · ·
· · · · · · · ■ · ·
· · · · · · · ■ ■ ·
· · · · · · · ■ ■ ■

10. 小结、常见误区、练习

10.1 小结

  1. 预训练数据要大、要干净、要多样;数据几乎总是越多越好(RoBERTa),唯一可行的大规模来源是网页,而网页数据嘈杂、有偏、版权复杂、混着大量模板和机器生成的文字。
  2. 三层偏差:网上有什么、爬虫爬到什么、过滤留下什么。小互联网上,聚焦爬取把好文字比例推到 96%,代价是方言论坛一页都没爬到;每站上限用质量换多样性。
  3. 清洗:正文抽取(博客类页面 35% 的字符是样板)、语言识别(混合页面的去留取决于阈值)、C4 规则(句末标点规则实际上是正文抽取器;垃圾页能骗过它)、Gopher 规则(照搬英语停用词表在花园语上误删大量好页面——规则要按语言重新校准)、质量分类器(AUC 0.959,但学的是”像不像正例”:样板、方言词、不加 s 的动词都会拉低分数;阈值 0.75 时方言只剩 3%)。
  4. 屏蔽词删掉的方言页面(89%)比骂人页面还多——Dodge et al. 在 C4 上发现了同样的模式,建议不要用屏蔽词表过滤,并认真文档化数据集。
  5. 去重:精确哈希、C4 三句片段、MinHash + LSH(签名误差 ≈J(1−J)/k\approx \sqrt{J(1-J)/k},候选概率 1−(1−sr)b1 - (1 - s^r)^b)。片段去重会留下碎片,先做页面级去重更好。
  6. 测试集污染:n-gram 检测有两难;在单调的语言里”共享 13-gram”不等于泄露。少量泄露影响很小,重复 50 份时泄露文档的困惑度(3.370)降到它们在真实分布下的困惑度(3.701)之下。
  7. 数据决定模型:同样的计算量,清洗把困惑度从 5.603 降到 3.950;屏蔽词让方言困惑度从 4.283 升到 7.447,而标准测试集上看不出任何差别。
  8. 分词:BPE = 反复合并最常见的相邻对;字节级 BPE 永不 OOV;增量计数快一个数量级;词表越大序列越短但收益递减;只用英语训练的分词器给外语收 4~5 倍的”分词税”;分词器与模型数据不一致留下从未训练过的 token。
  9. 批次:批大小通常以 token 计(百万量级);打包几乎不浪费,但块越长、跨文档注意力越多,文档掩码可以屏蔽它。

10.2 常见误区

  • “数据越干净越好,所以过滤越严越好”:过滤是在删数据,删掉的不全是坏数据,而且误删往往集中在某些人群、语言、方言上(§3.6、§3.7)。严格的过滤还会降低多样性。
  • “质量分类器学的是质量”:它学的是”像不像正例”。正例选什么,就定义了什么是质量(§3.6)。
  • “屏蔽词过滤只会删掉有毒内容”:同一个词在不同社区里意思不同;Dodge et al. 发现它不成比例地删掉了少数群体的文字。
  • “在标准测试集上没变差,说明这一步过滤没有副作用”:屏蔽词的代价在标准测试集上完全看不出来,只在方言测试集上出现(§6.2)。评估集里没有谁,谁的损失就看不见。
  • “去重就是删掉一模一样的文档”:近似重复远比精确重复多;片段级的重复(样板段落)要用不同的方法。
  • “做了 13-gram 去污染,评估就干净了”:短样本查不出来,改写过的题目查不出来;而且在单调的文字上还会大量误报(§5.2)。
  • “分词器是一个与模型无关的预处理工具”:分词器的训练数据决定了哪些语言付更多的”税”、哪些 token 从未被训练;词表大小影响参数量和序列长度(§8)。
  • “批大小 400 万是指 400 万条样本”:指 400 万个 token(§9.1)。

10.3 练习

练习 1(手动应用 C4 规则):对下面这个页面(一行一块)应用 C4 的规则 2–6(屏蔽词表用 zorp、blick、snerk、grut),哪些行留下?整页会被删掉吗?

Welcome to my garden
Home | Blog | About
I grow tomatoes. They are red!
Please enable JavaScript to see the photos.
Water daily
My friend feeds the hens every morning. She says they are happy.
Is it too late to plant beans?
Copyright 2024. All rights reserved.
参考答案(用 cleaning.c4_rules 验证)

留下 4 行:I grow tomatoes. They are red!、My friend feeds the hens every morning. She says they are happy.、Is it too late to plant beans?、Copyright 2024. All rights reserved.。删掉的:Welcome to my garden、Home | Blog | About、Water daily(没有句末标点);Please enable JavaScript…(含 javascript)。剩下的文字有 7 个以句末标点结尾的句子,≥ 5 句,所以整页保留。

注意最后一行:页脚 Copyright 2024. All rights reserved. 恰好以句号结尾、有 3 个以上的词,于是混了进来——规则只看格式。在整个数据集上,它会被第 7 条(三句片段去重)或页面级的去重处理掉吗?只有当它和前后的句子组成的三句片段重复出现时才会;单独一行的样板常常需要专门的”行级去重”(例如删掉在很多页面上出现的行)。

练习 2(MinHash 的性质):设 hh 是从全部可能元素到不同实数的一个随机排列(每种顺序等可能)。证明 P[min⁡x∈Ah(x)=min⁡x∈Bh(x)]=J(A,B)P[\min_{x \in A} h(x) = \min_{x \in B} h(x)] = J(A, B)。

参考答案

看 A∪BA \cup B 中 hh 值最小的那个元素 x∗x^*。因为 hh 是随机排列,A∪BA \cup B 中每个元素成为 x∗x^* 的概率相同,都是 1/∣A∪B∣1/|A \cup B|。两个最小值相等,当且仅当 x∗x^* 同时属于 AA 和 BB:若 x∗∈A∩Bx^* \in A \cap B,它同时是 AA 和 BB 的最小值;若 x∗x^* 只属于其中一个,比如 AA,那么 AA 的最小值是 h(x∗)h(x^*),而 BB 的最小值是某个更大的值(hh 是单射),两者不等。所以概率 =∣A∩B∣/∣A∪B∣=J(A,B)= |A \cap B| / |A \cup B| = J(A, B)。实际实现用 h(x)=(a⋅hash(x)+b) mod ph(x) = (a \cdot \text{hash}(x) + b) \bmod p 近似随机排列。

练习 3(LSH 的参数):签名长 100,取 b=20b = 20、r=5r = 5。(a)Jaccard 为 0.8 和 0.5 的两对文档成为候选的概率各是多少?(b)和 b=16b = 16、r=8r = 8(长 128)比,哪个更容易漏掉 J=0.8J = 0.8 的近似副本?哪个更容易产生 J=0.5J = 0.5 的误报?

参考答案

(a)J=0.8J = 0.8:1−(1−0.85)20=1−(1−0.32768)20=1−0.6723220≈0.99961 - (1 - 0.8^5)^{20} = 1 - (1 - 0.32768)^{20} = 1 - 0.67232^{20} \approx 0.9996。J=0.5J = 0.5:1−(1−0.55)20=1−0.9687520≈0.4701 - (1 - 0.5^5)^{20} = 1 - 0.96875^{20} \approx 0.470。

(b)b=16,r=8b = 16, r = 8:J=0.8J = 0.8 时 0.947,J=0.5J = 0.5 时 0.061。所以 b=16,r=8b=16, r=8 更容易漏掉 J=0.8J = 0.8 的副本(5.3% vs 0.04%),而 b=20,r=5b = 20, r = 5 在 J=0.5J = 0.5 时有 47% 的概率产生候选——这些候选会在精确比较时被排除,所以误报的代价是计算量,不是结果错误;漏报则是真的漏了。阈值约为 (1/20)1/5≈0.55(1/20)^{1/5} \approx 0.55 和 (1/16)1/8≈0.71(1/16)^{1/8} \approx 0.71。

练习 4(手算 BPE):Sennrich et al. (2016) 的经典例子:词频 (low, 5)、(lower, 2)、(newest, 6)、(widest, 3)。手算前 6 次合并(次数并列时取字典序最小的一对),以及最终每个词的切分。

参考答案(用 bpe.char_bpe_merges 验证)
  1. e + s:newest 6 + widest 3 = 9 次;
  2. es + t:9 次;
  3. l + o:low 5 + lower 2 = 7 次;
  4. lo + w:7 次;
  5. e + w:newest 里的 ew,6 次(此时 n + e、w + est 等也是 6 次,按字典序 e + w 最小);
  6. ew + est:6 次。

最终切分:low → low;lower → low e r;newest → n ewest;widest → w i d est。注意 est 很早就成了一个 token,它在两个不同的词里都是后缀——BPE 自动发现了常见的构词成分。

练习 5(分词税的来源):用 Python 算 len("猫".encode("utf-8"))、len("ø".encode("utf-8"))、len("a".encode("utf-8"))。一个只见过英语的字节级 BPE 遇到中文时,一个汉字至少要几个 token?为什么把中文加进分词器的训练数据能大幅降低 token 数?

参考答案

分别是 3、2、1。一个只见过英语的字节级 BPE 没有学过任何包含这些字节的合并规则,一个汉字就要切成 3 个字节 token(§8.5 里,A 分词器把 ø 切成了 <c3>|<b8> 两个 token)。把中文加进训练数据后,常见汉字的 3 个字节会被合并成一个 token,常见的词(两三个字)甚至会成为一个 token,token 数可以降到原来的几分之一。代价是词表里要留出位置给这些 token——多语言模型的词表通常更大(Llama 3 的 12.8 万词表比 Llama 2 的 3.2 万大很多,原因之一就是要更好地覆盖多种语言)。

练习 6(动手:换一个正例):在 03_quality_classifier.py 里,把 30 个方言论坛页面也加进正例(正例权重相应调整为 500 / 130),重新训练。阈值 0.75 时方言的保留比例会怎样?

参考答案(作者机器上的实际结果)
种类平均分阈值 0.50.70.75
百科0.774100%87%69%
博客0.75199%80%59%
方言0.782100%90%77%

阈值 0.75 时方言的保留比例从 3% 升到 77%,甚至略高于博客。分类器没有变”更聪明”,只是”质量”的定义变了:正例里有了 grut 和不加 s 的动词,它们就不再是负特征。谁被放进正例,谁的文字就被当成高质量。

练习 7(动手:每站上限太严会怎样):把 01_web_and_crawl.py 里聚焦爬取的每站上限改成 5,结果会怎样?

参考答案(作者机器上的实际结果)

191 个站点 × 5 页 = 955 页,比预算 1000 还少:爬虫把每个站点都爬了 5 页(全网的站点都被发现了),然后就没有可爬的了。结果好文字只有 58.1%、垃圾 41.9%,几乎等于全网的组成(59.4% / 40.6%)——每站上限太严,爬取就退化成”按站点均匀抽样”,800 个垃圾页面所在的 40 个垃圾站和好站点拿到了同样的份额。上限是在质量和多样性之间调节的旋钮,两头都有代价。(对照:BFS 加每站 ≤ 10 页,好文字 73.8%。)

练习 8(思考:C4 的标点规则删掉了什么好内容):列举几类”好的、值得训练的”文字,它们会被”只保留以句末标点结尾的行”这条规则删掉或严重破坏。

参考答案

列表和要点(菜谱的配料表、操作步骤)、诗歌和歌词(很多行不以标点结尾)、表格、代码(行尾是 )、{、; 之类)、对话记录里的短回复、标题和小标题、数学公式、以及不用 .!?" 作句末标点的语言(中文的”。“、日语、泰语等——C4 只做英语,但照搬到其他语言就会出问题)。这些都是对模型很有价值的结构化知识。这也是后来的数据集(RefinedWeb、FineWeb)放弃或放宽这条规则、改用正文抽取工具的原因之一;代码则通常单独从代码仓库收集。

练习 9(动手:批大小与补齐浪费):在 07_batching.py 的随机组批里,把每批 32 篇改成 8 篇和 128 篇,补齐浪费各是多少?为什么批越大浪费越多?

参考答案(作者机器上的实际结果)

8 篇:57.3%;32 篇:70.9%;128 篇:77.8%。批越大,批里出现一篇特别长的文档的可能越大,而所有文档都要补齐到批内最长;文档长度服从长尾分布(几何分布,最长 208、中位数 20),批越大越”倒霉”。这正是大批量预训练必须打包、而不能按文档补齐的原因。

10.4 延伸阅读

  • 原课阅读:Dodge et al. (2021), Documenting Large Webtext Corpora: A Case Study on the Colossal Clean Crawled Corpus;Longpre et al. (2024), Consent in Crisis: The Rapid Decline of the AI Data Commons;Overwijk et al. (2022), ClueWeb22
  • 数据集:Raffel et al. (2020)(T5 / C4);Rae et al. (2021)(Gopher / MassiveText 的规则);Penedo et al. (2024), The FineWeb Datasets;Soldaini et al. (2024), Dolma
  • 去重与记忆:Lee et al. (2022), Deduplicating Training Data Makes Language Models Better;Carlini et al. (2023), Quantifying Memorization Across Neural Language Models;Broder (1997)(MinHash 的原始论文)
  • 文档化:Gebru et al. (2021), Datasheets for Datasets;Bandy & Vincent (2021)(BookCorpus 的回溯文档)
  • 分词:Sennrich, Haddow & Birch (2016)(BPE);Kudo (2018)(Unigram 与 subword regularization);Kudo & Richardson (2018)(SentencePiece);Petrov et al. (2023), Language Model Tokenizers Introduce Unfairness Between Languages;Land & Bartolo (2024), Fishing for Magikarp(自动发现未训练的 token)
  • 本仓库的相关内容:CS336 第 13、14 讲(数据来源与处理,更偏工程);CS336 第 1 讲(分词)

10.5 下一讲预告

第 2 讲搭起来的是 2017 年原版 Transformer 的”教科书版”。今天的 Llama、Qwen、DeepSeek 在四个地方做了改动:前馈层(ReLU → GELU → SwiGLU)、注意力(多头 → 分组查询 → 多头潜在注意力)、层归一化(post-norm → pre-norm、LayerNorm → RMSNorm)、位置编码(正弦 / 可学习 → 相对位置 → RoPE)。第 4 讲逐一拆开:每处改动解决了什么问题,代价是什么,并在花园世界上把”原版”和”现代配方”正面比较。

← 返回课程目录