# 《Attention Is All You Need》核心精读

> 论文：Vaswani et al., 2017. arXiv:1706.03762
> 标注：扣子 | 2026-06-19

---

## 第一步：为什么需要 Attention？（§4 论文论证）

Transformer 出现之前，序列建模靠 RNN（循环）或 CNN（卷积）。论文用三个指标对比，证明了为什么可以**扔掉它们、只用注意力**：

| 指标 | Self-Attention | RNN (循环) | CNN (卷积) | 谁赢 |
|---|---|---|---|---|
| **每层计算复杂度** | O(n²·d) | O(n·d²) | O(k·n·d²) | n < d 时 Attention 更快 |
| **并行度**（最少串行操作） | O(1) | O(n) | O(log_k n) | **Attention 完胜**：所有位置同时算 |
| **最长路径长度**（信号从位置 1 传到位置 n） | O(1) | O(n) | O(log_k n) | **Attention 完胜**：任意两位置直接连接 |

三个指标的具体含义：

1. **计算复杂度 O(n²·d)**：序列长度 n 平方项是"每个词看所有词"的代价。但当 n 不大时（如句子翻译 n≈50），n² < d²(=512²)，Attention 反而比 RNN 快。

2. **并行度 O(1)**：这是 Transformer 最大的杀手锏。RNN 必须等前一个 token 算完才能算下一个（n 步串行），Self-Attention 所有 token 一步并行——训练快太多了。

3. **最长路径 O(1)**：RNN 里位置 1 的信号传到位置 n 要穿 n 层，梯度可能消失。Self-Attention 里任意两个位置**直接相连**——"making"和 50 个词后的"difficult"之间只有一步，不存在梯度衰减。

> 这就是论文标题《Attention Is All You Need》的底气：自注意力在并行度和长距离依赖上完胜 RNN/CNN，计算复杂度可以接受——所以可以把循环和卷积全扔掉，**只用注意力**。

---

## 第二步：整体长什么样？（Figure 1 架构全景图）

