---
title: 注意力、前馈网络与 MoE
url: https://doc.liz6.com/ai/01-model-foundations/02-attention-and-moe
locale: zh
area: ai
tags:
- 大模型与 Agent
- 模型基础
date: 2026-06-30
modified: 2026-09-10
description: 知道 logits 如何用于采样之后，接着看这些分数之前的表示计算。本文沿一个位置的向量，解释注意力怎样汇集其他位置、FFN 怎样变换表示，以及 MoE 怎样选择前馈专家。权重驻留与 KV 空间另见下一篇，避免把模型结构和部署容量混在一个参数标签里。
---

# 注意力、前馈网络与 MoE

知道 logits 如何用于采样之后，接着看这些分数之前的表示计算。本文沿一个位置的向量，解释注意力怎样汇集其他位置、FFN 怎样变换表示，以及 MoE 怎样选择前馈专家。权重驻留与 KV 空间另见下一篇，避免把模型结构和部署容量混在一个参数标签里。

## 一个 Decoder 层如何更新表示

本文以自回归 Transformer decoder 的常见结构为例。token ID 先映射成向量，模型还需要表示位置信息；位置信息可以通过位置向量或对注意力中的向量做位置相关变换等方式引入，并非所有模型都简单地把两种 embedding 相加。

一层通常包含注意力、前馈网络、残差连接和归一化。下面画的是便于理解的 pre-norm 形式；原始 Transformer 与后续模型在归一化位置、激活函数和位置机制上存在差异。[Attention Is All You Need](https://arxiv.org/abs/1706.03762)

<svg viewBox="0 0 760 395" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="一个简化的 pre-norm Decoder 层" style="max-width:100%;height:auto" font-family="Source Han Sans CN,Microsoft YaHei,sans-serif">
<defs><marker id="decoder-layer-arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path d="M0,0 L8,4 L0,8 Z" fill="#64748b"></path></marker></defs>
<rect width="760" height="395" rx="12" fill="#f8fafc"></rect>


<g transform="translate(0 0)"><rect x="155" y="65" width="450" height="68" rx="8" fill="#e0e7ff" stroke="#c7d2fe"></rect><text x="380.0" y="96.0" font-size="15" fill="#1e293b" text-anchor="middle" font-weight="600">输入表示 x</text><text x="380.0" y="118.0" font-size="12" fill="#475569" text-anchor="middle" font-weight="400">每个位置一个向量</text><line x1="380" y1="133" x2="380" y2="158" stroke="#64748b" stroke-width="1.8" marker-end="url(#decoder-layer-arrow)"></line><rect x="155" y="160" width="450" height="68" rx="8" fill="#e0e7ff" stroke="#c7d2fe"></rect><text x="380.0" y="191.0" font-size="15" fill="#1e293b" text-anchor="middle" font-weight="600">u = x + Attention(Norm(x))</text><text x="380.0" y="213.0" font-size="12" fill="#475569" text-anchor="middle" font-weight="400">因果注意力混合允许访问的位置</text><line x1="380" y1="228" x2="380" y2="253" stroke="#64748b" stroke-width="1.8" marker-end="url(#decoder-layer-arrow)"></line><rect x="155" y="255" width="450" height="68" rx="8" fill="#e0e7ff" stroke="#c7d2fe"></rect><text x="380.0" y="286.0" font-size="15" fill="#1e293b" text-anchor="middle" font-weight="600">y = u + FFN(Norm(u))</text><text x="380.0" y="308.0" font-size="12" fill="#475569" text-anchor="middle" font-weight="400">前馈网络逐位置变换；MoE 可替换这里</text></g><text x="24" y="29" font-size="19" fill="#0f172a" text-anchor="start" font-weight="700"><tspan x="24" dy="0">一个简化的 pre-norm Decoder 层</tspan></text><text x="24" y="356" font-size="13" fill="#475569" text-anchor="start" font-weight="400"><tspan x="24" dy="0">重复多层后，经输出投影得到 logits；归一化位置与具体 FFN 形式依架构而变。</tspan></text>
</svg>

### 注意力：当前表示怎样利用其他位置

对输入矩阵 X，线性投影得到 Q=XWQ、K=XWK、V=XWV。某个位置的 query 与允许访问位置的 key 做点积，经缩放、掩码与 softmax 得到权重，再对 value 加权求和。单头常写为：

**Attention(Q,K,V) = softmax(QKᵀ/√dₖ + M)V**

因果掩码 M 让未来位置不能参与当前位置的注意力，通常允许访问**当前位置及此前位置**。位置 i 的表示用于预测后续 token，所以包含当前位置并没有偷看未知的未来。多头注意力把这种计算分成多组，再合并结果；不能据某一张注意力图就认定某个头负责固定语义或完整解释模型判断。

用一个三位置的教学例子看掩码：如果第 2 个位置对三个位置的原始分数都是零，掩码后第三项不可访问，前两项各得 1/2。若对应 value 为 `[2,0]`、`[0,4]`、`[99,99]`，结果是 `[1,2]`，未来位置的大数不会混进来。第 3 个位置才有资格结合前三个位置。

### FFN、残差与层深度

FFN 通常把每个位置的向量做线性变换、非线性激活，再投影回模型维度；现代实现也常使用门控。这里“逐位置”指这一子层不直接在位置之间混合，但输入已经带有此前注意力汇集的信息。注意力与 FFN 在多层中共同构成表示计算，不能简单分成“注意力只看、FFN 才思考”。

残差连接把子层增量加回原表示，使信息和梯度有直接路径；归一化帮助控制数值尺度。增加层数改变顺序变换的深度，增加专家数主要改变可选择的参数集合，这两种“变大”不等价。激活参数量也不是模型内推理步骤数的度量。

### 掩码怎样改变加权结果？

公式中的三个操作各有作用：点积与缩放产生相对分数，掩码决定哪些项有资格参与，softmax 决定这些项怎样分配权重。先保持 q 为零观察因果边界，再改变 q 看贡献变化，避免把高亮直接当成完整的模型解释。

$$
\alpha_i=\operatorname{softmax}_i(qK^\top/\sqrt{d_k}+M),\qquad o=\sum_i\alpha_i v_i
$$

**掩码怎样改变加权结果？**

不可访问位置的权重严格为零；softmax 只在可访问位置之间分配质量，然后加权 value。


## 从稠密 FFN 到稀疏专家

在稠密 FFN 层，各位置使用同一组前馈权重。稀疏 MoE 层则准备多个专家网络，由 router 根据当前位置表示选择其中一部分。其他组件如注意力仍有自己的计算，不是把整个模型简单切成若干互斥子模型。

设路由器对三个专家给出权重 `[0.6,0.3,0.1]`，该层选择 top-2，并在选中集合内归一化，就得到 `[2/3,1/3]`。如果两个专家输出分别为 `[3,0]` 和 `[0,6]`，合并结果为 `[2,2]`。第三个专家未参与这个位置的专家计算，但下个位置或下层可以选到它。

<svg viewBox="0 0 760 406.6076412200928" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="示例路由：每个位置选择专家，再合并输出" style="max-width:100%;height:auto" font-family="Source Han Sans CN,Microsoft YaHei,sans-serif">
<defs><marker id="moe-routing-arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path d="M0,0 L8,4 L0,8 Z" fill="#64748b"></path></marker></defs>
<rect width="760" height="406.6076412200928" rx="12" fill="#f8fafc"></rect>


<g transform="translate(0 0)"><rect x="30" y="160" width="160" height="75" rx="8" fill="#e0e7ff" stroke="#c7d2fe"></rect><text x="110.0" y="194.5" font-size="15" fill="#1e293b" text-anchor="middle" font-weight="600">当前位置表示 h</text><text x="110.0" y="216.5" font-size="12" fill="#475569" text-anchor="middle" font-weight="400">路由权重 0.6 / 0.3 / 0.1</text><rect x="290" y="70" width="180" height="68" rx="8" fill="#ccfbf1" stroke="#c7d2fe"></rect><text x="380.0" y="101.0" font-size="15" fill="#1e293b" text-anchor="middle" font-weight="600">专家 1</text><text x="380.0" y="123.0" font-size="12" fill="#475569" text-anchor="middle" font-weight="400">E₁(h) = [3, 0]</text><line x1="190" y1="197" x2="285" y2="104" stroke="#64748b" stroke-width="1.8" marker-end="url(#moe-routing-arrow)"></line><line x1="470" y1="104" x2="530" y2="197" stroke="#64748b" stroke-width="1.8" marker-end="url(#moe-routing-arrow)"></line><rect x="290" y="175" width="180" height="68" rx="8" fill="#ccfbf1" stroke="#c7d2fe"></rect><text x="380.0" y="206.0" font-size="15" fill="#1e293b" text-anchor="middle" font-weight="600">专家 2</text><text x="380.0" y="228.0" font-size="12" fill="#475569" text-anchor="middle" font-weight="400">E₂(h) = [0, 6]</text><line x1="190" y1="197" x2="285" y2="209" stroke="#64748b" stroke-width="1.8" marker-end="url(#moe-routing-arrow)"></line><line x1="470" y1="209" x2="530" y2="197" stroke="#64748b" stroke-width="1.8" marker-end="url(#moe-routing-arrow)"></line><rect x="290" y="280" width="180" height="68" rx="8" fill="#e2e8f0" stroke="#c7d2fe"></rect><text x="380.0" y="311.0" font-size="15" fill="#1e293b" text-anchor="middle" font-weight="600">专家 3</text><text x="380.0" y="333.0" font-size="12" fill="#475569" text-anchor="middle" font-weight="400">本位置未选择</text><line x1="190" y1="197" x2="285" y2="314" stroke="#64748b" stroke-width="1.8" stroke-dasharray="5 4" marker-end="url(#moe-routing-arrow)"></line><rect x="535" y="155" width="195" height="85" rx="8" fill="#e0e7ff" stroke="#c7d2fe"></rect><text x="632.5" y="194.5" font-size="15" fill="#1e293b" text-anchor="middle" font-weight="600">加权结果 [2, 2]</text><text x="632.5" y="216.5" font-size="12" fill="#475569" text-anchor="middle" font-weight="400">权重重新归一化：2/3、1/3</text></g><text x="24" y="29" font-size="19" fill="#0f172a" text-anchor="start" font-weight="700"><tspan x="24" dy="0">示例路由：每个位置选择专家，再合并输出</tspan></text><text x="24" y="381" font-size="13" fill="#475569" text-anchor="start" font-weight="400"><tspan x="24" dy="0">这是 top-2 且重新归一化的教学例子；不同 MoE 的门控和共享专家设计可不同。</tspan></text>
</svg>

这只是某一种门控形式：top-k、权重归一化方式、容量控制、哪些层使用 MoE 都依具体设计而定。Switch Transformer 展示了 top-1 稀疏路由；Mixtral 的已公开架构在每层为每个 token 选择两个前馈专家。[Switch Transformers](https://arxiv.org/abs/2101.03961)、[Mixtral of Experts](https://arxiv.org/abs/2401.04088)

### 专家不会自动变成人工划分的学科

“专家”是子网络的名字，不意味着系统预先配置了数学专家、法律专家和代码专家。训练可能产生某种专业化，实际路由还受表示、位置及优化目标影响；不能把某次选择当作可靠的主题分类结果。

训练中的负载失衡会让少数专家接收大量 token，而其他专家训练不足；服务时也可能形成热点和等待。设计可以用辅助损失、路由约束等方法改善，但“每批必须绝对均匀”也不一定符合任务需求。部分架构引入始终参与的共享专家，试图承接公共知识并减少冗余；这是特定架构选择，并非所有 MoE 的必备部件。[DeepSeekMoE](https://arxiv.org/abs/2401.06066)

### 一个参数账看容量与计算的分离

假设模型的非专家部分有 1B 参数，所有 MoE 层合计每一路专家对应 2B 参数，共 8 路，每个位置选 2 路。则总参数为 1+8×2=17B，按此简化口径，每 token 参与的参数约为 1+2×2=5B。增加到 16 路而仍选 2 路，总量变成 33B，激活口径仍约 5B。

这个例子说明可以增加候选容量而不按同样比例增加专家计算，但没有证明计算量完全不变：路由器、通信和负载都会变化。实际模型的参数统计还涉及 embedding、输出头、共享专家等口径，应读模型报告，不能仅用“专家数×某个稠密模型大小”猜总量。

### 专家的选择与加权是两步

路由器给出的分数不等于最终混合系数：本例先选 top-k，再在选中项之间重新归一化。提高 E1 权重会改变混合结果；增大 k 还会增加实际执行的专家数量。图示固定专家输出，用来隔离路由这一层。

**专家的选择与加权是两步**

只在选中的专家中归一化，再合并它们的输出；未选中不等于权重不存在。


## 架构信息与任务能力分开验证

工具调用失败先区分模型输出、聊天模板、工具协议解析、上下文缺失和工具执行异常。出现了文本形式的函数名，却没有合法调用结构，不足以证明 MoE 激活参数太少；模型可能没有经过对应协议训练，也可能是推理引擎没有使用正确模板。

没有通用的“7B 可以稳定单步、20B 可以稳定多步”的激活参数门槛。训练数据、后训练方法、上下文、解码配置、任务难度和工具反馈都会影响结果。把参数规模当筛选信息可以，把它当成功保证则缺少证据。

继续阅读：[推理阶段、KV cache 与显存](/ai/01-model-foundations/03-inference-and-kv-cache)。
