从0训练LLM · 现代大模型体系 阅读 + 实验 ≈ 30 分钟 页内实验 ×2
C4 一个 Transformer 块:FFN、残差、归一化、门控 第 1 课

注意力取回信息之后,每个 token 自己还要做什么加工?

FFN:每个 token 自己的加工
注意力只会按权重把别人的向量取平均;要把取回来的信息在一个 token 内部组合成新特征,需要一个带非线性的、逐位置的前馈网络。

本课一问:注意力取回信息之后,每个 token 自己还要做什么加工?

这一课先不碰:现代 FFN 为什么有三个矩阵(SwiGLU,L4)、FFN 到底占多少参数(岔路 B1)、把 FFN 拆成很多个专家(MoE,C11)。

② 从已有出发

从已有出发 · 累计数据流 点已学环节看形状与参数
解码层

到 C3 为止,「小明把苹果给了小王」的每个 token 都通过注意力拿到了上下文:「给了」这个位置已经知道是谁把什么给了谁。

这一课换个例子,原因是:贯穿例句里没有哪个词的意思需要「两件事同时成立」才能确定,线性方案在它上面找不到一个干净的失败数字;而本课要证明的恰恰是「线性组合做不到某种组合」。所以借用同一只「苹果」的四种评价——好吃、不好吃、难吃、不难吃。注意力已经把「不」这个字的信息搬进了评价词所在的位置,于是这个位置同时知道两件事:

要回答「这句评价偏正面吗」,答案是 p 和 n 恰好一个成立、一个不成立的时候才是「是」:好吃(1, 0)→ 是,不好吃(1, 1)→ 否,难吃(0, 0)→ 否,不难吃(0, 1)→ 是。信息已经都在这个位置上了,问题是怎么把它们组合起来。

先回忆注意力对一个位置做了什么。第 i 个位置的输出是

输出_i = Σ_j a_ij · (x_j · W_V) · W_O

权重 a_ij 来自 softmax,这里确实有非线性;但权重一旦定下来,从 x 到输出这条「值通路」就只剩两次矩阵乘法。最极端的是第 1 个位置:因果掩码下它只能看自己,softmax 在一个元素上恰好等于 1 ,它的输出就是 x₁ · W_V · W_O——对它自己的特征完全是线性的。

③ 先自己设计:只靠注意力行不行?

最省事的方案:不加任何新零件,让每个位置的输出直接是自身特征的线性组合(加一个常数),ŷ = a·p + b·n + c。你来找 a、b、c。

  1. ① 朴素方案
  2. ② 它在哪失败
  3. ③ 改进方案
朴素方案只靠注意力:每个位置的输出对自身特征是线性的

拖动 a、b、c,或者直接点「用最小二乘求最优」。

教学构造:p、n 两个特征名是为了讲解手工起的;真实模型里某一维不能读成「是否正面」。

评价pn目标 y线性输出 ŷ
均方误差 MSE = — (全部猜 0.5 也是 0.25)

失败的原因可以证明。 对任何 a、b、c:ŷ(1,0) + ŷ(0,1) = (a + c) + (b + c) = a + b + 2c,ŷ(1,1) + ŷ(0,0) = (a + b + c) + c = a + b + 2c,两边永远相等 。而目标两边分别是 2 和 0。线性方案能做到的最好,是对四句一律给 0.5——它完全分不出哪句是夸、哪句是骂。

两个神经元为什么够?看它们各自读出了什么:

🎯

试三件事:① 在朴素方案里随便拖 a、b、c,盯着表格下面那行恒等式——两边永远相等,MSE 从来低不过 0.25;② 先点「看它在哪失败」「换成改进方案」展开右边的 FFN,再点「无(去掉)」:两层线性合成一层 ŷ = −p − n + 2,MSE 变成 1.0,非线性一去掉,多一层也没用;③ 把神经元 2 的偏置从 −1 拖到 0,它不再只在「两件事同时成立」时亮,先错的是哪一句?点表格里那一行,看列式展开。

④ 改进到现代做法

第 1 步:两个神经元 → 一个前馈网络

把「每个神经元读一行、过非线性、写一列」一次做完,就是 Transformer 原文的 position-wise feed-forward network:

前馈网络 FFN(Vaswani et al. 2017 式 (2),行向量写法 x·W)
= ( · + ) · +
👆 公式里带下划线的每一项都可以点——点哪项,讲哪项。
d_ff 是中间层的宽度。整条式子里没有任何一项跨越位置:第 t 个 token 的输出只取决于第 t 个 token 的 x。有的资料写成 W·x(列向量),那时「读取方向」是 W_up 的行,两种写法只差一个转置。

