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 对齐。
|