☰
VLA 系统学习第 15 课:为什么一个 Attention Head 还不够?——Multi-Head Attention 与 Transformer Block
2026/10/9 12:24:38 网站建设 项目流程

第十四课标准答案

先把上一课的 12 道题收掉,再把一些常见问题凝练成一段,给出问题与答案,最后进入第 15 课。

1. Q、K、V 分别怎么理解?

最适合当前阶段的理解是:

\[ Q=\text{我想找什么} \]\[ K=\text{我拿什么和你进行匹配} \]\[ V=\text{如果你关注我,我真正提供什么信息} \]

所以 Attention 不是直接拿 \(Q\) 和 \(K\) 当最终信息,而是先用它们决定:

谁应该关注谁、关注多少。

最终真正被加权汇总的是 \(V\)。


2. Self-Attention 中 Q、K、V 都来自 \(X\),为什么还不是同一个 Tensor?

因为经过了三套不同的可训练参数:

\[ Q=XW_Q \]\[ K=XW_K \]\[ V=XW_V \]

虽然原始来源都是:

\[ X \]

但是:

\[ W_Q\neq W_K\neq W_V \]

所以得到的数值不同,功能也不同。


3. 如果

\[ X:[32,10,64] \]

并且暂时统一:

\[ D=64 \]

那么:

\[ Q:[32,10,64] \]\[ K:[32,10,64] \]\[ V:[32,10,64] \]


4. 为什么

\[ Q:[32,10,64] \]

乘:

\[ K^T:[32,64,10] \]

得到:

\[ [32,10,10] \]

?

因为矩阵乘法:

\[ [A,B]\times[B,C]\rightarrow[A,C] \]

所以每个 Batch 内:

\[ [10,64]\times[64,10] \]

得到:

\[ [10,10] \]

加上 Batch:

\[ \boxed{[32,10,10]} \]


5.[32,10,10]中最后两个 10 分别是什么?

它们都表示 Token 位置,但扮演的角色不同。

第一个:

\[ 10 \]

表示:

10 个 Query Token。

第二个:

\[ 10 \]

表示:

每个 Query 都要比较的 10 个 Key Token。

所以元素:

\[ S_{ij} \]

表示:

第 \(i\) 个 Query Token 和第 \(j\) 个 Key Token 的匹配程度。


6. 为什么 Softmax 对最后一个维度做?

因为对于一个固定 Query,我们希望:

它对所有 Key 的注意力权重加起来等于 1。

例如:

\[ [0.1,0.6,0.2,0.1] \]

表示当前 Query:

  • 10% 关注 Token 1;
  • 60% 关注 Token 2;
  • 20% 关注 Token 3;
  • 10% 关注 Token 4。

所以:

weights = torch.softmax(scores, dim=-1)

是在“所有 Key”这一维做 Softmax。


7. 为什么除以

\[ \sqrt D \]

?

因为:

\[ Q\cdot K \]

需要对 \(D\) 个元素进行乘加。

当 \(D\) 很大时,点积的数值尺度容易变大。

如果很大的分数直接进入 Softmax,Softmax 可能变得过于极端,不利于稳定训练。

所以进行缩放:

\[ \frac{QK^T}{\sqrt D} \]

控制数值尺度。

严格写法通常是 \(\sqrt{d_k}\),但在我们当前单头且统一维度的阶段先用 \(D\) 理解即可。


8. 为什么 Attention Weight 后还必须乘 \(V\)?

因为 Attention Weight:

\[ [B,T,T] \]

只告诉我们:

应该从谁那里拿多少信息。

真正的信息内容在:

\[ V:[B,T,D] \]

里面。

所以:

\[ AttentionWeights\times V \]

才真正完成:

按照注意力关系重新汇总 Token 信息。

最终:

\[ [B,T,T]\times[B,T,D] \rightarrow [B,T,D] \]


9. Self-Attention 和 Cross-Attention 的核心区别是什么?

Self-Attention:

\[ Q,K,V \]

来自同一个 Sequence。

可以理解成:

一组 Token 内部相互交流。

Cross-Attention:

\[ Q \]

来自一组 Token,

而:

\[ K,V \]

来自另一组 Token。

可以理解成:

一组 Token 去另一组 Token 中检索信息。

