lijn14
完成C部分内容
d572bbd
Raw
History Blame Contribute Delete
7.23 kB
"""
注意力机制模块 — Person B 负责实现
包含:
1. MultiHeadAttention: 标准多头注意力 (支持 RoPE)
2. FlashMultiHeadAttention: Flash Attention 2 加速版本
技术要点:
- Scaled Dot-Product Attention
- 支持 key_padding_mask 和 attn_mask
- Flash Attention 2 使用 torch.nn.functional.scaled_dot_product_attention
- RoPE 旋转位置编码集成
"""
from __future__ import annotations
import math
from typing import Optional
import torch
import torch.nn as nn
import torch.nn.functional as F
class MultiHeadAttention(nn.Module):
"""
标准多头注意力机制。
"""
def __init__(
self,
d_model: int = 512,
nhead: int = 8,
dropout: float = 0.1,
use_rotary_embedding: bool = False,
):
super().__init__()
assert d_model % nhead == 0, "d_model 必须能被 nhead 整除"
self.d_model = d_model
self.nhead = nhead
self.d_k = d_model // nhead
self.use_rotary_embedding = use_rotary_embedding
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)
self.dropout = nn.Dropout(p=dropout)
self.rope: Optional[nn.Module] = None
def forward(
self,
query: torch.Tensor, # [B, L_q, D]
key: torch.Tensor, # [B, L_k, D]
value: torch.Tensor, # [B, L_v, D]
key_padding_mask: Optional[torch.BoolTensor] = None, # [B, L_k]
attn_mask: Optional[torch.Tensor] = None, # [L_q, L_k]
is_causal: bool = False,
) -> torch.Tensor:
B, L_q, _ = query.size()
L_k = key.size(1)
L_v = value.size(1)
Q = self.q_proj(query)
K = self.k_proj(key)
V = self.v_proj(value)
Q = Q.view(B, L_q, self.nhead, self.d_k).transpose(1, 2)
K = K.view(B, L_k, self.nhead, self.d_k).transpose(1, 2)
V = V.view(B, L_v, self.nhead, self.d_k).transpose(1, 2)
if self.use_rotary_embedding and self.rope is not None:
Q, K = self.rope.apply_rotary_pos_emb(Q, K)
scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)
if key_padding_mask is not None:
scores = scores.masked_fill(
key_padding_mask.unsqueeze(1).unsqueeze(2), float("-inf")
)
if is_causal:
L_q_local, L_k_local = scores.size(-2), scores.size(-1)
causal_mask = torch.triu(
torch.ones(L_q_local, L_k_local, device=scores.device), diagonal=1
).bool()
scores = scores.masked_fill(
causal_mask.unsqueeze(0).unsqueeze(0), float("-inf")
)
if attn_mask is not None:
scores = scores.masked_fill(attn_mask.unsqueeze(0).unsqueeze(0), float("-inf"))
attn_weights = F.softmax(scores, dim=-1)
attn_weights = self.dropout(attn_weights)
attn_output = torch.matmul(attn_weights, V)
attn_output = attn_output.transpose(1, 2).contiguous().view(B, L_q, self.d_model)
output = self.out_proj(attn_output)
return output
try:
from torch.nn.functional import scaled_dot_product_attention
_has_flash_attn = True
except ImportError:
_has_flash_attn = False
class FlashMultiHeadAttention(nn.Module):
"""
Flash Attention 2 加速的多头注意力。
如果 PyTorch < 2.0 或 GPU 不支持,会自动回退到标准注意力。
"""
def __init__(
self,
d_model: int = 512,
nhead: int = 8,
dropout: float = 0.1,
use_rotary_embedding: bool = False,
):
super().__init__()
if not _has_flash_attn:
self._fallback = MultiHeadAttention(
d_model=d_model,
nhead=nhead,
dropout=dropout,
use_rotary_embedding=use_rotary_embedding,
)
self.d_model = d_model
self.nhead = nhead
self.d_k = d_model // nhead
self.use_rotary_embedding = use_rotary_embedding
self.rope: Optional[nn.Module] = None
return
assert d_model % nhead == 0, "d_model 必须能被 nhead 整除"
self.d_model = d_model
self.nhead = nhead
self.d_k = d_model // nhead
self.use_rotary_embedding = use_rotary_embedding
self.dropout_p = dropout
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)
self.rope: Optional[nn.Module] = None
def forward(
self,
query: torch.Tensor,
key: torch.Tensor,
value: torch.Tensor,
key_padding_mask: Optional[torch.BoolTensor] = None,
is_causal: bool = False,
) -> torch.Tensor:
if not _has_flash_attn:
return self._fallback(query, key, value, key_padding_mask, None, is_causal)
B, L_q, _ = query.size()
L_k = key.size(1)
Q = self.q_proj(query)
K = self.k_proj(key)
V = self.v_proj(value)
Q = Q.view(B, L_q, self.nhead, self.d_k).transpose(1, 2)
K = K.view(B, L_k, self.nhead, self.d_k).transpose(1, 2)
V = V.view(B, L_k, self.nhead, self.d_k).transpose(1, 2)
if self.use_rotary_embedding and self.rope is not None:
Q, K = self.rope.apply_rotary_pos_emb(Q, K)
# Build an additive attention bias so we can combine causal mask and
# key_padding_mask without conflicting with the is_causal flag.
# F.scaled_dot_product_attention raises if both is_causal and attn_mask are set.
if is_causal or key_padding_mask is not None:
attn_bias = torch.zeros(B, 1, L_q, L_k, device=Q.device, dtype=Q.dtype)
if is_causal:
causal = torch.triu(
torch.ones(L_q, L_k, device=Q.device, dtype=torch.bool), diagonal=1
)
attn_bias = attn_bias.masked_fill(causal.unsqueeze(0).unsqueeze(0), float("-inf"))
if key_padding_mask is not None:
# key_padding_mask: [B, L_k] bool, True = pad
attn_bias = attn_bias.masked_fill(
key_padding_mask.unsqueeze(1).unsqueeze(2), float("-inf")
)
attn_output = F.scaled_dot_product_attention(
Q, K, V,
attn_mask=attn_bias,
dropout_p=self.dropout_p if self.training else 0.0,
is_causal=False,
scale=1.0 / math.sqrt(self.d_k),
)
else:
attn_output = F.scaled_dot_product_attention(
Q, K, V,
attn_mask=None,
dropout_p=self.dropout_p if self.training else 0.0,
is_causal=False,
scale=1.0 / math.sqrt(self.d_k),
)
attn_output = attn_output.transpose(1, 2).contiguous().view(B, L_q, self.d_model)
output = self.out_proj(attn_output)
return output