FeedForward 自测题
完成以下题目检验你的理解程度
🎮 交互式自测(推荐)
Q1
FeedForward 为什么要"扩张-压缩"?
Q2
SwiGLU 使用几个投影矩阵?
Q3
SiLU 激活函数的公式是什么?
Q4
SwiGLU 中门控机制的作用是什么?
Q5
FeedForward 与 Attention 的主要区别是什么?
Q6
为什么 SwiGLU 通常比 ReLU 效果更好?
Q7
MiniMind 中 intermediate_size 通常是 hidden_size 的几倍?
🎯 综合问答题
Q8: 实战问题
如果你发现 FeedForward 的输出总是接近 0,可能是什么问题?如何调试?
点击查看参考答案
可能的原因:
权重初始化问题:
- 权重初始化太小
- 导致输出值太小
门控信号问题:
- gate_proj 的输出总是负数
- SiLU(负数) ≈ 0
- 导致 gate * up ≈ 0
梯度消失:
- 深层网络中梯度传不回来
- 权重没有更新
数值精度问题:
- 使用了太低的精度(如 FP16)
- 小数值被截断为 0
调试方法:
python
# 1. 检查中间值
gate = self.gate_proj(x)
print(f"gate mean: {gate.mean()}, std: {gate.std()}")
silu_gate = F.silu(gate)
print(f"silu_gate mean: {silu_gate.mean()}, std: {silu_gate.std()}")
up = self.up_proj(x)
print(f"up mean: {up.mean()}, std: {up.std()}")
hidden = silu_gate * up
print(f"hidden mean: {hidden.mean()}, std: {hidden.std()}")
# 2. 检查权重初始化
for name, param in self.named_parameters():
print(f"{name}: mean={param.mean():.6f}, std={param.std():.6f}")
# 3. 检查梯度
output.sum().backward()
for name, param in self.named_parameters():
if param.grad is not None:
print(f"{name} grad: {param.grad.abs().mean():.6f}")解决方案:
调整初始化:
pythonnn.init.xavier_uniform_(self.gate_proj.weight)检查 RMSNorm:
- 确保输入已经归一化
- 避免数值范围过大或过小
使用混合精度:
- 关键计算用 FP32
- 其他部分用 BF16
Q9: 概念理解
为什么 FeedForward 要在每个位置独立处理,而不是像 Attention 一样进行全局交互?
点击查看参考答案
设计考虑:
分工明确:
- Attention 已经做了"信息融合"
- FeedForward 负责"深度处理"
- 避免重复功能
计算效率:
- 全局交互:O(n²d)
- 独立处理:O(nd²)
- 当 n > d 时,独立处理更高效
参数效率:
- 全局交互需要位置相关的参数
- 独立处理的参数可以复用
理论基础:
- Transformer 的设计思想:
- Attention = 全局信息路由
- FFN = 局部特征变换
- 类似 CNN 中的空间卷积 + 1x1 卷积
- Transformer 的设计思想:
类比:
- 开会(Attention):大家交换信息
- 思考(FFN):各自消化、整理
- 两者交替进行,效果最好
实验验证:
- 论文证明这种分工设计效果很好
- 混合设计(如全 FFN 或全 Attention)效果更差
Q10: 代码理解
解释以下代码的每一步:
python
def forward(self, x):
return self.down_proj(
F.silu(self.gate_proj(x)) * self.up_proj(x)
)点击查看参考答案
逐步解析:
python
def forward(self, x):
# x: [batch, seq, hidden_dim]
# 例如: [32, 512, 512]
# Step 1: 计算门控信号
gate = self.gate_proj(x)
# gate: [batch, seq, intermediate_dim]
# 例如: [32, 512, 2048]
# Step 2: SiLU 激活
gate_activated = F.silu(gate)
# SiLU(x) = x * sigmoid(x)
# 平滑激活,保留部分负数信息
# gate_activated: [32, 512, 2048]
# Step 3: 计算值信号
up = self.up_proj(x)
# up: [32, 512, 2048]
# Step 4: 门控相乘
hidden = gate_activated * up
# 逐元素相乘
# gate_activated 作为"开关"控制 up 的信息
# hidden: [32, 512, 2048]
# Step 5: 压缩回原维度
output = self.down_proj(hidden)
# output: [32, 512, 512]
return output关键点:
- 两条并行路径:gate_proj 和 up_proj 独立计算
- 门控机制:SiLU(gate) 控制 up 的信息流
- 维度变化:512 → 2048 → 512(扩张-压缩)
- 无偏置:所有 Linear 都是 bias=False
为什么这样写?
- 简洁:一行代码
- 高效:现代深度学习框架会优化这种写法
- 明确:直接对应 SwiGLU 公式
✅ 完成检查
完成所有题目后,检查你是否达到:
- [ ] Q1-Q7 全对:基础知识扎实
- [ ] Q8 能提出 2+ 调试方法:具备调试能力
- [ ] Q9 能解释设计思想:理解架构设计
- [ ] Q10 能逐步解释代码:理解实现细节
如果还有不清楚的地方,回到 teaching.md 复习,或重新运行实验代码。
🎓 进阶挑战
想要更深入理解?尝试:
修改实验代码:
- 对比 ReLU、GELU、SiLU 的激活分布
- 可视化门控信号的模式
- 测量不同 intermediate_size 的效果
阅读论文:
- GLU Variants Improve Transformer - GLU 系列详细对比
- Searching for Activation Functions - Swish 激活函数
实现变体:
- 实现 GeGLU(GELU 门控)
- 实现标准 FFN(对比效果)
- 实现 MoE 版本的 FeedForward
下一步:前往 05. Residual Connection 学习残差连接!