ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

模型架构研究:Transformer、Scaling Law与手写实践中的关键细节

模型架构研究:Transformer、Scaling Law与手写实践中的关键细节

1. 从一张手绘草图说起:模型架构到底在研究什么

前阵子整理硬盘,翻出来一张两年前的手绘草图,画的是当时想改的一个注意力模块。线条歪歪扭扭,旁边还写着"这样真的能收敛吗"几个字。现在回头看,那个设计其实挺幼稚的,但它记录了一个很真实的状态:做模型架构研究的人,大部分时间都在跟"不确定"打交道。

这篇东西不是教程,也不是综述,就是一些碎碎念。围绕模型架构、LLM、Transformer、Scaling Law这几个关键词,聊聊我在实际折腾过程中形成的一些判断。如果你刚接触 Transformer,想搞清楚它到底怎么回事;或者你已经能跑通训练,但在架构设计上总觉得缺一套自己的方法论,那这些内容应该能给你一些参考。我不会给你一个"标准答案",因为架构研究这件事本身就没有标准答案,但我会尽量把每个判断背后的逻辑讲清楚,让你知道我是怎么想的,以及为什么这么想。

先说一个基本认知:模型架构研究的核心矛盾,是表达能力与训练可行性之间的平衡。你想让模型更强,就得加参数、加深度、加复杂度,但每加一点,训练难度、显存占用、推理延迟都会跟着涨。Transformer 之所以能统治这么多年,不是因为它理论最优,而是因为它在这个平衡点上找到了一个极其优雅的位置——注意力机制天然可并行,残差连接让深层网络可训练,层归一化稳定了分布。这些设计单独看都不复杂,但组合在一起,就形成了一个"能 scale"的结构。

而Scaling Law的发现,本质上是在说:只要你把模型做大、数据做多、算力堆够,性能就会可预测地提升。这个结论听起来简单,但它把架构研究的重心从"设计一个更聪明的结构"部分转移到了"设计一个更容易 scale 的结构"。因为如果 scale 就能涨点,那架构的职责就变成了"别拖 scale 的后腿"。

2. Transformer 里那些容易被忽略的设计细节

2.1 位置编码不只是"加个向量"那么简单

很多人第一次学 Transformer 的时候,对位置编码的理解就是"因为注意力没有顺序信息,所以加一个位置向量进去"。这个理解没错,但太浅了。位置编码的设计直接影响到模型能不能外推到更长的序列,以及它学到的位置关系是不是合理的。

原始 Transformer 用的是正弦位置编码,公式是:

PE(pos, 2i) = sin(pos / 10000^(2i/d_model)) PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))

这个设计有个很妙的性质:对于任意固定的偏移量 k,PE(pos+k) 可以表示为 PE(pos) 的线性函数。这意味着模型理论上可以通过线性变换来学习相对位置关系。但实际训练中,模型能不能真的学到这个线性关系,是另一回事。

后来 RoPE(旋转位置编码)出来了,思路是把位置信息编码成旋转矩阵,直接作用在 query 和 key 上。这样做的好处是,注意力分数天然就包含了相对位置信息,而且外推性更好。我在实际项目里对比过正弦编码和 RoPE,在序列长度不超过训练长度的时候,两者差距不大;但一旦推理长度超过训练长度,RoPE 的衰减明显更平缓。

还有一个容易被忽略的点:位置编码的波长范围。正弦编码的最小波长是 2π,最大波长是 10000·2π。如果你的序列长度远小于最大波长,那高频部分基本没用到;如果远大于,那低频部分会重复。所以有些工作会调整这个 base 值,让波长范围匹配实际序列长度。这个细节在长文本场景下特别重要。

2.2 残差连接和层归一化的顺序问题

Transformer 原始论文里,残差连接是放在子层外面的,层归一化放在子层里面,也就是 Post-LN 结构:

x = LayerNorm(x + Sublayer(x))

但后来大家发现,Post-LN 在深层网络里训练很不稳定,需要 warmup 才能收敛。于是 Pre-LN 变成了主流:

x = x + Sublayer(LayerNorm(x))

Pre-LN 的好处是梯度可以直接通过残差路径回传,不需要经过层归一化,训练更稳定。但代价是,Pre-LN 的表达能力理论上比 Post-LN 弱一点,因为归一化后的输入进入子层,信息有损失。

我自己的经验是:如果你在训练一个不超过 24 层的模型,Post-LN 加 warmup 完全能用,而且最终效果可能略好;但如果你要上 48 层甚至更深,Pre-LN 是更稳妥的选择。现在很多大模型用的是 Pre-LN 的变体,比如在残差分支上加一个缩放因子,或者用 RMSNorm 替代 LayerNorm,都是为了在稳定性和表达能力之间找更好的平衡。

