上一篇看了 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 里的局部组件。