返回 2026-04-20
🤖 AI / ML

从零构建 LLM(第32部分):干预实验与指令微调结果更新Writing an LLM from scratch, part 32l -- Interventions: updated instruction fine-tuning results

gilesthomas.com·2026-04-20

作者基于 Sebastian Raschka 的《Build a Large Language Model (from Scratch)》一书,持续开发一个类 GPT-2-small 模型,并通过多种干预手段尝试逼近原始 OpenAI GPT-2-small 在保留测试集上的损失表现。文章更新了最新的指令微调实验结果,探讨不同训练策略对模型性能的影响。

Giles Thomas

Archives

Categories

Blogroll

我一直在基于 Sebastian Raschka 的著作《Build a Large Language Model (from Scratch)》开发一个 GPT-2-small 风格的 LLM,并尝试了多种不同的方法,试图让它逼近原始 OpenAI GPT-2-small 的质量水平——衡量标准是在一个保留的测试数据集上的损失值。在上一篇博文中,经过一系列尝试后,我最终训练出了一个几乎(如果说还没完全达到)达到该水平的模型。

在开始深入这些改进措施之前,我为每个构建的模型都做了三项评估:一项冒烟测试(看它能否对 “Every effort moves you” 给出连贯的补全),一项测试集损失评估,以及一项指令遵循测试——该测试会对模型在 Alpaca 数据集上进行微调,让它为指令测试集生成结果,然后用一个 LLM 作为裁判来打分。

这样做的思路是:测试集上的损失是一个衡量模型质量的有意思的技术指标,但它并不能真正告诉我们这个模型在实际使用中可能有多大的用处。

遗憾的是,今年一月我意识到我的方法有问题:因为我让 LLM 单独对某个模型打分,而 LLM 本身具有天然的随机性,这就导致结果不具备真正的可比性,尤其是当模型之间质量相近时。

例如,如果两个模型对

Name the author of 'Pride and Prejudice'.

的回复都是:

The author of 'Pride and Prejudice' is Sarah Palin.

……那么某一次指令遵循测试可能会“碰上裁判 LLM 心情好”,给出比如 5% 的分数——毕竟模型确实尝试回答了,甚至还用了一个真实人物的名字,哪怕答案完全错误。但在另一次运行中,裁判可能“心情较差”,直接打 0%。

