同一段话在语料里出现几万次,会怎样?
本课一问:规则留下来的文本里,同一段话可能被转载、镜像、套模板成千上万次。不管它,会怎样?
这一课先不碰:MinHash 为什么能估出相似度的证明(岔路课)、「合法但没营养」的文本(第 3 课)、有意把好数据多喂几遍(第 5 课)。
从已有出发
贯穿任务走到了这里:《三國志演義》的原始文件去掉了英文声明,17,584 行硬换行拼回了 3,697 段,每段都用第 1 课的规则逐条求值过。规则看的是一篇文档自己长什么样;重复看的是文档和文档之间的关系,规则管不到。所以本课先问:这 3,697 段之间有没有重复?网页上的重复又多到什么程度?
模型这条流水线上,本章始终停在最左边的「文字」一格;把这一格展开,就是下面这条数据管线:
先自己设计:不去重,有多少喂多少
最省事的做法是不管重复。Lee 等人在 C4 里找到一句 61 个词的英文句子,重复了 61,036 次;去重之后,模型原样复述训练文本的频率降到原来的 1/10,而 C4 的验证集里有 4.6% 的样本和训练集重叠——这部分「考题」模型其实见过 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条在 C4 等英文数据集上的实验;「10 倍」指模型生成时逐字复现训练文本的频率。具体模型规模见原文。 出处Lee et al. 2021 摘要、§1、表 2(需联网) ↗需联网 。
重复还有一个更早、更隐蔽的后果:分词器也会把重复的东西学成 token。我们自己的项目就有现成的例子。
- ① 朴素方案
- ② 它在哪失败
- ③ 改进方案
真实的分词器里已经有这个现象。项目分词器(词表 2,048,只在训练集上训练)里最长的 token 是 「且看下文分解」(id 1639)——章末套话「未知……如何,且看下文分解」的后半句,在训练集里出现 88 次。它比任何人名都长:「司馬懿」「諸葛亮」都只有 3 个字。
放大它。Project Gutenberg 里的每一本书都带同一段英文头部声明和尾部许可证(我们的原始文件里是 752 + 18,437 个字符)。假设把训练集的 107 回当成 107 本独立的电子书,每本原样套上声明(教学构造:文字全是真的,重复次数是构造的),用项目的 minillm/tokenizer.py 重训同样大小的词表:
- 英文声明占掉的合并:1,034 / 1,791;
- 中文验证集:58,386 → 71,330 个 token,多 22.2%;
- 干净条件下学到的 426 个多字中文 token,有 372 个被挤掉。
项目的实际做法是先把声明去掉、再训分词器:1,791 次合并里英文 token 为 0 个。只保留一份声明(整本书不去头尾)时,英文 token 已经有 54 个,「貂蟬」「新野」等 22 个中文 token 被挤出词表。
去重要在训练分词器之前做:分词器一旦训完,词表就和模型终身绑定(C1),回头再改,embedding 全部作废。
试三件事:① 读最上面一行,找出排第一的 token 是什么、在训练集里出现了多少次;② 在三种条件之间切换,看堆叠条里红色(英文声明)的部分怎么从 0 涨到一半以上;③ 在「每一回都带一份声明」下读「还在不在词表里」:「諸葛亮」被挤掉了,「司馬懿」还在——出现得越少的词,越先被挤掉。
朴素方案二:精确哈希去重
那就删掉重复的。最直接的办法是给每篇文档算一个哈希值(或者比 URL),哈希相同就只留一篇。它能删掉一字不差的拷贝,但网页上的重复大多是近似的:转载时加一行「来源:某某网」,改几个字,换个日期。改一个字,整篇的哈希值就完全不同,精确去重认为它们毫无关系。下面实验台的第 ⓪ 步就是这个。
改进:shingle + Jaccard → MinHash → 分桶
第一步,把「像不像」变成一个数。把文档切成连续的片段(shingle,这里是连续 5 个字),两篇文档的相似度定义为两个片段集合的 Jaccard:交集大小除以并集大小。一字不差是 1,完全无关是 0;转载时改几个字、加一行来源,Jaccard 仍然很高(下面实验台的默认例子就是这种)。
第二步的麻烦是配对数。两两算 Jaccard,n 篇文档要比 n(n−1)/2 次。我们整本书只有 3,697 段,已经是 6,832,056 对;一个 Common Crawl 快照有几十亿个网页,两两比较根本不可能。
MinHash 的做法:对每个哈希函数,只留下一篇文档所有 shingle 里哈希值最小的那个。两篇文档的最小值相等的概率,恰好等于它们的 Jaccard(为什么?见岔路课)。于是每篇文档压缩成 k 个数的「签名」,签名逐位相等的比例就是 Jaccard 的估计。
分桶(LSH)解决配对数:把 k 个数切成 b 个桶、每桶 r 个。只有在某个桶里 r 个数全部相等的两篇文档才会被拿出来比较——查桶是按键找邻居,不用两两比。代价是它变成了一个概率筛:
代入 FineWeb 的 14 × 8(共 112 个哈希函数):s = 0.7、0.75、0.8、0.85 时,被选中的概率是 56.5%、77.2%、92.4%、98.8%,与 FineWeb 附录 E.1 给的 56%、77%、92%、98.8% 一致 证据等级 · 数学可证明 在写明的前提下可以严格推导出来,不依赖实验。只要前提成立,换数据、换规模仍然成立。 本条前提:每个哈希相等的概率是 s(MinHash 的性质,岔路课证明),且各哈希函数相互独立。在前提下,1 − (1 − s^r)^b 是精确的概率,不是经验拟合。 出处FineWeb 附录 E.1 ↗ 。RefinedWeb 用 450 × 20 = 9,000 个哈希,同样四个点是 30.2%、76.1%、99.5%、100.0%——更陡,但每篇文档要算、存、比 9,000 个数。FineWeb 认为省下的算力和存储值得换那一点点阈值附近的不确定性。
整本《三國志演義》上真跑一遍(Python,3,697 段)
试三件事:① 默认的「原文 vs 转载改写版」:看第 ⓪ 步的指纹完全不同,而第 ① 步的 Jaccard 仍然很高,第 ③ 步把它捞了出来;② 在文档 B 里多删几句,把 Jaccard 拖到 0.7 附近,多试几次——它有时被选中、有时没有,这就是 S 曲线在阈值附近的样子;③ 切到「整本书里最像的一对」,再把分桶切成 RefinedWeb 450 × 20,读这对在两条曲线上的概率差。
在整本书上真跑一遍的结论写在实验台最下面:3,697 段里,Jaccard ≥ 0.75 的段落对是 0 对,MinHash-LSH 给出的候选也是 0 对;最像的一对只有 0.5833,是两句章末套话「未知勝負如何,且看下文分解。」和「未知負如何,且看下文分解。」。一部精心编校的书几乎没有近重复——网页才是去重的主战场。
新的失败:去重越狠越好吗?
有了 MinHash,FineWeb 的第一反应是把全部快照放在一起去重:重复就删,删得越多越干净。先猜一下结果:
背景:FineWeb 最初把全部 96 个 Common Crawl 快照放在一起做 MinHash 去重——从最新的快照开始,逐个往更老的快照推进,老快照里凡是和已处理数据重复的都删掉。最老的快照被删掉了 90% 以上。
实验:取 2013-48 这个老快照,拿两份数据各训一个同样的 1.71B 模型:
① 全局去重后留下的约 31B token;
② 被全局去重删掉的约 460B token,只在它们内部再去一次重,剩 171B token。
你猜哪一份训出的模型更好?
- 论文的人工翻看:留下的数据里广告、关键词堆砌、格式混乱的文本更多。论文的说法是全局去重在最后处理的老快照里「相对上采样了低质量数据」。一种解释(本课推断,论文没有逐字这样写):老快照里被反复转载、后来的快照又收录过的内容多半是好内容,于是被当成重复删掉;没人转载的那部分反而留了下来。
- 全局去重后总共剩 4T token;从中随机抽 350B 训练,和完全不去重相比几乎没有提升,远低于 RefinedWeb。改成每个快照内部独立去重(参数不变),剩 20T token,追平 RefinedWeb。
- 论文的假设:去重的收益主要来自删掉那些有几十万份拷贝的超大簇(每个快照里都有);再去删只有不到约 100 份拷贝的小簇(约等于快照数),反而有害。
| 方法 | 删掉 | 剩下 |
|---|---|---|
| URL 去重(每个规范化 URL 只留一篇) | 71.5% | 5.6T |
| 行级去重(每个重复行只留一处) | 77.8% | 4.4T |
| 行级去重 + 至少 10 词的行 + 删掉少于 3 句的文档 | 85% | 2.9T |
| 3 行窗口去重(数字当 0) | 80.9% | 3.7T |
出处:FineWeb 论文 §3.4(图 3、4、5)与附录 E.3(图 15);规模:1.71B 参数的消融模型。论文把 31B 与 460B 说成「保留的 10%」「删掉的 90%」,是约数。论文正文没给各曲线的具体分数,本组件只写结论方向。
常见误解一:「去重越狠越好。」跨 96 个快照的全局去重只留下 4T token,训出的模型几乎没有变好;每个快照内部独立去重留下 20T,追平 RefinedWeb。删掉的不只是垃圾,还有被反复转载的好内容。
常见误解二:「精确哈希去重就够了。」它只能删一字不差的拷贝。转载改一个字、加一行来源,哈希就完全不同;近重复必须用 shingle 级的相似度。
常见误解三:「去重 = 每段话只能出现一次。」去重针对的是网页上成千上万份的无意重复。有意把高质量数据多喂几遍是另一回事,4 个 epoch 以内几乎不亏(第 5 课)。
🔍 FineWeb、Gopher、我们的 shingle 为什么不一样?
FineWeb 对英文按「词」取 5-gram(先用英文分词器切词);Gopher 的 MinHash 用 13-gram、Jaccard 超过 0.8 算重复。n 越大,shingle 越「挑剔」:一处改动会毁掉 n 个 shingle,相似度掉得越快。中文没有空格,我们按字取 5 个连续字符——5 个汉字大约是英文 2–3 个词的信息量,和 FineWeb 的设置在同一个量级。这是本课的设计选择,没有做消融。
放回演进图里
去重和规则属于同一代:都在不理解内容的前提下删东西。下一代才开始让模型「读」数据。
- 解决了量几乎无限:Common Crawl 定期抓取全网,FineWeb 用了 96 个快照。卡在WET 纯文本里样板和菜单太多(FineWeb §3.2);我们的原始文件不还原硬换行就跑 C4 句末规则,整本书只剩 17.02% 的字。
- 解决了Reddit 上至少 3 karma 的外链,4,500 万个链接,清洗后约 800 万文档、40 GB。卡在量太小:C6 算出的需求是十几 T token;而且只覆盖被分享过的那一小部分网页。
- 解决了句末标点、词数、平均词长、停用词、重复行……便宜,能在全网快照上跑。卡在绑语言、绑求值单位:英文原版在整本《三國志演義》3,697 段上通过 0 段;C4 句末标点规则删掉约 30% 的 token,FineWeb 最后没用它。
- 解决了C4 里一句话重复 61,036 次;去重后模型复述训练文本的频率降到 1/10。MinHash 把两两比较变成查桶。卡在去得越狠未必越好:跨 96 个快照全局去重只剩 4T,训不出更好的模型;每快照独立去重留 20T,才追平 RefinedWeb。
- 解决了判断「有没有营养」:FineWeb-Edu 让 Llama-3-70B 标 46 万页、蒸馏成分类器,MMLU 33% → 37%。卡在参考语料的困惑度偏向「像参考」:我们模型写的胡话困惑度 93.417,低于真原文 273.209;阈值 3 删掉 92%,长程训练不够用。
- 解决了DoReMi 用 280M 代理模型搜域权重,8B 模型下游平均 +6.5 个点,达到基线快 2.6 倍。卡在权重一变就要重复:按 DoReMi 的权重花完 The Pile 的有效体量,YouTube 字幕要走约 16.9 遍(按字节近似 token,只看量级)。
- 解决了重复 4 遍以内几乎等于新数据;改写真实网页(WRAP)预训练约快 3 倍;Phi-4 约 10T token 里合成占 40%。卡在让模型只吃自己生成的数据,分布的尾巴一代代消失:在三国演义真实字频上重采样,只出现 1 次的字每一代有约 36.8% 的概率丢掉,丢了就回不来。
- 解决了最好的数据放到学习率衰减段(C5 的 WSD)加量;再发展成独立阶段,顺便拉长上下文。卡在同名不同方:Qwen3 的高质量阶段占 13.9%,Llama 3 的退火只占 2.6e-6;收益与学习率调度缠在一起,很难单独归因。
证据三问
| 问 | 本课的回答 |
|---|---|
| ① 它解决什么问题? | 删掉网页上的大量重复:它们让模型死记硬背、污染验证集,还会占用分词器的词表 |
| ② 数学上为什么可能有效? | MinHash 相等的概率 = Jaccard,分桶的候选概率 = 1 − (1 − sr)b,都可以严格推出 证据等级 · 数学可证明 在写明的前提下可以严格推导出来,不依赖实验。只要前提成立,换数据、换规模仍然成立。 本条前提是哈希函数近似于随机置换、各函数独立。 出处岔路课的证明 ↗ |
| ③ 实验上为什么这样做? | 去重的收益来自 Lee 2021 的实验;「每个快照独立去重」与「14 × 8 的参数」来自 FineWeb 的消融 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条1.71B 模型在 350B token 上的消融;全局与独立去重的比较只在 FineWeb 的快照结构下成立。 出处FineWeb §3.4、附录 E ↗ |
一句话
去重要删的是被复制成千上万份的大簇,不是所有「相似」;MinHash 把两两比较变成查桶,但删到多狠,要让训练出来的模型说了算。
先停在这里:记住三件事——精确哈希抓不住近重复;MinHash + 分桶是一条 S 形的概率筛;全局去重反而更差。
再深一层:去岔路课亲手做 1,000 次随机置换,看「最小值相等的频率」怎么收敛到 Jaccard。
下一个缺口
不重复、也合法,但很多文本「没营养」,怎么筛? → 第 3 课:规则都过了、也不重复,没营养的文本怎么筛?
你可能会问
- 「MinHash 凭什么能估出 Jaccard?」 → 岔路课:一个最小哈希值,凭什么能估出两篇文档有多像?
- 「A 像 B、B 像 C,但 A 不像 C,删谁?」 FineWeb 做传递闭包:A、B、C 放进同一个重复簇,每簇随机留一篇,其余全删。所以一篇文档哪怕只和簇里的一篇相似,也会被删。
- 「代码、数学也这样去重吗?」 思路一样,shingle 的定义和阈值要按数据重新调。GLM-5 报告只写了代码数据「模糊去重后的唯一 token 增加 28%」,没有展开是和什么比、为什么增加,这里不做解读。