后面 ACT Decoder 会再次遇到这个区别。


10. Padding Mask 为什么通常在 Softmax 前作用?

因为我们希望 Padding 的注意力权重最终为:

\[ 0 \]

一种常见做法是在 Softmax 前把对应 Score 设置成非常大的负数,例如:

\[ -\infty \]

那么经过 Softmax:

\[ e^{-\infty}\approx0 \]

因此模型不会从 Padding Token 中读取信息。


11. Attention 中主要学习哪些 Parameter?

最核心的是:

\[ W_Q,\quad W_K,\quad W_V \]

以及真实 Multi-Head Attention 中常见的最终输出投影:

\[ W_O \]

这些参数都会通过:

\[ Loss \rightarrow Backward \rightarrow Gradient \rightarrow Optimizer \]

进行学习。


12. 完整解释 Attention 公式

\[ Attention(Q,K,V) = softmax \left( \frac{QK^T}{\sqrt D} \right)V \]

可以直接翻译成:

\[ QK^T \]

每个 Query 和所有 Key 做匹配。

↓

\[ \frac{QK^T}{\sqrt D} \]

控制匹配分数的数值尺度。

↓

\[ softmax \]

把匹配分数变成注意力比例。

↓

\[ \times V \]

按这些比例把真正的信息重新汇总。

最终:

\[ \boxed{ 每个Token得到融合其他Token信息后的新表示 } \]

常见问题的凝练

这段可以直接放在这一课 Attention 公式后面。

问题:为什么计算 Attention 时要把 \(K\) 转置,使 \(QK^T\) 从 \([B,T,D]\) 得到 \([B,T,T]\)?既然 \([B,T,T]\) 已经是一张注意力矩阵,为什么还要继续与 \(V:[B,T,D]\) 相乘?最后重新得到的 \([B,T,D]\) 又有什么作用?

答案可以浓缩成下面这一条因果链:

第一步:为什么要 \(K^T\)?

对,首先就是矩阵乘法的需要,但更深层原因是我们想让“每个 Query 和每个 Key 两两做点积”。

因为:

\[ Q:[T,D] \]

每一行是一条 Query,

\[ K:[T,D] \]

每一行是一条 Key。

我们想计算:

\[ q_i\cdot k_j \]

对于所有:

\[ i,j \]

所以把:

\[ K:[T,D] \]

转成:

\[ K^T:[D,T] \]

这样:

\[ [T,D]\times[D,T] = [T,T] \]

矩阵乘法一次就把所有:

\[ q_i\cdot k_j \]

都算出来了。

所以:

\[ \boxed{ K转置不是单纯为了凑Shape,而是为了让每个Q和每个K完成两两点积 } \]


第二步:为什么[B,T,T]还不是最终结果?

因为:

\[ [B,T,T] \]

只是一张:

Token 和 Token 之间的关系表。

例如某一行:

\[ [0.1,0.7,0.2] \]

只说明:

当前 Token 应该分别从三个 Token 中拿 10%、70%、20% 的信息。

但是这张表本身没有告诉你:

那三个 Token 到底有什么信息。

真正的信息放在:

\[ V \]

里。

因此还需要:

\[ AttentionWeights\times V \]


第三步:为什么乘完又回到[B,T,D]?

因为:

\[ [B,T,T] \times [B,T,D] \]

本质上是在对 \(T\) 个 Value Token 做加权求和。

对于第 \(i\) 个 Token:

\[ o_i = \sum_{j=1}^{T} a_{ij}v_j \]

因此第 \(i\) 个位置仍然得到一个:

\[ D \]

维向量。

一共有 \(T\) 个位置,所以重新得到:

\[ [B,T,D] \]

但是这个新的:

\[ [B,T,D] \]

和原来的已经不一样。

原来的 Token:

主要表示自己的信息。

Attention 后的 Token:

已经把其他 Token 的相关信息融合进来了。

因此整个 Attention 最重要的 Shape 变化可以记成:

\[ \boxed{ [B,T,D] \overset{QK^T}{\longrightarrow} [B,T,T] \overset{\times V}{\longrightarrow} [B,T,D] } \]

含义分别是:

