You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在PyTorch的MultiHeadAttention层中获取值投影

解决PyTorch MultiheadAttention值投影获取问题,实现Graphormer边偏置自注意力

你在实现Graphormer架构时,需要在自注意力的注意力权重中加入边偏置,再与值投影后的张量做矩阵乘法,但PyTorch原生的nn.MultiheadAttention没有直接暴露值投影模块,这里提供几种可行的实现方式:

方法1:直接拆分原生MultiheadAttention的投影参数

PyTorch的nn.MultiheadAttention的in_proj_weight是query、key、value三个投影层的权重拼接而成(形状为[3*C, C]),in_proj_bias同理(形状为[3*C])。我们可以拆分出value对应的权重和偏置,手动完成值投影:

import torch as th
from torch import nn

# 变量初始化
B, T, C, H = 2, 3, 4, 2
self_attn = nn.MultiheadAttention(C, H, batch_first=True)

# 张量定义
x = th.randn(B, T, C)
attn_bias = th.ones((B, T, T))

# 1. 拆分q、k、v投影的权重和偏置
query_weight = self_attn.in_proj_weight[:C, :]
key_weight = self_attn.in_proj_weight[C:2*C, :]
value_weight = self_attn.in_proj_weight[-C:, :]

query_bias = self_attn.in_proj_bias[:C] if self_attn.in_proj_bias is not None else None
key_bias = self_attn.in_proj_bias[C:2*C] if self_attn.in_proj_bias is not None else None
value_bias = self_attn.in_proj_bias[-C:] if self_attn.in_proj_bias is not None else None

# 2. 手动完成q、k、v投影
q = th.matmul(x, query_weight.T)
k = th.matmul(x, key_weight.T)
v = th.matmul(x, value_weight.T)

if query_bias is not None:
    q = q + query_bias
if key_bias is not None:
    k = k + key_bias
if value_bias is not None:
    v = v + value_bias

# 3. 计算未归一化的注意力分数,添加边偏置后做softmax
attn_scores = th.matmul(q, k.transpose(-2, -1)) / th.sqrt(C / H)
if attn_bias is not None:
    attn_scores = attn_scores + attn_bias
attn_wei = th.softmax(attn_scores, dim=-1)

# 4. 注意力权重乘投影后的value得到最终输出
x = attn_wei @ v

print(x)

方法2:完全手动实现query/key/value投影

这种方式完全脱离原生nn.MultiheadAttention的内部逻辑,自己定义投影层,灵活性更高:

import torch as th
from torch import nn

# 变量初始化
B, T, C, H = 2, 3, 4, 2
head_dim = C // H

# 自定义投影层
q_proj = nn.Linear(C, C)
k_proj = nn.Linear(C, C)
v_proj = nn.Linear(C, C)
out_proj = nn.Linear(C, C)  # 对应原生MultiheadAttention的输出投影

# 张量定义
x = th.randn(B, T, C)
attn_bias = th.ones((B, T, T))

# 1. 完成q、k、v投影并适配多头维度
q = q_proj(x).view(B, T, H, head_dim).transpose(1, 2)  # [B, H, T, head_dim]
k = k_proj(x).view(B, T, H, head_dim).transpose(1, 2)
v = v_proj(x).view(B, T, H, head_dim).transpose(1, 2)

# 2. 计算注意力分数并添加边偏置
attn_scores = th.matmul(q, k.transpose(-2, -1)) / th.sqrt(head_dim)
# 扩展边偏置到多头维度
attn_scores = attn_scores + attn_bias.unsqueeze(1)
attn_wei = th.softmax(attn_scores, dim=-1)

# 3. 计算注意力输出、合并多头并做输出投影
attn_output = th.matmul(attn_wei, v).transpose(1, 2).contiguous().view(B, T, C)
x = out_proj(attn_output)

print(x)

方法3:自定义MultiheadAttention类,暴露投影层

如果需要复用类似逻辑,可以自定义一个继承自nn.Module的MultiheadAttention类,把q/k/v投影层作为类属性暴露:

import torch as th
from torch import nn

class CustomMultiheadAttention(nn.Module):
    def __init__(self, embed_dim, num_heads, batch_first=True):
        super().__init__()
        self.embed_dim = embed_dim
        self.num_heads = num_heads
        self.head_dim = embed_dim // num_heads
        self.batch_first = batch_first
        
        # 暴露投影层
        self.q_proj = nn.Linear(embed_dim, embed_dim)
        self.k_proj = nn.Linear(embed_dim, embed_dim)
        self.v_proj = nn.Linear(embed_dim, embed_dim)
        self.out_proj = nn.Linear(embed_dim, embed_dim)
    
    def forward(self, query, key, value, attn_bias=None):
        if self.batch_first:
            query, key, value = query.transpose(0,1), key.transpose(0,1), value.transpose(0,1)
        
        # 投影
        q = self.q_proj(query).view(-1, self.num_heads, self.head_dim).transpose(0,1)
        k = self.k_proj(key).view(-1, self.num_heads, self.head_dim).transpose(0,1)
        v = self.v_proj(value).view(-1, self.num_heads, self.head_dim).transpose(0,1)
        
        # 计算注意力分数并添加偏置
        attn_scores = th.matmul(q, k.transpose(-2, -1)) / th.sqrt(self.head_dim)
        if attn_bias is not None:
            attn_scores = attn_scores + attn_bias.transpose(0,1).unsqueeze(0) if self.batch_first else attn_bias.unsqueeze(0)
        
        attn_wei = th.softmax(attn_scores, dim=-1)
        attn_output = th.matmul(attn_wei, v)
        
        # 合并多头并投影输出
        attn_output = attn_output.transpose(0,1).contiguous().view(-1, self.embed_dim)
        if self.batch_first:
            attn_output = attn_output.transpose(0,1)
        
        return self.out_proj(attn_output), attn_wei, v

# 使用示例
B, T, C, H = 2, 3, 4, 2
custom_attn = CustomMultiheadAttention(C, H, batch_first=True)
x = th.randn(B, T, C)
attn_bias = th.ones((B, T, T))

# 直接获取输出、注意力权重、投影后的value
output, attn_wei, v = custom_attn(x, x, x, attn_bias=attn_bias)
print(output)

关键注意点

  • 原代码直接对softmax后的attn_wei加偏置不合理,应该在未做softmax的注意力分数上添加边偏置,再进行softmax归一化,否则会破坏注意力权重的概率分布。
  • 多头注意力中,边偏置需要扩展到多头维度,保证每个头都能应用对应的偏置。

内容的提问来源于stack exchange,提问作者Angelo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 03:31:37