自注意力机制:LLM 的核心引擎

LLM 工作原理 · Lesson 2 of ~10
Self-Attention — The Core Mechanism
2026-06-29 · 约 15 分钟

上一课我们完成了 text → vectors 的转换。现在我们有一组向量,每个代表一个 token 的"意义"。但问题来了:

这些向量是独立的。"cat" 的 embedding 不知道 "sat" 的存在。

然而,语言的核心就是关系。"The cat sat on the mat because it was tired" — 这里的 "it" 指代什么?人类能轻松理解 "it" 指 "cat",但模型怎么做到?

答案就是 Self-Attention(自注意力)——让每个 token "看看"句子中的其他 token,决定该关注谁。

直觉:一个"动态搜索"系统

想象你在一个图书馆里。你有一个问题(query),每本书有一个标签(key)和内容(value)。你会怎么做?

  1. 拿你的问题,和每本书的标签比较
  2. 算出"匹配度"——哪些书和你的问题最相关
  3. 根据匹配度,把相关书的内容加权混合起来

Self-Attention 就是这个过程的数学实现。只不过"图书馆"是当前句子的所有 token。

Self-Attention 的本质:让每个 token 根据当前上下文,动态地决定该关注其他哪些 token,以及关注多少。这不是固定的规则——同样的词在不同句子中会关注不同的东西。

三个关键角色:Query、Key、Value

在 Self-Attention 中,每个 token 的 embedding 向量会被变换成三个不同的向量:

向量角色类比
Query (Q) "我在找什么?" 搜索关键词
Key (K) "我是什么?" 书籍标签/索引
Value (V) "我能提供什么信息?" 书籍内容

用代码来理解:

# 每个 token 的 embedding (e.g., 4096 维)
embedding = [0.12, -0.87, 0.34, ...]

# 通过三个不同的线性变换(三个权重矩阵),得到 Q, K, V
query   = W_q @ embedding  # "我想找什么"
key     = W_k @ embedding  # "我是什么"
value   = W_v @ embedding  # "我能提供什么"

这里的 W_qW_kW_v可学习的权重矩阵——它们在训练过程中被优化。你可以把它们想象成三个不同的"投影镜头",从不同角度观察同一个 embedding。

计算过程:一步步来

让我们用 "The cat sat on the mat" 这句话,看看 Self-Attention 如何工作。

Step 1: 生成 Q, K, V

# 6 个 token,每个变成 3 个向量
"The"  → q₁, k₁, v₁
"cat"  → q₂, k₂, v₂
"sat"  → q₃, k₃, v₃
"on"   → q₄, k₄, v₄
"the"  → q₅, k₅, v₅
"mat"  → q₆, k₆, v₆

Step 2: 计算注意力分数

对于每个 token,我们用它的 Query 去和所有 token 的 Key 做点积(dot product),得到"匹配分数":

# 以 "sat" (token 3) 为例
score(sat→The)  = q₃ · k₁  = 0.1
score(sat→cat)  = q₃ · k₂  = 2.8  ← 高分!"sat" 关注和 "cat" 的关系
score(sat→sat)  = q₃ · k₃  = 0.5
score(sat→on)   = q₃ · k₄  = 1.2
score(sat→the)  = q₃ · k₅  = 0.0
score(sat→mat)  = q₃ · k₆  = 1.5

点积越高,说明两个 token 越"相关"。这里 "sat" 和 "cat" 的匹配度最高——因为 "sat" 的 query 在寻找"谁在坐?",而 "cat" 的 key 正好标识了"我是坐的主体"。

Step 3: Softmax 归一化

原始分数不太好用——它们的大小没有固定范围。所以我们用 softmax 把分数转换成概率分布(总和为 1):

# Softmax 后的注意力权重
weight(sat→The)  = 0.02
weight(sat→cat)  = 0.65  ← 65% 的注意力在 "cat" 上
weight(sat→sat)  = 0.04
weight(sat→on)   = 0.08
weight(sat→the)  = 0.01
weight(sat→mat)  = 0.20
Softmax 的作用:把任意数字变成"概率分布"。分数高的获得更大比例,分数低的被压缩到接近 0。这确保模型的注意力是有选择的——不会平均分配给所有 token。

