3.2 Transformer架构 - 从零开始的完全指南

📖 目录


🎯 开篇:为什么要学Transformer?

你可能会问:Transformer到底是什么?

12岁版解释

想象你在看一本书,理解一个句子时,你不是一个字一个字地看,而是会同时关注整个句子中的所有字,看它们之间的关系。比如”我爱吃苹果”,你会同时看到”我”、“爱”、“吃”、“苹果”,并理解它们的关系。

Transformer就是这样一个模型:它能同时看到所有信息,并找出它们之间的关系。

为什么Transformer这么重要?

  • GPT系列(ChatGPT、GPT-4):基于Transformer
  • BERT:基于Transformer
  • 所有现代大语言模型:都基于Transformer

一句话:不懂Transformer,就无法理解现代AI。


第一部分:预备知识 - RNN的困境

这一部分解决什么问题?

背景问题:在Transformer出现之前(2017年之前),处理文本序列主要用RNN(循环神经网络)。但RNN有两个致命缺陷。

RNN的问题:

  1. 记忆衰减:处理长序列时,前面的信息会逐渐被遗忘
  2. 无法并行:必须按顺序一个一个处理,训练速度极慢

为什么需要Transformer:彻底解决这两个问题,让模型能同时看到所有位置的信息,并且可以并行计算。

1.1 什么是序列到序列任务?

12岁版解释

把一串东西变成另一串东西。

例子:

  • 机器翻译:中文 → 英文
  • 文本摘要:长文章 → 短摘要
  • 对话系统:问题 → 回答

1.2 RNN的问题是什么?

在Transformer之前,大家用**RNN(循环神经网络)**处理序列。

RNN的工作方式:

一个字 → 处理 → 下一个字 → 处理 → 下一个字 → ...

问题:

  1. 慢:必须一个一个处理,无法并行
  2. 记不住远处的信息:处理第100个字时,第1个字的信息已经忘了

类比

就像你读一本书,只能一个字一个字地读,而且读到后面就忘了前面讲了什么。

1.3 Transformer的核心思想

一句话:同时看所有信息,找出它们之间的关系。

类比

  • RNN:像用手电筒照路,一次只能看一小块
  • Transformer:像开灯,一次看到整个房间

关键机制:Self-Attention(自注意力)


第二部分:Self-Attention机制 - Transformer的核心突破

这一部分解决什么问题?

核心问题:RNN只能顺序处理,无法同时看到所有位置。如何让模型”同时看到”整个序列,并计算任意两个位置之间的关系?

解决方案:Self-Attention机制 —— 通过Q、K、V三个矩阵,计算每个位置对其他所有位置的注意力权重。

突破点:

  1. 打破了顺序处理的限制,可以并行计算
  2. 任意两个位置的距离都是1(直接连接),解决了长距离依赖问题
  3. 计算复杂度是O(n²),但可以并行,实际速度比RNN快很多

局限:O(n²)的复杂度意味着序列长度翻倍,计算量变4倍。这就是为什么后来有了各种改进(如Gated Attention)。

2.1 什么是Attention?

12岁版解释:查字典

想象你在查字典:

  1. 你有一个要查的字(Query,查询)
  2. 字典里有很多字(Key,键)
  3. 每个字都有解释(Value,值)

你的过程:

  1. 拿着要查的字,和字典里的每个字比较相似度
  2. 找到最相似的字
  3. 读取那个字的解释

Attention就是这个过程!

2.2 Query、Key、Value是什么?

这是Attention机制的三个核心概念。

Query(查询)

12岁版解释

你要查的东西

在Transformer中:当前位置的词,想知道”我应该关注哪些其他词?”

例子: 句子:“我爱吃苹果”

  • 当处理”吃”这个字时,Query就是”吃”
  • “吃”想知道:我应该关注哪些词?

Key(键)

12岁版解释

字典里的索引

在Transformer中:所有位置的词,用来和Query比较

例子:

  • Key包括:“我”、“爱”、“吃”、“苹果”
  • 用来和Query(“吃”)比较相似度

Value(值)

12岁版解释

字典里的解释

在Transformer中:所有位置的词的实际内容

例子:

  • Value也是:“我”、“爱”、“吃”、“苹果”
  • 但这是实际要提取的信息

为什么Key和Value不一样?

虽然它们来自同样的词,但:

  • Key:用来匹配(“这个词和我相关吗?”)
  • Value:用来提取信息(“给我这个词的信息”)

它们通过不同的线性变换得到,所以数学上是不同的。

2.3 Attention的计算过程

步骤1:计算相似度(Q·K^T)

目标:看Query和每个Key有多相似

数学:

相似度 = Query · Key^T

例子

Query: "吃"
Keys: ["我", "爱", "吃", "苹果"]

相似度:
- "吃" vs "我": 0.1
- "吃" vs "爱": 0.3
- "吃" vs "吃": 1.0  ← 自己和自己最相似
- "吃" vs "苹果": 0.8  ← "吃"和"苹果"很相关!

步骤2:缩放(除以√d)

公式:

缩放后的相似度 = (Q·K^T) / √d

其中d是向量的维度。

为什么要除以√d?

问题:如果向量维度很大(比如d=512),点积的结果会很大。

例子:

d=2时:Q·K^T 可能是 [0.1, 0.3, 1.0, 0.8]
d=512时:Q·K^T 可能是 [50, 150, 500, 400]  ← 太大了!

后果:数值太大,Softmax会出问题(后面会讲)。

解决:除以√d,把数值缩小到合理范围。

类比

就像你用温度计测体温,如果刻度太大,就看不清细微差别了。

步骤3:Softmax

目标:把相似度变成概率分布(加起来等于1)

公式:

Attention权重 = Softmax(Q·K^T / √d)

Softmax是什么?

12岁版解释

把一堆数字变成百分比

数学定义:

例子

输入: [0.1, 0.3, 1.0, 0.8]

步骤1:取指数
exp([0.1, 0.3, 1.0, 0.8]) = [1.11, 1.35, 2.72, 2.23]

步骤2:除以总和
总和 = 1.11 + 1.35 + 2.72 + 2.23 = 7.41
Softmax = [1.11/7.41, 1.35/7.41, 2.72/7.41, 2.23/7.41]
        = [0.15, 0.18, 0.37, 0.30]

检查:0.15 + 0.18 + 0.37 + 0.30 = 1.0 ✓

为什么用Softmax?

  1. 归一化:所有权重加起来等于1
  2. 突出重点:大的值变得更大,小的值变得更小
  3. 可微分:可以用梯度下降训练

步骤4:加权求和(·V)

目标:根据权重,提取Value的信息

公式:

输出 = Attention权重 · V

12岁版解释

你查字典时,找到了相关的字,现在要读取它们的解释。但不是只读一个,而是按照相关程度,读取多个解释的加权平均。

例子

Attention权重: [0.15, 0.18, 0.37, 0.30]
Values:
  "我" → [1, 0, 0]
  "爱" → [0, 1, 0]
  "吃" → [0, 0, 1]
  "苹果" → [0.5, 0, 0.5]

输出 = 0.15×[1,0,0] + 0.18×[0,1,0] + 0.37×[0,0,1] + 0.30×[0.5,0,0.5]
     = [0.15+0.15, 0.18, 0.37+0.15]
     = [0.30, 0.18, 0.52]

解释:

  • “吃”最关注自己(0.37)和”苹果”(0.30)
  • 所以输出主要包含”吃”和”苹果”的信息

2.4 完整的数学公式

Self-Attention的完整公式:

其中:

  • (Query权重矩阵)
  • (Key权重矩阵)
  • (Value权重矩阵)
  • :输入(所有词的embedding)
  • :可学习的参数矩阵

完整流程:

输入X →
  → Q = X·W_Q
  → K = X·W_K
  → V = X·W_V
→ 相似度 = Q·K^T
→ 缩放 = 相似度 / √d_k
→ 权重 = Softmax(缩放)
→ 输出 = 权重·V

2.5 手算一个例子

句子:“我爱吃”(3个词)

假设:

  • 每个词用2维向量表示(实际是512维)
  • 简化计算

输入X:

"我": [1, 0]
"爱": [0, 1]
"吃": [1, 1]

权重矩阵(简化,实际是可学习的):

W_Q = [[1, 0], [0, 1]]  (单位矩阵,简化)
W_K = [[1, 0], [0, 1]]
W_V = [[1, 0], [0, 1]]

步骤1:计算Q, K, V

Q = X·W_Q = X = [[1,0], [0,1], [1,1]]
K = X·W_K = X = [[1,0], [0,1], [1,1]]
V = X·W_V = X = [[1,0], [0,1], [1,1]]

步骤2:计算相似度矩阵

Q·K^T = [[1,0], [0,1], [1,1]] · [[1,0,1], [0,1,1]]
      = [[1, 0, 1],
         [0, 1, 1],
         [1, 1, 2]]