2.3 注意力头的数量不是越多越好

多头注意力是 Transformer 的核心设计之一。直观理解是,不同的头可以关注不同的模式——有的头关注语法关系,有的头关注语义相似度,有的头关注位置邻近。但实际训练出来的模型里,很多头是冗余的,甚至有些头可以被剪掉而不影响性能。

我做过一个实验:在一个 12 层的模型里,逐层剪掉注意力头,看性能变化。结果发现,浅层的头冗余度最高,剪掉 30% 几乎没影响;深层的头更关键,剪掉 10% 就开始掉点。这说明浅层学到的特征比较通用,多个头之间有重叠;深层学到的特征更特化,每个头都有独特作用。

那头的数量怎么定?常见的做法是d_model / num_heads = 64或128。这个比例不是随便定的。如果每个头的维度太小,注意力分布会过于集中,学不到丰富的模式;如果太大,又失去了多头的意义。我试过d_model=512, num_heads=16(每头 32 维),效果明显不如num_heads=8(每头 64 维)。所以每头 64 维是一个比较稳的默认值,除非你有特别的理由,否则不建议偏离太多。

3. Scaling Law 告诉我们的和没告诉我们的

3.1 幂律关系背后的直觉

Scaling Law 的核心结论是:模型性能(通常用 loss 衡量)与模型参数量、数据量、算力之间呈幂律关系。用公式表示大概是:

L(N, D, C) = a·N^(-α) + b·D^(-β) + c·C^(-γ) + L∞

其中 N 是参数量,D 是数据量,C 是算力,L∞ 是不可约损失。这个公式的意思是,随着这三个量增加,loss 会下降,但下降速度越来越慢,最终趋近于一个下界。

这个结论的直觉解释是:模型在学习数据分布,数据分布本身有熵,模型容量有限,所以总有一部分信息学不到。增加参数量相当于增加容量,增加数据量相当于提供更多样本,增加算力相当于让优化更充分。三者缺一不可。

但这里有个容易被忽略的点:幂律关系是在一定范围内成立的。当模型小到一定程度,或者数据少到一定程度,关系会偏离幂律。而且不同架构的系数不一样,有的架构在同样参数量下就是比别人好,这就是架构研究的价值所在。

3.2 Chinchilla 最优与实际情况的差距

Chinchilla 那篇论文给出了一个"计算最优"的配比:在给定算力下,模型参数量和训练 token 数应该大致按 1:20 的比例增长。也就是说,一个 10B 的模型,应该用 200B token 来训练。

但实际中,很多模型并没有严格遵循这个比例。原因有几个:第一,推理成本。训练是一次性的,推理是持续的。一个更小的模型如果训练更充分,推理成本更低,长期看更划算。第二,数据质量。Chinchilla 假设数据是无限且同质的,但实际中高质量数据是有限的,你不可能无限增加 token 数而不遇到数据瓶颈。第三,架构差异。不同架构的数据效率不一样,有的架构用更少数据就能达到同样效果。

我自己的判断是:Chinchilla 最优是一个理论参考点,不是必须遵守的教条。如果你有充足的算力但数据有限,那就把模型做小一点、训练充分一点;如果你数据很多但算力有限,那就把模型做大一点、训练轮数少一点。关键是理解你面临的约束是什么。

3.3 架构创新在 Scaling 时代的角色

有人会说,既然 scale 就能涨点,那架构研究还有什么意义?直接堆参数不就行了?

这个观点忽略了一个事实:Scaling Law 的系数是架构决定的。同样参数量下,好的架构可以用更少数据、更少算力达到同样效果。而且,当 scale 到一定程度,某些架构会碰到瓶颈——比如训练不稳定、梯度消失、注意力退化。这些问题不是靠加参数能解决的,必须从架构层面入手。

举个例子:MoE(混合专家)架构。它的核心思想是,不是所有参数都参与每次计算,而是根据输入动态激活一部分专家。这样可以在总参数量很大的情况下,保持计算量可控。DeepSeek、Mixtral 这些模型都用了 MoE,效果很好。但 MoE 也带来了新的问题:负载均衡、专家坍缩、通信开销。这些都是架构研究要解决的。

所以我的看法是:Scaling Law 改变了架构研究的优先级,但没有取消架构研究的必要性。以前大家追求"更聪明的结构",现在大家追求"更容易 scale 的结构"。目标变了,但工作还在。

4. 从零手写一个 Transformer 时踩过的坑

4.1 维度对不齐是最常见的错误

第一次手写 Transformer 的时候,我花了整整一个下午在调维度。注意力的 Q、K、V 投影,多头拆分和合并,残差连接的形状匹配,每一处都可能出错。而且这种错误往往不会报错,只会让 loss 不下降,你根本不知道哪里出了问题。