\[ \boxed{ Token信息 \rightarrow Token关系 \rightarrow 融合关系后的新Token信息 } \]

而最后这个新的:

\[ [B,T,D] \]

还能继续进入下一层 Transformer、Feed Forward Network 或最后的任务 Head。

这一点,就是 Transformer 能不断堆叠的基础。

VLA 系统学习第 15 课:为什么一个 Attention Head 还不够?——Multi-Head Attention 与 Transformer Block

我们现在已经懂了单头 Attention:

\[ X \rightarrow Q,K,V \rightarrow QK^T \rightarrow AttentionWeights \rightarrow V \rightarrow 新的X \]

但这里还有一个限制:

如果所有 Token 之间只有一套 Q/K/V 表示,那么模型每次只能在这一套表示空间里判断 Token 关系。

真实 Sequence 里的关系可能很多。

例如一段机器人历史中,一个 Token 和另一个 Token 之间可能同时存在:

  • 时间上的关系;
  • 状态变化关系;
  • 视觉对应关系;
  • 任务相关关系。

我们不应该人工规定:

Head 1 一定学时间,Head 2 一定学空间。

真实网络学到什么由训练决定。

但是可以让模型:

并行使用多套不同的 Q/K/V 投影,从多个表示子空间建立 Attention。

这就是:

Multi-Head Attention


一、先用一个非常具体的 Shape 开始

假设:

\[ X:[B,T,D] \]

具体取:

\[ X:[32,10,64] \]

现在我们设置:

\[ H=4 \]

也就是 4 个 Attention Head。

我们把:

\[ D=64 \]

平均拆成:

\[ 4 \]

份:

\[ d_h=\frac{D}{H} = \frac{64}{4} = 16 \]

所以:

\[ \boxed{ D=H\times d_h } \]

这里开始重新引入小写:

\[ d_h \]

但这次含义非常明确:

一个 Head 自己处理的特征维度。

所以:

\[ D=64 \]

是整个模型的 Embedding Dimension,

而:

\[ d_h=16 \]

是单个 Head 的维度。


二、是不是直接把原来的 64 个特征硬切四块?

从 Shape 上看,确实会:

\[ 64 \rightarrow 4\times16 \]

但在此之前一般先经过可训练 Projection:

self.q_proj = nn.Linear(64, 64)self.k_proj = nn.Linear(64, 64)self.v_proj = nn.Linear(64, 64)

输入:

\[ X:[32,10,64] \]

得到:

\[ Q,K,V:[32,10,64] \]

然后才重新组织 Shape:

\[ [32,10,64] \rightarrow [32,10,4,16] \]

这里:

  • 32:Batch;
  • 10:Token;
  • 4:Head;
  • 16:每个 Head 的维度。

代码:

q = self.q_proj(x)q = q.reshape(B, T, 4, 16)

这一步没有增加或者减少数据数量:

\[ 64=4\times16 \]

只是重新解释了最后一个维度。


三、为什么还要再transpose?

现在:

\[ Q:[B,T,H,d_h] \]

也就是:

\[ [32,10,4,16] \]

但我们希望:

每一个 Head 独立计算自己的一套 Attention。

所以更方便的排列是:

\[ [B,H,T,d_h] \]

也就是:

\[ [32,4,10,16] \]

代码:

q = q.transpose(1, 2)

K、V 同样处理:

q = q.reshape(B, T, H, d_h).transpose(1, 2)k = k.reshape(B, T, H, d_h).transpose(1, 2)v = v.reshape(B, T, H, d_h).transpose(1, 2)

现在每个 Head 都拥有:

\[ [T,d_h] \]

的一套 Q、K、V。


四、现在每个 Head 独立做我们上一课完全一样的事情

现在:

\[ Q:[B,H,T,d_h] \]\[ K:[B,H,T,d_h] \]

K 交换最后两个维度:

\[ K^T:[B,H,d_h,T] \]

于是:

\[ QK^T \]

得到:

\[ \boxed{ [B,H,T,T] } \]

例如:

\[ [32,4,10,10] \]

这意味着:

32 个 Sample,每个 Sample 有 4 张不同的 Attention Matrix,每张都是 10×10。

所以单头时:

\[ [B,T,T] \]

多头以后:

\[ \boxed{ [B,H,T,T] } \]

