
大模型预训练这件事最近几年被聊得很多但真正能从头到尾把它讲清楚的内容其实很少。不少人一开始以为预训练就是把数据喂给模型跑起来结果一上手才发现洗数据、配比、写并行策略、调学习率、看loss曲线每一步都能让人折腾几个通宵。这篇文章我想把一套完整的预训练流程拆开揉碎从前期方案设计、数据工程、训练配置到分布式训练、稳定性排查、评估上线全部过一遍。你如果真的想从零开始把预训练跑通这篇值得收藏也值得在动手前反复翻几遍。我默认你有一定的Python和深度学习基础比如会用PyTorch写模型、知道梯度下降是怎么回事但不需要你有过大规模训练经验。文里涉及的专业概念我都会用实际场景解释尽量不堆术语。1. 项目整体思路与方案设计1.1 预训练到底在解决什么问题预训练的本质是让模型从海量文本中学到语言的基础规律——词怎么组合、句子的上下文怎么关联、世界常识以什么模式出现在语言里。你可以把它理解成让一个新人通过大量阅读建立语感而不是直接教他做某道题。这个“建立语感”的过程就是在大规模无标注文本上做自监督学习最常见的做法是预测下一个词Next Token Prediction或者掩码恢复Masked Language Modeling。很多做应用的人容易忽略一个关键点预训练的质量直接决定了下游任务的天花板。你后面不管做指令微调、人类偏好对齐还是接各种Agent工具都是在预训练底座上加东西。底座要是没打好后面怎么修都有限。我见过一个项目语料里中文和代码混在一起没有做配比处理模型跑完效果很差指令微调阶段花了大量时间才勉强拉回来。后来重新洗数据做预训练同样的微调流程效果直接上了一个台阶。所以如果你决定要搞预训练第一件事不是打开代码库而是先想清楚我要做的是一个通用底座还是面向某个垂直领域的底座这决定了数据配比、模型规模、训练成本的整个设计方向。1.2 选型逻辑与参数规模估算模型规模和算力之间是一场明牌交易。你手里有多少卡多少显存决定了你能跑多大的模型。大的模型确实有更好的泛化能力但训练成本不止是显卡的钱还有时间、电力、以及你调试过程中的精力。一个很常见的做法是先在几百M或1B-3B的小模型上把数据流程和训练策略跑通再放大到目标规模。这就像拍电影先做样片而不是直接上粗剪。规模估算有一个粗略的经验公式可以参考总计算量FLOPs约等于6 × 参数量 × 训练token数。比如你要训一个1.8B参数的模型数据量定在20B token左右那总计算量差不多是6 × 1.8e9 × 20e9 2.16e20 FLOPs。一张卡如果是理论算力100 TFLOPS的芯片抠掉利用率通常50%-60%你大概能估算出要用多少卡时。我自己习惯先按这个公式算一遍如果发现时间成本超出预期会先调数据量而不是调模型规模。1.3 架构选择与关键技术组件现在做预训练基本上绕不开Transformer。但具体用哪种形态的Transformer差别很大。常见的选项有标准的Decoder-only结构、带RoPE旋转位置编码的变体、还有混合专家模型MoE这类稀疏结构的方案。对新人来说我建议先从标准的Dense模型入手也就是每个token都激活全部参数的那种。MoE虽然算力效率高但工程复杂度也高路由不均匀、专家负载失衡、通信开销这些问题第一次做预训练的人会很难排查。举个例子你训一个Dense模型遇到loss不降可能的原因就那么几个数据、学习率、精度策略。但如果你训MoE模型除了上面的都要排查还要看各个专家被激活的频率是否均衡否则一半专家在加班、另一半专家在摸鱼性能就直接崩了。不如先跑通Dense把训练流程中的核心问题都摸熟再做MoE改造。2. 数据工程预训练的地基工程2.1 数据清洗与去重预训练的第一课不是模型而是数据。数据质量比数据量级更重要这是我反复强调的一句话。我在实际项目里见过有人从网上爬了几TB文本直接开训结果模型学会了大量的重复句式甚至把一些网站的导航栏和广告文案背下来了。这既浪费算力又会污染生成的连贯性。数据清洗有一套相对标准化的流程。第一步是解析和提取把HTML、PDF、各种格式里的正文捞出来去标签去噪声。第二步是规则过滤比如去掉过短的文本、识别并过滤掉乱码字符、去掉机器生成的日志格式文本。第三步是更细粒度的质量筛选用语言模型打分器或者基于困惑度Perplexity的过滤器来判断一段文本是否足够“像人话”。这些听起来简单但每一步的阈值设置都会直接影响最终数据分布需要反复看采样样本来调。去重是一个容易忽略但又很重要的环节。你爬下来的语料里同一篇文章可能有多个转载版本。直接用MinHash这类近似去重算法在超大语料上做一次去重能显著降低模型的“复读机”倾向。我自己的经验是去重之后再按相似度阈值过滤一遍效果最明显。2.2 数据配比的多语言经验数据配比是一个“每个团队都有自己的祖传秘方”的环节。通用底座一般以英文为主但如果你想做中文场景就需要在中英文之间找到一个平衡点。代码数据在最近两年越来越被重视因为代码能教会模型严格遵循逻辑结构和语法对推理能力有明显帮助。还有数学数据、百科类数据、书籍类长文本数据各有各的作用。配比的核心原则是关注“信息密度”而不是“来源比例”。同样是1GB文本维基百科的信息密度远高于普通论坛灌水。你在设计配比时可以先定一个初始比例比如中文35%、英文30%、代码25%、数学5%、其他5%训一小段时间后用小规模评测集看效果再决定往哪个方向调。这中间千万别迷信拍脑袋定下的比例数据配比必须动态调整。2.3 Tokenizer训练与词表选择Tokenizer是预训练里最容易被新手忽视、但影响非常大的一个模块。它决定了模型切词的方式也决定了你的词汇表是否有冗余。比如直接用某一个开源词表遇到自己领域里常见的专业词汇被切得稀碎模型学起来就会很吃力。更麻烦的是如果词表太小或压缩率太低序列长度会无端变长计算开销也会直线上升。训练自己的Tokenizer比如用BPE算法是很多团队在建底座时做的第一步。要点在于训练语料的分布要和最终预训练语料尽量一致不能拿英文为主的语料去训中文场景的词表。另外一个容易被忽略的点是词表大小和嵌入层参数量的关系词表越大嵌入层占的显存和参数量越多。你需要根据自己的模型规模选择合适的词表大小比如7B级别模型常用的词表在32K到64K之间但具体还要看你做单语还是双语场景。3. 训练配置与关键细节3.1 超参数初始化与学习率调度预训练的超参数在整个训练过程中极少保持不动尤其是学习率。通常的做法是先用一个小的模型做几次“小规模试训”来确定大概范围。比如以1e-4为基准分别试5e-5、8e-5、1.2e-4看哪个步数内loss下降最稳定。注意如果你的batch size变得很大学习率通常也要跟着调大一些因为梯度估计更稳定了可以走更大的步子。这本来没有特别严格的线性关系但实际用的时候参考一下能省去很多调试时间。学习率调度策略最主流的是Warmup Cosine Decay。Warmup是为了让模型在训练初期不至于因为过大的梯度导致震荡一般占整个训练步数的1%-2%。Cosine Decay则是让学习率在训练后期缓慢降低帮助模型收敛到更平坦的极小值。如果总训练步数很长你还可以把cosine的最低值设成峰值学习率的10%而不是降到0。这个余量对后续继续训练continue pretraining会更友好。3.2 梯度累积、批次大小与显存博弈在实际训练中你很可能会发现“理想batch size”和“单卡能塞下的batch size”存在巨大差距。比如你希望通过32768的全局batch size去训练但单张卡一次只能塞下4个样本。这时候就要用梯度累积Gradient Accumulation来解决也就是多攒几轮梯度再统一更新参数。但是要注意梯度累积不等于真正把batch变大因为BatchNorm这类依赖全局统计量的模块在累积场景下的行为会略有不同Transformer模型因为没有BatchNorm所以问题不大。关于batch size我有一个粗浅但实用的经验小模型可以先从16到32的batch size起调大模型10B级别以上则往512到2048甚至更大去走。但这只是起点决定batch size上限的往往不是理论效果而是你的训练稳定性和硬件资源。batch太小梯度噪声大收敛不稳定batch太大虽然单步更稳但总步数变少学习率调度也要跟着改不然效果反而打折。3.3 混合精度与梯度裁剪大模型训练几乎离不开混合精度。以BF16为例它比FP16有更大的指数范围在小数部分精度略低一些但训练中很少出现溢出。这也是为什么很多大模型训练框架默认支持BF16的原因。实际操作中我建议模型的前向和反向都跑在BF16下但优化器状态比如Adam的两个动量保留FP32Loss Scaling则用Dynamical Loss Scaling或者干脆关掉直接BF16。经验下来BF16确实比FP16省心很多。梯度裁剪也是一个容易被忽略但关键时刻能救命的参数。预训练中loss突然冲高、梯度爆炸是家常便饭全局梯度范数裁剪通常设在max_grad_norm1.0或0.5取决于你的模型规模和数据质量。如果你发现训练初期经常出现“loss飙到几千”的情况除了裁剪梯度还要回头看一下数据里是不是混入了异常长文本或者某个batch的样本质量有问题。4. 分布式训练框架与并行策略4.1 数据并行与ZeRO优化到了大模型这个量级单卡显存再怎么塞也放不下整个模型所以分布式训练是绕不开的。最基础也最先上手的策略是数据并行也就是每张卡都放一份完整的模型副本喂不同的数据然后同步梯度更新。不过当模型大到一定程度光复制参数和优化器状态就很肉疼这时候就需要ZeRO优化出场。ZeRO通过把优化器状态、梯度、参数分片到多张卡上来避免每个进程都存一份全量副本。我试过用ZeRO Stage 2模型参数量从7B到13B的显存压力明显下降通信开销也在可接受范围。如果模型继续变大Stage 3会把参数也分片每张卡只持有部分参数跑的时候再通过通信收集。这里要提醒一下Stage 3虽然显存友好但对通信带宽有较高要求机器之间的网络如果不给力训练效率会很难看。4.2 张量并行与流水线并行的取舍数据并行和ZeRO解决了“参数放不下”的问题但当模型单层已经大到一张卡塞不下的时候就需要张量并行。张量并行的思路是把一个层的矩阵乘法拆到多张卡上同时算。比如一个线性层的输入和输出维度很大可以按列或按行切开分别在不同卡上做部分计算最后再拼接结果。这种策略在A100集群上很常见但每一层都有多次AllReduce通信所以对卡间互联速度很敏感。流水线并行则是把模型按层切分每个设备负责若干层数据像流水线一样在不同设备间传递。它的优点在于通信量相对较低但缺点是容易出现设备之间负载不均一部分卡在干活一部分卡在等待。实际工程中很少只用某一种并行而是几者组合。比如用4路张量并行 8路流水线并行 数据并行先把单层显存压下来再提高整体吞吐量。4.3 检查点保存与故障恢复长时间训练中卡挂掉是必然事件而不是偶然事件所以检查点保存策略直接决定了你能不能按时交差。我的建议是开启异步保存不要因为保存检查点阻塞在训练过程中。把检查点写入到独立的高速存储目录里避免和训练数据在一个磁盘上抢I/O。另外检查点不仅要存模型权重还要保存优化器状态、学习率调度器的当前位置、随机数种子状态以及数据加载器的迭代位置。否则恢复之后训练状态会对不上相当于“断片重训”。频繁保存检查点虽然安全但开销也很大。一般我会设定每隔固定步数比如每1000步保存一个检查点同时保留最近两到三个检查点再定期把更早的清理掉避免磁盘被撑爆。对于超长训练还需要做“中途验证”在下游小评测集上跟踪基座模型的能力变化。等待整个训练结束再一次性评测风险太大了。5. 训练稳定性与可观测性5.1 Loss曲线解读与异常值处理预训练过程中你盯得最多的就是loss曲线。正常的loss下降曲线通常是一路向下偶有小波动但整体符合学习率调度的趋势。如果你发现loss出现“突然升高后几天都降不下来”的情况先别急着调模型结构大概率是数据问题。我处理过好几次类似case最后定位到都是某些batch里混入了损坏的文本比如把裁剪异常的网页源码当成正文处理了。另一个常见异常是loss出现明显的周期性尖峰。这种可能是数据加载器的shuffle策略不对导致模型频繁在一定间隔里看到同一批异常数据。排查方法是把每个step的loss记录成日志再结合本地的数据样本hash做回溯。很多人会用wandb这类工具做训练可视化我自己也会本地记录一份JSONL日志便于离线做根因分析。5.2 梯度范数监控与模型更新诊断光看loss还不够梯度范数是一个更早暴露问题的指标。正常训练中梯度范数应该保持在一个相对稳定的区间如果你观察到梯度范数暴涨说明某个batch的loss贡献特别大或模型处于不稳定训练区。此时除了梯度裁剪你还可以做梯度日志聚类看看这些异常step都对应哪些数据样本。我习惯在训练脚本里同时记录三个东西loss、梯度范数、权重更新范数。当梯度范数正常但权重更新范数异常时往往意味着优化器参数出了问题比如AdaFactor这类二阶矩估计异常导致更新步长过大。如果是这样光调学习率解决不了需要重新审视优化器配置。5.3 模型参数初始化与“起飞失败”问题大模型训练经常会遇到一个现象初始loss比随机猜测还高或者loss从一个很大的值开始不掉。这很多时候是初始化方式不对。比如使用残差结构时如果没有对最后几层做特殊缩放深层网络在初始化阶段可能产生过大的残差输出导致数值不稳定。现在很多架构采用特殊的初始化策略比如对靠近输出层的线性层使用较小的标准差初始化参数。遇到这类问题我的建议是先做一个“单步调试”用一个小batch跑一次前向检查各层激活值的均值和方差。如果激活值在某一层开始爆炸就说明初始化或网络结构有问题需要在这一层加初始化缩放或归一化处理。6. 评估基线设定与后训练衔接6.1 中间阶段评测集设计预训练工作流里评测集设计最好在开训前就定好而不是临到准备上线本体时才开始想。一般来说评测集分成两类一类用来观测语言建模能力比如在某个固定验证集上的loss另一类用来观测具体能力比如阅读理解、数学推理、代码生成。后一类需要提前设定好评测prompt模板和打分标准确保每次跑的评测条件一致。为什么强调在训练前定好因为在训练过程中你无法简单通过肉眼判断模型“变聪明了没有”。如果没有一个统一评测基准你很难决定是否该继续训练、是否该调整数据配比。我习惯每几千步跑一次小型评测集记录指标变化这样在训练结束后你能画出一条能力增长曲线这对后续排查问题也有很大价值。6.2 指令微调与对齐的衔接预训练完成后模型的输出通常是“一种语言模型式的续写”而不是“针对用户指令的回答”。所以很多人都需要再做指令微调SFT和人类偏好对齐比如RLHF或DPO。与单独从零训练一个SFT模型不同从预训练底座出来之后做SFT重点在于数据配比和任务多样性。你要让底座在保留通用能力的同时学会遵循指令、格式化和安全表达。这里容易出现一个误区为了快速得到好的对话效果SFT阶段把大量高质量人工数据反复迭代、训练很多epoch。这会让模型严重过拟合到SFT数据的分布上及至损失掉一些预训练阶段学到的通用知识和长文本能力。我见过有的模型SFT后整体表现“飘了”就是这个问题。一个合理的做法是SFT阶段只训练一到三个epoch同时混入一定量的预训练语料做辅助保持语言建模能力不退化。7. 常见问题与排查技巧实录7.1 Loss不下降但梯度正常的排查这是预训练新手最常遇到的困局之一。你设置了合理的超参数loss曲线却像个平底锅一样死活不动。我的排查顺序是先看数据加载部分打印一个batch的输入和tokenizer还原结果确认文本没有被错误截断或引入大量空白字符然后看标签是否正确对齐有些新手会把预测下一个token的标签错位一位导致模型什么都没学到再检查模型输出的logits和label的维度是否匹配。这几步下来一般能找到问题。如果数据没问题下一步查精度策略。BF16下如果某个模块在fp32和bf16之间切换出错可能整个模型处于“半瘫痪”状态。比如某些LayerNorm算子在高精度和低精度切换时行为不一致需要用统一的计算精度来重算这些层。7.2 多卡训练中的性能瓶颈很多人发现加了更多卡之后训练速度并没有按预期线性增长。这时候瓶颈往往不在算力而在通信。比如使用TP时每个Transformer层都有多次AllReduce如果用的是千兆以太网而不是高速互联传输时间会超过计算时间。排查思路是记录每个step的耗时分布看前向时间、反向时间、通信时间各占多少。如果通信占比过高就要考虑降低张量并行的路数或者把数据并行与张量并行的比例重新分配。另外数据加载的CPU瓶颈也容易被忽视。如果数据集的图文文件比较碎、数量很多磁盘I/O会成为整个训练链路里的短板。解决方法是使用更高IOPS的存储或者把数据打包成更少的大文件分段加载。我通常会把训练数据在预处理阶段做压缩序列化能明显减少I/O等待。7.3 断点续训时的额外注意事项断点续训不止是“加载检查点接着跑”那么简单。我还遇到过几次类似问题检查点加载成功了但loss和保存时对不上训练曲线出现断崖。最后发现是数据加载器没有恢复到这个检查点对应的数据位置导致模型重复或者漏掉了一部分数据。所以保存检查点时一定要把数据加载器的状态一并序列化进去加载时先恢复训练状态再恢复数据流。还有随机数生成器RNG的状态。如果你在代码中使用了dropout或者某些随机采样操作而保存检查点时没有重建RNG状态恢复之后的训练会从一个“未知的随机状态”继续导致对应的优化动态和当初不一致。虽然不是每次都会出现严重异常但在数据配比或者在线采样场景下影响会比较明显。7.4 硬件故障前的预防性体检大规模训练对硬件的稳定性要求极高。我一般会在正式开训前花半小时到一小时跑一次全员健康检查包括每张卡的显存压力测试、通信带宽测试、NVLink或IB的连通性测试。如果机器之间通信速率差异过大那很可能是某根线缆或某个交换端口出现了问题。提前发现这些问题总比训了几天之后突然挂掉再去排查划算得多。训练过程中也要持续记录硬件指标比如显卡温度、功耗、内存利用率。有时某个卡开始频繁触发温度降频整体训练速度就会被拖着下降。如果发现这种问题尽早把这部分任务重新调度到别的节点上去而不是干等着完事。8. 预训练成本估算与算力资源规划8.1 基于公式的算力与时长估算前面提到的大致公式在实际估预算时很实用。假设你要训练一个13B模型数据量100B token那总计算量大概在6 * 13e9 * 100e9 7.8e21FLOPs。如果你用128张某款加速卡来跑每张卡有效可用算力是80 TFLOPS理论峰值算力是100 TFLOPS但利用率按50%算那一秒能完成的总FLOPs就是128 * 80e12 1.024e16。总时间约7.8e21 / 1.024e16 / 86400 ≈ 88天。这个估算能让你在项目启动前就对“这个方案能不能在deadline内完成”有数。不过要特别强调这个公式没有考虑通信开销、日志同步、评测验证、检查点保存的时间实际时间往往要在这个基础上再乘上1.2到1.5。如果你发现估算时间超期优先考虑降低数据量其次考虑换更高效的并行策略而不是盲目加卡——加卡带来的通信开销增长可能会吃光新增算力。8.2 显存预算的逐项拆解除了训练时长显存预算也是预训练之前必须算清的一笔账。显存消耗主要由四部分构成模型参数、优化器状态、梯度、激活值。以Adam优化器为例每个参数要额外存一份一阶动量、一份二阶动量并且如果是FP32存储每个参数要占8字节。也就是说一个7B的模型优化器状态就要占约56GB显存再算上参数和梯度一张80GB的卡已经不够单卡全量训练了。这就是为什么实际训练中要用ZeRO和混合精度来“压缩”显存。用ZeRO Stage 2跑7B模型通常需要两三张80GB卡才比较从容这还不算激活值。激活值可以通过重计算Activation Checkpointing来大幅降低但代价是增加少量前向计算。我一般会在训练启动时打一个显存占用快照确认各模块的占用是否和理论预期一致否则后面突然OOM时会非常被动。8.3 异构场景下的多模态扩展如果你需要训练的是多模态大模型也就是除了文本还掺入了图像、音频甚至视频数据那整体方案又会复杂一截。多模态预训练的核心在于对齐不同模态的表征空间图像编码器输出的视觉向量要能和文本嵌入空间做有效拼接或交互同时要保持各模态编码器的稳定性。一般来说图像编码器可以是冻结的也可以在训练后期阶段做轻量解冻。但多模态训练的稳定性更难控制因为不同模态的学习难度不同容易出现某一个模态的训练“抢跑”另一个模态还在原地踏步。实际操作中我建议用多模态数据配比表来约束每个batch里各模态样本的比例同时在训练中分别监控每个模态对应任务上的loss。如果发现某个模态的loss长期不降不要急着调整个网络先单独检查那个模态的预处理流程和编码器结构是否合理。9. 经验复制清单与社区协作建议9.1 我从多次预训练项目中提炼的关键动作随便翻了翻我和同行们的交流记录发现每次预训练项目其实都存在几个重复性的关键动作。如果你要接手一个预训练项目我建议你把这些动作写成一个checklist按顺序执行这里也分享给你参考。第一步锁定数据规模和模型规模并计算总计算量。第二步小模型打通数据管线设定数据质量标准和配比方案。第三步选好并行策略和优化器配置做显存估算提前跑通检查点保存恢复流程。第四步启动正式训练前做完整健康检查和固定步数的预热训练。第五步训练过程中持续监控loss、梯度范数、学习率曲线、数据加载状态。第六步每走一个阶段跑一次评测集记录能力变化曲线。第七步训练结束后先用基座模型做指令微调的试水确认下游任务表现。这套流程不一定适合所有团队但它帮我规避了大部分“调了两周模型却训练效果一团糟”的问题。9.2 开源工具与社区资源怎么选选型是预训练工程里相当花费时间的一件事。好在现在开源生态足够丰富我接触过的且口碑不错的工具有不少。训练框架层面比如开源的分布式训练框架类似开源社区里常见的Megatron风格和DeepSpeed风格都有成熟的并行方案与文档。数据质量过滤、去重、分词也可以直接套用社区里成熟的数据处理组件没必要自己重复造轮子但一定要花时间读懂它们默认配置里的具体行为尤其是涉及过滤阈值和采样策略的部分。社区选择上我的建议是关注那些有活跃更新和真实训练经验分享的项目而不是只看star数量。很多仓库star很多但issue累积了一堆没有回复遇到卡住的问题时这种项目很难指望得上。我一般会看项目的issue列表里是否讨论过分布式稳定性、数据预处理细节这些实际训练中会出现的问题。如果一个项目文档里能给出详细的训练配方类似可复现的训练配置和超参数组合那么它的参考价值会高很多。9.3 避免“从零复现”陷阱站在成熟配方的肩膀上现在很多公开模型都会附带训练数据配比、学习率和并行策略的说明这些配方对少走弯路非常关键。不要总觉得“从零自己调一遍才算真本事”预训练工程的目标是做出可靠、高效的结果而不是重新发明训练方法。我会在项目开始时先找一个和自己目标规模相近的开源模型用它的训练配方做基线。然后基于自己的数据分布稍做修改再跑一次小规模验证。这样做既节省了从头调参的时间也能更快暴露数据层面的问题。当你的数据和配方适配度提高后再考虑加入一些新的训练技巧比如课程学习Curriculum Learning或中途换数据配比等。结合长期实战的一点补充想法做预训练这件事最大的感受就是它很像一个“系统工程”九分准备、一分训练。数据和硬件准备多花一点时间后面训练阶段会顺利得多。如果不是亲自跑过一轮完整流程很难理解那些开源文档里为什么用这种方式组织检查点或者为什么要专门处理数据加载器状态。最后分享一个我常用的习惯每次训练结束把loss曲线截图、数据配比表、超参数记录、评测结果一起存成一个训练报告。下一次启动类似项目时直接拿来对照和复盘。你会发现预训练最值钱的产出不只是那个模型还有一整套运行稳定、结果可复用的训练流程。这些积累会在后续每一次迭代中产生复利。