一个常用的类比入口: 把 FFN 想成一张「键-值」表——W_up 的每一列是一把键(在找什么模式),激活值是这把键和当前 token 的匹配程度,W_down 的每一行是这把键对应的值(匹配上之后往输出里写什么)。

第 2 步:为什么升到 4 倍宽?

两个神经元解决了一个手工问题;真实模型要同时处理成千上万种「组合」,就需要成千上万个读取方向。升维到 d_ff = 4d 就是给每个 token 配 4d 个神经元。「为什么偏偏是 4 倍」没有推导。原文 Table 3 做过对照:d_model = 512 时,d_ff 取 1024 / 2048 / 4096,英德翻译开发集困惑度 5.12 / 4.92 / 4.75,参数 53M / 65M / 90M——越宽越好,但也越贵,2048 是在两者之间选的 。GPT-2 沿用了 4 倍(768 → 3072);现代模型各按预算取宽度,Qwen3.8 是 3.4 倍(L4 会讲为什么门控版的倍数不能直接和 4 比)。

下面是 GPT-2 small 一层 FFN 的真实形状(官方 config):

GPT-2 small 的一层 FFN(d = 768,d_ff = 3072)
Y = GELU(X·Wup)·Wdown
输入参数(可学习)输出中间结果
边长按对数比例缩放,块边上的数字是真实值;同一维度符号的边在全图等长。
维度含义 真实规模 小规模
T 序列长度(这里取 5,仅为画图) 5 5
d 隐藏维 n_embd 768 2
dff FFN 中间维(4 × d) 3072 2
切到「小规模」:d = 2、d_ff = 2,就是上面两个神经元的例子。T 行彼此不交换信息——每一行各自乘同一套 W_up、W_down。

第 3 步:逐位置,是分工而不是缺陷

FFN 对第 t 个 token 的计算只用到第 t 行。把句子里的 token 打乱顺序,FFN 的输出只会跟着打乱,每一行的数值不变——它不在 token 之间传信息。这正是和注意力的分工:注意力决定「从谁那里取什么」,FFN 决定「取回来之后在自己这里怎么组合」。一个块里两者交替出现:先交流,再各自加工。

演进图:本课在第几代

L1 讲的是这张表的第一行:从 2017 年两矩阵 + ReLU 的 FFN,到 GPT-2 的 GELU,再到第 4 课才讲的门控版。本课的两个神经元对应最左边那一格。

一个 Transformer 块的演进:每一代都被上一代的某个真数字逼出来
零件 \ 年代
2017 · 原版Vaswani 等
2019 · GPT-2OpenAI
2020–2023 · Llama 式Shazeer 2020 起
2025–2026 · Qwen3.8本课程锚点
2026 · 分歧C15 展开
FFN 本课
两矩阵 + ReLU,d_ff = 4d(512 → 2048) 卡在d_ff 取 1024 / 2048 / 4096,开发集困惑度 5.12 / 4.92 / 4.75:越宽越好,只能拿参数换
两矩阵 + GELU,768 → 3,072 卡在参数对齐后仍输给门控版:GELU 1.983 对 SwiGLU 1.944(T5-base)
SwiGLU 三矩阵,中间维 8d/3:DeepSeek-LLM-67B 为 8,192 → 22,016 卡在8d/3 只是公平对照时的换算,真实模型按预算另取宽度
SwiGLU,5,120 → 17,408(3.4d),占全模型参数 61.6% 卡在输出没有上界:SiLU(10) × 10 = 99.995,低精度下会溢出
给 SwiGLU 截断(gpt-oss 7、DeepSeek V4 10)或软封顶(Kimi K3 的 SiTU-GLU);再往后拆成很多小 FFN(MoE,C11)
每个子层 x + F(x)(ResNet 2015 的想法搬进 Transformer) 卡在残差流越加越大:128 次单位方差写入后,标准差 √129 = 11.36
残差输出投影按 1/√N 初始化(我们的项目:0.02 ÷ √8 = 0.00707) 卡在一条 d 维向量要承载所有层的读写,层数一多成了瓶颈(各家自报)
普通残差,同 GPT-2
普通残差:64 层 × 2 个子层 = 128 次写入
加宽成 4 条流(mHC、Gated Residual),或让每层对前面所有层做注意力(AttnRes)
LayerNorm(2016,逐 token 统计;取代按批统计的 BatchNorm)放在加法之后:Post-LN 卡在离不开学习率 warmup(原因在 C8 兑现)
LayerNorm 移到子层入口(Pre-LN)+ 最终 LayerNorm 卡在还要减均值、带 β;Zhang & Sennrich 认为平移不变可以不要
Pre-RMSNorm:只除以均方根,没有 β 卡在标准增益 g 被 weight decay 拉向 0
zero-centered RMSNorm:乘 1 + w,w 初始化 0,eps 1e-6;另有 QK-Norm
OLMo 2 把归一化挪到子层输出端;DeepSeek V4 用 QKV RMSNorm 取代 QK-Clip(C8)
没有(GLU 在 2016 年已提出,用在卷积语言模型里)
没有
FFN 里的门:SwiGLU = SiLU(x·W_gate) ⊙ (x·W_up) 卡在注意力 softmax 必须分出 100%:首 token 平均占 46.7%(Qwen 的 15B MoE 实验)
再加注意力输出门:q_proj 5,120 → 12,288 = 2 × 6,144,一半过 sigmoid 当门(首 token 占比降到 4.8%)
Kimi K3 的 Gated MLA;Qwen3.8-Flash-Next 把线性注意力的输出门也换成 sigmoid(C11)
数字口径:中间维、比例、FFN 占比、q_proj 宽度、写入次数由 anchor-model.json / frontier-models.json 的官方 config 在构建时现算;4 倍宽的对照出自 Vaswani 2017 表 3(本地 PDF),GELU 对 SwiGLU 出自 Shazeer 2020 表 1,首 token 占比出自 Qiu 等 2025 表 4(Qwen 自报),截断值出自各家 config(R3 §3.6);√129 假设每次写入独立、方差为 1。

