> 技术文档 > 已知transformer中QKV的运算,在代码中实现比较复杂抽象,解释一下。 注意高维张量的树状图表示:无论转置还是乘积操作都可以用树状图表示,且第三个维度head(用来分成小块矩阵

已知transformer中QKV的运算,在代码中实现比较复杂抽象,解释一下。 注意高维张量的树状图表示:无论转置还是乘积操作都可以用树状图表示,且第三个维度head(用来分成小块矩阵

明白,你的问题核心是:在 PyTorch 里 qkv.unbindtranspose 后,矩阵维度为什么是 [batch, heads, seq, head_dim],以及 torch.matmul(q, k.transpose(-2,-1)) 怎么保证每个 head 的 Q·K^T 是独立计算的。我帮你用你的例子一步步拆开。


1️⃣ 假设输入

x.shape = [batch, seq_len, d_model] = [1, 2, 4]num_heads = 2head_dim = 2

经过 Linear 映射:

qkv = self.qkv(x) # [1, 2, 12] (d_model*3)qkv.view(batch, seq, 3, num_heads, head_dim)
  • 现在 [1, 2, 3, 2, 2]
  • dim=2 是 Q/K/V → 拆开:
q, k, v = qkv.unbind(dim=2) # 每个 shape: [1, 2, 2, 2]
  • [batch, seq, heads, head_dim]
  • batch=1
  • seq=2 → 两个 token
  • heads=2 → 两个注意力头
  • head_dim=2 → 每个头的维度

示意:这个表示1 2 2 2维度的形式很重要,注意维度的排序
所以一个矩阵就可以是好几维的张量。行代表第一个向量的维度,然后排序方式就是接下来的很多个维度(哪个维度在前面就按照哪个维度先排列)。

q[0, :, 0, :] = head1 token1
q[0, :, 1, :] = head2 token1
q[0, :, 0, :] = head1 token2
q[0, :, 1, :] = head2 token2

---## 2️⃣ 转置维度```pythonq = q.transpose(1, 2) # [batch, heads, seq, head_dim]
  • 现在 heads 在前 → 每个 head 是一个独立矩阵计算单元
  • q[0,0,:,:] → head1 的 Q 矩阵(shape [seq, head_dim] = [2,2])
  • q[0,1,:,:] → head2 的 Q 矩阵(shape [2,2])

同理 k、v 也转置了。

✅ 这样就实现了每个 head 独立计算,不会混在一起。


q[0, :, 0, :] = head1 token1q[0, :, 0, :] = head1 token2q[0, :, 1, :] = head2 token1q[0, :, 1, :] = head2 token2

按照对应的维度排序应该是

q[0, :, 0, :] = token1 head1 q[0, :, 0, :] = token2 head1q[0, :, 1, :] = token1 head2q[0, :, 1, :] = token2 head2

3️⃣ 计算 scores

**

scores = torch.matmul(q, k.transpose(-2,-1)) / sqrt(head_dim)
  • q: [batch, heads, seq, head_dim] = [1,2,2,2]
  • k.transpose(-2,-1): [1,2,head_dim,seq] = [1,2,2,2]
  • torch.matmul 对最后两维做矩阵乘法:[seq, head_dim] · [head_dim, seq] → [seq, seq]

head1 举例

q[0,0,:,:] = [[1,0],  [0,1]] # seq=2, head_dim=2k[0,0,:,:].T = [[0,1], [1,0]] # seq=2, head_dim=2 → 转置 [head_dim, seq]scores[0,0,:,:] = q[0,0,:,:] @ k[0,0,:,:].T = [[0,1],[1,0]]
  • 每个 head 都是独立矩阵运算
  • batch 维度并行
  • heads 维度并行

4️⃣ 为什么这样就对应“每个头相乘”

  • 转置把 heads 放在前面 → [batch, heads, seq, head_dim]
  • torch.matmul 最后两维矩阵乘 → 每个 head 的 Q·K^T 独立计算
  • batch 维并行 → 多个样本一起计算
  • 不会不同 head 或不同 token 混在一起

所以每个 head 的 scores 就是它自己 token 的 Q·K^T,独立的注意力矩阵。


🔹 总结直观理解

  1. qkv.view → 把 Q/K/V 拆开成 [batch, seq, 3, heads, head_dim]
  2. unbind(dim=2) → 拿到 q/k/v [batch, seq, heads, head_dim]
  3. transpose(1,2)[batch, heads, seq, head_dim],heads 放在前面,方便并行
  4. q @ k.transpose(-2,-1) → 每个 head 单独矩阵乘,得到 [batch, heads, seq, seq]
  5. 每个 head 的矩阵都是它自己 token 间的注意力分数,不混头

%%%%%%%%%%%%%%%%%%%%%%%%%%%
补充:本文出现了使用二维矩阵表示高维张量的方法,其实就是树状图,无论转置还是乘积操作都可以用树状图表示,且第三个维度head(用来分成小块矩阵)

  • q[0,0,0,:] = [q_11_1, q_11_2] → token1 head1
  • q[0,0,1,:] = [q_12_1, q_12_2] → token1 head2
  • q[0,1,0,:] = [q_21_1, q_21_2] → token2 head1
  • q[0,1,1,:] = [q_22_1, q_22_2] → token2 head2
    矩阵乘法后也可以用树状图表示。
    重要的是head在第三维度,这样才能是head分块的矩阵进行矩阵乘法

1️⃣ 转置后的 Q/K/V

原始 [batch, seq, heads, head_dim]

q[0,0,0,:] = [q_11_1, q_11_2] → token1 head1q[0,0,1,:] = [q_12_1, q_12_2] → token1 head2q[0,1,0,:] = [q_21_1, q_21_2] → token2 head1q[0,1,1,:] = [q_22_1, q_22_2] → token2 head2

经过 q.transpose(1,2)[batch, heads, seq, head_dim]

q[0,0,0,:] = [q_11_1, q_11_2] → head1 token1q[0,0,1,:] = [q_21_1, q_21_2] → head1 token2q[0,1,0,:] = [q_12_1, q_12_2] → head2 token1q[0,1,1,:] = [q_22_1, q_22_2] → head2 token2

K 同样转置:

k[0,0,0,:] = [k_11_1, k_11_2] → head1 token1k[0,0,1,:] = [k_21_1, k_21_2] → head1 token2k[0,1,0,:] = [k_12_1, k_12_2] → head2 token1k[0,1,1,:] = [k_22_1, k_22_2] → head2 token2

然后K交换最后两个维度代表每个以head分块的2*2矩阵转置,好做内积.
最后相乘,代表每个以head分块的矩阵相乘