
简介面向自然语言处理研究者与机器学习工程师这份综合报告聚焦大规模预训练语言模型的数据集构成。报告基于2018年至2022年初公开信息逐项比较GPT-1、GPT-2、GPT-3、GPT-NeoX-20B、Megatron-11B、MT-NLG和Gopher七个模型的训练数据来源、规模、令牌数量与内容类型并重点剖析Wikipedia、Common Crawl、Books、Reddit链接等常见语料的占比与偏差同时指出GPT-3中Books1、Books2的数据分析疑点。针对当前模型文档中数据集透明度不足的问题作者提出了改进方向强调参照透明度标准披露细节为后续数据集构建和模型训练提供重要参考读者可据此了解各模型数据组合的异同识别语料偏差风险也可作为构建新数据集时的对照基准。资源为单个PDF文件压缩包大小2.36MB内含分章节分析、对比表格、参考文献与附录材料信息密度较高。已有111人学习下载适合需要深入理解大模型数据底细的读者查阅。1. 大规模预训练语言模型数据集先把“喂什么”想清楚“把模型做大不如把数据做干净”这句话在训练千亿参数模型时不是口号而是成本约束下的必然选择。做过大规模数据清洗的人都知道几十 TB 的原始网页文本里重复内容、机器生成的噪声和低质量会话要占掉很大一块直接拿去训练模型学的不是语言规律而是“背题”。下面要拆解的是大规模预训练语言模型数据集的完整处理链路不同来源怎么配比、重复数据怎么去重、质量怎么打分、采样概率怎么设。适合正在跑预训练、准备扩充训练语料以及想搞清 LLaMA、Mistral 这类模型为什么选这些数据的工程师。2. 从 Common Crawl 到精编语料预训练语言模型数据集的来源与构成2.1 数据不是“一堆文本”是分层的四类来源预训练语言模型的数据集在工程上从来不是一个巨大的 txt 文件而是几十个来源、几种格式、几套清洗历史拼起来的混合体。按内容和作用可以分成四个层级网页底座、知识库、书籍与长文、代码与结构化数据。每个层级承担的能力不同处理方式也不同混在一起统一清洗是后面所有坑的根源。网页底座是绝对主力代表是 Common Crawl 这类网络爬虫产物规模最大但噪声也最大知识库指 Wikipedia 和各语言百科规模小但事实密度高书籍和长文负责长程依赖和叙事连贯性训练模型“把话说完整”的能力代码语料则直接影响程序语言理解和逻辑推理。对话与指令数据虽然量小却决定了模型最终的回复格式通常放到后训练阶段再介入。实际切分数据时可以参考下面这张分层表它也是多数开源数据集构建时采用的思路数据层典型来源规模量级对模型能力的贡献网页底座Common Crawl WARC/WET原始 PB 级语言流利度、常识覆盖清洗后网页C4、RefinedWeb 等清洗后 TB 级预训练主训练体知识库Wikipedia 及各语言百科GB 到 TB 级事实性知识、实体关系书籍与长文开源书籍语料TB 级长程依赖、叙事连贯代码GitHub 公开仓库快照TB 级程序语法、逻辑推理对话与指令开放问答、合成指令GB 级指令跟随、回复格式这里说的量级是行业常见区间不是某个特定数据集的精确统计。实际建库时你会发现网页底座往往比其它所有层加起来还要大两个数量级所以它的清洗质量基本决定了模型的下限。2.2 网页语料从 WARC 到纯文本warcio 解析的最小流程Common Crawl 的物理格式分三层WARC 是完整抓取包WET 是已经抽出来的纯文本WAT 是元数据。大多数人直接下载 WET 用但如果要自己做定制清洗往往还是得回到 WARC自己控制解析逻辑。常见做法是用 warcio 这个库读取它不是把整个文件解压进内存而是按记录流式读取这对动辄几百 GB 的压缩包来说很关键。import warcio from warcio.archiveiterator import ArchiveIterator with open(CC-MAIN-2024-*.warc.gz, rb) as f: for record in ArchiveIterator(f): if record.rec_type ! response: continue url record.rec_headers.get_header(WARC-Target-URI, ) payload record.content_stream().read() # payload 是原始 HTTP 响应体下一步需要剥 HTML 标签逻辑说明ArchiveIterator 会逐条返回 WARC 记录rec_type 用来过滤掉请求记录和元数据记录只保留真正的 HTTP 响应content_stream 是惰性读取不会把整个大文件一次性加载。文件名里的星号是通配符实际按 crawl 批次和 segment 编号组织你需要在构建脚本里先用 glob 展开真实路径。解析出响应体后还要走几步固定动作剥掉 HTML 标签、去掉 script 和 style 里的内容、做 HTML 实体反转义。不要在这一步省事后面所有质量过滤都建立在“拿到干净的正文文本”这一前提上。2.3 高价值子集怎么挑启发式打分先于模型过滤从原始网页里捞出来的文本质量参差到什么程度都有。直接上分类器或困惑度过滤太早成本高且不可解释。我一般会先跑一轮启发式打分用几条简单规则快速把明显不行的文档筛掉再做精细过滤。def heuristic_score(text: str) - float: sents text.split(。) if len(sents) 3: return 0.0 alpha sum(ch.isalpha() for ch in text) total max(len(text), 1) return (alpha / total) * min(len(sents) / 10, 1.0)逻辑说明这个函数用两个信号衡量文本质量。字母密度过滤掉表格、乱码和 Base64 噪声句子数量下限保证文档有基本上下文不是一句口号或一条导航。分数只用来排序不建议设绝对阈值截断而是排序后按分位数切分比如保留前 80%。启发式打分的优点是可解释、零成本、不会误杀新领域文本缺点是只看局部特征抓不住“整体读起来通顺但内容是模板拼凑”的文档。所以它只能当第一道粗筛把明显垃圾清走给后面的模型过滤留出干净输入。2.4 开源数据集能直接用吗C4、The Pile 与自建取舍做数据集时最常见的第一个问题是有没有现成的能用C4、The Pile、SlimPajama 这些开源数据集质量不错在通用英文任务上表现稳定省掉大量清洗时间。但它们的短板也很明显中文占比偏低代码和领域文本比例固定没法针对下游任务调节。自己做预训练采集时我见过不少团队的做法是“开源数据集打底 自建领域语料叠加”而不是二选一。叠加的时候注意去重要跨两个集合做。开源数据集内部已经去重过但和自建语料之间必然有重叠尤其是转载量大的新闻和技术博客。直接拼接会让某一部分内容在最终语料里被隐式过采样后面配比阶段的权重计算就失真了。所以数据源管理从一开始就要给每个来源记录原始路径和版本后面回查重复来源时用得上。3. 重复数据与清洗预训练语言模型数据集的第一道减法3.1 重复数据的三种形态以及它们如何伤害预训练预训练数据集里的重复不只是“同一篇文章出现两次”这么简单。实际数据里重复有三种形态危害也不一样完全重复指内容一字不差来自镜像站、爬虫重抓近重复指内容主体相同但带少量差异典型是转载时加了编辑按语模板重复则是页面框架相同而正文不同比如商品详情页、评论区和多语言自动翻译页。完全重复最直接的后果是训练效率下降模型把大量计算花在重复梯度的文本上。近重复更隐蔽模型会把转载版本里的编辑按语也学进内部知识表现为生成内容里频繁出现与正文无关的插入句。模板重复会造成最典型的“背题”现象模型记住的不是语言规律而是某个网站的页面结构。重复形态典型来源推荐手段完全重复镜像站、爬虫重抓MD5 精确去重近重复转载改写、页面框架相同MinHash LSH 模糊去重模板重复导航栏、标签云、评论区n-gram 覆盖率 规则过滤三种形态处理手段完全不同。代价越低、判据越硬的手段放在越前面能精确判定的先精确判定剩下模糊的再交给概率方法。3.2 精确去重用哈希处理 TB 级文本的起点精确去重用 MD5 或 SHA-256 对文档做指纹用哈希集合判重。这套方案在 TB 级数据上跑得动瓶颈只在外排序和磁盘空间不需要 GPU。对中文数据要注意先做 Unicode 规范化再做哈希否则全角和半角、不同编码造成的同一篇文章会被当成两个不同文档。# 每个文档一个哈希按哈希值排序后找重复 find corpus -name *.txt -print0 | xargs -0 md5sum doc.md5 awk {print $1} doc.md5 | sort | uniq -d逻辑说明第一行把语料目录下所有 txt 文件计算 MD5结果写到 doc.md5第二行提取哈希列排序后用 uniq -d 列出出现次数大于 1 的哈希值。要注意这里只列出了重复的哈希如果要拿到具体哪些文档重复需要再 join 回原文件或记录文件名列表。精确去重的局限也在这里一个字节不同就不算重复。实际数据里转载文本几乎都会带后缀、版权声明或编辑修改所以精确去重只能清掉最蠢的那部分重复剩下的交给下一层处理。3.3 MinHash LSH 模糊去重参数怎么设才能稳定跑模糊去重的工业级做法是 MinHash 加 LSH。基本思想是把文本切成的定长 shingle 看成集合两个文档的重复程度用 Jaccard 相似度估计再用 LSH 把高相似度候选分到同一桶里做验证。datasketch 库里这两步都是现成的关键在参数。from datasketch import MinHash, MinHashLSH def char_shingles(text: str, k: int 8): text text.strip().lower() return {text[i:i k] for i in range(len(text) - k 1)} def minhash_for_text(text: str, num_perm: int 128): m MinHash(num_permnum_perm) for shingle in char_shingles(text): m.update(shingle.encode(utf-8)) return m m minhash_for_text(doc) lsh MinHashLSH(threshold0.75, num_perm128) lsh.insert(doc_id, m)逻辑说明char_shingles 按字符窗口生成 n-gram对中文来说一般直接用字级窗口不需要分词MinHash 把这个集合压缩成一个固定长度的签名LSH 用 threshold 控制相似度的判定边界。插入后可以用 query 方法从桶里取出相似文档候选再做精确 Jaccard 计算。参数是这套方案真正要调的地方。num_perm128 是精度和内存的平衡点降到 64 会快很多但小文档的估计误差会明显变大threshold0.75 表示 Jaccard 相似度高于这个值判定为重复0.7 到 0.85 之间都有人在用对新闻转载类语料我一般从 0.8 起调。shingle 长度 k8 适合中文如果语料偏短比如标题和摘要为主降到 5 更合适。提示LSH 是概率型结构相似度恰好卡在阈值附近的文档会漏判。生产环境常见做法是“MinHash 召回候选 精确 Jaccard 验证”两阶段先用宽松阈值召回再用精确计算剔除假阳性。3.4 清洗规则集把 HTML 残迹、乱码和广告位清出去去重之后是清洗。清洗规则的关键是按顺序执行顺序错了效果就差很多。比如先做 HTML 剥离再做 Unicode 规范化否则标签里的实体字符会影响文本统计先过滤语言再过滤长度否则短文本过滤会把混合语言里本来有效的句子误删。步骤规则常用参数HTML 剥离去掉 script/style/header/footer 标签BeautifulSoup 的 decompose()Unicode 规范化NFKC统一全角半角中文语料必须做乱码检测mojibake 正则匹配替换匹配常见乱码字节序列语言过滤fastText lid.176 模型目标语言概率大于 0.8长度过滤句子数大于等于 3字符数大于 200按语种和用途调整行级重复相邻行去重连续出现 5 次以上剔除提醒一句清洗是减法跑完一轮先看数据分布再决定下一轮。很多团队在清洗阶段过度激进把多样化的文本全杀光了模型训练出来语言正确但风格单一。清洗目标是去掉确定有害的内容而不是把语料“打磨”成只有一种形态。4. 质量打分与过滤预训练语言模型数据集的第二道筛子4.1 质量信号的选择困惑度、分类器与规则各有盲区启发式规则能清掉垃圾但抓不住“语言通顺但内容空洞”的文本比如自动生成的商品描述、SEO 堆砌文章、机器翻译的残次品。第二种筛子要给每条文档打一个质量分然后按分数截断。业界常用三类信号困惑度、分类器打分、规则打分它们各有盲区需要组合使用。困惑度的直觉是“一个语言模型觉得这句话有多意外”文本越混乱、越不符合统计规律困惑度越高。它不需要标注数据跑起来快但受模型先验影响大Gopher、T5 做数据集过滤时都实践过这条路线。分类器能捉住“读着通顺但模板化很重”的文本缺点是需要手工标注一部分数据来训练。规则打分在三个方案里最容易被低估。它其实最适合拦截确定性问题比如硬编码的脏词列表、乱码特征、超长无标点段落。它的好处是可解释坏处是覆盖面窄只能做兜底不能做主过滤。4.2 用 GPT-2 困惑度给文档打分能直接跑的样例用困惑度过滤有一个很直接的实现拿一个小型语言模型当评分器。常见做法是加载 GPT-2对每篇文档算平均负对数似然再取指数的平均得到困惑度。打分结果排序后按分位数截断比如保留困惑度最低的 70% 到 80%。from transformers import GPT2LMHeadModel, GPT2TokenizerFast import torch tokenizer GPT2TokenizerFast.from_pretrained(gpt2) model GPT2LMHeadModel.from_pretrained(gpt2) model.eval() def perplexity(text: str) - float: inputs tokenizer(text, return_tensorspt, truncationTrue, max_length512) with torch.no_grad(): logits model(**inputs).logits shift_logits logits[:, :-1, :].contiguous() shift_labels inputs[input_ids][:, 1:].contiguous() loss_fn torch.nn.CrossEntropyLoss() loss loss_fn( shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1), ) return torch.exp(loss).item()逻辑说明模型输出的 logits 和输入标签做平移对齐每个位置的预测概率与真实 token 求交叉熵平均后取指数换算成困惑度。分数越低表示这段文本越符合模型对自然语言的预期。注意这里用truncationTrue截断到 512 token超过 512 的文档只算了前半段算长文档时要分段处理再按 token 数加权平均。提示gpt2 的 tokenizer 是英文 BPE 词表对中文语料打分偏差明显。处理中文时要么用支持中文的 GPT-2 变体要么把打分模型换成多语言模型否则过滤结果会系统性偏向英文语料。4.3 分类器细筛标几百条数据训练一个质量二分类困惑度对模板化文本的识别效果有限。商品页面、SEO 垃圾文语义是连贯的困惑度并不高但内容重复度高、可读性差。这类问题用质量二分类器更合适手工标注几百条高质量和低质量样本训练一个文本分类器然后对全量语料打分。用 fastText 是最低成本的做法。每行一条文本标签前缀用__label__high和__label__low训练和推理都是命令行操作。# 训练一个文本质量二分类器 fasttext supervised \ -input train.txt \ -output quality_model \ -epoch 25 \ -wordNgrams 2 \ -dim 100 # 对单条文档打分 fasttext predict quality_model.bin doc.txt逻辑说明supervised 模式训练的是有监督分类器输入是训练文本输出是质量类别。wordNgrams 2 会让模型看到相邻两个词的组合这对抓“模板化短语”很重要比如商品详情页里反复出现的固定表达。epoch 25 在几千条训练样本上足够再多会过拟合。这一层最关键的是标注质量而不是模型结构和参数。标注时要把低质量样本标注清楚标注重点是“类型”而不是“喜好”比如 SEO 堆砌、机器翻译痕迹、无信息量短句。分类器训练完成后要用一批模型没见过的数据做人工抽检防止分类器把某个领域的正常文本误判为低质量。4.4 三层筛网怎么配合按数据源切分而不是按全量排序三个质量信号不是互相替代而是流水线上下游。我建议的配合方式是第一层困惑度做粗筛按分位数截断把语义混乱的文本清掉第二层分类器只剔除高置信的坏样本避免误杀第三层规则做兜底拦截编码错误和黑名单内容。这里有一个常见的错误做法是把所有来源的文档混在一起统一排序后按比例截断。不同来源的困惑度分布差异很大代码语料的困惑度天然比百科高混在一起会系统性地清掉某一个来源。正确做法是按数据源分组在每个来源内部排序再截断。层级操作目标误杀风险第一层 困惑度按分位数截断去掉语义混乱文本中第二层 分类器高置信坏样本剔除去掉模板化低质文本低第三层 规则黑名单、长度下限拦截编码垃圾最低每轮过滤之后都要做一次“被过滤样本抽检”确认被删掉的文档里没有意外的高价值内容。数据过滤是迭代过程不是跑一条流水线就结束。5. 数据配比与采样预训练语言模型数据集的第二个旋钮5.1 配比决定能力上限从 token 预算反推概率模型最终会具备什么能力很大程度上由语料里各类内容的 token 占比决定而不是由语料文件大小决定。同一批网页文本清洗后有效 token 数可能只剩三成Books 数据文件很大但按行数统计的 token 密度并不高。所以做配比的时候先定目标 token 占比再由各来源的有效 token 量反推采样概率。拿一个通用中文模型举例常见的目标配比大概是清洗网页 60%、书籍与长文 15%、百科 10%、代码 10%、对话与指令 5%。这个比例不是标准答案只是说明性的起点。你的下游任务偏代码就调高代码占比偏知识问答就调高百科和书籍占比关键是把“目标占比”和“实际文件大小”区分开。数据源目标 Token 占比实际数据量说明清洗网页60%大语言底座决定流利度书籍与长文15%中决定长程依赖能力百科10%小事实密度最高代码10%中程序语言与逻辑对话与指令5%小回复格式在计算配比之前先确保每个数据源都经过同一套 tokenizer 统计有效 token 数。不同清洗历史会导致同一份文本的 token 效率差很多用文件大小做分母算出来的权重是错的。5.2 用带温度的采样权重修正数量不均衡配比最大的坑是数据量不平衡。网页语料轻易上千亿 token百科可能只有几十亿直接按目标比例采样几次 epoch 就把百科数据反复学了很多遍导致过拟合。常见做法是引入温度参数做平滑采样。target {web: 0.60, book: 0.15, wiki: 0.10, code: 0.10, chat: 0.05} size {web: 900e9, book: 30e9, wiki: 5e9, code: 40e9, chat: 2e9} T 0.5 raw {k: target[k] / (size[k] ** T) for k in target} total sum(raw.values()) weight {k: v / total for k, v in raw.items()} print(weight) # 数据量小的书籍和对话会被明显过采样网页占比下降逻辑说明每个来源的权重等于目标占比除以数据量的 T 次方。size 的次方项相当于给数据量做缩放T 越小缩放越弱小语料来源会被抬得越高。T1 时完全按 target 比例分配 token小语料会被反复重采样T0 时完全按来源数量比例走目标占比形同虚设。0.3 到 0.7 是常见取值区间T0.5 是个不错的起点。采样权重要在训练循环里转成每个 batch 的实际取数逻辑。常见做法是按权重为每个数据源分配一个采样器batch 的样本来源由 these 权重抽样决定。还要定期检查实际投喂的 token 数与目标占比的偏差因为不同来源的文档长度分布差异很大。5.3 多语言与混合来源的坑别让英文和模板文本主导多语言数据集的第一个坑是英语天然过剩。Common Crawl 里英文网页占到一半以上在没有语言采样策略的情况下英文会挤占其它语言的训练量。第二个坑是低资源语言的文本往往来源单一典型的小语种新闻站就那么几个去重不干净的话模型容易把站点的模板风格当成语言规律。解决多语言不平衡的常见手段是“分语言配比”先用语言识别模型对每条文档打标再按目标语言占比做分组采样。采样时语言概率不是拍脑袋定的而是按下游任务的验证集表现来回调如果某个语言的下游任务分数明显落后优先检查是数据占比不够还是该语言语料去重不干净。代码语料也要单独提一句。代码数据要先按扩展名过滤再按许可证过滤。许可证过滤是硬要求不要因为少部分数据量而模糊处理这直接关系到数据集能不能对外发布和模型能不能商用。6. 先小规模试错再盯三个数据指标6.1 用 1% 的数据跑配方验证大规模预训练跑一次动辄几万美元直接拿最终数据集去训练来验证配比是代价最大的做法。常见做法是先用 1% 到 2% 的数据量保持原有配比不变训练一个小模型到 10B 到 20B token看几组候选配方的 loss 曲线差异。数据量缩小后训练时间从几周降到几小时足够判断哪套配方更平滑、下游任务分数更高。# 从各子集按 1% 等比抽样保持原始配比 for f in subsets/*.txt; do lines$(wc -l $f) shuf -n $(( lines / 100 )) $f ablation_sample.txt done逻辑说明脚本对每个数据源文件按行数抽 1%抽样时使用 shuf 保证随机性。抽样后拼接成一个文本文件供小规模训练使用。这一步的关键是抽样比例必须对所有来源一致否则验证出来的配比分不清是配方差异还是抽样偏差。6.2 训练日志里加三个指标重复率、退避率、有效 token 率数据质量好不好不能只看训练 loss。loss 降得很漂亮但重复率很高说明模型在背数据而不是学语言。训练进入稳定期后我一般会盯这三个指标重复率、退避率、有效 token 率。重复率计算简单可以在训练前对数据集做一次扫描统计出现次数大于 1 的文档在总 token 里的占比。退避率指训练时因长度超限被截断的文档比例退避率过高说明语料准备阶段没做好长度分析。有效 token 率是清洗后 token 数除以清洗前原始 token 数它反映清洗管线的工作效率主要用于评估清洗步骤是否过度。import hashlib from collections import Counter seen Counter() for chunk in chunks: digest hashlib.sha256(chunk.encode(utf-8)).digest() seen[digest] 1 dup_count sum(c - 1 for c in seen.values()) ratio dup_count / len(chunks) print(fduplicate_ratio{ratio:.3f})逻辑说明对每个 chunk 计算 SHA-256 摘要并计数统计计数大于 1 的额外出现次数。这个值除以总 chunk 数就是重复率。注意这里算的是“完全重复”实际生产环境还应配合 MinHash 做模糊重复率统计才能反映近重复文本的比例。6.3 用一组稳定的小任务做锚点loss 降不代表能力涨最后的验证手段是拿一组稳定的小型下游任务做锚点贯穿整个数据调优周期。loss 是全局平均视角下游任务才能暴露具体能力短板。任务集不要求覆盖全面但要稳定、快、和你的数据来源强相关。单点能力推荐小评测数据源相关性知识问答MMLU 子集、常识问答百科、书籍代码能力HumanEval、MBPP代码语料长文连贯摘要、续写评测书籍、长文多语言各语言困惑度对比各语言子集调完一批数据后跑一遍锚点任务并记录分数。最好把每次数据变更和对应分数记录成表这样哪次改动导致哪个能力下降回查数据变更记录就能定位。把重复率统计脚本挂进每天的数据构建流程里再跑一轮小规模训练对比这是当前成本最低、收益最稳定的数据质量闭环。本文还有配套的精品资源点击获取