⑤ 极小真数字算一次

在上面的 FFN 组件里点「不好吃」这一行、再点神经元 2 的「看它怎么算」,组件会逐项算给你:z = 1 × 1 + 1 × 1 + (−1) = 1 → ReLU → 1 → 乘写回系数 −2 = −2;神经元 1 读到 z = 2 → 写回 +2;合计 0。点组件里的「GELU」:四句的输出变成 0.841、0.272、0.318、0.841,不再精确,但按 0.5 一刀切仍然全对——这就是「非线性的具体形状不重要,有没有非线性才重要」的现场证据。

真模型 · 我们自己训练的小模型里,FFN 神经元在哪些字上亮

下面是项目基线模型(4 层、d = 256、SwiGLU 中间维 688,在《三國志演義》上训练)第 2 层 FFN 的真实神经元。同一段验证集文字,文字本身就是热力图。

项目实测 · 真实 FFN 神经元在哪些字上亮

项目实测 · 去掉 FFN 会怎样(含参数对齐的 12 层纯注意力)

读图(2026-09-26 导出的数据,以组件显示为准): 去掉 FFN 的 4 层模型只剩 1,574,144 个参数,val loss 从 4.626 升到 5.205;把层数加到 12、参数补到 3,673,344(和基线只差 0.42%)的纯注意力模型是 5.330——参数一样多,全用在注意力上反而更差,差距 0.70 远超噪声底线(C14:同配置 5 次重跑极差 0.016)。上面的神经元也值得看:按最大激活挑出的 #641 在「公」上最亮,#27 盯着「依」,#22 在「鎮」「征」「右」这类官衔用字上亮;随机抽的 #359、#110 最亮的地方多是被拆开的字节(显示为 �),看不出规律。

⑥ ⚠ 常见误解

⚠️

「FFN 也在 token 之间传信息」✗ —— 它对每个位置单独计算,打乱 token 顺序只会打乱输出的行。token 之间的信息只在注意力(以及 C11 的线性注意力)里流动。

「注意力里有 softmax,已经是非线性了,FFN 可有可无」✗ —— softmax 的非线性只作用在「取多少」上;权重定下之后,值通路 x → x·W_V·W_O 是线性的。第 1 个位置甚至只能取自己,输出就是 x 的线性函数。

「一个神经元对应一个可以命名的概念」✗ —— 上面两个神经元能命名,是因为权重是我手工构造的。真实模型里多数神经元同时响应好几种互不相关的模式,随机挑一个往往什么规律都看不出(真模型实验里的「随机对照」就是为此放的)。

⑦ 证据三问

⑧ 一句话

注意力在 token 之间搬信息,FFN 在每个 token 内部做非线性组合。

⑨ 下一个缺口

一个块 = 注意力 + FFN。Qwen3.8 把这样的块堆了 64 层。最直接的做法是一层的输出直接当下一层的输入,一路叠上去——叠深了会怎样?下一课:为什么要残差。

⑩ 你可能会问