大模型预训练数据集构建全指南:从选型清洗到配比落地 做预训练这几年我最深的体会是模型架构大家都能抄训练技巧论文里也写得很明白但大模型预训练数据集构建这件事很少有一篇文章能把里面的坑和细节讲透。我见过太多团队把精力花在调结构、琢磨学习率上结果训练一启动就发现loss下不去或者模型知识面异常狭窄最后排查来排查去问题全出在数据上。预训练数据集是整个大模型训练的地基它的质量、规模、配比直接决定模型智商的上限和知识面的广度。数据集没做好哪怕把模型规模翻倍、把学习率调得再花哨也是在地基没打牢的空中楼阁上硬盖楼。这篇文章我把自己在预训练数据集构建这条流水线上的实操经验完整梳理一遍从数据选型、清洗过滤、去重、配比、采样到工程化落地适合准备启动预训练或者正在为领域模型准备重新训练语料的同学参考。就算你暂时不碰全量预训练里面的清洗和配比思路用在增量预训练、领域继续预训练上同样管用。1. 预训练数据集构建的整体设计与思路1.1 为什么说数据集决定了模型的上限先聊一个经常被低估的事实预训练的本质不是让模型背题而是让模型从海量语料中隐式地学习语言规律、世界知识和推理模式。模型架构决定了它有没有能力学进去而预训练数据集构建决定了它能学到什么、学得是否全面。一个经典的量化参考是Chinchilla定律它告诉我们给定模型参数量大概对应多少数据量才划算。比如一个7B模型在约1.4T到2T token的数据量上训练性价比最好。这其中的意思是数据量不够模型学不完数据量过大而模型太小又浪费算力。但Chinchilla定律只说量没有说质。实际操作中你会发现1000亿token的干净、多样、配比合理的数据很可能比4000亿token的堆砌、重复、噪声数据训练出来的模型更强。这一点我在做小规模消融实验时反复验证过同样的模型、同样的训练步数换一套清洗干净的语料下游评测平均能涨3到5个点。数据质量差带来的问题也很典型。重复数据会让模型在训练时反复见到相同句子造成局部记忆过拟合表现为生成时大量复读噪声文本、乱码、错误标注会让模型学到错误映射有毒或者低质内容混进语料轻则影响生成质量重则让输出方向发生偏移。所以数据集构建不是把公开数据下载下来扔给训练脚本就完了它本身就是一个需要精心设计和持续迭代的工程环节。1.2 开源语料库选型从哪里拿到原始素材预训练数据集构建的第一步是决定原料从哪里来。绝大多数团队不会自己爬全网数据成本太高合规风险也大通常做法是基于开源语料做二次加工。这里我列几个我常用的开源数据源以及它们各自的定位。Common Crawl从2008年开始持续抓取的网页快照每月新增几十TB原始网页数据是大规模预训练语料的基础原料。缺点是极其脏直接洗到能用的程度需要做大量过滤和去重。C4Colossal Clean Crawled Corpus谷歌清洗过Common Crawl后公开的版本质量比原始爬虫数据高很多但仍有不少噪声。C4约156B token适合做混合语料中的通用成分。The PileEleutherAI整理的800GB多领域语料囊括网页、书籍、论文、代码、对话等多类数据适合做领域多样性研究。RedPajama-1T开源复刻LLaMA训练数据配比的1.2T token多源语料涵盖C4、Github、Wikipedia、ArXiv、StackExchange、Books等。中文语料中文开源语料相对分散常见的包括WuDaoCorpora、SkyPile以及Hugging Face上各种清洗后的中文网页语料。做中文预训练时我通常会把多个来源都拉下来做领域配比而不是只依赖单一数据源。选型时除了看规模还要看三件事。第一是许可协议很多语料虽然是公开的但禁止商用或者要求保留来源声明做产品时要提前合规。第二是时效性预训练模型的知识截止时间取决于语料时间范围Common Crawl按月出快照需要按业务对时效的要求挑选窗口。第三是加工程度从Raw到半干净再到已去重不同加工程度省下的预处理时间完全不一样但太干净的语料往往也意味着某些信息点被过度清洗掉了后面再做针对性增强比较困难。1.3 一条完整的预处理流水线长什么样预训练数据集构建不是一次性的数据处理任务它是一条多级流水线。我习惯把它拆成六个主要环节每个环节有独立的输入输出和质检标准。原始数据获取与格式标准化把不同来源的数据统一成JSONL格式每条样本包含文本内容和元信息来源、语言、时间、领域标签。粗清洗剔除HTML标签、乱码、控制字符做Unicode规范化处理编码不一致问题。质量过滤通过规则、打分、分类器筛掉低质量文本这一步能砍掉50%以上的原始量。去重做文档级和段落级的精确去重与近似去重防止训练数据重复率过高。领域配比与采样把清洗后的数据按领域打标签再按配比方案做采样生成最终训练集。格式打包与质量抽检按固定长度打包成训练样本随机抽样做人工检查同时统计百分比分布和重复率等指标。这套流水线里最容易被忽略的是最后一步。很多团队处理完数据直接开训结果训了一周才发现数据里有问题。正确的做法是在打包后做一次小样本验证抽2%的数据跑一个几十步的smoke test观察token覆盖率、样本长度分布、batch内的重复度确认没有诡异样本再上全量训练。这个习惯帮我避开过好几次重大返工。2. 数据清洗与过滤从原始语料到高质量语料的核心工序2.1 清洗优先级先解决的三个问题拿到原始语料后不要一上来就写复杂的深度学习过滤器先把最基本的三个问题处理掉能解决大部分脏数据的情况。第一个是编码和格式问题。原始网页数据里经常混着GBK、UTF-16、Latin-1等不同编码直接按UTF-8读取会得到一堆乱码。我一般先做编码探测用charset-normalizer或Chardet检测统一转成UTF-8然后做Unicode标准化NFKC把全角半角、特殊变体收敛成标准形式。接着把HTML标签、Markdown语法残留、控制字符、零宽字符U200B、U200C这种全部清掉。零宽字符是训练数据里最隐蔽的坑肉眼看不出来但会让模型学到奇怪的字符映射导致生成结果里出现不可见伪影。第二个是语言过滤。预训练语料经常是多语言混杂的如果做中文模型就需要把英文、日文、韩文等大段其他语言文本剔掉或者单独分层处理。我用fastText的language identification模型做语言识别速度快、精度也够用对每个文档按语言占比打分目标语言占比低于85%的文档直接丢弃。这里要注意的是混着少量英文对某些中文模型不一定是坏事比如中英混合语料能提升模型对代码和LaTeX的理解所以语言过滤不一定一刀切可以给英文数据单独一个配比通道。第三个是启发式规则过滤。这些规则虽然朴素但性价比极高文档长度少于50个字符的丢弃平均词长度过短或过长的丢弃特殊字符占比超过30%的丢弃重复行占比超过40%的丢弃段落数量太少导致语义密度不足的丢弃。我常用的阈值组合会写进配置文件每次清洗跑完看一眼这些规则的过滤率如果过滤率异常高或者异常低说明源数据发生了结构性变化需要及时检查。规则过滤能砍掉原始爬虫数据里大约40%到60%的垃圾量是整条流水线里产出比最高的一步。2.2 去重精确去重和近似去重怎么选先解释一下为什么去重这么重要。预训练模型的一个关键风险是记忆训练数据而重复出现的文本会显著放大这种记忆效应。比如一段话在训练集里出现了500次模型对它的记忆强度就会远超只出现一次的常规文本生成时一旦触达相似语义就会整段复读。另外重复样本还会让梯度估计发生偏移让模型在重复数据上过拟合降低泛化能力。精确去重是最简单的方案对每篇文档计算哈希值MD5、SHA或更快的xxHash然后删除哈希值完全相同的文档。但网页级别的重复通常不是逐字相同的爬虫抓到的同一篇文章可能因为广告、页脚不同导致文本有细微差异这时候精确去重就失效了。我一般把精确去重用在句子级别先按句号切句再对每个句子算哈希删除重复出现的句子。这一步能在不牺牲多样性太多的情况下去掉大量重复的模板化句子。近似去重才是解决网页级重复的关键工具。业界最常用的是MinHash和SimHash两类算法。MinHash配LSH局部敏感哈希适合在超大规模文档集上求相似文档对它的思路是把文档拆成n-gram集合然后用多个哈希函数做聚合签名最终用Jaccard相似度近似度量文档间的相似性。实际操作中我常用datasketch库设置n-gram size为6LSH的band数为8、row数为16相似度阈值取0.7。这样处理下来能识别出大量经过句子增删、插入广告后依然同源的重复网页把它们从训练集里剔除。SimHash则更适合做相似度检索把文本转成64位哈希指纹两个文档的汉明距离小于等于3就认为是近似重复。它在处理超大规模数据时内存占用更可控但阈值调校的难度比MinHash高一些。我的经验是文档级用SimHash或MinHash做粗筛段落级用精确哈希做细筛两层配合能达到比较干净的重复率指标一般控制在1%以内。2.3 质量过滤规则、打分、分类器三种流派对比规则过滤只能拦下明显不像人话的数据比如乱码、表格碎片、纯标签内容。但更多低质量数据是看着像文章实际毫无价值的比如SEO拼凑文、机器翻译痕迹明显的文本、内容农场的重复汇编。这类数据需要更高阶的过滤手段。第一种是困惑度打分。用一个已经训练好的小型语言模型比如GPT-2或一个专门训练的中文小模型计算每篇文档的困惑度PPL。困惑度高说明文本对模型而言出乎意料通常意味着语法混乱、逻辑不通或者内容拼凑。设置阈值比如PPL超过150的中文文档直接丢弃。这个方法的缺点是PPL受领域影响很大专业论文、代码片段的PPL天然偏高所以阈值要按领域分开设定我通常先按领域聚类再在聚类内部算分位点取后5%到10%作为过滤线。第二种是分类器打分。人工标注一批高质量和低质量样本比如各几千条训练一个轻量级fastText二分类器然后对全量语料打分。相比PPL方法分类器能利用更多特征比如词语分布、句长、重复度、实体密度等对农场景SEO文这类内容识别效果更好而且推理速度非常快在全量清洗时几乎不成为瓶颈。一个实用技巧是把分类器和PPL打分结合两者都通过的文档保留两者有冲突的单独抽出来做人工复核。第三种是更前沿的用大模型蒸馏做质量标注比如Textbooks Are All You Need的做法用大模型生成或改写教科书级别的高质量语料再用这些语料微调一个小模型来做过滤。说实话效果确实好但成本不是普通团队能承受的。我在实际项目里只在最关键的几百万条核心数据上这么做全量数据还是靠规则加分类器完成。质量过滤的目标不是追求100%干净而是在成本和收益之间找到平衡点。我会先做一个清洗小样本对比实验看不同过滤强度下下游评测的变化再决定全量清洗的激进程度。3. 数据配比与采样策略决定模型口味的技术细节3.1 领域配比怎么搭配数据才能全面又不偏科清洗完的数据是一堆干净的混合原材料但不同领域的文本对模型能力的影响差别极大。如果训练集里娱乐八卦占了60%哪怕模型参数量再大数理逻辑能力也会长期偏弱。这就是领域配比要解决的问题按什么比例混合各领域数据才能让模型既全面又不偏科。业界公开的配比方案很有参考价值。以LLaMA为例它的训练数据大致配比是CommonCrawl占67%、C4占15%、Github占4.5%、Wikipedia占4.5%、Books占4.5%、ArXiv占2.5%、StackExchange占2%。你会发现通用网页语料占了绝大多数但代码和论文的比例被有意拔高远超它们在网页自然分布中的占比。这是因为代码数据能显著提升模型的逻辑推理和工具使用能力ArXiv论文则提供了严谨的长篇推理语料这些数据对提升模型聪明程度的帮助远大于同等体量的娱乐八卦网页。做配比时我先给每篇清洗后的文档打领域标签。标签可以用关键词规则快速初标再用之前提到的分类器做更细的划分。常用领域标签包括网页通用、百科、书籍、论文、代码、数学、新闻、对话、法律法规、医疗健康、金融等。打标签完成后再按预先设定的配比表做采样。一个重要建议是配比表不是拍脑袋定死就完事而是要通过消融实验来校准。比如先固定其他配比不变把代码比例从5%提到15%跑一个小规模预训练用代码生成评测和逻辑推理评测对比效果再决定最终配置。配比还有一个容易被忽视的方向短文本和长文本的平衡。如果训练数据里绝大多数都是几十到几百字的短文模型对长上下文建模的能力会很差哪怕位置编码支持再长也没用。所以要专门保留一部分书籍、论文、长报告语料用来训练模型的长文档能力。Books和ArXiv在LLaMA配比里看起来占比不高但它们对长序列建模的贡献非常大。3.2 采样策略温度参数与轮次配平领域配比定下来之后具体怎么从清洗后的数据池里采出目标分布同样有讲究。最朴素的做法是直接打包等于让数据按原始数量占比参与训练但真实网络数据的长尾分布非常恐怖小众领域样本量可能少到直接被淹没。这时候就需要上采样和降采样的配合对低资源领域做上采样让它的出现频率超过自然占比对高资源领域做降采样避免模型被单一类型文本喂养过量。具体实现上我通常用一个温度采样法调节分布。给每个样本赋一个权重权重和它所属领域的数量占比的-α次幂成正比α越大权重越向稀有领域倾斜。实践中α取0.3到0.7比较常见取到1.0时基本是把所有领域拉齐到近似均匀。控制这个α值比手工逐个领域调倍率要平滑得多。要注意的是上采样本身并没有给模型提供新信息它只是改变了同一份数据被看到的次数所以上采样比例过大会导致严重重复记忆一般在领域内部再配合去重策略控制任何单一来源的样本出现次数。数据量和epoch的配平也是预训练特有的问题。按Chinchilla定律7B模型配2T token数据很合适如果只有1T token很多人会想只训一个epoch让模型把数据见一遍。但实际中为了提升模型训练充分度我经常会在部分关键领域做多epoch比如数学、代码这类高质量低重复的数据可以过两遍甚至三遍而通用网页语料只过一遍或零点几个epoch。这种混合epoch策略比全量数据统一epoch效果好得多代价是实现时要以样本为中心做采样调度而不是以文件为单位简单遍历。还有一个细节是课程学习Curriculum Learning思想的应用。我不太建议一上来就把所有长文档、论文、代码直接丢进第一批batch因为模型早期上下文能力弱大量长文本会导致学习效率低。一种实测有效的做法是先用较短文本256到512 token的截断样本预热训几千步再逐渐提高到2048或4096的完整序列。这种渐进式策略能显著提升训练曲线的稳定性尤其适合从零开始的大模型。3.3 数据格式与Tokenizer细节这部分是很多初学者最容易踩的雷数据内容明明很好但格式和分包的细节没处理好训练时各种奇怪问题就冒出来了。先讲存储格式。我习惯把整个数据集切成若干个shard每个shard是一个JSONL文件大约2GB到4GB。每个训练样本是一个JSON对象包含textsourcedomainquality_score这些字段。这样在训练框架里做分布式读取时各GPU卡只需要按shard做数据分配不会因为某个超大文件造成IO热点。还要给每条数据带一个全局唯一ID后续定位问题样本、做数据溯源时这个ID能省下大量时间。再讲打包拼接。预训练时模型的输入是固定长度序列比如2048或4096个token但单篇文档长度往往参差不齐。常见的做法是拿多篇文档拼接成一个训练序列文档之间插入一个特殊分隔符比如|endoftext|。我以前犯过的一个错误是拼接时直接截断导致同一篇文档的内容一半在这一batch、一半在下一个batch而模型上下文窗口根本看不到完整文档训练效果大打折扣。正确的做法是把每篇文档按token长度切分成块最后不足长度的用分隔符补齐尽量让一个训练序列内的内容来自尽可能少的文档保持语义连贯性。更高级的做法是引入重建式打包类似Roberta预训练时的reorganization把文档块按长度匹配组合但工程复杂度和收益不一定成正比数据规模不大时可以先不做只保证不做粗暴截断就好。最后是Tokenizer。预训练之前要先用语料训练一个tokenizer常见方案是BPE或者Unigram词表大小一般选32K到128K。选词表大小是个权衡词表太大会大幅增加embedding参数量小模型可能吃不消词表太小又会增加token数变相拉长序列、增加训练成本。我常用的做法是先对目标语料做统计分析看字符级覆盖率和词频分布然后尝试32K、64K、96K三档词表分别测平均token数再结合模型参数量选一个折中值。Tokenizer一定要用最终配比后的语料来训练不要拿一个通用英文语料训练出的BPE直接跑到中文模型上否则中文会被切成大量单字序列长度直接翻倍训练效率惨不忍睹。字符级逻辑、数字拆分的处理也要专门设计比如让1000成为一个token还是拆成1000会影响模型对数值运算的敏感度。4. 常见问题与排查技巧实录4.1 训练异常先查数据的三个方向大模型训练过程中出现NaN、loss不降、loss突然暴涨很多人第一反应是调学习率、换优化器。我的习惯是先控制变量把数据列为第一嫌疑对象。排查时有三个固定方向。第一是看是否存在异常样本。比如一个文档里包含了一万个连续数字tokenizer会把它切成大量的数字token梯度更新时数值范围可能爆炸又比如个别样本长度异常长超出模型最大长度在截断时留下一个不完整的拼接碎片导致后面跟着大量padding ID。我写了一个简单的数据体检脚本在全量数据里扫描最大值、分位数、异常token占比任何超过预设阈值的样本都会被单独拎出来打印。别小看这种笨办法很多NaN问题就是被一两千条异常样本搞出来的。第二是看数据分布和tokenizer的匹配度。如果语料里有大量词汇在tokenizer中是拆碎的说明tokenizer和语料分布偏差太大也会造成训练时的高loss基线。解决办法是重新训练tokenizer或者在数据清洗阶段对罕见字符做归一化处理比如把特殊符号统一替换成unk之外的专用token。第三是看重复率和临场翻车现象。训练几天后模型开始复读、输出固定模板往往是重复率超标。我常用一个快速指标随机抽1000个batch统计batch内部句子级重复的占比如果超过多个百分点就要回头强化去重。三方面都排查过还找不到问题再去看学习率和模型初始化也不迟。4.2 模型偏科如何用评估集反推配比问题预训练跑完之后最常见的评价是感觉某些领域还行某些领域特别弱。要定位是数据集问题还是模型能力问题我的做法是提前建立一套多领域评估集且这套评估集必须和训练数据完全隔离。评估集至少覆盖几档基础语言能力中文词义、语法判断、常识和百科知识、代码生成与推理、数学计算、专业领域法律、医疗、金融。每个领域准备几百到上千条带答案的样本做成统一评测脚本。如果评测结果里数学和代码很弱而其他领域正常第一步去查代码和数学数据的配比。比如代码类语料在训练集里的占比是否只有不到1%数学文本的长序列样本是否在打包阶段被强行截断了这些都是常见原因。第二步我会做一次增量预训练来验证把数学数据过采样后只训练几千步看评测分数是否明显回升。如果回升明显说明源头就是数据配比不够如果回升有限可能还要查模型规模和知识容量的问题。偏科排查还有一个小技巧用训练数据的领域标签反过来做混淆分析。把评测样本按文本相似度检索训练集里最接近的几千条样本看看评测样本涉及的知识到底有没有出现在训练集里。这个检索我常用BM25或向量检索来做虽然没有严格的理论保证但实操中定位某个领域的知识根本没进入训练数据非常直观比盲调参数有效得多。4.3 数据管线的工程化经验与版本管理预训练数据集构建和普通数据分析最大的区别是清洗规则一变整个数据集就要重跑一遍如果所有中间结果没有做版本管理很快就会陷入数据版本混乱、不知道当前用的哪个版本的噩梦。我踩过这个坑之后养成了几个硬性习惯。第一每个处理节点都要产生独立的输出目录目录名包含处理版本号和参数哈希值。比如清洗参数变了输出目录名就要跟着变。不建议直接在原目录上覆盖写否则想回退到上一个版本哭都来不及。第二用DVC或简单的git-lfs管理数据集的元数据训练框架读取数据时同时读取一个dataset_config.json里面记录数据路径、字符串版本、配比表、处理日期。这样每次训练的重复性才有保障后续写技术报告也清晰。第三建立自动化数据质量报告每次打包完成自动输出token总数、样本数、领域占比分布、重复率、PPL分位数等指标对比不同版本的数据集时用得上。看上去多花一点时间但在动辄几千张卡跑几个月的预训练项目里任何一次数据版本错了的返工成本都是极其惨痛的。数据管线的并行性能也要提前规划。我当时用Spark做全量数据清洗后来又试过Ray Data两者都能满足大规模处理但调优思路略有不同。Spark更吃内存和shuffle配置Ray Data则更容易和Python生态无缝衔接。我个人的建议是如果团队已有Hadoop/Spark基建可以继续用Spark如果从零开始Ray Data上手更快。清洗不是一次性的后续每次调整都要重跑所以管线要写成可配置、可断点续跑的脚本而不是一个跑完就扔的一次性Jupyter notebook。另外最好在一开始就建立一个小规模冒烟数据集比如选几万条文档把全流程跑通并固定下来。每次改动清洗规则后先只重跑冒烟集确认输出格式和质量指标正常再放全量。冒烟集还能用来做训练前的试训——一个足够小但结构一致的数据集用来快速验证训练代码和数据的兼容性能避免很多大规模返工。我自己现在做项目几乎形成了肌肉记忆不管数据看起来多干净正式训练前永远会先跑一遍小步验证确认loss稳得下来确认生成的文本读起来正常再启动全量训练。这个数据先行、小步验证的节奏是从无数次熬夜排查里换来的教训。大模型训练每一个细节都昂贵但预训练数据集构建这个环节绝对值得你花最多的耐心。数据对了后面的训练只是水到渠成。