Step 4: 加权求和

最后,用注意力权重对所有 token 的 Value 做加权求和,得到输出向量:

# "sat" 的新表示
output_sat = 0.02 × v₁("The")
           + 0.65 × v₂("cat")   ← 主要来自 "cat"
           + 0.04 × v₃("sat")
           + 0.08 × v₄("on")
           + 0.01 × v₅("the")
           + 0.20 × v₆("mat")

# 结果:一个新的 4096 维向量,融合了上下文信息
# "sat" 现在"知道"了自己在描述 "cat" 的动作

这就是 Self-Attention 的核心:每个 token 的新表示,不再是孤立的 embedding,而是融合了整个上下文的"信息混合物"

完整流程图

输入: 6 个 token 的 embedding [e₁, e₂, e₃, e₄, e₅, e₆]
          │
          ▼
    ┌─────────────┐
    │ 线性变换     │  每个 e → (q, k, v)
    └─────────────┘
          │
          ▼
    ┌─────────────┐
    │ Q·Kᵀ        │  计算所有 token 之间的匹配分数
    │ (点积)       │  得到一个 6×6 的分数矩阵
    └─────────────┘
          │
          ▼
    ┌─────────────┐
    │ Softmax      │  分数 → 注意力权重(概率分布)
    └─────────────┘
          │
          ▼
    ┌─────────────┐
    │ × V          │  用权重对 Value 加权求和
    │ (矩阵乘法)   │
    └─────────────┘
          │
          ▼
输出: 6 个新的向量 [o₁, o₂, o₃, o₄, o₅, o₆]
      每个都融合了全局上下文信息

为什么叫"Self"?

注意:所有 token 的 Q、K、V 都来自同一个序列。模型不是在一个"外部记忆库"中搜索,而是在自己的输入中搜索关系。这就是"self"的含义——自己关注自己。

对比一下:如果你用搜索引擎查资料,那是"cross-attention"(查询和文档是两个不同的东西)。而 Self-Attention 是一句话里的词互相查看——"cat" 既可以是 searcher(用 Query 搜索),也可以是被搜索的目标(用 Key 被找到)。

Multi-Head Attention:多个视角

一个 Self-Attention 层只能学到一种"关注模式"。但语言关系是多维的——语法关系、语义关系、指代关系……怎么办?

解决方案:Multi-Head Attention(多头注意力)。并行运行多组 Self-Attention,每组学习不同的关注模式:

# 例如 96 个 head(GPT-3 使用 96 个注意力头)
Head 1: 可能关注"语法主语"关系 (sat → cat)
Head 2: 可能关注"介词宾语"关系 (on → mat)
Head 3: 可能关注"指代"关系 (it → cat)
Head 4: 可能关注"邻近词"关系
...
Head 96: 可能关注某种我们尚未理解的模式

# 所有 head 的输出拼接起来,再做一次线性变换
output = W_o @ concat(head₁, head₂, ..., head₉₆)
Multi-Head Attention 就像一个团队:每个人戴着不同的"眼镜"看同一句话,有人看语法,有人看语义,有人看指代。最后大家把发现汇总起来,得到一个更全面的理解。

Causal Masking:GPT 的特殊限制

GPT 系列模型(包括 Claude)有一个重要限制:token 只能关注它自己和前面的 token,不能看后面的

为什么?因为 GPT 是一个自回归模型——它一个 token 一个 token 地生成文本。当它在生成第 5 个 token 时,第 6、7、8 个 token 还不存在。

# Causal masking(因果遮罩)的效果
# 行 = 当前 token,列 = 可以关注的 token
# ✅ = 可以看到,❌ = 被遮罩

       The  cat  sat  on  the  mat
The  [ ✅   ❌   ❌   ❌   ❌   ❌  ]
cat  [ ✅   ✅   ❌   ❌   ❌   ❌  ]
sat  [ ✅   ✅   ✅   ❌   ❌   ❌  ]
on   [ ✅   ✅   ✅   ✅   ❌   ❌  ]
the  [ ✅   ✅   ✅   ✅   ✅   ❌  ]
mat  [ ✅   ✅   ✅   ✅   ✅   ✅  ]