步骤3:缩放

d_k = 2, √d_k = 1.41
缩放后 ≈ [[0.71, 0, 0.71],
          [0, 0.71, 0.71],
          [0.71, 0.71, 1.41]]

步骤4:Softmax(对每一行)

第1行: [0.71, 0, 0.71] → Softmax: [0.40, 0.20, 0.40]
第2行: [0, 0.71, 0.71] → Softmax: [0.21, 0.39, 0.39]
第3行: [0.71, 0.71, 1.41] → Softmax: [0.24, 0.24, 0.52]

步骤5:加权求和

输出 = Softmax · V

第1行输出("我"的表示):
  = 0.40×[1,0] + 0.20×[0,1] + 0.40×[1,1]
  = [0.80, 0.60]

第2行输出("爱"的表示):
  = [0.60, 0.78]

第3行输出("吃"的表示):
  = [0.76, 0.76]

结果解释

  • “我”的新表示:[0.80, 0.60] - 主要关注自己和”吃”
  • “爱”的新表示:[0.60, 0.78] - 关注”爱”和”吃”
  • “吃”的新表示:[0.76, 0.76] - 最关注自己(权重0.52)

关键发现:每个词的新表示都融合了其他词的信息!这就是Self-Attention的魔力。


第三部分:多头注意力(Multi-Head Attention)

这一部分解决什么问题?

核心问题:单个Attention只能学习一种关系模式。比如”it”指代”animal”,单个Attention可能只学会主谓关系,无法同时学习其他关系。

解决方案:多头注意力 —— 并行运行多个Attention,每个head学习不同的关系模式。

突破点:

  • 不同head可以关注不同的语言现象(语法、语义、位置等)
  • 所有head并行计算,不增加时间成本
  • 通过拼接和线性变换,融合多个视角的信息

局限:增加了参数量(每个head都有独立的W_Q、W_K、W_V),但这是值得的。

3.1 为什么需要多头?

问题

一个Attention只能学习一种关系

例子

句子:“The animal didn’t cross the street because it was too tired”

“it”指的是什么?

  • 可能是”animal”(动物累了)
  • 也可能是”street”(街道太累?不对)

一个Attention head:可能只学会了一种关系(比如主谓关系)

多个Attention heads:可以学习多种关系

  • Head 1:主谓关系
  • Head 2:动宾关系
  • Head 3:修饰关系
  • …

3.2 多头的实现

核心思想:并行运行多个Attention,然后拼接

公式:

其中:

参数:

  • :head的数量(通常是8)
  • 每个head有自己的

流程:

输入X →
  → Head 1: Attention(Q1, K1, V1) → 输出1
  → Head 2: Attention(Q2, K2, V2) → 输出2
  → ...
  → Head 8: Attention(Q8, K8, V8) → 输出8
→ 拼接: [输出1, 输出2, ..., 输出8]
→ 线性变换: Concat · W_O
→ 最终输出

12岁版解释

就像你看一本书,用不同的角度看:

  • 第1遍:看情节
  • 第2遍:看人物关系
  • 第3遍:看写作手法
  • …

每一遍都能发现不同的东西,最后综合起来,理解更深刻。

3.3 多头的好处

  1. 学习多种关系:不同head关注不同的模式
  2. 增强表达能力:多个视角,理解更全面
  3. 并行计算:所有head可以同时计算,速度快

实验发现

  • 有些head学会了语法关系(主谓、动宾)
  • 有些head学会了语义关系(同义词、反义词)
  • 有些head学会了位置关系(相邻词)

第四部分:Transformer完整架构

4.1 整体结构

Transformer = Encoder + Decoder

  • Encoder:理解输入
  • Decoder:生成输出

12岁版解释

  • Encoder:读懂中文句子
  • Decoder:翻译成英文句子

4.2 Encoder

结构:

输入 →
  → 位置编码
  → Multi-Head Attention
  → Add & Norm(残差连接 + Layer Normalization)
  → Feed-Forward Network
  → Add & Norm
→ 输出

重复N次(原论文N=6)

每一层做什么?

  1. Multi-Head Attention:找词之间的关系
  2. Add & Norm:稳定训练
  3. Feed-Forward Network:对每个词独立处理,增加非线性
  4. Add & Norm:再次稳定

4.3 Decoder

结构:

输入 →
  → 位置编码
  → Masked Multi-Head Attention(只能看之前的词)
  → Add & Norm
  → Multi-Head Attention(看Encoder的输出)
  → Add & Norm
  → Feed-Forward Network
  → Add & Norm
→ 输出

关键区别:

  • Masked Attention:生成第i个词时,只能看前i-1个词
  • Cross Attention:Decoder看Encoder的输出

为什么要Mask?

例子:翻译”我爱吃苹果”

生成"I"时:只能看"我"
生成"love"时:只能看"我爱"
生成"eating"时:只能看"我爱吃"
...

如果能看到后面的词,就是”作弊”了!

4.4 位置编码

问题

Attention没有位置信息

例子

  • “我爱吃苹果”
  • “苹果吃爱我”

对Attention来说,这两个句子是一样的!(因为只看词之间的关系,不看顺序)

解决:加入位置编码

公式:

12岁版解释

给每个位置一个独特的”身份证号”,让模型知道词的顺序。

为什么用sin/cos?

  1. 有界:值在[-1, 1]之间
  2. 周期性:可以处理任意长度的序列
  3. 相对位置:sin和cos的性质让模型容易学习相对位置

4.5 残差连接和Layer Norm

残差连接(Residual Connection)

公式:

12岁版解释

走楼梯时,既可以走楼梯(F(x)),也可以坐电梯(x)。如果楼梯太难走,就直接坐电梯。

作用:

  1. 缓解梯度消失:深层网络训练更容易
  2. 保留原始信息:即使F(x)学不好,至少还有x

Layer Normalization

公式:

作用:

  1. 稳定训练:把数值归一化到合理范围
  2. 加速收敛:训练更快

4.6 Feed-Forward Network

结构:

12岁版解释

两层全连接网络,中间加个ReLU激活函数。

作用:

  1. 增加非线性:ReLU是非线性函数
  2. 独立处理每个词:不像Attention那样看所有词

为什么需要FFN?

Attention是线性的(后面会详细讲),FFN引入非线性,增强表达能力。

4.7 从文本到向量的完整流程

你可能会问

一句话是如何变成Transformer输入的向量的?

第一步:分词(Tokenization)

输入:原始文本

"今天天气很好"

输出:Token序列

["今天", "天气", "很好"]

常见分词方法:

  • 字符级:[“今”, “天”, “天”, “气”, “很”, “好”]
  • 词级:[“今天”, “天气”, “很”, “好”]
  • 子词级(现代常用):[“今天”, “天气”, “很好”]

12岁版解释

就像把一句话切成小块,每一块叫一个token。

子词分词算法:

  • BPE (Byte Pair Encoding):GPT系列使用
  • WordPiece:BERT使用
  • SentencePiece:通用工具

第二步:Token Embedding(词嵌入)

Embedding = 查表

# Embedding矩阵(可学习参数)
embedding_matrix = 随机初始化(vocab_size, d_model)
                 = 随机初始化(50000, 512)
 
# 查表
token_id = 1234  # "今天"的ID
embedding = embedding_matrix[1234]  # 取第1234行
          = [0.23, -0.45, 0.67, ..., 0.12]  # 512维向量

和Word2Vec的区别

  • Word2Vec:预训练 → 固定 → 静态(一个词一个向量)
  • Transformer Embedding:端到端训练 → 可更新 → 动态(通过Attention考虑上下文)

Embedding如何学习?

  1. 初始化:随机初始化,没有意义
  2. 前向传播:查表获取embedding
  3. 计算损失:预测下一个词的损失
  4. 反向传播:计算梯度(只有被使用的token有梯度)
  5. 梯度下降:更新embedding矩阵
# 更新规则
embedding[token_id] = embedding[token_id] - lr × ∂loss/∂embedding[token_id]

为什么相似的词有相似的embedding?

  • 共现关系:经常一起出现的词,embedding会变得相似
  • 上下文相似:出现在相似上下文的词,embedding相似
  • 任务驱动:为了预测下一个词,embedding必须学到语义

第三步:位置编码(Positional Encoding)

为什么需要?

Attention机制本身没有位置信息!

对于Attention来说,这两个句子是一样的:
"今天天气很好"
"很好天气今天"

方法1:固定的正弦位置编码(原始Transformer)

方法2:可学习的位置编码(GPT等,更常用)

# 位置编码矩阵(可学习参数)
positional_embedding = nn.Embedding(max_seq_len, d_model)
                     = 随机初始化(2048, 512)
 
# 查表
PE[0] = positional_embedding[0]  # 位置0的编码
PE[1] = positional_embedding[1]  # 位置1的编码

第四步:相加得到最终输入

