先让它学会

坐下 5 / 6

每个位置问一遍:我该读谁,读完加权。

卷积用同一套局部权重。RNN 按固定顺序把过去压进一个向量。注意力让当前位置按内容决定读哪些位置:Query 说「我在找什么」,Key 说「我能被怎样匹配」,Value 说「匹配之后取走什么」。

形状先写清

输入 X[B, T, D] 乘三个矩阵,得到 QKV。分数是 Q @ Kᵀ,shape [B, T_query, T_key]。沿最后一轴做 softmax:每一个 Query 在所有 Key 上分配总和为 1 的权重。再 A @ V,被求和的是 key 位置,不是 query 位置。

点积随 D_k 变大会变尖,所以除以 √D_k。缩放跟序列长度无关,只跟这一头的维度有关。

三个 token,手算一行

一个头,D_k=2。令 Q=K=[[1,0],[0,1],[1,1]]V=[[1,0],[0,2],[3,1]]。第一个 Query 与三个 Key 的点积是 [1, 0, 1],除以 √2 约 [0.707, 0, 0.707]。softmax 约 [0.401, 0.198, 0.401]。读 V:0.401[1,0] + 0.198[0,2] + 0.401[3,1] ≈ [1.604, 0.797]。输出通常不是复制某一个 token,是三条 Value 的混合。

对整个 3×3 一次性 softmax 是错的:九个数加起来为 1。应该每行自己归一化。第二行缩放分数 [0, 0.707, 0.707],第三行 [0.707, 0.707, 1.414],你可以自己把权重补完。

掩码写在 softmax 之前

语言模型训练时,位置 i 不能看见 j>i 的 token,否则它在抄未来的答案。做法:把不允许的分数改成很大的负数(习惯上写成 −∞),再 softmax,那些格的权重接近 0。

三个位置的分数表:

       k0    k1    k2
q0    s00   s01   s02
q1    s10   s11   s12
q2    s20   s21   s22

因果掩码之后,s01s02s12 变成 −∞。下三角留着:已经发生的位置可以读。第一行只剩 s00。第一个 token 没有过去,但可以看自己——权重 1 全给自己的 Value。若连 s00 也盖掉,这一行全是 −∞,softmax 会得到 NaN。

先 softmax 再把权重乘 0,剩下的和小于 1,除非再归一化一遍。标准写法是在 softmax 前改分数。padding 掩码和因果掩码不要混用:前者由每条样本的真实长度决定,补齐位置整列都盖;后者所有样本共享下三角。序列从 512 加到 1024,一张注意力矩阵的格子数大约变四倍,显存会先在这里炸,而不是在线性层。

多头只是把 D 切成几份,每份独立做上面这件事再拼回。头数要能整除 D。热力图不是因果证明:Value、残差、下一层都会改输出。要验证某个位置关不关键,遮住它再看损失,比盯颜色可靠。

点出变成 −∞ 的格子

点一下变为 −∞,再点回来。然后检查。再回答第一行为什么还能工作。

k0k1k2 q0 q1 q2

第一个 token 为什么还能算注意力?

自己对过

s01s02s12。留下 s00。位置 0 的允许集合是 {0}。

这一页走完,若还想看原文:Vaswani 等人 2017 年的 Attention Is All You Need。先把 3×3 算过,再去读多头和位置编码。下一页用它之前,先把一次完整训练跑通。