Transformer 中 FFN 层有什么作用?
FFN层:Transformer的"深度思考"引擎,SwiGLU为何成为大模型标配
Attention看见全局,FFN在想什么
上篇文章我们聊了 RoPE——它让模型能够理解"我在这句话的第几位"。但你有没有想过另一个问题:Attention 负责看全局,token 与 token 之间的关联它搞定了,那每个 token 内部的"消化理解"是谁在做?
答案是 FFN,全称 Feed-Forward Network。
这两个组件的关系很多人容易搞混。Attention 是"外交官",到处找相关信息;FFN 是"分析师",把找到的信息深度加工。你可以让模型看完全部参考资料,但如果没有 FFN 来消化理解,它只能复述原文,而不能真正"想明白"。
对于 Transformer 来说,容量(capacity)这个词你可能见过很多次。这个容量主要来自 FFN,而不是 Attention。Attention 参数量虽然也大,但它更像是信息路由,真正让模型"有知识"的,是 FFN 里存储的参数。
标准FFN:升维-激活-降维的流水线
先看最经典的 FFN 结构。两层线性网络,中间夹一个激活函数,看起来简单到不可思议:
FFN(x) = W2 · ReLU(W1 · x + b1) + b2但这个结构藏着精妙的设计逻辑。
第一层 W1 把向量从 d_model 维度扩展到 d_ff 维度。d_ff 通常是 d_model 的 4 倍。这意味着什么?原本 512 维的向量被投射到 2048 维的空间。就像把一张压缩的照片展开成高清版本,原来模糊的细节突然清晰了。
激活函数 ReLU 在这里扮演什么角色?它把负值变成 0,只保留正值的信号。你可以理解为"信息过滤器"——负数对应的特征通道被关闭,正数对应的通道被保留并放大。
第二层 W2 再把向量从 2048 维压缩回 512 维。这个压缩不是简单的"扔掉一半",而是有选择性地整合——哪些特征重要,哪些不重要,由前面的激活结果决定。
为什么偏偏是 4 倍?因为这是工程实践中的经验最优值。2 倍升维容量不够,8 倍又会让推理时的显存爆炸。4 倍恰好在"表达力"和"资源消耗"之间找到了平衡点。
ReLU的硬伤:三个致命问题
ReLU 在 2012 年的 AlexNet 中一战成名,但把它塞进 FFN 一直有个问题:它太"粗暴"了。
第一个问题是信息截断。把负数直接变成零,听起来合理,但神经网络中的负值不一定代表"无意义"。很多特征在原始空间里可能是负的,经过激活后反而能表达重要的信息。ReLU 一刀切,直接扔掉,这不是优化,是浪费。
第二个问题是神经元死亡。想象一个神经元,因为初始权重的原因,它的输出永远是负的。ReLU 之后永远是 0。这个神经元就"死"了,梯度流不过来,永远不会恢复。更糟糕的是,倒霉的神经元可能占比不低,你以为模型有很多参数,实际上有效通道在不断减少。
第三个问题是缺乏差异化处理。ReLU 对所有负值一视同仁,要么全保留,要么全丢弃。但真实数据的特点是:有些负值接近零,略微调整就能变正;有些负值确实很小,可能真的不重要。ReLU 分不清这些,一律简单粗暴处理。
这三个问题加在一起,就是标准 FFN 的天花板。你可以把参数调得更好,但激活函数的局限性卡在那里,再怎么努力也有上限。
SwiGLU:双路并行的智能门控
2016 年,Noam Shazeer 提出了 GLU(Gated Linear Unit),核心思想是:不要让激活函数独自决定信息流向,再加一个"门控"来精细调控。
GLU 的公式长这样:
GLU(x) = σ(W1·x) ⊙ (W2·x)它分成两条路:一条路计算激活值,另一条路计算门控信号。两条路通过 Hadamard 积(逐元素相乘)融合。门控值在 0 到 1 之间,动态决定每条通道该放行多少信息。
这就好比以前只有一个编辑决定内容去留,现在变成两个编辑协作:第一个编辑负责提炼内容精华,第二个编辑负责判断哪些内容值得保留。协同工作的效果远超单打独斗。
SwiGLU 在 GLU 的基础上更进一步:用 Swish 激活函数替代 ReLU。
Swish(x) = x · σ(x)
这个函数的曲线不是截断的,而是平滑的。它在负数区域趋近于零但不会变成硬零,在正数区域保持线性增长。这意味着负值没有被彻底丢弃,只是被压制了。一旦参数调整让某个负值变成正的,这条通道立刻激活,而不是"死透了等复活"。
SwiGLU 的完整公式:
SwiGLU(x) = Swish(W1·x) ⊙ (W2·x) + b等等,这里好像少了一个矩阵?标准 FFN 有 W1 和 W2 两个矩阵,SwiGLU 公式里也只看到 W1 和 W2。
仔细看:标准 FFN 是 W2 · ReLU(W1·x),SwiGLU 是 Swish(W1·x) ⊙ (W3·x)。
SwiGLU 需要三个权重矩阵:W1、W2、W3。W1 负责特征提取支路,W3 负责门控支路,两者并行计算后再融合。这是"以参数量换性能"的典型做法。
为什么大模型集体转向SwiGLU
LLaMA、Mistral、PaLM、ChatGLM……如果你仔细看过这些主流大模型的技术报告,会发现一个共同选择:它们都用了 SwiGLU。
这不是跟风,而是有充分理由的。
第一,表达能力更强。双路门控机制让模型学会更精细的信息筛选,哪些该强化,哪些该抑制,不再由单一激活函数粗暴决定。
第二,训练更稳定。Swish 的平滑特性避免了 ReLU 的硬截断,梯度流更加顺畅。神经元死亡的问题大幅缓解,模型能更充分地利用参数量。
第三,内存效率反而更好。这听起来反直觉——明明多了一个矩阵,为什么反而省内存?关键在于扩维倍率的调整。标准 FFN 用 4 倍扩维,SwiGLU 通常用 8/3 ≈ 2.67 倍扩维就能达到同等甚至更好的效果。少了一个维度的矩阵参数量,内存压力反而降低了。
从行业演进来看,激活函数的进化路径很清晰:ReLU → GELU → Swish → SwiGLU。每一步都在解决前一步的痛点:梯度截断、训练不稳定、表达能力不足。
就像手机处理器的架构演进,不是简单地堆核心数量,而是通过架构优化实现性能跃升。SwiGLU 也是如此——不是堆参数,而是让每个参数都发挥更大价值。
实战:如何在你的模型中用SwiGLU
如果要在一行 PyTorch 代码里实现 SwiGLU,大概长这样:
class SwiGLU(nn.Module):
def __init__(self, d_model, d_ff):
super().__init__()
self.w1 = nn.Linear(d_model, d_ff)
self.w2 = nn.Linear(d_model, d_ff)
self.w3 = nn.Linear(d_ff, d_model)
def forward(self, x):
return self.w3(F.silu(self.w1(x)) * self.w2(x))注意这里用的是 F.silu,silu 就是 Swish 的变体,计算方式是 x * sigmoid(x),和 Swish 等价。
维度设计有个小技巧:中间维度通常设为 int(d_model * 8 / 3) 然后向下取整。如果 d_model 是 768,中间维度就是 2048,刚好是 768 的 8/3 倍。
调参时,如果发现模型收敛困难或者表达能力不足,可以检查 FFN 部分的设置是否合理。扩维太小会导致容量不足,扩维太大又会让显存爆炸,SwiGLU 的 8/3 倍是一个不错的起点。
升级到 SwiGLU 不是简单的"换个激活函数",而是重新设计了一个更智能的特征加工流水线。你需要三个矩阵而不是两个,但换来的是更稳定的训练、更强的表达能力,以及更合理的内存使用。
从FFN看向LayerNorm
Attention 负责"看见",FFN 负责"想透"。这两个组件配合,让 Transformer 具备了处理长序列、理解上下文、完成复杂推理的能力。
但还有一个组件我们还没聊到:Normalization。LayerNorm 几乎出现在每个 Transformer block 里,它的作用是什么?RMSNorm 和 LayerNorm 有什么关系?为什么现在很多大模型开始用 RMSNorm?
这个话题,我们下一篇接着聊。
