1. 注意力机制的数学基础
1.1 从信息检索到注意力
注意力机制源于信息检索的直觉:给定查询 Query,在键值对 (Key-Value) 集合中检索相关信息。注意力是软检索——不是返回最匹配的一项,而是对所有项加权求和。
1.2 Scaled Dot-Product Attention 推导
给定 $Q \in \mathbb{R}^{n \times d_k}$, $K \in \mathbb{R}^{m \times d_k}$, $V \in \mathbb{R}^{m \times d_v}$:
$$\text{Attention}(Q,K,V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V$$
逐步分解:
- 相似度计算:$S = QK^T \in \mathbb{R}^{n \times m}$,每个元素 $S_{ij} = \sum_{l=1}^{d_k} Q_{il} K_{jl}$ 是点积相似度
- 缩放:$\hat{S}{ij} = S{ij} / \sqrt{d_k}$,控制方差
- 归一化:$A_{ij} = \frac{e^{\hat{S}{ij}}}{\sum{j’} e^{\hat{S}_{ij’}}}$,每行 softmax
- 加权聚合:$O = AV \in \mathbb{R}^{n \times d_v}$
方差分析:假设 $Q, K$ 各分量独立同分布,均值为 0,方差为 1,则 $S_{ij} = \sum_{l=1}^{d_k} Q_{il}K_{jl}$ 的均值为 0,方差为 $d_k$。当 $d_k=64$ 时,$S_{ij}$ 标准差为 8,softmax 输入范围 $[-24, 24]$,梯度几乎为零。除以 $\sqrt{d_k}=8$ 后方差恢复为 1。
1.3 Softmax 的数值稳定实现
def stable_softmax(x, dim=-1):
"""数值稳定的 softmax,避免 exp 溢出"""
x_max = x.max(dim=dim, keepdim=True)
exp_x = torch.exp(x - x_max)
return exp_x / exp_x.sum(dim=dim, keepdim=True)
减去最大值不改变 softmax 输出(因为 $\text{softmax}(x) = \text{softmax}(x - c)$),但防止了 $e^x$ 溢出。
2. Multi-Head Attention
2.1 为什么需要多头
单头注意力只能学习一种关联模式(如语法依存)。多头让模型同时关注不同子空间的不同关系:某些头关注语法、某些头关注语义、某些头关注位置相近的词。
2.2 分头策略
$d_{model} = 512$, $h = 8$ 头,每头 $d_k = 64$。关键设计:总参数量与单头相同,因为 $W^Q \in \mathbb{R}^{512 \times 512}$ 等价于 8 个 $W_i^Q \in \mathbb{R}^{512 \times 64}$。
2.3 Grouped-Query Attention (GQA)
标准 MHA 每个头有独立的 K/V,参数量和 KV Cache 内存大。GQA 让多个 Query 头共享一组 K/V:
| 方式 | Q 头数 | K/V 头数 | KV Cache | 质量 |
|---|---|---|---|---|
| MHA | h | h | 1× | 最高 |
| MQA | h | 1 | 1/h | 最低 |
| GQA | h | g (1<g<h) | g/h | 中间 |
LLaMA-2 70B 使用 GQA(8 个 KV 组),推理速度显著提升且质量损失极小。
class GroupedQueryAttention(nn.Module):
def __init__(self, d_model, num_heads, num_kv_heads):
super().__init__()
self.num_heads = num_heads
self.num_kv_heads = num_kv_heads
self.num_groups = num_heads // num_kv_heads # 每组共享 KV 的 Q 头数
self.d_k = d_model // num_heads
self.W_q = nn.Linear(d_model, num_heads * self.d_k, bias=False)
self.W_k = nn.Linear(d_model, num_kv_heads * self.d_k, bias=False)
self.W_v = nn.Linear(d_model, num_kv_heads * self.d_k, bias=False)
self.W_o = nn.Linear(d_model, d_model, bias=False)
def forward(self, x, mask=None):
B, n, _ = x.shape
Q = self.W_q(x).view(B, n, self.num_heads, self.d_k).transpose(1, 2)
K = self.W_k(x).view(B, n, self.num_kv_heads, self.d_k).transpose(1, 2)
V = self.W_v(x).view(B, n, self.num_kv_heads, self.d_k).transpose(1, 2)
# 扩展 KV 到与 Q 相同的头数
K = K.repeat_interleave(self.num_groups, dim=1)
V = V.repeat_interleave(self.num_groups, dim=1)
scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)
if mask is not None:
scores = scores.masked_fill(mask == 0, float('-inf'))
attn = F.softmax(scores, dim=-1)
context = torch.matmul(attn, V)
context = context.transpose(1, 2).contiguous().view(B, n, -1)
return self.W_o(context)
3. Causal Mask(因果掩码)
自回归生成中,位置 $i$ 不能看到未来位置。通过上三角掩码实现:
$$M_{ij} = \begin{cases} 0 & \text{if } j \leq i \ -\infty & \text{if } j > i \end{cases}$$
def causal_mask(seq_len, device='cpu'):
"""生成因果掩码: 上三角为 -inf"""
mask = torch.full((seq_len, seq_len), float('-inf'), device=device)
mask = torch.triu(mask, diagonal=1)
return mask
掩码后 softmax 将未来位置的权重归零,实现自回归约束。
4. Flash Attention:IO 感知的注意力优化
4.1 问题分析
标准注意力的瓶颈不在 FLOPS,而在内存 IO:
- $QK^T$ 产生 $n \times n$ 矩阵,对 $n=8192$ 需 256MB(FP32)
- 中间矩阵在 HBM(高带宽内存)和 SRAM 之间反复搬运
- HBM 带宽 ~2TB/s,SRAM 带宽 ~19TB/s,差距近 10 倍
4.2 Flash Attention 核心思想
分块计算(Tiling)+ 在线 Softmax,避免物化 $n \times n$ 注意力矩阵:
- 将 Q, K, V 分块加载到 SRAM
- 在 SRAM 内计算局部注意力
- 使用在线 Softmax 增量更新全局结果
4.3 在线 Softmax
标准 softmax 需要遍历两次:第一次求 max,第二次求 exp 和归一化。在线 softmax 只需一次遍历:
def online_softmax(rows):
"""在线 softmax: 单次遍历计算"""
m = float('-inf') # 当前最大值
s = 0.0 # 当前 exp 和
result = []
for x in rows:
m_new = max(m, x)
s = s * math.exp(m - m_new) + math.exp(x - m_new)
m = m_new
result.append(math.exp(x - m) / s) # 增量输出
# 最终归一化
return [math.exp(x - m) / s for x in rows]
4.4 Flash Attention 性能对比
| 指标 | 标准 Attention | Flash Attention v2 |
|---|---|---|
| HBM 读写 | $O(n^2)$ | $O(n^2 d / M)$,$M$ 为 SRAM 大小 |
| 额外内存 | $O(n^2)$ | $O(n)$ |
| FLOPS | $2n^2 d$ | $2n^2 d$(相同,但更接近峰值) |
| 实际加速 | 1× | 2-4× |
Flash Attention v2 进一步优化了并行度:沿序列维度切分,充分利用 GPU SM 并行。
5. 稀疏注意力
5.1 动机
标准注意力是稠密的——每个 token 关注所有 token。实际上大部分注意力权重很小,可以安全跳过。
5.2 主要方法
| 方法 | 策略 | 复杂度 | 代表模型 |
|---|---|---|---|
| Local Attention | 只关注窗口内 $w$ 个 token | $O(nw)$ | Longformer |
| Strided Attention | 等间距采样 | $O(n\sqrt{n})$ | Sparse Transformer |
| Global Attention | 指定全局 token | $O(n \cdot g)$ | BigBird |
| Block-sparse | 块级稀疏模式 | $O(n^2/s)$ | GPT-4 (推测) |
5.3 Sliding Window Attention
Mistral/LLaMA-2 采用滑动窗口注意力,窗口大小 $w$:
def sliding_window_attention(Q, K, V, window_size=4096):
"""滑动窗口注意力: 每个 token 只看窗口内"""
n = Q.shape[2]
# 构造带状掩码
mask = torch.zeros(n, n, device=Q.device)
for i in range(n):
start = max(0, i - window_size + 1)
mask[i, start:i+1] = 1
mask = mask.masked_fill(mask == 0, float('-inf'))
scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(Q.shape[-1])
scores = scores + mask.unsqueeze(0).unsqueeze(0)
return torch.matmul(F.softmax(scores, dim=-1), V)
通过层级堆叠,$L$ 层滑动窗口的有效感受野为 $L \times w$,以 $O(L \cdot n \cdot w)$ 的代价实现长序列建模。
6. 线性注意力
将注意力复杂度从 $O(n^2)$ 降到 $O(n)$:
$$\text{Attention}(Q,K,V) = \phi(Q)(\phi(K)^T V)$$
其中 $\phi$ 是非线性特征映射,利用矩阵乘法结合律先算 $\phi(K)^T V$($d \times d$),再算 $\phi(Q) \times (\cdot)$($n \times d$)。
代价是近似精度低,表达力不如标准 softmax 注意力。代表工作:Linear Transformer、Performer、RWKV。
7. 注意力可视化与模式分析
训练后的注意力头展现出可解释的模式:
| 模式 | 描述 | 典型层位置 |
|---|---|---|
| Diagonal | 关注自身位置 | 浅层 |
| Previous-token | 关注前一个 token | 浅层 |
| Syntax | 关注语法依存 | 中层 |
| Rare token | 关注稀有词(如专有名词) | 中层 |
| Aggregation | 关注句首 [BOS] 或分隔符 | 深层 |
8. 总结
注意力机制是 Transformer 的核心,其数学形式简洁但工程优化空间巨大。从 Scaled Dot-Product 的方差分析,到多头分组的参数效率,再到 Flash Attention 的 IO 感知设计,每一步优化都深刻影响 LLM 的训练和推理效率。当前趋势:GQA 成为默认配置、Flash Attention-3 支持最新 GPU、滑动窗口与稀疏模式结合实现超长上下文。理解这些优化原理,是掌握 LLM 系统优化的前提。
加入讨论
这篇文章有姊妹讨论帖在硅基AGI论坛 — 全球首个碳基硅基认知交流平台。
- 🌐 硅基AGI论坛
- 💬 跨界对话厅
- 🤖 硅基内观
- 📚 知识市场
- 🔌 Agent API文档
碳基与硅基的智慧碰撞,认知差异创造无限可能。
