返回 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

归档

分类

友情链接

我一直在基于 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,希望它能相对一致地对这些回复进行评分。
  • 具体细节见这里。

    因为这样做工作量大大增加,我没有把这些测试纳入干预措施的迷你系列中。我觉得更有意义的做法是等尝试完一系列干预措施、积累多个模型后再统一测试。

    现在我已经有了这些模型,让我们开始吧!

    背景与上次测试结果

    在上轮 IFT 测试结束时,我有过这张表格。它按测试集损失值排序(精确到小数点后三位),并列出了模型在指令微调后的得分:

    总体趋势是损失越低,IFT 得分越高,但有两个异常情况:两次 FineWeb-Edu 训练的结果远高于根据损失值预期的水平。

    我当时认为,模型获得高分有两个关键因素:

  • 首先是它的基础智能水平:损失越低的模型越聪明,微调后执行指令的能力也越强。
  • 其次是知识密度。除了 FineWeb-Edu 外,所有模型(包括我的和 OpenAI 的)都基于互联网上经过最低限度筛选的数据训练而成。而 FineWeb-Edu 被设计为“最具教育价值”的 FineWeb 子集,因此很可能包含更多有用的知识点。
  • 这么说来,OpenAI 模型和 Cloud FineWeb(8x A100 40 GiB)可能“聪明但所知不多”,而 FineWeb-Edu 模型则可能是“愚钝却知识渊博”。相比之下,中间的那些模型也可能相对愚钝,而且所知也有限。

    还有一个奇怪之处:Cloud FineWeb(8x A100 40 GiB)模型在 IFT 测试中表现异常出色,尽管它的损失值并不低——也许存在某种阶跃函数机制,一旦模型在损失上优于(比如)3.7,它就会突然变得“智能”起来,至少在相关指标上如此。

    当然,这纯属猜测,但至少是一种可能的解释。新模型是否符合这一模式?是时候验证一下了。

    初次运行与谜团

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

  • 8xa100m40-baseline,即所有干预实验所基于的云训练基准模型。
  • 1xrtx3090-baseline,这是同一模型的本地训练版本——也就是本文第一篇中提到的第一个模型。
  • 8xa100m40-stacked-interventions-1,我们在云端能获得的最好模型。
  • 1xrtx3090-stacked-interventions,本地训练的最佳模型——即本文第二篇中介绍的那个。
  • 现在,我已经有了经过微调的其他模型响应文件,因此只需用我的两个微调脚本中的第一个,对这四个新模型进行处理即可。

    我这样做了,同时也调整了评判脚本,使其不再使用 GPT-5.1,而是改用 GPT-5.4。如果你多次运行该脚本,每次结果通常都会不同;不过希望排名大致保持不变。考虑到我不得不重新运行脚本来获取新的汇总结果,而这些结果本身也难以与原数据直接比较,那么为了( hopefully)一个更聪明的评判者而付出这个代价似乎是合理的。

    我只运行了一次,就得到了令人惊讶的结果——惊讶到让我决定多做三次运行,看看结果是否稳定。事实证明确实如此;以下是新表格,列出了每次运行的分数、平均值,以及根据平均值得出的排名。

    你可以看到,IFT 各次运行的相对排名相当一致。虽然总体而言,损失越低的模型在 IFT 测试中表现越好,但现在这种趋势出现了比以往更多的例外。

    让我们来看看“IFT rank”列,它基于 IFT 的平均得分:

  • 第一个意外是 8xa100m40-stacked-interventions-1。它的损失排第四,但在所有模型中却是指令微调测试中最差的!它除了 OpenAI 模型和 FineWeb-Edu 模型外,使用的训练数据与其他模型完全相同。更令人困惑的是,它与 1xrtx3090-stacked-interventions 的匹配度已经尽可能接近,却得出了完全相反的结果。你可能还记得,这两个实验最初共享相同的权重和完全一致的训练配置;唯一的区别在于它们运行在不同架构上,其中一个使用了 DDP 并实现了真实的全局批量大小为 96,另一个则通过梯度累积达到相同批量大小。
  • 1xrtx3090-baseline 的表现远不如其损失数值所预期的那样好;它在损失指标上仅略逊于 Cloud FineWeb(8块 A100 40 GiB),但在 IFT 测试中却差得多。这本质上是一个克隆模型:8xa100m40-baseline,同样是使用 DDP 而非梯度累积的训练运行。问题相同——在一对高度匹配的模型中,总有一个在 IFT 测试中表现更差。但这次情况相反:反而是采用梯度累积的那个模型表现不佳。
  • 这种情况非常奇怪。如果梯度累积(GA)始终比 DDP 差,或者反过来总是更差,我们或许还能设想某种关联。但实际情况是:第一次 GA 优于 DDP,第二次却是 DDP 更好。

    除了这一点,我们依然看到两个 FineWeb-Edu 模型明显优于其他模型。其余模型在损失值和排名上都相当接近,唯独 Local FineWeb train 两项都表现糟糕。

    不过有趣的是,Local FineWeb-Edu extended train 虽然在数据量上是 Local FineWeb-Edu train 的两倍,但在 IFT 分数上却持续落后。我之前测试时并非如此。

    这让我十分困惑。“大量知识让模型在此任务上表现更好”这一观点,似乎被两个 FineWeb-Edu 模型的相对排名削弱了——毕竟若该说法成立,训练更多数据的模型理应全面领先。而“低损失即代表智能且更优”的说法,也被 8xa100m40-stacked-interventions-1 和 1xrtx3090-baseline 的糟糕结果所质疑。

    那么,究竟发生了什么?

    微调轮次的影响

    查看训练代码后,我发现一个关键点。流程如下:

  • 在训练集上最多微调 100 个 epoch。
  • 如果在保留的验证集上损失高于上一 epoch,则提前终止,并使用前一 epoch 的模型生成回复。
  • 实际上,早停机制几乎每次都迅速触发。我在最初为新模型生成结果时就注意到了这一点:

  • 8xa100m40-baseline 在第 6 个 epoch 开始出现验证损失上升。
  • 1xrtx3090-baseline 在第 5 个 epoch。
  • 8xa100m40-stacked-interventions-1 在第 4 个 epoch。
  • 1xrtx3090-stacked-interventions 在第 5 个 epoch。
  • 我决定重新为所有模型生成回复,并将新回复再次提交给 LLM judge。但这次我会记录每个模型实际训练了多少个 epoch 后才退出:

    结果愈发难以看出任何有用模式!但有一点突出:那个 IFT 分数仍异常高的 Cloud FineWeb(8xA100 40GiB)模型恰好训练了七个 epoch。值得注意的是,两个 FineWeb-Edu 模型也都有这个“优势”。然而 Local FineWeb train 同样训练了七轮,得分却很低;OpenAI 模型只训练了两轮,反而拔得头筹;而仅训练六轮的 1xrtx3090-baseline 也取得了较差的结果。

    尽管如此,如果我们能消除这个干扰因素会怎样?我进行了另一组实验:修改微调/生成脚本,强制固定训练四轮——不再启用早停。之所以选四,是因为它是之前训练中最常见的轮数,别无深意。

    训练四轮后的结果

    最终结果如下:

    仍然没有明显的规律。

    训练七轮

    如果我们让所有模型都训练七轮,这样它们都能像 FineWeb-Edu 模型一样获得“好处”(如果这真是好处的话)呢?

    还是一样让人困惑……

    综合来看

    以下是我们在这些测试中获得的所有排名的表格:

    很难从中得出什么明确的结论,但有些现象是清晰的:

  • 在这个测试中的表现与损失相关,但远非唯一决定因素。
  • OpenAI 权重始终处于领先地位。
  • 在我们自己的模型中,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 的表现也不尽如人意。
  • 一方面,为不同数量的 epoch 训练不同的模型,在这种评估方式下感觉不太合理,因为它们被“区别对待”了;另一方面,如果这种评估旨在真实反映模型的实际用途,那么不同模型根据验证损失进行不同时长的微调也是合理的。所以也许这样反而是更好的做法?

    但这些差异的结果依然是个谜。我想现代 AI 应该能轻松为我构建一个专门用于分析原始结果和七轮训练结果的探索界面,于是我问了 Claude,它给了我这样一个相当不错的工具。

    然而仔细研究之后,我仍然没有找到确凿的证据——比如某种系统性错误导致 8xa100m40-stacked-interventions-1 的分数持续偏低。

    目前我认为最合理的解释(尽管有些模糊且不完整)是这样的:如果我们把这些模型看作处于同一个损失景观中,它们都被训练到尽可能低的损失位置。当我们对其进行指令微调时,实际上是在改变这个景观——“更好地遵循指令”的目标与“更好地最小化损失”是不同的。

    这两个景观可能完全不同!你可以想象一个任务,它完全与损失最小化无关,甚至呈负相关。

    但指令遵循任务相对接近,至少共享一些特征,比如“生成连贯文本”。因此当我们进行指令微调时,实际上是从预训练后模型所处的位置出发,迁移到一个在新目标——即指令遵循——上表现最佳的位置。

    接下来我要开始有些模糊地推测了。你可以想象某些低损失区域可能存在通往新指令遵循景观中良好位置的“下坡路径”。通过指令微调,我们就能得到一个优秀的 IFT 模型。

    但其他低损失区域可能不具备这种优势;它们可能位于或靠近 IFT 景观中的“局部极小值”——也就是说那里没有通往更好位置的路径。因此简单的微调可能永远无法取得好结果!

    在这种思路下,我们可以说 OpenAI 的权重在损失景观和 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 旅程的最后阶段。敬请期待!

    这是本系列下一篇文章的链接。

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