只是多了:

\[ H \]

这个 Head Dimension。


五、代码已经可以和数学完全对应

scores = q @ k.transpose(-2, -1)

Shape:

\[ [32,4,10,16] \times [32,4,16,10] \]

得到:

\[ [32,4,10,10] \]

然后:

scores = scores / math.sqrt(self.head_dim)weights = torch.softmax(scores, dim=-1)

Shape 仍然:

\[ [32,4,10,10] \]

然后:

out = weights @ v

因为:

\[ [32,4,10,10] \times [32,4,10,16] \]

得到:

\[ \boxed{ [32,4,10,16] } \]

也就是:

每个 Head 都完成了一次独立的信息聚合。


六、四个 Head 算完以后怎么办?

现在:

\[ out:[B,H,T,d_h] \]

即:

\[ [32,4,10,16] \]

Transformer 最终还是希望回到:

\[ [B,T,D] \]

所以先交换回来:

\[ [B,H,T,d_h] \rightarrow [B,T,H,d_h] \]

代码:

out = out.transpose(1, 2)

得到:

\[ [32,10,4,16] \]

然后把:

\[ H\times d_h \]

重新合并:

\[ 4\times16=64 \]

代码:

out = out.reshape(B, T, D)

得到:

\[ \boxed{ [32,10,64] } \]

所以整个 Multi-Head Attention Shape 特别值得记住:

\[ [B,T,D] \]

↓

\[ [B,H,T,d_h] \]

↓

\[ [B,H,T,T] \]

↓

\[ [B,H,T,d_h] \]

↓

\[ \boxed{ [B,T,D] } \]


七、为什么最后还有一个out_proj?

标准 Multi-Head Attention 通常还会有:

self.out_proj = nn.Linear(D, D)

所以多头拼回来以后:

out = self.out_proj(out)

Shape:

\[ [B,T,D] \rightarrow [B,T,D] \]

不变。

但是这个 Linear 可以进一步学习:

多个 Head 拼接回来的信息应该如何重新混合。

所以完整思想:

多个Head分别读取信息 ↓ 把多个Head结果拼回来 ↓ 再经过Output Projection重新融合

八、现在写出一个最小可读版本

import mathimport torchimport torch.nn as nnclass SimpleMultiHeadAttention(nn.Module): def __init__(self, d_model=64, num_heads=4): super().__init__() self.d_model = d_model self.num_heads = num_heads self.head_dim = d_model // num_heads self.q_proj = nn.Linear(d_model, d_model) self.k_proj = nn.Linear(d_model, d_model) self.v_proj = nn.Linear(d_model, d_model) self.out_proj = nn.Linear(d_model, d_model) def forward(self, x): B, T, D = x.shape q = self.q_proj(x) k = self.k_proj(x) v = self.v_proj(x) q = q.reshape( B, T, self.num_heads, self.head_dim ).transpose(1, 2) k = k.reshape( B, T, self.num_heads, self.head_dim ).transpose(1, 2) v = v.reshape( B, T, self.num_heads, self.head_dim ).transpose(1, 2) scores = q @ k.transpose(-2, -1) scores = scores / math.sqrt(self.head_dim) weights = torch.softmax(scores, dim=-1) out = weights @ v out = out.transpose(1, 2) out = out.reshape(B, T, D) out = self.out_proj(out) return out

如果输入:

\[ x:[32,10,64] \]

输出仍然:

\[ \boxed{[32,10,64]} \]

这点特别重要。

Multi-Head Attention 不是把 Sequence 变没了。

而是:

让原来的 Token Sequence 完成一次多视角的信息交换,然后仍然返回同样结构的 Token Sequence。


九、Attention 做完以后为什么还不是完整 Transformer?

现在我们只有:

\[ X \rightarrow MultiHeadAttention \rightarrow Y \]

但是 Transformer Block 还要解决另外一个问题。

Attention 擅长的是:

\[ \boxed{ Token之间交换信息 } \]

例如:

Token 1 从 Token 2、3、4 读取什么。

但每个 Token 拿到这些信息以后,还需要进一步:

在自己的 Feature Dimension 内进行非线性加工。

这就是:

Feed Forward Network

简称:

\[ FFN \]