# Token Embedding (3, 512)
token_emb = [[0.23, -0.45, ..., 0.12],   # "今天"
             [0.56, 0.78, ..., 0.45],    # "天气"
             [-0.12, 0.34, ..., -0.23]]  # "很好"
 
# Positional Encoding (3, 512)
pos_enc = [[0.00, 1.00, ..., 1.00],      # 位置0
           [0.84, 0.54, ..., 0.99],      # 位置1
           [0.91, -0.42, ..., 0.98]]     # 位置2
 
# 相加(逐元素)
X = token_emb + pos_enc  # (3, 512)

这就是Transformer的输入!

为什么相加而不是拼接?

  • 相加更高效,维度不变
  • 实验表明效果和拼接差不多
  • 参数量更少

第五部分:原始Attention的三大缺陷

这一部分解决什么问题?

背景:前面讲的Self-Attention和Multi-Head Attention是Transformer的核心,但它们并不完美。研究人员在实际使用中发现了三个严重问题。

三大缺陷:

  1. 低秩瓶颈:输出投影W_O被”吸收”,表达能力受限
  2. Attention Sink:第一个token的注意力权重异常高(46.7%)
  3. 注意力熵崩塌:长文本生成时,注意力分布变得极度集中

为什么重要:理解这些缺陷,才能理解后续的改进(如Gated Attention)是如何突破瓶颈的。

为什么要讲这个?

理解原始Attention的问题,才能理解后面Gated Attention的改进!

5.1 缺陷1:低秩瓶颈(线性瓶颈)

什么是秩?

12岁版解释

秩 = 独立方向的数量

想象你在一个房间里:

  • 1维空间(秩=1):只能前后移动(一条线)
  • 2维空间(秩=2):可以前后、左右移动(一个平面)
  • 3维空间(秩=3):可以前后、左右、上下移动(整个空间)

秩越高 = 独立方向越多 = 表达能力越强

矩阵的秩例子:

A = [1  2]
    [2  4]
    [3  6]

观察:第2列 = 2 × 第1列
     第2行 = 2 × 第1行
     第3行 = 3 × 第1行

所有行都可以用第1行表示
→ 只有1个独立方向
→ 秩 = 1

原始Attention为什么是线性的?

原始Attention的公式:

分解:

步骤1:计算注意力权重
  A = Softmax(Q·K^T / √d)  ← 这是权重矩阵

步骤2:加权求和
  z = A · V  ← 线性操作!

步骤3:输出投影
  o = z · W_O  ← 又是线性操作!

关键问题

  • z = A · V 是线性的(矩阵乘法)
  • o = z · W_O 也是线性的
  • 两个线性操作的组合还是线性!

数学证明:

其中

结论

输出投影可以被”吸收”到中,没有增加表达能力!

低秩瓶颈的数学证明

线性代数定理:

具体到Attention:

V的维度:n × d_k (比如 n × 64)
W_O的维度:d_k × d_model (比如 64 × 512)
V·W_O的维度:n × d_model (比如 n × 512)

但是:rank(V·W_O) ≤ min(n, d_k) ≤ 64

12岁版解释

就像你有一个2维的影子,无论怎么旋转,它还是2维的,无法变成3维。

后果:

  1. 表达能力受限:无法学习复杂的非线性关系
  2. 参数冗余:的作用有限
  3. 性能瓶颈:模型能力受限于

5.2 缺陷2:Attention Sink(注意力陷阱)

什么现象?

实验发现

在长文本生成时,第一个token的注意力权重异常高!

数据:

原始Attention:
- 第1个token的平均注意力权重:46.7%
- 其他token的平均权重:53.3% / (n-1)

例如n=100时:
- 第1个token:46.7%
- 其他99个token:平均每个0.54%

12岁版解释

就像你看一本书,总是盯着第一个字看,其他字都不怎么看。这显然不对!

为什么会这样?

理论解释:

  1. Softmax的特性:必须把权重分配给所有token
  2. 无关信息的处理:当某个位置不需要关注任何信息时,Softmax还是要分配权重
  3. 第一个token成为”垃圾桶”:模型学会把不需要的注意力都扔给第一个token

类比

就像你做选择题,必须选一个答案,即使所有选项都不对。你只能随便选一个(通常是A)。

为什么是第一个token()?

1. 总是可见:在因果注意力中,<BOS>对所有位置都可见
   - Token 0 (<BOS>): 可以看 [0]
   - Token 1: 可以看 [0, 1]
   - Token 2: 可以看 [0, 1, 2]
   - ...
   → <BOS>是唯一对所有位置都可见的token

2. 没有语义:<BOS>只是个标记,不包含实际信息
   → 把注意力扔给它不会影响其他有用信息

3. 训练中涌现:模型自己发现了这个"技巧"

有什么危害?

  1. 信息失真:第一个token的信息被过度放大
  2. 长文本不稳定:扩展到更长序列时,问题更严重
  3. 浪费计算:大量注意力集中在一个无关的token上

5.3 缺陷3:缺乏稀疏性

Softmax的特性

公式:

特性

所有权重都大于0!

例子

输入:[-100, -100, 10, -100]
Softmax:[0.0000...001, 0.0000...001, 0.9999..., 0.0000...001]

即使输入是-100(非常小),Softmax输出也不是0!

为什么无法真正”忽略”信息?

问题:

  • 即使某个token完全无关,Softmax也会给它一个小权重
  • 所有token的信息都会被混合进来
  • 无法真正”关闭”某个head或某个token

12岁版解释

就像你听音乐,即使某个乐器的声音很小,你还是能听到一点点。无法完全静音。

为什么需要稀疏性?

  1. 提高效率:不需要的head可以关闭,节省计算
  2. 增强可解释性:知道哪些head在工作,哪些没用
  3. 防止过拟合:减少冗余信息

第六部分:Gated Attention改进

超级简单的解决方案

在Attention输出后,乘以一个sigmoid门控!

6.1 门控机制的设计

核心思想

公式对比:

原始Attention:

门控Attention:

其中:

  • :门控参数(每个head一个标量)
  • :sigmoid门控分数,范围
  • :逐元素乘法

12岁版解释

就像给每个Attention head加一个开关,可以调节它的”音量”。

实现细节

每个head一个门控参数:

假设有8个head:
g = [g₁, g₂, g₃, g₄, g₅, g₆, g₇, g₈]

每个gᵢ是一个可学习的标量

Sigmoid的作用:

特性:

  • 输出范围:
  • 很大时:(门打开)
  • 很小时:(门关闭)
  • 时:(半开)

12岁版解释

就像一个音量旋钮,可以从0(静音)调到1(最大音量)。

门控分数的分布

实验发现

训练后的门控分数分布:
- 大部分head:0.7-0.9(基本打开)
- 少数head:0.1-0.3(基本关闭)
- 极少数head:0.4-0.6(半开)

解释:

  • 模型自动学会了哪些head有用(打开)
  • 哪些head没用(关闭)
  • 实现了自动稀疏化!

6.2 如何突破低秩瓶颈

非线性的力量

原始Attention(线性):

Linear → Linear = Linear

门控Attention(非线性):

Linear → NonLinear → Linear ≠ Linear

关键

sigmoid在中间插入了非线性!和无法合并了!

为什么非线性能增加秩?

线性操作不能增加秩:

非线性操作可以增加秩:

12岁版解释

就像做菜,如果只是把食材混合(线性),味道有限。但如果中间加热(非线性),就能产生新的味道!

验证Sigmoid是非线性:

例子:

  • ✓

实验验证

秩的测量:

原始Attention:
平均秩 = 127.3(接近128的瓶颈)

Gated Attention:
平均秩 = 156.8(突破了128的瓶颈!)

提升:156.8 / 127.3 = 1.23(提升23%)

性能提升:

原始Attention:PPL = 6.026
Gated Attention:PPL = 5.761

提升:(6.026 - 5.761) / 6.026 = 4.4%

6.3 如何消除Attention Sink

门控的解决方案

原始Attention的问题:

Softmax约束:Σ α_ij = 1(必须分配注意力)
↓
模型有时不想关注任何位置
↓
但必须分配注意力
↓
把不想要的注意力扔给第一个token(<BOS>)
↓
第一个token获得46.7%注意力

门控Attention的解决:

门控值可以接近0:sigmoid(g) → 0
↓
整个head的输出接近0
↓
模型可以"不关注"
↓
不需要把注意力扔给<BOS>了!

实验结果

F-Attr(First-token Attention Ratio):

模型              F-Attr
---------------------------
原始Attention     46.7%  ← 异常高!
门控Attention     4.8%   ← 正常!

改进:降低了90%!

计算方法:

发现

门控机制让第一个token的注意力从46.7%降到4.8%,几乎消除了Attention Sink!

6.4 如何实现稀疏性

Sigmoid可以接近0

Softmax的问题:

Softmax(x) = exp(x) / Σ exp(x)
→ 输出恒 > 0
→ 无法真正为0

Sigmoid的优势:

σ(x) = 1 / (1 + exp(-x))
→ 当x → -∞时,σ(x) → 0
→ 可以接近0!

实际例子:

σ(-10) = 0.000045  ≈ 0
σ(-20) = 0.0000000002  ≈ 0

软稀疏 vs 硬稀疏

硬稀疏:直接设为0

weights = [0.5, 0, 0.3, 0, 0.2]

软稀疏:接近0但不完全为0

weights = [0.92, 0.03, 0.88, 0.05, 0.02]

门控实现的是软稀疏:

  • 不需要的head:门控值≈0(如0.03)
  • 有用的head:门控值≈1(如0.92)
  • 可微分,可以训练

稀疏性的好处

  1. 计算效率:门控值<0.1的head可以跳过计算
  2. 可解释性:知道哪些head在工作
  3. 防止过拟合:减少冗余信息

6.5 实验结果详解

主要实验(15B MoE模型)

不同激活函数对比:

激活函数PPL相对改进
无(原始)6.026-
ReLU6.0050.3%
GELU5.9980.5%
Sigmoid5.7614.4% ✅

分析:

  • 任何非线性都有帮助
  • Sigmoid效果最好
  • 验证了非线性增益的重要性

门控位置对比

位置描述PPL改进
Baseline无门控6.026-
G₂Value层后5.8203.4%
G₁SDPA输出后5.7614.4% ✅
G₅最终输出后6.0170.1%

结论:G₁位置(SDPA输出后)效果最好!

长文本性能

扩展到128k tokens:

序列长度原始PPL门控PPL改进
4k10.209.754.4%
16k10.8510.126.7%
64k11.5210.489.0%
128k12.3110.8911.5%

发现

  • 序列越长,门控的优势越明显
  • 原始Attention在长序列上退化严重
  • 门控Attention更鲁棒

6.6 Sigmoid门控 vs 原始Attention

两层不同的机制

重要!Softmax和Sigmoid作用在不同的层面:

层面1:Token层面的注意力(Softmax)

# 在每个head内部,对token分配注意力
attention_weights = softmax(Q @ K.T / sqrt(d))
 
# 例如:处理"今天天气很好"
attention_weights = [0.1, 0.3, 0.4, 0.2]  # 对4个token的注意力
#                    今天  天气  很   好

Softmax的作用:

  • 决定当前位置应该关注哪些token
  • 权重和为1
  • 原始Attention有,Gated Attention也有

层面2:Head层面的门控(Sigmoid)

# 在多个head之间,控制哪些head工作
gate_values = [sigmoid(g_1), sigmoid(g_2), ..., sigmoid(g_8)]
 
# 例如:8个head的门控值
gate_values = [0.95, 0.03, 0.88, 0.05, 0.02, 0.91, 0.04, 0.06]
#              工作  关闭  工作  关闭  关闭  工作  关闭  关闭

Sigmoid的作用:

  • 决定哪些head应该工作
  • 每个head独立控制
  • 原始Attention没有,Gated Attention新增

乐队类比

想象一个乐队演奏

原始Attention(没有门控):

乐队有8个乐手:
- 钢琴手:演奏旋律A(用心分配注意力到每个音符)✅
- 小提琴手:演奏旋律B(用心分配注意力)✅
- 大提琴手:演奏旋律C(用心分配注意力)✅
- 鼓手:乱打(虽然也在"用心",但打的是噪声)❌
- 吉他手:乱弹(虽然也在"用心",但弹的是噪声)❌

问题:所有人都在演奏,包括那些制造噪声的乐手

Softmax的作用:每个乐手决定如何演奏自己的部分(注意力分配)

Gated Attention(有门控):

乐队有8个乐手 + 1个指挥(门控机制):
- 钢琴手:音量100%(σ(g₁)=0.95)✅
- 小提琴手:音量5%(σ(g₂)=0.05)← 几乎静音
- 大提琴手:音量90%(σ(g₃)=0.90)✅
- 鼓手:音量3%(σ(g₄)=0.03)← 几乎静音
- 吉他手:音量2%(σ(g₅)=0.02)← 几乎静音

改进:指挥控制每个乐手的音量,噪声被压制

Softmax的作用:每个乐手还是决定如何演奏(没变) Sigmoid的作用:指挥控制每个乐手的音量(新增)

6.7 为什么以前没人用门控?

你可能会问

Sigmoid函数很早就有了,为什么大家以前不在每个head后面加Sigmoid?

原因1:问题没被发现

Attention Sink问题是最近才被重视的

时间线:
2017年:Transformer提出,大家关注"Attention真好用!"
2018-2020年:BERT、GPT-2、GPT-3,大家忙着扩大模型
2021-2023年:开始有人注意到"第1个token获得异常高的注意力"
2024年:这篇论文系统性地解决这个问题

类比:

  • 就像你买了新手机,一开始只关注”好用”
  • 用了几年后才发现”电池不耐用”
  • 然后才有人研究”如何优化电池”

原因2:计算资源限制

以前的模型太小,问题不明显

时期模型规模Head数量问题严重性
2017-2018BERT-base12个head问题不明显
2019-2020GPT-212-48个head开始出现
2021-2023GPT-3/496-128个head问题严重
2024+大模型更多head必须解决

当head数量少时:

  • 即使有几个head是冗余的,影响不大
  • 计算成本还能接受

当head数量多时:

  • 冗余head浪费大量计算
  • 必须找方法优化

原因3:研究惯性

“Transformer已经很好了,为什么要改?“

研究者的思维:
2017-2020年:Transformer效果好,不要乱改
2021-2022年:开始有人尝试各种改进(但很多失败了)
2023-2024年:积累了足够的理解,找到了有效的改进方法

原因4:细节很重要

很多人可能试过类似的想法,但没成功

可能尝试过的失败方案:

  1. 直接在head输出上加sigmoid → 破坏了数值范围
  2. 用固定的门控值 → 没有学习能力
  3. 用其他激活函数(ReLU、Tanh)→ 效果不如sigmoid
  4. 门控参数初始化不当 → 训练不稳定

这篇论文的贡献:

  • 找到了正确的设计(独立的可学习参数)
  • 找到了正确的位置(SDPA输出后)
  • 找到了正确的初始化方法(从0开始)
  • 证明了确实有效(大规模实验验证)

第七部分:数学推导和实现细节

7.1 Attention的复杂度分析

时间复杂度

Self-Attention的计算步骤:

# 假设:序列长度n,embedding维度d
# 1. 计算Q, K, V
Q = X @ W_Q  # (n, d) @ (d, d_k) = (n, d_k)
K = X @ W_K  # (n, d) @ (d, d_k) = (n, d_k)
V = X @ W_V  # (n, d) @ (d, d_v) = (n, d_v)
 
# 2. 计算注意力分数
scores = Q @ K.T  # (n, d_k) @ (d_k, n) = (n, n)
 
# 3. Softmax
attention = Softmax(scores)  # (n, n)
 
# 4. 加权求和
output = attention @ V  # (n, n) @ (n, d_v) = (n, d_v)

复杂度分析:

步骤操作复杂度
计算Q, K, V3次矩阵乘法
计算scores
Softmax对每行归一化
加权求和attention·V

总时间复杂度:

关键观察:

  • 当时:主导(短序列)
  • 当时:主导(长序列)

长序列的问题

对于长序列(如n=10000),项会变得非常大!

例如:

  • n=1000, d=512: 操作
  • n=10000, d=512: 操作(增加100倍!)

空间复杂度

需要存储的矩阵:

矩阵形状空间
Q, K, V(n, d_k) × 3
scores(n, n)
attention(n, n)
output(n, d_v)

总空间复杂度:

关键问题:的注意力矩阵!

具体例子

序列长度n=1024,d=512:
- Q, K, V: 3 × 1024 × 64 = 196K参数
- attention矩阵: 1024 × 1024 = 1M参数

序列长度n=10240,d=512:
- Q, K, V: 3 × 10240 × 64 = 1.96M参数
- attention矩阵: 10240 × 10240 = 105M参数(增加100倍!)

7.2 为什么Transformer比RNN快?

RNN的计算方式

# RNN必须顺序计算
h_0 = initial_state
h_1 = RNN(x_1, h_0)  # 依赖h_0
h_2 = RNN(x_2, h_1)  # 依赖h_1
h_3 = RNN(x_3, h_2)  # 依赖h_2
...
h_n = RNN(x_n, h_{n-1})  # 依赖h_{n-1}

关键问题:

  • ❌ 必须等待前一步完成
  • ❌ 无法并行
  • ❌ 时间复杂度:步,每步

Transformer的计算方式

# Transformer可以并行计算
Q = X @ W_Q  # 所有位置同时计算
K = X @ W_K  # 所有位置同时计算
V = X @ W_V  # 所有位置同时计算
 
scores = Q @ K.T  # 一次矩阵乘法
attention = Softmax(scores)  # 并行
output = attention @ V  # 一次矩阵乘法

关键优势:

  • ✅ 所有位置同时计算
  • ✅ 完全并行
  • ✅ 时间复杂度:步(在GPU上)

并行度对比

模型顺序操作数并行度GPU利用率
RNN低低(10-30%)
Transformer高高(80-95%)

实际速度对比

在相同硬件上:

  • RNN处理1000个token:约1秒
  • Transformer处理1000个token:约0.1秒

Transformer快10倍!

为什么RNN不能并行?

依赖关系:

RNN的计算图:
x_1 → h_1 → h_2 → h_3 → ... → h_n
      ↓     ↓     ↓           ↓
      y_1   y_2   y_3         y_n

每个h_i都依赖h_{i-1},必须顺序计算
Transformer的计算图:
x_1 ↘
x_2 → Attention → y_1, y_2, y_3, ..., y_n
x_3 ↗

所有x_i同时输入,所有y_i同时输出

7.3 Embedding如何学习?

你可能会问

Embedding矩阵是随机初始化的,它是如何学到有用的表示的?

初始状态(训练前)

创建Embedding矩阵:

import torch.nn as nn
 
# 假设:词表大小50000,embedding维度512
vocab_size = 50000
d_model = 512
 
# 创建Embedding层(可学习参数)
embedding = nn.Embedding(vocab_size, d_model)
 
# 初始化:随机,没有意义
print(embedding.weight[1234])  # "今天"的embedding
# tensor([0.023, -0.015, 0.031, ..., -0.008])  # 512个随机数

初始状态的特点:

  • ❌ 完全随机,没有任何意义
  • ❌ 相似的词没有相似的向量
  • ❌ 无法表达语义关系

训练过程(如何学习)

完整训练循环:

# 1. 前向传播
token_ids = [1234, 5678, 9012]  # "今天 天气 很好"
embeddings = embedding(token_ids)  # 查表
 
X = embeddings + positional_encoding
output = transformer(X)
logits = output_projection(output)
 
# 2. 计算损失
target_ids = [5678, 9012, 1]  # "天气 很好 <EOS>"
loss = CrossEntropyLoss(logits, target_ids)
 
# 3. 反向传播(计算梯度)
loss.backward()
 
# 关键:只有被使用的token有梯度!
print(embedding.weight.grad[1234])  # "今天":有梯度 ✓
# tensor([0.0012, -0.0034, 0.0056, ..., -0.0023])
 
print(embedding.weight.grad[9999])  # 未使用的词:无梯度 ✗
# tensor([0., 0., 0., ..., 0.])
 
# 4. 梯度下降(更新参数)
optimizer.step()
 
# 更新规则
embedding.weight[1234] = embedding.weight[1234] - lr × grad[1234]

为什么只有被使用的token有梯度?

Loss → Output → Transformer → Embedding → Token IDs

只有被查表的token,才会有梯度传回来!

为什么Embedding会学到语义?

原因1:共现关系(Co-occurrence)

# 训练数据中经常出现:
"今天天气很好"
"今天天气不错"
"今天天气真棒"
 
# 模型学到:
# - "今天"和"天气"经常一起出现
# - 它们的embedding应该相似
# - 这样预测下一个词时更准确

数学上:

如果"今天"和"天气"经常一起出现
→ 它们的embedding相似时,loss更小
→ 梯度下降会让它们的embedding变得相似

原因2:上下文相似(Distributional Hypothesis)

语言学原理

“You shall know a word by the company it keeps” (通过一个词的伙伴,你就能了解这个词)

# 训练数据:
"今天天气很好"
"明天天气很好"
 
# 模型学到:
# - "今天"和"明天"出现在相似的上下文中
# - 它们的embedding应该相似

原因3:任务驱动(Task-driven)

# 语言模型任务:预测下一个词
# 输入:"今天天气很___"
# 目标:预测"好"
 
# 如果embedding学到了语义:
# - "今天"的embedding包含"时间"信息
# - "天气"的embedding包含"气候"信息
# - 模型更容易预测出"好"
 
# 如果embedding是随机的:
# - 模型无法理解"今天"和"天气"的关系
# - 预测会很差

训练过程的变化

初始状态(Epoch 0):

# Token 1234("今天")的embedding
embedding.weight[1234]
# tensor([0.023, -0.015, 0.031, ..., -0.008])  # 随机
 
# Token 5678("天气")的embedding
embedding.weight[5678]
# tensor([0.012, 0.045, -0.023, ..., 0.019])  # 随机
 
# 相似度(余弦相似度)
similarity = cosine_similarity(
    embedding.weight[1234],
    embedding.weight[5678]
)
print(similarity)  # 0.03(几乎不相关)

训练后(Epoch 10000):

# Token 1234("今天")的embedding
embedding.weight[1234]
# tensor([0.456, 0.789, -0.234, ..., 0.678])  # 学到了有用的表示
 
# Token 5678("天气")的embedding
embedding.weight[5678]
# tensor([0.423, 0.812, -0.201, ..., 0.645])  # 学到了有用的表示
 
# 相似度
similarity = cosine_similarity(
    embedding.weight[1234],
    embedding.weight[5678]
)
print(similarity)  # 0.87(高度相关!)
 
# 因为"今天"和"天气"经常一起出现,模型学到了它们的关系

可视化:Embedding的学习过程

假设embedding只有2维(实际是512维):

初始状态(随机):

      y
      |
  香蕉 •
      |
      |    • 苹果
      |
------+------• 橙子---- x
      |
      |

没有任何规律,完全随机。

训练后(Epoch 10000):

      y
      |
      |    •• 苹果橙子(重叠)
      |
------+-------------- x
      |
      |
  香蕉 •
      |

明显的聚类:相似的词embedding相似。

和Word2Vec的对比

维度Word2VecTransformer Embedding
训练方式独立预训练端到端训练
训练任务预测上下文词预测下一个token
更新时机只在预训练时整个训练过程中
训练后固定,不再更新固定,不再更新
推理时固定固定
上下文感知静态(无上下文)动态(通过Attention)

关键区别:

# Word2Vec(预训练,固定)
word2vec = load_pretrained("word2vec.bin")
embedding = word2vec["今天"]  # 固定的向量
 
# Transformer Embedding(端到端训练,可更新)
embedding_matrix = nn.Embedding(vocab_size, d_model)  # 可学习参数
embedding = embedding_matrix[token_id]  # 训练过程中会更新

7.4 Transformer的局限性

局限1:二次复杂度

问题:

  • 时间复杂度:
  • 空间复杂度:
  • 长序列时计算量和内存消耗爆炸

具体例子:

序列长度n=1024:
- 注意力矩阵:1M参数
- 可以处理

序列长度n=10240:
- 注意力矩阵:105M参数
- 内存不够!

改进方法:

  • Sparse Attention(稀疏注意力)
  • Linear Attention(线性注意力)
  • Flash Attention(优化内存访问)

局限2:无位置信息

问题:

  • Attention本身不知道词的顺序
  • 必须手动添加位置编码

例子:

# 对于Attention来说,这两个句子是一样的:
"今天天气很好"
"很好天气今天"

改进方法:

  • 相对位置编码(Relative Positional Encoding)
  • 旋转位置编码(RoPE)
  • ALiBi(Attention with Linear Biases)

局限3:长距离依赖衰减

问题:

  • 虽然理论上可以关注任意距离
  • 但实际上远距离的注意力权重会很小

原因:

# Softmax的特性
scores = [10, 5, 1, 0.1, 0.01]  # 距离越远,分数越小
attention = Softmax(scores)
# [0.88, 0.11, 0.01, 0.00, 0.00]  # 远距离权重接近0

改进方法:

  • 分段注意力(Segmented Attention)
  • 记忆机制(Memory Mechanism)

局限4:训练不稳定

问题:

  • 深层Transformer训练困难
  • 容易梯度消失或梯度爆炸

改进方法:

  • Pre-Norm(Layer Norm放在前面)
  • 更好的初始化方法
  • 梯度裁剪(Gradient Clipping)

局限5:缺乏归纳偏置

问题:

  • Transformer没有内置的结构假设
  • 需要大量数据才能学到模式

对比:

模型归纳偏置数据需求
CNN局部性、平移不变性中等
RNN顺序性、时间依赖中等
Transformer无大量

改进方法:

  • 加入卷积层(Conformer)
  • 加入循环结构(Universal Transformer)

🧪 自我检验:25道思考题

基础题(检验概念理解)

  1. Attention机制的核心思想是什么?用自己的话解释。

  2. Query、Key、Value分别是什么?它们有什么区别?

  3. 为什么要除以?如果不除会怎样?

  4. Softmax的作用是什么?为什么不用简单的归一化?

  5. Self-Attention的输出是什么?它和输入有什么关系?

  6. 为什么需要多头注意力?一个head不够吗?

  7. 位置编码为什么重要?如果没有位置编码会怎样?

中等题(检验原理理解)

  1. Encoder和Decoder有什么区别?为什么Decoder需要Mask?

  2. 残差连接解决了什么问题?

  3. Feed-Forward Network的作用是什么?为什么Attention之后还需要FFN?

  4. 为什么Transformer可以并行,而RNN不行?

  5. 原始Attention有哪三大缺陷?分别是什么?

  6. 什么是低秩瓶颈?为什么会限制模型的表达能力?

  7. 什么是Attention Sink?为什么第一个token会获得异常高的注意力?

  8. 门控机制是如何工作的?用自己的话解释。

  9. 为什么用Sigmoid而不是ReLU作为门控函数?

进阶题(检验应用理解)

  1. 手算一个简单的Attention例子(2个词,2维向量)。

  2. 证明:两个线性变换的组合还是线性变换。

  3. 计算Self-Attention的时间复杂度和空间复杂度。

  4. 门控机制如何突破低秩瓶颈?非线性起了什么作用?

  5. 门控机制如何消除Attention Sink?实验结果是什么?

  6. Sigmoid门控和Softmax注意力有什么区别?它们作用在哪个层面?

  7. 为什么以前没人用门控机制?列举至少3个原因。

  8. Gated Attention的实验结果如何?在长文本上的表现怎么样?

  9. Transformer的主要局限性是什么?如何改进?


📚 参考答案

基础题答案

  1. Attention机制的核心思想 Attention让模型能”同时看到”所有位置的信息,并计算每个位置对其他位置的关注程度。就像查字典:用Query(要查的字)和Key(字典索引)比较相似度,然后提取Value(字典解释)。

  2. Query、Key、Value的区别

    • Query:当前位置想知道”我应该关注哪些位置”
    • Key:所有位置用来和Query比较相似度
    • Value:所有位置的实际内容,用于提取信息 虽然都来自同样的输入,但通过不同的线性变换得到,数学上是不同的。
  3. 为什么要除以√d 防止点积结果过大。当维度d很大时,Q·K

  4. Softmax的作用 把任意实数转换成概率分布(和为1,都是正数)。简单归一化(除以总和)无法处理负数,也无法突出重要的值。Softmax通过exp函数放大差异,让重要的值更突出。

  5. Self-Attention的输出 输出是输入的加权组合。每个位置的输出是所有位置Value的加权和,权重由Query和Key的相似度决定。输出维度和输入相同。

  6. 为什么需要多头注意力 单个head只能学习一种关系模式。多头可以让不同head学习不同的语言现象:有的学语法关系(主谓、动宾),有的学语义关系(同义、反义),有的学位置关系。就像从多个角度看同一本书。

  7. 位置编码的重要性 Attention本身不知道词的顺序,“今天天气很好”和”很好天气今天”对它来说是一样的。位置编码告诉模型每个词在句子中的位置,让模型能理解顺序关系。

中等题答案

  1. Encoder和Decoder的区别

    • Encoder:双向Attention,可以看到所有位置,用于理解输入
    • Decoder:单向Attention(带Mask),只能看到当前位置之前的内容,用于生成输出 Decoder需要Mask是因为生成时不能”偷看”未来的词。
  2. 残差连接的作用 解决深层网络的梯度消失问题。通过”输出=F(x)+x”,即使F(x)学不好,至少还有x的直接通路,梯度可以直接传回去。就像走楼梯和坐电梯的双重选择。

  3. Feed-Forward Network的作用 Attention是线性的(矩阵乘法),FFN引入非线性(ReLU激活函数),增强模型的表达能力。FFN对每个位置独立处理,而Attention关注位置之间的关系,两者互补。

  4. Transformer可以并行的原因 Transformer的所有位置可以同时计算Q、K、V,然后一次矩阵乘法得到所有位置的输出。RNN必须顺序计算,因为h_i依赖h_{i-1},无法并行。在GPU上,Transformer比RNN快10倍以上。

  5. 原始Attention的三大缺陷

    • 低秩瓶颈:输出投影W_O被”吸收”到V中,表达能力受限于d_k(如64维)
    • Attention Sink:第一个token获得异常高的注意力(46.7%),成为”垃圾桶”
    • 注意力熵崩塌:长文本生成时,注意力分布变得极度集中,失去多样性
  6. 低秩瓶颈的含义 秩表示独立方向的数量。原始Attention中,z=A·V和o=z·W_O都是线性操作,组合后还是线性,W_O被”吸收”到V中。导致输出的秩≤min(n, d_k),通常受限于d_k(如64),无法充分利用d_model(如512)的表达能力。

  7. Attention Sink的原因 Softmax必须把权重分配给所有token。当某个位置不需要关注任何信息时,模型学会把不需要的注意力都扔给第一个token(),因为它对所有位置都可见,且没有实际语义,不会影响其他有用信息。

  8. 门控机制的工作原理 在每个Attention head的输出后,乘以一个Sigmoid门控值σ(g)。当σ(g)接近1时,head正常工作;当σ(g)接近0时,head被抑制。模型自动学习哪些head应该工作,哪些应该关闭。

  9. 为什么用Sigmoid而不是ReLU

    • Sigmoid输出在[0,1]之间,可以解释为”开关程度”
    • Sigmoid可以接近0但不会完全为0,实现软稀疏
    • ReLU输出在[0,∞),无界,不适合做门控
    • ReLU在负数时完全为0,太硬,梯度消失

进阶题答案

  1. 手算Attention例子 假设2个词,2维向量:

    Q = [[1, 0], [0, 1]]
    K = [[1, 0], [0, 1]]
    V = [[2, 3], [4, 5]]
    
    步骤1:Q·K^T = [[1, 0], [0, 1]](相似度矩阵)
    步骤2:除以√2 = [[0.71, 0], [0, 0.71]]
    步骤3:Softmax = [[1, 0], [0, 1]](对角矩阵)
    步骤4:乘以V = [[2, 3], [4, 5]](输出)
    
  2. 证明线性组合还是线性 设f(x)=Ax,g(x)=Bx都是线性变换。 组合:h(x)=g(f(x))=B(Ax)=(BA)x=Cx,其中C=BA h(x)=Cx还是线性变换。所以两个线性变换的组合还是线性。

  3. Self-Attention的复杂度

    • 时间复杂度:O(n²·d),其中n是序列长度,d是维度
      • Q·K
      • Softmax:O(n²)
      • 乘以V:O(n²·d)
    • 空间复杂度:O(n²),存储注意力矩阵
  4. 门控如何突破低秩瓶颈 Sigmoid是非线性函数,σ(2x)≠2·σ(x)。非线性操作可以增加秩,打破线性瓶颈。实验显示,门控后平均秩从127提升到157(提升23%),突破了d_k=128的限制。

  5. 门控如何消除Attention Sink 门控值可以接近0,让整个head的输出接近0,模型可以”不关注”任何位置,不需要把注意力扔给了。实验结果:第一个token的注意力从46.7%降到4.8%,降低了90%。

  6. Sigmoid门控和Softmax注意力的区别

    • Softmax:作用在token层面,决定”关注哪些token”,输出和为1
    • Sigmoid:作用在head层面,决定”哪些head工作”,每个head独立控制 就像乐队:Softmax决定每个乐手如何演奏,Sigmoid决定每个乐手的音量。
  7. 为什么以前没人用门控

    • 问题没被发现:Attention Sink是最近才被重视的(2021-2023年)
    • 计算资源限制:以前模型小,head少,问题不明显
    • 研究惯性:“Transformer已经很好了,为什么要改?”
    • 需要积累理解:需要足够的实验和理论才能找到有效的改进方法
  8. Gated Attention的实验结果

    • PPL提升4.4%(6.026→5.761)
    • 长文本(128k)性能提升11.5%
    • 第一个token注意力降低90%(46.7%→4.8%)
    • 平均秩提升23%(127→157)
    • 参数增加<2M(每个head只增加1个标量)
  9. Transformer的主要局限性

    • 二次复杂度:O(n²),长序列时计算量爆炸
    • 无位置信息:必须手动添加位置编码
    • 长距离依赖衰减:远距离的注意力权重很小
    • 训练不稳定:深层网络容易梯度消失/爆炸
    • 缺乏归纳偏置:需要大量数据才能学到模式 改进方法:Sparse Attention、Flash Attention、RoPE、Pre-Norm等。

📚 核心要点总结

一句话总结

Transformer通过Self-Attention机制,让每个词同时关注所有其他词,找出它们之间的关系。Gated Attention通过引入门控机制,突破了原始Attention的低秩瓶颈、Attention Sink和稀疏性问题。

关键概念

基础Transformer:

  1. Self-Attention:找相似度,Softmax归一化,加权求和
  2. Multi-Head:多个视角,学习多种关系
  3. Encoder-Decoder:理解输入,生成输出
  4. 位置编码:告诉模型词的顺序
  5. 残差连接:缓解梯度消失,保留原始信息

Gated Attention改进:

  1. 突破低秩瓶颈:Sigmoid引入非线性,打破秩限制(127→157,提升23%)
  2. 消除Attention Sink:门控抑制第一个token的异常注意力(46.7%→4.8%)
  3. 实现稀疏性:Sigmoid可以接近0,实现软稀疏,提高效率
  4. 参数极少:每个head只增加1个标量参数,总共<2M参数
  5. 效果显著:PPL提升4.4%,长文本(128k)性能提升11.5%

核心公式

原始Attention:

Multi-Head Attention:

Gated Attention:

其中:

  • :Sigmoid门控函数
  • :逐元素相乘(Hadamard积)
  • :可学习的门控参数

Transformer的优势

  1. 并行计算:所有位置同时处理,比RNN快10倍
  2. 长距离依赖:直接建模任意两个词的关系
  3. 灵活性:可以处理各种序列任务
  4. 可扩展性:容易扩展到大规模模型

Transformer的局限

原始Attention的三大缺陷:

  1. 低秩瓶颈:线性变换限制表达能力,秩≤
  2. Attention Sink:第一个token获得46.7%的异常注意力
  3. 缺乏稀疏性:Softmax无法产生真正的0,所有head都在工作

其他局限:

  1. 二次复杂度:,长序列时计算量大
  2. 内存消耗大:注意力矩阵,长序列时内存不够
  3. 需要位置编码:本身没有位置信息
  4. 训练不稳定:深层网络容易梯度消失

Gated Attention如何解决

缺陷原始AttentionGated Attention改进效果
低秩瓶颈秩≤128秩可以>128秩提升23%
Attention Sink46.7%注意力在第1个token4.8%注意力在第1个token降低90%
缺乏稀疏性所有权重>0门控值可接近0实现软稀疏
参数开销-每个head +1参数<2M参数
性能提升PPL 6.026PPL 5.761提升4.4%
长文本性能PPL 12.31 (128k)PPL 10.89 (128k)提升11.5%

为什么Gated Attention有效?

核心机制:

原始Attention(线性):
Linear(V) → Linear(W_O) = Linear
→ 秩受限,无法突破

Gated Attention(非线性):
Linear(V) → Sigmoid(g) ⊙ → Linear(W_O) ≠ Linear
→ 非线性打破秩限制

两层机制:

  1. Token层面(Softmax):决定关注哪些token
  2. Head层面(Sigmoid):决定哪些head工作

类比:

  • Softmax:每个乐手决定如何演奏(注意力分配)
  • Sigmoid:指挥控制每个乐手的音量(门控)

🎓 下一步学习

现在你已经理解了Transformer的基础和Gated Attention的改进,可以:

  1. 阅读原论文:

    • “Attention is All You Need” (Vaswani et al., 2017) - Transformer原论文
    • “Gated Attention” (Qwen团队, 2024) - 门控注意力改进
  2. 深入理解:

    • 实现一个简单的Transformer
    • 实现Gated Attention机制
    • 在小数据集上训练和测试
  3. 探索变体:

    • BERT:双向Encoder
    • GPT:单向Decoder
    • T5:Encoder-Decoder统一框架
    • Llama:现代大语言模型架构
  4. 优化技术:

    • Flash Attention:优化内存访问
    • Sparse Attention:降低复杂度
    • Linear Attention:线性复杂度
    • RoPE:旋转位置编码
  5. 用费曼学习法讲给我听:检验你的理解

    • 用自己的话解释Self-Attention
    • 解释为什么需要多头注意力
    • 解释Gated Attention如何改进原始Attention

准备好了吗?

你现在已经掌握了:

  • ✅ Transformer的完整架构
  • ✅ Self-Attention的数学原理
  • ✅ 从文本到向量的完整流程
  • ✅ Embedding的学习机制
  • ✅ 原始Attention的三大缺陷
  • ✅ Gated Attention的改进方法
  • ✅ 实验结果和性能提升

这是理解现代大语言模型的坚实基础!


📎 附录A:Gated Attention深度答疑

本附录回答常见问题

这些问题来自学习过程中的常见疑惑,帮助你更深入理解Gated Attention。

A.1 PPL是什么?

PPL = Perplexity(困惑度)

12岁版解释

想象你在玩猜词游戏:

  • 我说”今天天气很___“,你猜”好”
  • 如果你猜对了,说明你对语言的理解很好(困惑度低)
  • 如果你完全猜不到,说明你很困惑(困惑度高)

PPL就是衡量模型”有多困惑”的指标。

技术定义:

通俗解释:

  • :给定前面的词,预测第i个词的概率
  • 如果模型总是能准确预测下一个词(概率接近1),PPL就低
  • 如果模型经常预测错(概率很小),PPL就高

实际例子:

模型PPL含义
GPT-320平均每次在20个词中选择
差模型100平均每次在100个词中选择
完美模型1每次都能准确预测

为什么叫"困惑度"

PPL=20意味着模型平均在20个词之间”困惑”,不知道选哪个。 PPL越低,模型越”不困惑”,预测越准确。

重要:PPL越低越好!

A.2 46.7%的数字是如何得出的?

来源:论文的实验统计

实验方法:

  1. 选择数据集:在大规模语料库上训练模型
  2. 统计注意力权重:
    • 对每个样本,记录第1个token获得的注意力权重
    • 对所有样本求平均
  3. 得出结论:第1个token平均获得46.7%的注意力

如何复现这个数字?

# 伪代码
total_first_token_attention = 0
num_samples = 0
 
for batch in dataset:
    attention_weights = model.get_attention(batch)  # shape: [batch, heads, seq_len, seq_len]
 
    # 提取第1个token获得的注意力(所有位置对第1个token的注意力)
    first_token_attn = attention_weights[:, :, :, 0].mean()  # 对所有head和位置求平均
 
    total_first_token_attention += first_token_attn
    num_samples += 1
 
average = total_first_token_attention / num_samples
print(f"第1个token平均注意力: {average:.1%}")  # 输出: 46.7%

A.3 为什么第一个token成为”垃圾桶”?

核心原因:Softmax的归一化约束

问题的本质:

Softmax要求所有注意力权重加起来必须等于1:

但有时候,模型不想关注任何位置!

类比

想象你在考试:

  • 题目:“下列哪个是水果?A.石头 B.铁块 C.木头 D.塑料”
  • 你的想法:“都不是啊!但我必须选一个…”
  • 你随便选了A(虽然A完全不对)

模型也是这样:

  • 当前token不需要关注任何其他token
  • 但Softmax强制要求”必须把注意力分配给某个位置”
  • 模型学会了:把不需要的注意力都扔给第1个token

为什么是第1个token,而不是其他位置?

三个原因:

  1. 第1个token总是可见

    • 因果注意力(causal attention)中,每个位置只能看到自己和之前的token
    • 第1个token对所有位置都可见
    • 其他位置可能看不到(比如第100个token看不到第101个)
  2. 第1个token通常是特殊token

    • 很多模型用<BOS>(Begin of Sequence)作为第1个token
    • 这个token本身没有实际语义
    • 模型学会了:反正它没用,就把垃圾注意力扔给它
  3. 训练中的自发涌现

    • 模型在训练中自己发现了这个”技巧”
    • 一旦开始这样做,就会强化这个模式
    • 最终形成稳定的”垃圾桶”行为

A.4 稀疏性与过拟合的关系

你的理解是对的

过拟合的常见原因:

  • ✅ 训练轮次太多
  • ✅ 参数量过大(相对于数据量)
  • ✅ 模型过于复杂

稀疏性如何防止过拟合?

关键:减少”有效参数量”

类比:学生做题

场景A:没有稀疏性(过拟合风险高)

  • 学生有100个解题技巧
  • 每道题都用上所有100个技巧
  • 结果:把训练题的细节都记住了(包括噪声)
  • 测试时:遇到新题就懵了

场景B:有稀疏性(过拟合风险低)

  • 学生有100个解题技巧
  • 每道题只用最相关的5个技巧
  • 结果:学到了核心规律,忽略了噪声
  • 测试时:能灵活应对新题

技术解释:

原始多头注意力:

  • 8个head全部工作
  • 即使某些head学到的是噪声,也会影响输出
  • 模型容易记住训练数据的特殊模式

Gated Attention(稀疏):

  • 门控机制可以关闭不重要的head
  • 只有真正有用的head参与计算
  • 减少了模型”记忆噪声”的能力

数学角度:

假设模型有H个head:

  • 无稀疏性:有效参数 = H × (参数/head)
  • 有稀疏性:有效参数 = k × (参数/head),其中k < H

有效参数减少 → 模型复杂度降低 → 过拟合风险降低

A.5 增强可解释性是什么意思?

可解释性 = 理解模型为什么做出某个决策

12岁版解释:老师批改作文

不可解释的情况:

  • 你问老师:“为什么我的作文得了80分?”
  • 老师说:“因为综合考虑了很多因素…”
  • 你还是不知道具体哪里好、哪里不好

可解释的情况:

  • 老师说:“你的开头很好(+20分),结构清晰(+30分),但结尾太仓促(-10分),有3个错别字(-20分)”
  • 你清楚地知道每个部分的贡献

在Gated Attention中的体现:

原始多头注意力(不可解释):

# 8个head都在工作
head_outputs = [h1, h2, h3, h4, h5, h6, h7, h8]
final_output = concat(head_outputs) @ W_O
 
# 问题:你不知道哪个head真正有用

Gated Attention(可解释):

# 门控值
g = [0.9, 0.05, 0.8, 0.02, 0.1, 0.85, 0.03, 0.06]
#    ↑    ↓     ↑    ↓     ↓    ↑     ↓     ↓
#   工作  关闭  工作  关闭  关闭  工作  关闭  关闭
 
# 只有3个head真正工作
active_heads = [h1, h3, h6]  # g > 0.5

分析容易:

  • 清楚知道head 1, 3, 6在工作
  • 可以单独分析这3个head在关注什么
  • 理解模型为什么做出这个预测

📎 附录B:Gated Attention流程图解

本附录提供完整的流程图

用图示彻底说清楚:原始Attention vs Gated Attention 的完整流程。

B.1 原始Multi-Head Attention完整流程

输入:X (batch_size, seq_len, d_model)
例如:X = "今天天气很好" 的embedding
     shape: (1, 4, 512)

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
第1步:对每个head计算Q、K、V
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Head 1:
  X ──×W_Q1──> Q1 (1, 4, 64)
  X ──×W_K1──> K1 (1, 4, 64)
  X ──×W_V1──> V1 (1, 4, 64)

Head 2:
  X ──×W_Q2──> Q2 (1, 4, 64)
  X ──×W_K2──> K2 (1, 4, 64)
  X ──×W_V2──> V2 (1, 4, 64)

... (共8个head)

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
第2步:对每个head计算注意力
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Head 1:
  scores1 = Q1 @ K1^T / sqrt(64)
  attn_weights1 = softmax(scores1)  # (1, 4, 4)
  head_output1 = attn_weights1 @ V1  # (1, 4, 64)

Head 2:
  scores2 = Q2 @ K2^T / sqrt(64)
  attn_weights2 = softmax(scores2)
  head_output2 = attn_weights2 @ V2  # (1, 4, 64)

... (共8个head)

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
第3步:拼接所有head
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

concat_output = concat([head_output1, head_output2, ..., head_output8])
# shape: (1, 4, 512)  ← 8个head × 64维 = 512维

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
第4步:输出投影
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

output = concat_output @ W_O  # (1, 4, 512)

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
结束:输出
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

关键点:

  • ✅ 所有8个head都参与计算
  • ✅ 每个head的输出直接拼接
  • ❌ 没有门控机制

B.2 Gated Attention完整流程

输入:X (batch_size, seq_len, d_model)
例如:X = "今天天气很好" 的embedding
     shape: (1, 4, 512)

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
第1步:对每个head计算Q、K、V(和原来一样)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Head 1:
  X ──×W_Q1──> Q1 (1, 4, 64)
  X ──×W_K1──> K1 (1, 4, 64)
  X ──×W_V1──> V1 (1, 4, 64)

Head 2:
  X ──×W_Q2──> Q2 (1, 4, 64)
  X ──×W_K2──> K2 (1, 4, 64)
  X ──×W_V2──> V2 (1, 4, 64)

... (共8个head)

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
第2步:对每个head计算注意力(和原来一样)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Head 1:
  scores1 = Q1 @ K1^T / sqrt(64)
  attn_weights1 = softmax(scores1)
  head_output1 = attn_weights1 @ V1  # (1, 4, 64)

Head 2:
  scores2 = Q2 @ K2^T / sqrt(64)
  attn_weights2 = softmax(scores2)
  head_output2 = attn_weights2 @ V2  # (1, 4, 64)

... (共8个head)

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
第3步:【新增】应用门控机制
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

门控参数(可学习的标量):
g = [g1, g2, g3, g4, g5, g6, g7, g8]
例如训练后的值:
g = [4.5, -3.2, 5.1, -2.8, -3.5, 4.8, -2.9, -3.1]

对每个head应用门控:

Head 1:
  gate1 = sigmoid(g1) = sigmoid(4.5) = 0.989
  gated_output1 = gate1 × head_output1
                = 0.989 × head_output1  # (1, 4, 64)

Head 2:
  gate2 = sigmoid(g2) = sigmoid(-3.2) = 0.039
  gated_output2 = gate2 × head_output2
                = 0.039 × head_output2  # (1, 4, 64)

Head 3:
  gate3 = sigmoid(g3) = sigmoid(5.1) = 0.994
  gated_output3 = gate3 × head_output3
                = 0.994 × head_output3  # (1, 4, 64)

... (共8个head)

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
第4步:拼接所有gated head
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

concat_output = concat([gated_output1, gated_output2, ..., gated_output8])
# shape: (1, 4, 512)

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
第5步:输出投影
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

output = concat_output @ W_O  # (1, 4, 512)

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
结束:输出
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

关键点:

  • ✅ 前两步和原始Attention完全一样
  • ✅ 第3步新增门控:每个head乘以sigmoid(g_i)
  • ✅ gate值接近1的head贡献大,接近0的head贡献小

B.3 对比图:关键区别在哪里?

原始Attention:
┌─────────────┐
│  Head 1     │──> head_output1 (1, 4, 64) ──┐
└─────────────┘                               │
┌─────────────┐                               │
│  Head 2     │──> head_output2 (1, 4, 64) ──┤
└─────────────┘                               │
┌─────────────┐                               ├──> Concat ──> W_O ──> Output
│  Head 3     │──> head_output3 (1, 4, 64) ──┤
└─────────────┘                               │
      ...                                     │
┌─────────────┐                               │
│  Head 8     │──> head_output8 (1, 4, 64) ──┘
└─────────────┘

所有head等权重拼接


Gated Attention:
┌─────────────┐                    g1=4.5
│  Head 1     │──> head_output1 ──×σ(g1)=0.989──┐
└─────────────┘                                  │
┌─────────────┐                    g2=-3.2       │
│  Head 2     │──> head_output2 ──×σ(g2)=0.039──┤
└─────────────┘                                  │
┌─────────────┐                    g3=5.1        ├──> Concat ──> W_O ──> Output
│  Head 3     │──> head_output3 ──×σ(g3)=0.994──┤
└─────────────┘                                  │
      ...                                        │
┌─────────────┐                    g8=-3.1       │
│  Head 8     │──> head_output8 ──×σ(g8)=0.043──┘
└─────────────┘

每个head有独立的门控权重

B.4 门控参数如何学习?

初始化(训练开始):

# 所有门控参数初始化为0
g = [0, 0, 0, 0, 0, 0, 0, 0]
 
# sigmoid(0) = 0.5
gate_values = [0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5]
 
# 所有head初始贡献50%

训练过程(梯度下降):

第1轮训练:
  前向传播:
    - 计算所有head的输出
    - 应用门控:gated_output_i = sigmoid(g_i) × head_output_i
    - 拼接、投影、得到最终输出
    - 计算损失:loss = CrossEntropy(output, target)

  反向传播:
    - 计算损失对g的梯度:∂loss/∂g_i
    - 如果head_i有用:梯度为正,g_i增加
    - 如果head_i无用:梯度为负,g_i减少

  更新参数:
    g_i = g_i - learning_rate × ∂loss/∂g_i

第2轮训练:
  g的值开始分化:
  g = [0.2, -0.1, 0.3, -0.15, -0.2, 0.25, -0.18, -0.12]
  gate_values = [0.55, 0.48, 0.57, 0.46, 0.45, 0.56, 0.45, 0.47]

... 继续训练 ...

第1000轮训练:
  g的值明显分化:
  g = [4.5, -3.2, 5.1, -2.8, -3.5, 4.8, -2.9, -3.1]
  gate_values = [0.989, 0.039, 0.994, 0.057, 0.029, 0.992, 0.052, 0.043]

  结果:Head 1, 3, 6有用(gate≈1),其他head无用(gate≈0)

相关笔记


📖 参考文献

  1. Vaswani, A., et al. (2017). “Attention is All You Need”. NeurIPS.
  2. Qwen Team (2024). “Gated Attention: Improving Transformer Efficiency”.
  3. Devlin, J., et al. (2018). “BERT: Pre-training of Deep Bidirectional Transformers”. NAACL.
  4. Radford, A., et al. (2018). “Improving Language Understanding by Generative Pre-Training”. OpenAI.

ai/transformer 机器学习进阶 深度学习 注意力机制 gated-attention