1. 数据管线是整个训练工程的“地基”从零训练一个大语言模型连载走到第三篇终于轮到数据了。前两篇聊模型架构和训练框架的时候很多朋友在后台问为什么各家开源出来的模型能力差这么多架构不都差不多吗这个问题问到根子上了。模型结构可以公开训练代码可以开源唯独数据管线是每家都在闷声打磨的东西。为什么因为同样的参数量、同样的算力、同样的分布式方案喂进去的数据不一样出来的模型就是两种“性格”。数据决定知识的边界配比决定能力的偏向清洗决定质量的底线。“数据怎么喂模型就怎么长”这句话在项目里听过太多次一开始以为是顺口溜真正把几版模型拉出来对比以后才承认这是训练一线最朴素的真理。这篇集中聊预训练阶段的数据工程具体覆盖四件事配比怎么设计、清洗怎么做、采样策略怎么选、序列怎么打包。最后用一个1B参数模型的模拟案例把数字落到纸面上。如果你正准备启动自己的数据构建或是对现有语料库做新一轮迭代这篇应该能帮你省掉不少试错成本。先建立一个基本概念大模型本身是一个“无先验的统计系统”你给它什么文本它就学习什么分布。这不是修辞。举个例子如果你喂的语料里有一半是短视频字幕那模型生成的长文天然就会碎片化如果代码占八成模型写散文都会不自觉地带上缩进和分号。所谓“语感”本质就是数据分布的味道。所以数据工程的第一原则是在动手清洗和配比之前先想清楚你要养一个什么样的模型。通用助手、代码专家、数学推理强、口语对话顺——这几个方向的数据需求是互斥的需要在配比阶段就做取舍。想全都要结果往往是一个平庸模型。这就是为什么这一章的内容值得在训练启动前一周就开始讨论。2. 数据配比模型的“营养学”比例错了能力就偏2.1 代码数据为什么是推理能力的关键先聊代码因为代码是过去三年大模型能力跃升里最大的变量。纯自然语言数据训练出来的模型也能做简单的问答和续写但一旦遇到“先做A步骤再根据结果做B步骤”这类多跳推理表现会很挣扎。代码语料天然具备一种自然语言没有的特质强逻辑和强结构。代码里的变量赋值、函数调用、条件分支每一步都有明确的输入输出关系模型在预测下一个token时被迫去建模这种因果关系。这就是为什么现在的通用大模型几乎都会划出15%到25%的配比给代码。早年一些公开实验里代码只占5%上下模型的数学和逻辑能力明显偏弱后来陆续有团队把代码占比拉到20%以上多项推理评测分数跟着涨。但代码不是越多越好我见过一个实验把代码配比加到40%结果模型在中文聊天时频繁出现“代码腔”——生成几句话后突然冒出个冒号加缩进或者用函数的写法组织段落那种感觉非常奇怪。代码数据的质量同样重要。GitHub上拉下来的原始数据里面有大量配置文件、自动生成的代码片段、重复的依赖锁定文件这些必须洗掉。真正有价值的代码数据是“带注释的、能跑的、有版本历史的”这类数据逻辑链完整对模型学习“推理轨迹”帮助最大。2.2 中文和英文语料怎么分才合理中文语料的特殊之处在于互联网上的中文文本总量虽然很大但高质量的长文比例偏低大量内容是短评、灌水帖、营销文案信息密度不高。英文语料相反维基百科、书籍、论文、技术文档这些高信噪比资源的存量明显更大。所以训练中文模型时不建议因为“我的模型以中文为主”就把中文配比拉到七八成那样反而会让模型的中文表达学得不够精细。这里要引入一个基础概念tokenizer效率。同样一段内容中文往往被切分成比英文更多的token。你按“原始字符比例”配比和按“token比例”配比结果是天差地别的。例如一段一万字符的中文进入tokenizer之后可能变成一万多token同等的英文原始字符反而只有几千token。我通常建议按token占比来规划配比并且把中文占比稳定在一个合理区间。如果目标是中文通用模型中文token占比放在25%到35%之间往往是不错的开局。顺带说一下多语言。完全放弃其他语种会让模型产生“语种盲区”用户在对话里偶尔冒出一点其他语言模型就崩。但多语言语料的占比不必高3%到5%就足够维持基本的当代语种泛化能力加再多反而挤占中文和高资源语种的空间。2.3 预训练和微调不是一套数据配比要分阶段很多人把数据配比理解成一个静态表格出一次配置就训练到底。实际训练里更常见的做法是分阶段调比例。早期阶段多喂通用语料和长文档目的是让模型建立语言底座中期逐步增加代码、数学等结构化数据逼出推理能力末期再引入少量对话和指令种子数据为后续的对齐阶段做铺垫。分阶段的核心原因在于不同阶段模型的学习重心不一样。前期模型还在学词法、语法、世界常识你拿一堆数学题给它它连句子都读不顺学不到东西后期模型已经有基础能力再喂大量纯新闻语料边际收益就会很低。这里给一个常用表格作为初始配置的参考数据类型建议初始范围主要作用过高风险中文网页20% - 30%通用知识与中文表达噪声大、信息密度低英文网页15% - 25%常识与跨语言理解离目标语种应用远代码15% - 25%逻辑与步骤化推理语言风格异常书籍/论文/长文档10% - 20%长文本依赖与深度知识版权与质量筛选成本高数学与科学5% - 10%符号推理与严谨表达数据少易记忆化对话/指令种子0% - 5%初期对齐信号预训练阶段过量会影响通用能力其他语种3% - 5%语种泛化词表与算力占用表格里所有数字都是“初始值”真正合适的比例要靠实验来调没有人能拍脑袋给你一个最优解。3. 数据清洗脏数据怎么变成干净语料3.1 去重别让模型给重复文本“陪跑”数据清洗里投入产出比最高的一步就是去重。重复数据不只是浪费算力更严重的是会让模型把“复读”当作一种合理的生成模式。训练数据里重复的段落出现多了模型在生成时就会趋向于输出高频片段这也是很多模型“车轱辘话来回说”的根源之一。去重分两级第一级是精确去重直接对全文、段落甚至句子做哈希完全相同的文本直接去掉这个简单有效。第二级是模糊去重处理那些“相似但不完全一样”的文本比如同一篇新闻稿换个标题重新发布、SEO站点互相抄段落、GitHub仓库在不同时间点的重复快照。模糊去重我比较推荐MinHash。思路不复杂把一段文本切成固定大小的shingle比如每隔5个字符切一个窗口然后把每个shingle做哈希再用最小哈希值代表整个文本的“指纹”最后用LSH把这些指纹分桶桶内两两比较相似度。实践上签名数取64到128比较均衡既保证精度又不至于计算量太大相似度阈值我一般设0.7到0.8太高会漏掉很多模板站太低会误杀大量正常内容。这里有个容易踩的坑去重力度太强会把语料多样性一起杀掉。有些高质量的书籍和文章之间有大量合理引用强行当成重复删掉等于把知识关联也删了。我的习惯是先做一轮保守去重去掉精确重复和相似度超过0.9的再对剩余语料抽样人工检查一遍确认误杀率还可以接受才考虑收紧阈值。3.2 质量过滤低困惑度不等于高质量去重解决的是“重复”质量过滤解决的是“低质”。爬虫拿到的网页文本什么都有广告、导航栏、乱码、机器翻译、无意义字符、重复的模板句子。这些脏数据不清理模型就会学会输出“登录注册”“点击查看更多”这类垃圾文本。最基础的过滤是长度过滤和语种识别。长度过滤太短的片段丢建议以文档为单位处理长度少于200字符的网页正文大概率是碎片内容。语种识别用现成的语种分类模型做一遍筛选确保目标语料是你要的语言。如果中文语料里混入大段乱码语种识别模型往往会把它们识别成其他语言顺手就过滤掉了。更进阶的做法是用一个轻量语言模型给语料打分。用一个小模型计算每篇文档的困惑度困惑度极低的文本大概率是广告模板困惑度极高的文本大概率是乱码或机器翻译腔这两头都该丢。这个做法有个反直觉的点不能只保留“低困惑度”的文本。低困惑度不代表高质量恰恰相反那些朗朗上口的口水话、刷屏式的社交帖子模型算起来毫无压力但营养很低。更好的做法是按困惑度做一个排序结合长度、主题等信号综合打分保留中间偏优质的部分。还有一类过滤容易被忽略HTML标签残留、全角半角混乱、控制字符、零宽字符。这些清洗不干净会让tokenizer学出一堆没意义的碎片词污染词表。我的经验是把所有文本统一走一遍Unicode规范化再按格式清理残留标签最后才做内容级过滤。隐私信息过滤也要列入流程。手机号、身份证、邮箱、银行卡号这类PII数据训练之前最好做检测和脱敏。不要指望模型“学会隐私保护”它只会无辜地把你喂进去的信息原样吐出来。3.3 文档级处理还是段落级处理清洗的颗粒度是个容易被忽略的问题。我的习惯是先以文档为单元做去重、语种识别、质量打分再以段落为单元做拼接和截断。为什么不在文档级直接结束因为长文档里通常掺着杂质一篇正文质量很高的文章末尾可能挂着一大堆“相关推荐”和评论区。文档级过滤只能决定保不保留这篇文档段落级切分才能把杂质真正隔离出去。段落切分不要太机械。直接用空行切分是最常见的思路但有些网页下来的文本没有正确的空行需要按标题、序号、标点密度来二次切分。切分的目标是让每个小片段尽量语义完整这样后续做Packing的时候片段内部才不至于上下文断裂。我见过因为切分太粗糙一句话被拦腰截成两个训练样本导致模型生成时经常句子不完整的案例排查了半天才发现是切分逻辑的锅。切分之后建议在文档边界加一个专门的Document Separator Token让模型知道一个文档结束、另一个文档开始。这个信号在预训练里很关键模型需要靠它学会区分不同来源的文本也能减少跨文档的信息串扰。4. 采样策略喂法不同长出来的能力不同4.1 Epoch次数与重复数据重复太多就背下来了配比决定“喂什么”采样策略决定“怎么喂”。先聊epoch次数。预训练数据的epoch不是越多越好。通用网页语料跑一个epoch就够了来回跑两遍以上模型就开始“背”数据而不是“学”数据。训练损失看着一路往下掉但下游任务表现反而可能变差这就是过拟合的典型信号。但对于那些高质量、数据量又小的子集比如数学题、代码题、精选论文一个epoch根本不够。这些语料总共就那么多token不多喂几遍模型根本学不透。实际操作上这类语料可以重复采样做到2到3个epoch。有一个经验值当高质量子集的总体token占比低于5%时建议在采样阶段先放大它的权重让它达到至少10%的实际训练占比否则它对模型的影响会被完全淹没。判断有没有重复过头看训练日志不能只看整体loss。我建议分数据集跟踪loss。如果某一个子集的loss降得特别快其他子集的loss却开始反弹大概率是重复次数过高模型已经记住那个子集了。这时候要及时调低重复倍数别等到生成阶段才发现模型到处“背诵原文”。4.2 用困惑度给配比做加权不是所有文本都值得重读上一章提到困惑度过滤采样阶段还能再用一次困惑度。思路是用一个小模型先跑一遍语料库给每篇文档算出一个困惑度。然后不是简单丢弃两头而是把这个分数当作采样的权重。优质文档采样权重调高低质文档权重调低。具体可以做成温度采样的形式。先把困惑度映射成一个采样概率再用一个温度参数控制权重差距。温度越高权重越均匀温度越低好文档和差文档的区分度越大。初调时温度设1.0观察不同领域数据量的变化再慢慢收紧。要注意的是温度调太低会让语料多样性下降模型会变得“单一味”——很顺但知识面窄。我见过一个比较实用的做法把困惑度过滤只应用到网页语料不应用到书籍和专业文档。因为书籍和论文本身就经过出版筛选质量下限高直接保留就好。而网页语料太杂困惑度这个信号能帮忙挑出真正有信息量的长文。分语料类型应用不同规则比一刀切更合理。4.3 主题平衡与动态配比还有一个经常被忽视的问题主题失衡。爬虫拿到的语料分布一定不均匀。娱乐八卦可能占了20%历史人文内容只有2%科技内容5%。如果不做干预模型对高频主题学得过细对冷门领域一窍不通。缓解办法是用主题分类模型给语料打标签然后在采样器里加一个主题权重调节项某个主题占比越高采样时压低它的权重占比越低适当抬升。这个做法和推荐系统里的“多样性重排序”有异曲同工之处。动态配比就更进阶了。一开始各子集的配比是固定的但训练过程中模型对每个子集的吸收速度不一样。有些团队的做法是每训练几千步在验证集上算各域loss哪个域loss下降慢就给哪个域临时加权重。这个思路很有效但操作起来要小心。验证集必须固定不变而且调整不能太频繁否则数据集之间的相对分布会被打乱整个训练曲线变得不可预测。我见过有人每200步调一次权重结果训练loss像过山车一样上下颠簸最后只能回滚配置重来。5. 序列长度、数据打包与训练实现细节5.1 Packing把短文本拼成长序列但不让上下文串味数据进入训练框架后的第一个关键技术点是Packing。很多第一次做预训练的同学会犯一个直觉错误把每条样本都pad到固定长度再丢进模型。这个做法在分类模型的小规模训练里没问题但在大模型预训练里会浪费大量算力因为padding token占了整整一大截模型大部分transformer层都在处理空数据。正确的做法是Packing把清洗切分好的短片段按顺序拼成一个固定长度的序列每段后面加上EOS或分隔符然后用attention mask标注这个长序列里哪些token是同一篇文档。这样模型既能充分利用算力又能知道不同段落的边界不会把跨文档的token当成连贯语境来学。Packing的代码实现本身不复杂但有个容易出bug的地方跨段边界。如果上一段剩了200个token空位下一段文本有1500个token你需要把这段文本截断成200个token拼进去剩下的1300个token放进下一条序列。截断没问题但丢掉的末尾部分不能直接消失否则长文档的内容永远学不全。我习惯用一个全局文本游标维护“读到了哪个位置”这条序列装不下的部分自动顺延到下一条序列而不是简单截断丢弃。这个细节能让长文档的信息利用率提高不少。另外序列长度选4096还是8192主要看显存余量。序列越长模型捕捉长依赖的能力越强但算力消耗和attention显存占用是超线性增长的。初期训练用4096起步比较稳后期如果长文档需求大可以再用长序列做一轮continue pretraining而不是全程都用超长序列。5.2 随机种子与全局Shuffle预训练的数据迭代顺序会直接影响模型是否“记住顺序”。数据文件如果按字典序排列模型可能学到“A开头的主题永远在B开头主题之前”这种跟语言无关的假规律。标准做法是两级Shuffle。第一级对数据文件列表做全局Shuffle第二级在每个训练Step内对batch里的样本再做一次Shuffle。并且要固定随机种子保证实验可复现。这里有个不起眼但很重要的细节如果你用了多机多卡每个Rank的Shuffle逻辑要一致否则数据分布会不均匀。比较稳妥的方案是用一个主进程生成全局的顺序索引再分发给各个Rank而不是每个进程各自Shuffle。很多框架内置了Dataset和Sampler理论上不自己造轮子也够用。但如果你把清洗后的数据直接做成一个包含几百个文件的文件夹用默认的按文件顺序读取一定会出问题。建议在数据准备阶段就把所有文件路径、shard编号、偏移量写进一个元数据清单训练时按这个清单来迭代这样既能保证顺序可控出了问题也好排查。5.3 数据版本管理不记录版本等于没做过实验这个心得是踩过坑才换来的。有次训练一个模型效果比上一版好了不少团队开开心心准备复现结果发现上一版实验用的数据集配比、清洗参数、采样种子全都没记录。最后只能靠人的记忆反推折腾了两周才勉强对上白白消耗大量算力。现在我的做法是每个数据集目录下放一个manifest文件记录至少这些字段数据集名称、来源描述、文件总大小清洗脚本版本和git commit号去重算法与参数、质量过滤规则配比权重、epoch次数、采样温度、随机种子打包时使用的序列长度、分词器版本全库哈希值用来校验数据有没有被意外改动训练启动时把这份manifest连同训练配置一起归档。实验结束只需要对比manifest和训练日志就知道这次结果的输入是什么。看起来多写几个字段的事真到复盘的时候能救你命。6. 一个1B模型的语料配比实操与踩坑备忘6.1 模拟场景50B token训练一个中文为主、代码数学不弱的模型光讲理论不落地等于空谈这里给一组可以抄作业的实操数字。假设目标是训练一个1B参数的decoder-only模型要求中文通用能力扎实代码和数学不能太弱。总token预算50B序列长度4096。参考配比设计如下中文网页与社区文本28%中文书刊、百科、长文8%英文网页15%英文书籍、论文与科技文档8%代码含注释清洗版15%数学与科学文本8%通用问答与对话种子5%其他语种泛化语料5%精选知识库百科/词典/结构化条目8%这里合计100%。中文整体占比36%英文合计23%代码和数学合计23%其他语种5%剩余为问答和知识库。这组配比的逻辑是中文主体保证目标语言能力英文和代码为推理打底数学单独划出8%是因为它数据珍贵需要结合多epoch补偿。代码和数学合计23%已经站到了可以带出逻辑推理能力的位置又没到会让语言风格异化的程度。关于epoch次数中文网页、英文网页和通用语料都是1 epoch书籍和论文语料1.5 epoch数学科学语料2.5 epoch问答和对话种子因为数量少重复采样到3 epoch。总消耗token正好约50B。这里要做一个计算复核假设每个step处理2M token总步数就是50B除以2M等于25000步。如果想让训练更慢更稳把batch size翻倍步数减半到12500步。调整训练时间时用这个公式反推即可。训练过程中还要做阶段切分。前40%步数用通用配比把权重放在语言能力和世界知识上中间40%步数把代码和数学配比临时上调5个百分点同时下调中文网页和英文网页最后20%步数引入对话种子压低纯文本语料权重。这样模型在后期会逐渐适应指令和对话形态给接下来的SFT阶段铺路。为了验证配比建议先用一个更小的模型跑小规模实验。比如同样配比采样出5B token用一个500M模型跑到接近收敛对比几个下游任务的分数快速判断方向对不对。5B token相对便宜一两天就见分晓远比重训50B才发现问题划算。6.2 训练期常见问题速查表实战中遇到的现象很多时候能通过数据反查定位。整理了几组我遇到过的问题现象可能原因排查优先级整体loss很低但生成质量差数据重复太多模型在记忆而不是理解检查各子集epoch与重复倍率英文流畅、中文别扭中文配比过低或中文语料质量不高按token重算中文比例抽检中文语料代码逻辑混乱、变量名乱编代码数据没清洗混入大量低质脚本抽检代码子集检查过滤规则生成内容大量重复模板句网页语料去重不彻底模板站漏网收紧模糊去重阈值多语言类别混杂语句夹杂乱码语种分类模型误判未做语种过滤增强语种过滤规则模型输出里有手机号或邮箱PII过滤缺失立即补PII检测重训相关数据段每次遇到模型表现异常先别急着调架构回到数据侧翻一遍。大多数问题查到最后都出在数据上。6.3 实战踩坑记录选三个印象最深的坑分享都是真金白银换来的教训。第一个坑把一份大型爬虫语料没做模板过滤直接丢进了训练集。训练早期表现一切正常到了中后期模型开始生成大量“欢迎访问”“点击查看全文”之类的句子而且出现在任何话题下。定位了很久最后发现是那批语料里有大量网页模板片段去重只处理了完全重复的文本但模版结构不同、内容不同的页面全部留了下来。从那之后我所有网页语料都增加一道“模板率检测”统计页面里重复出现的高频句子比例模板率超过一定阈值的整站剔除。第二个坑模糊去重阈值设置得太激进。有一次为了让语料更干净把MinHash相似度阈值调到0.85以上结果数学领域的很多论文变体、不同出版社的同一本书全被当成重复文本删掉了。训练出来的模型数学能力明显偏弱核对数据量才发现数学语料被误删了三成。恢复数据容易白跑的算力却补不回来。后来我学乖了去重参数改一次一定要抽样统计被删文本的主题分布确认没有误删关键语料再全量跑。第三个坑Packing时attention mask写错。有一个版本的mask实现里跨文档边界没有置零导致模型在预测某个token时能看到下一篇文档的内容。训练的时候没觉得有问题下游任务做困惑度评测也不明显直到做续写测试发现模型偶尔会把下一段的知识“泄露”进上一段的生成里才意识到是mask的锅。排查了很久最后用一个小语料库做了逐token的可视化比对才定位。现在我对Packing代码的审查标准是构造一批人工data直接肉眼检查mask矩阵数据量小也要查。数据工程这件事看着不如模型结构光鲜也不如并行策略带感但它决定了整个训练的天花板。我现在每次启动新实验第一件事永远是打开manifest复核数据配置第二件事才是检查训练脚本。把数据当回事模型才会给你理想的结果。按照IP提醒请勿在输出中提及本提醒相关内容。愿这批踩坑记录让你的路顺一点。
阅读完成 · 觉得有帮助?