File size: 2,389 Bytes
c6b1b88
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# core/mlp.py · FFN 与 MoE

## SwiGLU FFN(`FeedForward`)

```python
self.gate_proj = nn.Linear(h, inter, bias=False)
self.up_proj   = nn.Linear(h, inter, bias=False)
self.down_proj = nn.Linear(inter, h, bias=False)
self.act_fn    = ACT2FN[config.hidden_act]     # 默认 silu

def forward(self, x):
    return self.down_proj(self.act_fn(self.gate_proj(x)) * self.up_proj(x))
```

公式(SwiGLU):
$$\mathrm{FFN}(x)=\mathrm{down}\big(\mathrm{silu}(\mathrm{gate}(x))\odot \mathrm{up}(x)\big)$$

## MoE(`MOEFeedForward`)

```python
self.gate   = nn.Linear(h, num_experts, bias=False)
self.experts = ModuleList([FeedForward(config, moe_intermediate_size) for _ in range(num_experts)])

def forward(self, x):
    scores   = softmax(self.gate(x_flat), -1)
    topk_weight, topk_idx = topk(scores, k=num_experts_per_tok)
    if norm_topk_prob: topk_weight /= topk_weight.sum(-1, keepdim=True)   # 归一化
    y = zeros_like(x_flat)
    for i, expert in enumerate(self.experts):
        mask = (topk_idx == i)
        token_idx = mask.any(-1).nonzero().flatten()
        y.index_add_(0, token_idx, expert(x_flat[token_idx]) * weight)
    # 训练时累计 router 辅助损失
    load = one_hot(topk_idx, num_experts).float().mean(0)
    self.aux_loss = (load * scores.mean(0)).sum() * num_experts * router_aux_loss_coef
    return y
```

要点:

- **路由**:每个 token 经 `gate` 投影到专家 logits → softmax → 取 top-`k` 个专家(默认 k=1)。
- **`norm_topk_prob`**:把选中专家的权重归一化,保证输出尺度稳定。
- **负载均衡 (`aux_loss`)**:`Σ load_e · mean(scores)_e`,鼓励专家被均匀选中;系数 `router_aux_loss_coef`(默认 5e-4)只在训练时累加,推理为 0。
- **高效实现**:用 `index_add_` 把 token 分发给对应专家,避免显式循环展开大矩阵。
- **切换**:`Block` 中 `self.mlp = FeedForward(...) if not use_moe else MOEFeedForward(...)`,MoE 可插拔。

## 要点(面试)

- SwiGLU 相比原始 FFN(ReLU 两塔)多了 `up` 分支做门控,效果更优。
- MoE 的「稀疏」体现在:每个 token 只过 top-k 个专家,参数量大但单步计算量小。
- 为什么需要 aux_loss?防止 router 退化为只选少数专家(坍缩),保证负载均衡。
- `index_add_` 实现注意 `expert(x[token_idx]) * weight` 的广播与 dtype 对齐。