首页 / 资讯中心 / 文章详情

大模型预训练数据集构建实战:清洗去重、配比与Token化全流程

大模型预训练数据集构建实战:清洗去重、配比与Token化全流程 ★ FEATURED ARTICLE
直接开工。这篇是系列第十六篇前几篇我们把模型架构、分布式框架、并行策略、超参调优都聊了个遍但说实话模型这条路走到越深我越确信一件事预训练数据集才是大模型能力的真正天花板。参数结构决定了下限数据规模和数据质量才决定上限。这一篇就专门把“预训练数据集构建”这件事掰开揉碎从整体设计思路、数据获取、清洗去重、配比混合、Token化、质量验证到踩坑记录给你一份可以直接抄作业的完整实战方案。我默认你准备训练的是 10B30B 参数规模的模型目标 Token 量在 1T2T 左右。如果你做的是 1B 以内的小模型流程可以等比缩水如果是 100B 以上超大模型流程差不多但工程复杂度会再往上翻几倍。这篇文章的方案基于我们自己在真实集群上调试多轮后的沉淀很多细节是公开论文里不会写、但实操中一定躲不开的。1. 预训练数据集构建的整体思路与设计拆解1.1 为什么说数据决定了大模型的“智力天花板”我在初学阶段有过一段错误认知以为模型效果主要靠网络结构、注意力机制、层数堆叠。后来亲手把同样的 LLaMA 结构用不同数据去训练才真正理解那句话——数据质量决定模型上限模型结构只是逼近这个上限的手段。举一个特别直观的例子同样都是 7B 参数A 组用 1.2T 清洗严谨、配比合理的 Token 训练B 组用 300B 没怎么去重、直接从网上爬下来就喂进去的 Token 训练。前者在常识问答、代码生成、多语言能力上会全面碾压后者甚至极端情况下 B 的效果连一些微调过的 3B 模型都不如。原因很简单重复数据导致模型反复“背诵”同一句话浪费了宝贵的参数量去记忆冗余模式低质量数据杂音、乱码、机器生成文本会干扰语言模型的统计规律让生成变得语无伦次领域配比失衡会让模型出现“偏科”——代码语料多了写代码流畅但闲聊降智学术论文多了说话文绉绉但常识缺失。预训练数据集构建的核心目标就三个规模足够大、质量足够高、配比足够合理。规模不够模型学不到足够的世界知识质量不够模型学到的全是噪声配比不合理模型的综合能力就会失衡。所以这一步绝对不能省也绝对不能靠运气。1.2 数据集构建的完整流程与规模估算一套可落地的预训练数据流水线在我这里大致分 7 个环节原始语料获取与合规审查内容提取与标准化语言识别与按需筛选质量过滤分类器 困惑度多级去重精确去重 近似去重领域配比与样本权重调整分词器训练、Token 化与数据落盘每个环节都会直接或间接地决定最终训练效果。以我们训练 20B 模型、目标 1.2T Token 为例做一下规模估算你会对数据量级有更直观的概念目标 Token1.2T这里我按一个 Token 约等于 0.75 个英文单词、或者约 0.6 个汉字来估算。原始文本量要产出 1.2T Token原始文本差不多需要 1.6T2T 字节。因为清洗、去重和过滤环节至少会砍掉 40%60% 的原始语料所以上游采集量通常要准备目标数据量的 2 倍以上也就是 3T4T Token 的原始材料。存储成本原始 JSON 文本按 4T 算三副本存储约 12T配合 SSD 做随机读取加速这部分成本在预算里占比不小但省不了。Token 化后的二进制数据我习惯用 Memmap 格式大概在 2.5T同样三副本存储。这里给出一个我自己常用的快速估算公式原始语料预估体积 目标Token数 × 平均每Token字节数 ÷ (1 - 预估损耗率) 举例1.2T Token × 1.4字节 ÷ (1 - 0.5) ≈ 3.36T 字节损耗率取 0.5 是一个比较保守的经验值如果数据源本身比较干净比如以书籍和论文为主损耗率可以放宽到 0.3如果以 Common Crawl 这类网页数据为主损耗率往 0.6 以上走。拿不准的时候先用小批量样本试跑完整流程再推算整体损耗比拍脑袋靠谱得多。2. 数据获取与初筛从源头选料的门道2.1 常见公开数据源对比与选择逻辑数据源的选择基本决定了预训练数据的“基因”。我整理了一张常用数据源对比表方便你根据模型定位做取舍数据源规模量级优点缺点适合场景Common Crawl 网页快照PB 级体量大、覆盖广、免费噪声多、低质量内容多、需要重度清洗作为底座数据撑规模Wikipedia数十亿 Token质量高、结构规范、百科知识密集规模有限、风格单一知识型底座补充书籍开源书库百亿 Token 级长文本能力强、叙事连贯版权敏感、风格偏文学提升长文生成与推理能力学术论文arXiv、PubMed百亿 Token 级逻辑性强、术语丰富风格单一、公式识别困难提升专业领域能力代码仓库GitHub 等开源代码千亿 Token 级代码能力刚需注释噪声大、重复率高代码模型必备多语言语料多语种新闻、论坛百亿 Token 级多语言泛化低资源语言数据稀缺多语言模型必备对话/论坛数据Reddit、贴吧等百亿 Token 级口语化、问答形式多样质量参差、隐私风险提升对话流畅度我的建议是底座用 Common Crawl精粮用书籍论文Wikipedia代码模型再加 GitHub 代码多语言模型按目标语言占比补多语料。这套组合拳打下来模型的知识密度和文体多样性都有保障。底层数据源的比例建议控制在 70% 左右精粮 30% 左右太高精粮比例会限制模型规模扩展太低又会让模型“营养不良”。2.2 数据获取、提取与初筛实操Common Crawl 不需要自己去爬网页直接下载月度快照的 WARC 文件即可。文件巨大通常用分布式任务并行下载解压。注意 WARC 里除了网页正文还包含请求头和响应头需要用warcio这类库解析出干净的main或article正文。这块工作量大但没太多技术含量核心是别丢字段。自己做针对性爬虫的话优先采集目标领域的头部站点和高质量长文站点爬取时注意规范 robots、限速、去重 URL避免给源站造成压力。采集的原始 HTML 建议保留成 JSON 格式字段至少包含url、html、timestamp、lang方便后面追溯数据来源和分析问题。初筛阶段三个动作要一起做PII 过滤用正则加实体识别把身份证号、手机号、邮箱、家庭住址、银行卡号等个人信息替换或删除。这一步不只是合规问题——模型会“记忆”训练数据如果里面有真实个人信息推理时可能泄漏这是非常严重的隐私事故。有害内容过滤基于关键词黑名单 分类模型双通道把暴力、色情、仇恨言论等有毒内容剔除。不要只用正则效果差至少配套一个 fastText 或 BERT 的分类模型做二次判断。语言识别推荐用 fastText 的lid.176.bin语言识别模型速度快、效果好支持 170 多种语言。实践中我会对整篇文本和文本前 500 字符分别做一次语言判定防止多语言混排的文档被误判。初筛这一环节不要追求一步到位目的只是把明显不该进训练集的东西清掉真正的质量关在下一步。3. 清洗与去重决定数据质量的硬核环节3.1 从精确去重到 MinHash 近似去重去重为什么这么关键重复数据在预训练里不是“无害冗余”而是危险内容。假设一条文本重复出现 100 次模型会在学习中不断强化这条文本的权重把它当成更重要的规律。结果是模型对重复内容的输出概率异常高、泛化能力下降甚至出现“复读机”现象。去重分两层精确去重和近似去重。精确去重最简单对每篇文档做 sha256或 md5哈希值相同的文档只保留一份。注意要做全局去重不能只对单个数据源内部去重不同来源之间完全可能重复比如同一篇文章被多个网站转载。但精确去重只能处理“完全一样”的情况对“改了改标题、调换段落顺序、加了几个错字”的重复就无能为力了所以需要近似去重。近似去重我在实战中最常用的是 MinHash。概念理解起来不难把文档切成若干个连续 n-gram一般取 5-gram 或 6-gram把每个 n-gram 哈希后得到一个集合两个文档的相似程度可以用 Jaccard 相似度来度量Jaccard 相似度 两个文档 n-gram 集合的交集大小 ÷ 并集大小但直接算所有文档两两之间的 Jaccard 复杂度太高了。MinHash 的秘密在于通过对集合做多次随机排列取每次排列后集合的最小哈希值生成固定长度的“签名向量”。签名向量中相等的分量比例就是原集合 Jaccard 相似度的无偏估计。然后用 LSH局部敏感哈希把签名按 band 分桶只对映射到相同桶的文档对做进一步的精确相似度计算极大压缩了计算量。实际动手时直接用datasketch这个库就能实现 MinHash LSH。我把核心思路贴出来from datasketch import MinHash, MinHashLSH # 取 5-gram 并做初步哈希 def ngrams(text, n5): tokens text.split() for i in range(len(tokens) - n 1): yield .join(tokens[i:in]) # 生成文档的 MinHash 签名 def minhash_from_text(text, num_perm128): m MinHash(num_permnum_perm) for gram in ngrams(text): m.update(gram.encode(utf-8)) return m # 建 LSH 索引先只对疑似重复对做精查 lsh MinHashLSH(threshold0.7, num_perm128)注意 threshold 参数要根据你的业务语义调节。经验值文档级去重 threshold0.70.8 比较合适低于 0.7 会把主题相同但内容差异较大的文档误杀句子级去重 threshold 建议 0.80.9抓完全重复或近似复述的句子时效果更好。如果你训练集规模特别大可以先按 64-gram、128-gram 分段做“局部指纹”再在指纹级去重效率更高。还能再加一个后缀数组Suffix Array来精确找出长公共子串对代码语料效果尤其好因为代码仓库经常有大量复制粘贴的代码块。常用工具是Borg或者你自己实现一个简单版本。想省事的话text-dedup这个开源库把 MinHash、SimHash、精确去重都封装好了可以直接集成到自己的流程里。3.2 质量过滤分类器打分与困惑度阈值双通道网页数据里充斥着广告、SEO 堆砌、机器翻译、乱码、无意义符号这类低质量内容光靠规则清不完需要建一个质量过滤器。我采用的是分类器 困惑度两条腿走路。分类器方案人工抽几千条高质量文本和几千条低质量文本训练一个 fastText 二分类器。特征用 n-gram 词袋训练速度很快。分类器输出P(高质量)作为文本质量分设定阈值过滤。比如我习惯把分数低于 0.4fastText 默认概率校准不算完美但排序能力没问题的文本直接丢掉对 0.40.7 的中间带做二次格外的规则检查。困惑度PPL方案用一个小规模但训练充分的语言模型比如 GPT-2 或一个 1B 自训模型计算每条文本的平均困惑度。困惑度越低说明文本越符合自然语言规律越高越可疑。实际操作时把 PPL 某个阈值的文本过滤掉。这个阈值需要在自己语料上跑分布统计后定——先随机抽 10 万条样文算 PPL画个直方图看尾部在哪个位置断层把 cutoff 放在断层附近。这两种方案建议并行使用不要只用其中一种。我遇到过纯用 PPL 漏掉“流利但无意义”的文本例如顺着话术不断说车轱辘话PPL 反而很低也遇到过纯用分类器漏掉“不常见话题但质量正常”的内容低资源语言语料尤其明显。双通道联合后误杀率和漏网率都可以降到可接受范围。顺带提一句长度过滤也不能省。我一般过滤掉小于 200 字符的“碎片文本”几乎都是导航栏、评论片段、验证码提示串因为碎片序列无法提供完整语义上下文但对超大文本要小心整页拆出的超长文本比如整本书一次性塞一个文件可以切片到 2048 token 以下再做长度过滤以免误伤书籍这类高价值长文本。4. 数据配比、分词器训练与数据落盘4.1 领域配比不同来源的“菜谱”怎么定数据配比会直接改变模型的下游能力分布。同一批清洗后的语料A 配比训出的模型代码能力强但逻辑推理弱B 配比训出的模型百科知识丰富但写代码像呓语这不是玄学是统计学上的必然——模型只会从它见过的词序列分布里学模式。公开的知名模型配比是很好的参考起点。GPT-3 用了约 60% 的 Common Crawl 22% WebText2 8% 书籍 3% Wikipedia 其他LLaMA 系列则在维基、书籍、论文上给了更高权重代码模型CodeLlama、StarCoder会把代码语料占比提到 30% 以上甚至更高。我不是让你照抄而是建议按自己的应用场景倒推配比通用助手型核心诉求是知识问答和对话50% 网页 15% 书籍 10% 论文 10% 代码 10% 多语言 5% 其他。代码开发型目标是自动补全和代码生成35% 代码 30% 网页技术博客/文档/问答 15% 书籍 10% 论文 10% 其他。垂直领域型比如医疗或法律领域语料建议直接拉到 50% 以上否则模型很难在专业文本上形成深层的领域能力。配比还有个容易忽略的细节按 Token 量算而不是按文档数算。一份代码文档平均 Token 数可能只有网页的一半相同的文档数下代码语料的贡献度远不如网页语料。我习惯先把各领域语料 Token 数统计出来再按目标占比倒推每类的体积和份数。再提一个进阶玩法数据退火Data Annealing。做法是在训练的最后 5%10% 步数里把高质量领域书籍、论文、代码的采样权重临时提高让模型在收敛末期接触到更大比例的“精品内容”。这像炒菜最后撒一把香菜——整个训练都在积累基础味道最后阶段集中强化高品质细节。实证下来数据退火对常识能力、代码能力的提升都很可观而且只需要改采样器权重不增加任何训练成本。4.2 分词器训练BPE 与词汇表选择数据配比确定后就要做 Token 化。先训练分词器再对全量数据做 Token 化落盘。现在主流是 BPEByte Pair Encoding和 Unigram 两种。BPE 按字符/字节对高频共现逐步合并擅长处理多语言混合场景Unigram 用概率模型从候选子词集合里挑最优切分对形态复杂的语言中文、日文表现更稳。如果训练数据是多语言的我建议用 Unigram如果纯英文或纯代码BPE 完全够用可选方案是直接用 SentencePiece 库实现。词汇表大小一般选 32K、64K 或 96K。参数规模小于 3B 时 32K 性价比最高10B 以上建议 64K代码模型可以试试 96K代码 token 种类多更大的词表能降低序列长度。词表太大也有代价嵌入矩阵和输出头的参数会膨胀增加显存开销和收敛难度。我的经验是 20B 模型搭配 64K 词表比较均衡序列长度 4096 时每一步训练成本相对可控。分词器训练时加入一个操作能让你后期省很多事用全量语料的 0.1%1% 抽样来训练分词器样本要覆盖所有领域和语言别只拿网页语料去训。如果分词器没见过足够多的代码和低资源语言实际 Token 化时会产生大量 UNK 或切得很碎。# SentencePiece 训练示例字符覆盖模型、vocab size 64K spm_train \ --inputsampled_corpus.txt \ --model_prefixsp_64k \ --vocab_size64000 \ --model_typeunigram \ --character_coverage0.9995 \ --shuffle_input_sentencetrue \ --input_sentence_size30000004.3 数据落盘切片、混洗与检查点数据预处理最后一步是把 Token 化结果落盘成训练可直接读取的格式。我一般不直接把每篇文档拼成一个超长序列而是按固定长度切片比如每片 2048 Token然后做全局随机混洗。直接按原始文档顺序排列会有两个问题一是相邻文档主题高度相关同一网站上连续爬取的页面导致训练出现局部偏差二是同一文档切出的连续序列容易过长让模型在长程依赖上偷懒。落盘格式我用的是二进制 Memmap# 伪代码示意把 token ids 写入 uint16 数组按 2048 切片 import numpy as np tokens_all [...] # 全量 token id seq_len 2048 num_seq len(tokens_all) // seq_len data np.array(tokens_all[:num_seq * seq_len], dtypenp.uint16) data data.reshape(num_seq, seq_len) np.save(pretrain_data.npy, data) # 后续训练 torch.from_numpy(np.load(...)) 即可注意 Token id 超过 65535 就要改成 uint32否则溢出。另外我习惯把原始 JSON 文本、清洗后文本、Token 化数据分三层存储每一层都加版本号。数据版本管理是预训练工程里最容易被低估的环节——模型训练到一半发现数据有 bug如果没版本号想回溯到底哪批数据出了问题会非常酸爽。用 Git LFS 管小文件大文件用清单文件manifest.csv记录每批数据的生成时间和清洗参数。到这里数据集的构建已经走完一大半。但千万别急着开训先做一轮质量验证。5. 数据质量验证与迭代别等训完才发现数据有问题5.1 训练前的小规模试训与三向诊断我见过不少团队把数据集构建当成“一次性产出”清洗完直接丢给训练脚本跑了两周以后 loss 曲线开始异常回头查才发现某批数据中间混入了大量重复文本或者语言标签打错了。等训练到一半再返工等于前面的 GPU 费用全部打水漂。所以我的习惯是大规模训练前先跑一个小规模试训。试训配置可以很轻量用一个 100M300M 的模型在 1B2B Token 的小样本上训练几百步重点观察三个指标Loss 曲线形状正常情况下 loss 应该稳定下降、无明显震荡。如果 mid 阶段突然出现 loss 低谷后又反弹很可能是混入了高重复数据导致模型先“背”后“忘”。生成质量抽查训练几百步后手动生成几条文本看看是否语句通顺、有无乱码、是否出现莫名其妙的人名地名。生成结果语无伦次先别怀疑模型大小优先怀疑数据质量。重复率检测用小模型在验证集上算重复 n-gram 比例如果重复率明显高于正常水平说明去重环节还缺火候。5.2 常见诊断脚本与指标参考下面这个脚本虽然简单但我每次做数据集验证都会跑一遍from datasets import load_from_disk from collections import Counter ds load_from_disk(your_tokenized_data) seqs [s for s in ds[input_ids]][:10000] # 统计连续重复 2-gram 的比例 repeat_count 0 total_count 0 for seq in seqs: grams list(zip(seq, seq[1:])) total_count len(grams) dup {g for g, c in Counter(grams).items() if c 1} repeat_count len(dup) print(f重复2-gram占比: {repeat_count / total_count:.4f})经验上在线网页类数据中重复 2-gram 占比超过 1% 就需要警惕超过 2% 基本可以判定去重不彻底。但代码类数据重复率天然偏高因为有大量重复语法结构这个阈值要放宽到 4%5% 再下结论。5.3 数据版本管理与回流机制数据不可能一次做到完美所以我一向把数据集当作“活的产物”而不是“一次性交付物”。我在团队里推行的管理方式每次清洗流程变更都要生成新版本目录目录名包含日期和核心参数例如pretrain_v3_20250301_minhash07_thresh04用一张manifest.csv记录每个版本对应的清洗参数、各领域占比、过滤阈值、具体数据源清单试训发现问题后直接回到清洗 pipeline 调整参数、生成新版本、重新试训直到诊断指标通过再开大规模训练。整个过程有点像一个“数据炼油厂”——从原油到成品油要多道工序反复调试急不得。6. 预训练数据集实战中的常见问题与排查技巧6.1 问题速查表现象、原因与解法我把自己踩过以及在社区里高频看到的问题整理成了速查表遇到异常现象可以先在这里定位问题现象可能原因排查方式解决方案训练 loss 后期开始反复震荡重复数据混入、数据顺序未被全局洗牌检查重复 token 占比、shuffle 随机种子重新做全局去重 切分后全局 shuffle模型生成内容出现大量“复读机”某条文本在语料中重复次数过高检索生成文本与训练集相似度加强近似去重、对高频句块做全局去重多语言语料测试效果很差语言识别误判导致语料失衡按语言统计 token 占比与 PPL 分布用多语言分类器重筛人工抽查低资源语言模型在隐私内容上表现出“记忆”PII 过滤不彻底、直接训练了原文对生成文本回测训练集检索增强 PII 过滤 去重后按需重训代码补全能力显著低于预期代码语料占比偏低或代码文本解析错误统计代码 token 占比增加代码语料并修复注释/文档字符串解析小规模试训 loss 突降后反弹存在大量完全重复样本检查重复率指标重新去重或提高 LSH 阈值6.2 几则实操踩坑记录血泪教训踩坑一测试集被污染模型“高分低能”。有一次我们拿公开 benchmark 测模型分数非常漂亮但人工一测就露馅。后来定位到原因我们用的训练语料里包含了 benchmark 测试集的原文模型相当于“背了答案”。从那以后我在数据清洗的最后一步增加了一个“黑名单过滤”把常见评估集、已知测验集、网上公开答案的文档哈希提前建库在预训练数据里全局剔除。这个动作成本极低但能避免后续整个评估环节失真。踩坑二清洗过度把长尾知识也洗没了。早期我为了追求数据纯净度把过滤阈值调得特别高PPL cutoff 一路收紧。结果模型训练完以后主流知识没问题但问一些偏门但真实存在的事实比如小众开源项目、地方志、特定人物生平模型完全哑火。原因就是过度过滤把长尾但真实的知识样本误杀掉了。现在我对过滤策略多了一分谨慎核心目标是去掉“不真实、无意义”的内容而不是只保留“高频、主流”的内容。只有 4 分以上再丢中段 67 分的内容可以留一部分给长尾多样性。踩坑三分词器只拿网页语料训练中文和代码被切得稀碎。早期图省事直接用 Common Crawl 抽样的英文数据训练 SentencePiece结果中文语料 Token 化后序列长度暴涨训练效率直线下降。后来重新用多领域多语言混合样本训练分词器中文的 Token 化效率明显改善。这件事的教训就是分词器的训练数据分布必须贴近最终训练数据分布。踩坑四忘做跨数据集全局去重。我第一次构建数据时对 Wikipedia、书籍、网页分别做了内部去重但跨数据集的重复一个没管。后来发现大量书籍内容在网页上被全文转载导致等价内容在训练集里出现了多遍。全局去重和单源去重的计算量不在一个量级但这件事不能省。建议按数据源分布拆分任务用 MinHash LSH 在那批最大的语料上建索引把其他所有语料逐条去比对能省不少时间。6.3 工具链与流程自动化建议数据集构建涉及的环节多、数据量大全手动跑根本不现实。我推荐把整个流程串成 DAG 工作流比如用 Airflow、Argo Workflows 或者干脆就是一组 Makefile shell 脚本把每个环节做成独立任务中间产物落盘失败自动重试。这样做的好处是每个中间产物都能被监控、审计、回溯。另外强烈建议在流程里记录所有“关键指标”比如每轮清洗去重后剩余 token 数、各领域占比变化、去重率、过滤率。训练结束后再回看这些指标对于分析模型行为和排查数据问题有巨大价值——我很多次定位模型奇怪的偏差都是通过回看数据指标而不是重读文档解决的。整个预训练数据集构建写下来我自己最大的体会是这是一项 70% 工程化 30% 研究品味的工作。工程化的部分——分布式清洗、去重算法、存储优化——都是可复制的但那 30% 的品味比如过滤阈值放在哪、配比怎么微调、什么数据该留什么该丢只能靠一轮轮试错去积累手感。所以别指望第一次就把数据集做到完美把流水线和验证机制搭好反复迭代你手里的数据质量会一版比一版好。
阅读完成 · 觉得有帮助?
咨询建站