| import torch |
| import torch.nn as nn |
| import matplotlib.pyplot as plt |
| import seaborn as sns |
|
|
|
|
| class SelfAttention(nn.Module): |
| def __init__(self, embed_dim, head_dim): |
| super().__init__() |
| self.W_query = nn.Linear(embed_dim, head_dim, bias=False) |
| self.W_key = nn.Linear(embed_dim, head_dim, bias=False) |
| self.W_value = nn.Linear(embed_dim, head_dim, bias=False) |
| self.head_dim = head_dim |
|
|
| def forward(self, x): |
| Q = self.W_query(x) |
| K = self.W_key(x) |
| V = self.W_value(x) |
|
|
| scores = Q @ K.T / self.head_dim**0.5 |
| weights = torch.softmax(scores, dim=-1) |
| output = weights @ V |
| return output, weights |
|
|
|
|
| class CausalSelfAttention(nn.Module): |
| def __init__(self, embed_dim, head_dim, dropout=0.0) -> None: |
| super().__init__() |
| self.W_query = nn.Linear(embed_dim, head_dim, bias=False) |
| self.W_key = nn.Linear(embed_dim, head_dim, bias=False) |
| self.W_value = nn.Linear(embed_dim, head_dim, bias=False) |
| self.head_dim = head_dim |
| self.dropout = nn.Dropout(dropout) |
|
|
| def forward(self, x): |
| seq_len = x.shape[-2] |
| Q = self.W_query(x) |
| K = self.W_key(x) |
| V = self.W_value(x) |
|
|
| |
| scores = Q @ K.transpose(-2, -1) / self.head_dim**0.5 |
|
|
| |
| mask = torch.triu( |
| torch.ones(seq_len, seq_len, device=x.device), diagonal=1 |
| ).bool() |
| scores = scores.masked_fill(mask, float("-inf")) |
|
|
| weights = torch.softmax(scores, dim=-1) |
| weights = self.dropout(weights) |
| output = weights @ V |
| return output, weights |
|
|
|
|
| class MultiHeadAttention(nn.Module): |
| def __init__(self, embed_dim, head_dim, dropout=0.0, num_heads=2) -> None: |
| super().__init__() |
| self.out_proj = nn.Linear(embed_dim, embed_dim) |
| self.heads = nn.ModuleList( |
| [ |
| CausalSelfAttention(embed_dim, head_dim, dropout) |
| for _ in range(num_heads) |
| ] |
| ) |
|
|
| def forward(self, x): |
| return self.out_proj(torch.cat([head(x)[0] for head in self.heads], dim=-1)) |
|
|
|
|
| def plot_attention_heatmap( |
| weights: torch.Tensor, tokens: list[str], output_path: str |
| ) -> None: |
| sns.heatmap( |
| weights.detach().numpy(), |
| annot=True, |
| fmt=".2f", |
| cmap="Blues", |
| xticklabels=tokens, |
| yticklabels=tokens, |
| ) |
| plt.xlabel("Key (attended to)") |
| plt.ylabel("Query (attending)") |
| plt.title("Self-Attention Weights") |
| plt.savefig(output_path) |
| plt.close() |
|
|