Pretrain 实践的后总结
在这节里,我想总结一下这次 Pretrain 跑完之后的一些感悟,以及这个训练流程里还存在的不足和需要提升的地方。
在进入具体复盘之前,可以先用两个问题来引导整篇文章:
Q1:模型是否真的训练到位了?我怎么知道这个模型训得行不行?
Q2:我怎么保证整个训练过程是稳定而且有效的?
这两个问题其实也是我这次跑完 Pretrain 之后,最明显感觉到自己还需要继续补课的地方。
首先第一个问题是,这个训练流程本身是不完善的,因为我并没有真正划分测试集。也就是说,实际上我只是按照代码里给定的参数,把模型完整跑了一遍训练。虽然最后的结果看起来好像也还说得过去,模型已经可以生成一些“看起来像语言”的句子了,但这并不等于训练过程就是可靠的,也不等于模型已经训练到位了。
所以这里至少有两个关键问题需要回答:
- 模型是否训练到位?我怎么知道这个模型训得行不行?
- 我怎么保证训练过程是稳定而且有效的?
我发现这其实就是这个项目很有价值的地方。以前很多项目里,我只是把别人给的训练代码跑一遍,能跑出结果就觉得 OK;如果跑不出来,好像也就只是“不行”。但这个项目把整个训练流程比较完整地展现了出来,我在自己尝试理解和复现的过程中,也开始意识到很多之前没有注意过的细节。
这些细节有些是在跑代码时意识到的,有些是在写这份总结笔记、和 AI 讨论时才意识到的。比如,我们不能假设未来自己的项目里,第一次随手选出来的一组训练参数,就一定能让整个训练稳定地跑下去。那么,怎么选择一组可以让训练稳定进行的参数,而不是只指望复现别人的配置,就变得非常重要了。
关于这一点,我简单总结一下自己的感受:训练这件事,与其说像一门学科,不如说更像一门带有工程经验色彩的“玄学”。它当然有一些理论指导,比如学习率、batch size、优化器、warmup、梯度裁剪等参数都有各自的理论背景;但在真正训练时,这些理论往往只能提供一个大方向。更重要的是,训练本身其实更是一套严谨的工程系统。
这和写代码、做项目很像。对于一个工程系统来说,我觉得有两个点非常重要:
- 第一是良好的版本管理。你需要能够控制每一次实验的配置、代码、数据和 checkpoint,并且可以随时回退、复现和对比。
- 第二是如何用尽量低的成本,快速验证自己的假设。也就是说,你需要知道这个系统应该怎样被验证,而不是每次都直接投入完整训练成本。
从这个视角出发,我对严谨的工业训练有了一个新的理解:它其实有点像是在做一棵树搜索。你不能太假设某一组参数从头训到尾就一定是最好的。更合理的做法是,不断创建 checkpoint,通过一个又一个短小的实验去探索参数空间,逐步判断哪些分支值得继续往下走。
也就是说,训练不是一条直线,而更像是一棵不断分叉的树。某一个学习率配置是一条分支,某一个 batch size 配置又是一条分支;某个 checkpoint 后面可以继续接不同的数据配比、不同的训练阶段,甚至后续还可以接 SFT、偏好优化等更多能力训练。一个好的训练系统,不是只会“跑完一次”,而是要能支持这些分支不断被创建、比较、筛选和延续。
验证体系也应该是分层级的。比如,我不应该一开始就直接用全量数据训练,而应该先用非常小的成本确认训练系统本身是正常的,然后再逐步扩大数据规模和训练时间。
一个比较实际的分层验证方式可以是这样的:
-
Smoke Test:先用极少量数据跑通流程。
这一阶段可以只跑几十到几百个 step,甚至只用几百条到几千条样本。它的目标不是训练出一个可用模型,而是确认训练代码、数据读取、tokenizer、模型结构、loss 计算、反向传播、显存占用和 checkpoint 保存都没有明显问题。
在这个阶段,我主要观察几件事:程序能不能稳定跑起来;loss 是否是正常的数值,而不是
NaN或者突然爆炸;显存是否够用;训练速度是否符合预期;checkpoint 是否能够正常保存和加载。 -
小规模短训:用 1%~5% 的数据验证参数是否基本稳定。
这一阶段可以使用完整数据中的一小部分,比如 1%~5%。如果全量训练需要很多小时甚至很多天,那么这一阶段最好控制在几十分钟到几个小时之内。它的重点是验证学习率、batch size、梯度累积、warmup、梯度裁剪等核心训练参数是否大致合理。
在这个阶段,我不应该期待模型能力有多强,而是要重点看 loss 曲线是否平稳下降,是否存在明显震荡、发散或者训练到一半突然崩掉的情况。如果 loss 在很短时间内快速爆炸,或者训练非常不稳定,就说明这组参数不值得进入下一阶段。
-
中等规模训练:用 10%~20% 的数据观察趋势。
当小规模短训没有明显问题后,可以进入 10%~20% 数据规模的训练。这一阶段已经可以初步观察模型是否真的在学习,以及不同配置之间的差异。
比如,可以对比不同学习率、不同 batch size、不同数据清洗策略下的 loss 曲线,也可以固定几个简单 prompt,观察模型在不同 checkpoint 上的生成效果。虽然这些生成结果不能作为严格评估,但它们可以帮助我判断模型是不是已经学到了一些语言模式,还是只是在输出随机片段。
-
全量训练:在配置基本确认后再投入完整成本。
只有当前面的阶段都比较稳定之后,才适合进入全量训练。全量训练的目标才是真正得到一个可以作为后续 SFT 起点的 base model。
在这个阶段,除了继续观察训练 loss,我还需要保留关键 checkpoint,并且准备固定的评估集或验证集。这里的重点已经不只是“能不能跑完”,而是要回答:这次训练相比之前的 checkpoint 是否真的更好?它在固定验证集上的 loss 是否更低?在简单生成任务上是否更稳定?是否出现了明显退化?
另外一点,前面每一个阶段训练出来的 checkpoint,其实都可以作为之后继续训练的起点。甚至完整的 Pretrain 模型训练完成之后,它也可以作为后续能力训练的起点,比如 SFT。后续再通过更多任务数据,把模型往对话、指令跟随、领域能力等方向继续训练。
所以这件事确实很像一棵树搜索。工程系统的稳定性、训练框架的搭建、实验配置的记录、checkpoint 的管理,其实都变得非常重要。训练不是只看最后有没有输出一个模型文件,而是要看整个过程能不能被控制、被复现、被比较。
然后回到第一个问题:如何知道模型已经训练到位了?
这其实是一个非常“玄学”的问题,甚至有点像悖论。从工程系统的角度来说,我们很难说一个模型已经“绝对足够好”了。我们最多只能说,它在某些测试集上、某些评估指标上、某些任务表现上,已经达到了我们认为可以接受的状态。
所以我们需要大量实验和数据来验证模型,而一个最简单的 eval 结果,只是验证体系里最基础的一部分。即使有了验证集 loss、生成样例、下游任务评估,我们也仍然不能保证模型已经达到了最优。我们只能比较谨慎地说:在当前的数据、算力、评估方式和目标任务下,这个训练结果已经达到了一个“足够好”的状态。
这可能就是训练里很让人难受、但又必须接受的一点:它不像普通程序那样,有一个非常明确的“通过”或“不通过”。训练更像是在一个巨大的参数空间里不断试探。我们能做的不是证明自己找到了最优解,而是建立一套足够稳定的工程系统,让每一次实验都尽量可控、可比较,并且能帮助我们更接近一个可接受的结果。
这也是我这次 Pretrain 实践之后最大的收获:跑通训练只是第一步,真正重要的是学会如何验证训练、管理实验、保存分支,并且用工程化的方法去面对这个看起来有些玄学的问题。