![](https://arxiv.org/html/1706.03762v7/Figures/ModalNet-21.png)

**数据流（从左到右）**：
1. Inputs → Input Embedding + Positional Encoding → Encoder（左半）
2. Encoder 输出 → Decoder（右半）的 Encoder-Decoder Attention 层
3. Outputs（shifted right）→ Output Embedding + Positional Encoding → Decoder
4. Decoder 输出 → Linear + Softmax → 预测下一个 token

> 🎯 **你现在关注的重点**：Decoder 那条线。DeepSeek-V4 是 Decoder-only，只保留右半 + 去掉 Encoder-Decoder Attention。

---

## 第三步：基础骨架——Encoder 和 Decoder 怎么搭的？（§3.1）

### Encoder
- 堆叠 **N=6** 层相同结构
- 每层两个子层：
  1. **Multi-Head Self-Attention**（自注意力）
  2. **Position-wise Feed-Forward Network**（逐位置前馈网络）
- ⚡ 每个子层都用 **Residual Connection**（残差连接）：
$$\text{output} = \text{LayerNorm}(x + \text{Sublayer}(x))$$
- 所有子层输出维度 d_model = 512

> 🔑 **残差连接的意义**：把输入 x 直接"短路"加到输出上，防止深层网络梯度消失。你可以理解为"每层只学残差部分，原始信息始终有一条高速公路直达深层"。

### Decoder
- 同样 N=6 层
- 每层**三个**子层（比 Encoder 多一个）：
  1. Masked Multi-Head Self-Attention（**掩盖式**自注意力）
  2. Multi-Head Attention over Encoder output（**交叉注意力**——Q 来自 Decoder，K、V 来自 Encoder）
  3. Position-wise Feed-Forward Network
- ⚡ **Masked** 的含义：Decoder 生成 token i 时，只允许看到位置 < i 的内容，后面的全遮住（设为 -∞）

> 🎯 **这就是自回归生成的本质**：不能偷看未来的答案。你之前问的 KV Cache 就是在这个 Masked Self-Attention 层里发挥作用的——前面的 K、V 已经算好了，只算当前 token 的 Q。

---

## 第四步：前置数学——矩阵维度视角

公式里的 Q、K、V 不是向量，是**矩阵**。搞清楚形状，一切自明：

| 符号 | 形状 | 含义 |
|---|---|---|
| Q | (seq_len, d_k) | 每一行是一个 token 的"我在找什么" |
| K | (seq_len, d_k) | 每一行是一个 token 的"我是什么" |
| V | (seq_len, d_v) | 每一行是一个 token 的"我有什么信息" |
| Q·K^T | (seq_len, seq_len) | **注意力矩阵**——第 i 行第 j 列 = token i 对 token j 的关注度 |
| softmax(Q·K^T/√d_k) | (seq_len, seq_len) | 每行是一个概率分布（每行之和 = 1） |
| · V | (seq_len, d_v) | 加权融合后的输出 |

> 🔑 **softmax 在哪一维做的？** 论文在 K^T 方向做——即每一行独立 softmax。这意味着**每个 query 对所有 key 的权重加起来等于 1**，不是整个矩阵加起来等于 1。

**类比**：你走进一个房间（你是 Q），房间里每个人脖子上挂了一个名牌（K）和一本手册（V）。你先扫一遍名牌，算出跟每个人的"相关度"（Q·K^T），然后按相关度比例去翻阅对应的手册内容，最后融合成你的理解。

---

## 第五步：核心心脏——缩放点积注意力（§3.2.1）

原文定义：

> An attention function can be described as mapping a query and a set of key-value pairs to an output, where the query, keys, values, and output are all vectors. The output is computed as a weighted sum of the values, where the weight assigned to each value is computed by a compatibility function of the query with the corresponding key.

### 公式（这篇论文最重要的一个公式）：

$$\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V$$

### 逐项拆解：

| 步骤 | 操作 | 含义 |
|---|---|---|
| ① | `Q · K^T` | 计算 Query 和每个 Key 的相似度（点积），得到一个"相关性分数矩阵" |
| ② | `÷ √d_k` | **缩放**：d_k 是 Key 的维度。不缩放的话点积值会很大→softmax 梯度趋近 0→学不动 |
| ③ | `softmax(...)` | 把分数转成概率分布（所有权重之和 = 1），类似"注意力分配比例" |
| ④ | `· V` | 根据权重加权求和 Value，得到最终输出 |

### 用你之前理解的 KV Cache 来对照：

> 推理时每一轮：**新 token 的 Q** 去和 **缓存里的旧 K** 做点积 → softmax → 加权旧 V。新 token 的 K、V 算完后也塞进缓存。

### 为什么要除以 √d_k？

论文原话（脚注）：
> 假设 q 和 k 的分量是均值为 0、方差为 1 的独立随机变量，则它们的点积 q·k 的均值为 0，**方差为 d_k**。

d_k 越大，点积值越分散 → softmax 输出趋近 one-hot → 梯度几乎为零。除以 √d_k 把方差压回 1，让 softmax 平滑、梯度正常流动。

---

## 第六步：代码视角——PyTorch 伪代码

看公式不如看代码。一个完整的 Scaled Dot-Product Attention 长这样：

```python
import torch
import torch.nn.functional as F

def scaled_dot_product_attention(Q, K, V, mask=None):
    """
    Q: (batch, seq_len, d_k)
    K: (batch, seq_len, d_k)
    V: (batch, seq_len, d_v)
    """
    d_k = Q.size(-1)

    # 步骤 ① + ②：点积 + 缩放
    scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(d_k)

    # 步骤 ② 补充：如果提供了 mask（Decoder 自回归用），把未来位置遮为 -∞
    if mask is not None:
        scores = scores.masked_fill(mask == 0, float('-inf'))

    # 步骤 ③：softmax 在最后一个维度（每行独立）
    attn_weights = F.softmax(scores, dim=-1)

    # 步骤 ④：加权求和
    output = torch.matmul(attn_weights, V)

    return output, attn_weights
```

对照公式看代码，每一行都有明确的数学对应。mask 就是 Decoder 里 "设为 -∞" 的操作——softmax(e^(-∞)) = 0，那个位置的权重归零。

---

## 第七步：为什么要多头？（§3.2.2 Multi-Head Attention）

> Instead of performing a single attention function... we found it beneficial to linearly project the queries, keys and values **h times** with different, learned linear projections... perform the attention function in parallel... concatenated and once again projected.

### 为什么需要多头？

单头注意力只能学一种"关系模式"。多头让模型同时从**不同子空间**去理解：

- Head 1 可能关注 **语法结构**（主谓宾）
- Head 2 可能关注 **长距离依赖**（代词 → 先行词）
- Head 3 可能关注 **局部搭配**（形容词 → 名词）

### 参数配置（论文原始）

- h = 8 个头
- 每个头：d_k = d_v = d_model / h = 512 / 8 = **64**
- 计算量 ≈ 单头全维 Attention（维度降低抵消了多头开销）

---

## 第八步：三种注意力怎么组合使用？（§3.2.3）

论文里三个地方用了注意力，不是重复，是分工不同：

| 类型 | 类比 | 为什么需要它 |
|---|---|---|
| **Encoder Self-Attention** | 同声传译的"听完整句" | 翻译一个词需要看整个源句上下文 |
| **Decoder Masked Self-Attention** | 你说上半句时不能偷看下半句 | 生成第 i 个词时只能看前 i-1 个，保持自回归 |
| **Encoder-Decoder Attention** | 翻译员"边听边翻" | Q 来自 Decoder（我要翻什么），K、V 来自 Encoder（源句有什么）——这就是跨语言的信息桥 |

> 🎯 Encoder-Decoder Attention 是关键设计：Encoder 把整个源句压成一层层表示，Decoder 每生成一个词就通过 Cross-Attention "回看"一眼源句。这也是为什么 Encoder 要 6 层全并行（一次性读完源句），Decoder 要 6 层逐 token 生成。

> 🎯 **DeepSeek-V4 是 Decoder-only**，所以它只有前两种 Attention，没有第三种（Encoder-Decoder Attention）。这也是现在 GPT 系列的主流做法。

---

## 第九步：注意力之后——为什么还要 FFN？（§3.3）

Attention 学的是**Token 之间的关系**（"这个词跟那个词有多相关"）。但关系学完以后，每个 token 自己的语义还需要**独立加工**。

换个角度看：Self-Attention 是**全局交流会**（每个人跟所有人聊一圈），FFN 是**会后独自消化**（把聊到的信息转化成自己的新理解）。

1. Attention → 横向：把上下文融进来
2. FFN → 纵向：对每个位置做非线性变换 `ReLU(xW₁+b₁)W₂+b₂`

FFN 内部维度 d_ff = 2048，是 d_model(512) 的 4 倍——先升维再降维的设计，让模型有足够的"思考空间"再压缩回标准维度。

这种**Attention + FFN 交替 N 层**的设计，和 CNN 里的 Conv + ReLU、RNN 里的 Hidden + Output 一样，都是"全局交互 → 局部加工"的节奏。

---

## 第十步：没有 RNN，怎么知道词序？（§3.5 位置编码）

Transformer 没有循环也没有卷积，token 顺序信息完全丢失。如果不加位置编码，"A 打了 B"和"B 打了 A"在模型眼里是完全一样的。

论文选了正弦波的位置编码，核心原因是这个性质：

> 对于任何固定偏移 k，`PE(pos+k)` 可以表示为 `PE(pos)` 的**线性函数**。

这意味着模型不需要"背"每个位置的编码，而是可以**通过线性变换推导相对位置**。正弦波的周期性天然把"距离"编码进了相位差：

- 高频维度（i 小）：对相邻位置敏感（细粒度）
- 低频维度（i 大）：对大跨度位置敏感（粗粒度）

相当于给每个位置装了一个多频段的"位置指纹"，不同维度捕捉不同尺度的距离信息。

### 为什么 sin/cos 这个设计能让模型学会相对位置？

核心洞见：**位置 pos+k 的编码，可以通过位置 pos 的编码经过一个线性变换得到。**

对任意固定偏移 k，存在矩阵 M_k（只依赖于 k 和各维度频率），使得：

$$PE(pos + k) = M_k \cdot PE(pos)$$

具体看某一对维度（2i 和 2i+1），记频率为 ω：

$$ 
\begin{bmatrix} PE_{(pos+k, 2i)} \\ PE_{(pos+k, 2i+1)} \end{bmatrix} = 
\begin{bmatrix} \cos(\omega k) & \sin(\omega k) \\ -\sin(\omega k) & \cos(\omega k) \end{bmatrix} 
\begin{bmatrix} PE_{(pos, 2i)} \\ PE_{(pos, 2i+1)} \end{bmatrix} 
$$

这本质就是一个**二维旋转矩阵**——每个维度对在自己的平面上旋转了一个固定角度 ω·k。

因为 PE 之间的变换是**线性的**，模型学 Q 和 K 的投影矩阵时很容易捕捉到这种线性关系，让 QK^T 的结果直接反映出 pos₁ 和 pos₂ 之间的**相对距离**，而不是绝对位置。

这也是后来 **RoPE（旋转位置编码）** 的灵感来源——把这种旋转的直觉从"加法形式"改成"直接作用在 Q 和 K 上"，让相对位置信息更显式地编码进 Attention 计算。现在几乎所有主流模型（LLaMA、DeepSeek、Qwen 等）都用 RoPE 而非原始正弦编码，但核心思想一脉相承。

---

## 收尾：一句话总结

**Self-Attention = 让每个词自动判断"其他词跟我有多相关"，按相关性加权融合其他词的信息——不依赖距离，不串行等待，一步完成。**
