语言模型架构与超参数
把近几年发布的大语言模型摊开来看,有个现象很明显:名字、训练语料、参数规模和工程系统各不相同,但 dense Transformer 的主体结构反而越来越趋同。今天所谓的“现代语言模型架构”,往往已经不是原始 Transformer 的直接复刻,而是一套经过大量模型验证后的保守组合:Pre-Norm、RMSNorm、无 bias 线性层、RoPE、SwiGLU / GeGLU,以及相当固定的维度比例。11 这里讨论的是 dense decoder-only LM 的常见结构。MoE、SSM、混合注意力等路线会引入额外变化,但它们通常仍然保留不少 Transformer block 的设计经验。
这节课真正关心的,不是“某一个架构技巧为什么一定正确”,而是更实际的问题:当我们要实现或训练一个语言模型时,哪些选择已经几乎成为默认项?哪些地方还存在模型之间的差异?哪些超参数看似随意,实际上有经验共识?弄清这些问题之后,我们不必每次都从原始 Transformer 重新出发,而是能知道哪些地方该保守,哪些地方值得实验。
从原始 Transformer 到现代变体
原始 Transformer 中的很多设计,今天看起来已经有些“古典”:位置编码使用正弦和余弦的绝对位置编码,前馈网络使用 ReLU,归一化层放在残差连接之后,LayerNorm 中带有均值、方差、缩放和平移参数。这套结构当然是可行的,它奠定了 Transformer 的基本形式;只是模型规模变大、训练长度变长之后,硬件利用率和稳定性问题越来越突出,许多细节便被逐渐替换。
一个简化的现代 decoder-only Transformer block 可以写成:
这就是所谓的 Pre-Norm 结构。与此同时,Norm 往往从 LayerNorm 换成 RMSNorm,FFN 从 ReLU / GeLU 换成带门控的 SwiGLU 或 GeGLU,位置编码从 additive positional embedding 换成 RoPE,并且许多线性层不再使用 bias 项。
这些改动看起来分散,背后的动机却相当一致:尽量让 residual stream 成为稳定的信息主干,把容易破坏数值稳定性的操作移到旁路中,同时减少不划算的参数移动和运行时开销。现代大模型训练里,真正昂贵的不只是 FLOPs,还有显存访问、激活搬运、KV cache 读写和跨设备通信。一个“参数很少但每层都要反复读写”的 bias 或 norm 参数,在小模型里无所谓,到了大模型和长序列推理中就可能变成值得优化的细节。
Pre-Norm:保护残差主干
原始 Transformer 使用的是 Post-Norm:
这个写法很自然:每经过一个子层就做一次归一化,输出分布看起来更加规整。代价也在这里,归一化层直接压在残差主路径上。层数变深以后,梯度需要穿过大量 Norm 操作,训练初期更容易出现梯度衰减、梯度尖峰或不稳定。
现代语言模型几乎都转向了 Pre-Norm:
直观地看,Pre-Norm 保留了一条更干净的 identity mapping。即使子层一开始还没有学好,残差主干也能让信息和梯度比较顺畅地穿过网络。大规模语言模型尤其需要这一点:模型很深,训练步数很长,早期一点不稳定都可能被放大。BERT 这类较早的 encoder 模型使用 Post-Norm 并不奇怪;当时的模型规模、训练配方和稳定性需求与今天的大型 decoder-only LM 并不完全相同。
有些较新的模型还会加入所谓的 double norm 或 non-residual post norm。它的思路并不是回到原始 Post-Norm,而是在不破坏残差主干的前提下,在某些子层输出附近再加一层归一化。这里能看出一个设计原则:问题不在于 Norm 本身,而在于不要让 Norm 截断 residual stream。
RMSNorm 与无 bias 设计
LayerNorm 的计算可以写成:
它同时做两件事:减去均值,控制方差。RMSNorm 则省略均值中心化,只保留尺度归一化:
从表达能力上看,RMSNorm 少了一部分操作;但在现代 Transformer 中,它通常表现得和 LayerNorm 一样好,某些设置下还更稳定、更快。原因不只是 FLOPs 更少。矩阵乘法占据了模型的大部分算术运算,但 norm 这类操作需要频繁读取和写回激活,更受内存带宽影响。换句话说,FLOPs 不是 wall-clock time 的全部。
同样的成本意识也会落到 bias 上。一个 bias 向量看起来参数量很小,但它每层都需要读写,并且在大规模训练中未必带来明显收益。对于形如
的线性层,许多现代 LM 直接使用
去掉它不是因为 bias 在数学上“错误”,而是因为在大模型训练的成本结构里,它的收益和代价不够划算。RMSNorm 和无 bias 线性层共同指向一个朴素原则:如果一个组件不能稳定地带来质量提升,就尽量不要让它增加数据移动和优化复杂度。
FFN:从 ReLU 到 GLU
Transformer block 中除了 Attention,另一个主要计算量来自前馈网络。原始形式可以写成:
早期 Transformer 使用 ReLU,GPT 系列常用 GeLU。GeLU 的形式可以理解为用一个平滑概率门来控制激活:
其中 是标准正态分布的累积分布函数。相比 ReLU 的硬截断,GeLU 更平滑,这在许多早期语言模型中表现很好。
不过现在更常见的是 GLU 系列。GLU 的核心是给 FFN 加一个逐元素的门控分支:
其中 表示逐元素乘法。SwiGLU 使用 Swish / SiLU 作为 :
这个结构相比普通 FFN 多了一组投影参数 。如果直接保持同样的 hidden size,参数量和计算量都会增加。实践中通常会把 GLU 的中间维度缩小到普通 FFN 的约 。普通 FFN 常见的经验公式是
而 GLU 版本常见的是
很多 LLaMA-like 模型的 FFN expansion ratio 看起来不是 4,而是 2.6 到 3.5 左右,原因就在这里。它不是随便选出来的“小一点”,而是在门控分支带来额外参数之后,对总体计算量做出的折中。
GLU 的直觉可以这样理解:FFN 不再只是“先非线性变换再线性投影”,而是让一个分支决定另一个分支中哪些维度应该被放大或抑制。这给模型提供了更细粒度的乘性相互作用,也解释了为什么它在现代语言模型中越来越像默认选择。22 这并不意味着 GeLU 或 ReLU 训练不出好模型。例如 GPT-3 使用 GeLU 仍然有效。这里的重点是:在近年的公开模型中,SwiGLU / GeGLU 的经验收益已经足够稳定,以至于它们经常成为默认项。
串行层与并行层
标准 Transformer block 是串行的:先做 Attention,再做 MLP。写成公式就是:
有些模型尝试过并行层结构,也就是让 Attention 和 MLP 从同一个归一化后的输入出发,再把两者结果一起加回残差:
这样做的好处偏工程:LayerNorm 可以共享,一些矩阵乘法可以融合,训练或推理时可能更容易优化。GPT-J、PaLM、GPT-NeoX 等模型都使用过类似结构。不过从更广泛的模型趋势看,多数现代模型仍然使用串行结构。原因并不神秘:串行 block 足够稳定,表达能力清晰,也更容易和现有实现、检查点转换、并行策略兼容。
所以,并行层更像是一个工程上值得知道的变体,还不是 Pre-Norm 或 RMSNorm 那样的强共识。
位置编码:为什么 RoPE 成为主流
位置编码的选择是现代模型之间仍然存在明显差异的地方。经典方案包括:
- 正弦位置编码:把不同频率的 和 加到 token embedding 上。
- 可学习绝对位置编码:为每个位置学习一个向量。
- 相对位置编码:在 attention 计算中显式加入与相对距离有关的项。
- RoPE:在 query 和 key 上施加与位置相关的旋转。
RoPE 想要达到的效果可以概括为:位置编码后的 query 和 key 做点积时,只依赖相对位置,而不是绝对位置。形式上,希望存在某个函数 ,使得
RoPE 的做法是把向量分成若干二维子空间,并在第 个子空间中施加旋转:
由于旋转矩阵满足
两个位置上的向量做内积时,绝对位置会自然抵消,只留下相对位置信息。RoPE 比简单 additive position embedding 更优雅,也正在这里:它不需要把一个位置向量硬加到语义向量上,而是直接改变 query/key 的几何关系。
实际实现中,RoPE 通常只作用在 attention 的 query 和 key 上,而不是 value 上。attention score 可以感知相对位置;被加权汇聚的内容向量本身,则不需要做同样旋转。这是现代 Transformer 实现里很典型的细节。
超参数的经验共识
架构之外,还有一件事值得单独记:许多看似自由的超参数,其实已经有相当稳定的经验默认值。
先看 FFN 维度。普通 FFN 的经验默认值是
如果使用 GLU,由于多了一个门控投影,常见折中是
实际模型会有一些偏移,例如某些 LLaMA、Mistral、Qwen 或 DeepSeek 模型落在 2.6 到 3.5 的范围内。T5 是一个著名例外,它曾经在 11B 模型中使用极大的 FFN multiplier,但后续 T5 v1.1 又回到了更标准的 GeGLU 配方。极端选择不是不能工作,只是默认情况下没有必要。
再看 attention head 的维度关系。理论上,多头注意力不要求
但多数模型确实大致遵循这个比例。也就是说,query/key/value 投影后的总维度通常和模型 hidden size 同阶,有时略大,但很少无节制扩张。这个选择更多来自工程和经验,不是一个强定理。
模型的深宽比也有经验范围。一个模型可以更深,也可以更宽。课件中列出的许多模型,其
大致落在几十到两百之间。非常深的模型会带来更高 latency,也更难做流水线并行;非常宽的模型则会增加每层矩阵乘法和激活显存。所谓“最优深宽比”并不只由损失曲线决定,还由硬件、并行方式和推理延迟共同决定。
最后是词表大小。单语模型常见词表在 30k 到 50k 左右,多语言或生产系统中的模型常常使用 100k 到 250k 甚至更大的词表。词表越大,短文本可能被切得更少,跨语言覆盖更好,但 embedding 和输出 softmax 的参数、显存与计算开销也会随之增加。tokenizer 不是一个纯预处理工具,而是模型架构和训练成本的一部分。
正则化:Dropout 变少,Weight Decay 还在
在小数据训练里,dropout 是很自然的正则化手段。到了大语言模型预训练,情况变了:训练语料往往有数万亿 token,模型通常只在语料上走很少 epoch,传统意义上的“记住训练集”并不是唯一风险。许多较新的公开模型在预训练阶段不再使用 dropout,或者论文中根本不强调它。
但这不意味着大模型不需要正则化。weight decay 仍然很常见,只是它的作用不一定是防止过拟合,而更像是和学习率调度、参数范数、优化稳定性一起构成训练动力学的一部分。特别是在 cosine learning rate schedule 下,weight decay 会影响参数在训练后期如何收缩和稳定下来。
更准确地说,现代 LM 的 regularization 从“随机丢弃激活以防过拟合”,转向了“通过优化器和训练配方控制训练轨迹”。
稳定性技巧:softmax 是危险区域
训练大模型时,很多数值问题都和 softmax 有关。softmax 内部有指数运算:
如果 logits 过大,就可能出现数值溢出、梯度尖峰或训练 loss 突然爆炸。一些模型会对输出 logits 或 attention logits 做额外处理。
一个例子是 z-loss。对于输出 softmax,设
则可以在损失中加入类似
的惩罚项,避免 log-partition 过大。PaLM 等模型使用过这一技巧。直觉上,就是不要让模型用过大的 logits 来表达置信度,因为这会让 softmax 区域变得数值上很脆弱。
另一个例子是 QK norm。attention score 的核心是
如果 query 或 key 的范数过大,进入 softmax 的分数也会过大。QK norm 会在 query/key 进入 softmax 之前做 LayerNorm 或 RMSNorm,从源头控制 attention logits 的尺度。近年来不少模型都采用了类似技巧。
还有一种更直接的做法是 logit soft-capping,例如用 把 logits 限制在某个范围内:
这样可以防止 logits 无限增大,但也可能限制模型表达非常强的偏好,所以它往往需要和具体训练配方一起验证。
推理成本与 GQA / MQA
训练时的 attention 已经很贵,自回归推理还会多出一个特殊问题:生成必须逐 token 进行。对于一个长度为 的 prompt,prefill 阶段可以一次性并行计算所有 token 的 hidden states;进入 decode 阶段后,第 个 token 必须等第 个 token 生成之后才能继续。decode 的计算形态就和训练很不一样:batch 维度和序列维度都不再那么容易提供大规模并行,反而要频繁从显存中读取过去所有 token 的 key/value。
KV cache 就是在这里出现的。为了避免每生成一个新 token 都重新计算整个前缀的 key/value,模型会把历史 token 在每一层的 和 存下来。下一步生成时,只需要为新 token 计算新的 ,再让 去和缓存中的所有历史 做 attention:
这省掉了重复计算,代价是显存读写变重。对一层来说,如果 batch size 是 ,当前缓存长度是 ,key/value head 数是 ,每个 head 的维度是 ,那么 KV cache 的规模大致正比于
这里的 来自 key 和 value 两份缓存。这个量还会乘上层数,并且随着上下文长度线性增长。长上下文推理里,GPU 往往不是“算不过来”,而是“数据搬不过来”:每生成一个 token,都要把大量历史 从显存中读出来,做完 attention 后再继续下一层。lecture 中强调 arithmetic intensity,原因就在这里。prefill 阶段更像大矩阵乘法,算术密度高;decode 阶段每步只处理一个新 token,更容易被 KV cache 的内存带宽限制。
标准 MHA(Multi-Head Attention)中,每个 query head 都有自己的 key head 和 value head。若 query heads 的数量是 ,通常有
于是每层缓存形状可以理解为:
这种设计的表达能力最直接:每个 attention head 都有独立的 query/key/value 子空间。但到了 decode 阶段,它也意味着 KV cache 的 head 数和 query head 数一样多,显存占用与读取量都比较大。
MQA(Multi-Query Attention)的想法更激进:保留多个 query heads,但让所有 query heads 共享同一组 key/value heads。也就是
此时缓存形状变成:
相比 MHA,KV cache 近似缩小了 倍。假设一个模型有 个 query heads,那么 MQA 理论上可以把每层的 KV cache head 维度从 份降到 份。对长上下文服务来说,这很诱人:同样显存下可以容纳更长上下文或更大的 batch;同样 batch 下,每步 decode 要读取的数据也更少,token latency 更低。
MQA 的代价也很明显。所有 query heads 共享同一套 key/value,等于把 attention 中“取什么信息”的部分压缩得非常厉害。query 仍然可以从多个角度发问,但它们查询的是同一组 key/value 表示。这可能损失一部分表达能力,尤其在较大模型或复杂任务中,perplexity 或下游质量可能会有轻微下降。
GQA(Grouped-Query Attention)站在 MQA 和 MHA 之间:把 query heads 分成若干组,同一组内共享一套 key/value,不同组之间仍然使用不同的 key/value。形式上有:
当 时就是普通 MHA;当 时就是 MQA;中间值就是 GQA。假设 、,那么每 个 query heads 共享一组 key/value,KV cache 约为 MHA 的 ,但比 MQA 保留了更多独立的 key/value 子空间。
从形状上看,GQA 可以理解为:
计算 attention 时,每个 query head 会被映射到某个 key/value group。若令
那么每 个 query heads 共享一组 key/value。这个旋钮很实用: 越小,推理越省; 越大,表达能力越接近原始 MHA。
这类设计的重点不只在训练 loss 上,也在服务成本上。训练时,MHA/GQA/MQA 的差异当然会影响模型质量;部署时,KV cache 的大小又会直接决定最大上下文长度、可承载 batch size、每 token latency 以及显存成本。对于需要长上下文、高并发、低延迟的模型,减少 KV cache 读写往往比微小的 perplexity 差异更重要。一些架构技巧在论文指标里看起来“不显眼”,但在真实部署中非常关键,因为它们优化的是内存带宽、缓存大小和每 token 延迟。
可以用一个索引系统的类比来记:MHA 给每个注意力头都配一套独立索引系统,表达能力充足,但缓存昂贵;MQA 让所有头共用一套索引系统,缓存最省,但可能过度压缩;GQA 则是在若干组之间共享索引系统,通常能保住大部分质量,同时显著降低推理成本。这个折中足够好,所以 GQA 已经成为许多现代 LLM 的默认推理友好设计。
稀疏与滑动窗口注意力
全量 attention 的计算和显存复杂度随序列长度近似二次增长。为了支持更长上下文,一些模型会限制 attention pattern,例如使用 sliding window attention,让每个 token 主要关注附近窗口内的 token。这样能显著降低长序列成本,代价是远距离信息传递变弱。
一种常见折中是交错使用局部注意力和全局注意力。例如大部分层使用 sliding window attention,每隔若干层插入一层 full attention。局部层负责短距离建模,全局层负责让远距离信息穿过网络。到了长上下文场景,attention 不再只有“全连接”这一种默认形态,而会变成可调的系统设计。
总结
回到这一讲的主线,现代大语言模型的架构选择既没有想象中那么神秘,也没有简单到只剩一个模板。强共识主要集中在 residual stream 的稳定性,以及 FFN / Norm 的高性价比设计上:Pre-Norm、RMSNorm、无 bias、GLU、RoPE 已经构成了非常常见的 LLaMA-like 基线。超参数上, 或 GLU 下的 、head 总维度接近 model dimension、合理的深宽比和词表大小,也都形成了经验默认值。
仍在活跃变化的部分,更多来自训练稳定性和推理效率:QK norm、z-loss、logit soft-capping、GQA/MQA、滑动窗口与交错全局注意力。它们背后有同一个现实背景:模型规模变大后,架构不再只是数学表达能力的问题,而会同时受到优化稳定性、显存带宽、KV cache、并行策略和部署成本的约束。
所以,实现一个现代 LM 时,合理的起点不是“照搬原始 Transformer”,而是先采用这套保守基线:Pre-Norm + RMSNorm + RoPE + SwiGLU + no bias + 合理的 FFN/head 维度。等任务、硬件或上下文长度提出额外需求,再去调整注意力结构、稳定性技巧和 tokenizer 规模。这样更接近今天大模型实践中的真实设计逻辑。