十、FFN 和 Attention 的职责非常不同

这是 Transformer 特别需要理解的一点。

Attention

主要进行:

\[ \boxed{ Token\leftrightarrow Token } \]

的信息交互。


FFN

则对每个 Token分别使用同一个 MLP:

\[ D \rightarrow D_{ff} \rightarrow D \]

例如:

\[ 64 \rightarrow 256 \rightarrow 64 \]

代码:

ffn = nn.Sequential( nn.Linear(64, 256), nn.GELU(), nn.Linear(256, 64))

如果输入:

\[ [32,10,64] \]

第一层:

\[ [32,10,64] \rightarrow [32,10,256] \]

GELU:

\[ [32,10,256] \]

第二层:

\[ [32,10,256] \rightarrow [32,10,64] \]

注意:

\[ T=10 \]

一直没变。

因为 FFN 对每一个 Token 独立进行特征变换。


十一、所以 Attention 和 FFN 可以这样区分

这是这一课非常重要的一组条件反射:

\[ \boxed{ Attention: 不同Token之间交流 } \]

而:

\[ \boxed{ FFN: 每个Token内部继续加工Feature } \]

可以粗略画成:

Token 1 ─┐ Token 2 ─┼→ Attention → Token之间交换信息 Token 3 ─┘ 然后: Token 1 → FFN → 自己内部加工 Token 2 → FFN → 自己内部加工 Token 3 → FFN → 自己内部加工

两部分配合起来,才形成 Transformer Block 的核心计算。


十二、但为什么还有 Residual Connection?

如果我们直接:

\[ X \rightarrow Attention \rightarrow Y \]

那么新结果完全替换旧 \(X\)。

Transformer 通常会做:

\[ Y=X+Attention(X) \]

这叫:

Residual Connection

残差连接。

代码:

x = x + attention_output

Shape:

\[ [B,T,D] + [B,T,D] \]

得到:

\[ [B,T,D] \]


十三、Residual 为什么重要?

可以先抓两个直觉。

第一:

Attention 新学到的是“补充信息”,原来的 Token 信息不需要全部扔掉。

所以:

\[ X+Attention(X) \]

有一种:

原始信息 + 新信息

的意味。

第二:

Residual 能为深层网络提供更直接的梯度传播路径。

所以堆很多 Transformer Block 时,更容易优化。

以后学 ResNet 时还会系统碰到 Residual;当前理解这两个作用就够。


十四、LayerNorm 又是什么?

Transformer 中还会频繁看到:

nn.LayerNorm(D)

它用于对 Feature 做归一化。

假设:

\[ X:[B,T,D] \]

例如:

\[ [32,10,64] \]

那么:

nn.LayerNorm(64)

通常是对:

每一个 Token 自己的 64 个 Feature

进行归一化处理。

所以 Shape 不变:

\[ [32,10,64] \rightarrow [32,10,64] \]

它主要帮助网络内部数值尺度更加稳定。

LayerNorm 的数学细节后面遇到需要时再进一步展开,现在先理解它的位置和职责。


十五、终于拼成一个完整 Transformer Block

一种经典的 Post-Norm 表达可以写成:

\[ Y= LayerNorm( X+MHA(X) ) \]

然后:

\[ Z= LayerNorm( Y+FFN(Y) ) \]

完整链:

X ↓ Multi-Head Attention ↓ + X ↓ LayerNorm ↓ FFN ↓ + 前面的结果 ↓ LayerNorm ↓ Output

Shape 从头到尾:

\[ [B,T,D] \]

保持不变。


十六、代码里完整长什么样?

把刚才的 Multi-Head Attention 放进去:

class SimpleTransformerBlock(nn.Module): def __init__(self, d_model=64, num_heads=4): super().__init__() self.attention = SimpleMultiHeadAttention( d_model=d_model, num_heads=num_heads ) self.norm1 = nn.LayerNorm(d_model) self.ffn = nn.Sequential( nn.Linear(d_model, d_model * 4), nn.GELU(), nn.Linear(d_model * 4, d_model) ) self.norm2 = nn.LayerNorm(d_model) def forward(self, x): attn_out = self.attention(x) x = x + attn_out x = self.norm1(x) ffn_out = self.ffn(x) x = x + ffn_out x = self.norm2(x) return x