我的建议是:在写代码之前,先把每个张量的形状写在纸上。比如输入是(batch, seq_len, d_model),经过 Q 投影变成(batch, seq_len, d_model),拆成多头变成(batch, num_heads, seq_len, d_head),注意力分数是(batch, num_heads, seq_len, seq_len),加权求和后是(batch, num_heads, seq_len, d_head),合并多头变回(batch, seq_len, d_model)。每一步都写清楚,写代码的时候对照着检查。

还有一个容易错的地方是 mask 的形状。因果 mask 通常是(1, 1, seq_len, seq_len)或者(seq_len, seq_len),广播到(batch, num_heads, seq_len, seq_len)。如果 mask 的形状不对,广播会出错,或者 mask 不生效。我习惯在 mask 之后打印一下注意力分数的最大值和最小值,确认被 mask 的位置确实是负无穷。

4.2 初始化方式对训练稳定性的影响

Transformer 的初始化很关键。原始论文用的是 Xavier 初始化,但后来大家发现,对于深层 Transformer,Xavier 会导致激活值逐层放大或缩小。GPT-2 用的是std=0.02的正态初始化,并且对残差分支的输出做了缩放。

我自己的经验是:残差分支的最后一层投影,初始化标准差要调小。因为残差连接是累加的,如果每个子层的输出都很大,深层激活值会爆炸。具体来说,如果模型有 N 层,残差分支的初始化标准差可以设为0.02 / sqrt(2N)。这个缩放因子能让激活值的方差在深层保持稳定。

另外,embedding 层的初始化也值得注意。如果 embedding 初始化太大,第一层的激活值就会很大;如果太小,梯度会很小。我一般用std=0.02,和 GPT-2 保持一致。

4.3 学习率 warmup 不是可选项

Post-LN 的 Transformer 如果不加 warmup,训练很容易发散。原因是深层网络的梯度在初期很不稳定,大的学习率会让参数更新过猛,进入坏的区域。Warmup 的作用是让模型在初期用小学习率"热身",等梯度稳定后再放大。

Warmup 的步数怎么定?常见做法是总步数的 1% 到 5%。比如你训练 100k 步,warmup 用 2k 到 5k 步。我试过更短的 warmup,比如 500 步,在浅层模型上能用,但深层模型还是会抖。所以如果你不确定,就用 5% 的 warmup,稳一点。

还有一个细节:warmup 之后的衰减策略。常见的是余弦衰减,从最大学习率慢慢降到 0。也有用线性衰减的。我对比过,余弦衰减在后期收敛更平滑,最终 loss 略低一点。但差距不大,选哪个都行。

5. 架构研究中的一些方法论思考

5.1 消融实验怎么做才有意义

架构研究离不开消融实验。你改了一个模块,想知道它有没有用,就得做对比。但消融实验很容易做得没意义,比如只跑一个 seed,或者只在一个数据集上验证。

我的做法是:至少跑 3 个 seed,取均值和方差。如果改进带来的提升小于方差,那这个改进可能只是噪声。另外,消融实验要控制变量。你改了注意力机制,就不要同时改初始化方式,否则你分不清是哪个改动带来的效果。

还有一个容易被忽略的点:消融实验的 baseline 要足够强。如果你的 baseline 本身就没调好,那任何改动都可能看起来有提升。我习惯先把 baseline 调到接近论文报告的水平,再开始做消融。

5.2 从失败实验中学到的东西往往更多

我做过很多失败的实验。有的想法理论上很漂亮,但实际训练就是不收敛;有的改动在小模型上有效,放大到大规模就失效了。这些失败实验虽然没产出论文,但让我对架构的理解更深了。

比如有一次,我想用一个可学习的温度系数来调节注意力分布的锐度。小模型上效果很好,loss 降得更快。但放大到 1B 参数时,温度系数学到了一个极端值,注意力变成了 one-hot,模型完全失去了泛化能力。这个失败让我意识到:有些机制在小规模下有效,是因为小模型本身容量有限,需要更强的归纳偏置;但大模型容量足够,过强的归纳偏置反而限制了它。

所以我现在做实验,会尽量在小模型上快速筛选想法,但最终一定要在大模型上验证。小模型的结果只能作为参考,不能作为结论。

5.3 读论文时要关注"没写出来的部分"

很多论文只报告成功的结果,不报告失败的尝试。但那些失败的尝试往往更有信息量。我读论文的时候,会特别关注几个地方:实验设置的细节、超参数的搜索范围、baseline 的选择、以及作者在 limitation 部分提到的问题。

比如,如果一篇论文的 baseline 是一个很弱的模型,那它的改进可能被高估了。如果一篇论文只在短序列上验证,那它的长序列外推能力就存疑。如果一篇论文没有报告方差,那它的提升可能不显著。

这些"没写出来的部分",往往决定了这篇论文的方法能不能真正用起来。

6. 关于 AGI 和多模态的一些零散想法

6.1 AGI 不是单一架构能解决的问题

现在很多人把 AGI 和 LLM 绑在一起谈,好像只要把 LLM 做得足够大,AGI 就自然实现了。我不这么看。

LLM 的核心能力是序列建模和模式补全。它在语言任务上表现很好,是因为语言本身就是高度结构化的序列。但智能不仅仅是语言能力。规划、推理、因果理解、世界模型,这些能力可能需要不同的架构组件。

我倾向于认为,AGI 需要的是一个系统,而不是一个模型。这个系统里可能有 LLM 作为语言接口,有专门的规划模块,有记忆机制,有工具调用能力。现在的一些 agent 框架已经在往这个方向走了,但离真正的 AGI 还很远。

6.2 多模态的关键是表征对齐

多模态模型的核心挑战,是如何把不同模态的信息映射到同一个表征空间。图像是连续的像素,文本是离散的 token,音频是时序信号。要让模型理解"一只猫"的图片和"一只猫"的文字是同一个概念,就需要一个共享的表征空间。

现在主流做法是用一个投影层,把视觉特征映射到语言模型的 embedding 空间。但这样做的问题是,投影层学到的对齐可能很浅,模型只是学会了"这张图对应这些词",而不是真正理解了视觉概念。

我觉得更有希望的方向是,在预训练阶段就让不同模态共享注意力,而不是事后对齐。这样模型可以在底层就学到跨模态的对应关系。但这样做计算成本很高,而且需要大量的配对数据。

6.3 架构研究的下一步可能在哪里

如果让我猜,我觉得下一步的突破可能在几个方向:一是动态计算,让模型根据输入难度自适应地决定计算量,而不是固定深度;二是记忆机制,让模型有长期记忆,而不是只依赖上下文窗口;三是模块化,让不同模块负责不同能力,可以独立训练和组合。

这些方向都有一些初步工作,但还没有形成统一范式。Transformer 统治了这么多年,也许下一个范式已经在某个实验室里萌芽了,只是我们还没看到。

7. 一些实操层面的建议

如果你正在做模型架构相关的工作,我有几个建议。

第一,先把一个标准的 Transformer 手写一遍。不要用现成的库,自己从零实现注意力、残差、层归一化、位置编码。这个过程会让你对每个细节都有感觉。我见过很多人,能用 HuggingFace 跑模型,但问他注意力怎么算的,说不清楚。这种状态下,你很难做出有价值的架构改进。

第二,建立自己的实验基线。找一个你熟悉的任务和数据集,把标准 Transformer 调到最好,记录下所有超参数和结果。以后任何新想法,都先在这个基线上验证。这样你才能判断一个改进是真的有效,还是只是调参带来的。

第三,多读代码,少读公式。论文里的公式往往是简化版,实际实现有很多细节。比如 LayerNorm 的 epsilon 取多少,注意力 mask 怎么加,位置编码怎么插,这些细节在论文里经常一笔带过,但实际影响很大。读开源代码是理解这些细节的最好方式。

第四,保持对失败的记录。我有个习惯,每次实验失败都会记下来:改了什么、结果如何、可能的原因。这些记录后来成了我最有价值的资料。因为成功的实验往往有运气成分,但失败的实验能告诉你边界在哪里。

第五,不要迷信大模型。小模型上做实验,成本低、迭代快。很多架构问题在小模型上就能暴露出来。当然,最终验证一定要在大模型上做,但筛选阶段用小模型完全没问题。

8. 最后聊几句个人体会

做模型架构研究这几年,最大的感受是:这是一个需要耐心的领域。一个想法从产生到验证,可能要几周甚至几个月。中间会遇到各种意外:训练不收敛、结果不可复现、改进不显著。很多时候你会怀疑自己是不是在浪费时间。

但也有一些时刻,让你觉得一切都值得。比如你改了一个小模块,loss 曲线突然变得更平滑了;比如你发现了一个别人没注意到的细节,实验验证确实有效;比如你读了一篇论文,突然想通了之前困惑很久的问题。

这些时刻不多,但足够支撑你继续走下去。

模型架构研究没有捷径。你需要理解原理,需要动手实现,需要做实验,需要读论文,需要跟人交流。但如果你真的对这件事感兴趣,那这个过程本身就是有意义的。因为你在探索的是智能的本质,哪怕只是很小的一步。

提示:架构研究中的很多结论都有适用边界。在小模型上有效的改动,放大后可能失效;在短序列上有效的改动,长序列上可能出问题。做任何判断之前,先问自己:这个结论是在什么条件下成立的?

就写这么多吧。都是一些零散的想法,不一定对,但都是真实踩过的坑和想明白的事。希望对你有用。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进