LLM训练实战手册
0. 引言
前段时间,我完整地读完了Hugging Face团队发布的SmolLM训练技术文章,这真的是我最近读过介绍最详尽的大模型训练技术文章,强烈推荐给每一位对LLM感兴趣的朋友!
不同于那些只展示最终成果的学术论文或技术报告,这篇文章更像是一本毫无保留的 “实战剧本”。它不仅分享了成功的“秘方”,更坦诚地揭示了训练过程中混乱的现实——那些深夜调试的崩溃瞬间、意外出现的Bug,以及一次次推倒重来的艰难决策。
文章完整覆盖了从零到一训练一个世界级模型的全过程:从最初的“灵魂三问”(为什么要训练模型?),到具体的架构设计、数据筛选、消融实验,再到长达一个月的训练马拉松和最后的后训练调优。每一个环节都充满了宝贵的实战细节和踩坑经验。更难得的是,SmolLM的训练代码和数据集也已完全开放,这让整篇文章的参考价值和可复现性更上一层楼。
我深感这篇文章对于任何想要深入了解大模型训练的从业者都极具价值,因此在Gemini的协助下,我将文章的纯文字部分翻译整理了出来,希望能帮助到中文社区的同学们。
原文中包含了大量宝贵的图表、动画和交互式内容,它们是理解许多核心概念的关键。由于格式所限,这篇翻译未能包含这些视觉元素。可以阅读原文以获得最完整的体验。
原文链接:https://huggingface.co/spaces/HuggingFaceTB/smol-training-playbook
1. 前言
表面上看,学术论文总将模型训练描绘得一帆风顺:正确的架构选择、完美的数据集配比,再加上充足的算力。最终呈现的结果光鲜亮丽,消融实验严谨清晰,事后复盘时,每个决策都显得那么理所当然。
然而,这些报告往往只展示了最终的成功,并带上了一层“往事皆美好”的滤镜。它们不会告诉你那些凌晨两点还在调试的数据加载器、训练中突然出现的损失尖峰,或是那个悄无声息地破坏了整个训练过程的张量并行(Tensor Parallelism)Bug(我们稍后会详细聊到!)。现实世界中的模型训练,远比论文中描述的更加混乱和曲折,充满了大量未能被载入最终篇章的“至暗时刻”。
这一次,我们将带你深入幕后,完整揭秘 SmolLM 的训练历程——这是一个在 11万亿(11T)Token 数据上训练出的 30亿(3B)参数多语言推理模型。
这并非一篇按部就班的技术报告,而是一份错综复杂的实录,它记录了我们在无数决策、发现和弯路之间摸索出的核心洞见,而这些正是构建世界级语言模型的关键所在。
同时,本文也是我们模型训练系列长文的收官之作。在此之前,我们已经深入探讨了如何构建大规模数据集(FineWeb)、如何调度数千块GPU协同工作(Ultra Scale Playbook),以及如何在训练的每个阶段选择最佳评估方案(Evaluation Guidebook)。现在,我们将把所有这些要素融会贯通,共同打造一个强大的AI模型。
在这趟旅程中,我们不仅会分享最终成功的“秘方”,更会坦诚地剖析那些失败的尝试、基础设施的故障以及艰难的调试过程——正是这些塑造了我们的每一个决策。
整个故事读起来,就像一出跌宕起伏的戏剧:
- 你会看到,为何那些在小规模实验中前景光明的想法,到了大规模训练时却可能完全失效;
- 为什么我们在训练了 1万亿(1T)Token 之后,不得不推倒重来;
- 我们是如何在强大的英语能力基础上,巧妙地平衡多语言、数学和代码这几个相互“竞争”的目标;
- 以及最终,我们是如何通过后训练(post-train),打造出一个强大的混合推理模型。
我们尽力将这段冒险之旅编织成一个连贯的故事,而不是一份冷冰冰的操作手册。希望你能把它当作一本实战派的武功秘籍,献给所有渴望从“我们有不错的数据和算力”迈向“我们构建了一个真正顶尖的模型”的探索者。
我们希望,这种毫无保留的分享能够填平理论研究与工程实践之间的鸿沟,让你在下一次模型训练时,少一些意外,多几分从容。
1.1 如何阅读这篇文章?(你也许不必一口气读完)
首先得承认,这篇文章非常、非常长。想从头到尾一次性读完,几乎是不可能的任务。
但好消息是,我们已将文章拆分为几个相对独立且主题明确的板块。你可以根据自己的兴趣和需求,随时跳转或选择性阅读。
以下是文章的结构指南:
- 🧭 训练指南针(Training Compass):
- 核心内容: 宏观层面的探讨,帮助你判断是否真的需要从头预训练一个模型。
- 适用人群: 在你“烧光”所有融资之前,我们为你准备了几个关键的自查问题,并带你系统地梳理决策思路。
- 友情提示: 这部分偏向战略思考。如果你是“硬核技术党”,渴望直奔技术细节,可以快速跳过此节。
- 🚀 预训练(Pretraining):
- 核心内容: 这里涵盖了从零开始构建一个可靠预训练模型所需的一切:如何设计和运行消融实验、选择评估指标、调配数据来源、做出架构决策、调整超参数,以及最终如何“熬过”这场训练的马拉松。
- 适用人群: 无论你是计划从零开始预训练,还是对在现有模型基础上继续预训练(Continued Pretraining)感兴趣,这个板块都值得一读。
- 🍒 后训练(Post-training):
- 核心内容: 在这一部分,你将学到如何将预训练模型的潜力发挥到极致。从 SFT (监督微调)、DPO (直接偏好优化)、GRPO 等一系列令人眼花缭乱的缩写术语,到模型合并(Model Merging)这种充满了“玄学”与“炼金术”色彩的操作。
- 价值所在: 那些让算法真正奏效的宝贵知识,往往是用惨痛的教训换来的。我们将在此分享我们的经验,希望能帮你少走弯路。
- 🏭 基础设施(Infrastructure):
- 打个比方: 如果说预训练是制作蛋糕胚,后训练是涂抹糖霜和点缀樱桃,那么基础设施就是那台工业级烤箱。没有它,一切免谈;如果它中途罢工,你原本轻松的周末烘焙计划,就可能演变成一场厨房灾难。
- 核心内容: 理解、分析和调试GPU集群的知识,往往散落在各类技术库、官方文档和网络论坛的角落里。本节将详细剖析 GPU 布局、CPU/GPU/节点/存储之间的数据通信模式,以及如何精准定位并解决性能瓶颈。
那么,我们从哪儿开始呢? 很简单,选择你最感兴趣的板块,即刻出发吧!
2. 训练指南针:灵魂三问(Why → What → How)
在机器学习领域,我们似乎对“优化”这个词有种近乎偏执的迷恋。我们的目光总是聚焦在损失曲线上、模型架构的改进和训练吞吐量的提升上。毕竟,从根本上说,机器学习的核心就是优化一个损失函数。然而,在深入这些技术细节之前,一个更根本的问题却常常被我们忽略:我们真的有必要从头开始训练这个模型吗?
如今的开源AI生态日新月异,几乎每天都有世界级的模型涌现:Qwen、Gemma、DeepSeek、Kimi、Llama(及其迭代版本)、Olmo……这份名单每月都在变长。它们早已不是仅供研究的原型或“玩具”,而是可以直接投入生产环境的强大模型,覆盖了从多语言理解、代码生成到复杂推理等极为广泛的应用场景。更重要的是,它们大多附带宽松的开源许可,背后还有活跃的社区随时为你答疑解惑。
这一切,揭示了一个你可能不愿面对的现实:或许,你根本不需要从头训练一个模型。
这听起来,像是一篇“大模型训练指南”最出人意料的开场白。但事实是,许多训练项目的失败,并非源于超参数没调好或代码里有Bug,而是因为决策者从一开始就决定去训练一个他们根本不需要的模型。因此,在投入真金白银和宝贵时间,一头扎进“如何做”的技术细节之前,你必须先清晰地回答两个前置问题:你为什么要训练这个模型? 以及 你应该训练一个什么样的模型? 如果对这两个问题没有明确的答案,你很可能会耗费数月的算力和工程师资源,最终只造出了一个“世人早已拥有”的轮子,甚至更糟——一个根本没人需要的东西。
让我们从 “Why”(为什么) 开始。因为如果目标不明确,后续的所有决策都将是无的放矢。
2.1 Why:那个没人愿意回答的问题
不妨坦诚一点,在实际操作中,我们经常看到这样的情景。
某人(如果足够幸运的话)拿到了一笔GPU集群的访问权限——可能来自一笔研究经费,或是公司内部的闲置算力。他们的想法通常是这样的:“我们有100块H100,能用三个月。来训练个模型吧!”于是,模型的大小被随意敲定,数据集则从各种能找到的来源东拼西凑而成。训练开始了。六个月后,算力预算和团队士气双双耗尽,模型却无人问津,因为从始至终,都没有人认真问过那个“为什么”。
以下是一些绝对不应该成为你训练模型的理由:
- “我们正好有闲置的算力。”(这是资源,不是目标。)
- “别人都在做,我们也不能落下。”(这是盲目跟风,不是商业战略。)
- “AI是未来。”(这是正确的废话,不是行动计划。)
- “我们想要一个最强的模型。”(这个目标过于模糊,无法指导任何具体决策。)
“我们训练了自己的模型”——这句话听起来确实很有诱惑力。但在投入大量时间和资源之前,请务必扪心自问:你究竟为什么需要训练这个模型?
在启动一个大型预训练项目之前,你可以先进行一次思想实验。从最基本的技术层面出发,你首先应该搞清楚的是:是否已经有一个现成的开源模型,通过简单的提示工程(Prompting)或微调(Fine-tuning)就能满足你的需求?
本质上,只有在以下三个领域,定制化的预训练才真正具备战略价值:你想进行开创性的科学研究,你的生产应用场景有高度特殊化的需求,或者你想填补当前开源模型生态中的某个空白。让我们逐一分析。
2.1.1 科研(Research):你到底想探索什么?
在大语言模型(LLM)领域,有无数激动人心的研究课题等待探索。
这些研究项目的共同点是:它们通常都始于一个定义清晰的问题。例如:
- 我们能否将一款新型优化器的训练规模扩展到百亿(10B)参数以上的模型?(参见:Muon in LLM training)
- 我们能否仅通过强化学习,而不依赖监督微调(SFT)来激发模型的推理能力?(参见:DeepSeek-RL: A Human-like LLM with Real-world Feedback)
- 我们能否仅使用纯合成的“教科书式”数据,就训练出性能卓越的小模型?(参见:Textbooks Are All You Need)
- 我们能否通过只使用开放许可(Openly Licensed)的数据,来达到业界领先的性能水平?(参见:The Common Pile v0.1)
把研究假设设定得尽可能具体,并提前规划好所需的实验规模,将极大提高你成功的概率。
2.1.2 产品化(Production):为什么现成的模型不够用?
在企业应用中,现成的通用模型无法满足特定业务需求的原因,主要有三点。其中两个是技术性的,另一个则与合规治理相关。
第一个理由是:领域特殊性(Domain Specificity)。 当你的数据或任务涉及高度专业化的词汇、知识体系或数据结构,而现有的通用模型无法有效处理时,自研模型就变得必要。例如:
- 一个专注于DNA序列分析的模型,它需要独特的词汇表和理解长距离依赖关系的能力。
- 一个法律或金融模型,它必须对行业术语和内在逻辑有深刻的理解。
第二个理由是:部署约束(Deployment Constraints)。 当你需要模型适配特定的硬件、延迟或隐私要求时。例如,一个需要在无人机上本地运行的模型,或是在配备了定制硬件(如FPGA)的私有化环境中运行的模型。
这里有一个简单的验证方法:花几天时间,基于当前最先进的开源模型(如Qwen、Gemma等)进行快速验证。你 能否通过提示工程、工具调用或后训练来达到你的性能目标?如果答案是否定的,那么,也许是时候考虑自己训练一个了。
- 温馨提示: 即使为了满足业务需求,你需要投入庞大的后训练预算,它也大概率比从零开始预训练更经济。 毕竟,在现有模型基础上再训练1万亿(1T)Token,仍然比从头开始训练10万亿(10T)Token要划算得多。
- (也正是在这种情况下,大模型训练者们开始巧妙地将其称为“持续预训练”(Mid-training),而不是后训练了。)
第三个理由是:安全与治理(Safety and Governance)。 如果你身处一个受到严格监管的行业,或处理高风险应用,你就必须对训练数据、模型行为和迭代周期拥有完全的掌控力。你需要精确地知道模型学了什么,并能向监管机构证明其合规性。在这些场景下,自研模型往往是唯一的选择。
以上是企业内部训练模型的主要动机。那么,那些致力于发布开源模型的机构又是如何思考的呢?
2.1.3 战略性开源(Strategic Open-Source):你是否看到了可以填补的空白?
经验丰富的AI实验室发布新的开源模型,最常见的原因是:他们敏锐地识别出了开源生态系统中的某个特定空白,或是预见了一个全新的AI应用场景。
这种模式通常是这样的:你发现了一个尚未被充分开发的领域。也许市场上缺少一款功能强大、支持超长上下文的端侧(on-device)模型;或者现有的多语言模型在小语种上的表现普遍很差;又或者,行业正在向交互式世界模型(如Genie)这样的新范式演进,但还没有成熟的开源方案。
你有理由相信自己能做得更好。也许你整理出了质量更高的数据集,开发出了更优的训练“配方”,或者拥有别人无法企及的算力优势来支持模型进行“超充分训练”(Overtrain)。你的目标非常具体:不是要打造“史上最强模型”,而是要构建“最适合端侧部署的3B模型”,或是“首个支持1M上下文的小模型”。
这是一个现实且有价值的目标。一旦成功,你将创造巨大的价值:开发者会纷纷采用你的模型,它会成为他人创新的基础,或是为你奠定技术领先的声誉。但成功需要深厚的经验。在这个竞争激烈的赛道上,你必须清楚地知道什么是真正可行的,以及如何可靠地将想法付诸实践。
为了让这个思路更具体,让我们来看看Hugging Face自身的思考。
2.1.4 Hugging Face 的思考:我们为什么要训练开源模型?
那么,Hugging Face为什么要投入资源去训练和发布开源模型呢?答案很简单:我们致力于为开源生态系统创造价值,并填补那些少有人涉足的空白。
这不仅包括模型,也包括数据集和工具。我们启动的每一个LLM训练项目,都始于发现一个行业缺口,并坚信我们能为此做出有意义的贡献。
我们的第一个大语言模型项目,始于GPT-3 (Brown et al., 2020) 发布之后。当时,似乎没有人愿意构建一个开放的替代品,我们担心相关的核心知识最终会被少数几家巨头实验室垄断。因此,我们发起了BigScience研讨会,旨在训练一个开源版本的GPT-3。其最终成果Bloom模型,是数十位贡献者历时一年心血的结晶——从构建训练框架、分词器到预训练语料库,最终我们成功预训练了一个1750亿(175B)参数的模型。
Bloom的继任者是2022年的StarCoder (Li et al., 2023)。当时,OpenAI为GitHub Copilot开发的Codex (Chen et al., 2021)模型是闭源的。我们清楚地看到,构建一个开源的替代品将为整个开发者生态带来巨大价值。因此,我们与ServiceNow合作,在BigCode项目框架下,构建了The Stack数据集,并训练了StarCoder 15B来复现Codex的能力。StarCoder2 (Lozhkov et al., 2024)的诞生,则源于我们意识到可以进行更长时间的训练,并且一个更小但训练更充分的模型,可能比一个参数巨大的模型更有价值。我们训练了一个模型家族(3B/7B/15B),使用了数万亿(Trillions)的Token进行训练,其训练量远超当时任何开放的代码模型。
SmolLM系列也遵循了同样的逻辑。我们注意到当时强大的小型模型非常稀缺,而我们恰好刚刚构建了FineWeb-Edu (Penedo et al., 2024)这个高质量的预训练数据集。SmolLM (135M/360M/1.7B)是我们的首次尝试。SmolLM2 (Allal et al., 2025)则专注于更好的数据和更长的训练,在多个基准上达到了SOTA性能。而SmolLM3则将参数规模扩展到30亿,同时融入了混合推理、多语言能力和长上下文等2025年社区高度关注的新特性。
这种模式甚至延伸到了预训练之外:我们训练了Zephyr (Tunstall et al., 2023)来验证DPO可以被大规模应用;启动了Open-R1项目来复现DeepSeek R1的蒸馏管线;并发布了用于算法竞赛的OlympicCoder,在国际信息学奥林匹克竞赛中取得了SOTA性能。我们还积极探索其他模态,例如用于视觉的SmolVLM (Marafioti et al., 2025)和用于机器人技术的SmolVLA (Shukor et al., 2025)。
希望到这里,我们已经成功说服你:在开启训练之前,深入思考“为什么”是至关重要的。
在本文的后续部分,我们将假设你已经完成了这场“灵魂拷问”,并找到了一个充分且合理的理由来启动你的训练项目。
2.2 What:将目标转化为具体决策
既然你已经明确了为什么要训练,接下来就要决定应该训练什么了。
这里的“做什么”(What)指的是一系列具体的技术规格:模型类型(密集型、专家混合MoE、混合架构,还是全新范式)、模型规模、架构细节以及数据配比。
一旦“Why”被确定,“What”便可以顺理成章地推导出来。举例来说:
| 训练目的 (Why) | $\rightarrow$ | 模型规格 (What) |
|---|---|---|
| 追求能在端侧设备上快速运行 | $\rightarrow$ | 小巧、高效的模型 |
| 构建强大的多语言理解能力 | $\rightarrow$ | 更大的分词器词汇表 |
| 需要处理超长上下文 | $\rightarrow$ | 采用混合注意力等特殊架构 |
除了这些由应用场景驱动的决策外,还有一些选择旨在优化训练过程本身,比如让训练更稳定、样本效率更高,或者速度更快。这些决策并非总有标准答案,但你可以大致将决策过程分为两个阶段:
规划阶段(Planning): 在动手实验之前,你需要将你的目标映射到具体的模型组件上。你的部署环境决定了模型规模的上限;你的项目周期决定了你可以承担多大的架构创新风险;你的目标能力则决定了对数据集的要求。这个阶段的核心就是:将“Why”中的每一个约束,都与“What”中的具体规格一一对应起来。
验证阶段(Validation): 当你有了一个初步方案和一系列潜在的改进点后,就需要进行系统性的测试。由于测试成本高昂,你应该将精力集中在那些能显著提升目标场景性能,或能大幅优化训练效率的改动上。这便是消融实验(Ablations)的用武之地,我们将在后续章节中详细探讨。
在接下来的章节中,你将了解到定义一个模型的所有技术选项,以及如何通过系统性实验来做出明智的选择。但在那之前,我们想分享一些关于如何组织团队和项目的经验——这些经验不仅来自我们自身的实践,也来自我们对那些成功构建顶尖LLM团队的观察。
2.3 How:迭代速度与数据质量
成功的路径不止一条,但我们发现,顶尖的大模型训练团队之所以能脱颖而出,其关键就在于迭代速度。
训练LLM本质上是一个 “边做边学” 的过程,你训练的次数越多,你的团队就越专业。因此,一个一年只训练一个模型的团队,与一个每季度都能推出新模型的团队相比,后者的学习和进步速度会快得多。你可以看看Qwen和DeepSeek这些如今家喻户晓的团队,他们正是凭借着长期、快速、持续地发布新模型而奠定地位的。
除了迭代速度,数据精选与管理(Data Curation)是迄今为止对LLM训练影响最大的因素。人们很容易沉迷于通过调整模型架构来寻求性能突破,但那些真正卓越的团队,无一不是对高质量数据有着近乎信仰般的追求。
另一个与迭代速度紧密相关的因素是团队规模。对于核心的预训练任务,你其实只需要一个精干的小团队,并为他们配备充足的算力。例如,在今天,要预训练一个Llama 3级别的模型,核心团队可能只需要2到3名工程师。只有当你开始涉足更多元的任务(如多模态、多语言、后训练等)时,才需要逐步扩充团队,以确保在每个领域都有专家。
所以,成功的秘诀可以归结为:从一个小型、装备精良的团队开始,保持每两到三个月构建一个新模型的节奏。这样,你将在短时间内迅速跻身行业前沿。
好了,从现在开始,本文将聚焦于这个团队日常工作中的技术细节!
3. 每个大模型的诞生,都始于一场小小的“消融实验”
在正式开启一个大型语言模型(LLM)的训练之前,我们需要做出无数个将深刻影响模型性能和训练效率的决策。哪种架构最适合我们的应用场景?应该采用什么样的优化器和学习率策略?如何调配不同数据源的比例?
“这些决策是如何做出的?”——这是一个我们经常被问到的问题。人们有时会期望答案是源于某种深刻的理论推演。虽然战略性思考至关重要——正如前一章所讨论的,它能帮你识别出哪些架构调整值得一试——但单纯依靠逻辑推理是远远不够的。在LLM这个领域,直觉往往会失灵,那些“理论上应该可行”的假设,在实践中常常碰壁。
举个例子,使用那些看起来 “质量最高的数据”,并不总能带来更好的模型。以arXiv为例,它无疑是人类科学知识的巨大宝库。直觉上,用如此丰富的STEM(科学、技术、工程和数学)数据进行训练,理应能产出更强大的模型,对吗?但事实并非总是如此,尤其对于小模型而言,这样做甚至可能损害其性能 (Shao et al., 2024)。为什么会这样? 原因是,尽管arXiv的论文知识密度极高,但它们的行文风格高度专业化,语言范式也相对狭隘,这与模型最擅长学习的多样化、通用性的文本大相径庭。
那么,如果苦思冥想不管用,我们该如何判断什么方案才真正有效呢?答案是:像真正的经验主义者那样,用大量的实验来说话! 机器学习并非纯粹的数学,它更像是一门实验科学。这些实验将指导我们做出许多关键决策,因此,如何正确地设计它们便至关重要。我们希望实验具备两个核心特质:
- 速度(Speed): 实验的运行速度要尽可能快,以便我们能频繁迭代。我们能运行的消融实验越多,就能验证越多的假设。
- 可靠性(Reliability): 实验结果应具备强大的区分度(discriminative power)。如果我们关注的指标在实验早期无法清晰地分辨不同方案的优劣,那么这些消融实验就几乎无法提供有价值的洞见(如果结果充满噪声,我们最终可能只是在追逐随机性!)。
但在着手设计消融实验之前,我们需要先对架构类型和模型规模做出一些基础性选择。这些由我们的“训练指南针”所指导的决策,将影响我们使用何种训练框架、如何分配算力预算,以及从哪个基准模型开始。
对于SmolLM3,我们的目标是打造小型的端侧模型,因此我们选择了30亿参数的密集型(dense)Llama风格架构。但正如你将在 “设计模型架构” 一章中看到的,MoE(专家混合)或混合架构可能更适合你的应用场景,而不同的模型规模也伴随着不同的权衡。稍后我们将深入探讨这些选择,并向你展示如何做出决策。现在,让我们从最实际的第一步开始:选择你的基线(Baseline)。
3.1 选择你的基线(Baseline)
每一个成功的模型,都是站在一个经过验证的坚实基础之上,再根据自身需求进行调整和创新的。
- 当Qwen团队训练他们的第一个模型家族时 (Bai et al., 2023),他们以Llama的架构为起点。
- 当Meta训练Llama 3时,他们从Llama 2的经验出发。
- Kimi K2则始于DeepSeek-V3的MoE架构。
这种“传承”不仅体现在架构上,也同样适用于训练超参数和优化器。
为什么呢? 优秀的模型架构和训练方案是多年迭代和众多组织智慧的结晶。我们今天所熟知的标准Transformer结构和Adam等优化器,都是经过成千上万次实验才逐步完善的。前人已经摸清了它们的失效模式,解决了训练中的不稳定性,并优化了其实现方式。
从一个经过验证的基础开始,意味着你直接继承了所有这些宝贵的经验积累。 而从零开始,则意味着你需要独自重新趟过每一个坑。
一个好的架构起点应具备以下条件:
- 符合你的约束: 能够契合你的部署目标和实际应用场景。
- 经过大规模验证: 在相似或更大的规模上,成功运行过数万亿(multi-trillion)Token的训练。
- 文档完善: 有明确的、已被证明在其他开源模型中行之有效的超参数配置。
- 框架支持良好: 理想情况下,它应被你选用的训练框架和计划用于推理的推理框架原生支持。
下表列出了一些在2025年,针对不同架构类型和模型规模的强大基线选项(非详尽清单):
| 架构类型 | 模型家族 | 常见规模 (Sizes) |
|---|---|---|
| 密集型(Dense) | Llama 3.1 | 8B, 70B |
| 密集型(Dense) | Llama 3.2 | 1B, 3B |
| 密集型(Dense) | Qwen3 | 0.6B, 1.7B, 4B, 14B, 32B |
| 密集型(Dense) | Gemma3 | 12B, 27B |
| 密集型(Dense) | SmolLM2, SmolLM3 | 135M, 360M, 1.7B, 3B |
| MoE (专家混合) | Qwen3 MoE | 30B-A3B, 235B-A122B |
| MoE (专家混合) | GPT-OSS | 21B-A3B, 117B-A5B |
| MoE (专家混合) | Kimi Moonlight | 16B-A3B |
| MoE (专家混合) | Kimi-k2 | 1T-A32B |
| MoE (专家混合) | DeepSeek V3 | 671B-A37B |
| 混合型(Hybrid) | Zamba2 | 1.2B, 2.7B, 7B |
| 混合型(Hybrid) | Falcon-H1 | 0.5B, 1.5B, 3B, 7B, 34B |
| MoE + 混合型 | Qwen3-Next | 80B-A3B |
| MoE + 混合型 | MiniMax-01 | 456B-A46B |
因此,请先确定你的架构类型,然后选择一个参数量与你目标模型接近的基线。不必在此处过度纠结,因为你最初选择的架构并非一成不变。在下一节,我们将看到如何从一个基线出发,一步步构建出最适合你的最终架构。
3.1.1 修改你的基线:“去风险化”的原则
现在,你已经有了一个行之有效且符合你应用场景的基线模型。你完全可以止步于此,用你的数据集(假设数据质量不错)进行训练,大概率会得到一个不错的模型。许多成功的项目正是这样做的。然而,基线模型并非为你特定的需求而优化,它们是为其创造者的应用场景和部署目标而设计的。因此,你很可能需要进行一些修改,使其更好地服务于你的目标。但请注意,每一次架构上的改动都伴随着风险:它可能提升性能,也可能彻底搞砸,或者毫无作用,白白浪费了你的实验算力。
要确保项目不偏离正轨,就必须遵守 “去风险化”(Derisking) 的原则:除非你已经通过实验测试并确认其有效性,否则绝不轻易改动任何东西。
真正的挑战在于,你的基线和训练设置中有太多可以调整的组件:注意力机制、位置编码、激活函数、优化器、训练超参数、归一化方案、模型层布局等等。每一个都代表着一个潜在的实验方向,而这些组件之间往往存在着非线性的相互作用。你既没有足够的时间,也没有充足的算力去测试所有可能的组合。
正确的做法是:从那些最有前景的改动开始,逐一与你当前的基线进行比较。 当某个改动被证明有效时,就将其整合,形成一个新的基线,然后再基于这个新基线测试下一个改动。如果你的算力预算允许,也可以同时独立地测试多项改动,并进行 “留一法分析”(leave-one-out analysis)。
务必警惕这个陷阱: 避免对每一个超参数进行详尽的网格搜索(Grid Search),也避免盲目跟风,测试每一个新出现的架构变体。
现在,你已经知道了如何通过战略规划来确定哪些改动值得一试,接下来就该进入经验验证的环节了。在接下来的部分,我们将向你展示如何在实践中真正地测试这些改动。我们将涵盖如何搭建可靠的实验环境、如何解读结果,以及如何避开常见的误区。随后,在后续章节中,我们将通过具体案例,讲解如何测试流行的架构、数据、基础设施和训练策略。
那么,让我们先搭建一个可用于实验的简单消融环境。第一步,我们需要选择一个训练框架。
3.2 挑选训练框架
我们需要做的第一个决策是:选择哪个框架来训练模型,这也将决定我们用什么框架来运行所有的消融实验。这个选择需要平衡以下三个关键因素:
- 架构兼容性: 框架必须支持我们的目标架构,或者至少能让我们轻松地进行扩展。
- 稳定性和生产就绪性: 框架需要足够稳定和成熟,不会在训练中途意外崩溃。
- 高吞吐量: 它应该能提供强大的吞吐量,以便我们快速迭代,最大限度地利用算力。
在实践中,这些要求可能相互制约,需要我们做出权衡。让我们来看看市面上的主流选项。
| 框架 | 特性覆盖 | 实战检验 | 优化程度 | 核心/总代码行数 | 扩展性与调试难度 |
|---|---|---|---|---|---|
| Megatron-LM | ✅ 功能全面 | ✅ Kimi-K2, Nemotron | ✅ 3D并行先驱 | 93k / 269k | ⚠️ 难度大,不适合新手 |
| DeepSpeed | ✅ 功能全面 | ✅ BLOOM, GLM | ✅ ZeRO & 3D并行先驱 | 94k / 194k | ⚠️ 难度大,不适合新手 |
| TorchTitan | ⚡ 功能集不断增长 | ⚠️ 较新,但经PyTorch团队检验 | ⚡ 针对密集模型优化,MoE正在改进中 | 7k / 9k | ⚡ 适中:需了解并行知识 |
| Nanotron | 🎯 极简,为HF预训练定制 | ✅ 是 (StarCoder, SmolLM) | ✅ 高度优化 (UltraScale Playbook) | 15k / 66k | ⚡ 适中:需了解并行知识 |
上表总结了当前流行框架之间的关键权衡。(前三个框架的代码行数数据来自TorchTitan技术报告 (Liang et al., 2025))。让我们逐一分析:
Megatron-LM Nvidia的Megatron-LM久经沙场,是Kimi K2 (Team et al., 2025)等顶尖模型的幕后功臣,提供了可靠的吞吐量和我们需要的大多数生产级功能。但这种成熟也带来了复杂性:其庞大的代码库对新手而言,可能是一道难以逾越的门槛。
DeepSpeed DeepSpeed与Megatron-LM类似。它是ZeRO优化的开创者,为BLOOM和GLM等模型提供了动力。和Megatron-LM一样,它经过了广泛的实战检验和深度优化,但也面临着同样的复杂性挑战。其庞大的代码库(总计19.4万行)在入门时可能会令人望而生畏,尤其是在你需要实现自定义功能或调试未知行为时。
TorchTitan 相比之下,PyTorch最近推出的TorchTitan库则更加轻量且易于上手,这得益于其精简和模块化的代码设计。它具备预训练所需的核心功能,非常适合用于快速实验。然而,由于它相对较新,其实战检验不如前两者充分,并且由于仍在积极开发中,稳定性可能略逊一筹。
Nanotron 在Hugging Face,我们选择了一条不同的道路,从零开始构建了自己的框架Nanotron。这为我们带来了完全的灵活性和对大规模预训练的深刻理解——这些洞见后来演变成了《Ultra Scale Playbook》。虽然我们开源了该库并从社区获得了宝贵的反馈,但在大多数情况下,我们不得不率先对自己开发的功能进行实战检验。该框架目前已支持我们训练所需的所有生产级功能,但在MoE支持等方面仍在不断完善。
对我们而言,从头构建在当时是合理的选择,但这需要团队在专业知识和时间上进行巨大投入,以解决各种问题和补全缺失的功能。一个强有力的替代方案是 复刻(fork) 一个现有框架,并根据你的需求进行定制。例如,Thinking Machines Lab就是将他们的内部预训练库作为TorchTitan的一个复刻版本来构建的(来源)。
最终,你的选择取决于团队的专业水平、目标功能集,以及你愿意投入多少时间进行二次开发,而不是直接使用最成熟的生产方案。
如果多个框架都能满足你的需求,那么请在你的硬件上实际比较它们的吞吐量。对于需要快速实验和迭代的场景,代码库更简洁的框架通常会更胜一筹。
3.3 消融实验设置
既然训练框架已经选定,我们现在需要设计消融实验的具体方案。我们希望实验足够快以便快速迭代,但同时规模又要足够大,以确保其结果能提供有价值的信号,并能够 外推(extrapolate) 到最终的全尺寸模型上。让我们看看如何实现这一平衡。
3.3.1 搭建消融实验框架
消融实验的目标是在小规模上运行实验,并获得可以自信地推广到最终生产级别训练的结论。
主要有两种方法:
- 方法一(缩短训练步数): 保持目标模型的大小不变,但在更少的Token上进行训练。例如,在SmolLM3的消融实验中,我们使用完整的30亿参数模型,但在1000亿(100B)Token上进行训练,而不是最终的11万亿Token。
- 方法二(缩小模型规模): 如果目标模型过大,我们可以训练一个更小的代理模型来进行消融实验。例如,Kimi团队在开发拥有32亿激活参数的1万亿(1T)参数Kimi K2模型时,用完整尺寸模型进行所有消融实验的成本显然过高,因此他们使用了一个30亿参数的MoE模型(5亿激活参数)来运行部分消融实验 (Team et al., 2025)。
一个关键问题是:这些在小规模实验中得出的发现,真的能迁移到大规模训练中吗?根据我们的经验,如果某个改动在小规模上损害了性能,你基本可以放心地将其排除。但如果某个改动在小规模上表现良好,你仍需要确保在足够多的Token上进行了训练,才能更有把握地认为这些发现可以外推。训练的时间越长,消融模型与最终模型越接近,其结果就越可靠。
在本文中,我们将使用一个标准版Transformer来进行所有的消融实验。我们的主要配置为:一个遵循Llama 3.2 1B架构的10亿参数Transformer,在450亿Token上进行训练。这个规模的实验在一个配备8块H100的节点上大约需要1.5天(使用nanotron配置,速度约为每GPU每秒4.2万Token)。在SmolLM3的实际训练中,我们是在一个3B模型上用100B Token来运行这些消融实验的(配置)。我们会在每个章节的末尾分享这些实验的结果(你会发现结论是吻合的)。
我们的基线1B配置以结构化的YAML格式记录了所有重要的训练细节。以下是关键部分:
## 数据集及其混合权重
data_stages:
- data:
dataset:
dataset_folder:
- fineweb-edu # FineWeb教育数据集
- stack-edu-python # Python代码数据集
- finemath-3plus # 3级以上数学数据集
dataset_weights:
- 0.7 # 权重 70%
- 0.2 # 权重 20%
- 0.1 # 权重 10%
## 模型架构,Llama3.2 1B配置
model:
model_config:
hidden_size: 2048 # 隐藏层大小
num_hidden_layers: 16 # 隐藏层数量
num_attention_heads: 32 # 注意力头数量
num_key_value_heads: 8 # K/V头数量 (GQA)
intermediate_size: 8192 # 中间层大小
max_position_embeddings: 4096 # 最大位置嵌入
rope_theta: 50000.0 # RoPE旋转角度
tie_word_embeddings: true # 绑定词嵌入
## 训练超参数,带余弦调度的AdamW优化器
optimizer:
clip_grad: 1.0 # 梯度裁剪
learning_rate_scheduler:
learning_rate: 0.0005 # 最大学习率
lr_decay_starting_step: 2000
lr_decay_steps: 18000
lr_decay_style: cosine
lr_warmup_steps: 2000
lr_warmup_style: linear
min_decay_lr: 5.0e-05 # 最小衰减学习率
optimizer_factory:
adam_beta1: 0.9
adam_beta2: 0.95
adam_eps: 1.0e-08
name: adamW
## 并行化,单节点配置
parallelism:
dp: 8 # 数据并行,跨8块GPU
tp: 1 # 1B规模不需要张量或流水线并行
pp: 1
## 分词器
tokenizer:
tokenizer_max_length: 4096
tokenizer_name_or_path: HuggingFaceTB/SmolLM3-3B
## Batch size, 序列长度和总计30B Token的训练量
tokens:
batch_accumulation_per_replica: 16 # 每个副本的批次累积
micro_batch_size: 3 # GBS (全局批次大小)=dp * batch_acc* MBS * sequence=1.5M tokens
sequence_length: 4096
train_steps: 20000 # GBS * 20000 = 30B
...
在我们的消融实验中,我们会根据测试内容修改配置文件的不同部分,同时保持其他设置不变:测试架构时修改model部分,测试优化器时修改optimizer部分,测试数据策略时修改data_stages部分。
运行消融实验时,某些架构改动会显著改变模型的参数量。例如,将绑定(tied)的词嵌入切换到不绑定(untied)会使嵌入层的参数翻倍,而从MHA切换到GQA或MQA则会大幅减少注意力层的参数。为确保公平比较,我们需要跟踪参数量的变化,并偶尔调整其他超参数(如隐藏层大小或层数)以保持模型总大小基本一致。
3.3.2 理解效果:评估至关重要
一旦我们启动了消融实验,该如何判断哪些改动有效,哪些无效呢?
任何训练过模型的人,第一反应可能都是查看损失(Loss)曲线,这确实很重要。你希望看到它平滑下降,没有剧烈的尖峰或不稳定性。对于许多架构选择而言,损失与下游任务的性能有很好的相关性,可能就足够了 (Y. Chen et al., 2025)。
然而,只看损失曲线并非总是可靠的。以数据消融实验为例,你会发现用维基百科训练比用网页文本训练能得到更低的损失(因为预测下一个词更容易),但这并不意味着你会得到一个能力更强的模型。同样,如果在不同的实验中更换了分词器,损失值就失去了直接可比性,因为文本被切分的方式不同了。某些改动可能还会专门影响推理和数学等特定能力,而这些影响在平均损失中会被稀释。最后同样重要的一点是,模型即使在预训练损失收敛后,其在下游任务上的性能仍可能持续提升 (Liu et al., 2022)。
我们需要更精细的评估方法,才能看清全貌并理解这些细微的影响。一个自然的选择是使用 下游评测(downstream evaluations) 来测试模型的知识、理解、推理以及我们关心的任何其他能力。
对于消融实验,最好关注那些能提供良好早期信号并 避免结果嘈杂的基准测试(noisy benchmarks) 的任务。在FineTasks和FineWeb2的研究中,可靠的评测任务由四个关键原则定义:
- 单调性(Monotonicity): 基准分数应随着模型训练时长的增加而持续改善。
- 低噪声(Low noise): 当我们使用相同设置但不同随机种子训练模型时,基准分数不应出现剧烈波动。
- 超越随机基线(Above-random performance): 许多能力只有在训练后期才会涌现,因此那些在很长时间内性能都停留在随机水平的任务,对消融实验没有太大帮助。例如,我们稍后将解释的多项选择格式的MMLU就是这种情况。
- 排序一致性(Ranking consistency): 如果某种方法在早期阶段优于另一种方法,那么随着训练的继续,这种优劣排序应该保持稳定。
评测任务的质量还取决于任务形式(task formulation)(我们如何向模型提问)和指标选择(metric choice)(我们如何为答案计分)。
三种常见的任务形式是:多项选择(MCF)、完形填空(CF)和自由生成(FG)。
- MCF 要求模型从提示中明确给出的A/B/C/D选项中进行选择(例如MMLU的做法)。
- CF 中,我们比较不同选项的似然度(Likelihood),看哪个选项的可能性更高,而无需在提示中列出它们。
- FG 中,我们评估模型对给定提示进行贪婪生成(greedy generation)的准确性。FG需要模型具备大量的储备知识,对于预训练早期的短时消融实验来说,通常难度太大而缺乏参考价值。
因此,在运行小型消融实验时,我们主要关注MCF或CF。
研究表明,模型在训练早期难以处理MCF,只有在大量训练后才能掌握这项技能,这使得CF更适合用于获取早期信号 (Du et al., 2025; Gu et al., 2025; J. Li et al., 2025)。因此,我们使用CF进行小型消融实验,并在主训练运行中加入MCF,因为当模型能力达到一定阈值后,MCF能提供信噪比更高的中期训练信号。补充说明: 为了在CF这样的序列似然评估中为模型的答案计分,我们将准确率计算为正确答案具有最高对数概率(Log Probability)(并按字符数归一化)的问题所占的百分比。这种归一化可以防止模型偏向于选择较短的答案。
我们的消融评测套件包括了FineWeb消融实验中的基准,但排除了SIQA(我们发现其结果噪声较大)。我们增加了GSM8K和HumanEval等数学和代码基准,以及用于长上下文消融的RULER长上下文基准。如下表所示,这些任务组合在一起,以多种形式测试了模型的世界知识、推理能力和常识。为了加快评测速度,我们牺牲了一些精度,只评估每个基准的1000个问题(GSM8K、HumanEval和RULER除外,我们在3B SmolLM3消融中使用了完整集合,但在下面的1B实验中省略了它们)。我们还对所有多项选择基准使用了完形填空(CF)的评测方式。请注意,对于多语言消融和实际训练,我们增加了更多基准来测试多语言能力,这将在后面详述。下表总结了每个基准的关键特征:
| 基准 | 领域 | 任务类型 | 问题数量 | 测试能力 |
|---|---|---|---|---|
| MMLU | 知识 | 多项选择 | 14k | 跨57个学科的广泛学术知识 |
| ARC | 科学与推理 | 多项选择 | 7k | 小学级别的科学推理 |
| HellaSwag | 常识推理 | 多项选择 | 10k | 关于日常情景的常识推理(叙事补全) |
| WinoGrande | 常识推理 | 二元选择 | 1.7k | 需要世界知识的代词消歧 |
| CommonSenseQA | 常识推理 | 多项选择 | 1.1k | 关于日常概念的常识推理 |
| OpenBookQA | 科学 | 多项选择 | 500 | 包含推理的小学科学知识 |
| PIQA | 物理常识 | 二元选择 | 1.8k | 关于日常物体的物理常识 |
| GSM8K | 数学 | 自由生成 | 1.3k | 小学数学应用题 |
| HumanEval | 代码 | 自由生成 | 164 | 根据文档字符串合成Python函数 |
注意,MMLU和ARC用多项选择题来测试事实知识,GSM8K需要计算数学问题的数值答案,而HumanEval则需要生成完整的Python代码。这种多样性确保了我们在整个消融实验中全面地测试了模型能力的各个方面。
3.3.2.1 消融实验使用什么数据配比?
对于架构消融实验,我们使用固定的高质量数据集组合进行训练,这些数据集能在广泛的任务上提供早期信号。我们使用了英语(FineWeb-Edu)、数学(FineMath)和代码(Stack-Edu-Python)。架构上的发现理应能很好地推广到其他数据集和领域(包括多语言数据),因此我们可以保持数据组合相对简单。
对于数据消融实验,我们则采取相反的策略:固定架构,并系统性地改变数据配比,以了解不同数据源如何影响模型性能。
有时,评测结果的差异可能非常微小。如果你的算力充足,不妨用不同的随机种子(Seeds)重新运行相同的实验,以观察结果的波动范围。
一个可靠的消融实验流程的真正价值,并不仅仅在于帮助你构建一个好模型。当我们在主训练中不可避免地遇到问题时(无论我们准备得多充分,问题总会发生),我们希望对自己做出的每一个决策都充满信心,并能快速定位那些未经充分测试的、可能是问题根源的组件。这种充分的准备工作可以节省大量的调试时间,并 “拯救” 我们未来的心理健康。
3.3.3 估算消融实验成本
消融实验效果显著,但它们需要消耗宝贵的GPU时间,因此理解这些实验的成本至关重要。下表展示了SmolLM3预训练的完整算力分解:主训练运行(包括偶尔的停机时间)、训练前后的消融实验,以及用于解决意外扩展问题(这迫使我们重启了训练)和一些调试所花费的算力(我们将在后面详细介绍)。
| 阶段 | GPU数量 | 天数 | GPU小时数 |
|---|---|---|---|
| 主预训练运行 | 384 | 30 | 276,480 |
| 消融实验(预训练前) | 192 | 15 | 69,120 |
| 消融实验(训练中) | 192 | 10 | 46,080 |
| 训练重启与调试 | 384/192 | 3/4 | 46,080 |
| 总成本 | - | - | 437,760 |
这些数字揭示了一个重要的事实:消融实验和调试的总开销(161,280 GPU小时)超过了我们主训练运行(276,480 GPU小时)本身成本的一半。 在SmolLM3的开发过程中,我们总共进行了超过100次消融实验:我们在预训练前的消融上花费了20天,在训练中的消融上花费了10天,并花费了7天从一次意外的训练故障中恢复,这导致了一次重启和大量的调试工作。
这凸显了为什么消融实验的成本必须计入你的总算力预算:你需要为训练成本 + 消融成本 + 应对意外情况的缓冲进行统一规划。如果你以业界顶尖性能为目标,正在尝试新的架构改动,或者还没有一套经过验证的训练方案,那么消融实验将成为一个主要的成本中心,而不仅仅是小打小闹的实验。
在进入下一节之前,让我们先确立几条每个实验者都应遵守的基本法则。
3.4 实验守则
法则一:验证你的评测套件。 在训练任何模型之前,请确保你的评测代码能够复现你将要对标的模型的公开结果。如果任何基准测试是生成式的(例如GSM8K),请格外警惕,手动检查几个样本,确保提示格式正确,并且任何后处理步骤都能准确提取所需信息。由于评测结果将指导你的每一个决策,因此确保这一步的准确性对项目的成败至关重要!
法则二:测试每一个改动,无论多小。 不要低估那个看似无害的库版本升级,或是那个声称“只修改了两行代码”的提交。这些微小的变化可能会引入难以察觉的Bug或性能偏差,从而污染你的实验结果。你需要使用一个在你关心的场景下有强大测试套件的库,以避免意外的性能回退。
法则三:一次只改动一个变量。 在不同的实验之间,保持所有其他设置完全相同。有些变化可能会以意想不到的方式相互作用,因此我们必须先评估每个变化的独立贡献,然后再尝试将它们组合起来,观察其综合效果。
法则四:训练足够多的Token,并使用充分的评测。 正如我们前面提到的,我们需要确保评测套件有良好的覆盖面,并训练足够长的时间以获得可靠的信号。在这一步“偷工减料”将导致充满噪声的结果和错误的决策。
遵守这些规则可能让你感觉过于谨慎,但另一种选择是花费数天时间去调试神秘的性能下降,结果却发现罪魁祸首只是几天前一个不相关的依赖项更新。一条黄金准则:一旦你有了一套行之有效的基准设置,任何改动都必须经过严格测试!
4. 设计模型架构
至此,我们的实验框架已经就绪,是时候做出那些将定义模型“性格”的重大决策了。从模型规模到注意力机制,再到分词器的选择,我们做出的每一个决定,都会创造出一系列独特的约束和机遇,直接影响模型的训练过程和最终用途。
请时刻牢记我们的“训练指南针”:在做出任何技术选择之前,我们必须对“为什么”(Why)和“做什么”(What)有清晰的认知。我们为什么要训练这个模型?它应该具备哪些核心能力?
这听起来似乎显而易见,但正如我们在“训练指南针”一章中所强调的,保持深思熟虑将塑造我们的决策,并防止我们在无尽的实验空间中迷失方向。我们的目标是构建一个英语性能达到业界顶尖的模型吗?长上下文处理能力是我们的首要任务吗?还是我们试图验证一种全新的架构?虽然在这些不同场景下,训练流程可能看起来相似,但我们所进行的实验以及我们愿意接受的权衡将截然不同。尽早明确目标,有助于我们决定如何在数据和架构工作之间合理分配时间,以及在正式开跑前,需要在每个环节进行多大程度的创新。
因此,让我们以身作则,回顾一下指导SmolLM3设计的目标。我们希望打造一个强大的、适用于端侧设备的模型,同时具备有竞争力的多语言性能、可靠的数学与编码能力,以及强大的长上下文处理能力。正如我们前面提到的,这引导我们选择了一个30亿参数的密集型模型:它足够大,能提供强大的能力;又足够小,可以轻松部署在手机上。考虑到边缘设备的内存限制和我们大约3个月的项目周期,我们最终选择了密集型Transformer,而不是MoE或混合模型。
我们从SmolLM2那里继承了一套小规模(17亿参数)的英语模型训练“配方”,但扩大规模意味着需要重新验证一切,并应对多语言和长上下文等新挑战。这正是明确目标如何塑造我们方法的一个绝佳范例。例如,在SmolLM2中,我们在预训练后期才努力扩展上下文长度,因此对于SmolLM3,我们从一开始就做出了相应的架构选择——比如使用NoPE和文档内掩码(intra-document masking)(稍后会详细介绍)——以最大化成功的概率,而事实证明这种方法是有效的。
一旦目标明确,我们就可以开始将蓝图变为现实。在本章中,我们将系统性地探讨这些核心决策:架构、数据和超参数。请将此视为我们的战略规划阶段,打好这些基础,将使我们在漫长的训练马拉松中避免那些代价高昂的错误。
4.1 架构选择
如果你观察近期的模型,如Qwen3、Gemma3或DeepSeek v3,你会发现尽管它们各有千秋,但都共享同一个基础——2017年问世的Transformer架构。多年来,改变的并非其基本结构,而是对其核心组件的精炼与改进。无论你构建的是密集型模型(Dense Model)、专家混合模型(Mixture of Experts, MoE)还是混合架构(Hybrid Architecture),你都在使用这些相同的构件。
这些改进源于各大团队对更优性能的追求,以及对特定挑战的攻克:推理时的内存限制、大规模训练时的不稳定性,或是处理更长上下文的需求。一些修改,比如从多头注意力(MHA)转向计算效率更高的分组查询注意力(GQA),现已被广泛采纳。而其他修改,比如不同的位置编码方案,仍在持续的讨论和演进中。今天的实验,将凝结成明天的基线架构。
那么,当今的LLM实际上在用什么呢?让我们来看看领先模型所趋同的关键技术点。不幸的是,并非所有模型都公开了其训练细节,但从DeepSeek、OLMo、Kimi和SmolLM等模型家族中获得的透明度,足以让我们一窥当前的技术格局:
| 模型 | 架构 | 参数量 | 训练Token | 注意力机制 | 上下文长度(最终) | 位置编码 | 精度 | 初始化 (Std) | 优化器 | 最大学习率 | 学习率调度 | Warmup步数 | Batch Size |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| DeepSeek LLM 7B | Dense | 7B | 2T | GQA | 4K | RoPE | BF16 | 0.006 | AdamW | $4.2 \times 10^{-4}$ | Multi-Step | 2K | 9.4M |
| DeepSeek LLM 67B | Dense | 67B | 2T | GQA | 4K | RoPE | BF16 | 0.006 | AdamW | $3.2 \times 10^{-4}$ | Multi-Step | 2K | 18.9M |
| DeepSeek V2 | MoE | 236B (21B 激活) | 8.1T | MLA | 128K | Partial RoPE | - | 0.006 | AdamW | $2.4 \times 10^{-4}$ | Multi-Step | 2K | 9.4M -> 37.7M (Warmup 225B) |
| DeepSeek V3 | MoE | 671B (37B 激活) | 14.8T | MLA | 129K | Partial RoPE | FP8 | 0.006 | AdamW | $2.2 \times 10^{-4}$ | Multi-Step + Cosine | 2K | 12.6M -> 62.9M (Warmup 469B) |
| MiniMax-01 | MoE + Hybrid | 456B (45.9B 激活) | 11.4T | Linear + GQA | 4M | Partial RoPE | - | Xavier Init + Deepnorm | AdamW | $2 \times 10^{-4}$ | Multi-Step | 500 | 16M -> 32M -> 64M -> 128M |
| Kimi K2 | MoE | 1T (32B 激活) | 15.5T | MLA | 128K | Partial RoPE | BF16 | 可能是 0.006 | MuonClip | $2 \times 10^{-4}$ | WSD | 500 | 67M |
| OLMo 2 7B | Dense | 7B | 5T | MHA | 4K | RoPE | BF16 | 0.02 | AdamW | $3 \times 10^{-4}$ | Cosine | 2K | 4.2M |
| SmolLM3 | Dense | 3B | 11T | GQA | 128K | NoPE | BF16 | 0.02 | AdamW | $2 \times 10^{-4}$ | WSD | 2K | 2.3M |
如果你还不理解其中的一些术语,比如MLA、NoPE或WSD,请不必担心。我们将在本节中逐一解释。现在,你只需要注意其中的多样性:不同的注意力机制(MHA, GQA, MLA)、位置编码(RoPE, NoPE, partial RoPE)以及学习率调度(Cosine, Multi-Step, WSD)。
看到如此长的架构选择清单,你可能会感到不知所措。与大多数类似情况一样,我们将循序渐进,逐步建立必要的专业知识。我们将首先关注最简单的基础架构——密集型模型,并详细研究每个架构方面。稍后,我们将深入探讨MoE和混合模型,并讨论何时应选择它们。最后,我们将探索分词器(Tokenizer)——一个经常被忽视但至关重要的组件。我们应该使用现有的分词器还是自己训练?我们又如何评估一个分词器的好坏?
但现在,让我们从每个LLM的核心开始:注意力机制(Attention Mechanism)。
4.1.1 注意力机制(Attention)
在Transformer架构中,最活跃的研究领域之一就是注意力机制。虽然在预训练期间,前馈网络(feedforward layers)占据了大部分计算量,但在推理时,注意力机制却成为了主要的性能瓶颈(尤其是在长上下文场景中)。在这种情况下,它会推高计算成本,并且其KV缓存(KV cache)会迅速耗尽GPU内存,从而降低吞吐量。让我们快速回顾一下主要的注意力机制,以及它们如何在模型容量和推理速度之间进行权衡。
4.1.1.1 我的注意力机制需要多少个头?
多头注意力(Multi-Head Attention, MHA)是最初的Transformer引入的标准注意力机制。其核心思想是,你有N个注意力头,每个头独立地执行相同的检索任务:将隐藏状态转换为查询(Queries)、键(Keys)和值(Values),然后用当前的查询去匹配历史的键,检索出最相关的Token,最后将与这些Token关联的值转发出去。在推理时,我们不必重新计算过去Token的KV值,可以直接复用它们。存储这些历史KV值的内存被称为KV缓存。随着上下文窗口的增长,这个缓存会迅速成为推理的瓶颈,并消耗GPU内存的很大一部分。以下是一个简单的计算,用于估算Llama 3架构在MHA和8192序列长度下的KV缓存内存占用:
\(s_{KV} = 2 \times n_{bytes} \times seq \times n_{layers} \times n_{heads} \times dim_{heads}\) \(= 2 \times 2 \times 8192 \times 32 \times 32 \times 128 \approx 4 \text{ GB (Llama3 8B)}\) \(= 2 \times 2 \times 8192 \times 80 \times 64 \times 128 \approx 20 \text{ GB (Llama3 70B)}\)
(注:最前面的因子2代表我们同时存储了键和值两个缓存。)
正如你所见,缓存大小与序列长度呈线性增长,但上下文窗口却在以指数级的速度增长,如今已达到数百万Token。因此,提高缓存的效率将极大地便利推理时上下文长度的扩展。
一个自然而然的问题是:我们真的需要为每个注意力头都计算新的KV值吗?也许并不需要。多查询注意力(Multi-Query Attention, MQA) (Shazeer, 2019)和分组查询注意力(Grouped Query Attention, GQA) (Ainslie et al., 2023)都致力于解决这个问题。
最简单的方案是在所有头之间共享同一份KV值,这将使KV缓存的大小减少$\text{n}_{heads}$倍。例如,对于Llama 3 70B,这可以带来64倍的内存节省!这便是MQA的核心思想,并被StarCoder等模型用作MHA的替代方案。
然而,这样做可能会牺牲掉我们不愿放弃的注意力容量。因此,我们可以考虑一个折中方案:在分组的头之间共享KV值,例如每4个头共享同一份KV值。这便是GQA的方法,它在MQA和MHA之间取得了巧妙的平衡。
最近,DeepSeek-v2(并在v3中沿用)引入了多潜变量注意力(Multi-Latent Attention, MLA) (DeepSeek-AI et al., 2024),它采用了一种不同的策略来压缩缓存:它不减少KV值的数量,而是缩小它们的尺寸,只存储一个潜变量(latent variable),该变量可以在运行时被解压缩为完整的KV值。通过这种方法,他们成功地将缓存压缩到相当于GQA分组数为2.25的水平,同时提供了比MHA更强的性能!为了让它与RoPE协同工作,需要一个小小的调整,即增加一个额外的微小潜向量。在DeepSeek-v2中,他们选择了$4 \times \text{dim}{head}$作为主潜变量,和$1/2 \times \text{dim}{head}$用于RoPE部分,总共是$4.5 \times \text{dim}_{head}$,它同时用于K和V,从而消除了公式最前面的乘数2。
下表比较了我们刚才讨论的几种注意力机制。为简化起见,我们比较了每个Token所需的参数量。如果你想计算总内存,只需将其乘以每个参数的字节数(通常为2)和序列长度即可:
| 注意力机制 (Attention Mechanism) | 每个Token的KV缓存参数量 |
|---|---|
| MHA (多头注意力) | $= 2 \times n_{heads} \times n_{layers} \times dim_{head}$ |
| MQA (多查询注意力) | $= 2 \times 1 \times n_{layers} \times dim_{head}$ |
| GQA (分组查询注意力) | $= 2 \times g \times n_{layers} \times dim_{head}$ (通常 $g=2, 4, 8$) |
| MLA (多潜变量注意力) | $= 4.5 \times n_{layers} \times dim_{head}$ |
现在,让我们看看这些注意力机制在实际实验中的表现如何!
4.1.1.2 消融实验 - GQA胜过MHA
在这里,我们比较了不同的注意力机制。我们的基线模型使用了32个查询头和8个KV头,这对应于GQA(分组查询注意力),其分组比率为32/8=4。如果我们使用MHA,或者如果我们使用更少的KV头和更高的GQA比率,性能会如何变化?
请注意,更改KV头的数量会影响参数量,特别是对于MHA。为了保持一致性,我们调整了MHA运行的层数,因为它否则会多出超过1亿个参数;对于其他设置,我们保持了默认的16层。
| 注意力类型 | 查询头 | KV头 | 层数 | 参数量 | 备注 |
|---|---|---|---|---|---|
| MQA | 32 | 1 | 16 | 1.21B | |
| GQA (比率 16) | 32 | 2 | 16 | 1.21B | |
| GQA (比率 8) | 32 | 4 | 16 | 1.22B | 我们的基线 |
| GQA (比率 4) | 32 | 8 | 16 | 1.24B | |
| GQA (比率 2) | 32 | 16 | 15 | 1.22B | 减少了层数 |
| MHA | 32 | 32 | 14 | 1.20B | 减少了层数 |
| GQA (比率 2) | 32 | 16 | 16 | 1.27B | 参数量过大 - 未进行消融 |
| MHA | 32 | 32 | 16 | 1.34B | 参数量过大 - 未进行消融 |
因此,我们比较了MHA、MQA和4种GQA设置(比率分别为2、4、8、16)。
观察消融结果,我们发现MQA和GQA(分组比率16)(分别只留下1个和2个KV头)的性能显著低于MHA。另一方面,GQA配置(分组比率2、4、8)的性能与MHA大致相当。
这个结果在损失曲线和下游评测中都保持一致。我们在HellaSwag、MMLU和ARC等基准测试中清楚地观察到了这一点,而OpenBookQA和WinoGrande等基准则显示出少许噪声。
基于这些消融实验,GQA是MHA的一个可靠替代品。它在保持性能的同时,在推理时更加高效。一些最新的模型采用了MLA来实现更进一步的KV缓存压缩,尽管它尚未被广泛采用。由于在进行我们的消融实验时,MLA尚未在nanotron中实现,我们没有对其进行测试。
对于SmolLM3,我们最终使用了GQA,分组数量为4。
除了注意力架构本身,我们在训练中使用的注意力模式(Attention Pattern)也很重要。接下来,让我们看看注意力掩码(Attention Masking)。
4.1.1.3 文档掩码(Document Masking)
我们如何在训练序列中应用注意力,直接影响着计算效率和模型性能。这就引出了文档掩码(Document Masking)以及一个更广泛的问题:我们应该如何在数据加载器(dataloader)中构建训练样本?
在预训练期间,我们使用固定长度的序列进行训练,但我们的文档长度却是可变的。一篇研究论文可能有1万个Token,而一个简短的代码片段可能只有几百个Token。我们如何将这些可变长度的文档装入固定长度的训练序列中呢?
将较短的文档用填充符(Padding)补齐到目标长度,会浪费大量算力在无意义的填充Token上。因此,我们通常使用打包(Packing):将文档与序列结束(End-of-Sequence, EOS)Token一起打乱并拼接起来,然后将结果分割成与序列长度相匹配的固定长度块。
实际操作看起来是这样的:
File 1: "Recipe for granola bars..." (400 tokens) <EOS>
File 2: "def hello_world()..." (300 tokens) <EOS>
File 3: "Climate change impacts..." (1000 tokens) <EOS>
File 4: "import numpy as np..." (3000 tokens) <EOS>
...
拼接并切分为4k长度的序列后:
Sequence 1: [File 1] + [File 2] + [File 3] + [partial File 4]
Sequence 2: [rest of File 4] + [File 5] + [File 6] + ...
如果一个文件足够长,能填满我们的4k上下文,那么一个训练序列可能只包含一个完整的文件。但在大多数情况下,文件都很短,所以一个序列会包含多个被随机拼接在一起的文件。
在标准的因果掩码(Causal Masking)下,一个Token可以关注打包序列中的所有先前Token。在上面的例子中,文件4中那个Python函数里的Token,可以同时关注到燕麦棒食谱、气候变化文章以及其他碰巧被打包在一起的内容。
让我们快速看看一个典型的4k预训练上下文中会包含什么。一项快速分析显示,在CommonCrawl和GitHub数据集中,绝大多数(约80-90%)的文件都短于2k Token。
这意味着,在一个2k或4k的训练序列和标准因果掩码下,绝大多数Token将浪费算力去关注那些被打包在一起的、毫不相关的文档内容。
除了计算效率低下之外,Zhao et al. (2024)发现这种方法会引入来自不相关内容的噪声,可能降低性能。他们建议使用文档内掩码(intra-document masking):我们修改注意力掩码,使得Token只能关注同一文档内的先前Token。
Zhu et al. (2025)在SkyLadder中也发现了文档内掩码的类似益处,但提供了另一种解释。他们发现较短的上下文长度对训练更有利,而文档内掩码有效地降低了平均上下文长度。
Llama 3 (Grattafiori et al., 2024)也使用文档内掩码进行训练,他们发现在短上下文预训练期间,其影响有限,但对于长上下文扩展来说益处显著,因为在这种情况下,注意力的开销变得更加重要。此外,ProLong论文 (Gao et al., 2025)表明,在持续预训练中利用文档掩码来扩展Llama 3 8B的上下文,对长上下文和短上下文的基准都有益处。
我们决定在我们的1B基线模型上进行一项消融实验,测试文档掩码是否会影响短上下文性能。
结果显示,与标准因果掩码相比,损失曲线和下游评测得分完全相同。我们唯一观察到的一个微小改进是在PIQA上。
要在nanotron中启用文档掩码,只需在模型配置中将以下标志设置为true:
model_config:
_attn_implementation: flash_attention_2
_fused_rms_norm: true
_fused_rotary_emb: true
- _use_doc_masking: false
+ _use_doc_masking: true # 启用文档内掩码
与Llama 3类似,我们没有在短上下文任务上观察到明显的性能影响,除了PIQA的微小改进。然而,文档掩码在扩展到长序列时变得至关重要,因为它可以加快训练速度。这对于我们的长上下文扩展尤为重要,我们将序列从4k扩展到64k Token(详情请见“训练马拉松”章节)。因此,我们在SmolLM3的整个训练过程中都采用了它。
在本节中,我们讨论了注意力如何处理序列。现在,让我们看看Transformer中的另一个主要参数块:嵌入层(Embeddings)。
4.1.2 嵌入层共享(Embedding Sharing)
如果你观察我们基线消融模型的配置,会发现与标准Transformer不同的地方之一是,它通过tie_word_embeddings标志启用了嵌入层共享。
LLM有两个嵌入组件:
- 输入嵌入(Input Embeddings): 作为Token到向量的查找表(大小为$\text{vocab_size} \times \text{hidden_dim}$)。
- 输出嵌入(Output Embeddings): 最后一个线性层,将隐藏状态映射到词汇表Logits(大小为$\text{hidden_dim} \times \text{vocab_size}$)。
在经典情况下,如果这两个矩阵是分开的,总嵌入参数量为$2 \times \text{vocab_size} \times \text{hidden_dim}$。因此,在小型语言模型中,嵌入层可以占据总参数量的很大一部分,尤其是在词汇表大小很大的情况下。这使得嵌入层共享(在输出层重用输入嵌入)成为小型模型的一种自然优化。
大型模型通常不使用这种技术,因为嵌入层只占其参数预算的一小部分。例如,在Llama 3.2 8B中,不共享的总嵌入参数仅占13%,而在Llama 3.1 70B中仅占3%。
4.1.2.1 消融实验 - 绑定嵌入的模型可媲美参数量更大的非绑定变体
现在我们将评估嵌入层共享对我们消融模型的影响。我们借鉴了MobileLLM在125M规模上对该技术的全面消融实验所获得的洞察,该实验表明:共享嵌入在 参数量减少11.8% 的同时,准确率退化极小。
由于非绑定嵌入会将我们的参数量从1.2B增加到1.46B,我们将训练另一个具有非绑定参数但层数更少的模型,使其参数量与基线的1.2B相匹配。我们将比较三个模型:
- 基线模型 (1.2B): 绑定嵌入(16层)。
- 非绑定-减少层数模型 (1.2B): 非绑定嵌入,但层数更少(12层)以保持相同的参数预算。
- 非绑定-相同层数模型 (1.46B): 非绑定嵌入,层数与基线相同(16层),作为额外的参考点。
损失和评测结果表明,我们的1.2B绑定嵌入基线模型,在所有基准测试(WinoGrande除外)上,都实现了与1.46B非绑定等效模型相当的性能,尽管其参数少了18%。
而非绑定嵌入且层数减少的1.2B模型(12层对比16层)性能不如前两者,表现出更高的损失和更低的下游评测分数。这表明,在参数预算相等的情况下,增加模型深度比解绑嵌入层带来了更大的益处。
基于这些结果,我们为SmolLM3 3B模型保留了绑定嵌入(Tied Embeddings)。
至此,我们探索了嵌入层共享策略及其权衡。但仅靠嵌入层本身并不能捕获序列中Token的顺序;提供这些信息是位置编码(Positional Encodings)的作用。在下一节中,我们将探讨位置编码策略是如何演变的,从标准的RoPE到像NoPE (No Position Embedding)这样更新颖的方法,后者能更有效地进行长上下文建模。
4.1.3 位置编码与长上下文(Positional Encodings & Long Context)
当Transformer处理文本时,它们面临一个根本性的挑战:它们天生对词序没有感知,因为它们通过并行的注意力操作同时处理整个序列。这使得训练高效,但也产生了一个问题。在没有明确的位置信息的情况下,从模型的角度来看,“亚当击败了穆恩”和“穆恩击败了亚当”看起来是相似的。
解决方案是位置嵌入(Positional Embeddings):一种数学编码,赋予序列中的每个Token一个独特的“地址”。但是,随着我们不断将上下文推向更长——从早期BERT的512个Token到今天的百万级Token模型——位置编码的选择对于性能和计算效率变得越来越关键。
4.1.3.1 位置编码的演变
早期的Transformer使用简单的绝对位置嵌入(Absolute Position Embeddings, APE) (Vaswani et al., 2023),它本质上是一个学习到的查找表,将每个位置$(1, 2, 3…)$映射到一个向量,然后将其添加到Token嵌入中。这对于短序列运行良好,但有一个主要限制:模型的最大输入序列长度被限制在它所训练的最大长度内。它们不具备开箱即用的泛化到更长序列的能力。
该领域转向了相对位置编码(Relative Position Encodings),它捕获的是Token之间的距离,而不是它们的绝对位置。这在直觉上是合理的:两个词相隔3个位置,比它们是处于位置(5, 8)还是(105, 108)更重要。
ALiBi (Attention with Linear Biases) (Press et al., 2022)通过Token距离来修改注意力得分。两个Token距离越远,它们的注意力得分就会通过一个施加在线性偏差上的简单惩罚而变得越小。
但主导近期大型语言模型的技术是旋转位置嵌入(Rotary Position Embedding, RoPE) (Su et al., 2023)。
4.1.3.2 RoPE:将位置编码为旋转
RoPE的核心洞察是:将位置信息编码为高维空间中的旋转角度。RoPE不是将位置向量添加到Token嵌入中,而是通过依赖于其绝对位置的角度来旋转查询(Query)和键(Key)向量。
其直觉是,我们将嵌入向量中的每对维度视为圆上的坐标,并根据以下因素确定的角度来旋转它们:
- Token在序列中的位置$p$。
- 我们正在处理的维度对$k$(不同的维度对以不同的频率旋转,这些频率是基础/参考频率的指数)。
这个过程可能听起来复杂,所以让我们用一个具体的例子来分解它。考虑句子“The quick brown fox”中的“fox”这个词。在我们的1B基线模型中,每个注意力头都使用一个64维的查询/键向量。RoPE将这个向量分成32对:$(x_1, x_2), (x_3, x_4), (x_5, x_6)$,依此类推。我们对这些对进行操作,因为我们在二维空间中围绕圆进行旋转。为简单起见,我们关注第一对$(x_1, x_2)$。词“fox”出现在句子中的位置3,因此RoPE将旋转第一对维度:
\[\text{rotation\_angle} = \text{position} \times \theta_0 = 3 \times \left(1 / 10000^{(0/32)}\right) = 3 \times 1.0 = 3.0 \text{ 弧度} = 172^{\circ}\]我们的基础频率是10000,但对于第一对维度($k=0$),指数为零,因此基础频率不影响计算(我们将其提高到0次方)。
现在,当两个Token通过注意力相互作用时,奇迹发生了。它们的旋转表示之间的点积(Dot Product)直接通过它们旋转角度之间的相位差(Phase Difference)来编码它们的相对距离(其中$m$和$n$是Token的位置):
\[\text{dot\_product}(\text{RoPE}(x, m), \text{RoPE}(y, n)) = \sum_{k} \left[x_k \times y_k \times \cos((m-n) \times \theta_k)\right]\]注意力模式仅取决于$(m-n)$,因此相隔5个位置的Token将始终具有相同的角度关系,无论它们在序列中的绝对位置如何。因此,模型学习了基于距离的模式,这些模式适用于序列中的任何绝对位置,并可以外推到更长的序列。
4.1.3.3 如何设置RoPE频率?
在实践中,大多数LLM预训练都是从相对较短的上下文长度(2K-4K Token)开始的,使用的RoPE基础频率是几万,例如$10\text{K}$或$50\text{K}$。从一开始就用非常长的序列进行训练,由于注意力机制的二次方缩放以及长上下文数据(上下文长度超过4K的样本)的有限可用性,计算成本会非常高昂。研究还表明,这可能会损害短上下文性能 (Zhu et al., 2025)。模型通常从学习词语之间的短程相关性开始,因此长序列帮助不大。
典型的方法是用较短的序列完成大部分预训练,然后进行持续预训练(Continual Pretraining),或在最后几千亿Token上使用更长的序列。然而,随着序列长度的增长,与Token位置成正比的旋转角度也会增长,这可能导致远处Token的注意力得分衰减过快 (Rozière et al., 2024; Xiong et al., 2023):
\[\theta = \text{position} \times 1 / (\text{base}^{(k/(\text{dim}/2))})\]解决方案是,随着序列长度的增加而增加基础频率,以防止这种衰减,使用诸如ABF和YaRN之类的方法。
-
RoPE ABF (RoPE with Adjusted Base Frequency) (Xiong et al., 2023b):通过增加RoPE公式中的基础频率来解决长上下文中的注意力衰减问题。这种调整减慢了Token位置之间的旋转角度,防止了远处Token的注意力得分衰减过快。ABF可以单阶段应用(直接提升频率)或多阶段应用(随着上下文增长而逐渐增加)。该方法易于实现,并以增加的粒度分布嵌入向量,使模型更容易区分远距离位置。虽然简单有效,但ABF对所有维度的统一缩放可能不适用于极长上下文。
-
YaRN (Yet another RoPE extensioN) (Peng et al., 2023):采取了一种更复杂的方法,通过使用斜坡或缩放函数在RoPE维度上不均匀地插值频率。与ABF的统一调整不同,YaRN对不同的频率分量应用不同的缩放因子,从而优化了扩展的上下文窗口。它包括动态注意力缩放和注意力Logits中的温度调整等额外技术,有助于在极大的上下文尺寸下保持性能。YaRN支持高效的“短训练,长测试”策略,只需要更少的Token和更少的微调即可实现稳健的外推。尽管比ABF更复杂,但YaRN通过提供更平滑的缩放和减轻灾难性的注意力损失,通常为极长上下文带来更好的经验性能。它也可以单独在推理中使用,无需任何微调。
这些频率调整方法减缓了注意力得分衰减效应,并保持了远程Token的贡献。例如,Qwen3的训练就涉及在将序列长度从4k扩展到32k上下文时,使用ABF将频率从10k增加到1M(该团队随后应用YaRN来达到131k,即4倍外推)。
请注意,对于最佳值目前没有强烈的共识,通常最好在上下文扩展阶段尝试不同的RoPE值,以找到最适合你特定设置和评估基准的值。
今天大多数主要的模型都使用RoPE:Llama、Qwen、Gemma等等。该技术已被证明在不同模型大小和架构(密集型、MoE、混合型)中都稳健可靠。
4.1.3.4 混合位置编码方法(Hybrid Positional Encoding Approaches)
然而,随着模型推向越来越大的上下文 (Meta AI, 2025; Yang et al., 2025),即使是RoPE也开始遇到性能挑战。当在比“大海捞针”(Needle in the Haystack, NIAH) (Kamradt, 2023)更具挑战性的长上下文基准(如Ruler和HELMET (Hsieh et al., 2024; Yen et al., 2025))上进行评估时,在长上下文扩展期间增加RoPE频率的标准方法存在局限性。一些更新的技术被引入来提供帮助。
我们以Transformer需要位置信息来理解Token顺序开始本节,但最近的研究挑战了这一假设。如果明确的位置编码根本不是必需的呢?
NoPE (No Position Embedding) (Kazemnejad et al., 2023)在没有任何明确位置编码的情况下训练Transformer,允许模型通过因果掩码和注意力模式隐式学习位置信息。作者表明,与ALiBi和RoPE相比,这种方法表现出更好的长度泛化能力。由于没有明确的位置编码来外推训练长度之外,NoPE自然可以处理更长的上下文。然而在实践中,与RoPE相比,NoPE模型在短上下文推理和知识任务上表现较弱 (Yang et al.)。这表明,虽然明确的位置编码可能会限制外推,但它们为训练上下文长度内的任务提供了有用的归纳偏置(inductive biases)。
RNoPE混合方法: 考虑到这些权衡,B. Yang et al. (2025)提出结合不同的位置编码策略可能很有趣。他们引入了RNoPE,它在整个模型中交替使用RoPE和NoPE层。RoPE层提供明确的位置信息,并以近因偏置(recency bias)处理局部上下文,而NoPE层则改善了跨长距离的信息检索。这项技术最近被用于Llama 4、Command A和SmolLM3。
4.1.3.5 消融实验 - NoPE在短上下文上与RoPE匹配
让我们测试一下混合的NoPE方法。我们将比较一个纯RoPE 1B消融基线、一个每隔4层移除位置编码的NoPE变体,以及结合NoPE和文档掩码的第三种配置来测试这些技术之间的相互作用。我们的基本问题是:我们能否在保持强大的短上下文性能的同时,获得长上下文能力?
损失和评测结果显示,所有三种配置的性能相似,这表明NoPE保持了强大的短上下文能力,同时为更好的长上下文处理提供了基础。基于这些结果,我们为SmolLM3采用了NoPE + 文档掩码的组合。
部分/分数RoPE(Partial/Fractional RoPE): 另一个互补的想法是只在模型维度的一个子集上应用RoPE。与RNoPE在整个层面上交替使用RoPE和NoPE不同,Partial RoPE在同一层内混合它们。最近的模型,如GLM-4.5 (5 Team et al., 2025)或Minimax-01 (MiniMax et al., 2025),采用了这种策略,但这在gpt-j (Wang & Komatsuzaki, 2021)等较旧的模型中也存在。你也会在每个使用MLA的模型中看到这一点,因为它是拥有合理推理成本的必备条件。
4.1.3.6 限制注意力范围以实现长上下文
到目前为止,我们探索了如何处理长上下文的位置信息:启用RoPE、禁用它(NoPE)、在某些层上部分应用(RNoPE)或在某些隐藏维度上应用(Partial RoPE),或调整其频率(ABF, YaRN)。这些方法修改了模型编码位置的方式,以处理比训练期间看到的序列更长的序列。
但还有一种互补的策略:我们可以限制哪些Token相互关注,而不是调整位置编码。
为了理解为什么这很重要,考虑一个用8个Token序列预训练的模型。在推理时,我们想处理16个Token(超过训练长度)。位置8-15超出了模型位置编码的分布范围。虽然像RoPE ABF这样的技术通过调整位置频率来解决这个问题,但注意力范围方法采取了不同的方法:它们策略性地限制哪些Token可以相互关注,将注意力模式保持在熟悉的范围内,同时仍然处理整个序列。这降低了计算成本和内存需求。
以下是处理我们的16个Token序列(预训练窗口为8)的五种策略:
-
分块注意力(Chunked Attention)将序列分成固定大小的块,其中Token只能在自己的块内关注。在我们的示例中,16个Token被分成两个8个Token的块(0到7和8到15),每个Token只能看到其块内的其他Token。注意Token 8到15根本不能关注到前面的块。这创建了在块边界重置的隔离注意力窗口。Llama 4 (Meta AI, 2025)在RoPE层(四分之三的解码器层)中使用了8192个Token块的分块注意力,而NoPE层则保持对完整上下文的访问。这通过限制每层的KV缓存大小来减少内存需求,尽管它意味着Token不能关注到前面的块,这可能会影响某些长上下文任务。
-
滑动窗口注意力(Sliding Window Attention, SWA),由Mistral 7B (Child et al., 2019; Jiang et al., 2023)推广,基于最近的Token最相关的直觉,采用了不同的方法。每个Token只关注最近的N个Token,而不是硬性的块边界。在我们的示例中,每个Token最多可以看到前面8个位置,创建了一个在序列中连续移动的滑动窗口。注意Token 15可以关注位置8到15,而Token 10关注位置3到10。窗口向前滑动,在整个序列中保持局部上下文,没有分块的人为障碍。Gemma 3将SWA与完整注意力在交替层中结合使用,类似于混合位置编码方法混合不同策略的方式。
-
双块注意力(Dual Chunk Attention, DCA) (An et al., 2024)是一种免训练的方法,它扩展了分块注意力,同时保持了跨块的信息流。在我们的示例中,我们使用块大小$s=4$,将16个Token分为4个块。DCA结合了三种机制:(1)块内注意力,Token在其块内正常关注。(2)块间注意力,查询使用位置索引$c-1=7$来关注前面的块,创建相对位置上限为7。(3)连续块注意力,具有局部窗口$w=3$,保留相邻块之间的局部性。这使得所有相对位置都保持在训练分布内(0到7),同时保持跨块边界的平滑过渡。DCA使Qwen 2.5等模型能够在推理时支持高达100万Token的超长上下文窗口,而无需在百万Token序列上进行持续训练。
4.1.3.7 注意力汇聚(Attention Sinks)
在具有长上下文的Transformer模型中,出现了一种有趣的现象:模型会为序列中的起始Token分配异常高的注意力分数,即使这些Token在语义上并不重要。这种行为被称为注意力汇聚(Attention Sinks) (Xiao et al.)。这些起始Token充当了注意力分布的稳定机制,起到了注意力可以积累的“汇聚点”作用。
实际的洞察是:当上下文长度超过缓存大小时,仅保留起始几个Token的KV缓存以及最近Token的滑动窗口,可以在很大程度上恢复性能。这种简单的修改使模型能够处理更长的序列,而无需微调或性能下降。
现代的实现以不同的方式利用注意力汇聚。最初的研究建议在预训练期间添加一个专用的占位符Token作为明确的注意力汇聚点。最近,像gpt-oss这样的模型将注意力汇聚实现为学习到的“每头偏差Logits”(learned per-head bias logits),将其附加到注意力分数上,而不是作为输入序列中的实际Token。这种方法在不修改分词输入的情况下达到了相同的稳定效果。
有趣的是,gpt-oss还在注意力层本身使用了偏差单元(bias units),这是自GPT-2以来就很少见的设计选择。虽然这些偏差单元通常被认为对于标准注意力操作是多余的(Dehghani et al.的实证结果显示对测试损失的影响很小),但它们可以服务于实现注意力汇聚的专门功能。
核心洞察是:无论是作为特殊Token、学习到的偏差还是每头Logits实现,注意力汇聚都为长上下文场景中的注意力分布提供了一个稳定的“锚点”,允许模型存储关于整个序列的通用有用信息,即使上下文任意增长。
至此,我们已经涵盖了注意力的核心组件:平衡内存和计算的不同头部配置(MHA、GQA、MLA),帮助模型理解Token顺序的位置编码策略(RoPE、NoPE及其变体),以及使长上下文变得可处理的注意力范围技术(滑动窗口、分块和注意力汇聚)。我们还研究了嵌入层应如何配置和初始化。这些架构选择定义了你的模型如何处理和表示序列。
但拥有正确的架构只是成功的一半。即使是精心设计的模型,也可能遭受训练不稳定的困扰,尤其是在大规模训练时。让我们来看看有助于保持训练稳定的技术。
4.1.4 提高稳定性(Improving Stability)
现在,让我们转向LLM预训练中最大的挑战之一:不稳定性(Instabilities)。这些问题通常表现为损失尖峰或训练损失的突然跳跃,在大规模训练时尤为常见。
虽然我们将在“训练马拉松”部分深入探讨不同类型的尖峰以及如何处理它们(深入研究浮点精度、优化器和学习率),但某些架构和训练技术也可以帮助我们减少不稳定性。因此,让我们花点时间在这里研究它们。
我们将介绍最近大规模训练运行中(例如,Olmo2 (OLMo et al., 2025)和Qwen3 (A. Yang, Li, et al., 2025))用于提高稳定性的几种简单技术:Z-Loss、从嵌入层中移除权重衰减和QK-norm。
4.1.4.1 Z-loss
Z-loss (Chowdhery et al., 2022)是一种正则化技术,它通过在损失函数中添加一个惩罚项来防止最终输出的Logits变得过大。这种正则化促使Softmax的分母(即$Z$项)保持在一个合理的范围内,这有助于在训练过程中保持数值稳定性。
我们的1B模型上的消融结果显示,添加Z-loss不会影响训练损失或下游性能。对于SmolLM3,我们最终没有使用它,因为在开始训练时,我们的Z-loss实现引入了一些我们没有优化的训练开销。
4.1.4.2 从嵌入层中移除权重衰减
权重衰减(Weight Decay)通常作为一种正则化技术应用于所有模型参数,但OLMo et al. (2025)发现,将嵌入层排除在权重衰减之外可以提高训练稳定性。
其推理是:权重衰减会导致嵌入层范数在训练过程中逐渐减小,这可能导致早期层中的梯度变大,因为层归一化(Layer Normalization)的雅可比矩阵与输入范数成反比 (Takase et al., 2025)。
我们通过训练三种配置来测试这种方法:
- 基线模型: 使用标准权重衰减。
- 变体模型: 嵌入层无权重衰减。
- 组合模型: 结合我们所有已采纳的更改(嵌入层无权重衰减 + NoPE + 文档掩码),以确保技术之间没有负面相互作用。
损失曲线和评测结果在所有三种配置中几乎相同。因此,我们在SmolLM3训练中采用了所有这3项更改。
4.1.4.3 QK-norm
QK-norm (Dehghani et al., 2023)在计算注意力之前,对查询(Query)和键(Key)向量同时应用层归一化(Layer Normalization)。这项技术有助于防止注意力Logits变得过大,并被许多最近的模型用于提高稳定性。
然而,B. Yang et al. (2025)发现QK-norm会损害长上下文任务。他们的分析显示,QK-norm导致相关Token(即“针”)上的注意力质量降低,而不相关上下文上的注意力质量增高。他们认为发生这种情况是因为归一化操作消除了query-key点积中的幅度信息(magnitude information),这使得注意力Logits的幅度更接近。
由于这个原因,我们没有在SmolLM3中使用QK-norm。此外,作为一个小的30亿参数模型,与那些QK-norm已被证明最有益处的更大模型相比,它面临的训练不稳定性风险也较小。
4.1.5 其他核心组件(Other Core Components)
除了我们已经涵盖的组件之外,还有一些其他值得一提的架构决策,以求完整性。
-
参数初始化(Initialization): 现代模型通常使用截断正态分布初始化(均值=0,标准差$\text{std}=0.02$或$\text{std}=0.006$),或者像 $\mu\text{P}$ (G. Yang & Hu, 2022)这样的初始化方案,例如Cohere的Command A (Cohere et al., 2025)。这也可以是另一个消融实验的主题。
-
激活函数(Activation Functions): SwiGLU已成为现代LLM中的事实标准(除了使用GeGLU的Gemma2和使用$\text{relu}^2$的Nvidia (Nvidia et al., 2024; NVIDIA et al., 2025)),取代了像ReLU或GELU这样较旧的选择。
-
架构布局(Architectural Layout): 从更宏观的层面看,架构布局的选择也对模型的行为起着作用。尽管总参数数量在很大程度上决定了语言模型的容量,但这些参数如何分布在深度(depth)和宽度(width)上也至关重要。Petty et al. (2024)发现,在语言建模和组合性任务上,更深的模型比参数量相等的更宽的模型表现更优,直到这种益处达到饱和。“深而薄”的策略在MobileLLM的子十亿参数LLM消融实验中表现良好 (Z. Liu et al., 2024),而更宽的模型由于具有更大的并行性,往往能提供更快的推理速度。现代架构以不同的方式体现了这种权衡取舍,正如本文所指出的。
至此,我们已经涵盖了值得为你的训练运行进行优化的密集型Transformer架构的最重要方面。
然而,最近也出现了涉及模型整体的其他架构干预,即MoE(专家混合)模型和混合(Hybrid)模型。让我们来看看它们能提供什么,从MoE开始。
4.1.6 走向稀疏:专家混合模型(MoE)
专家混合模型(MoE)的直觉是:我们不需要为了每一个Token的预测都用到整个模型,这类似于我们的大脑会根据手头的任务激活不同的区域(例如视觉或运动皮层)。对于一个LLM来说,这意味着模型在执行翻译任务时,那些学习了代码语法的组件就不需要被使用。如果能做好这一点,就意味着我们可以节省大量的计算资源,因为在推理时我们只需要运行部分模型。
在技术层面上,MoE的目标很简单:增加总参数量,同时不增加每个Token的“活跃”参数量。简化来说,总参数量影响模型的总体学习容量,而活跃参数量决定了训练成本和推理速度。这就是为什么现在许多前沿系统(例如DeepSeek V3、K2,以及Gemini、Grok等闭源模型)都在使用MoE架构。如果你是第一次接触MoE,不用担心,其机制并不复杂。让我们从标准的密集型架构开始,看看MoE所需的必要改变。在MoE中,我们将单个MLP替换为多个MLP(“专家 Experts”),并在这些MLP之前添加一个可学习的路由器(Router)。对于每个Token,路由器会选择一小部分专家来执行计算。总参数量和活跃参数量的区别就源于此:模型拥有很多专家,但任何给定的Token只使用其中少数几个。
设计MoE层会引出几个核心问题:
- 专家形态与稀疏度: 你应该使用许多小型专家还是少数大型专家?每个Token应该有多少专家是活跃的?你总共需要多少专家(即稀疏度或“top-k”)?是否应该让某些专家成为通用型(universal)专家而始终保持活跃?
- 利用率与专业化: 如何选择路由的专家,并确保它们被充分利用(避免闲置容量),同时鼓励它们实现专业化?在实践中,这是一个负载均衡(load-balancing)问题,对训练和推理效率有着重要影响。
在这里,我们专注于一个目标:给定固定的计算预算,我们如何选择一个能将损失降到最低的MoE配置?这是一个不同于纯系统效率(吞吐量/延迟)的问题,我们稍后会再讨论后者。本节的大部分内容遵循蚂蚁集团MoE扩展定律论文 (Tian et al., 2025)中的分析。我们将使用他们提出的效率杠杆(Efficiency Leverage, EL)的概念。简单来说,EL衡量了你需要多少密集型计算才能匹配MoE设计所达到的损失,衡量单位是FLOPs。更高的EL意味着与密集型训练相比,该MoE配置每单位计算能带来更多的损失改进。让我们仔细看看如何设置MoE的稀疏度来提高效率杠杆。
4.1.6.1 稀疏度/激活比率(Sparsity / Activation Ratio)
在本节中,我们想找出哪种MoE设置是最佳的。渐进地来看,很容易看出两个极端情况都不是理想的设置:
- 一方面,始终激活所有专家会使我们回到密集型设置,即所有参数始终被使用。
- 另一方面,如果活跃参数非常少(极端情况下只激活1个参数),显然不足以解决任务,即使是在一个狭窄的领域。
因此,我们显然需要找到一个中间点。在我们深入寻找最佳设置之前,定义两个量是有用的:激活比率(activation ratio)及其倒数稀疏度(sparsity):
\[\text{激活比率} = \frac{\text{激活的专家数量}}{\text{总专家数量}}\] \[\text{稀疏度} = \frac{\text{总专家数量}}{\text{激活的专家数量}} = \frac{1}{\text{激活比率}}\]从计算的角度来看,成本仅由活跃参数驱动。如果你保持活跃专家的数量(和大小)固定,并增加专家的总数,你的推理/训练FLOPs预算大致保持不变,但你增加了模型的容量,因此只要训练时间足够长,模型通常会变得更好。
如果你对最近的MoE论文进行调查,会发现一些有趣的经验性结论:在固定活跃专家的数量和大小的情况下,增加专家的总数(即降低激活比率/增加稀疏度)可以改善损失,但当稀疏度变得非常高时,回报会递减。
两个例子:
- Kimi K2 (K. Team et al., 2025):显示了两种效应:更高的稀疏度提高了性能,但随着稀疏度的增长,收益会逐渐减少。
- 蚂蚁集团 (Tian et al., 2025):与K2得出相同结论,并额外指出稀疏度越高的MoE从增加计算中获得的益处越多。
下面是一些MoE模型的稀疏度表格:
| 模型 | 总专家数量 | 每个Token激活数量(含共享) | 稀疏度 |
|---|---|---|---|
| Mixtral-8x7B | 8 | 2 | 4.0 |
| Grok-1 | 8 | 2 | 4.0 |
| Grok-2 | 8 | 2 | 4.0 |
| OLMoE-1B-7B-0924 | 64 | 8 | 8.0 |
| gpt-oss 20b | 32 | 4 | 8.0 |
| Step-3 | 48 路由 + 1 共享 = 49 | 3 路由 + 1 共享 = 4 | 12.25 |
| GLM-4.5-Air | 128 路由 + 1 共享 = 129 | 8 路由 + 1 共享 = 9 | 14.3 |
| Qwen3-30B-A3B | 128 | 8 | 16.0 |
| Qwen3-235B-A22B | 128 | 8 | 16.0 |
| GLM-4.5 | 160 路由 + 1 共享 = 161 | 8 路由 + 1 共享 = 9 | 17.8 |
| DeepSeek-V2 | 160 路由 + 2 共享 = 162 | 6 路由 + 2 共享 = 8 | 20.25 |
| DeepSeek-V3 | 256 路由 + 1 共享 = 257 | 8 路由 + 1 共享 = 9 | 28.6 |
| gpt-oss 120b | 128 | 4 | 32.0 |
| Kimi K2 | 384 路由 + 1 共享 = 385 | 8 路由 + 1 共享 = 9 | 42.8 |
| Qwen3-Next-80B-A3B-Instruct | 512 路由 + 1 共享 = 513 | 10 总激活 + 1 共享 = 11 | 46.6 |
最近的趋势很明显:MoE模型正变得越来越稀疏。尽管如此,最佳稀疏度仍然取决于硬件和端到端效率。例如,Step-3旨在达到峰值效率,并有意不将稀疏度最大化,以适应其特定的硬件和带宽约束,而gpt-oss-20b的稀疏度较低则是由于设备上的内存限制(被动专家仍然占用一些内存)。
4.1.6.2 粒度(Granularity)
除了稀疏度之外,我们还需要决定每个专家应该有多大。这由蚂蚁集团引入的粒度(Granularity)来捕获。让我们明确这个术语的含义。术语在不同论文中有所不同,有些使用略有不同的公式。在这里,我们将使用与我们引用的图表相匹配的定义:
\[G = \alpha \times \frac{d_{model}}{d_{expert}}\]更高的粒度值对应于拥有更多具有更小维度($d_{expert}$)的专家(给定固定的参数数量)。该指标是专家维度($d_{expert}$)与模型维度($d_{model}$)之间比值的倒数(乘上归一化系数$\alpha$)。
在密集型模型中,一个常用的经验法则是将MLP的维度设置为$d_{intermediate} = 4 \times d_{model}$。如果$\alpha=4$(像Krajewski et al. (2024)),你可以大致将粒度视为匹配密集型MLP宽度所需的专家数量($4 \times d_{model} = d_{intermediate} = G \times d_{expert}$)。
这种解释只是一个粗略的启发式:现代MoE设计通常分配比单个密集型MLP大得多的总容量,因此一对一匹配在实践中会失效。蚂蚁团队的设置选择了$\alpha=2$,这只是一种不同的归一化选择。为了保持一致性,我们将采用这种约定并坚持下去。
以下是一些MoE版本的不同粒度值表格:
| 模型 | $d_{model}$ | $d_{expert}$ | $G = 2 \times d_{model} / d_{expert}$ | 年份 |
|---|---|---|---|---|
| Mixtral-8x7B | 4,096 | 14,336 | 0.571 | 2023 |
| gpt-oss-120b | 2,880 | 2,880 | 0.5 | 2025 |
| gpt-oss-20b | 2,880 | 2,880 | 0.5 | 2025 |
| Grok 2 | 8,192 | 16,384 | 1.0 | 2024 |
| StepFun Step-3 | 7,168 | 5,120 | 2.8 | 2025 |
| OLMoE-1B-7B | 2,048 | 1,024 | 4.0 | 2025 |
| Qwen3-30B-A3B | 2,048 | 768 | 5.3 | 2025 |
| Qwen3-235B-A22B | 4,096 | 1,536 | 5.3 | 2025 |
| GLM-4.5-Air | 4,096 | 1,408 | 5.8 | 2025 |
| DeepSeek V2 | 5,120 | 1,536 | 6.6 | 2024 |
| GLM-4.5 | 5,120 | 1,536 | 6.6 | 2025 |
| Kimi K2 | 7,168 | 2,048 | 7.0 | 2025 |
| DeepSeek V3 | 7,168 | 2,048 | 7.0 | 2024 |
| Qwen3-Next-80B-A3B | 2,048 | 512 | 8.0 | 2025 |
让我们谈谈粒度如何影响行为(来自蚂蚁集团的论文):
粒度看起来不是EL的主要驱动因素——它有帮助,尤其是当粒度超过2时,但它不是决定损失的主导因素。不过,存在一个最佳点:提高粒度到一定程度会有帮助,然后收益就会趋于平稳。因此,粒度是一个有用的调整旋钮,最近发布的趋势明显倾向于更高的值,但不应该孤立地进行优化。
另一种广泛用于改进MoE的方法是 共享专家(Shared Experts) 的概念。让我们来看看!
4.1.6.3 共享专家(Shared Experts)
共享专家设置将每个Token路由到一组始终处于激活状态的专家。这些共享专家吸收数据中基本、重复的模式,以便其余的专家可以更积极地进行专业化。
在实践中,你通常不需要很多共享专家;模型设计者通常选择一个,最多两个。随着粒度的增加(例如,从Qwen3风格的设置转向更接近Qwen3-Next的设置),共享专家往往变得更有用。总体影响是适度的,它不会戏剧性地改变EL。一个简单的经验法则是只使用一个共享专家,这与DeepSeek V3、K2和Qwen3-Next等模型的选择相符,并且倾向于在不增加不必要复杂性的情况下最大化效率。
那么,共享专家就是所有Token都会始终路由通过的专家。那其他专家呢?我们如何学习何时路由到每个专家,并确保我们不会只使用少数几个专家?接下来我们将讨论负载均衡,它正是解决这个问题的。
4.1.6.4 负载均衡(Load Balancing)
负载均衡是MoE中的关键环节。如果设置不当,它可能会破坏所有其他设计选择。通过以下示例,我们可以看到为什么糟糕的负载均衡会给我们带来很多痛苦。
考虑一个非常简单的分布式训练设置,我们有4块GPU,并将模型的4个专家均匀地分布在这些GPU上。如果路由崩溃,所有Token都被路由到专家1,这意味着只有1/4的GPU被利用,这对训练和推理效率来说非常糟糕。除此之外,这也意味着模型的有效学习容量也降低了,因为并非所有专家都被激活。
为了解决这个问题,我们可以向路由器添加一个额外的损失项。你可以在下方看到标准的 基于辅助损失(auxiliary loss–based) 的负载均衡 (LBL):
\[\text{Loss}_{LBL} = \alpha \times \sum_{i} f_i \times P_i\]这个简单的公式只使用了三个因子:
- 系数$\alpha$决定了损失的强度。
- $f_i$是流量分数(traffic fraction),即流经专家$i$的Token分数。
- $P_i$是概率质量(probability mass),简单地是流经该专家的Token概率总和。
两者都是必需的:$f_i$对应于实际的均衡,而$P_i$是平滑且可微分的,允许梯度流动。如果我们实现了完美的负载均衡,我们会得到$f_i = P_i = 1/N_r$。然而,我们需要小心如何调整$\alpha$,因为$\alpha$值太小,我们对路由的引导不够;而如果$\alpha$太大,路由的均匀性就变得比主要的语言模型损失更重要。
一个关键的细节是计算路由统计信息的范围:$f_i$和$P_i$是按局部批次(每个Worker的Mini-Batch)计算,还是按全局(跨Worker/设备聚合)计算?Qwen团队的分析 (Qiu et al., 2025)表明,当每个局部批次中没有足够的Token多样性时,局部计算可能会损害专家专业化(路由健康状况的良好代理)和整体模型性能。专家专业化是一种现象,即一个或多个专家在特定领域被激活得比其他专家更频繁。换句话说,如果一个局部批次很窄,它的路由统计数据就会嘈杂/有偏差,不会带来好的均衡。这表明,只要可行,我们就应该使用全局统计信息(或至少是跨设备的聚合)。值得注意的是,在那篇论文发表时,许多框架——包括Megatron——默认都是局部计算这些统计信息的。
通常,围绕MoE进行架构选择的消融实验是棘手的,因为它涉及许多方面的相互作用。例如,共享专家的有效性可能取决于模型的粒度。因此,值得花一些时间来确保你有一套好的实验,才能真正获得你正在寻找的洞察!
我们现在已经涵盖了MoE的基础知识,但仍有更多内容有待发现。以下是一些非详尽的、可供进一步研究的项目清单:
- 零计算专家、MoE层重新缩放和训练监控(来自LongCat-Flash论文)。
- 正交损失负载均衡(如ERNIE 4.5中所示)。
- 在训练过程中调度负载均衡系数。
- 架构/优化与MoE的相互作用,例如:
- 优化器排名是否会因MoE而改变。
- 如何将$\mu\text{P}$应用于MoE。
- 如何为MoE调整学习率(因为它们在每个批次中看到的Token数量不同)。
- 起始处的密集层数量。
- …更多。
我们把进一步深入探索的重任留给你们这些求知若渴的读者,现在我们将转向最后一个主要的架构选择:混合模型(Hybrid Models)!
4.1.7 混合模型(Hybrid Models)
最近的一个趋势是使用状态空间模型(State Space Models, SSM)或线性注意力机制(Linear Attention)来增强标准的密集型或MoE架构 (MiniMax et al., 2025; Zuo et al., 2025)。这些新型模型试图解决Transformer的一些根本性弱点:高效处理极长上下文。
它们采取了一种中间路线:
- 循环模型(Recurrent Models)可以高效处理任意长度的上下文并线性扩展,但可能难以充分利用上下文中的信息。
- Transformer在长上下文下变得极其昂贵,但可以很好地利用上下文中的模式。
混合模型则试图兼得两者之长,这也是它们得名的原因。
有一些研究 (Waleffe et al., 2024)试图了解Mamba模型(一种SSM形式)的弱点,发现这类模型在许多基准测试中表现良好,但例如在MMLU上表现不佳,并推测是缺乏上下文学习能力(in-context learning)导致了这种差距。这就是为什么它们会与密集型或MoE模型中的模块结合使用的原因。
这些线性注意力方法背后的核心思想是重新排序计算,使注意力不再需要$O(n^2d)$的成本,因为在长上下文时这是无法处理的。这是如何工作的呢?
首先,回顾推理时的注意力公式。为Token $t$生成输出$o_t$看起来像这样(带有Softmax):
\[o_t = \frac{\sum_{j=1}^t \exp(q_t^\top k_j) v_j}{\sum_{l=1}^t \exp(q_t^\top k_l)}\]现在,去掉Softmax(朴素线性注意力):
\[o_t = \sum_{j=1}^t (q_t^\top k_j) v_j\]重新排序后得到(交换了$q_t$的位置):
\[\sum_{j=1}^t (q_t^\top k_j) v_j = \left(\sum_{j=1}^t v_j k_j^\top\right) q_t\]定义运行状态(Running State)$S_t$:
\[S_t \triangleq \sum_{j=1}^t k_j v_j^\top = K_{1:t}^\top V_{1:t} \in \mathbb{R}^{d \times d}\]通过简单的更新公式:
\[S_t = S_{t-1} + k_t v_t^\top\]因此我们可以写成:
\[o_t = S_t q_t = S_{t-1} q_t + v_t (k_t^\top q_t)\]为什么重新排序很重要?
左边的形式$\sum_{j \leq t} (q_t^\top k_j) v_j$意味着:“对于每个过去的Token $j$,计算一个点积$q_t^\top k_j$(一个标量),用它来缩放$v_j$,并将这$t$个向量相加”——这在第$t$步的成本约为$O(t d)$。
右边的形式将其重写为$(\sum_{j \leq t} v_j k_j^\top) q_t$:你保留了一个单一的运行状态矩阵$S_t = \sum_{j \leq t} v_j k_j^\top \in \mathbb{R}^{d \times d}$,它已经总结了所有过去的$(k_j, v_j)$。
- 每个新Token用一个外积$v_t k_t^\top$更新$S_t$,成本是$O(d^2)$。
- 然后输出只是一个矩阵-向量乘法$S_t q_t$(另一个$O(d^2)$)。
因此,通过左边的形式从头生成$T$个Token是$O(T^2 d)$;而通过维护$S_t$并使用右边的形式是$O(T d^2)$。当$T$(序列长度)远大于$d$(维度)时,右边的形式就具备了显著的效率优势,实现了$O(T)$级别的线性缩放**。
(直觉上:左边 = “每一步有多次小规模点积-缩放-相加”;右边 = “一个预先总结好的矩阵乘以查询”,用对序列长度的依赖换取对维度的依赖。)
我们在这里关注推理和循环形式,但它在训练中也更高效,重新排序就像下面的等式一样简单:
\[(QK^\top)_{n \times n} V = Q (K^\top V)_{d \times d}\]因此,我们可以看到这现在看起来与RNN类似的结构非常相似。这解决了我们的问题,对吗?几乎如此。
在实践中,Softmax扮演着重要的稳定作用,而朴素的线性形式在没有某种归一化的情况下可能不稳定。这就催生了一种实用的变体,称为Lightning或Norm Attention!
Lightning和Norm Attention
这一系列方法出现在Minimax01 (MiniMax et al., 2025)中,最近又出现在Ring-linear (L. Team, Han, et al., 2025)中,它们建立在Norm Attention (Qin et al., 2022)的思想之上。关键步骤很简单:对输出进行归一化。“Lightning”变体专注于使实现快速高效,并使公式略有不同。这是两者的公式:
- NormAttention:
- \[\text{RMSNorm}(Q(K^T V))\]
- LightningAttention: \(Q=\text{Silu}(Q), \quad K=\text{Silu}(K), \quad V=\text{Silu}(V)\) \(O=\text{SRMSNorm}(Q(K V^T))\)
根据Minimax01的说法,经验上,采用Norm Attention的混合模型在大多数任务上与Softmax匹配。
有趣的是,在像“大海捞针”(Needle in a Haystack, NIAH)这样的检索任务上,它比完整的Softmax注意力做得好得多,这似乎令人惊讶,但可能表明Softmax和线性层协同工作时存在某种协同效应!
现在,让我们看看更多这些方法,以及如何用一个统一的框架来理解它们。
进阶线性注意力(Advanced Linear Attention)
从循环模型中得到的一个有益教训是:让状态偶尔能够“放下过去”。在实践中,这意味着为先前的状态引入一个门控(Gate)$G_t$:
\[S_t = G_t \odot S_{t-1} + v_t k_t^\top\]几乎所有最近的线性注意力方法都有这个门控组件,只是$G_t$的实现不同。以下是一些论文中提出的不同变体及其对应的架构:
| 模型 | 参数化(Parameterization) | 可学习参数(Learnable parameters) |
|---|---|---|
| Mamba (A. Gu & Dao, 2024) | $G_t = \exp(-(1t^\top \alpha_t) \odot \exp(\mathbf{A}))$, $\alpha_t = \text{softmax}(x_t W{\alpha 1} W_{\alpha 2})$ | $\mathbf{A} \in \mathbb{R}^{d_k \times d_k}$, $W_{\alpha 1} \in \mathbb{R}^{d \times d_{\alpha}}$, $W_{\alpha 2} \in \mathbb{R}^{d_{\alpha} \times d_k}$ |
| Mamba-2 (Dao & Gu, 2024) | $G_t = \gamma_t 1t^\top 1$, $\gamma_t = \exp(-\text{softmax}(x_t W\gamma) \exp(a))$ | $W_\gamma \in \mathbb{R}^{d \times 1}$, $a \in \mathbb{R}$ |
| mLSTM (Beck et al., 2025; H. Peng et al., 2021) | $G_t = \gamma_t 1t^\top 1$, $\gamma_t = \sigma(x_t W\gamma)$ | $W_\gamma \in \mathbb{R}^{d \times 1}$ |
| Gated Retention (Sun et al., 2024) | $G_t = \gamma_t 1t^\top 1$, $\gamma_t = \sigma(x_t W\gamma)^{\frac{1}{2}}$ | $W_\gamma \in \mathbb{R}^{d \times 1}$ |
| DFW (Mao, 2022; Pramanik et al., 2023) | $G_t = \alpha_t^\top \beta_t$, $\alpha_t = \sigma(x_t W_\alpha)$, $\beta_t = \sigma(x_t W_\beta)$ | $W_\alpha \in \mathbb{R}^{d \times d_k}$, $W_\beta \in \mathbb{R}^{d \times d_k}$ |
| GateLoop (Katsch, 2024) | $G_t = \alpha_t^\top 1$, $\alpha_t = \sigma(x_t W_{\alpha 1}) \exp(x_t W_{\alpha 2} i)$ | $W_{\alpha 1} \in \mathbb{R}^d$, $W_{\alpha 2} \in \mathbb{R}^d$ |
| HGRN-2 (Qin et al., 2024) | $G_t = \alpha_t^\top 1$, $\alpha_t = \gamma + (1-\gamma) \sigma(x_t W_\alpha)$ | $W_\alpha \in \mathbb{R}^{d \times d_k}$, $\gamma \in (0, 1)^d$ |
| RWKV-6 (B. Peng et al., 2024) | $G_t = \alpha_t^\top 1$, $\alpha_t = \exp(-\exp(x_t W_\alpha))$ | $W_\alpha \in \mathbb{R}^{d \times d_k}$ |
| Gated Linear Attention (GLA) | $G_t = \alpha_t^\top 1$, $\alpha_t = \sigma(x_t W_{\alpha 1} W_{\alpha 2})^{\frac{1}{2}}$ | $W_{\alpha 1} \in \mathbb{R}^{d \times 16}$, $W_{\alpha 2} \in \mathbb{R}^{16 \times d_k}$ |
一个值得注意的变体是列表中的Mamba-2 (Dao & Gu, 2024)。它被用于许多混合模型中,如Nemotron-H (NVIDIA, :, Blakeman, et al., 2025)、Falcon H1 (Zuo et al., 2025)和Granite-4.0-h (IBM Research, 2025)。
然而,现在仍处于早期阶段,在扩展到大型混合模型时,需要考虑重要的细微差别。虽然它们显示出希望,但MiniMax对M2的经验突出表明,小规模的益处并不总是能转化为大规模生产系统,尤其是在复杂的推理任务、RL训练稳定性和基础设施成熟度方面。
话虽如此,混合模型正在迅速发展,并且仍然是前沿训练的一个可靠选择。Qwen3-Next (采用门控DeltaNet更新) (Qwen Team, 2025)报告称,它们在长上下文推理时更快、训练更快,并且在常规基准上更强。我们也期待Kimi的下一个模型,它很可能会使用他们新的“Kimi Delta Attention”。
最后,我们还要提一下稀疏注意力(Sparse Attention),它通过选择块或查询来计算注意力,解决了与线性注意力相同的长上下文问题。一些例子包括Native Sparse Attention (Yuan et al., 2025)、DeepSeek Sparse Attention (DeepSeek-AI, 2025)和InfLLM v2 (M. Team, Xiao, et al., 2025)。
在转向分词器之前,我们将通过构建一个小的决策树来决定是训练密集型、MoE还是混合模型,从而结束架构选择的讨论。
4.1.8 要不要MoE:如何选择基础架构?
我们现在已经见识了密集型(Dense)、MoE(专家混合)和混合(Hybrid)模型,你自然会好奇到底应该使用哪一种?
你的架构选择通常取决于模型的部署环境、团队的专业知识和项目时间线。让我们简要回顾每种架构的优缺点,并得出一个简单的指导流程,以帮助你找到最适合你的架构。
| 架构类型 | 描述 | 优点 (Pros) | 缺点 (Cons) |
|---|---|---|---|
| 密集型 (Dense) | 标准的仅解码器Transformer,每个参数都为每个Token激活。 | 广泛支持、成熟易懂、训练稳定、每参数性能良好。 | 计算成本与模型大小线性扩展,一个70B模型比3B模型贵约23倍。 |
| 专家混合 (MoE) | 用多个“专家”替代Transformer中的前馈层。一个门控网络只将每个Token路由到少数专家。 | 每单位计算的训练和推理性能更好。 | 内存占用高(所有专家都必须加载)。训练比密集型更复杂。框架支持不如密集型成熟。分布式训练的专家放置、负载均衡和全对全通信(All-to-all)是梦魇。 |
| 混合型 (Hybrid) | 结合Transformer与状态空间模型(SSM,如Mamba),为部分操作提供线性复杂度,以对抗注意力的二次方缩放。 | 潜在地更好的长上下文处理能力。 对于极长序列更高效。 | 成熟度低于密集型和MoE,经过验证的训练“配方”更少。框架支持有限。 |
简而言之,如何做决定:
首先,问问你的模型将部署在哪里。然后,考虑你团队的专业知识和训练时间线,以评估你可以承受多少探索成本:
- 部署场景决定了内存约束:
- 设备端/边缘设备/低显存要求: 内存是瓶颈,基本排除MoE (MoE必须加载所有参数)。首选密集型或考虑极致优化的混合型。
- 云计算/数据中心/追求SOTA性能/算力充裕: 内存不是主要瓶颈,优先考虑MoE(最佳的性能/计算比)或混合型(极致的长上下文)。
- 团队经验和时间线决定了技术风险:
- 时间紧迫/团队经验相对较少: 选择密集型架构,这是最稳健、文档最丰富的路径。
- 有时间探索/拥有资深分布式/系统工程师: 可以在MoE或混合型上进行探索。
以SmolLM3为例:
我们想构建一个强大的设备端部署小型模型,时间线大约是3个月,并且过去主要训练的是密集型模型。
- 这排除了MoE(设备内存约束)。
- 这也排除了混合型(探索新架构的时间太短,而且密集型模型也能达到我们目标的最大128k Token长上下文)。
因此,我们选择了Llama风格的密集型模型。
现在我们已经研究了模型架构的内部细节,接下来让我们看看分词器(Tokenizer),它是连接数据和模型的桥梁。
4.1.9 分词器(Tokenizer):被低估的“语言翻译官”
虽然分词方案很少能像架构创新那样引人注目,但它很可能是任何语言模型中最被低估的组件之一。
你可以将分词器视为人类语言和模型所处的数学世界之间的翻译官,而正如任何翻译官一样,翻译的质量至关重要。那么,我们如何为自己的需求构建或选择一个合适的分词器呢?
分词器的基本原理
分词器的核心作用是将原始文本转换为模型可以处理的数字序列,它通过将一段运行文本分割成称为Token的单个可处理单元来实现。在深入技术细节之前,我们应该首先回答一些将指导我们分词器设计的根本问题:
- 我们想要支持哪些语言?如果我们正在构建一个多语言模型,但我们的分词器只见过英语,那么当遇到非英语文本时,模型将是低效的,文本会被分割成比必要多得多的Token。这直接影响性能、训练成本和推理速度。
- 哪些领域对我们很重要?除了语言之外,像数学和代码这样的领域需要仔细表示数字。
- 我们知道目标数据混合配比吗?如果我们计划从头开始训练我们的分词器,理想情况下,我们应该用一个反映我们最终训练混合配比的样本来训练它。
一旦我们回答了这些问题,我们就可以研究主要的设计决策:
词汇表大小(Vocabulary Size)
词汇表本质上是一个列出了模型识别的所有Token(最小文本单元,如词语、子词或符号)的字典。
更大的词汇表能更有效地压缩文本,因为每个句子生成的Token更少,但这涉及到一个计算上的权衡。词汇表大小直接影响我们的嵌入矩阵的大小。如果词汇表大小为$V$,隐藏层维度为$h$,则输入嵌入有$V \times h$个参数,输出层也有$V \times h$个参数。正如我们在“嵌入层共享”一节中看到的,对于较小的模型,这占了总参数量的很大一部分,但随着模型的扩展,相对成本会缩小。
最佳点取决于我们的目标覆盖范围和模型大小。对于仅英语模型,大约50k个Token通常就足够了,但多语言模型通常需要100k+才能有效处理不同的书写系统和语言。像Llama 3这样的现代SOTA模型已经采用了128k+的词汇表范围,以提高跨不同语言的Token效率。同一家族中较小的模型则通过应用嵌入层共享来减少嵌入参数的百分比,同时仍受益于更大的词汇表。Dagan et al. (2024)分析了词汇表大小对压缩、推理和内存的影响。他们观察到,更大的词汇表带来的压缩收益呈指数级下降,暗示存在一个最优大小。对于推理,更大的模型受益于更大的词汇表,因为压缩节省的前向传播成本高于额外的嵌入Token在Softmax中的成本。对于内存,最优大小取决于序列长度和批次大小:更长的上下文和更大的批次受益于更大的词汇表,因为Token减少节省了KV缓存空间。
分词算法(Tokenization Algorithm)
BPE (Byte-Pair Encoding) (Sennrich et al., 2016)仍然是最流行的选择,WordPiece或SentencePiece等其他算法也存在,但采用较少。人们对无分词器方法(直接在字节或字符上工作)的研究兴趣也在增长,这有可能完全消除分词过程。
既然我们已经了解了定义分词器的关键参数,我们面临一个实际的决定:我们应该使用现有的分词器还是从头开始训练?答案取决于覆盖范围:具有我们目标词汇表大小的现有分词器是否能很好地处理我们的语言和领域。
虽然这两种分词器在英语上的表现似乎相似,但在阿拉伯语上差异显著:GPT2将文本分解成一百多个片段,而Gemma 3由于其多语言训练数据和更大、更具包容性的词汇表,生成的Token数量要少得多。
但是,要衡量一个分词器的质量,我们不能仅仅凭直觉看几个分词示例就断定好坏,就像我们不能在没有运行消融实验的情况下凭直觉进行架构更改一样。我们需要具体的指标来评估分词器的质量。
衡量分词器质量
为了评估分词器的表现,我们可以使用FineWeb2中使用的两个关键指标 (Penedo et al., 2025)。
-
Token消耗比(Fertility): 它衡量了编码一个词平均所需的Token数量。较低的Token消耗比意味着更好的压缩,这转化为更快的训练和推理。你可以这样想:如果一个分词器需要一个或两个以上Token来编码大多数词语,而另一个分词器需要的Token更少,那么后者显然更高效。
衡量Token消耗比的标准方法是计算词语-Token比率(word fertility),它衡量平均每个词需要多少Token。这个指标是围绕词语的概念定义的,因为它在有合适的词语分词器可用时(例如Spacy和Stanza),能提供有意义的跨语言比较 (Penedo et al., 2025)。
在比较单一语言的分词器时,你也可以使用字符或字节而不是词语来获得字符-Token比率或字节-Token比率 (Dagan et al., 2024)。然而,这些指标在跨语言比较方面存在局限性。字节可能会产生偏差,因为不同脚本中的字符需要不同的字节表示(例如,中文汉字在UTF-8中使用三个字节,而拉丁字符使用一到两个)。同样,使用字符数并没有考虑到词语长度在不同语言之间差异巨大的事实。例如,中文词语通常比德语复合词短得多。
-
连续词比例(Proportion of continued words): 这个指标告诉我们有多少百分比的词语被分割成多个片段。较低的百分比更好,因为它意味着被片段化的词语更少,从而实现更高效的分词。
对于像代码和数学这样的专业领域,除了Token消耗比之外,我们还需要更深入地研究分词器处理领域特定模式的能力。大多数现代分词器都进行单数字分割(例如,“123”被分割为[“1”, “2”, “3”])(Chowdhery et al., 2022; DeepSeek-AI et al., 2024)。将数字拆开可能看起来违反直觉,但它实际上有助于模型更有效地学习算术模式。如果“342792”被编码为一个不可分割的Token,模型必须记住将这个特定Token与每个其他数字Token相加、相减或相乘时会发生什么。但如果它被分割,模型就会学习数字级别的运算是如何工作的。像Llama 3 (Grattafiori et al., 2024)这样的分词器将数字1到999编码为独特的Token,其余的数字则由这些Token组成。
因此,我们可以衡量在我们的目标领域上的Token消耗比,以评估一个分词器的弱点和优势。下表比较了流行分词器在不同语言和领域的Token消耗比。
评估分词器
为了比较不同语言的分词器,我们将使用FineWeb2分词器分析中的设置,使用Wikipedia文章作为我们的评估语料库。对于每种语言,我们将抽取100篇文章以获得有意义的样本,同时保持计算可控。
结果揭示了一些赢家和权衡取舍,具体取决于你的优先事项:
Token消耗比(每个词的Token数)
| 分词器 (词汇表大小) | 英语 | 中文 | 法语 | 阿拉伯语 |
|---|---|---|---|---|
| Llama3 (128k) | 1.48 | 1.60 | 1.73 | 2.35 |
| Mistral Small (131k) | 1.59 | 1.78 | 1.69 | 2.15 |
| Qwen3 (151k) | 1.54 | 1.45 | 1.75 | 2.26 |
| Gemma3 (262k) | 1.41 | 1.47 | 1.56 | 2.25 |
连续词比例 (%)
| 分词器 (词汇表大小) | 英语 | 中文 | 法语 | 阿拉伯语 |
|---|---|---|---|---|
| Llama3 (128k) | 32.2% | 42.6% | 48.2% | 71.8% |
| Mistral Small (131k) | 36.8% | 47.1% | 46.5% | 66.0% |
| Qwen3 (151k) | 32.8% | 30.7% | 47.8% | 66.0% |
| Gemma3 (262k) | 26.0% | 33.1% | 39.9% | 70.1% |
Gemma 3分词器在多种语言上实现了较低的Token消耗比和分词率,尤其在英语、法语和西班牙语上,这可以通过其分词器训练数据和非常庞大的262k词汇表(大约是Llama 3的128k的两倍)来解释。Qwen 3分词器在中文上表现出色,但在英语、法语和西班牙语上落后于Llama 3分词器。Mistral Small分词器 (Mistral AI, 2025)在阿拉伯语上表现最佳,但在英语和中文上落后于其他分词器。
选择现有分词器还是自定义分词器
目前,市面上有一系列不错的强大分词器可供选择。许多最近的模型都是从GPT4的分词器 (OpenAI et al., 2024)开始,然后通过额外的多语言Token进行增强。正如我们在上表中所看到的,Llama 3的分词器在多语言文本和代码上的平均分词质量表现良好,而Qwen 2.5在中文和一些低资源语言上表现尤为出色。
-
何时使用现有分词器:如果我们的目标用例与上表最佳分词器(Llama、Qwen、Gemma)的语言或领域覆盖范围匹配,那么它们是经过实战检验的可靠选择。对于SmolLM3训练,我们选择了Llama 3的分词器:它在我们的目标语言(英语、法语、西班牙语、葡萄牙语、意大利语)上提供了有竞争力的分词质量,同时词汇表大小适中,这对于我们的小模型规模是合理的。对于嵌入层只占总参数量一小部分的大型模型,Gemma 3的效率增益变得更具吸引力。
-
何时训练我们自己的分词器:如果我们正在为低资源语言训练,或者我们的数据混合配比非常不同,我们可能需要训练自己的分词器以确保良好的覆盖范围。在这种情况下,重要的是我们要用一个接近我们认为最终训练混合配比的数据集来训练分词器。这会产生一个 “先有鸡还是先有蛋” 的问题,因为我们需要一个分词器才能运行数据消融实验并找到最佳混合配比。但我们可以在启动最终运行之前重新训练分词器,并验证下游性能有所改善且Token消耗比仍然良好。
分词器的选择可能看起来像一个技术细节,但它会影响你模型性能的方方面面。所以,不要害怕投入时间去确保它的正确性。
4.1.10 SmolLM3:将所有洞察付诸实践
既然我们已经探索了架构格局并运行了系统性的消融实验,那么让我们看看这一切是如何在SmolLM3这样的模型中付诸实践的。
SmolLM家族旨在突破小型模型能力的边界。SmolLM2交付了135M、360M和1.7B参数的三个有能力高效运行在设备端的模型。对于SmolLM3,我们希望在提升性能的同时保持手机上运行所需的小巧规模,并解决SmolLM2的弱点:多语言能力、极长上下文处理和强大的推理能力。我们选择了30亿参数作为实现这种平衡的最佳点。
由于我们是在扩大一个经过验证的“配方”,我们自然而然地倾向于密集型Transformer。当时nanotron中尚未实现MoE,而且我们已经拥有训练强大的小型密集型模型的专业知识和基础设施。更重要的是,对于边缘设备部署,我们受到内存限制,一个即使只有少量参数活跃但总参数量很大的MoE模型也会受到限制,因为我们仍然需要将所有专家加载到内存中。这使得密集型模型对于我们的边缘部署目标更为实用。
消融实验:我们以SmolLM2的1.7B架构作为基础,然后在1000亿Token上训练了一个3B的消融模型,使用了Qwen 2.5-3B的布局。这为我们提供了坚实的基线,以便单独测试每个修改。每一次架构更改都需要要么提升英语基准上的损失和下游性能,要么在不降低质量的情况下提供可衡量的益处,例如推理速度。
以下是我们进行最终运行之前测试并通过的关键修改:
- 分词器(Tokenizer):在深入架构修改之前,我们需要选择一个分词器。我们找到了一个很好的分词器集合,它们覆盖了我们的目标语言和领域。根据我们的词元化效率(Fertility)分析,Llama 3.2的分词器在我们的6种目标语言之间提供了最佳的权衡,同时将词汇表保持在128k——足够大以实现多语言效率,但又不会大到因嵌入权重而使我们3B参数模型的参数量过度膨胀。
- 分组查询注意力 (GQA):我们再次确认了我们早期的发现,即分组数量为4的GQA匹配多头注意力的性能,但这一次是在3B规模和1000亿Token上进行的。KV缓存的效率提升实在太诱人,尤其对于内存珍贵的设备端部署。
- 用于长上下文的NoPE:我们实施了NoPE,通过每隔4层移除RoPE。我们的3B消融实验证实了前面章节的发现:NoPE在不牺牲短上下文性能的情况下改进了长上下文处理。
- 文档内注意力掩码 (Intra-document attention masking):我们阻止了训练期间的跨文档注意力,以帮助在训练非常长的序列时提高训练速度和稳定性,我们再次发现这不影响下游性能。
- 模型布局优化:我们比较了文献中最近3B模型的布局,有些偏向深度,有些偏向宽度。我们在我们的训练设置中测试了Qwen 2.5-3B (3.1B)、Llama 3.2-3B (3.2B)和Falcon 3-H1-3B (3.1B)的布局,它们的深度和宽度各不相同。结果很有趣:所有布局都达到了几乎相同的损失和下游性能,尽管Qwen 2.5-3B的参数量实际上更少。但Qwen 2.5-3B更深的架构与研究结果一致,即网络深度有助于泛化 (Petty et al., 2024)。因此,我们选择了更深的布局,赌它会随着训练的进展而带来帮助。
- 稳定性改进:我们保留了SmolLM2的绑定嵌入(Tied Embeddings),并添加了一个受OLMo 2启发的新技巧:移除嵌入层的权重衰减。我们的消融实验显示,这在不损害性能的同时降低了嵌入层范数,有助于防止训练发散。
这种系统性消融方法的美妙之处在于,我们可以自信地将所有这些修改结合起来,因为我们知道每一个修改都已经得到了验证。
4.1.11 架构设计准则 (Rules of Engagement)
一句话总结:你的用例驱动你的选择。
- 让你的部署目标指导架构决策。在评估新的架构创新时,请考虑你的模型最终将在何处以及如何运行。
- 在创新与实用主义之间取得恰当的平衡。我们不能忽略重大的架构进步——在GQA和更好的替代方案存在的情况下,今天仍使用多头注意力(MHA)将是一个糟糕的技术选择。要随时了解最新研究,并采纳那些在大规模上提供清晰、经过验证的益处的技术。但要抵抗住追逐每一篇承诺微小收益的新论文的诱惑(除非你有足够的资源或你的目标就是架构研究)。
- 系统性胜过直觉。验证每一个架构更改,无论它在论文上看起来多么有前景。然后先单独测试修改,再将它们结合起来以理解它们的相互作用。
- 规模效应是真实存在的 — 尽可能在目标规模上重新消融。不要假设你的小规模消融结果在你的目标模型规模上会完美成立。如果你有算力,请尝试重新确认它们。
- 在你的实际领域上验证分词器效率。你的目标语言和领域上的Token消耗比(Fertility)指标,比追随最新的模型使用了什么更重要。一个50k的英语分词器无法胜任严肃的多语言工作,但如果你没有覆盖那么多语言,你也不需要一个256k的词汇表。
现在,模型架构已经确定,是时候着手解决将驱动学习过程的优化器和超参数了。
4.2 优化器与训练超参数
一切正在逐步就位。我们已经运行了消融实验,确定了架构,并选择了分词器。但在我们真正启动训练之前,仍然缺少一些至关重要的组件:我们应该使用哪个优化器(Optimizer)?应该设置多大的学习率(Learning Rate)和批次大小(Batch Size)?我们应该如何调度学习率在训练过程中的变化?
在这里,最诱人的方法是直接借用文献中另一个强大模型的参数值。毕竟,如果它对大实验室有效,对我们也应该有效,对吗?
在许多情况下,如果我们选取的是来自相似架构和模型大小的值,这样做确实可行。
然而,通过不对这些值针对我们的特定设置进行调优,我们有可能会牺牲掉潜在的性能。文献中的超参数是为特定的数据和约束条件优化的,而这些约束有时甚至与性能无关。也许某个学习率是在开发初期就选定的,从未被重新审视。即使模型作者进行了彻底的超参数扫描(Hyperparameter Sweeps),那些最优值也是针对他们精确的架构、数据和训练方案组合而找到的,而不是针对我们的。
文献中的值永远是一个很好的起点,但探索一下我们能否在附近找到更好的值,总是一个好主意。
在本章中,我们将探索最新的优化器(并看看老当益壮的AdamW (Kingma, 2014)是否依然经得起时间的考验 🎉)、深入研究超越标准余弦衰减的学习率调度,并找出如何针对给定的模型和数据大小来调优学习率和批次大小。
让我们从优化器之战开始吧。
4.2.1 优化器:AdamW以及其他
优化器(Optimizer)是整个LLM训练操作的核心。它根据过去的更新、当前的权重以及从损失函数中导出的梯度,来决定每个参数的实际更新步骤。同时,它也是一个吞噬内存和计算资源的野兽,因此会影响你需要多少GPU以及你的训练速度有多快。
我们总结了当前用于LLM预训练的优化器:
| 模型 | 优化器 |
|---|---|
| Kimi K2, GLM 4.5 | Muon |
| 其他所有人 | AdamW |
所以,你可能会好奇为什么大家都在用AdamW?
撰写这篇博客的人认为这是因为“人们很懒”,但更现实的说法可能是:AdamW在不同规模上长期以来一直表现良好/更优,而且改变这样一个核心组件总是有点可怕,尤其是当测试它在非常长的训练中的表现有多好既困难又昂贵时。
此外,公平地比较优化器比看起来更难。规模的变化会以一种难以在小型消融实验中模拟的方式改变动态,使得超参数调优变得复杂。你可能会说:“没关系,我的AdamW已经调优了好几周,我可以直接重用相同的超参数进行比较!”我们多么希望这是真的。但不幸的是,对于每种优化器,你都需要进行适当的超参数搜索(1D?2D?3D?),这使得优化器研究既困难又昂贵。
因此,让我们从经典且成就了Durk Kingma令人恐惧的Google Scholar统治地位的基础开始:AdamW。
AdamW
Adam(Adaptive Momentum Estimation,自适应矩估计)是一种一阶优化技术。这意味着,除了单独查看梯度之外,我们还会考虑权重在先前步骤中改变了多少。这使得每个参数的学习率根据动量进行自适应。
细心的读者可能会问:嘿,你是不是漏了一个W?没错!我们特意加上W(=权重衰减 Weight Decay)的原因如下。在标准的SGD中,我们可以简单地向损失添加一个$\lambda\theta^2$项(其中$\theta$是权重)来应用L2正则化。然而,如果我们对Adam做同样的事情,自适应学习率也会影响L2正则化。这意味着正则化强度变得依赖于梯度幅度,从而削弱了它的效果。这不是我们想要的,这就是为什么AdamW将L2正则化与主优化循环解耦应用来解决这个问题。
有趣的是,在过去几年中,AdamW的超参数几乎没有变动:
- $\beta_1 = 0.9, \beta_2 = 0.95$
- 梯度范数裁剪(grad norm clipping)$= 1.0$
- 权重衰减(weight decay)$= 0.1$(Llama-3-405B将其降至$0.01$)
从Llama 1, 2, 3到DeepSeek-V1, 2, 3 671B,几乎都重复使用了这三个值,没有变化。Durk Kingma从头到尾都是对的吗?或者我们能做得更好?
一句话理解Muon
Adam是一种一阶方法,因为它只使用了梯度。Muon则是一种二阶优化器,它作用于参数张量的矩阵视图。
\[\begin{aligned} G_t &= \nabla_\theta L_t(\theta_{t-1}) \\ B_t &= \mu B_{t-1} + G_t \\ O_t &= \text{NewtonSchulz5}(B_t) \approx UV^\top \quad \text{if } B_t = U\Sigma V^\top \quad (\text{SVD}) \\ \theta_t &= \theta_{t-1} - \eta O_t G_t \end{aligned}\]看着这些方程,你可能会好奇为什么这是一种二阶方法,我只看到了梯度,没有更高阶的项。二阶优化实际上发生在Newton Schulz步骤内部,但我们在此不做进一步的详细解释。
已经有一些高质量的博客深入解释了Muon,所以在这里我们只列出Muon的三个关键思想:
- 矩阵级几何 vs. 参数级更新:AdamW是每参数预处理(对角二阶矩)。Muon将每个权重矩阵视为一个单一对象,并沿着$G=UV^\top$进行更新,从而捕获行/列子空间结构。
- 通过正交化实现各向同性步骤:使用奇异值分解(SVD)将$G=U\Sigma V^\top$分解,将幅度($\Sigma$)与方向(左右子空间$U, V$)分离。用$UV^\top$替换$G$会丢弃奇异值,使步骤在活跃子空间中各向同性(Isotropic)。这乍一看有点反直觉——因为丢弃$\Sigma$看起来像是丢失信息——但它减少了轴对齐的偏差,并鼓励探索那些原本会被非常小的奇异值抑制的方向。这种探索是否会为模型带来不同的能力(而不仅仅是看损失)仍然是一个悬而未决的问题。
- 对更大批次大小的经验容忍度:在实践中,Muon通常能容忍更高的批次大小。我们将在批次大小部分更深入地讨论这一点,但这可能是Muon被采纳的关键点!
多年来,社区大多满足于AdamW,并且前沿实验室的优化器“配方”通常是保密的(例如Qwen就没有公开谈论他们的优化器)。但最近,Muon在高知名度的发布中得到了应用(例如Kimi K2、GLM-4.5)。希望我们能看到更多开放和稳健的使用配方。
优化器的世界是一个狂野的动物园,研究人员在组合所有可能的动量和导数方面所展现的创造力,唯一能与之匹敌的就是为它们命名:Shampoo、SOAP、PSGD、CASPR、DION、Sophia、Lion……就连AdamW也有自己的变体,如NAdamW、StableAdamW等。深入探讨所有这些优化器值得单独写一篇博客,但我们将其留待下次。同时,我们推荐斯坦福/Marin团队的这篇精彩论文 (Wen et al., 2025),他们对许多不同的优化器进行了基准测试,以展示在进行比较时超参数调优是多么重要。
与几乎所有优化器密切相关的问题是:我们应该以多大的强度更新权重?这由通常出现在优化器方程中的学习率这个标量值决定。让我们看看这个看似简单的话题是如何仍然拥有许多面向的。
4.2.2 学习率(Learning Rate)
学习率是我们即将设置的最重要的超参数之一。在每个训练步骤中,它控制着我们根据计算出的梯度来调整模型权重的幅度。
- 如果学习率设置得太低,我们的训练将变得慢得痛苦,并且我们可能会陷入一个糟糕的局部最小值。损失曲线将看起来平坦,我们会在没有取得有意义进展的情况下烧完算力预算。
- 另一方面,如果学习率设置得太高,我们会导致优化器采取巨大的步骤,从而越过最佳解决方案,永远无法收敛;或者发生无法想象的事情:损失发散,直接冲向云霄。
但最佳的学习率甚至不是恒定的,因为学习动态在训练过程中会发生变化。当我们离好的解决方案很远时,高学习率是有效的,但在接近收敛时,它们会导致不稳定性。这就是 学习率调度(Learning Rate Schedules) 发挥作用的地方:从零开始 预热(Warmup) 以避免早期的混乱,然后 衰减(Decay) 以稳定到一个好的最小值。这些模式(例如,Warmup + 余弦衰减)已被验证适用于神经网络训练多年。
让我们看看常见的调度方案,然后讨论如何选择峰值。
学习率调度:不止余弦衰减
多年来,人们就知道改变学习率有助于收敛 (Smith & Topin, 2018),而余弦衰减(Cosine Decay) (Loshchilov & Hutter, 2017)是训练LLM的首选调度方案:在预热后以峰值学习率开始,然后沿着余弦曲线平稳下降。它简单且效果好。
但它的主要缺点是缺乏灵活性:我们需要预先知道总训练步数,因为余弦周期的长度必须匹配你的总训练时长。这在常见的场景中会成为问题:你的模型尚未达到稳定期,或者你获得了更多算力想要训练更久,或者你正在运行扩展定律实验,需要用不同的Token数量训练相同的模型。余弦衰减会迫使你从头开始重启。
现在,许多团队使用不需要在预热后立即衰减的调度方案。这就是Warmup-Stable-Decay (WSD) (Hu et al., 2024)和Multi-Step(多步骤)变体 (DeepSeek-AI, :, et al., 2024)的情况。你可以在大部分训练中保持一个恒定的高学习率,然后在最后阶段(通常是最后10%-20%的Token)急剧衰减(WSD),或者进行离散的下降(步骤)来降低学习率,例如在80%训练之后,然后在90%之后再进行一次(如DeepSeek LLM的Multi-Step调度所做)。
这些调度方案提供了优于余弦衰减的实用优势。我们可以在运行中途延长训练而无需重启,无论是我们想训练更久,还是想提前衰减以更准确地衡量训练进度,或者我们可以用一次主训练运行来运行跨不同Token数量的扩展定律实验。此外,研究表明WSD和Multi-Step都与余弦衰减相匹配 (DeepSeek-AI, :, et al., 2024; Hägele et al., 2024),同时对于实际训练场景更具实用性。
但你可能注意到了,与余弦相比,这些调度引入了新的超参数:WSD中衰减阶段应该持续多久?Multi-Step变体中每个步骤应该持续多久?
- 对于WSD: 匹配余弦性能所需的冷却时间随着训练运行时间的延长而缩短,建议将 总Token的10%-20% 分配给衰减阶段 (Hägele et al., 2024)。我们将在下面的消融实验中确认这种设置是否匹配余弦。
- 对于Multi-Step:DeepSeek LLM的消融实验发现,虽然他们的基线80/10/10分割(稳定直到80%,第一次下降从80-90%,第二次从90-100%)匹配余弦,但调整这些比例甚至可以超越它,例如使用70/15/15和60/20/20的分割。
但我们可以对这些调度方案变得更有创意。让我们看看DeepSeek模型家族中使用的调度方案:
- DeepSeek LLM使用了基线Multi-Step调度(80/10/10)。
- DeepSeek V2将比例调整为60/30/10,给第一次衰减步骤更多的时间。
- DeepSeek V3采取了最具创意的方法:他们用余弦衰减从恒定阶段过渡(从67%到97%的训练),然后应用一个简短的恒定阶段,最后是最终的急剧下降。
让我们在这里结束对奇特学习率调度的调查,并烧掉一些GPU小时来确定在实践中什么有效!
消融实验 - WSD匹配余弦
现在是进行消融实验的时候了!让我们测试一下WSD在实践中是否真的匹配余弦的性能。我们在这里不会展示Multi-Step的消融实验,但我们推荐DeepSeek LLM的消融实验,他们在其中展示了Multi-Step在不同的阶段分割下与余弦相匹配。在本节中,我们将比较余弦衰减与具有两种衰减窗口(10%和20%)的WSD。
评测结果显示,所有三种配置的最终性能相似。观察损失和评测曲线(特别是HellaSwag),我们看到一个有趣的模式:余弦在稳定阶段(WSD衰减开始之前)实现了更好的损失和评测分数。然而,一旦WSD进入其衰减阶段,损失和下游指标都出现了近乎线性的改进,使WSD在训练结束时追赶上余弦。
这证实了WSD的10%-20%衰减窗口足以匹配余弦的最终性能,同时保持了在训练中途延长的灵活性。我们为SmolLM3选择了具有10%衰减的WSD。
既然我们对流行的学习率调度有了很好的概述,下一个问题是:峰值学习率到底应该是什么?
寻找最优学习率
我们如何为我们特定的学习率调度和训练设置挑选正确的学习率?
我们可以像对架构选择一样,在短消融实验中运行学习率扫描(Learning Rate Sweeps)。但最优学习率取决于训练时长:在短消融实验中收敛最快的学习率,可能不是完整运行的最佳学习率。而且我们负担不起仅仅为了测试不同的学习率而多次运行昂贵的多周训练。
让我们首先看看我们可以快速运行的简单扫描,帮助我们排除那些明显太高或太低的学习率,然后我们将讨论用于超参数的扩展定律。
消融实验 - 学习率扫描
为了说明不同学习率的影响,让我们看一下在我们1B消融模型上进行的扫描,该模型在45B Token上训练。我们使用4个不同的学习率训练相同的模型,使用相同的设置:$1\text{e-}4, 5\text{e-}4, 5\text{e-}3, 5\text{e-}2$。结果清楚地显示了两个极端的危险:
- $5\text{e-}2$ 几乎立即发散,损失早期飙升,从未恢复,使得模型无法使用。
- $1\text{e-}4$ 过于保守,虽然训练稳定,但收敛速度比其他学习率慢得多。
- 中值$5\text{e-}4$和$5\text{e-}3$显示出更好的收敛性,并且性能具有可比性。
但为每种模型尺寸运行扫描会很快变得昂贵,更重要的是,正如我们前面所述,它没有考虑到计划的训练Token数量。这就是扩展定律(Scaling Laws)变得无价的地方。
对于SmolLM3,我们使用WSD调度和AdamW在100B Token上训练3B模型,比较了几个学习率。我们发现$2\text{e-}4$在损失和下游性能上的收敛速度都比$1\text{e-}4$快得多,而$3\text{e-}4$仅略优于$2\text{e-}4$。$3\text{e-}4$带来的边际收益伴随着在长期训练中不稳定性增加的风险,因此我们将$2\text{e-}4$选为我们的甜蜜点。
这些扫描有助于我们排除那些明显太高(发散)或太低(收敛缓慢)的学习率,但为每种模型尺寸运行扫描会很快变得昂贵,更重要的是,正如我们前面所述,它没有考虑到计划的训练Token数量。这就是扩展定律(Scaling Laws)变得无价的地方。
但在我们深入研究超参数的扩展定律之前,让我们讨论另一个与学习率相互作用的关键超参数:批次大小(Batch Size)。
4.2.3 批次大小(Batch Size):效率与数据效率的平衡艺术
批次大小是在更新模型权重之前处理的样本数量。它直接影响训练效率和最终模型性能。如果你的硬件和训练堆栈能够很好地跨设备扩展,增加批次大小可以提高吞吐量。
但是,超过某个点后,更大的批次开始损害数据效率:模型需要更多的总Token才能达到相同的损失。发生这种情况的临界点被称为临界批次大小(Critical Batch Size) (McCandlish et al., 2018)。
- 低于临界批次大小增加批次:在增加批次大小并重新调整学习率后,你可以用相同数量的Token达到与较小批次运行相同的损失,没有数据浪费。
- 高于临界批次大小增加批次:更大的批次开始牺牲数据效率;现在需要更多的总Token才能达到相同的损失(因此需要更多的钱),即使由于更多的芯片处于繁忙状态,挂钟时间(Wall-Clock Time)下降了。
让我们试着直观地理解为什么我们需要重新调整学习率,以及如何计算临界批次大小的估算值。
当批次大小增长时,每个Mini-Batch的梯度是真实梯度的更好估计,因此你可以安全地采取更大的步骤(即增加学习率),并在更少的更新次数内达到目标损失。问题是如何进行缩放。
对$B$个样本进行平均
- 批次梯度:$\tilde{g}B = \frac{1}{B} \sum{i=1}^B \tilde{g}^{(i)}$
- 均值保持不变:$E[\tilde{g}_B] = g$
- 协方差缩小:$Cov(\tilde{g}_B) = \frac{\Sigma}{B}$
SGD参数更新是:
\[\Delta w = - \eta \tilde{g}_B\]这个更新的方差与$\text{Var}(\Delta w) \propto \eta^2 \frac{\Sigma}{B}$成正比。因此,为了保持更新方差大致恒定,如果你将批次大小缩放$k$倍,你就需要将学习率缩放$\sqrt{k}$倍。
所以,假设你已经计算出了你的最优批次大小和学习率,并且你发现增加到临界批次大小是可行的并且提高了吞吐量,你还需要相应地调整最优学习率。
\[B_{\text{critical}} \rightarrow k B_{\text{optimal}} \implies \eta_{\text{critical}} \rightarrow \sqrt{k} \eta_{\text{optimal}}\]对于AdamW或Muon这样的优化器来说,随着批次大小增长,学习率进行平方根缩放是一个有用的经验法则,但这也会取决于优化器。例如,使用AdamW时,它与$\beta_1 / \beta_2$存在相互作用,可能引入非常不同的行为。
一个实用的替代方案是短期分支训练:保持一次运行使用原始批次,开始第二次运行使用更大的批次和重新缩放后的学习率(Rescaled LR),并且只有当重新缩放后两个损失曲线对齐时才采用更大的批次 (Merrill et al., 2025)。在这篇论文中,他们在切换批次大小时重新预热学习率并重置优化器状态。他们还设置了一个容忍度和时间窗口来决定损失是否“匹配”,这两个旋钮都是凭经验选择的。他们发现$B_{\text{simple}}$估算(它也有噪音)低估了“实际”的临界批次大小。这为你提供了一个快速、低风险的检查,确保新的批次/学习率对保持了训练动态。
临界批次大小不是固定的,它会随着训练的进展而增长。
- 在训练早期,模型正在进行大梯度步骤,所以$|g|^2$很大,这意味着$B_{\text{simple}}$很小,因此模型有一个较小的临界批次大小。
- 随后,随着模型更新的稳定,更大的批次变得更有效。
这就是为什么一些大规模训练不保持批次大小恒定,而是使用我们称之为批次大小预热(Batch Size Warmup)的原因。例如,DeepSeek V3在前约4690亿Token中以12.6M的批次大小开始,然后增加到62.9M,用于训练的剩余部分。像这样的批次大小预热调度与学习率预热的目的一样:它使模型在梯度噪声规模增长时保持在效率前沿,从而在整个过程中保持稳定和高效的优化。
另一种有趣的方法是将损失作为临界批次大小的代理。Minimax01使用了这种方法,在最后阶段他们用128M的批次大小进行训练!这有点不同,因为他们没有增加学习率,所以他们的批次大小调度充当了学习率衰减调度。
正如上面所提到的,选择批次大小和学习率的起始点的一种方法是通过扩展定律(Scaling Laws)。让我们看看这些扩展定律是如何工作的,以及它们如何根据你的计算预算来预测这两个超参数。
4.2.4 超参数的扩展定律(Scaling Laws for Hyperparameters)
最优学习率和批次大小不仅仅与模型架构和大小有关,它们还取决于我们的计算预算(Compute Budget),这结合了模型参数数量和训练Token数量两个因素。在实践中,这两个因素相互作用,共同决定了我们的更新应该有多激进或多保守。这就是 扩展定律(Scaling Laws) 发挥作用的地方。
扩展定律建立了经验关系,描述了当我们增加训练规模时(无论是通过更大的模型还是更多的数据),模型性能如何演变(有关完整历史,请参见本章末尾的“扩展定律”部分)。
但扩展定律也可以帮助我们预测如何随着训练规模的扩大而调整关键超参数,例如学习率和批次大小,正如DeepSeek和Qwen 2.5最近的工作所做的那样。这为我们提供了有原则的默认值,而不是完全依赖于超参数扫描。
为了在这种情况下应用扩展定律,我们需要一种量化训练规模的方法。标准指标是计算预算,表示为$C$,以FLOPs(浮点运算次数)为单位进行测量,可以近似为:
\[C \approx 6 \times N \times D\]其中,$N$是模型参数的数量(例如,1B = 1e9),$D$是训练Token的数量。这通常以FLOPs(浮点运算次数)来衡量,这是一种硬件无关的方式来量化实际完成了多少计算。但如果FLOPs感觉太抽象,你可以这样想:训练一个1B参数模型在100B Token上消耗的FLOPs,大约是训练一个2B模型在100B Token上,或一个1B模型在200B Token上的1/2。
常数6来自于经验估计,即训练一个Transformer大约需要每参数每Token 6个FLOPs。
现在,这与学习率有什么关系?我们可以推导出将最优学习率和批次大小预测为总计算预算 ($C$)的函数的扩展定律。它们有助于回答以下问题:
- 当我从1B扩展到7B参数时,学习率应该如何改变?
- 如果我将训练数据加倍,我应该调整学习率吗?
让我们通过DeepSeek使用的方法来看看这是如何工作的:
首先,我们选择我们的学习率调度方案,理想情况下是WSD,因其灵活性。然后,我们在一系列计算预算下(例如,1e17, 5e17, 1e18, 5e18, 1e19, 2e19 FLOPs)用不同的批次大小和学习率组合来训练模型。简单来说:我们用不同的Token数量训练不同的模型大小,并测试不同的超参数设置。这就是WSD调度方案的优势所在:我们可以将同一次训练运行扩展到不同的Token数量,而无需重启。
对于每种设置,我们对学习率和批次大小进行扫描,并识别出那些能带来近乎最优性能的配置,这通常定义为在最佳验证损失(在独立的验证集上计算,其分布与训练集相似)的一个很小的裕度内(例如,0.25%)。
每个近乎最优的配置都为我们提供了一个数据点——一个 (计算预算$C$,最优学习率$\eta$) 或 ($C$,最优批次大小$B$)的元组。当在对数-对数(log-log)尺度上绘制时,这些关系通常遵循幂律(Power-Law)行为,呈现为近似的直线。通过拟合这些数据点,我们可以提取出描述最优超参数如何随计算预算演变的扩展定律。
这个过程中的一个重要发现是:对于固定的模型大小和计算预算,性能在广泛的超参数范围内保持稳定。这意味着存在一个宽泛的最佳点,而不是一个狭窄的最优点。我们不需要找到完美的值,只需一个足够接近的值即可,这使得整个过程更加实用。
这些结果背后的核心直觉是:随着训练变得更大、更长,我们希望更稳定的更新(因此,学习率更小),以及更高效的梯度估计(因此,批次大小更大)。
这些扩展定律为我们提供了学习率和批次大小的起点。但我们的目标不是“每个梯度的最优样本数”,而是“在我们时间和GPU天数约束下可达到的更低损失”,同时仍然从每个Token中提取出完整的信号。
在实践中,你可能能够将批次大小增加到超出预测的最优批次大小,以显著提高吞吐量而不会明显损害数据效率,直到我们前面讨论的临界批次大小。
4.2.5 SmolLM3的最终选择
那么,我们最终为SmolLM3使用了什么呢?
在启动SmolLM3之前的消融实验阶段,我们在一个1B模型上用1000亿Token比较了AdamW、AdEMAMix和Muon。
- Muon在适当调优后可以超越AdamW,但对学习率敏感,且容易发散。
- AdeMaMix不那么敏感,并且达到了与Muon相似的损失。
- AdamW是最稳定的,但达到的最终损失比经过调优的替代方案要高。
然而,当我们扩展到30亿参数时,我们遇到了Muon和AdeMaMix更频繁的发散。这可能是由于我们在完成消融实验后发现的一个并行化Bug,尽管我们尚未证实这一点。最终,我们决定使用AdamW(beta1: 0.9, beta2: 0.95),权重衰减为0.1,梯度裁剪为1。总而言之,是一个非常普通(Vanilla)的设置。
对于学习率调度,我们选择了WSD。我们在SmolLM2中成功使用过它,并且它被证明是我们在易用性、总训练时长灵活性以及运行中途衰减实验能力方面做出的最佳决策之一。我们运行了学习率扫描,并最终确定为2e-4。
对于全局批次大小,我们测试了从200万到400万Token的值,但发现对损失或下游性能的影响微乎其微,因此我们选择了236万Token——这个大小为我们带来了最佳的吞吐量。
4.2.6 优化器选择准则 (Rules of Engagement)
一句话总结:平衡探索与执行,“完成”比“完美”更重要。
我们已经讨论了很多关于“做什么”(优化器、学习率、批次大小)的问题,但 “怎么做” 同样重要。我们如何决定什么值得实验?我们如何安排时间?我们何时应该停止探索,直接开始训练?
- 明智地分配你在探索和执行之间的时间。花费数周时间去完善一种新方法带来的微小改进,远不如将同样的算力投入到更好的数据策划或更彻底的架构消融中有价值。根据我们的经验,尽管这可能会让架构爱好者失望,但最大的性能提升通常来自数据策划。
- 当有疑问时,选择灵活性和稳定性,而非峰值性能。如果两种方法表现同样出色,选择提供更多灵活性或具有更好实现成熟度和稳定性的那一种。像WSD这样允许我们延长训练或运行中途实验的学习率调度,比一个可能收敛得略好但僵化的调度更有价值。
- 知道何时停止优化并开始训练。总有下一个超参数需要调优,总有下一个优化器可以尝试。为探索设定一个截止日期并遵守它——我们真正完成训练的模型,永远胜过我们从未开始的那个完美模型。
- 完美是优秀的敌人(Perfect is the enemy of good),尤其是在我们面对有限的计算预算和截止日期时。
4.3 扩展定律:多少参数?多少数据?
在深度学习的早期,当语言模型(以及训练它们的集群)还不够“大”时,训练运行通常不会受到计算资源的严重限制。训练模型时,你只需选择能装入硬件的最大模型和最大批次大小,然后一直训练,直到模型开始过拟合或你用完了数据。然而,即使在早期,人们也已经意识到规模是有益的——例如,Hestness et al.在2017年提供了一套全面的结果,表明训练更大、更久的模型能产生可预测的收益。
在大型语言模型时代,我们总是受到计算资源的限制。为什么?
Kaplan et al.的《神经网络语言模型的扩展定律》(Scaling Laws for Neural Language Models)论文将早期关于可扩展性的概念形式化了,该研究表明,语言模型性能在多个数量级的规模上具有惊人的可预测性。这引发了语言模型在大小和训练时长上的爆炸式增长,因为它提供了一种准确预测通过增加规模能带来多少性能提升的方法。因此,构建更好语言模型的竞赛变成了用不断增长的计算预算,在更大量的数据上训练更大模型的竞赛,语言模型的开发也迅速变得受计算资源限制。
当面临计算限制时,最重要的问题是:应该训练更大的模型,还是用更多的数据进行训练?
令人惊讶的是,Kaplan et al.的扩展定律表明,将更多计算资源分配给模型规模比之前的最佳实践更有优势——这激励了例如用相对适度的Token预算(3000亿Token)来训练庞大的GPT-3模型(1750亿参数)。
在重新审视时,Hoffman et al.发现了Kaplan et al.方法中的一个方法论问题,最终重新推导了扩展定律,建议将更多计算资源分配给训练时长。这表明,例如,GPT-3的1750亿参数模型的计算最优训练应该消耗3.7万亿Token!
这一发现(通常被称为Chinchilla定律)将该领域从 “让模型更大” 转向了 “将它们训练得更久、更好”。
然而,大多数现代训练仍然没有严格遵循Chinchilla定律,因为它们有一个缺点:它们旨在预测在给定计算预算下能获得最佳性能的模型大小和训练时长,但它们未能考虑到更大的模型在训练后更昂贵的事实。换句话说,我们实际上可能更愿意用给定的计算预算来将一个较小的模型训练更长时间——即使这在计算上不是“最优”的——因为这将使推理成本更便宜 (Sardana et al., de Vries)。如果我们预计一个模型将会有大量的推理使用(例如,因为它将作为开源模型发布 🤗),情况就可能如此。
最近,这种“过度训练(Overtraining)”模型,即超过扩展定律建议的训练时长的做法,已经成为标准实践,也是我们在开发SmolLM3时采取的方法。
虽然扩展定律为给定特定计算预算的模型大小和训练时长提供了建议,但选择过度训练意味着你必须自己决定这些因素。对于SmolLM3,我们从选择30亿参数的目标模型大小开始。基于近期类似规模的模型,如Qwen3 4B、Gemma 3 4B和Llama 3.2 3B,我们认为3B大小足以具备有意义的能力(例如推理和工具调用),但又足够小以实现超快速的推理和高效的本地使用。
为了选择训练时长,我们首先注意到最近的模型被极度过度训练——例如,前面提到的Qwen3系列据称训练了36万亿Token!因此,训练时长通常由可用的计算资源决定。我们预计使用384块H100s一个月时间作为训在11万亿Token上进行训练的预算(假设MFU约为30%)。
扩展定律的价值
尽管存在这些偏差,扩展定律仍然具有实际价值。它们为实验设计提供基线,人们经常使用Chinchilla最优设置来获取消融实验的信号,并且它们有助于预测一个模型大小是否能达到目标性能。正如de Vries在这篇博客中指出的,通过缩小模型大小,你可以达到一个临界模型大小:达到给定损失所需的最小容量,低于这个容量,你将开始获得递减的回报。
现在我们已经确定了模型架构、训练设置、模型大小和训练时长,我们需要准备两个关键组件:将教会我们模型的数据混合配比,以及将可靠地训练它的基础设施。SmolLM3的架构设定为30亿参数,我们需要策划一个能带来强大多语言、数学和代码性能的数据混合配比,并建立一个足够稳健以支持11万亿Token训练的基础设施。将这些基础工作做好至关重要,即使是最好的架构选择也无法从糟糕的数据策划或不稳定的训练系统中拯救我们。
5. 数据策划的艺术
想象一下这样的场景:你花了数周时间,精心打磨模型架构、反复调整超参数,并搭建起一套极其稳健的训练基础设施。你的模型完美地收敛了,损失曲线一路下降,然后……它却无法编写一段连贯的代码,在基础的数学问题上举步维艰,甚至可能在句子中途突然切换语言。究竟是哪里出了问题?
答案通常就藏在数据里。
当我们沉醉于各种花哨的架构创新和超参数扫描时,往往忽略了一个事实:数据策划(Data Curation),才是最终决定我们的模型是成为一个真正有用的工具,还是仅仅沦为又一个昂贵实验的关键。这正是随机的网络爬虫数据与经过精心筛选、能够真正教会模型所需技能的高质量数据集之间的本质区别。
如果说模型架构定义了你的模型如何学习,那么数据就决定了它学什么。再多的计算资源或再精妙的优化器调优,也无法弥补在错误内容上进行训练所造成的损失。
此外,搞定训练数据不仅仅是拥有好的数据集那么简单,更关键在于如何调配出一个恰当的混合配比(Mixture):既要平衡相互冲突的目标(例如,强大的英语能力 vs. 稳健的多语言能力),又要根据我们的性能目标来调整数据比例。这个过程与其说是寻找一个放之四海而皆准的最佳配方,不如说是提出正确的问题,并设计具体的计划来回答它们:
- 我们希望模型在哪些方面表现出色?
- 每个领域有哪些最优质的数据集?我们该如何将它们组合起来?
- 对于我们的目标训练规模,我们是否拥有足够的高质量数据?
本节旨在通过原则性的方法、系统性的消融实验,再加上一点点“炼金术”,来驾驭这些复杂的问题,将一堆优秀的数据集,转变为一个卓越的训练混合配比。
5.1 什么是好的数据混合配比?为什么它最重要?
我们对语言模型寄予厚望:它们应该能帮助我们写代码、提供建议、回答几乎所有问题、调用工具完成任务等等。然而,像网页这样丰富但庞杂的预训练数据源,并不能完全覆盖这些任务所需的全部知识和能力。因此,近期的模型越来越依赖于针对特定领域(如数学和代码)的专业化预训练数据集。
(我们过去在数据集策划方面投入了大量精力,但对于SmolLM3,我们主要使用了现有的数据集。要了解更多关于数据集策划的信息,请参阅我们关于构建FineWeb、FineWeb-Edu、FineWeb2、Stack-Edu和FineMath的系列报告。)
5.1.1 数据混合的非直观性
如果你是训练语言模型的新手,可能会觉得找到一个好的数据混合配比很简单:确定目标能力,为每个领域收集高质量的数据集,然后把它们混合在一起就行了。
然而,现实要复杂得多,因为不同的领域可能会为了有限的训练预算而相互“竞争”。当专注于像编程这样的特定能力时,人们很自然地会想增加相关数据(如源代码)的比重。但是,增加一个数据源的权重,就意味着隐式地降低了所有其他数据源的权重,这可能会损害模型在其他场景下的通用能力。因此,在不同来源的数据集上进行训练,需要在各种下游能力之间找到一个微妙的平衡。
此外,在所有这些数据源和领域中,通常都存在一部分“高质量”数据,它们对于提升模型能力尤为关键。那么,为什么不干脆扔掉所有低质量数据,只在最高质量的数据上训练呢?对于SmolLM3这样需要11万亿Token的庞大训练预算来说,进行这种极端过滤将导致数据被重复使用多次。而先前的研究表明,这种重复可能是有害的 (Muennighoff et al., 2025)。因此,我们理想的策略应该是在最大化模型性能的同时,能够同时利用高质量和较低质量的数据。
为了平衡不同来源的数据并充分利用高质量数据,我们需要精心设计混合配比:即来自每个数据源的训练文档所占的相对比例。由于模型在某个特定任务上的性能,很大程度上取决于它在该任务上看到的数据量,调整混合权重为我们提供了一种直接调控模型跨领域能力的手段。因为这些权衡是高度依赖于具体模型且难以预测的,所以消融实验至关重要。
但混合配比在整个训练过程中并非一成不变。通过在训练过程中动态调整混合配比——我们称之为多阶段训练(Multi-Stage Training)或课程学习(Curriculum)——我们可以更好地利用高质量和较低质量的数据。
5.1.2 训练课程的演变
在大型语言模型训练的早期,标准做法是在整个训练过程中固定一个单一的数据混合配比。像GPT-3和早期版本的Llama都是在从一而终的静态混合配比上训练的。
然而,最近该领域已转向多阶段训练 (Allal et al., 2025),即数据混合配比在训练过程中会发生变化。其主要动机在于:语言模型的最终行为,受到其在训练末期所接触数据的强烈影响 (Y. Chen et al., 2025b)。这一洞见催生了一种实用的策略:在训练早期增加更丰富、更多样化数据源的权重,而在接近尾声时,则混入更小、更高质量的数据源。
一个常见的问题是:如何决定何时改变混合配比?虽然没有通用的规则,但我们通常遵循以下原则:
- 性能驱动的干预: 密切监控关键基准的评测指标,并根据特定的能力瓶颈来调整数据集混合。例如,如果数学性能停滞不前,而其他能力仍在持续提升,这就是一个引入更高质量数学数据的明确信号。
- 为后期阶段保留高质量数据: 小而精的高质量数学和代码数据集,在退火阶段(即学习率开始衰减的最后阶段)引入时,其影响最大。
现在我们已经明确了为什么混合配比如此重要,以及课程学习是如何运作的,接下来让我们讨论如何将这两者结合起来进行系统性的调整。
5.2 消融实验设置:如何系统地测试数据“配方”
在测试数据混合配比时,我们的方法与运行架构消融实验类似,但有一个关键区别:我们尽量在目标模型规模上运行它们。小型和大型模型具有不同的学习容量,例如,一个非常小的模型可能难以同时处理多种语言,而一个更大的模型则可以轻松吸收它们而不会牺牲其他方面的性能。因此,在过小的规模上运行数据消融实验,有得出关于最优混合配比的错误结论的风险。
对于SmolLM3,我们直接在3B模型上运行了我们的主要数据消融实验,使用了500亿和1000亿Token的较短训练周期。我们还使用了另一种消融设置:退火实验(Annealing Experiments)。我们没有用不同的混合配比从头开始训练,而是从主训练运行中取一个中间检查点(例如在7T Token处),然后用修改后的数据组合继续训练。这种方法使我们能够高效地测试用于多阶段训练(即在训练中途改变混合配比)的数据组合,并且在近期的工作中(如SmolLM2、Llama 3和Olmo 2)得到了广泛应用。
在评测方面,我们将我们的基准测试套件扩展到包括多语言任务,与我们的标准英语评测一起进行,以确保我们能够准确地评估不同语言比例之间的权衡。
近期的研究提出了一些自动寻找最优数据比例的方法,包括:
- DoReMi (Xie et al., 2023):使用一个小型代理模型来学习能够最小化验证损失的领域权重。
- Rho Loss (Mindermann et al., 222):根据保留集损失来选择单个训练样本,优先选择那些可学习、与任务相关且模型尚未完全掌握的样本。
- RegMix (Q. Liu et al., 2025):通过正则化回归来确定最优数据混合比例,平衡多个评测目标和数据领域的性能。
我们在过去的项目中尝试过DoReMi和Rho Loss,但发现它们倾向于收敛到一个大致反映数据集大小自然分布的比例,基本上是建议我们更多地使用我们拥有更多的数据。虽然这些方法在理论上很吸引人,但在我们的实践中,它们并没有胜过仔细的手动消融实验。目前,最先进的模型仍然依赖于通过系统性的消融实验和退火实验进行的手动混合调优,这也是我们为SmolLM3所采用的方法。
5.3 SmolLM3:策划数据混合(网络、多语言、数学、代码)
对于SmolLM3,我们的目标是构建一个能够熟练处理英语和其他多种语言,并在数学和代码方面表现出色的模型。这些领域——网络文本、多语言内容、代码和数学——在大多数LLM中都很常见,但我们在此描述的过程同样适用于你为小语种或特定领域(如金融或医疗保健)进行训练的情况。方法是相同的:识别优秀的候选数据集,运行消融实验,并设计一个能够平衡所有目标领域的混合配比。
(我们在此不讨论如何构建高质量的数据集,因为我们已经在早期的工作(FineWeb、FineWeb2、FineMath和Stack-Edu)中详细阐述了这一点。相反,本节重点关注我们如何将这些数据集组合成一个有效的预训练混合配比。)
5.3.1 建立在经过验证的基础上
在预训练数据方面,好消息是我们很少需要从零开始。开源社区已经为大多数常见领域构建了强大的数据集。有时我们需要创造一些新的东西——就像我们用Fine系列(FineWeb、FineMath等)所做的那样——但更多时候,挑战在于如何选择和组合现有的数据源,而不是重新发明轮子。
SmolLM2已经在1.7B参数规模下,为英语网络数据建立了一套强大的配方,并确定了我们能接触到的最好的数学和代码数据集。我们的目标是在此基础上扩展到30亿参数,同时增强某些能力:稳健的多语言能力、更强的数学推理和更好的代码生成。
5.3.2 英语网络数据:基础层
网络文本构成了任何通用LLM的支柱,但质量和数量同等重要。
从SmolLM2的经验中,我们知道FineWeb-Edu和DCLM是训练时最强大的开放英语网络数据集。它们共同为我们提供了5.1万亿Token的高质量英语网络数据。问题是:最佳的混合比例是什么?FineWeb-Edu有助于提升在教育和STEM基准上的表现,而DCLM则能改善常识推理能力。
遵循SmolLM2的方法,我们在我们的3B模型上,用1000亿Token运行了一次扫描,测试了20/80、40/60、50/50、60/40和80/20的比例(FineWeb-Edu/DCLM)。结果显示,将它们混合(大约60/40或50/50)得到了最佳的权衡。我们在3B模型上,用1000亿Token重新运行了SmolLM2论文中的相同消融实验,并得出了相同的结论。
我们为第一阶段(Stage 1)使用了50/50的比例。
我们还添加了其他数据集,如Pes2o、Wikipedia & Wikibooks和StackExchange。虽然这些数据集对性能没有显著影响,但我们包含它们是为了增加数据的多样性。
5.3.3 多语言网络数据
对于多语言能力,我们瞄准了另外5种语言:法语、西班牙语、德语、意大利语和葡萄牙语。我们从FineWeb2-HQ中选择了它们,总共为我们提供了6280亿Token。我们还以较小的比例包含了其他10种语言,如中文、阿拉伯语和俄语,目的不是为了在这些语言上达到顶尖性能,而是为了让社区能够轻松地在这些语言上对SmolLM3进行持续预训练。
关键问题是:我们的网络数据中应该有多少是非英语的?我们知道,模型在一种语言或领域中看到的数据越多,它在该语言或领域上的表现就越好。权衡来自于我们固定的计算预算:增加一种语言的数据,就意味着减少包括英语在内的其他语言的数据。
通过在3B模型上的消融实验,我们发现网络混合中12%的多语言内容达到了正确的平衡,在不降低英语基准性能的情况下提高了多语言性能。这符合SmolLM3的预期用途,其中英语仍将是主要语言。同样值得注意的是,我们只有6280亿Token的非英语数据,而英语数据则有5.1万亿Token,要再提高非英语内容的比例,就需要更多地重复使用多语言数据。
5.3.4 代码数据
我们第一阶段的代码数据来源是从The Stack v2和StarCoder2训练语料库中提取的:
- The Stack v2(16种语言),作为我们的基础,并按照StarCoder2Data的方式进行过滤。
- StarCoder2 GitHub Pull Requests,用于学习真实世界的代码审查和推理。
- Jupyter和Kaggle Notebooks,用于学习可执行的、分步的工作流程。
- GitHub Issues和StackExchange线程,用于学习围绕代码的上下文讨论。
Aryabumi et al. (2024)强调,代码不仅能提高模型的编码能力,还能提升自然语言推理和世界知识等方面的性能,并建议在训练混合中使用25%的代码。受此启发,我们以25%的代码比例开始了我们的消融实验。然而,我们观察到在英语基准(HellaSwag、ARC-C、MMLU)上性能显著下降。将代码比例降至10%后,我们没有看到与0%代码相比在英语基准上的改进,但我们还是包含了它,因为代码是模型的一项非常重要的能力。
我们推迟了添加Stack-Edu——我们对StarCoder2Data进行教育性过滤后的子集——直到后期阶段,遵循了为最大化后期训练影响而分阶段引入高质量数据的原则。
5.3.5 数学数据
数学数据的处理遵循了与代码类似的理念。在早期阶段,我们使用了更大、更通用的数据集 FineMath3+ 和 InfiWebMath3+ 。在后期阶段,我们 增加了FineMath4+ 和 InfiWebMath4+ 的采样比例,并引入了新的高质量数据集:
- MegaMath (Zhou et al., 2025)
- 指令和推理数据集,如OpenMathInstruct (Toshniwal et al., 2024)和OpenMathReasoning (Moshkov et al., 2025)
我们在第一阶段使用了3%的数学数据,在FineMath3+和InfiWebMath3+之间平均分配。由于我们只有540亿Token的数学数据可用,并且估计第一阶段需要8T到9T Token的训练量,使用超过3%的数学数据将需要在该数据集上进行超过5个周期的训练。
5.3.6 为新阶段寻找正确的混合配比
虽然我们通过从头开始的消融实验来确定第一阶段的混合配比,但为了测试新阶段的新数据集(在我们的案例中是两个新阶段),我们使用了退火消融实验:我们在大约7T Token处(第一阶段的后期)取一个检查点,并用以下设置运行了500亿Token的退火实验:
- 40% 基线混合: 我们一直使用的第一阶段混合配比。
- 60% 新数据集: 我们想要评估的候选数据集。
例如,为了测试MegaMath是否会提高我们的数学性能,我们运行了40%的第一阶段混合(保持75/12/10/3的领域分割)和60%的MegaMath。
随着我们的数据经过精心策划并通过消融实验验证了混合配比,我们准备好踏上真正的训练之旅。接下来的章节将讲述SmolLM3长达一个月的训练运行故事:准备工作、意想不到的挑战以及在此过程中学到的教训。
6. 训练马拉松
恭喜你,坚持到了这一步!真正的乐趣,现在才刚刚开始。
此时,我们已经万事俱备:一套经过反复验证的架构、一份最终确定的数据混合配比,以及一组精心调优的超参数。剩下的,就是配置好基础设施,然后按下那个令人心跳加速的“训练”按钮。
对于SmolLM3,我们在384块H100 GPU(48个节点)上进行了近一个月的训练,总共处理了11万亿(11T)Token。本节将带你身临其境地体验一次漫长的训练运行中实际会发生什么:从起飞前的检查清单,到不可避免的意外状况,再到我们如何保持训练的稳定。你将亲眼见证,为何严谨的消融实验和可靠的基础设施是如此至关重要。关于GPU硬件、存储系统和优化吞吐量的技术细节,我们将在最后一章详细阐述。
我们的团队已经无数次经历这个过程:从StarCoder和StarCoder2,到SmolLM、SmolLM2,再到现在的SmolLM3。然而,每一次运行都是独一无二的。即使你已经训练了十几个模型,每一次新的启动,都会以一种全新的方式给你带来“惊喜”。本节旨在为你提供一份实战指南,让你为这些“惊喜”做好充分准备。
6.1 起飞前检查清单:在按下“训练”按钮前,务必核实这些
在按下“训练”按钮之前,我们会逐一核对一份检查清单,以确保整个流程能够端到端地顺利运行:
基础设施准备情况:
- Slurm预留: 如果你的集群支持Slurm预留,请务必使用它。对于SmolLM3,我们在整个运行期间都 확보了一个固定的48节点预留。这意味着没有排队延迟、吞吐量稳定,并且能够随着时间的推移持续跟踪节点健康状况。
- GPU压力测试: 在启动前对GPU进行压力测试(我们使用GPU Fryer和DCGM Diagnostics),以及时发现性能节流(throttling)或性能下降等问题。对于SmolLM3,我们发现了两块GPU存在节流问题,并在开跑前及时更换了它们。
- 避免存储膨胀: 我们的系统会将每个检查点上传到S3,然后在保存下一个检查点后立即删除本地副本。这样,我们就从不在高速的本地GPU SSD上存储超过一个检查点。
评估设置:
- 自动化: 评估工作非常耗时。即使所有脚本都已就绪,手动运行、记录结果和制作图表每次都会耗费数小时。因此,尽量将评估流程完全自动化,并确保在开跑前它们能够正确运行和记录。对于SmolLM3,每个保存的检查点都会自动在集群上触发一个评估作业,并将结果记录到Wandb和Trackio中。
检查点与自动恢复系统:
- 验证: 验证检查点是否能正确保存,以及训练作业是否可以从最新的检查点自动恢复而无需任何手动干预。在Slurm上,我们使用
--requeue选项,这样失败的作业就会自动重新排队启动,并从最近的检查点恢复。
指标记录:
- 确认: 确认你正在记录所有你关心的指标:评测分数、吞吐量(Token/秒)、训练损失、梯度范数、节点健康状况(GPU利用率、温度、内存使用),以及任何针对你此次运行的自定义调试指标。
训练配置健全性检查:
- 仔细检查: 仔细检查你的训练配置文件、启动脚本和Slurm提交命令。
6.2 规模化带来的“惊喜”
在为SmolLM3进行了广泛的消融实验后,我们准备好了进行全尺寸的训练。我们在1000亿Token上进行的3B模型消融实验看起来前景光明。与SmolLM2相比,架构上的更改(详见架构选择:GQA、NoPE、文档掩码、分词器)要么改进了性能,要么维持了现有水平,并且我们找到了一个很好的数据混合配比,平衡了英语、多语言、代码和数学的性能(参见数据策划的艺术)。我们将配置优化为在384块GPU(48个节点)上达到约30%的MFU。
我们准备好迎接那个大家伙:11万亿Token。就在那时,现实开始给我们抛出各种难题。
6.2.1 问题 #1 – 消失的吞吐量
启动后仅几小时,吞吐量就出现了骤降。这是一个巨大的跳水,并伴随着反复的急剧下跌。
这在任何消融实验中都没有发生过,那么究竟是什么变了?三件事:
- 硬件状态会随时间变化。在消融实验中表现良好的GPU,在持续的高负载下可能会出现故障,网络连接也可能退化。
- 训练数据集的大小。我们现在使用了完整的约24TB的训练数据集,而不是消融实验中的较小子集,尽管数据源本身是相同的。
- 训练步数。我们将总步数设置为11万亿Token对应的真实步数,而不是短暂的1000亿Token消融实验的范围。
其他一切都与吞吐量消融实验完全相同:节点数量、数据加载器配置、模型布局和并行化设置……
直觉上,数据集大小和总步数都不应该导致吞吐量下降,所以我们自然首先怀疑是硬件问题。我们检查了节点监控指标,发现巨大的吞吐量跳水与磁盘读取延迟的尖峰高度相关。这直接将我们的矛头指向了数据存储。
对于SmolLM3的24TB数据集,我们最初将数据存储在FSx (Weka)中。随着24TB训练数据的加入,再加上其他几个团队已经占用的存储空间,我们正在将Weka的存储推向极限。因此,它开始在训练中途将数据集的分片从热存储中驱逐出去,这意味着我们需要重新将它们加载回来,从而造成了停顿,这完美地解释了巨大的吞吐量跳水。更糟糕的是:我们没有办法将我们的数据集文件夹固定为热数据以供整个训练过程使用。
修复 #1 – 更换数据存储
由于无法在Weka中将我们的数据集文件夹固定为热数据,我们尝试更换存储方案。直接从S3流式传输数据速度太慢,所以我们决定将数据存储在每个节点的本地存储/scratch中。
但这又带来了一个新问题:如果一个节点宕机并被替换,新的替换GPU上将没有数据。用s5cmd从S3下载24TB数据需要3个小时。我们通过从另一个健康的节点使用fpsync进行复制,而不是通过S3,将时间缩短到了1小时30分钟。鉴于所有节点都在同一个数据中心,这样做速度更快。
尽管如此,每个节点故障都需要1小时30分钟的停机时间,并且需要立即手动将数据复制到新节点,这仍然很痛苦。最终使这个方案变得可以忍受的技巧是:在我们的Slurm预留中预留一个备用节点,并预先加载好数据集。如果一个节点宕机,我们立即用备用节点替换它,从而实现零恢复延迟。在空闲时,备用节点可以运行评估或开发作业,所以没有资源浪费。
这解决了问题 #1……至少我们当时是这么想的。
6.2.2 问题 #2 – 持续的吞吐量下降
即使在迁移到/scratch之后,零星的吞吐量下降仍在发生,尽管我们在硬件监控指标中没有发现任何异常。吞吐量下降变得更加剧烈。
我们仍然怀疑是硬件问题,因此决定在更少的节点上进行测试。用384块GPU,很有可能有什么东西会出故障。令人惊讶的是,我们在单个节点上复现了完全相同的吞吐量下降,无论我们测试哪个特定节点。这排除了硬件问题。
还记得我们从消融实验中改变的三件事吗?我们已经通过迁移到本地节点存储解决了数据存储问题。硬件现在也被排除了。只剩下一个变量:总步数。
我们通过回滚到更小的总步数(从300万步减少到3.2万步)来测试这一点,吞吐量下降变小了!更大的总步数产生了更剧烈、更频繁的下降。
为了验证这一点,我们运行了完全相同的配置,只将训练总步数从3.2万更改为320万。结果很清楚:较短的运行只有微小的吞吐量下降,而较长的运行则产生了更剧烈、更频繁的下降。
所以问题不是硬件,而是软件瓶颈,很可能出在数据加载器上!因为大多数其他训练组件处理每个批次的方式与总步数无关。
就在那时,我们意识到我们从未真正用nanotron的数据加载器进行过大规模的预训练。SmolLM2是用一个派生自Megatron-LM的数据加载器(TokenizedBytes)通过一个内部的nanotron包装器进行训练的,其吞吐量一直很稳定。对于SmolLM3,我们切换到了nanotron的内置数据加载器(nanosets)。
在深入研究其实现后,我们发现它天真地构建了一个巨大的索引,这个索引会随着每个训练步骤而增长。对于非常大的总步数,这导致了更高的共享内存占用,从而引发了吞吐量下降。
修复 #2 – 引入TokenizedBytes数据加载器
为了确认数据加载器确实是罪魁祸首,我们用我们的内部SmolLM2框架,使用TokenizedBytes数据加载器启动了相同的配置。没有下降。即使在48个节点上使用相同的数据集也是如此。
最快的解决方案:将这个数据加载器复制到nanotron中。下降消失了,吞吐量回到了目标水平。
我们准备好重新启动了……直到下一个难题出现。
6.2.3 问题 #3 – 嘈杂的损失
用新的数据加载器,我们没有了吞吐量下降,但损失曲线看起来更嘈杂。
nanosets一直产生更平滑的损失,这种差异让我们想起了一次古老的调试战争:几年前,我们在我们的预训练代码中发现了一个洗牌Bug,其中文档被洗牌,但一个批次内的序列却没有,导致了小的尖峰。
检查我们新的数据加载器证实了这一点:它正在按顺序从每个文档中读取序列。这对于短文件来说没问题,但对于像代码这样的领域,一个单一的、长的、低质量的文件可能会填满整个批次,并导致损失尖峰。
修复 #3 – 在序列级别进行洗牌
我们有两个选择:
- 更改数据加载器以进行随机访问(风险:更高的内存使用)。
- 离线预先洗牌分词后的序列。
由于启动运行的时间压力和我们的集群预留正在运行,我们选择了选项 #2,作为更安全、更快的修复。分词后的数据已经在每个节点上,所以在本地重新洗牌的成本很低(约1小时)。我们还为每个周期用不同的种子生成了洗牌后的序列,以避免在周期之间重复相同的洗牌模式。
6.2.4 启动,第二次尝试
到目前为止,我们已经有了:
- 稳定的吞吐量(
/scratch存储 + 备用节点策略) - 没有因总步数引起的下降(TokenizedBytes数据加载器)
- 干净的、序列级别的洗牌(每个周期离线预洗牌)
我们重新启动了训练。这一次,一切都稳定了下来。损失曲线平滑,吞吐量一致,我们终于可以专注于训练本身而不是四处救火了。
6.2.5 问题 #4 – 不尽如人意的性能
在修复了吞吐量和数据加载器问题后,我们再次启动了运行,并顺利地训练了前两天。吞吐量稳定,损失曲线看起来符合预期,日志中也没有任何问题。然而,在大约1万亿(1T)Token的标记点,评测结果揭示了一些意想不到的事情。
作为我们监控的一部分,我们会评估中间检查点并与历史运行进行比较。例如,我们有SmolLM2 (1.7B)的中间检查点,它是用类似的配方训练的,所以我们可以跟踪两个模型在相同训练阶段的进展。结果令人费解:尽管拥有更多的参数和更好的数据混合,但3B模型在相同训练点的表现比1.7B更差。损失仍在下降,基准分数也在提高,但改进的速度明显低于预期。
鉴于我们已经彻底测试了SmolLM3中引入的每一个架构和数据更改,并且验证了训练框架,两个训练设置之间只剩下少数几个未经测试的差异。最明显的是张量并行(Tensor Parallelism)。SmolLM2可以放在单个GPU上,并且没有使用TP进行训练,而SmolLM3需要TP=2才能装入内存。我们之前没有怀疑它或考虑测试它,因为在3B消融实验中使用了TP,并且它们的结果是合理的。
修复 #4 - 最终的修复
为了测试TP Bug的假设,我们用与SmolLM3完全相同的设置训练了一个1.7B模型——相同的架构更改(文档掩码、NoPE)、相同的数据混合、相同的超参数——分别在有和没有TP的情况下进行。差异立竿见影:TP版本始终比非TP版本有更高的损失和更低的下游性能。这证实了我们正在处理一个与TP相关的Bug。
然后我们详细检查了TP的实现,比较了TP和非TP运行的权重。问题原来是微妙但重要的:我们在所有TP等级(Ranks)上使用了相同的随机种子,而每个等级应该用不同的种子进行初始化。这导致了跨分片的权重初始化存在相关性,从而影响了收敛。这个影响不是灾难性的——模型仍然在训练和改进——但它引入了足够多的低效率,足以解释我们在大规模上观察到的差距。
一旦我们修复了种子,使得每个TP等级使用不同的种子,我们重复了消融实验,并确认TP和非TP运行现在在损失曲线和下游性能上都匹配了。为了确保没有其他隐藏问题,我们运行了额外的健全性检查:一个3B参数的SmolLM2风格(架构和数据上)的运行,以及一个单独的3B参数的SmolLM3运行,并将两者都与SmolLM2的检查点进行比较。结果现在与预期一致:1.7B SmolLM2的表现比3B SmolLM2变体差,而后者又低于SmolLM3的3B性能。
这次调试过程强化了我们在这篇博客前面概述的核心原则之一:
“一个坚实的消融设置的真正价值,并不仅仅在于帮助你构建一个好模型。当我们的主训练运行中不可避免地遇到问题时(无论我们准备得多么充分,问题总会发生),我们希望对自己做出的每一个决定都充满信心,并能快速识别哪些组件没有经过充分测试,可能是问题的根源。这种充分的准备工作可以节省大量的调试时间,并拯救我们的理智。没有什么比盯着一个神秘的训练失败,却不知道Bug可能藏在哪里更糟糕的了。”
因为我们训练中的所有其他组件都已得到验证,我们可以将TP指向为唯一可能的原因,并在发现性能差距的一天内修复了Bug。
至此,我们解决了自启动以来出现的一系列意外问题中的最后一个。事不过三,从那时起,剩下的一个月训练相对平稳,只是将数万亿Token变成一个完成的模型的稳定工作,偶尔因节点故障而中断重启。
6.3 坚持到底
正如上一节所示,从消融实验扩展到全尺寸预训练并非只是“即插即用”。它带来了意想不到的挑战,但我们成功地识别并解决了每一个问题。本节涵盖了大规模训练运行所需的基本监控设置和注意事项。我们将解决关键问题:在遇到问题后,你何时应该重启训练?如何处理在运行深入后出现的问题?哪些指标真正重要?你应该在整个训练过程中保持固定的数据混合吗?
6.3.1 训练监控:超越损失曲线
我们之所以能发现张量并行Bug,不是因为损失曲线(它看起来没问题),而是因为下游评测结果落后于预期。此外,拥有SmolLM2的中间检查点评测结果至关重要:它们为我们提供了一个健全性检查,让我们早期就发现3B模型没有走在正确的轨道上。
因此,如果你正在训练大型模型,请尽早开始运行下游评测。如果你正在与一个开源模型进行比较,可以询问作者是否能提供中间检查点,这些可以作为无价的参考点。
在基础设施方面,最重要的指标是吞吐量,以Token/秒为单位测量。对于SmolLM3,我们期望在整个运行过程中吞吐量稳定在13,500–14,000 Token/秒之间,任何持续的偏差都是一个危险信号。但仅有吞吐量是不够的:你还需要持续的硬件健康监控来预测和检测硬件故障。我们跟踪的一些关键指标包括:GPU温度、内存使用和计算利用率。我们将它们记录到Grafana仪表板,并为硬件异常设置了实时Slack警报。
6.3.2 修复后重启 vs. 动态修复
鉴于我们在1万亿Token后重启了我们的运行,一个重要的问题出现了:当出现问题时,你总是需要重启吗?答案取决于问题的严重程度和根本原因。
在我们的案例中,TP种子Bug意味着我们从一开始就走错了路,我们一半的权重没有被正确初始化。模型的性能与SmolLM2相似,并在相似的点上停滞不前,这意味着我们最终可能会得到一个性能相同但训练成本几乎高出一倍的模型。重启是合理的。
然而,许多问题可以在运行中途进行修正,以避免浪费计算资源。最常见的问题涉及损失尖峰,这些训练损失的突然跳跃可能预示着小问题或发散。
正如Stas Bekman在《机器学习工程开放手册》中所说:“训练损失图就像心跳图——有好有坏,还有你应该担心的那些。”
损失尖峰分为两类:
- 可恢复的尖峰: 这些可以快速恢复(尖峰后立即)或缓慢恢复(需要更多训练步骤才能回到尖刺前的轨迹)。你通常可以继续训练。如果恢复非常缓慢,你可以尝试回滚到前一个检查点以跳过有问题的批次。
- 不可恢复的尖峰: 模型要么发散,要么在比尖刺前更差的性能上停滞不前。这些需要比简单地回滚到前一个检查点更重大的干预。
虽然我们不完全理解训练不稳定性,但我们知道它们在大规模时更频繁。常见的罪魁祸首,假设架构和优化器都保守,包括:
- 高学习率: 这些会在训练早期引起不稳定性,可以通过降低学习率来修复。
- 坏数据: 通常是可恢复尖峰的主要原因,尽管恢复可能很慢。这可能在训练深入时,当模型遇到低质量数据时发生。
- 数据-参数状态相互作用: PaLM (Chowdhery et al., 2022)观察到,尖峰通常是由特定数据批次和模型参数状态的组合引起的,而不仅仅是“坏数据”。从不同的检查点在相同的有问题批次上训练,并没有重现尖峰。
- 糟糕的初始化: OLMo 2最近的工作 (OLMo et al., 2025)表明,从缩放初始化切换到简单的正态分布(均值=0,标准差=0.02)可以提高稳定性。
- 精度问题: 虽然现在没有人再用FP16进行训练了,但BLOOM发现与BF16相比,它非常不稳定。
在尖峰发生前,就内置稳定性:
- 数据过滤和洗牌: 确保你的数据干净且充分洗牌可以防止尖峰。例如,OLMo 2发现,移除重复n-gram的文档(1-13 Token片段重复32次以上)显著降低了尖峰频率。
- 训练修改: Z-loss正则化可以防止输出Logits过大而不会影响性能。将嵌入层排除在权重衰减之外也有帮助。
- 架构更改: QK-Norm(在注意力前对查询和键投影进行归一化)已被证明是有效的。OLMo 2和其他团队发现它有助于稳定性,有趣的是,Marin团队发现它甚至可以在运行中途应用来修复发散问题。
当尖峰仍然发生时 - 损害控制:
即使有这些预防措施,尖峰仍然可能发生。以下是一些修复它们的选项:
- 跳过有问题的批次: 回滚到尖峰前并跳过有问题的批次。这是修复尖峰最常见的方法。Falcon团队 (Almazrouei et al., 2023)跳过了10亿Token来解决他们的尖峰,而PaLM团队 (Chowdhery et al., 2022)发现跳过尖峰位置周围200-500个批次可以防止复发。
- 收紧梯度裁剪: 临时降低梯度范数阈值。
- 应用架构修复: 例如QK-Norm,如Marin所做。
我们已经探讨了规模化挑战,从吞吐量下降到TP Bug,以及早期发现问题的监控实践,以及预防和修复损失尖峰的策略。让我们通过讨论多阶段训练如何增强模型的最终性能来结束本章。
6.4 中途训练
现代LLM预训练通常涉及具有不同数据混合的多个阶段,通常随后是扩展上下文长度的最后阶段。例如,Qwen3 (A. Yang, Li, et al., 2025)使用了三阶段方法:在30T Token上,以4k上下文进行通用阶段;一个推理阶段,使用5T更高质量的Token,强调STEM和编码;最后是一个长上下文阶段,在32k上下文长度上使用数百亿Token。SmolLM3遵循类似的理念,有计划的干预以引入更高质量的数据集和扩展上下文,同时根据性能监控进行反应性调整。
正如我们在数据策划部分解释的,数据混合不必在整个训练过程中保持不变。多阶段训练允许我们随着训练的进展策略性地改变数据集比例。一些干预从一开始就计划好了:对于SmolLM3,我们知道我们将在第二阶段引入更高质量的FineMath4+和Stack-Edu,然后在最终衰减阶段添加策划的Q&A和推理数据。其他干预是反应性的,由训练期间的性能监控驱动。例如,在SmolLM2中,当我们发现数学和代码性能落后于我们的目标时,我们策划了全新的数据集(FineMath和Stack-Edu),并在训练中途引入了它们。这种灵活性——无论是遵循计划好的课程还是适应新出现的差距——使我们能够最大化我们计算预算的价值。
6.4.1 第二阶段和第三阶段的混合
我们的3个训练阶段以及我们在训练期间网络/代码/数学比例的进展如下。每个阶段的SmolLM3训练配置及其确切的数据权重可在此处获得。有关每个阶段背后的理由和组成的更多详细信息,请参阅数据策划部分。
- 第一阶段:基础训练(8T Token,4k上下文) 基础阶段使用我们的核心预训练混合:网络数据(FineWeb-Edu、DCLM、FineWeb2、FineWeb2-HQ)、来自The Stack v2和StarCoder2的代码,以及来自FineMath3+和InfiWebMath3+的数学。所有训练都在4k上下文长度下进行。
- 第二阶段:高质量注入(2T Token,4k上下文) 我们引入了更高质量的过滤数据集:用于代码的Stack-Edu,用于数学的FineMath4+和InfiWebMath4+,以及用于高级数学推理的MegaMath(我们还添加了Qwen Q&A数据、合成重写和文代码交错块)。
- 第三阶段:带推理和Q&A数据的学习率衰减(1.1T Token,4k上下文) 在学习率衰减阶段,我们进一步上采样高质量的代码和数学数据集,同时引入指令和推理数据,如OpenMathReasoning、OpenCodeReasoning和OpenMathInstruct。Q&A样本只是简单地连接起来,并用换行符分隔。
6.4.2 长上下文扩展:从4k到128k Token
上下文长度决定了你的模型可以处理多少文本,这对于分析长文档、保持连贯的多轮对话或处理整个代码库等任务至关重要。SmolLM3从4k Token开始训练,但我们需要扩展到128k以适应现实世界的应用。
为什么在训练中途扩展上下文?
- 从一开始就在长上下文上训练计算成本高昂,因为注意力机制随序列长度二次方扩展。
- 研究表明,在训练末期或持续预训练期间,用几十到一百亿Token扩展上下文,就足以达到良好的长上下文性能 (Gao et al., 2025)。
顺序扩展:4k→32k→64k
我们没有直接跳到128k。相反,我们分阶段逐渐扩展上下文,让模型在每个长度上都有时间适应,然后再进一步推进。我们运行了两个长上下文阶段:首先从4k到32k,然后从32k到64k(128k的能力来自推理时的外推,而不是训练)。
我们发现,为每个阶段重新开始一个500亿Token的学习率调度,比在主衰减阶段的最后1000亿Token中扩展上下文效果更好。在每个阶段,我们都运行了消融实验来寻找一个好的长上下文数据混合和RoPE theta值,并在Ruler基准上进行评估。
在此阶段,通常会上采样长上下文文档(如冗长的网页和书籍)以提高长上下文性能 (Gao et al., 2025)。我们运行了几个消融实验,上采样了书籍、文章,甚至遵循Qwen 2.5-1M的方法 (A. Yang, Yu, et al., 2025)用FineWeb-Edu和Python-Edu合成生成的用于检索和填空任务的文档。令人惊讶的是,我们没有观察到比仅使用第三阶段的基线混合有任何改进,后者在Ruler上已经与Llama 3.2 3B和Qwen 2.5 3B等其他SOTA模型具有竞争力。我们推测这是因为基线混合自然地包含了来自网络数据和代码的长文档(估计占Token的10%),并且使用NoPE也有帮助。
- RoPE ABF(带调整基础频率的RoPE):从4k到32k时,我们将RoPE theta(基础频率)增加到2M,从32k到64k时,我们将其增加到5M。我们发现使用像10M这样的更大值会略微提高RULER分数,但会损害像GSM8k这样的某些短上下文任务,所以我们保留了5M,它不影响短上下文。在此上下文扩展阶段,我们还借机进一步上采样了数学、代码和推理Q&A数据,并添加了几十万个ChatML格式的样本。
- YARN外推:达到128k 即使在64k上下文上训练后,我们仍希望SmolLM3能在推理时处理128k。我们没有在128k序列上进行训练(成本高得令人望而却步),而是使用了YARN (B. Peng et al., 2023),它允许模型外推到其训练长度之外。理论上,YARN允许序列长度增加四倍。我们发现使用64k检查点在128k上比使用32k检查点表现更好,证实了在更接近目标推理长度上训练的好处。然而,推到256k(从64k增加四倍)显示Ruler性能下降,因此我们建议将模型使用到128k。
至此,我们已经走过了SmolLM3的完整预训练旅程,从规划和消融实验到最终的训练运行,以及在此过程中所有幕后的挑战。
6.5 预训练收官
我们已经涵盖了很多内容。从帮助我们决定为什么和训练什么的训练指南针,到战略规划、验证每个架构选择的系统性消融实验,再到实际的训练马拉松,其中在规模化时出现了意外(吞吐量神秘崩溃、数据加载器瓶颈,以及一个迫使我们在1T Token时重启的微妙的张量并行Bug)。
那些光鲜的技术报告背后混乱的现实现在清晰可见:训练LLM与其说是关于架构创新和数据策划,不如说是关于纪律严明的实验和快速调试。
- 规划确定了什么值得测试。
- 消融实验验证了每个决定。
- 监控早期发现问题。
- 当事情不可避免地出问题时,系统性的去风险化会告诉你确切地应该从哪里找。
具体到SmolLM3,这个过程交付了我们最初设定的目标:一个在11万亿Token上训练的3B模型,在数学、代码、多语言理解和长上下文任务上具有竞争力,处于Qwen3模型的帕累托前沿。
随着我们的基础模型检查点保存完毕,训练完成,GPU终于冷却下来,我们可能会想称之为大功告成。毕竟,我们已经有了一个能很好地预测文本、在基准测试中获得高分,并展示了我们目标能力的模型。
但还不够。因为今天人们想要的是助手和编码智能体,而不是原始的下一个Token预测器。
这就是 后训练(Post-Training) 发挥作用的地方。而就像预训练一样,现实比论文所暗示的更加混乱。
7. 超越基础模型 — 2025 年的后训练
“预训练结束后,我们应该在一天内拿到一个 SFT 基线模型。”
预训练赋予了 SmolLM3 强大的原始能力,但还没等 GPU 完全冷却,我们就已经迈入了模型能力的新篇章:后训练(Post-Training)。这涵盖了监督式微调、强化学习、模型合并等一系列技术——所有这些都旨在弥合“一个能预测文本的模型”与“一个人们能真正使用的模型”之间的鸿沟。
如果说预训练是将知识粗暴地灌输到模型权重中,那么后训练就是将这种原始能力精雕细琢,使其变得可靠且可控。然而,就像预训练一样,那些光鲜亮丽的后训练论文,并没有记录下深夜里的意外:GPU 突然熔断、挑剔的数据混合比例,或者一个看似微不足道的聊天模板(Chat Template)决定,如何像蝴蝶效应般影响下游的基准测试结果。
在本节中,我们将向你展示,我们是如何驾驭后训练这个充满变数的世界,将 SmolLM3 从一个强大的基础模型,转变为一个业界顶尖的混合推理模型。
7.1 后训练指南针:为什么 → 做什么 → 怎么做
和预训练一样,后训练也需要一个清晰的指南针,以避免在无尽的研究和工程周期中迷失方向。以下是如何构建它:
-
为什么要进行后训练? 我们在预训练指南针中概述的三种动机——研究、生产和战略性开源——同样适用于后训练。例如,你可能正在探索强化学习(RL)是否能解锁现有模型的新推理能力(研究),或者你可能需要出于延迟考虑将一个大模型蒸馏成一个小模型(生产),又或者你发现某个特定应用场景中缺少强大的开源模型(战略性开源)。
区别在于,后训练是在现有能力的基础上进行雕琢,而不是从零开始创造。然而,在你启动GPU之前,请先问自己几个问题:
- 你真的需要进行后训练吗? 许多开源模型如今在广泛的任务上已能与专有模型相媲美。有些甚至可以通过量化在本地设备上以适度的计算资源运行。如果你只是想要一个通用的聊天助手,Hugging Face Hub上现成的模型可能已经足够满足你的需求。
- 你是否有高质量、领域特定的数据? 当你的目标是通用模型表现不佳的特定任务或领域时,后训练的价值才能最大化。用正确的数据,你可以调整模型,使其在你最关心的应用场景中产生更准确的输出。
- 你能衡量成功吗? 没有清晰的评估标准,你就无法判断后训练是否真的带来了提升。
- 后训练应该实现什么? 这取决于你的优先事项:
- 你想要一个严格遵循指令、很少偏离主题的模型吗?
- 一个能按需切换语气和角色的多功能助手?
- 一个能处理数学、代码或智能体问题的推理引擎?
- 一个能用多种语言进行对话的模型?
- 你将如何实现它? 这就是“配方”发挥作用的地方。我们将涵盖:
- 监督式微调 (SFT) 来灌输核心能力。
- 偏好优化 (PO) 来直接从人类或AI的偏好中学习。
- 强化学习 (RL) 来超越监督数据的限制,提炼出可靠性和推理能力。
- 数据策划来在多样性和质量之间取得正确的平衡。
- 评估来跟踪进展并及早发现性能回退。
这个指南针让后训练的混乱变得有章可循。“为什么”提供了方向,“做什么”设定了优先事项,而“怎么做”则将宏伟的蓝图变成了实际的训练循环。
让我们看看我们是如何为SmolLM3回答这些问题的:
- 为什么? 对我们而言,“为什么”很直接,因为我们有一个基础模型,需要在发布前进行后训练。同时,像Qwen3这样的混合推理模型越来越受欢迎,但展示如何训练它们的开放配方却很少。SmolLM3给了我们一个解决这两个问题的机会:为一个真实世界的应用准备一个模型,并贡献一个完全开放的配方,使其与Qwen3的1.7B和4B模型一起处于性能的前沿。
- 做什么? 我们着手训练一个为SmolLM3的优势量身定制的混合推理模型,其核心要求是推理质量在非英语语言中也应保持良好。而且由于现实世界的使用越来越多地涉及工具调用和长上下文工作流,这些也成为了我们后训练配方的核心要求。
- 怎么做? 这就是本章其余部分的内容 😀。
就像预训练一样,我们从基础开始:评估和基线,因为每个伟大的模型都始于一个小小的消融实验。但我们进行消融实验的方式有一个关键区别。在预训练中,“小”通常意味着更小的模型和数据集。在后训练中,“小”意味着更小的数据集和更简单的算法。我们几乎从不为消融实验使用不同的基础模型,因为模型的行为高度依赖于其自身,而且运行时间足够短,可以直接在目标模型上进行迭代。
让我们从许多模型训练者直到项目后期才愿意面对的话题开始:评估。
7.2 首要任务:评估先于一切
后训练的第一步——就像预训练一样——是确定正确的评估集。由于今天大多数LLM都被用作助手,我们发现,目标是打造一个“好用”的模型,远比追求像ARC-AGI这样抽象的“智能”基准更有意义。那么,一个好的助手需要具备哪些能力呢?至少,它应该能够:
- 处理模糊的指令
- 进行分步规划
- 编写代码
- 在适当时调用工具
这些行为依赖于推理、长上下文处理,以及数学、代码和工具使用技能的综合能力。小到3B甚至更小的模型都可以成为好用的助手,尽管性能通常在1B参数以下会急剧下降。
在Hugging Face,我们使用一个分层的评估套件,这呼应了我们在预训练消融实验部分详述的原则(单调性、低噪声、高于随机的信号、排序一致性)。
以下是评估后训练模型的多种方式:
能力评估(Capability Evals)
这类评估针对的是模型的基本技能,如推理能力以及具有竞争力的数学和编程能力。
- 知识: 我们目前使用GPQA Diamond (Rein et al., 2024)作为科学知识的主要评估基准。这个基准由研究生水平的多项选择题组成。对于小模型,它远未饱和,比MMLU及其同类提供了更好的信号,同时运行速度也更快。另一个很好的事实性测试是SimpleQA (Wei et al., 2024),尽管小模型由于知识有限,在这个基准上往往表现不佳。
- 数学: 为了衡量数学能力,今天大多数模型都在最新版本的AIME(目前是2025年版)上进行评估。MATH-500 (Lightman et al., 2023)对于小模型仍然是一个有用的健全性测试,但基本上已被推理模型饱和。要获得更全面的数学评估,我们推荐来自MathArena的评估。
- 代码: 我们使用最新版本的LiveCodeBench来跟踪编码能力。尽管它针对的是算法竞赛问题,我们发现LiveCodeBench上的改进确实能转化为更好的编码模型,尽管仅限于Python。SWE-bench Verified是一种更复杂的编码技能衡量标准,但对于小模型来说往往太难了,因此我们通常不考虑它。
- 多语言性: 不幸的是,在测试模型的多语言能力方面,选择并不多。我们目前依赖Global MMLU (Singh et al., 2025)来针对我们模型应该表现良好的主要语言,并包含MGSM (Shi et al., 222)作为多语言数学能力的测试。
综合任务评估(Integrated Task Evals)
这些评估测试的是更接近我们最终交付产品的能力:在半现实环境中的多轮推理、长上下文使用和工具调用。
- 长上下文: 最常用的长上下文检索测试是 “大海捞针”(Needle in a Haystack, NIAH) (Kamradt, 2023),其中一个随机事实(“针”)被放置在一个长文档(“草堆”)的某个地方,模型必须将其检索出来。然而,这个基准太肤浅,无法区分长上下文理解能力,因此社区开发了更全面的评估,如RULER (Hsieh et al., 2024)和HELMET (Yen et al., 2025)。最近,OpenAI发布了MRCR和GraphWalks基准,扩展了长上下文评估的难度。
- 指令遵循: IFEval (J. Zhou et al., 2023)是目前最流行的衡量指令遵循的评估,它使用自动评分来对照“可验证的指令”。IFBench (Pyatkin et al., 2025)是Ai2的一个新扩展,它包含了比IFEval更多样化的约束,并减轻了近期模型发布中出现的“刷榜(benchmaxxing)”现象。对于多轮指令遵循,我们推荐Multi-IF (He et al., 2024)或MultiChallenge (Sirdeshmukh et al., 2025)。
- 对齐: 衡量模型与用户意图对齐的程度通常通过人类标注员或公共排行榜(如LMArena)来完成。这是因为像自由形式生成、风格或整体帮助性这样的质量很难用自动化指标进行量化衡量。然而,在所有情况下,运行这些评估都非常昂贵,这就是为什么社区转而使用LLM作为人类偏好的代理。这种风格最流行的基准包括AlpacaEval (Dubois et al., 2025)、ArenaHard (T. Li et al., 2024)和MixEval (Ni et al., 2024),后者与LMArena上的人类Elo评分相关性最强。
- 工具调用: BFCL提供了对工具调用的全面测试,尽管它通常很快就会饱和。TAU-Bench (Barres et al., 2025)测试了模型在模拟客户服务环境中使用工具和解决用户问题的能力,也已成为一个流行的报告基准。
防止过拟合评估(Overfitting-Prevention Evals)
为了测试我们的模型是否对特定技能过拟合,我们在我们的集合中包含了一些鲁棒性或适应性评估,如GSMPlus (Q. Li et al., 2024),它对GSM8k (Cobbe et al., 2021)中的问题进行扰动,以测试模型是否仍然能解决类似难度的问题。
内部评估(Internal Evals)
尽管公共基准在模型开发过程中可以提供一些有用的信号,但它们无法替代实现你自己的内部评估来针对特定能力,或要求内部专家与你的模型互动。例如,对于SmolLM3,我们需要一个基准来评估模型是否能够进行多轮推理,所以我们实现了一个Multi-IF的变体来衡量这一点。
“感觉”评估和竞技场(Vibe Evaluations and Arenas)
同样,我们发现 “感觉测试”(vibe testing) 中间检查点(即与你的模型互动)对于揭示评测分数无法捕捉到的模型行为的微妙怪癖至关重要。正如我们稍后讨论的,感觉测试揭示了我们数据处理代码中的一个Bug,即所有系统消息都从语料库中被删除了!这也可以大规模地进行以衡量人类偏好,就像在流行的LMArena上一样。然而,众包的人类评估往往是脆弱的(偏爱谄媚和华丽的言辞而不是实际的有用性),因此将其视为一种低信号反馈很重要。
具体到SmolLM3,我们想要一个能够可靠地遵循指令并在数学和代码等流行领域良好推理的混合推理模型。我们还想确保我们保留了基础模型的多语言能力和长上下文检索能力。
这引导我们选择了以下评估集:
| 基准 | 类别 | 提示数量 | 指标 |
|---|---|---|---|
| AIME25 | 竞技数学 | 30 | avg@64 |
| LiveCodeBench (v4 验证, v5 最终发布) | 竞技编程 | 100 (268) | avg@16 |
| GPQA Diamond | 研究生水平推理 | 198 | avg@8 |
| IFEval | 指令遵循 | 541 | accuracy |
| MixEval Hard | 对齐 | 1000 | accuracy |
| BFCL v3 | 工具使用 | 4441 | mixed |
| Global MMLU (lite 验证) | 多语言问答 | 590,000 (6,400) | accuracy |
| GSMPlus (mini 验证) | 鲁棒性 | 10,000 (2,400) | accuracy |
| RULER | 长上下文 | 6,500 | accuracy |
让我们看几个来自每个评估的示例问题,以具体感受这些评估实际测试了什么。
注意领域的多样性如何确保我们在整个消融实验中全面地测试了模型能力的各个方面。
对于我们正在处理的3B模型规模,我们认为这些评估会给我们可操作的信号,运行比训练本身更快,并让我们相信改进是真实的,而不仅仅是抽样带来的噪声。我们还跟踪了我们的预训练评估(完整列表见消融实验部分),以确保我们没有在基础模型性能上过多地回退。
7.2.1 评估准则 (Rules of Engagement)
让我们用一些我们从评估数千个模型中获得的来之不易的教训来总结本节:
- 在模型开发期间,使用小子集来加速评估。例如,LiveCodeBench v4与v5高度相关,但运行时间只有一半。或者,使用像tinyBenchmarks (Polo et al., 2024)这样的方法,它旨在找到可靠匹配完整评估的最小提示子集。
- 对于推理模型,从计分的输出中剥离思维链(Chain-of-Thought)。这消除了假阳性,也直接影响了像IFEval这样的基准,后者会惩罚违反“写一首50词以内的诗”等约束的响应。
- 如果一个评估使用LLM作为裁判,请固定裁判及其版本,以便进行苹果对苹果的比较。更好的是,使用一个开源模型,这样即使提供商弃用了裁判模型,评估也是可复现的。
- 警惕基础模型中的污染。例如,大多数在AIME 2025之前发布的模型表现远差于AIME 2024,这表明存在一些刷榜行为。
- 如果可能,将消融实验中使用的任何东西都视为验证集,而不是测试集。这意味着为最终模型报告保留一组未见过的基准,类似于Tulu3评估框架 (Lambert et al., 2025)。
- 始终包含一小部分关于你自己的数据和任务的 “感觉评估”,以捕获对公共套件的过拟合。
- 对于问题数量较少(通常少于约2k)的评估,抽样k次并报告avg@k准确率。这对于减轻可能导致开发过程中错误决策的噪声很重要。
- 在实施一个新的评估时,确保你能够复现几个已发布模型的结果(在一定误差范围内)。如果做不到这一点,以后如果需要修复实现并重新评估许多检查点,将会浪费大量时间。
- 当有疑问时,总是回到评估数据,特别是检查你正在用什么提示你的模型。
有了评估在手,是时候训练一些模型了!在此之前,我们首先需要选择一个后训练框架。
7.3 行业工具
每个后训练配方背后都有一个支持大规模实验的框架和库的工具箱。每个框架都带来了自己的一套支持的算法、微调方法和可扩展性特性。下表总结了主要的支持领域,从监督式微调 (SFT)到偏好优化 (PO)和强化学习 (RL):
| 框架 | SFT | PO | RL | 多模态 | 全量微调 (FullFT) | LoRA | 分布式 |
|---|---|---|---|---|---|---|---|
| TRL | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Axolotl | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| OpenInstruct | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ |
| Unsloth | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| vERL | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Prime RL | ✅ | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ |
| PipelineRL | ❌ | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ |
| ART | ❌ | ❌ | ✅ | ❌ | ❌ | ✅ | ❌ |
| TorchForge | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ | ✅ |
| NemoRL | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ |
| OpenRLHF | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ |
这里,FullFT指的是全量微调,其中所有模型参数都在训练期间更新。LoRA代表低秩适应,一种参数高效的方法,只更新小的低秩矩阵,同时保持基础模型冻结。多模态指的是是否支持训练文本以外的模态(例如图像),分布式表示是否可以在多于一个GPU上训练模型。
在Hugging Face,我们开发和维护TRL,所以它是我们的首选框架,也是我们用来后训练SmolLM3的框架。
7.3.1 为什么还要费心使用框架?
有一类研究人员喜欢抱怨使用训练框架,并认为你应该总是从头开始实现一切。这里的隐含主张是,“真正”的理解只来自于重新实现每一个RL算法,手动编码每一个分布式训练原语,或拼凑一个一次性的评估工具。
但这种立场忽略了现代研究和生产的现实。以RL为例。像PPO和GRPO这样的算法是出了名的难以正确实现 (Huang et al., 2024),在归一化或KL惩罚中的微小错误可能导致数天的计算和努力浪费。
同样,尽管写一个单文件实现的某些算法很诱人,但那个脚本能从1B扩展到100B+参数吗?
框架之所以存在,正是因为基础知识已经得到很好的理解,无休止地重新发明它们是对时间的低效利用。这并不是说低级修补没有价值。从头开始实现一次PPO是一个很好的学习练习。在没有框架的情况下编写一个玩具Transformer可以让你了解注意力是如何真正工作的。但在大多数情况下,只需选择一个你喜欢的框架,并为你的目的修改它。
吐槽完了,让我们看看我们通常从哪里开始我们的训练运行。
7.4 为什么(几乎)每个后训练流程都从SFT开始
如果你最近在X上花时间,你会认为强化学习(RL)是城里唯一的游戏。每天都有新的缩写词、算法调整和关于RL是否能引出新能力的激烈辩论 (Chu et al., 2025; Yue et al., 2025)。
当然,RL并不新鲜。OpenAI和其他实验室严重依赖来自人类反馈的强化学习 (RLHF) (Lambert et al., 222)来对齐他们的早期模型,但直到DeepSeek-R1 (DeepSeek-AI, Guo, et al., 2025)的发布,基于RL的后训练才真正在开源生态系统中流行起来。
但有一件事没有改变:几乎每个有效的后训练流程仍然以 监督式微调(SFT) 开始。原因很简单:
- 它便宜: 与RL相比,SFT需要适度的计算。你通常可以在不需要烧掉一大堆硅的情况下获得有意义的收益,并且时间只是RL的一小部分。
- 它稳定: 与RL不同,后者对奖励设计和超参数非常敏感,SFT“就是好用”。
- 它是正确的基线: 一个好的SFT检查点通常能给你带来你所追求的大部分收益,并且它使像DPO或RLHF这样的后续方法更有效。
在实践中,这意味着SFT不仅仅是因为它简单而成为第一步;它是在尝试任何更复杂的方法之前,始终能提高性能的一步。当你处理基础模型时,尤其如此。除了少数例外,基础模型太粗糙,无法从高级的后训练方法中受益。
所以,如果SFT是大多数流程开始的地方,下一个问题是:你应该微调什么?这从选择正确的基础模型开始。
7.4.1 挑选基础模型
在为后训练选择基础模型时,一些实际的维度最重要:
- 模型大小:尽管小模型随着时间的推移已大大改进,但今天仍然是更大的模型泛化得更好,而且通常用更少的样本。选择一个能代表你计划在训练后如何使用或部署模型的模型大小。在Hugging Face Hub上,你可以按模态和大小过滤模型以找到合适的候选者。
- 架构(MoE vs. 密集型):MoE模型每个Token激活一个参数子集,并提供每单位计算的更高容量。它们非常适合大规模服务,但根据我们的经验,微调起来更棘手。相比之下,密集型模型更简单易训,并且在较小规模上通常优于MoE。
- 后训练记录:基准测试很有用,但如果基础模型已经产生了一系列与社区产生共鸣的强大后训练模型,那就更好了。这为模型是否易于训练提供了一个代理指标。
根据我们的经验,来自Qwen、Mistral和DeepSeek的基础模型最适合后训练,其中Qwen是一个明显的宠儿,因为每个模型系列通常覆盖很大的参数范围(例如Qwen3模型的大小从0.6B到235B!)。这个特性使得扩展变得更加直接。
一旦你选择了一个符合你部署需求的基础模型,下一步就是建立一个简单、快速的SFT基线来探查其核心技能。
7.4.2 训练简单的基线
对于SFT,一个好的基线应该训练快,专注于模型的核心技能,并且在某个特定能力不达标时易于用更多数据扩展。选择用于初始基线的数据集涉及一些品味和熟悉度,需要了解哪些数据集可能质量高。
总的来说,避免过度依赖在学术基准上报告高分的公共数据集,而应专注于那些被用来训练像OpenHermes这样的优秀模型的数据集。例如,在SmolLM1的开发中,我们最初在WebInstruct上运行了SFT,它在纸面上是一个很好的数据集。然而,在我们的感觉测试中,我们发现它太偏向科学了,因为模型会对像“你好吗?”这样的简单问候用方程来回应。
这促使我们创建了Everyday Conversations数据集,它对于在小模型中灌输基本的聊天能力至关重要。
对于SmolLM3,我们着手训练一个混合推理模型,并最初选择了一小组数据集来针对推理、指令遵循和可控性。下表显示了每个数据集的统计数据:
| 数据集 | 推理模式 | 示例数量 | 示例百分比 | Token数量 (M) | Token百分比 | 平均每个示例Token数 | 平均上下文Token数 | 平均响应Token数 | 平均轮次 |
|---|---|---|---|---|---|---|---|---|---|
| Everyday Conversations | /no_think | 2,260 | 2.3% | 0.6 | 0.8% | 260.2 | 222.3 | 94.0 | 7.8 |
| SystemChats 30k | /no_think | 33,997 | 35.2% | 21.5 | 28.2% | 631.9 | 422.8 | 267.7 | 6.3 |
| Tulu 3 SFT Personas IF | /no_think | 29,970 | 31.0% | 13.3 | 17.5% | 444.5 | 119.8 | 380.7 | 2 |
| Everyday Conversations (Qwen3-32B) | /think | 2,057 | 2.1% | 3.1 | 4.1% | 1,522.4 | 376.8 | 1,385.6 | 4 |
| SystemChats 30k (Qwen3-32B) | /think | 27,436 | 28.4% | 29.4 | 38.6% | 1070.8 | 84.6 | 1,042.7 | 2 |
| s1k-1.1 | /think | 835 | 0.9% | 8.2 | 10.8% | 8,859.3 | 370.9 | 9,728.5 | 2 |
| 总计 | - | 96,555 | 100.0% | 76.1 | 100.0% | 2,131.5 | 266.2 | 2,149.9 | 4.0 |
正如我们在SmolLM3的开发过程中了解到的,训练混合推理模型比标准SFT更棘手,因为你不能只是将数据集混合在一起;你需要跨模式配对数据。每个示例都必须清楚地指示模型是应该进行扩展推理还是给出简洁的答案,理想情况下,你想要平行的示例来教它何时切换模式。
从上表中需要注意的另一件事是,你应该按Token而不是示例来平衡你的数据混合:例如,s1k-1.1数据集约占总示例的1%,但由于长的推理响应,占了总Token的约11%。
这为我们最关心的技能提供了基本覆盖,但也引入了一个新的挑战:每个数据集都必须以不同的方式格式化,取决于它是否应该启用扩展思考。为了统一这些格式,我们需要一个一致的聊天模板。
7.4.3 挑选一个好的聊天模板
在选择或设计聊天模板时,没有一刀切的答案。在实践中,我们发现有几个问题值得预先考虑:
- 用户能否自定义系统角色? 如果用户应该能够定义自己的系统提示(例如“像个海盗一样说话”),模板需要能干净地处理这一点。
- 模型需要工具吗? 如果你的模型需要调用API,模板需要能容纳工具调用和响应的结构化输出。
- 它是一个推理模型吗? 推理模型使用像
<think> ... </think>这样的模板来将模型的“思考”与最终答案分开。一些模型在对话中会跨轮次丢弃推理Token,聊天模板需要处理这种逻辑。 - 它能与推理引擎一起工作吗? 像vLLM和SGLang这样的推理引擎有专门的解析器用于推理和工具。与这些解析器的兼容性可以省去很多麻烦,尤其是在复杂的智能体基准中,一致的工具调用至关重要。
下表显示了一些流行的聊天模板,以及它们在关键考量上的比较:
| 聊天模板 | 系统角色自定义 | 工具 | 推理 | 推理兼容性 | 备注 |
|---|---|---|---|---|---|
| ChatML | ✅ | ✅ | ❌ | ✅ | 简单,适用于大多数用例。 |
| Qwen3 | ✅ | ✅ | ✅ | ✅ | 混合推理模板。 |
| DeepSeek-R1 | ❌ | ❌ | ✅ | ✅ | 用<think>预填充推理内容。 |
| Llama 3 | ✅ | ✅ | ❌ | ✅ | 内置工具,如Python代码解释器。 |
| Gemma 3 | ✅ | ❌ | ❌ | ❌ | 系统角色自定义在第一个用户轮次定义。 |
| Command A Reasoning | ✅ | ✅ | ✅ | ❌ | 每个模型有多个聊天模板。 |
| GPT-OSS | ✅ | ✅ | ✅ | ✅ | 基于Harmony响应格式。复杂但多功能。 |
在大多数情况下,我们发现ChatML或Qwen的聊天模板是一个很好的起点。对于SmolLM3,我们需要一个用于混合推理的模板,并发现Qwen3是少数几个在我们关心的维度上取得良好平衡的模板之一。然而,它有一个我们不完全满意的怪癖:推理内容在对话中除了最后一轮外都被丢弃。
尽管这对于推理是有意义的(为了避免撑爆上下文),我们得出结论,对于训练来说,保留所有轮次的推理Token以便适当地条件化模型是很重要的。
相反,我们决定制作我们自己的聊天模板,具有以下特点:
- 一个结构化的系统提示,像Llama 3和那些从专有模型中“越狱”出来的一样。我们也想提供完全覆盖系统提示的灵活性。
- 支持代码智能体,执行任意Python代码而不是进行JSON工具调用。
- 通过系统消息显式控制推理模式。
为了迭代聊天模板的设计,我们使用了Chat Template Playground。这个方便的应用程序由我们在Hugging Face的同事开发,可以轻松预览消息如何渲染和调试格式问题。
一旦你确定了一些初始数据集和一个聊天模板,是时候训练一些基线了!
7.4.4 婴儿基线 (Baby Baselines)
在我们深入优化并挤压每一点性能之前,我们需要建立一些“婴儿基线”。这些基线不是为了达到SOTA(还不是),而是旨在验证聊天模板是否按你所愿工作,以及初始超参数集是否产生稳定的训练。只有在我们有了这个基础之后,我们才开始大力调整超参数和训练混合。
在训练SFT基线时,以下是主要需要考虑的事情:
- 你将使用全量微调 (FullFT)还是像LoRA或QLoRA这样的参数高效方法?
- 你需要什么类型的并行化?对于小模型或用LoRA训练的模型,你通常可以用数据并行。对于更大的模型,你需要FSDP2或 DeepSpeed ZeRO-3 来共享模型权重和优化器状态。对于用长上下文训练的模型,使用像上下文并行这样的方法。
- 如果你的硬件支持,请使用像FlashAttention和Liger这样的内核。
- 屏蔽损失,只在助手Token上进行训练。
- 调整学习率;除了数据,这是决定你的模型是“平庸”还是“优秀”的最重要因素。
- 打包训练样本并调整序列长度以匹配你的数据分布。这将大大加快训练速度。TRL有一个方便的应用程序来为你做这件事。
让我们看看这些选择在SmolLM3中是如何发挥作用的。对于我们的第一个基线实验,我们想要一个简单的健全性检查:聊天模板是否真的能引出混合推理? 为了测试这一点,我们比较了我们表格中的三种数据混合:
- Instruct: 在非推理示例上训练。
- Thinking: 在推理示例上训练。
- Hybrid: 在所有示例上训练。
对于每种混合,我们用FullFT在SmolLM3-3B-Base上运行了SFT,学习率为1e-5,有效批次大小为128,并训练了1个周期。这些实验很快,在8块H100的一个节点上,根据子集的不同,耗时在30-90分钟之间。
这些结果很快向我们表明,混合模型表现出一种“分裂大脑”,即一种推理模式的数据混合对另一种几乎没有影响。
7.4.5 “感觉测试”你的基线
尽管评估看起来还行,但当我们试图让混合模型扮演不同角色(例如像个海盗)时,它始终忽略我们在系统消息中放置的任何内容。经过一番挖掘,我们发现原因在于我们格式化数据的方式:
我们数据处理代码中的一个Bug将custom_instructions设置为None,这实际上从每个训练样本中移除了系统消息 🙈!这尤其对SystemChats子集有问题,其中所有角色都通过custom_instructions定义,因此模型倾向于在对话中途随机切换角色。
修复这个Bug对评估没有影响,但最终我们确信聊天模板和数据集格式化是正常的。一旦你的设置稳定并且你的数据管道检查无误,下一步就是专注于开发特定的能力。
7.4.6 针对特定能力
在Open-R1的开发中,我们注意到完全在单轮推理数据上训练的基础模型将无法泛化到多轮。这并不奇怪;在没有此类示例的情况下,模型正在其训练分布之外进行测试。
为了为SmolLM3定量地衡量这一点,我们从Qwen3中获得灵感,他们开发了一个名为ThinkFollow的内部评估,它随机插入/think或/no_think标签来测试模型是否能持续切换推理模式。在我们的实现中,我们从Multi-IF中获取提示,然后检查模型是否生成了用<think>和</think>标签包裹的空或非空思考块。正如预期的那样,我们混合基线的结果显示,模型在第一轮之后就惨不忍睹地无法启用推理模式。
为了修复这个能力,我们构建了一个名为IFThink的新数据集。基于Multi-IF流程,我们从Tulu 3的指令遵循子集中获取单轮指令,并使用Qwen3-32B将它们扩展为多轮交流,以生成可验证的指令和推理轨迹。
在我们的基线混合中包含这些数据产生了戏剧性的改进。
7.4.7 哪些超参数真正重要?
在SFT中,只有少数几个超参数真正重要。学习率、批次大小和打包几乎决定了你的模型训练效率和泛化能力的一切。
- 屏蔽用户轮次(Masking user turns) 在大多数聊天式数据集中,每个训练示例都由交替的用户和助手消息组成。如果我们训练模型预测所有Token,它实际上是在学习自动完成用户查询,而不是专注于产生高质量的助手响应。屏蔽用户轮次可以防止这种情况,确保模型的损失只在助手输出上计算。
- 打包还是不打包?(To pack or not to pack?) 序列打包是对训练效率产生巨大影响的训练细节之一。在SFT中,大多数数据集包含可变长度的样本,这意味着每个批次包含大量的填充Token,浪费计算并减慢收敛。打包通过将多个序列连接在一起直到达到期望的最大Token长度来解决这个问题。根据批次大小的不同,我们看到打包将吞吐量提高了3-5倍!然而,打包会略微改变训练动态:虽然你处理了更多的数据,但你进行的梯度更新更少,这可能影响最终性能,尤其是在小数据集上。
- 调整学习率(Tuning the learning rate) 在SFT中,最优学习率通常比预训练时使用的小一个数量级(或更多)。这是因为我们从一个具有丰富表示的模型初始化,激进的更新可能导致灾难性遗忘。我们的建议是运行扫描,学习率范围在你SFT学习率的5倍到20倍之间。你很可能会在这个范围内找到你的最优性能!
- 扩展周期数(Scaling the number of epochs) 在我们的消融实验中,我们通常只训练一个周期以快速迭代。一旦你确定了一个好的数据混合并调整了像学习率这样的关键参数,下一步就是为最终训练增加周期数。
7.4.8 通过持续预训练提升推理能力
持续预训练——或者如果你想听起来更时髦,中途训练(mid-training)——意味着在进行SFT之前,将一个基础模型在大量领域特定的Token上进一步训练。当中途训练的目标能力与SFT共享一个共同的核心技能,例如编码或推理时,中途训练非常有用。
在我们的实验中,我们发现使用像NVIDIA的后训练数据集或OpenThoughts这样的推理数据集进行中途训练,然后再进行SFT,效果是戏剧性的:对于扩展思考,我们在AIME25和LiveCodeBench v4上的性能几乎翻了三倍,而GPQA-D获得了整整10个点的提升。这些结果为我们提供了明确的证据,即对于推理模型,如果你的基础模型在预训练期间没有看到大量的推理数据,进行一定量的中途训练几乎总是有意义的。
7.5 从SFT到偏好优化:教模型什么是“更好”
尽管你可以用更多数据来扩展SFT,但在某个点上,你会观察到收益递减或失败模式,比如你的模型无法修复自己的错误代码。为什么?因为SFT是一种模仿学习,所以模型只学习复制它训练数据中的模式。如果数据中没有好的修复方案,或者如果期望的行为很难通过蒸馏引出,模型就没有明确的信号来判断什么是 “更好”。
这就是偏好优化发挥作用的地方。我们不只是复制演示,而是给模型比较性的反馈,比如“响应A比响应B好”。这些偏好为质量提供了更直接的训练信号,并使模型性能能够超越仅SFT的限制。
另一个好处是,你通常需要的数据远少于SFT,因为起点已经是一个相当不错的模型,能够遵循指令并拥有先前训练阶段的知识。
7.5.1 创建偏好数据集
历史上,偏好数据集是通过向人类标注员提供成对的模型响应并要求他们评判哪个更好来创建的。这种方法仍然被LLM提供商用来收集人类偏好标签,但它极其昂贵且难以扩展。
最近,LLM已经能够产生高质量的响应,并且通常以一种成本效益高的方式。这些进步使得LLM生成许多应用的偏好变得可行。在实践中,有两种常见的方法:
- 强 vs. 弱(Strong vs. weak)
- 在策略(On-policy)与评分
在SmolLM3的开发时,不存在带有推理轨迹的偏好数据,所以我们决定用“强 vs. 弱”的方法生成一些我们自己的数据。我们使用来自Ai2的Tulu 3偏好混合中的提示,用Qwen3-0.6B和Qwen3-32B在/think模式下生成响应。结果是一个包含25万多个LLM生成偏好的大规模数据集,准备好用偏好优化算法同时在多个轴上改进我们的SFT检查点。
7.5.2 我应该选择哪种算法?
直接偏好优化 (DPO) (Rafailov et al., 2024)是第一个在开源领域获得广泛采用的偏好优化算法。它的吸引力在于实现简单、实践中稳定,并且即使有适量的偏好数据也有效。因此,DPO已成为在尝试更复杂的技术(如RL)之前改进SFT模型的默认方法。
但研究人员很快发现有很多方法可以改进DPO,如今有各种各样的替代方案可供探索。我们发现最有效的几种包括:
- 卡尼曼-特沃斯基优化 (KTO)
- 优势比偏好优化 (ORPO)
- 锚定偏好优化 (APO)
幸运的是,许多这些选择在TRL的DPOTrainer中只是一行代码的更改。在IFEval上,APO-zero比SFT检查点提高了15-20个百分点!
7.5.3 对于偏好优化,哪些超参数最重要?
对于偏好优化,通常只有三个影响训练动态的超参数:
- 学习率,通常比SFT使用的小10-100倍。
- $\beta$参数,通常控制偏好对之间的边际大小。
- 批次大小。
7.5.4 偏好优化准则 (Rules of Engagement)
总结我们关于偏好优化的发现,可能对你未来的项目有用:
- 不要害怕创建你自己的偏好数据!
- 选择DPO作为你的初始基线,并从那里开始迭代。
- 使用比SFT小约10倍的学习率。
- 扫描$\beta$,通常在0.01到0.5的范围内。
- 由于大多数偏好算法在一个周期后会过拟合,划分你的数据并迭代训练以获得最佳性能。
偏好优化通常是简单性与性能之间的甜蜜点,但它仍然继承了一个关键的限制:它只和你收集的离线偏好数据一样好。在某个点上,静态数据集的信号耗尽,你需要能够在模型与提示和环境互动时在线生成新的训练反馈的方法。这就是偏好优化与更广泛的在策略和基于RL的方法相遇的地方。
7.6 走向在策略及超越监督标签
如果你希望你的模型能持续解决数学问题、生成可执行代码或跨多个步骤进行规划,你通常需要一个奖励信号,而不仅仅是“A比B好”。
这就是RL开始有意义的地方。你不是用偏好来监督模型,而是让它与一个环境(可能是数学验证器、代码执行器,甚至真实的用户反馈)互动,并直接从结果中学习。RL在以下情况中大放异彩:
- 你可以自动检查正确性,例如,单元测试、数学证明、API调用,或有高质量的验证器或奖励模型。
- 任务需要多步推理或规划,其中局部偏好可能无法捕捉长期成功。
- 你想要优化超越偏好标签的目标,例如通过代码的单元测试或最大化某个目标。
在LLM领域,RL主要有两种风格:
- 来自人类反馈的强化学习 (RLHF)
- 带可验证奖励的强化学习 (RLVR)
对于SmolLM3,我们完全跳过了RL,主要是由于时间限制,以及我们的模型已经通过离线偏好优化达到了同类最佳。然而,自发布以来,我们重新审视了这个主题,并将通过分享我们从将RLVR应用于混合推理模型的一些教训来结束本章。
7.6.1 将RLVR应用于混合推理模型
混合推理模型给RLVR带来了额外的复杂性,因为生成长度根据推理模式的不同而有很大差异。例如,SmolLM3最终APO检查点在AIME25上的Token长度分布显示,/no_think模式生成的解决方案中位长度约为2k Token,而/think模式要大得多,有16k Token和更长的尾部。理想情况下,我们希望用RLVR提高两种模式的整体性能,而不彻底改变它们各自的长度分布。
令我们惊讶的是,天真地应用GRPO会导致一种奖励黑客(Reward Hacking):尽管从未被提示发出长的CoT,模型学会了利用其基础推理能力来增加奖励。
这个问题可以通过包含一个过长完成惩罚来缓解,该惩罚会对超过一定长度的完成进行惩罚。
7.6.2 RL是城里唯一的游戏吗?
其他在策略学习方法将偏好优化和蒸馏扩展到迭代循环中,随着模型的演变刷新训练信号:
- 在线DPO (Online DPO)
- 在策略蒸馏 (On-policy Distillation)
这些方法模糊了静态偏好优化和完整RL之间的界限:你仍然可以从适应模型的当前分布中获益,但没有设计和稳定强化学习循环的全部复杂性。
7.6.3 我应该选择哪种方法?
尽管有无数关于哪种在策略方法“最好”的研究论文,但在实践中,决定取决于几个因素:
| 算法 | 何时使用 | 权衡 | 最适合模型大小 |
|---|---|---|---|
| 在线DPO | 你可以廉价地获得偏好标签。最适合将行为与不断演变的分布对齐。 | 易于迭代扩展,比RL更稳定,但取决于标签质量和覆盖范围。在少数训练框架中支持。 | 任何大小,其中偏好能捕捉到超越模仿的改进。 |
| 在策略蒸馏 | 你可以访问一个更强的教师模型,并希望高效地转移能力。 | 实现简单,运行便宜,继承教师偏见,天花板受限于教师。仅在TRL和NemoRL中支持。 | 对中小型模型(<30B)最有效。 |
| 强化学习 | 当你有可验证的奖励或需要多步推理/规划的任务时最好。可以与奖励模型一起使用,但存在奖励黑客等挑战。 | 灵活且强大,但成本高且难以稳定;需要仔细的奖励塑造。在大多数后训练框架中支持。 | 中到大型模型(20B+),其中额外的容量让他们能利用结构化的奖励信号。 |
总的来说,我们认为在有效扩展RL (Khatri et al., 2025)和探索其他计算效率方法方面还有很多工作要做。确实是激动人心的时代!
7.7 后训练收官
如果你已经走到了这一步,恭喜:你现在拥有了成功进行后训练所需的所有核心要素。你现在准备好运行许多实验并测试不同的算法以获得SOTA结果了。
但你可能已经意识到,知道如何训练伟大的模型只是故事的一半。要真正将这些模型变为现实,你需要正确的基础设施。让我们用LLM训练的无名英雄来结束这部巨著。
8. 基础设施 - 幕后英雄
现在你已经了解了我们关于模型创建和训练的全部心法,让我们来聊聊那个决定着项目(以及你的银行账户)成败的关键,却又常常被低估的组件:基础设施。无论你关注的是框架、架构还是数据策划,理解基础设施的基础知识都有助于你识别训练瓶颈、优化并行策略和调试吞吐量问题。(至少,它能让你和基础设施团队的沟通顺畅得多 😉)。
大多数训练模型的人都对架构和数据了如指掌,但很少有人深究基础设施的细节。这方面的专业知识通常掌握在框架开发者和集群工程师手中,而其他人则想当然地认为这是一个已解决的问题:租几块GPU,装上PyTorch,然后就可以开干了。
我们用384块H100训练了SmolLM3近一个月,总共处理了11万亿Token……而这趟旅程并非一帆风顺!在此期间,我们应对了节点故障、存储问题和训练重启(详见“训练马拉松”一章)。你需要有周全的应急计划和策略,来为这些意外做好准备,并保持训练过程的平稳和低维护。
本章旨在弥合这一知识鸿沟。请将它视为一份关于硬件层的实战指南,专注于那些对训练至关重要的问题。(注意:每个小节都以一个“一句话总结”开篇,你可以根据需要选择阅读深度。)
前两个部分将探讨硬件工作的基础知识:GPU究竟由什么组成?内存层级结构是如何运作的?CPU和GPU如何通信?我们还将介绍你在采购GPU时应考虑的因素,以及在投入长期训练前如何对它们进行测试。最重要的是,我们将在每一步向你展示如何亲手测量和诊断这些系统。接下来的部分则更具应用性,我们将看到如何让你的基础设施具备故障恢复能力,以及如何最大限度地优化训练吞t-吐量。
本章的核心玩法是:找到并修复瓶颈!
请将这视为一次建立直觉的过程,让你理解某些设计决策为何至关重要。当你明白模型的激活值需要流经多个具有不同带宽和延迟特性的缓存层级时,你自然会开始思考如何构建训练流程以最小化数据移动。当你看到节点间通信比节点内通信慢几个数量级时,你就会理解为什么并行策略如此关键。
让我们从拆开一块GPU,看看里面有什么开始。
8.1 GPU 内部:核心架构
从根本上说,GPU是一个为吞吐量而非延迟而优化的大规模并行处理器。与擅长快速执行少量复杂指令流的CPU不同,GPU通过同时执行数千个简单操作来获得极致性能。
理解GPU性能的关键在于认识到,它不仅仅是原始算力,更是计算与数据移动之间的博弈。一块GPU可能拥有数万亿次浮点运算的理论算力,但如果数据无法足够快地送达计算单元,这些潜力就会被白白浪费。这就是为什么我们需要同时理解内存层级结构(数据如何移动)和计算管线(工作如何完成)。
因此,在最高层次上,GPU执行两个基本任务:
- 移动和存储数据(内存系统)
- 用数据做有用的工作(计算管线)
8.1.1 计算单元和 FLOPs
一句话总结: GPU的性能以FLOPs(每秒浮点运算次数)衡量。像H100这样的现代GPU在较低精度下提供显著更高的吞吐量:BF16为990 TFLOPs,而FP32仅为67 TFLOPs。然而,由于内存瓶颈,实际性能通常是理论峰值的70-77%。业界顶尖的训练实现了20-41%的端到端效率,即模型FLOPs利用率(MFU)。在规划训练时,请使用实际可达到的数字,而不是市场宣传的理论规格。
GPU的计算性能以 FLOPs(每秒浮点运算次数) 来衡量。一个FLOP代表一个单一的算术运算,比如a + b这样的浮点数加法,而现代GPU每秒可以执行数万亿次(TFLOPs)。
GPU计算的基本构件是 流式多处理器(Streaming Multiprocessors, SMs) ,它们是能够并行执行指令的独立处理单元。每个SM包含两种类型的核心:用于标准浮点运算的CUDA核心,以及为矩阵乘法(深度学习的绝对主力,对Transformer性能至关重要)优化的专用Tensor Cores。
现代GPU在芯片上集成了数百个这样的SM!例如,我们集群使用的H100 SXM5版本就包含132个SM。每个SM独立运行,同步执行被称为Warp的32个线程组。为了实现这一点,SM依赖于另一个组件——Warp调度器:通过在不同的Warp之间平衡指令,当一个Warp被阻塞时,它们可以通过切换Warp来“隐藏延迟”。这种 SIMT(单指令,多线程) 的执行模型意味着,一个Warp中的所有线程都在不同的数据上同时执行相同的指令。
由于有数百个SM,每个SM又同时执行多个Warp,一块GPU可以同时运行数万个线程。这种大规模并行性正是GPU如此擅长处理主导深度学习工作负载的矩阵运算的原因!
在讨论FLOPs时,精度至关重要。Tensor Cores可以在不同的精度下操作(FP64、FP32、FP16/BF16、FP8、FP4)。因此,可达到的吞吐量根据数据类型的不同而有巨大差异,通常是数量级的差异。较低的精度格式能够实现更高的吞吐量,因为它们需要更少的数据移动,并且可以在相同的硅面积上封装更多的操作。过去由于训练不稳定性,低精度被避免使用,但如今,得益于一系列新技术,训练和推理正越来越多地被推向更低的精度,甚至达到了FP8和FP4。
下表显示了不同NVIDIA GPU代和精度的理论峰值性能:
| 精度 \ GPU 类型 | A100 | H100 | H200 | B100 | B200 |
|---|---|---|---|---|---|
| FP64 | 9.7 | 34 | 34 | 40 | 40 |
| FP32 | 19.5 | 67 | 67 | 80 | 80 |
| FP16/BF16 | 312 | 990 | 990 | 1750 | 2250 |
| FP8 | - | 3960 | 3960 | 4500 | 5000 |
| FP4 | - | - | - | 9000 | 10000 |
较低精度下吞吐量的巨大提升,不仅仅是原始速度的问题,它反映了我们对数值计算思维方式的根本转变。FP8和FP4使模型能够在每瓦特和每秒内执行更多操作,这对于大规模训练和推理至关重要。
如何理解这些数字:这些理论峰值FLOPs代表了在理想条件下可达到的最大计算吞吐量,即所有计算单元都得到充分利用且数据随时可用。在实践中,实际性能在很大程度上取决于你的工作负载能否让计算单元持续“吃饱”数据,以及你的操作是否能有效地映射到硬件上。
对于SmolLM3,我们将在NVIDIA H100 80GB HBM3 GPU上进行训练,所以我们首先想将H100的理论TFLOPs规格与实际性能进行比较。为此,我们使用了SemiAnalysis GEMM基准测试:它测试了来自Meta的Llama 70B训练的真实世界矩阵乘法形状的吞吐量。
验证理论性能:我们的实验揭示了理论峰值与实际可达到性能之间的差距。
- 对于BF16操作,我们持续达到了714-758 TFLOPs,约占H100理论990 TFLOPs峰值的72-77%。在实践中,这对于真实世界的工作负载来说是一个极好的利用率!
- FP8的结果则更为微妙。使用PyTorch的
torch._scaled_mm内核和e4m3精度,我们达到了1,210-1,457 TFLOPs,约占理论3,960 TFLOPs峰值的31-37%。😮为什么会这样?这种较低的利用率(在FP8中)实际上并不代表性能不佳;相反,它反映了随着计算吞吐量的急剧增长,这些操作变得越来越受内存带宽的限制。Tensor Cores处理FP8数据的速度,远快于内存系统为其提供数据的速度,使得内存带宽成为了瓶颈。
对于SmolLM3的训练,这些实际测量帮助我们设定了现实的吞吐量期望。在规划你自己的训练时,请使用这些可达到的数字,而不是理论峰值来设定你的预期。
正如我们所见,当计算在低精度下变得过快时,GPU内存似乎成为了一个瓶颈。让我们看看GPU内存是如何工作的,以及导致瓶颈发生的原因!
8.1.2 GPU 内存层级结构:从寄存器到 HBM
一句话总结: GPU将内存组织成一个从快而小(寄存器、共享内存)到慢而大(HBM主内存)的层级结构。理解这个层级结构至关重要,因为现代AI通常是受内存限制的:瓶颈在于移动数据,而不是对其进行计算。像Flash Attention这样的算子融合技术,通过将中间结果保存在快速的片上内存中,而不是写入慢速的HBM,实现了2-4倍的加速。基准测试显示,H100的HBM3在实践中提供了约3 TB/s的带宽,与大尺寸传输的理论规格相符。
为了直观地看到内存操作在GPU中是如何流动的,让我们首先看看NVIDIA Nsight Compute的内存图,这是一个剖析图,它以图形方式展示了数据如何在任何你选择的计算核心(kernel)的不同内存单元之间移动。
一般来说,内存图显示了逻辑单元(绿色)和物理单元(蓝色)。单元之间的链接表示它们之间发生的指令(Inst)或请求(Req)数量,颜色则代表了峰值利用率的百分比:从未使用(0%)到以峰值性能运行(100%)。
现在,让我们来了解构成这个图表基础的底层内存层级结构。现代GPU将内存组织成一个在速度、容量和成本之间取得平衡的层级结构,这种设计是由基本的物理学和电路约束决定的。
在这个层级结构的底部是HBM(高带宽内存):GPU的主内存,也称为全局内存或设备内存。H100配备了HBM3,理论带宽为3.35 TB/s。HBM是内存层级中最大但最慢的一层。
向上移动到计算单元,我们会发现越来越快但越来越小的内存层:
- L2缓存: 一个跨GPU共享的大型基于SRAM的缓存,通常为几十兆字节。在H100上,这是50 MB,带宽约为 ~13 TB/s。
- L1缓存和共享内存 (SMEM): 每个流式多处理器(SM)都有自己的L1缓存和由程序员管理的共享内存,它们共享相同的物理SRAM存储。在H100上,这个组合空间是每个SM 256 KB,每个SM的带宽约为 ~31 TB/s。
- 寄存器文件 (RMEM): 在层级结构的顶端,寄存器是最快的存储,直接位于计算单元旁边。寄存器是单个线程私有的,每个SM提供以数百TB/s计的带宽。
为什么这很重要:理解这个层级结构对于内核优化至关重要。关键的洞见是,受内存限制的操作受限于你移动数据的速度,而不是你计算的速度。这就是为什么算子融合(operator fusion)如此强大的原因:通过将多个操作组合成一个内核,你可以将中间结果保存在快速的SRAM中,而不是在操作之间将它们写回慢速的HBM。Flash Attention正是这一原则在实践中的完美范例。
8.1.2.1 示例:在实践中验证我们的 HBM3 带宽
现在我们了解了内存层级结构,让我们将理论付诸实践,并验证我们H100 GPU上的实际带宽!这就是基准测试工具变得至关重要的地方。
NVBandwidth是NVIDIA的开源基准测试工具,专门用于测量GPU系统的带宽和延迟。
让我们用它来测量我们H100的本地内存带宽,使用device_local_copy测试。结果揭示了内存系统的一个重要特性:对于小消息尺寸(< 1 MB),我们是受延迟限制而不是受带宽限制。启动内存传输的开销主导了性能,阻止我们达到峰值带宽。然而,对于大消息尺寸(≥ 1 MB),我们为读写操作都达到了约1,500 GB/s的持续带宽。
由于HBM带宽同时考虑了读写操作,我们将它们相加得到3 TB/s的总双向带宽(1,519读 + 1,519写),这与H100的理论3.35 TB/s HBM3规格非常接近。
8.1.3 Roofline 模型
理解你的内核是受计算限制还是受内存限制,决定了哪种优化会有帮助。
Roofline模型提供了一个可视化的框架来理解这些性能特征并识别优化机会。它可以在我们之前提到的NSight Compute剖析工具中找到。
我们可以通过查看图表的两个划分区域来解释性能:
- 受内存限制(Memory Bound): 这个区域的内核受内存带宽限制。GPU在等待数据,增加计算能力无济于事。
- 受计算限制(Compute Bound): 这个区域的内核受计算吞吐量限制。GPU有足够的数据但处理不够快。
在我们的示例中,内核位于受内存限制的区域,表明通过优化内存流量仍有改进的空间!
现在我们了解了GPU内部发生的事情,让我们放大视野,探索GPU如何与世界其他部分通信。
8.2 GPU 外部:GPU 如何与世界对话
现在我们了解了GPU如何利用其内部内存层级结构进行计算,我们需要解决一个关键的现实:GPU并非孤立运作。在任何计算发生之前,数据必须加载到GPU的内存中。CPU需要调度内核并协调工作。在分布式训练中,GPU必须不断地相互交换激活值、梯度和模型权重。
这就是外部通信基础设施变得至关重要的地方。无论你的GPU计算单元有多强大,如果数据无法足够快地到达它们——无论是从CPU、从存储,还是从其他GPU——你昂贵的硬件就会闲置。
在本节中,我们将探讨连接GPU与外部世界的四个关键通信链路:
- GPU-CPU: CPU如何调度工作并将数据传输到GPU。
- GPU-GPU(节点内): 同一台机器上的GPU如何通信。
- GPU-GPU(节点间): 不同机器上的GPU如何通过网络通信。
- GPU-存储: 数据如何从存储流向GPU内存。
8.2.1 GPU-到-CPU
一句话总结: CPU通过PCIe连接来指挥GPU工作,但在我们的p5实例中,CPU到GPU的传输瓶颈约为14.2 GB/s (PCIe Gen4 x8)。CPU-GPU之间的延迟约为1.4微秒,这增加了内核启动(kernel launch)的开销,对于那些包含大量小型内核的工作负载来说是个大问题。CUDA Graphs可以通过批量处理操作来减少这种开销。在多CPU插槽的系统上,NUMA亲和性至关重要;在错误的CPU插槽上运行GPU进程会引入显著的延迟。像Grace Hopper这样的现代架构通过NVLink-C2C(900 GB/s vs 128 GB/s)彻底消除了PCIe的瓶颈。
CPU是GPU计算的总指挥官。它负责启动计算核心(kernels)、管理内存分配以及协调数据传输。但问题是,CPU与GPU的通信速度究竟能有多快?这取决于它们之间的PCIe(Peripheral Component Interconnect Express)连接。
理解这个连接至关重要,因为它直接影响到:
- 内核启动延迟:CPU能多快地在GPU上调度工作。
- 数据传输速度:我们能在CPU和GPU内存之间多快地移动数据。
- 同步开销:CPU与GPU协调点所需付出的代价。
在现代GPU服务器中,CPU-GPU连接已经有了长足的进步。早期系统使用直接的PCIe连接,而像DGX H100这样的现代高性能系统则采用更复杂的拓扑结构,通过PCIe交换机来高效管理多个GPU。而在最新的GB200架构中,NVIDIA更进一步,将CPU和GPU放在同一块印刷电路板上,完全无需外部交换机。
为了识别潜在的瓶颈,我们首先需要了解我们p5实例的物理拓扑,然后测量这个关键链路的实际性能。
通过分析系统拓扑,我们可以发现系统中两个关键的PCIe带宽值:
- 15.75 GB/s:对应PCIe Gen4 x8链路(从CPU到PCIe交换机)。
- 63.02 GB/s:对应PCIe Gen5 x16链路(从PCIe交换机到GPU)。
为了更好地理解整个拓扑结构,我们可以将其可视化。一个清晰的系统拓扑图会展示出我们系统的层级结构:
- 它包含两个NUMA(非一致性内存访问)节点(每个CPU插槽对应一个NUMA内存区域)。
- 每个CPU插槽通过PCIe Gen4 x8链路(15.75GB/s)连接到四个PCIe交换机。
- 每个PCIe交换机通过PCIe Gen5 x16链路(63.02GB/s)连接到一个H100 GPU。
- …(我们将在后续章节探讨NVSwitch、EFA网卡和NVMe硬盘等其他组件。)
不同代的PCIe规范,其每条通道的传输速率都翻了一番。值得注意的是,传输速率(Transfer Rate)以GT/s(每秒千兆次传输)为单位,代表原始信令速率;而吞吐量(Throughput)以GB/s(每秒千兆字节)为单位,它考虑了编码开销,代表了实际可用的带宽:
| PCIe版本 | 传输速率 (每通道) | 吞吐量 (GB/s) | ||
|---|---|---|---|---|
| ×1 | ×2 | ×4 | ||
| 1.0 | 2.5 GT/s | 0.25 | ||
| 2.0 | 5.0 GT/s | 0.5 | ||
| 3.0 | 8.0 GT/s | 0.985 | ||
| 4.0 | 16.0 GT/s | 1.969 | ||
| 5.0 | 32.0 GT/s | 3.938 | ||
| 6.0 | 64.0 GT/s | 7.563 | ||
| 7.0 | 128.0 GT/s | 15.125 |
从拓扑结构和PCIe带宽表中,我们可以看到CPU到GPU的路径经过了两次PCIe跳转:首先是从CPU到PCIe交换机(PCIe Gen4 x8,15.754 GB/s),然后是从PCIe交换机到GPU(PCIe Gen5 x16,63.015 GB/s)。这意味着CPU-GPU通信的瓶颈在于第一跳,即15.754 GB/s。让我们用另一个工具来验证这一点!
通过测量从主机(CPU)内存到设备(GPU)内存的异步拷贝带宽,结果确实显示,对于小消息,我们受限于延迟;但对于大消息,我们达到了约14.2 GB/s的速度,这大约是PCIe Gen4 x8理论带宽15.754 GB/s的90%。这证实了在CPU-GPU通信中,从CPU到PCIe交换机的链路确实是我们的瓶颈。
除了带宽,延迟对于CPU-GPU通信同样重要,因为它决定了我们能多快地调度内核。我们通过一个采用“指针追逐”内核的测试来测量往返延迟,这种测试通过在主机(CPU)上分配一个缓冲区,并让GPU通过一个特殊的内核来访问它,从而模拟CPU-GPU通信的真实世界延迟。
结果显示,延迟大约为1.4微秒。这就解释了为什么我们在机器学习工作负载中经常观察到几微秒的内核启动开销。对于需要启动大量小型内核的工作负载,累加的延迟可能会成为瓶颈;否则,这种开销可以通过重叠执行来隐藏。
在像我们的AMD EPYC 7R13节点(2个插槽,每个48核)这样的多插槽系统上,NUMA亲和性对GPU性能至关重要。它指的是将进程运行在与其目标设备(如GPU)共享同一插槽的CPU核心上。当你的GPU进程运行在与GPU连接的NUMA节点不同的CPU上时,操作必须跨越CPU互连(AMD Infinity Fabric),这会增加显著的延迟和带宽限制。
通过分析NUMA拓扑和节点间的“距离”,我们可以更好地理解性能影响。数据显示,访问同一NUMA节点上的内存(距离10)比跨到另一个NUMA节点(距离32)快得多。这种3.2倍的内存访问延迟差异,在你将进程绑定到错误的NUMA节点时,会严重影响GPU性能。
8.2.2 节点内 GPU-到-GPU
在分布式训练中,GPU必须频繁交换梯度、权重和激活值,每次迭代的数据量通常达到千兆字节。如此巨大的数据量需要对通信进行精细化处理。虽然H100的内部HBM读取速度可达约3 TB/s,但错误地使用标志(flags)可能会彻底摧毁你的GPU-到-GPU通信带宽!
让我们来探究一下在同一节点内,GPU之间所有可能的通信方式(以及你应该——或不应该——设置的所有标志)🙂
一句话总结:节点内的GPU有三种通信方式:通过CPU(最慢,约3 GB/s,瓶颈在PCIe),通过GPUDirect RDMA over EFA NICs(约38 GB/s),或通过GPUDirect RDMA over NVLink(约786 GB/s双向)。NVLink要快9到112倍,并完全绕过了CPU/PCIe。NCCL在可用时会自动优先选择NVLink。NVLink SHARP (NVLS)提供硬件加速的集合通信,将allreduce性能提升1.3倍,达到480 GB/s。然而,alltoall操作(340 GB/s)无法从NVLS加速中受益。
8.2.3 通过CPU
最朴素的方法是使用主机内存(SHM):数据从GPU1出发,经过PCIe交换机到达CPU,进入主机内存,再回到CPU,再次穿过PCIe交换机,最终到达GPU2。这种方式可以通过设置NCCL_P2P_DISABLE=1和FI_PROVIDER=tcp环境变量来实现(但不推荐)。
这种迂回的路径涉及多次内存拷贝,并会占满PCIe和CPU内存总线,从而造成拥塞。在我们的拓扑结构中,4个H100共享相同的CPU内存总线,当多个GPU尝试同时通信时,它们会争夺有限的CPU内存带宽,这个问题就变得更加严重……😢
在这种由CPU介导的方法中,我们从根本上受限于CPU和PCIe交换机之间约16 GB/s的PCIe Gen4 x8链路。幸运的是,我们的GPU有一种更好的通信方式,无需CPU的介入:GPUDirect RDMA。
8.2.4 通过Libfabric EFA
GPUDirect RDMA(远程直接内存访问)是一项允许NVIDIA GPU之间直接通信的技术,通过直接访问对方的GPU内存来实现。这省去了数据经过系统CPU的步骤,避免了通过系统内存的缓冲拷贝,相比传统的CPU介导传输,性能提升可达10倍。GPUDirect RDMA可通过PCIe在节点内实现快速的GPU-到-GPU通信,也可以利用支持RDMA的NICs(网络接口卡)实现跨节点通信。
回顾我们的拓扑图,可以看到每个PCIe交换机连接了4个EFA(弹性光纤适配器)网卡,这意味着每个GPU都可以访问4个EFA适配器。EFA是AWS为云实例定制的高性能网络接口,旨在提供低延迟、高吞吐量的实例间通信。在p5实例上,EFA提供了应用程序可以使用的libfabric接口(一种专为高性能计算设计的通信API),并提供类似RDMA的功能,从而支持GPUDirect RDMA实现跨节点的直接GPU-到-GPU通信。
每个EFA链路提供100 Gbps(12.5 GB/s)的带宽。每个GPU有4个EFA网卡,每个节点有8个GPU,这使得每个节点的总带宽达到100 × 4 × 8 = 3200 Gbps(400GB/s)。
为了确保我们启用了基于EFA的GPUDirect RDMA,你应该设置FI_PROVIDER=efa和NCCL_P2P_DISABLE=1环境变量。
虽然通过EFA的GPUDirect RDMA相比CPU介导的传输有了显著改进,每个GPU使用4个EFA网卡可达到约50 GB/s的速度,但我们还能更进一步吗?这就是NVLink发挥作用的地方。
8.2.5 通过NVLink
NVLink是NVIDIA的高速、直连的GPU-到-GPU互连技术,可在服务器内部实现快速的多GPU通信。H100采用了第四代NVLink (NVLink 4.0),通过18条链路为每个GPU提供900 GB/s的双向带宽,每条链路的双向速率为50 GB/s。
在DGX H100架构中,4个第三代NVSwitch以分层拓扑连接8个GPU,确保任意两个GPU对之间都有多条直接路径,且跳数恒定为1个NVSwitch,从而实现了3.6 TB/s的总双向NVLink网络带宽。
| NVLink 2.0 (Volta) | NVLink 3.0 (Ampere) | NVLink 4.0 (Hopper) | NVLink 5.0 (Blackwell) | |
|---|---|---|---|---|
| 带宽 | 300 GB/s | 600 GB/s | 900 GB/s | 1800 GB/s |
默认情况下,当可用时,NCCL会优先使用NVLink进行节点内GPU通信,因为它提供了同一台机器上GPU之间延迟最低、带宽最高的路径。但是,如果你的标志设置不当,可能会阻止NVLink的使用!😱
NVLink实现了直接的GPU-到-GPU内存访问,无需CPU或系统内存的参与。当NVLink不可用时,NCCL会回退到基于PCIe的GPUDirect P2P,或者在跨插槽PCIe传输性能不佳时使用共享内存(SHM)传输。
与EFA的约50 GB/s相比,NVLink 4.0的理论带宽为900 GB/s,我们预期在节点内通信上会有18倍的优势。为了在实践中验证这一点,我们测量了不同通信路径下的实际带宽:
结果毫无疑问地显示了NVLink的效率有多高:它达到了364.93 GB/s,而EFA为38.16 GB/s(快9倍,或双向18倍),CPU基线为3.24 GB/s(快112.6倍)。这些测量结果证实了为什么NCCL优先选择NVLink进行节点内GPU通信。为了进一步检验NVLink的性能,我们使用工具测量了所有GPU对之间同时双向拷贝的双向带宽:
测得的786 GB/s的双向带宽达到了NVLink 4.0理论900 GB/s规格的85%。使用NVLink完全绕过了CPU瓶颈(对于GPU-到-GPU通信而言)!
但这在集合通信模式中表现如何呢?让我们用NCCL测试中的all_reduce_perf基准来测量单个节点内的allreduce性能。
等等……我们达到了480 GB/s,这超过了NVLink 4.0的理论单向带宽450 GB/s 😮 这是什么黑魔法?怎么可能?
深入研究文档后,答案似乎在于NVLink SHARP (NVLS),这是NVIDIA的硬件加速集合操作技术。它们为单节点H100 GPU上的allreduce操作提供了大约1.3倍的加速!
对于alltoall操作,我们达到了340 GB/s,这与已发布的H100系统(使用NVLink 4.0)的基准测试结果相符。与allreduce不同,alltoall操作无法从NVLS硬件加速中受益,这就解释了为什么这里的速度是340 GB/s,而不是allreduce实现的480 GB/s。alltoall模式需要在所有GPU对之间进行更复杂的点对点数据交换,纯粹依赖于NVLink的基础带宽,而非NVSwitch的集合加速功能。
虽然NVLink在单个节点内提供了卓越的带宽,但训练前沿模型需要扩展到多个节点。这就引入了一个新的潜在瓶颈:节点间网络互连,其带宽远低于NVLink。
8.2.6 跨节点 GPU-到-GPU
一句话总结: 多节点GPU通信使用InfiniBand (400 Gbps)或RoCE (100 Gbps)等高速网络。Allreduce扩展性很好(在多节点间稳定在320-350 GB/s),从而能够支持大规模训练集群。由于算法复杂性,Alltoall的性能下降更剧烈。延迟从节点内的约13μs跃升至跨节点的55μs+。对于需要频繁进行all-to-all操作的MoE工作负载,NVSHMEM提供了异步的、由GPU发起的通信,其性能远超由CPU协调的传输。
随着模型规模超出单个节点的容纳能力,训练需要将计算分布到通过高速网络连接的多个节点上。在深入探讨基准测试之前,让我们先了解一下在多节点GPU集群中你会遇到的3种关键网络技术:
- 以太网 (Ethernet) 已从1 Gbps发展到100+ Gbps,并仍在HPC和数据中心集群中广泛使用。
- RoCE (RDMA over Converged Ethernet) 将RDMA功能引入以太网,使用ECN进行拥塞控制,而非传统的TCP机制。
- InfiniBand 是NVIDIA的行业标准交换网络,提供高达400 Gbps的带宽和亚微秒级的延迟,其RDMA支持通过GPUDirect RDMA实现直接的GPU-到-GPU内存访问,同时绕过主机CPU。
总结如下:
| 名称 | 以太网 (25–100 Gbps) | 以太网 (200–400 Gbps) | RoCE | Infiniband |
|---|---|---|---|---|
| 制造商 | 多家 | 多家 | 多家 | NVIDIA/Mellanox |
| 单向带宽 (Gbps) | 25–100 | 200–400 | 100 | 400 |
| 端到端延迟 (μs) | 10-30 | N/A | ~1 | <1 |
| RDMA | 否 | 否 | 是 | 是 |
对于AWS p5实例,我们使用弹性光纤适配器(EFA)作为网卡,每个GPU通过PCIe Gen5 x16通道连接到四个100 Gbps的EFA网卡。
如前所述,当GPU和网卡连接到同一个PCIe交换机时,GPUDirect RDMA使它们的通信能够仅通过该交换机进行。这种设置允许充分利用PCIe Gen5 x16的带宽,并避免涉及其他PCIe交换机或CPU内存总线。理论上,每个节点8个PCIe交换机 × 每个交换机4个EFA网卡 × 每个EFA网卡100 Gbps = 3200 Gbps (400GB/s)的带宽,这与我们在AWS p5规格中找到的带宽一致。那么在实践中表现如何呢?让我们通过在不同节点间运行相同的基准测试来找出答案!
8.2.6.1 带宽分析
- 点对点发送/接收操作在2-4个节点上达到约42-43 GB/s,但在5个以上节点时降至约21 GB/s。这种性能下降是因为当扩展到超过4个节点时,NCCL会自动将每个对等方的点对点通道数从2个减少到1个,这实际上将可用带宽利用率减半,而理论最大值仍为约50 GB/s(4个EFA网卡 × 12.5 GB/s)。
- all-reduce操作在单个节点内表现出色,达到480 GB/s的总线带宽。扩展到2个节点时,带宽几乎保持不变,为479 GB/s,之后在3-16个节点时稳定在320-350 GB/s左右。这种模式揭示了一个重要特性:虽然在跨越节点边界时由于从NVLink转换到节点间网络结构而出现初始下降,但随着我们增加更多节点,带宽几乎保持不变。
- all-to-all操作显示出更严峻的扩展挑战:从单个节点的344 GB/s开始,带宽在2个节点时降至81 GB/s,并继续下降到在更大集群中约为45-58 GB/s。这种更陡峭的下降反映了all-to-all模式对网络的密集需求,其中每个GPU必须与跨节点的所有其他GPU通信,从而产生比all-reduce操作严重得多的网络拥塞。
8.2.6.2 延迟分析
- 发送/接收操作在所有多节点配置中保持相对稳定的40-53 μs延迟,表明点对点通信延迟主要由基础网络往返时间决定,而非集群大小。
- All-reduce操作在单个节点内的延迟极小,为12.9 μs,但在2个节点时跃升至55.5 μs,并随着集群规模的扩大几乎呈线性增长,在16个节点时达到235 μs。
- All-to-all操作表现出类似的趋势,从单节点通信的7.6 μs开始,但在2个节点时攀升至60 μs,在16个节点时达到621 μs。all-to-all操作延迟的超线性增长表明,随着更多节点参与集合通信,网络拥塞和协调开销会复合增长。
8.2.7 互连故障排查
如果你的带宽低于预期,系统地检查以下几个方面:
-
库版本 过时的NCCL、EFA或CUDA库可能缺少关键的性能优化或错误修复。始终确认你正在运行最新的、兼容的通信库版本。
-
CPU亲和性配置 不正确的CPU亲和性设置可能导致不必要的跨NUMA流量,从而严重影响NCCL性能。每个GPU都应绑定到同一NUMA节点上的CPU,以最小化内存访问延迟。
-
网络拓扑和放置 了解你的网络拓扑是诊断性能问题的关键。云服务商的放置组虽有帮助,但并不能保证实例间的网络跳数最少。在现代数据中心的胖树(fat-tree)拓扑中,放置在不同顶级交换机下的实例会因路由路径中额外的网络跳数而经历更高的延迟和可能更低的带宽。
-
正确的环境变量 缺失或不正确的网络适配器环境变量会严重限制带宽利用率。像NCCL这样的通信库依赖特定的配置标志来启用最佳性能特性,如自适应路由、GPU发起的传输和适当的缓冲区大小。
-
容器特定注意事项 使用容器(如Docker)时,几个配置步骤对优化NCCL性能至关重要:
- 共享和固定内存:Docker容器默认的共享和固定内存资源有限。用
-shm-size=1g --ulimit memlock=-1启动容器以防止初始化失败。 - NUMA支持:Docker默认禁用NUMA支持。通过使用
-cap-add SYS_NICE调用Docker来启用NUMA支持。 - PCI拓扑发现:确保
/sys被正确挂载,以便NCCL能够发现GPU和网卡的PCI拓扑。
- 共享和固定内存:Docker容器默认的共享和固定内存资源有限。用
现在你已经知道如何调试GPU-CPU和GPU-GPU通信中的瓶颈了,让我们来看看一个通常被忽视的GPU通信部分——与存储层的通信!
8.2.8 GPU-到-存储
GPU和存储系统之间的连接常常被忽视,但它会显著影响训练效率。在训练期间,GPU需要持续从存储中读取数据(数据加载,特别是对于包含大型图像/视频文件的多模态数据),并定期将模型状态写回存储(即检查点)。对于现代大规模训练任务,如果这些I/O操作没有得到适当优化,就可能成为瓶颈。
一句话总结:GPU-存储I/O通过数据加载和检查点影响训练。GPUDirect Storage (GDS)实现了GPU到存储的直接传输,绕过CPU以获得更好的性能。即使我们的集群没有启用GDS,本地NVMe RAID(8×3.5TB硬盘组成的RAID 0)也能提供26.59 GiB/s的速度和337K IOPS(比网络存储快6.3倍),使其成为检查点的理想选择。
GPUDirect Storage 一个很自然的问题是,GPU能否直接访问NVMe硬盘而无需CPU的介入?答案是肯定的,通过GPUDirect Storage (GDS)。
GPUDirect Storage是NVIDIA GPUDirect技术家族的一部分,它在存储(本地NVMe或远程NVMe-oF)和GPU内存之间建立了一条直接数据路径。它通过允许存储控制器附近的DMA引擎直接将数据移入或移出GPU内存,从而消除了通过CPU“反弹缓冲区”造成的不必要的内存拷贝。这降低了CPU开销、减少了延迟,并显著提高了数据密集型工作负载的I/O性能。
块存储设备 通过分析系统中的块设备,我们可以观察到:
- 一个Amazon EBS卷作为根文件系统。
- 八个NVMe硬盘(nvme1n1到nvme8n1)被配置成一个名为MY_RAID的RAID阵列。
- 这个RAID阵列被暴露为
/dev/md0,格式化为XFS,并挂载在/scratch,可用空间为28TB(8x3.5TB)。
网络存储 除了本地NVMe存储,系统还可以访问网络附加存储系统,如FSx Lustre或WekaFS。
存储带宽基准测试 为了理解每个存储系统的性能特征,我们可以对其读写速度进行基准测试。基准测试评估了吞吐量、延迟、IOPS,以及不同传输方式(GPU_DIRECT vs. CPU_GPU vs. CPUONLY)的效率。
基准测试揭示了我们四种存储系统之间巨大的性能差异:
- /scratch (本地NVMe RAID)以26.59 GiB/s的吞吐量和337K IOPS遥遥领先,吞吐量比FSx快6.3倍,IOPS高6.6倍。这个由8个3.5TB NVMe硬盘组成的本地RAID阵列提供了最低的延迟,并且随着线程数的增加扩展性极佳。
- /fsx (WekaFS)提供了坚实的网络存储性能,为4.21 GiB/s和51K IOPS,是需要合理性能的共享数据的最佳选择。
- /admin (FSx Lustre)和/root (EBS)文件系统的吞吐量表现相似,约为1.1 GiB/s,但在IOPS能力上差异显著。Admin的IOPS性能(17K)比Root(730)好得多,使其更适合有许多小操作的工作负载。Root糟糕的IOPS性能证实了它只适合大型顺序操作。
- 最佳配置模式:在所有存储类型中,最大吞吐量出现在1M的I/O大小时,而最大IOPS出现在测试的最小尺寸(64K)时。这种经典的权衡意味着需要根据工作负载特性在原始带宽(大I/O)和操作并发性(小I/O)之间做出选择。
8.2.9 总结
如果你坚持看到了这里,恭喜你!你现在对我们训练基础设施中的存储层级结构以及不同组件如何交互有了全面的了解。但我们希望你带走的核心观点是:识别瓶颈是将理论知识转化为实际优化的分水岭。
在本指南中,我们测量了技术栈中每一层的实际带宽:单个GPU内HBM3的3TB/s,节点内GPU间NVLink的786 GB/s,CPU-GPU传输的PCIe Gen4 x8的14.2 GB/s,节点间网络的42 GB/s点对点通信,以及从26.59 GB/s(本地NVMe)到1.1 GB/s(共享文件系统)不等的存储系统。这些测量结果揭示了你的训练流程会在哪里变慢,对于实现高的模型FLOPs利用率(MFU)至关重要。
然而,原始带宽数据本身并不能说明全部问题。现代训练系统可以将计算与通信重叠,有效地将通信成本隐藏在计算操作之后。这种并行化有助于缓解瓶颈,即使互连速度较慢。
我们可以将所有基准测试的测量结果汇总起来,形成一个清晰的视图,它展示了随着距离GPU核心越来越远,带宽是如何急剧下降的。
现在我们知道了如何识别硬件和软件设置中的瓶颈,接下来让我们看看如何更进一步,确保我们拥有一个能够稳定运行数月的弹性系统。
8.3 构建弹性的训练系统
拥有快速的硬件只是拥有良好稳定的大模型训练基础设施的入场券。要从业余训练者走向专业,我们需要超越原始速度,关注那些不那么光鲜但却至关重要的基础设施部分,它们能让整个训练体验更顺畅,停机时间最少。
8.3.1 节点健康监控与替换
拥有足够多的快速GPU对训练很重要,但由于大模型训练通常持续数周或数月,而非几天,因此长期跟踪GPU的健康状况变得至关重要。
- 前期测试:在启动训练前,我们使用多种工具对GPU进行了全面的诊断,包括内部的压力测试工具和NVIDIA的DCGM诊断工具。这些前期测试帮助我们揪出了两个有问题的GPU,避免了它们在训练中引发故障。
- 节点预留:我们在Slurm管理的集群上为整个运行预留了固定的48个节点。这种设置使我们能够长期跟踪完全相同的节点的健康和性能。我们还预留了一个备用节点(就像汽车的备胎),一旦有节点出现故障,我们可以立即换上,而无需等待维修。
- 持续监控:在训练期间,我们跟踪所有节点的关键指标,如GPU温度、内存使用、计算利用率和吞吐量波动。我们使用Prometheus收集所有GPU的DCGM指标,并在Grafana仪表盘中进行实时监控。一个Slack机器人会在任何节点出现可疑行为时提醒我们,让我们能在硬件故障导致整个训练崩溃前主动更换它。
热量现实检查:当GPU变慢时
市场宣传的规格都假设有完美的散热,但现实要复杂得多。当GPU过热时,它们会自动降低时钟速度,即使在设计良好的系统中,性能也会降到理论最大值以下。我们通过监控NVIDIA DCGM的DCGM_FI_DEV_CLOCK_THROTTLE_REASONS指标来检测热节流。
热节流不仅会损害受影响的GPU,它还会波及整个分布式训练设置。在我们的测试中,我们观察到一个节流的节点如何显著影响集合通信性能。木桶效应在这里体现得淋漓尽致:你的速度取决于最慢的那个节点。
👉 关键教训:在投入长期训练运行之前,使用前面提到的工具对你的硬件进行压力测试,以识别热量和功率限制。使用DCGM遥测持续监控温度,并为实际的热量限制做好规划。
8.3.2 检查点管理
检查点是我们长期训练运行中的安全网。我们定期保存它们有三个实际原因:从故障中恢复、通过评估监控训练进度,以及与社区分享中间模型用于研究。其中,恢复方面最为重要。
在实现你的恢复机制时,要记住两个重要的细节:
- 检查点保存应在后台进行,不影响训练吞吐量。
- 注意你的存储空间。在一个24天的运行中,每4小时保存一次意味着约144个检查点。对于大型模型和优化器状态,这会迅速累积。我们的策略是,一次只在本地存储一个检查点(最新保存的),其余的卸载到S3,以避免占满集群存储。
一个惨痛的教训:在我们过去的一次大规模运行中,脚本末尾一个用于旧吞吐量测试的rm -rf $CHECKPOINT_PATH命令被遗留下来。这个破坏性命令只有在Slurm作业实际完成时才会触发,而这在之前的重启中从未发生过。幸运的是,我们有前一天保存的检查点,只损失了一天的重训时间。教训是明确的:永远不要在生产脚本中留下破坏性命令,并且在保存后立即自动化检查点备份,而不是依赖手动干预。
8.3.3 自动化评估
手动运行评估很快就会成为瓶颈。运行基准测试、跟踪和绘制每次运行的结果会累积成巨大的开销。解决方案?从一开始就自动化一切。
我们使用LightEval在nanotron检查点上运行评估。每个保存的检查点都会在集群上触发一个评估作业。结果直接推送到Weights & Biases,所以我们只需打开仪表盘,观察曲线的演变。这为我们节省了大量时间,并使评估跟踪在整个运行期间保持一致。
如果你在训练设置中只能自动化一件事,那就自动化评估。
8.4 优化训练吞吐量
8.4.1 我们需要多少GPU?
在讨论了所有规格和基准之后,你仍然需要解决一个实际问题:你到底应该租用或购买多少个GPU?
确定合适的GPU数量需要在训练时间、成本和扩展效率之间取得平衡。我们使用的框架如下:
基本规模估算公式:
GPU数量 = 总所需FLOPs / (单个GPU吞吐量 × 目标训练时间)
这个公式将问题分解为三个关键部分:
- 总所需FLOPs:训练你的模型所需的计算工作量。
- 单个GPU吞吐量:每个GPU实际能提供的每秒FLOPs(不是理论峰值!)。
- 目标训练时间:你愿意等待多长时间来完成训练。
关键在于:你需要估算真实的吞吐量,而不是峰值规格。这意味着要考虑模型FLOPs利用率(MFU):你实际达到的理论峰值性能的百分比。
对于我们的3B参数模型SmolLM3,我们的计算如下:
- 模型大小:3B参数
- 训练token数:11万亿
- 目标训练时间:约4周
- 预期MFU:30%(基于类似规模的实验)
首先,我们使用标准的6N FLOPs/token近似法计算总FLOPs(N=参数量): 总FLOPs = 6 × 3×10⁹ 参数 × 11×10¹² tokens = 1.98×10²³ FLOPs
考虑到30%的MFU,我们每个GPU的有效吞吐量为: 有效吞吐量 = 720×10¹² FLOPs/sec × 0.30 = 216×10¹² FLOPs/sec
现在代入我们的规模估算公式: GPU数量 = 1.98×10²³ FLOPs / (216×10¹² FLOPs/sec × 4周 × 604,800秒/周) ≈ 379个GPU
这个计算结果指向了375-400个H100 GPU,最终我们获得了384个H100,这个数量与我们的并行策略非常吻合,并给了我们一个现实的4周时间线,同时为节点故障和重启等意外问题留出了一些缓冲。
8.4.2 找到最优的并行配置
一旦你准备好了GPU,下一个挑战就是配置它们以实现高效训练。为此,并行策略变得至关重要。我们遵循“超大规模剧本”(Ultra-Scale Playbook)的方法来寻找最优的训练配置,该方法将问题分解为三个连续的步骤:首先确保模型能装入内存,然后达到目标批量大小,最后优化以获得最大吞吐量。
8.4.3 步骤1:让一个训练步骤能装入内存
第一个问题很简单:我们的SmolLM3 3B模型能装进单个H100的80GB内存吗?我们使用nanotron的predict_memory工具来估算内存消耗。
结果显示,我们已经非常接近80GB的上限。这意味着我们需要某种形式的并行策略来减少每个GPU的内存占用,无论是张量并行(TP)、流水线并行(PP),还是ZeRO优化器分片。
8.4.4 步骤2:达到目标全局批量大小
现在我们知道模型在某种并行策略下可以装入内存,我们需要确定如何达到我们约200万token的目标全局批量大小(GBS)。这个约束给了我们第一个方程:
GBS = DP × MBS × GRAD_ACC × SEQLEN ≈ 2M tokens
同时,我们还有来自384个H100的硬件约束:
DP × TP × PP = 384
这两个方程定义了我们的搜索空间。我们需要找到满足这两个约束条件,同时又能最大化训练吞吐量的值。
8.4.5 步骤3:优化训练吞吐量
我们的硬件设置呈现出两种截然不同的互连类型:用于节点内通信的NVLink(900 GB/s)和用于节点间通信的EFA(约50 GB/s)。这种拓扑结构自然地建议我们使用至少两种并行形式来匹配我们的网络特性。
在排除了流水线并行(对于我们这个相对较小的3B模型,通信开销过大)和高于ZeRO-0的ZeRO等级(all-gather和reduce-scatter操作损害了吞吐量)之后,我们将搜索空间大大缩小,专注于结合数据并行和适度张量并行的配置。
👉为了评估每种配置,我们运行5次迭代的基准测试,并记录每个GPU每秒处理的token数(tok/s/gpu),这最终是我们关心的指标。
在系统地对nanotron中可用的选项进行基准测试后,我们确定了DP = 192的配置,它利用节点间的EFA带宽进行数据并行的梯度同步。对于张量并行,我们选择了TP = 2,将张量并行的通信限制在单个节点内,以充分利用NVLink的高带宽。我们的微批量大小(Micro Batch Size)为3,在内存使用和计算效率之间取得了平衡。最后,我们选择了ZeRO-0,即不进行优化器状态分片。
这个配置达到了我们约200万token的目标全局批量大小(192 × 3 × 1 × 4096 ≈ 2.3M),同时在我们384个H100的集群上最大化了吞吐量。
9. 总结
我们的旅程始于一个简单的问题:在2025年,训练一个高性能的大语言模型(LLM)究竟需要什么?在走完从预训练到后训练的完整流程后,我们向你展示的不仅仅是技术,更是让这些技术行之有效的方法论。
规模化预训练。我们介绍了“训练罗盘”框架,用于决策是否要从头训练模型,然后展示了如何将目标转化为具体的架构决策。你已经看到如何建立可靠的消融实验流程,如何独立测试变更,以及如何从几十亿token的实验扩展到数万亿token的运行。我们记录了规模化训练中可能出现的各种基础设施挑战(吞吐量崩溃、数据加载器瓶颈、细微的bug),以及如何通过监控和系统性的风险规避来及早发现并快速调试它们。
后训练的实践。我们展示了从一个基础模型到一个生产级助理需要其自身系统化的方法:在开始任何训练前先建立评估体系,迭代优化监督微调(SFT)的数据混合,应用偏好优化,并可选择地通过强化学习(RL)进一步提升。你已经看到,“感觉测试”(vibe testing)如何捕捉到指标遗漏的bug,聊天模板如何悄无声息地破坏指令遵循能力,以及为什么在后训练中,数据混合的平衡与在预训练中同等重要。
在贯穿这两个阶段的过程中,我们反复回到同样的核心洞见:通过实验验证一切,一次只改变一件事,预料到规模化会以新的方式打破常规,并让你的用例驱动决策,而不是追逐每一篇新论文。遵循这一流程,我们训练出了SmolLM3:一个具备竞争力的、支持长上下文的3B多语言推理模型。一路上,我们学到了很多关于什么可行、什么会出问题,以及当事情出错时如何调试。我们尽力将这一切都记录下来,无论是成功还是失败。
接下来呢?
这篇博客涵盖了现代LLM训练的基础知识,但这个领域发展迅速。这里有一些深入探索的途径:
- 亲手做实验。阅读关于消融实验的文章很有用,但亲手运行自己的实验才能教会你什么才是真正重要的。选择一个小模型,建立评估体系,然后开始实验吧。
- 阅读源代码。像nanotron、TRL等训练框架都是开源的。理解它们的实现细节能揭示出论文中一笔带过的关键之处。
- 关注最新工作。近期最先进模型的论文展示了该领域的发展方向。文末的参考文献部分包含了我们精选的有影响力的论文和资源。
我们希望这篇博客能帮助你更清晰、更自信地着手你的下一个训练项目,无论你是在一个推动前沿的大型实验室,还是一个解决特定问题的小团队。
现在,去训练点什么吧。当你凌晨两点发现损失函数(loss)神秘地飙升时,请记住:每个伟大的模型背后都有一堆调试的故事。
愿开源与开放科学的原力与你同在!
Enjoy Reading This Article?
Here are some more articles you might like to read next: