拆解 Transformer 核心:深入浅出注意力机制(Attention Mechanism)
拆解 Transformer 核心:深入浅出注意力机制(Attention Mechanism)
在传统的序列模型(如 RNN、LSTM)中,信息的流动受限于单向或双向的时间步递归。无论输入序列多长,系统都必须将整个句子的语义强制压缩进一个固定维度的隐藏状态向量(Context Vector)中。这种结构带来了两个致命缺陷:长程依赖丢失(信息瓶颈) 与 无法并行计算(训练效率低下)。
2017 年,论文《Attention Is All You Need》提出 Transformer 架构,彻底废弃了循环机制,将注意力机制(Attention Mechanism)推上了绝对核心的舞台。注意力机制打破了距离限制,让任意两个 Token 之间的交互距离直接缩短为 ,成为现代大型语言模型(LLM)的基石。
一、 核心直觉:从“信息检索”理解 Q、K、V
注意力机制的本质是一种动态加权寻址机制,其底层逻辑与数据库检索系统高度同构。它由三个基本概念构成:
- Query(查询向量 ): 当前 Token 发出的“寻址需求”,代表“我在找什么”。
- Key(键向量 ): 序列中每个 Token 暴露的“匹配特征”,代表“我是什么”。
- Value(值向量 ): 序列中每个 Token 实际承载的“语义内容”,代表“如果匹配到了我,我能提供什么信息”。
Query (Q) ──┐
├──> [ 相似度计算 (Q · K^T) ] ──> [ 缩放 & Softmax ] ──> [ 权重分布 α ]
Key (K) ──┘ │
▼
Value (V) ─────────────────────────────────────────────────────────> [ 加权求和 Σ α · V ] ──> 输出向量
注意力机制执行的过程,就是拿自身的 去和环境中所有 Token 的 进行相关性打分,将得分归一化为概率权重后,对所有的 进行加权求和,生成融合了全局上下文的新表征。
二、 缩放点积注意力(Scaled Dot-Product Attention)
Transformer 采用的点积注意力在计算速度和显存利用率上远优于加性注意力(Additive Attention)。其数学公式为:
整个计算流程拆解为四个关键步骤:
1. 相似度度量()
设输入序列长度为 ,特征维度为 。 与 相乘,得到一个形状为 的原始关联矩阵(Attention Logits)。矩阵中第 项的值即为第 个词与第 个词的点积内积,内积越大表示语义关联度越高。
2. 缩放因子(Scaling Factor )的设计缘由
为什么必须除以 ?其核心目的在于防止梯度消失。
假设向量 与 中的每个分量都是均值为 0、方差为 1 的独立随机变量:
- 两个维度的点积展开为:
- 其数学期望为:
- 其方差为:
当隐藏维度 较大时(例如 或 ),内积的方差会扩大到几十甚至上百,导致点积结果出现极大值或极小值。直接输入 Softmax 会使函数进入极度陡峭或平缓的饱和区,导致局部梯度趋近于 0(梯度消失),模型难以收敛。
除以 可以将内积的方差强行拉回 1,使数值维持在 Softmax 梯度响应最灵敏的区间。
3. 掩码操作(Masking)
在实际计算中,注意力矩阵需要经过掩码控制:
- 填充掩码(Padding Mask): 在批处理(Batching)中,短句子通常会被填充占位符(如
<pad>)。在 Softmax 之前,将所有与<pad>对应的注意力分数设为 ,经 Softmax 计算后权重精确为 0,防止无效填充干扰语义。 - 因果掩码(Causal / Look-ahead Mask): 用于自回归生成任务(如 GPT)。将注意力矩阵右上三角(未来位置)强行置为 ,确保时刻 的预测只能依赖 的历史上下文。
4. 权重归一化与加权求和
经掩码处理后的得分通过 Softmax 归一化为概率分布矩阵 (满足每行元素之和为 1),再与 相乘,得到融合上下文后的隐藏表征。
三、 多头注意力(Multi-Head Attention)
如果只使用单组 计算注意力,所有不同语义尺度的关系会被强行平均化在一个权重矩阵中。为此,Transformer 引入了多头注意力(Multi-Head Attention, MHA)。
Input (X)
┌──────┼──────┐
▼ ▼ ▼
Linear Linear Linear (W_Q, W_K, W_V)
│ │ │
Split into h Heads (Q_i, K_i, V_i)
│ │ │
▼ ▼ ▼
Head 1 Head 2 ... Head h (Scaled Dot-Product Attention)
│ │ │
└──────┼──────┘
▼
Concat
▼
Linear (W_O)
▼
Output
1. 数学定义
将 维度的输入通过 组独立的投影矩阵映射到更低维的子空间(通常设 ):
其中:
- 投影矩阵 ,,
- 输出投影矩阵
2. 为什么需要多头?
- 多重语义聚焦: 不同的“头”能够在反向传播中自发学习关注不同类型的信息。例如,某个头专注于捕获邻近的代词指代关系(如 “it” 指向 “animal”),另一个头专注于动宾短语搭配,第三个头捕获远距离标点符号或从句边界。
- 集成表征能力: 类似卷积神经网络中设置多个卷积核来提取不同尺度的局部特征,多头机制让模型同时在多个互不干扰的正交子空间中建立关联。
四、 常见注意力形态:Self-Attention 与 Cross-Attention
在完整的 Transformer 体系中,注意力机制存在两种主要的数据交互形式:
| 机制类型 | 的来源 | 的来源 | 作用位置 | 核心用途 |
|---|---|---|---|---|
| 自注意力(Self-Attention) | 当前模块的上一层输出 | 与 来自同一输入序列 | Encoder 全层、Decoder 掩码层 | 建立序列内部 Token 与 Token 之间的全局依赖关系。 |
| 交叉注意力(Cross-Attention) | Decoder 上一层的自注意力输出 | 来自 Encoder 的最终隐藏状态 | Decoder 的中间交互层 | 将生成端的目标序列与源语言序列进行对齐和信息提取。 |
五、 PyTorch 向量化完整实现
以下是一个不依赖任何高阶封装、严格按维度切分实现的 MultiHeadAttention 模块:
import math
import torch
import torch.nn as nn
class MultiHeadAttention(nn.Module):
def __init__(self, d_model: int, num_heads: int, dropout: float = 0.1):
super().__init__()
assert d_model % num_heads == 0, "d_model 必须能被 num_heads 整除"
self.d_model = d_model
self.num_heads = num_heads
self.d_k = d_model // num_heads
# 线性投影矩阵
self.w_q = nn.Linear(d_model, d_model)
self.w_k = nn.Linear(d_model, d_model)
self.w_v = nn.Linear(d_model, d_model)
self.w_o = nn.Linear(d_model, d_model)
self.dropout = nn.Dropout(p=dropout)
def forward(self, q: torch.Tensor, k: torch.Tensor, v: torch.Tensor, mask: torch.Tensor = None) -> torch.Tensor:
"""
q, k, v: (batch_size, seq_len, d_model)
mask: (batch_size, 1, 1, seq_len) 或 (batch_size, 1, seq_len, seq_len)
"""
batch_size = q.size(0)
# 1. 线性投影并切分多头 -> (batch_size, num_heads, seq_len, d_k)
# 维度变换: (batch, seq, d_model) -> (batch, seq, heads, d_k) -> (batch, heads, seq, d_k)
Q = self.w_q(q).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
K = self.w_k(k).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
V = self.w_v(v).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
# 2. 计算缩放点积得分 -> (batch_size, num_heads, seq_len, seq_len)
scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)
# 3. 掩码操作
if mask is not None:
# 将需要遮盖的位置置为一个极小的浮点数 (-1e9)
scores = scores.masked_fill(mask == 0, -1e9)
# 4. Softmax 与 Dropout -> 获取权重矩阵
attn_weights = torch.softmax(scores, dim=-1)
attn_weights = self.dropout(attn_weights)
# 5. 加权聚合 Value -> (batch_size, num_heads, seq_len, d_k)
context = torch.matmul(attn_weights, V)
# 6. 多头拼接并投影回 d_model 维度
# 维度变换: (batch, heads, seq, d_k) -> (batch, seq, heads, d_k) -> (batch, seq, d_model)
context = context.transpose(1, 2).contiguous().view(batch_size, -1, self.d_model)
output = self.w_o(context)
return output
# ---------------- 验证前向传播与掩码 ----------------
if __name__ == "__main__":
batch_size = 2
seq_len = 5
d_model = 64
num_heads = 8
mha = MultiHeadAttention(d_model=d_model, num_heads=num_heads)
# 模拟输入序列
x = torch.randn(batch_size, seq_len, d_model)
# 构造因果下三角掩码 (Causal Mask)
causal_mask = torch.tril(torch.ones(seq_len, seq_len)).unsqueeze(0).unsqueeze(0) # (1, 1, seq_len, seq_len)
# 前向计算
out = mha(x, x, x, mask=causal_mask)
print("注意力输出张量形状:", out.shape) # torch.Size([2, 5, 64])
六、 现代大模型时代的演进:从 MHA 到 GQA 与 FlashAttention
标准注意力机制的计算复杂度为 ,显存占用随序列长度平方级增长。在数十亿至数千亿参数的长文本大模型时代,传统的 MHA 带来了严峻的显存带宽瓶颈(KV Cache 暴增)与显存读写延迟(Memory-Bound)。因此,学术界与工业界演化出了两类核心改进路径:
1. KV 缓存优化:MQA 与 GQA
- Multi-Head Attention (MHA): 每个 Query 头独立配备一套独立的 Key 和 Value 头。表达能力强,但自回归解码时的 KV Cache 极其庞大。
- Multi-Query Attention (MQA): 所有 Query 头共享唯一的一套 Key 和 Value 头。大幅缩减 KV Cache(约缩小 倍),但容易导致模型表征容量下降。
- Grouped-Query Attention (GQA): 当前主流折中方案(如 LLaMA-2/3、Mistral、DeepSeek)。将 个 Query 头分组,每组内部共享一套 Key 和 Value 头。在几乎不损失推理精度的前提下,实现了显存占用与推理吞吐的最佳平衡。
2. 硬件加速优化:FlashAttention
传统 Attention 需要在 GPU 高带宽显存(HBM)中完整实体化 大小的中间分数矩阵并进行多次搬运。
FlashAttention 利用 GPU 层次化内存体系(SRAM 高速缓存与 HBM 主存):
- 采用平铺切块(Tiling)技术,将 分块载入片上高速 SRAM 中计算。
- 结合在线 Softmax 缩放算法(Online Softmax),在不物化存储巨大 矩阵的情况下直接计算出输出。
- 将内存读写次数由 降低到 ,在不改变任何数学输出的前提下,带来了数倍的训练与推理加速。
注意力机制以其极具表达力的动态加权范式,打破了传统神经网络处理时序数据的局限。从最初解决翻译信息瓶颈的加性注意力,到 Transformer 确立的点积与多头机制,再到如今针对超长上下文优化演进的 GQA 与 FlashAttention,理解这一演进脉络,是洞察现代大语言模型底层运转逻辑的核心钥匙。
- 点赞
- 收藏
- 关注作者
评论(0)