核心要点

  • 由输入分别线性映射出 Q、K、V,三者形状均为 (L, d)。

  • 注意力分数 = Q·Kᵀ / sqrt(d),除以 sqrt(d) 防止点积过大导致 softmax 梯度消失

  • 对分数最后一维做 softmax 得权重,再与 V 相乘聚合:out = softmax @ V。

  • 易错点:softmax 需减去每行最大值保证数值稳定;缩放因子是 sqrt(d_k) 而非 d_k。

标准回答

一、先把结论讲清楚

可以先把公式说出来:Self-Attention = softmax(QKᵀ / √d_k) V

一、Q、K、V 分别干什么?

Q 可以理解成“我想找什么”,K 是“我能被什么匹配到”,V 是“真正要汇总的信息”。

二、拆开机制和判断点

输入 X 先分别乘 Wq、Wk、Wv 得到 Q、K、V。

二、为什么要除以 √d_k?

QKᵀ 的点积维度越大,数值方差越大。直接进 softmax 容易饱和,梯度会变小。所以要除以 √d_k,让分数尺度更稳定。

三、补上例子、边界和取舍

三、代码里最容易错什么?

softmax 要沿最后一维做,而且要先减去每行最大值,防止 exp 溢出。如果是解码器自注意力,还要在 softmax 前加 causal mask。下面给出纯 NumPy 实现:

面试里可以补一句:手撕代码:用 NumPy/PyTorch 实现 Self 在大模型场景下通常要同时权衡效果、延迟、成本和安全边界。回答时最好带一个例子,比如上下文过长、幻觉、缓存命中或工具调用失败时,系统应该如何降级和观测。

python
import numpy as np

def softmax(x, axis=-1):
    # 减去最大值保证数值稳定,避免 exp 溢出
    x = x - np.max(x, axis=axis, keepdims=True)
    e = np.exp(x)
    return e / np.sum(e, axis=axis, keepdims=True)

def self_attention(X, Wq, Wk, Wv):
    # X: (L, d_model) 输入序列;W*: (d_model, d_k) 投影矩阵
    Q = X @ Wq              # (L, d_k)
    K = X @ Wk              # (L, d_k)
    V = X @ Wv              # (L, d_v)
    d_k = Q.shape[-1]
    scores = Q @ K.T / np.sqrt(d_k)   # (L, L) 缩放点积
    attn = softmax(scores, axis=-1)   # 每行归一化为注意力权重
    out = attn @ V                     # (L, d_v) 加权聚合
    return out, attn

if __name__ == '__main__':
    np.random.seed(0)
    L, d_model, d_k = 4, 8, 8
    X = np.random.randn(L, d_model)
    Wq = np.random.randn(d_model, d_k)
    Wk = np.random.randn(d_model, d_k)
    Wv = np.random.randn(d_model, d_k)
    out, attn = self_attention(X, Wq, Wk, Wv)
    print(out.shape, attn.sum(axis=-1))  # (4, 8) 权重每行和为 1

常见误区

⚠️ 常见踩坑

误区一:忘记除以 √d_k。 不缩放会让 softmax 更容易饱和,训练不稳。

误区二:softmax 不减最大值。 分数稍大就可能 exp 溢出,最后得到 NaN。

追问

追问 1复杂度是多少?如何优化?

序列长度 L 时,QKᵀ 和 attn@V 都是 O(L²·d)显存也和 L² 强相关。优化可以提 FlashAttention、稀疏/线性注意力,以及推理时的 KV Cache

追问 2如何加入因果掩码实现解码器自注意力?

在 softmax 之前,把未来位置的 scores 设成 -inf 或很大的负数。这样 softmax 后未来位置权重就是 0,保证当前位置 只能看见自己和过去 token

🔗 相似问题

同一考点的不同问法,换着练更稳

延伸学习

按主题分类的相关资源,便于系统复习