这就像一个下三角矩阵。实现方式很简单——在 softmax 之前,把"未来"位置的分数设为负无穷大(-∞),softmax 后这些位置的权重就变成了 0。

对开发者的意义:
• 这解释了为什么 LLM 是"从左到右"生成的——它不能"回头修改"已经输出的 token。
• 这也意味着上下文窗口中的位置很重要。放在前面的指令更容易被后续 token 关注到。
• 当你发现模型"忘记"了前面的指令,可能是因为中间内容太多,注意力被稀释了。

计算复杂度:为什么上下文窗口有限

Self-Attention 需要计算每对 token 之间的关系。如果有 n 个 token,就需要计算 个注意力分数。

上下文长度注意力分数数量相对计算量
1,024~100 万
8,192~6700 万64×
100,000~100 亿~10,000×
200,000~400 亿~40,000×

计算量随上下文长度平方增长。这就是为什么长上下文窗口如此昂贵——不仅是内存问题,更是计算问题。

这就是为什么各家公司都在研究"高效注意力"(如 FlashAttention、sparse attention)——试图把 O(n²) 降下来。也是为什么超长上下文(100万+ token)的模型仍然比较慢且贵的原因。

用代码来感受(伪代码)

把整个过程写成一个函数,用 Python 风格的伪代码:

def self_attention(embeddings):
    """
    embeddings: shape (n_tokens, d_model)
    例如 (6, 4096) 表示 6 个 token,每个 4096 维
    """
    # Step 1: 计算 Q, K, V
    Q = embeddings @ W_q  # (6, 4096)
    K = embeddings @ W_k  # (6, 4096)
    V = embeddings @ W_v  # (6, 4096)

    # Step 2: 计算注意力分数
    scores = Q @ K.T       # (6, 6) — 每对 token 的匹配度
    scores = scores / sqrt(d_k)  # 缩放,防止分数太大

    # Step 2.5: 应用 causal mask (GPT 风格)
    mask = lower_triangular(6)
    scores = where(mask, scores, -infinity)

    # Step 3: Softmax 归一化
    weights = softmax(scores, axis=-1)  # (6, 6) — 每行和为 1

    # Step 4: 加权求和
    output = weights @ V   # (6, 4096)

    return output

就这么简单!整个 Self-Attention 的核心逻辑,不到 10 行代码。当然,实际实现中还有 multi-head 拆分、bias、layer normalization 等细节,但核心思想就是这些。

全流程回顾

现在我们可以把前两课串起来,看到完整的"输入处理"流程:

"The cat sat"
      │
      ▼
  ┌────────────┐
  │ Tokenizer   │  "The cat sat" → ["The", " cat", " sat"]
  └────────────┘
      │
      ▼
  ┌────────────┐
  │ Vocabulary  │  ["The", " cat", " sat"] → [464, 3797, 1042]
  │ Lookup      │
  └────────────┘
      │
      ▼
  ┌────────────┐
  │ Embedding   │  [464, 3797, 1042] → [e₁, e₂, e₃]  (3 个 4096 维向量)
  └────────────┘
      │
      ▼
  ┌────────────┐
  │ Self-       │  [e₁, e₂, e₃] → [o₁, o₂, o₃]  (融合了上下文的向量)
  │ Attention   │  "cat" 现在知道 "sat" 的存在
  └────────────┘
      │
      ▼
    ??? → 如何从向量变回文字?(下一课!)

检查一下理解

🧠 Quiz

Q1. 在 Self-Attention 中,Query 和 Key 的点积(dot product)表示什么?

Q2. 为什么 GPT 需要 causal masking?

Q3. Multi-Head Attention 中,不同的 head 主要区别是什么?

Q4. Self-Attention 的计算复杂度是 O(n²),其中 n 是什么?

📚 推荐深入学习

Self-Attention 是 LLM 最核心的概念。如果你还没看,强烈推荐:

下一课预告

现在我们有了"融合了上下文的向量表示"。但 LLM 最终要输出文字——怎么从向量变回 token?下一课我们来看 Output Layer(输出层):线性变换 + Softmax → 概率分布 → 下一个 token。还会涉及 temperaturetop-p 这些你在 API 中常见的参数到底在干什么。