预训练数据集这件事,很多人以为就是"把一堆文本打包成jsonl",真上手才发现坑远比想象的多。我见过太多团队在微调阶段反复调参却收效甚微,最后回头一查,问题根本不在模型和超参上,而是预训练数据本身的质量和结构出了岔子。这篇就围绕大模型训练全流程里的预训练数据集构建环节,把我在实际项目里踩过的坑、验证过的流程、以及那些文档里不会写的细节,完整地摊开讲一遍。不管你是刚接触大模型训练的新手,还是已经跑过几轮微调想回头夯实数据基础的老手,这篇内容都能给你一套可以直接抄作业的构建思路。
1. 预训练数据集到底在训练什么
1.1 预训练和微调的数据诉求根本不是一回事
先把一个常见误区掰开。很多人把预训练数据和微调数据混为一谈,觉得都是"喂给模型的文本",格式差不多就行。实际上这两者的目标完全不同。预训练阶段,模型学的是语言的统计规律、世界知识的分布、以及token之间的共现关系,它需要的是大规模、多样化、低噪声的原始语料。而微调阶段,模型学的是"针对特定任务该怎么回答",需要的是高质量、强对齐、格式规范的指令数据。
这个区别直接决定了数据构建的策略。预训练数据可以容忍一定程度的冗余和杂乱,因为模型本身就是在海量数据里做统计学习,个别噪声会被稀释掉。但预训练数据绝对不能容忍系统性的偏差——比如某个领域的语料占比过高,模型就会在这个领域过拟合,在其他领域表现拉胯。我做过一个对比实验,同样10GB的语料,一个是均匀混合的通用文本,一个是某垂直领域占比70%的文本,后者在通用问答上的表现明显下降,这就是数据分布偏差带来的直接后果。
所以构建预训练数据集的第一原则是:先想清楚你要模型学什么,再决定喂什么。如果你要做的是通用能力模型,那数据的多样性和覆盖面就是第一优先级;如果你要做的是领域增强模型,那领域语料的深度和纯度才是核心。
1.2 数据规模、质量、多样性三者的取舍逻辑
这三个维度不可能同时拉满,实际项目里必须做取舍。我的经验是分阶段处理:
- 冷启动阶段:优先保证多样性,规模可以适当放小,质量做基础清洗即可。这个阶段的目标是让模型建立基本的语言能力,数据太单一会导致模型"偏科"。
- 增强阶段:在多样性达标的基础上,逐步提升质量门槛,同时扩大规模。这时候要开始做去重、过滤低质内容、平衡各领域占比。
- 精调阶段:质量优先,规模反而可以收缩。用最高纯度的数据做最后一轮训练,把模型的能力"压实"。
这里有个反直觉的点:数据不是越多越好。我试过把语料从50GB扩到200GB,结果模型在验证集上的loss反而上升了。排查后发现,新增的150GB里有大量重复内容和低质网页文本,这些数据不仅没带来增益,反而稀释了原有高质量数据的信号。后来我把新增部分做了严格去重和质量过滤,只保留了40GB,效果立刻回来了。
1.3 从token预算倒推数据量的计算方法
很多人构建数据集时是"有多少用多少",这是不对的。正确的做法是从训练目标倒推。大模型训练有一个经验公式:训练token数 ≈ 模型参数量 × 20。也就是说,一个7B参数的模型,理想情况下需要大约140B token的训练数据。
但这是理想值,实际项目里受限于算力和时间,往往达不到。这时候就要做权衡:如果token预算只有理想值的1/5,那就要在数据质量上做补偿——用更高质量的数据,让每个token的信息密度更高。
计算数据量时还要注意token和字符的换算。中文大致是1个token对应1.5到2个汉字,英文是1个token对应4个字符左右。所以如果你有100GB的中文纯文本,大概对应50B到70B token。这个换算在规划阶段非常关键,能帮你快速判断手头的数据够不够用。
2. 语料来源的筛选与采集实操
2.1 通用语料和领域语料的配比策略
语料来源决定了模型的知识边界。我的做法是把语料分成三层:
| 层级 | 类型 | 占比建议 | 作用 |
|---|---|---|---|
| 底层 | 通用网页文本、百科、书籍 | 60%-70% | 建立基础语言能力和通用知识 |
| 中层 | 垂直领域文档、技术资料 | 20%-30% | 注入领域知识 |
| 顶层 | 高质量问答、结构化文本 | 5%-10% | 提升推理和指令理解能力 |
这个配比不是固定的,要根据模型用途调整。做通用助手就偏底层,做行业模型就偏中层。但顶层数据无论什么场景都不能省,它是拉开模型质量差距的关键。
采集通用语料时,网页文本是最大来源,但也是最脏的。我一般会用Common Crawl这类公开语料做基础,然后自己做清洗。领域语料则要靠定向采集,比如技术文档可以从开源项目的README、官方文档、技术博客里抓,这些内容结构清晰、噪声低,性价比很高。
2.2 采集过程中的去重:从文档级到段落级
去重是数据构建里最容易被低估的环节。我见过一个项目,语料里同一篇技术文章出现了上百次,原因是采集时不同来源都转载了同一内容。这种重复会让模型对这部分内容过度学习,相当于变相增加了它的权重。
去重要分三个粒度做:
- 文档级去重:用MinHash或SimHash做近似去重,阈值一般设在0.8左右。完全相同的文档直接删,高度相似的保留一篇。
- 段落级去重:同一文档内部也可能有重复段落,尤其是模板化的网页。用滑动窗口做段落指纹比对,重复段落只保留一次。
- 句子级去重:这个粒度最细,主要针对那些高频出现的套话、免责声明、导航栏文本。用n-gram匹配就能过滤掉大部分。
实操中我推荐用datasketch这个库做MinHash,配合LSH做快速检索,处理千万级文档也就几个小时。代码大概长这样:
from datasketch import MinHash, MinHashLSH def get_minhash(text, num_perm=128): m = MinHash(num_perm=num_perm) for token in text.split(): m.update(token.encode('utf8')) return m lsh = MinHashLSH(threshold=0.8, num_perm=128) for idx, doc in enumerate(documents): m = get_minhash(doc) lsh.insert(idx, m)注意:去重阈值不要设太低,0.7以下容易把正常的不同文档误判为重复,尤其是技术文档里术语和句式本来就相似。
2.3 低质内容的识别与过滤规则
低质内容是预训练数据的隐形杀手。它们不会让训练报错,但会悄悄拉低模型质量。我总结了几类必须过滤的内容:
- 乱码和编码错误:表现为大量非常用字符、问号、方块。用字符集检测就能识别。
- 模板化文本:比如"点击这里""版权所有""转载请注明出处"这类。用规则匹配加频次统计过滤。
- 过短文本:少于50个字符的文档基本没有训练价值,直接删。
- 高重复度文本:同一句话反复出现的文档,说明是机器生成的垃圾内容。
- 敏感和违规内容:这个必须用专门的分类模型过滤,规则匹配覆盖不全。
我一般会写一个多级过滤器,先过规则,再过模型,最后人工抽检。规则过滤能干掉80%的明显垃圾,模型过滤处理剩下的疑难杂症。抽检环节不能省,我每次都会随机抽200条看,经常能发现规则和模型都漏掉的问题。
3. 数据清洗的完整流水线设计
3.1 清洗流水线的阶段划分与顺序
清洗不是一步到位的,要分阶段做,而且顺序很重要。我的流水线是这样的:
- 格式统一:把所有来源的数据转成统一的纯文本格式,去掉HTML标签、Markdown标记、特殊符号。
- 基础清洗:去乱码、去控制字符、统一标点、修正编码。
- 质量过滤:按上面的规则和模型过滤低质内容。
- 去重:文档级、段落级、句子级三层去重。
- 语言识别:标记每条数据的语言,方便后续按语言配比。
- 敏感过滤:过一遍安全分类模型。
- 最终抽检:人工检查各阶段效果。
这个顺序不能乱。比如去重必须放在清洗之后,因为清洗会改变文本内容,如果先去重再清洗,清洗后可能又产生新的重复。语言识别也要放在清洗后,因为乱码会影响语言判断的准确率。
3.2 HTML和Markdown文本的提取要点
网页和文档类语料最大的问题是带格式。HTML要提取正文,Markdown要去掉标记但保留结构信息。这两类处理不好,会引入大量噪声。
HTML提取我推荐用trafilatura,它对正文的识别准确率比BeautifulSoup高不少,能自动去掉导航栏、广告、页脚这些干扰内容。用法很简单:
import trafilatura downloaded = trafilatura.fetch_url(url) text = trafilatura.extract(downloaded, include_comments=False, include_tables=True)Markdown处理要复杂一些。不能简单地把所有标记删掉,因为标题、列表、代码块这些结构信息对模型理解内容有帮助。我的做法是把Markdown转成带层级标记的纯文本,比如把# 标题转成[H1] 标题,把代码块用特殊标记包起来。这样模型能学到结构信息,又不会看到原始的Markdown符号。
提示:处理Markdown时要注意换行符。不同来源的Markdown换行规则不一样,有的用两个空格加换行,有的用空行。统一转成标准换行,否则会影响后续的分段和去重。
3.3 清洗效果的量化和验证方法
清洗做完不能凭感觉说"干净了",要量化。我一般看几个指标:
- 保留率:清洗后数据量占原始数据量的比例。通用语料一般在30%-50%,领域语料能到60%-70%。如果保留率过低,说明过滤太狠;过高则说明过滤不够。
- 重复率:清洗后数据里重复内容的比例,应该低于1%。
- 平均长度:清洗后文档的平均字符数,太短说明碎片化严重。
- 语言分布:各语言占比是否符合预期。
除了这些统计指标,我还会做抽样人工评估。随机抽100条,逐条看质量,记录问题类型和频次。这个环节最费时间,但最能发现问题。我有一次抽检发现,某个来源的数据虽然通过了所有自动过滤,但内容全是机器翻译的产物,语句不通顺。这种问题只有人工看才能发现。
4. 数据格式与LlamaFactory的对接细节
4.1 预训练数据的标准格式与字段设计
预训练数据的格式比微调数据简单,但字段设计有讲究。最基础的格式是纯文本,一行一条:
{"text": "这里是预训练语料内容..."}但实际项目里我建议加上元数据字段,方便后续做数据分析和配比调整:
{ "text": "这里是预训练语料内容...", "source": "web", "language": "zh", "domain": "tech", "quality_score": 0.85, "token_count": 128 }这些字段在训练时不一定都用得上,但在数据管理和问题排查时非常有用。比如发现模型在某领域表现差,可以快速定位是哪个来源的数据出了问题。
4.2 LlamaFactory的数据注册与配置
LlamaFactory对预训练数据的支持很友好,核心是两步:注册数据集和配置训练参数。
注册数据集时,在data/dataset_info.json里加一条:
{ "my_pretrain_data": { "file_name": "pretrain_data.jsonl", "formatting": "text", "columns": { "prompt": "text" } } }注意formatting要设成text,这是预训练模式的标志。如果设成alpaca或sharegpt,LlamaFactory会按指令数据的格式处理,那就跑偏了。
训练配置里,关键参数是stage要设成pt(pretrain的缩写):
stage: pt model_name_or_path: /path/to/base/model dataset: my_pretrain_data cutoff_len: 2048 max_samples: 100000cutoff_len是单条数据的最大长度,超过会被截断。预训练数据一般设2048或4096,具体看你的语料长度分布。如果大部分语料都很长,设太小会丢信息;设太大又浪费显存。
4.3 数据加载中的常见报错与排查
对接LlamaFactory时最容易遇到几个报错,我列一下排查思路:
| 报错信息 | 原因 | 解决方案 |
|---|---|---|
| KeyError: 'text' | 字段名不匹配 | 检查columns配置和实际jsonl的字段名 |
| UnicodeDecodeError | 编码问题 | 统一转成UTF-8,去掉BOM头 |
| 数据量为0 | 路径或格式错误 | 检查file_name路径,确认formatting设置正确 |
| 显存溢出 | cutoff_len太大 | 减小cutoff_len或减小batch_size |
我踩过最坑的一个是BOM头问题。有些Windows下生成的文件带BOM,LlamaFactory读取时会把BOM当成内容的一部分,导致第一条数据的开头多了几个不可见字符。排查了半天才发现是编码问题。解决办法是用utf-8-sig编码读取,或者提前用脚本去掉BOM。
5. 数据配比与课程学习的实战策略
5.1 多来源数据的混合比例怎么定
数据配比是预训练里最玄学的部分,但也不是完全没规律。我的经验是分三步定配比:
第一步,按目标能力定大方向。如果模型要强通用能力,通用语料占70%以上;如果要强领域能力,领域语料可以提到40%-50%。
第二步,按数据质量做微调。质量高的来源可以适当提高占比,质量一般的压低。我一般会给每个来源算一个质量分,然后按质量分加权。
第三步,小规模实验验证。定好配比后,先用小模型或小数据量跑一轮,看loss曲线和验证集表现,再调整。这一步不能省,我见过太多拍脑袋定配比最后翻车的案例。
5.2 课程学习:从易到难的数据排序
课程学习(Curriculum Learning)在预训练里效果很明显。核心思想是让模型先学简单的,再学复杂的。具体到数据上,可以按几个维度排序:
- 长度:先短后长。短文本结构简单,模型容易学。
- 质量分:先高后低。高质量数据信号强,能帮模型快速建立基础。
- 领域难度:先通用后专业。通用语料覆盖面广,专业语料深度大。
实操中我一般把数据分成3到5个难度档,训练时按档位逐步加入。比如前20%的训练步只用最简单的一档,中间50%加入中等难度,最后30%加入最难的数据。这样模型的学习曲线更平滑,最终效果也比随机打乱要好。
5.3 动态配比调整的监控指标
训练过程中数据配比不是一成不变的,要根据模型表现动态调整。我一般监控这几个指标:
- 各领域验证集loss:如果某领域loss下降慢,说明该领域数据不足或质量差,要增加配比。
- 梯度范数:梯度异常大说明当前batch的数据可能有问题,要检查。
- 生成样本质量:定期让模型生成样本,人工评估各领域表现。
这些指标要定期看,我一般每训练1000步就做一次全面评估。发现异常及时调整,比训练完再回头补救成本低得多。
6. 那些文档里不会写的踩坑记录
6.1 数据量虚高:重复内容带来的假象
这是我踩过最大的坑。项目初期我统计语料有80GB,觉得够用了,结果训练效果一直上不去。后来做去重分析发现,实际去重后只有35GB,接近一半是重复内容。这些重复内容让数据量看起来很美,实际上模型学到的东西很有限。
更隐蔽的是近似重复。有些内容不是完全一样,但改了几个词,MinHash阈值设高了就检测不出来。我后来把阈值从0.9降到0.8,又揪出了一批近似重复。所以去重阈值要反复调,不能一次定死。
6.2 编码问题导致的隐形数据损坏
编码问题是隐形的,不会报错,但会悄悄污染数据。我遇到过几种情况:
- GBK当UTF-8读:中文变成乱码,但程序不报错。
- BOM头残留:文件开头多了不可见字符,影响第一条数据。
- 混合编码:同一个文件里不同部分编码不一样,读取时部分乱码。
解决办法是统一转码流程,所有数据进来先过一遍编码检测和转换。用chardet检测编码,统一转成UTF-8无BOM格式。这个步骤看似简单,但能避免后面一大堆莫名其妙的bug。
6.3 清洗过度:把有用信息也过滤掉了
清洗要适度,过度清洗会损失有用信息。我有一次为了追求"干净",把过滤阈值设得很严,结果保留率只有15%,大量正常内容被误删。模型训练后表现反而更差,因为数据多样性被破坏了。
判断清洗是否过度的标准是保留率和效果的平衡。如果保留率低于30%,就要检查过滤规则是不是太严了。我一般会做A/B对比:用不同清洗强度的数据各训一个小模型,看验证集表现,选效果最好的那个强度。
6.4 格式转换中的信息丢失
格式转换是最容易丢信息的环节。比如HTML转纯文本时,表格结构丢了;Markdown转纯文本时,代码块和正文混在一起了。这些信息丢失在训练时看不出来,但会影响模型对特定内容的理解。
我的做法是保留结构标记。HTML转文本时,把表格转成用竖线分隔的文本;Markdown转文本时,用特殊标记标注代码块、标题、列表。这样既去掉了格式符号,又保留了结构信息。虽然处理起来麻烦一点,但对模型理解内容帮助很大。
7. 数据质量评估的落地方法
7.1 自动评估指标体系的搭建
自动评估能快速筛出明显问题,我一般用这几个指标:
- 困惑度(Perplexity):用一个小型参考模型算语料的困惑度,异常高或异常低的都要检查。异常高说明内容不通顺,异常低说明可能是重复内容。
- 重复率:n-gram重复率,超过阈值的文档标记为可疑。
- 字符分布:统计字符类型分布,异常分布说明可能有编码问题。
- 长度分布:长度分布应该符合预期,出现异常峰值要排查。
这些指标我一般做成一个评估脚本,每次数据更新后自动跑一遍,生成报告。这样能快速发现数据质量问题,不用等到训练时才发现。
7.2 人工抽检的抽样策略与评估表
自动评估覆盖不了所有问题,人工抽检必须做。但人工抽检不能随机抽,要分层抽样:
- 按来源抽:每个来源都抽,占比大的多抽。
- 按质量分抽:高质量、中等、低质量各抽一部分。
- 按长度抽:短、中、长各抽一部分。
抽检时用统一的评估表,记录问题类型和严重程度。我用的评估表大概长这样:
| 评估项 | 评分(1-5) | 问题描述 |
|---|---|---|
| 内容完整性 | ||
| 语言流畅度 | ||
| 信息准确性 | ||
| 格式规范性 | ||
| 是否有害 |
抽检结果要汇总分析,找出高频问题,反哺到清洗规则里。这个闭环很重要,能让数据质量持续提升。
7.3 用下游任务表现反推数据质量
最终检验数据质量的标准是模型表现。我一般会准备一组下游任务的验证集,覆盖不同领域和能力。训练后在这些任务上评估,如果某任务表现差,就回头查对应领域的数据。
这个方法的好处是直接反映数据对模型的实际影响。自动指标和人工抽检都是间接的,只有下游任务表现才是最终标准。我一般会做消融实验:去掉某部分数据训练,看下游任务表现变化,从而判断这部分数据的价值。
8. 从数据到训练的衔接要点
8.1 数据分片与训练并行策略的匹配
数据量大了之后,单机加载不现实,要分片。分片策略要和训练并行策略匹配。如果是数据并行,每个GPU加载完整数据的不同子集;如果是模型并行,数据要按batch切分。
我一般把数据切成固定大小的shard,每个shard大概1GB左右。训练时按shard加载,配合流式读取,避免一次性加载全部数据导致内存溢出。LlamaFactory支持流式加载,配置里设streaming: true即可。
8.2 训练过程中的数据监控
训练时不能只看loss,还要监控数据本身。我一般监控:
- 每个batch的数据来源分布:确保配比符合预期。
- 数据长度分布:异常长度可能是数据问题。
- loss与数据来源的关联:某来源loss异常高,说明该来源数据可能有问题。
这些监控能帮你快速定位问题。我有一次发现loss突然飙升,查监控发现是某个shard的数据有问题,及时替换后恢复正常。
8.3 数据迭代:训练后的反馈闭环
数据构建不是一次性的,训练后要根据模型表现迭代数据。我一般做这几件事:
- 分析bad case:模型表现差的样本,查对应训练数据,看是数据不足还是质量差。
- 补充数据:针对薄弱领域补充语料。
- 调整配比:根据表现调整各来源占比。
- 重新清洗:用新的规则重新清洗一遍。
这个闭环跑几轮,数据质量会有明显提升。我做过一个项目,经过三轮数据迭代,模型在下游任务上的表现提升了近15个百分点,而模型结构和超参都没变,纯粹是数据优化的功劳。
预训练数据集构建这件事,说到底是个细致活。没有什么一招制胜的秘诀,就是把每个环节做扎实,把每个坑都踩一遍然后填上。我最大的体会是:数据上的投入永远不会白费。模型和算法可以复用别人的,但数据是你自己的核心竞争力。花在数据上的时间,最终都会体现在模型表现上。