| """ |
| 注意力机制模块 — 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, |
| key: torch.Tensor, |
| value: torch.Tensor, |
| key_padding_mask: Optional[torch.BoolTensor] = None, |
| attn_mask: Optional[torch.Tensor] = None, |
| 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) |
|
|
| |
| |
| |
| 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: |
| |
| 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 |