我的解决方案是使用两个脚本:

  • 一个脚本负责微调模型并让它生成回复,然后将这些回复保存到文件中。
  • 另一个脚本则接收上述脚本生成的多个文件(每个文件对应一组不同模型中的一个),将它们一并呈现给 LLM,以便它( hopefully )在相对评分时保持一致性。
  • 具体细节见此处。

    由于这种方式工作量显著增加,我在“干预措施”迷你系列中一直没有进行这些测试。我觉得更合理的做法是,先尝试多种干预措施,积累一批可供测试的模型,再统一进行评估。

    现在我已经有了这些模型,那就动手试试吧!

    背景与上一次测试

    在上一轮 IFT 测试结束时,我得到了如下表格。它按测试集损失(保留三位小数)排序,并显示了模型在一次指令微调运行中获得的分数:

    总体上存在一种松散的相关性:损失越低,IFT 分数越高,但有两个奇怪的例外:FineWeb-Edu 的两次训练运行,其得分远高于根据其损失所预期的水平。

    我的工作假设是,模型获得高分有两个组成部分:

  • 其原始智能:损失更低的模型更“聪明”,因此在微调后更擅长遵循指令。
  • 其知识储备。除了 FineWeb-Edu 之外,所有模型(包括我的和 OpenAI 的)都是在基本未经筛选的互联网数据上训练的。而 FineWeb-Edu 旨在成为 FineWeb 中“最具教育意义”的子集,因此 presumably 包含更密集的有用事实。
  • 这么说来,OpenAI 的模型以及 Cloud FineWeb、8x A100 40 GiB 这类模型可能很“聪明”,但知识储备未必丰富;而 FineWeb-Edu 的模型则可能显得“愚钝”,但知识面广。至于介于两者之间的模型,则可能既不够聪明,知识也不够多。

    还有一个奇怪之处:Cloud FineWeb、8x A100 40 GiB 模型在 IFT 结果上的表现,相对于其 loss 值来说好得令人意外——也许存在某种阶跃函数效应,即一旦模型的 loss 降到某个阈值(比如 3.7)以下,它就会突然在关键方面变得“聪明”起来。

    当然,这些都只是粗略的推测,但也算是一种假设。新的模型是否符合这种模式?是时候验证一下了。

    初步运行与谜团

    我不认为有必要把我在干预测试中训练的全部 14 个模型都加进那张表格,所以决定只添加其中四个:

  • 8xa100m40-baseline:所有干预实验的基准云训练模型。
  • 1xrtx3090-baseline:同一模型的本地训练版本——即本文第一个提到的模型。
  • 8xa100m40-stacked-interventions-1:我们在云端设法得到的最佳模型。
  • 1xrtx3090-stacked-interventions:最佳的本地模型——即本文第二个提到的模型。
  • 对于其他模型,我已经有了其微调后版本的响应文件,因此只需对这四个新模型运行我的两个微调脚本中的第一个即可。

    我照做了,并同时修改了评判脚本,使其不再使用 GPT-5.1,而是改用 GPT-5.4。即使多次运行脚本,每次给出的分数通常也会不同;希望排名大致保持一致。既然无论如何都得重新运行脚本来获取新的汇总结果,而这些结果本身也无法与原始结果真正可比,那么为了( hopefully)获得一个更聪明的评判器,这个代价似乎是合理的。

    我运行了一次,得到了一些让我惊讶的结果——惊讶到决定再跑三次,看看结果是否稳定。确实如此;以下是新表格,包含每次运行的得分、平均值,以及基于平均值得出的排名。

    可以看出,在 IFT 运行中,各模型的相对排名相当一致。但总体而言,尽管 loss 较低的模型通常 IFT 表现更好,如今这一趋势的例外情况却比之前更多了。

    我们来看“IFT 排名”这一列,它是基于 IFT 平均值得出的:

  • 第一个意外是 8xa100m40-stacked-interventions-1。它的 loss 排名第四好,但在指令微调测试中却是所有模型里最差的!它的训练数据与其他模型(除了 OpenAI 和 FineWeb-Edu 的模型)完全相同。更令人困惑的是,它与我所能做到的最接近的 1xrtx3090-stacked-interventions 几乎一模一样,结果却截然不同。你可能还记得前文提到,这两个训练运行起始权重相同,训练配置也完全一致;唯一区别在于它们运行在不同的架构上,一个使用 DDP 实现真实全局 batch size 为 96,另一个则通过梯度累积达到相同的 batch size。
  • 1xrtx3090-baseline 的表现也远低于其损失数值所预期的水平;在损失指标上,它仅比 Cloud FineWeb、8x A100 40 GiB 略差一点,但在 IFT 测试中却差得多。同样,这个模型本质上是对另一个模型的复现:8xa100m40-baseline,两者属于同一训练任务,只是后者使用 DDP 而非梯度累积。问题依旧——一对高度相似的模型中,有一个在 IFT 测试中表现更差。但这一次,出问题的恰恰是使用了梯度累积的那个模型。
  • 这种情况确实很奇怪。如果所有使用梯度累积而非 DDP 的训练任务 consistently 更差(或者反过来),那我们或许还能找到某种关联。但实际情况是:第一次实验中 GA 优于 DDP,第二次却反过来。

    除此之外,我们仍能观察到两个 FineWeb-Edu 模型的表现明显优于其他模型。其余模型在损失值和排名上都相当接近,只有 Local FineWeb 训练模型例外,它在两方面都表现不佳。

    然而值得注意的是,Local FineWeb-Edu 扩展训练模型虽然训练数据量是 Local FineWeb-Edu 的两倍,但在 IFT 指标上 consistently 更差。这在我之前的测试中并未出现。

    所有这些结果都让我感到困惑。“知识越多,模型在此任务上表现越好”这一观点,似乎被两个 FineWeb-Edu 模型的相对排名削弱了(毕竟,如果该观点成立,我们理应看到训练数据更多的模型 consistently 更优)。而“聪明、低损失的模型表现更好”这一边,也被 8xa100m40-stacked-interventions-1 和 1xrtx3090-baseline 的糟糕结果所 contradicted。

    这里到底发生了什么?

    微调轮次(Epochs of fine-tuning)

    查看训练代码时,有一件事引起了我的注意。流程如下:

  • 在训练集上对模型进行最多 100 轮的微调。
  • 如果保留的验证集上的损失高于上一轮的结果,则提前退出,并使用上一轮的模型生成响应。
  • 实际上,提前退出的机制总是很快触发。我在最初生成新模型结果时就注意到了这一点:

  • 8xa100m40-baseline 在第 6 轮后验证损失开始上升。
  • 1xrtx3090-baseline 在第 5 轮后
  • 8xa100m40-stacked-interventions-1 在第 4 轮后
  • 1xrtx3090-stacked-interventions 在第 5 轮后
  • 我决定重新生成所有模型的响应,并再次让 LLM 评判器评估这些新响应。但这一次,我会记录提前退出前实际训练了多少轮:

    结果越来越难看出任何有用的规律!不过有一点确实突出:那个仍然异常高的 Cloud FineWeb、8x A100 40 GiB 模型接受了七轮指令微调。同样明显的是,两个 FineWeb-Edu 模型都表现出相同的“优势”(如果这算优势的话)。但 Local FineWeb 训练也跑了七轮,得分却很差;OpenAI 模型每轮只跑了两轮,却领跑榜单;而 1xrtx3090-baseline 尽管训练了六轮,结果也相当糟糕。

    不过,如果我们排除这个干扰因素会怎样?我又做了一轮实验;这次,我修改了微调/生成脚本,强制固定为四轮训练——不再提前退出。我选四轮是因为它在之前的训练中是众数——除此之外并无特别理由。

    四轮训练

    最终结果如下:

    依然没有明显的规律。

    训练七个周期

    如果我们让所有模型都训练七个周期,会怎么样?这样它们都能获得和 FineWeb-Edu 模型一样多的“好处”(如果这算好处的话)?

    还是一头雾水……

    汇总结果

    下表汇总了这些测试中我们得到的所有排名:

    从中很难得出太多结论,但有几点是明确的:

  • 该测试的性能与损失相关,但远非唯一影响因素。
  • OpenAI 的权重模型 consistently 领先。
  • 在我们自己的模型中,1xrtx3090-stacked-interventions、Cloud FineWeb、8x A100 40 GiB 以及 Local FineWeb-Edu train 表现相当不错。
  • 奇怪的是,Local FineWeb-Edu extended train —— 它只是在 Local FineWeb-Edu train 基础上额外训练了 30 亿个 FineWeb-Edu 数据集的 token —— 表现 consistently 比其基础模型更差。
  • 8xa100m40-stacked-interventions-1 和 1xrtx3090-baseline consistently 表现糟糕。Cloud FineWeb、8x A100 80 GiB 的表现也不尽如人意。
  • 一方面,在这种评估中对不同模型采用不同训练周期数似乎不妥,因为它们被“区别对待”了。但另一方面,如果目标是真实世界中模型实用性的良好评估,那么各个模型本应根据验证损失进行不同程度的微调。所以也许这样反而更好?

    但这些差异化的结果仍然令人费解。我想,现代 AI 应该能轻松为我构建一个数据探索界面,专门用于分析原始结果和七周期训练的结果,于是我问了 Claude,得到了一个相当不错的工具。

    然而仔细研究之后,我仍未能找到确凿证据——例如,8xa100m40-stacked-interventions-1 总是犯某种系统性错误,导致其得分偏低。

    目前我所能构建的最好的——尽管有些模糊且不完整——心理模型大致如下:如果我们考虑这些模型所处的损失 landscape,它们都已被训练至尽可能达到低损失的位置。当我们对其进行指令微调时,实际上是在改变这个 landscape——“更好地遵循指令”这一目标与“最小化损失”并不相同。

    现在,这两个 landscape 可能完全不同!你可以想象一个替代指令遵循的任务,它可能与损失最小化完全无关,甚至呈负相关。

    但指令遵循还算相对接近;它至少共享“生成连贯文本”这类特征。因此,在进行指令微调时,我们的目标是将模型从预训练结束时的位置,移动到在新目标——指令遵循——上表现最佳的位置。

    接下来我要说得更加模糊一些。你可以很容易地设想:在某些损失较低的位置,可能存在指向新指令遵循 landscape 中优良位置的下坡路径。通过指令微调,你就能得到一个不错的 IFT 模型。

    但在其他一些损失较低的位置,可能就没有这种优势;也许它们正处于或接近 IFT landscape 中的一个糟糕“局部最小值”——也就是说,那里没有通往更优位置的下坡路径。因此,像这样简单的微调可能永远无法得到好的结果!

    在这种思路下,我们可以说 OpenAI 的权重不仅处于损失景观(loss landscape)中的有利位置,在指令微调(IFT)景观中也同样如此。FineWeb-Edu 模型算是运气好,恰好落到了一个(尽管损失值较高)但对 IFT 目标而言位置优越的地方。相比之下,8xa100m40-stacked-interventions-1 和 1xrtx3090-baseline 则运气不佳:它们到达的位置中,损失景观与 IFT 景观之间缺乏良好相关性。

    这个解释在我看来足够合理,可以作为我目前的工作模型,并尝试找出某种方法来验证它。在指令微调过程中跟踪验证损失无疑是一个良好的开端;遗憾的是,我是在完成上述所有测试之后才意识到这一点,而重新做一遍工作量会相当大。

    最后还有一点值得重申:我们那两个“运气不佳”的模型——8xa100m40-stacked-interventions-1 和 1xrtx3090-baseline——各自都有一个“孪生”模型。前者是梯度累积版 1xrtx3090-stacked-interventions 对应的 DDP 训练版本,而后者则是 8xa100m40-baseline 对应的梯度累积版本。因此,虽然显然发生了某些异常情况,但 DDP 或梯度累积本身似乎并非罪魁祸首。

    我认为此时最好就此打住——我还有很多其他事情想做,而这项研究目前更像是一个支线任务。

    不过,我从中得出一个主要结论:追求更低的损失值虽然在技术上有其趣味,但并非唯一目标。在某些情况下,损失更低的模型在实际使用中反而可能表现更差。

    接下来:我将结束这个关于“干预措施”的迷你系列,转向我从零开始构建 LLM 之旅的最后阶段。到时见!

    需要完整排版与评论请前往来源站点阅读。