输入:

\[ [32,10,64] \]

Attention:

\[ [32,10,64] \]

Residual:

\[ [32,10,64] \]

Norm:

\[ [32,10,64] \]

FFN 中间:

\[ [32,10,256] \]

最后回来:

\[ [32,10,64] \]

最终整个 Block:

\[ \boxed{ [32,10,64] \rightarrow [32,10,64] } \]

但是里面的信息已经进行了:

Token 间交互 + Token 内特征变换。


十七、真实 Transformer 的 Norm 顺序可能与你看到的不一样

以后你可能看到:

Attention → Add → Norm

也可能看到:

Norm → Attention → Add

前者通常叫:

Post-Norm

后者通常叫:

Pre-Norm

所以不要以后发现代码顺序不一样,就认为哪个一定写错了。

不同 Transformer 实现可能选择不同结构。

尤其我们进入真实 ACT / DETR 风格代码以后,会直接检查:

它到底用的是哪一种。

现在重点不是背哪个唯一正确,而是知道 Transformer Block 的核心零件仍然是:

\[ \boxed{ Attention + FFN + Residual + Normalization } \]


十八、为什么 Transformer 可以堆很多 Block?

因为每一个 Block 输入:

\[ [B,T,D] \]

输出仍然:

\[ [B,T,D] \]

所以可以:

\[ X \rightarrow Block_1 \rightarrow Block_2 \rightarrow Block_3 \rightarrow \cdots \]

第一层 Token 完成一次信息交流。

第二层在新的 Representation 上继续交流。

第三层再继续。

于是 Representation 越来越丰富。


十九、现在nn.TransformerEncoderLayer已经不应该是纯黑盒了

以后看到:

layer = nn.TransformerEncoderLayer( d_model=64, nhead=4)

你应该先想到它内部大致包含:

Multi-Head Self-Attention + Residual + LayerNorm + Feed Forward Network + Residual + LayerNorm

而:

d_model=64

就是:

\[ D=64 \]

nhead=4

就是:

\[ H=4 \]

于是每 Head 大致:

\[ d_h=\frac{64}{4}=16 \]

这时候 API 就已经能和数学对应起来了。


二十、我们现在距离 ACT 到底还差什么?

其实已经非常近了。

我们已经掌握:

\[ [B,T,D] \]

Token / Embedding;

Position;

Q/K/V;

Self-Attention;

Cross-Attention 的基本思想;

Multi-Head Attention;

Transformer Block。

现在还差两个关键问题:

第一:

Transformer Encoder 和 Decoder 到底有什么区别?

第二:

一个 Action Query 如何通过 Decoder 去读取 Image / Robot State 的信息,然后产生 Action Sequence?

这两个问题恰好就是理解 ACT 架构非常重要的最后一段 Transformer 基础。

所以接下来不会再继续向外扩散。


第十五课自测

  1. 如果:

\[ D=128,\quad H=8 \]

那么单个 Head Dimension:

\[ d_h \]

是多少?

  1. 输入:

\[ X:[32,20,64] \]

设置:

\[ H=4 \]

拆 Head 后为什么得到:

\[ [32,4,20,16] \]

?

  1. Multi-Head Attention 中 Score Matrix 的 Shape 为什么从单头的:

\[ [B,T,T] \]

变成:

\[ [B,H,T,T] \]

?

  1. 如果:

\[ AttentionWeights:[32,4,20,20] \]\[ V:[32,4,20,16] \]

相乘以后 Shape 是什么?

  1. 为什么多个 Head 的结果最后还要重新合并成:

\[ [B,T,D] \]

?

  1. out_proj的作用是什么?

  2. Attention 和 FFN 的核心职责分别是什么?

  3. 为什么 FFN 通常不改变 Sequence Length \(T\)?

  4. Residual Connection:

\[ X+Attention(X) \]

为什么要求两者 Shape 相同?

  1. nn.LayerNorm(64)面对:

\[ [32,10,64] \]

主要归一化哪个维度?

  1. Transformer Block 为什么能够连续堆很多层?

  2. 为什么不能机械认为“第 1 个 Head 一定负责时间,第 2 个 Head 一定负责视觉”?

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询