MiniMind 学习笔记 05:Transformer Block 里到底发生了什么

上一篇看了 MiniMind 的整体结构:

MiniMindForCausalLM
-> MiniMindModel
-> MiniMindBlock x N

这篇进入每一层 MiniMindBlock

在 MiniMind 中,一个 block 的 forward 非常精炼:

def forward(self, hidden_states, position_embeddings, past_key_value=None,
            use_cache=False, attention_mask=None):
    residual = hidden_states
    hidden_states, present_key_value = self.self_attn(
        self.input_layernorm(hidden_states),
        position_embeddings,
        past_key_value,
        use_cache,
        attention_mask
    )
    hidden_states += residual
    hidden_states = hidden_states + self.mlp(
        self.post_attention_layernorm(hidden_states)
    )
    return hidden_states, present_key_value

这几行就是一层 decoder block 的主干。

1. Block 的整体流程

把代码画成流程图:

hidden_states
    |
    |---------------- residual ----------------|
    v                                          |
input RMSNorm                                 |
    |                                          |
    v                                          |
Self-Attention                                |
    |                                          |
    |---------------- add ---------------------|
    v
post-attention RMSNorm
    |
    v
MLP / MoE
    |
    |---------------- add residual ------------|
    v
output hidden_states

一句话概括:

先做 Attention,再做 FFN / MoE,每个子层前都有 RMSNorm,每个子层后都有残差连接。

2. 为什么要 residual

残差连接是 Transformer 能训练很深的重要原因之一。

没有 residual 时,每一层都完全覆盖上一层输出:

h -> Layer(h)

有 residual 后:

h -> h + Layer(h)

这表示每层不是从零重写表示,而是在原表示上增加一个增量。

直觉上:

原来的信息可以直接往后传;
当前层只需要学习“应该修改什么”。

这会让梯度传播更稳定,也降低深层网络训练难度。

3. 为什么是 Pre-Norm

MiniMind 的写法是:

self.self_attn(self.input_layernorm(hidden_states), ...)

也就是先 norm,再进入 attention。

MLP 部分也是:

self.mlp(self.post_attention_layernorm(hidden_states))

这种结构叫 Pre-Norm。

它和 Post-Norm 的区别是:

Pre-Norm:
  x + Sublayer(Norm(x))

Post-Norm:
  Norm(x + Sublayer(x))

现代 LLM 更常用 Pre-Norm,因为它通常训练更稳定,尤其是深层模型。

4. RMSNorm 是什么

MiniMind 用的是:

RMSNorm(config.hidden_size, eps=config.rms_norm_eps)

RMSNorm 和 LayerNorm 类似,都是归一化 hidden state。

LayerNorm 会做:

减均值,再除以标准差。

RMSNorm 更简单:

不减均值,只除以 root mean square。

公式可以粗略理解为:

x_norm = x / sqrt(mean(x^2) + eps)

然后再乘一个可学习缩放参数。

RMSNorm 的优点是:

计算更简单;
速度更友好;
在 LLM 中效果通常足够好;
训练稳定性好。

5. Attention 子层在学什么

Attention 子层负责让每个 token 汇聚上下文信息。

对于当前位置 token,它会问:

我应该关注前面哪些 token?
每个 token 对我有多重要?
我应该从它们那里拿到什么信息?

这对应 Q/K/V:

Q:当前位置想查询什么。
K:历史位置提供什么索引。
V:历史位置真正携带的内容。

Self-Attention 输出后,加回 residual:

hidden_states += residual

这一步让 token 表示融合了上下文。

6. FFN / MLP 子层在学什么

Attention 擅长 token 之间的信息交互,但每个位置拿到上下文之后,还需要进一步做非线性变换。

这就是 FFN / MLP 的作用。

MiniMind 的普通 FeedForward 是 SwiGLU 风格:

down_proj(act(gate_proj(x)) * up_proj(x))

可以理解成:

升维
-> 非线性门控
-> 降维

它主要提供每个 token 位置上的非线性计算能力。

如果 Attention 更像“读上下文”,FFN 更像“消化和加工信息”。

7. MoE 是 FFN 的替代版本

MiniMind 支持:

self.mlp = FeedForward(config) if not config.use_moe else MOEFeedForward(config)

如果 use_moe=False,每个 token 都走同一个 FFN。

如果 use_moe=True,每个 token 会先经过 router,选择一个或多个 expert:

token
-> router
-> top-k experts
-> 加权合并 expert 输出

所以 MoE 可以看成:

多个 FFN 专家 + 一个路由器。

它的目标是扩大参数容量,同时控制每个 token 实际激活的计算量。

8. 一个 block 的输入输出形状

假设:

batch_size = 1
seq_len = 6
hidden_size = 768

那么 block 输入是:

hidden_states: [1, 6, 768]

经过 Attention 后仍然是:

[1, 6, 768]

经过 MLP 后仍然是:

[1, 6, 768]

也就是说,Transformer block 通常不改变主干 hidden state 的维度。

它改变的是:

每个 token 表示中包含的信息。

9. 为什么 Block 结构这么固定

现代 decoder-only LLM 的基本 block 大多长得类似:

Norm
-> Self-Attention
-> Residual
-> Norm
-> MLP
-> Residual

原因很实际:

Attention 负责跨 token 信息交互;
MLP 负责逐 token 非线性变换;
Norm 保证数值稳定;
Residual 保证深层可训练。

这四件事组合起来,就构成了 Transformer decoder block 的核心。

10. 小结

MiniMindBlock 的代码很短,但包含了 Transformer 的核心结构:

RMSNorm
Self-Attention
Residual
RMSNorm
FeedForward / MoE
Residual

理解这个 block 后,再看 Attention、RoPE、GQA、FFN、MoE,就不再是孤立概念。

它们都只是这个 block 里的局部组件。

发表评论