transformer-lib 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,47 @@
1
+ Metadata-Version: 2.4
2
+ Name: transformer-lib
3
+ Version: 0.1.0
4
+ Summary: Attention mechanisms from Every_Thing_About_Transformer, as an importable library
5
+ Author: Aniket Verma
6
+ License: MIT
7
+ Requires-Python: >=3.9
8
+ Requires-Dist: torch>=2.0
9
+ Provides-Extra: dev
10
+ Requires-Dist: pytest>=7.0; extra == 'dev'
11
+ Provides-Extra: linear
12
+ Requires-Dist: linear-attention-transformer; extra == 'linear'
13
+ Description-Content-Type: text/markdown
14
+
15
+ # transformer-lib
16
+
17
+ Attention mechanisms from Every_Thing_About_Transformer, as an importable Python library.
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ pip install -e .
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ ```python
28
+ from transformer_lib import MultiHeadAttention
29
+ import torch
30
+
31
+ mha = MultiHeadAttention(embed_dim=512, num_heads=8)
32
+ x = torch.randn(2, 10, 512)
33
+ out = mha(x, x, x)
34
+ ```
35
+
36
+ ## Available Modules
37
+
38
+ | Module | Description |
39
+ |---|---|
40
+ | `SelfAttention` | Basic scaled dot-product self-attention |
41
+ | `MultiHeadAttention` | Multi-head attention with separate Q/K/V projections |
42
+ | `CausalAttention` | Multi-head with lower-triangular causal mask |
43
+ | `CrossAttention` | Encoder-decoder cross-attention with self-attention pre-step |
44
+ | `GlobalAttention` | Additive (Bahdanau-style) alignment scoring |
45
+ | `MultiQueryAttention` | Shared K/V across all heads |
46
+ | `GroupedQueryAttention` | Groups of query heads sharing K/V (GQA) |
47
+ | `MultiHeadLatentAttention` | Latent-space compression for keys/values |
@@ -0,0 +1,33 @@
1
+ # transformer-lib
2
+
3
+ Attention mechanisms from Every_Thing_About_Transformer, as an importable Python library.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install -e .
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```python
14
+ from transformer_lib import MultiHeadAttention
15
+ import torch
16
+
17
+ mha = MultiHeadAttention(embed_dim=512, num_heads=8)
18
+ x = torch.randn(2, 10, 512)
19
+ out = mha(x, x, x)
20
+ ```
21
+
22
+ ## Available Modules
23
+
24
+ | Module | Description |
25
+ |---|---|
26
+ | `SelfAttention` | Basic scaled dot-product self-attention |
27
+ | `MultiHeadAttention` | Multi-head attention with separate Q/K/V projections |
28
+ | `CausalAttention` | Multi-head with lower-triangular causal mask |
29
+ | `CrossAttention` | Encoder-decoder cross-attention with self-attention pre-step |
30
+ | `GlobalAttention` | Additive (Bahdanau-style) alignment scoring |
31
+ | `MultiQueryAttention` | Shared K/V across all heads |
32
+ | `GroupedQueryAttention` | Groups of query heads sharing K/V (GQA) |
33
+ | `MultiHeadLatentAttention` | Latent-space compression for keys/values |
@@ -0,0 +1,18 @@
1
+ import torch
2
+ from transformer_lib import MultiHeadAttention
3
+
4
+
5
+ def demo_multi_head_attention(embed_dim=512, num_heads=8, batch_size=2, seq_len=10):
6
+ mha = MultiHeadAttention(embed_dim=embed_dim, num_heads=num_heads)
7
+
8
+ query = torch.randn(batch_size, seq_len, embed_dim)
9
+ key = torch.randn(batch_size, seq_len, embed_dim)
10
+ value = torch.randn(batch_size, seq_len, embed_dim)
11
+
12
+ attn_output = mha(query, key, value)
13
+ print(f"Output shape: {attn_output.shape}")
14
+ return attn_output
15
+
16
+
17
+ if __name__ == "__main__":
18
+ demo_multi_head_attention()
@@ -0,0 +1,22 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "transformer-lib"
7
+ version = "0.1.0"
8
+ description = "Attention mechanisms from Every_Thing_About_Transformer, as an importable library"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = {text = "MIT"}
12
+ authors = [{name = "Aniket Verma"}]
13
+ dependencies = [
14
+ "torch>=2.0",
15
+ ]
16
+
17
+ [project.optional-dependencies]
18
+ linear = ["linear-attention-transformer"]
19
+ dev = ["pytest>=7.0"]
20
+
21
+ [tool.hatch.build.targets.wheel]
22
+ packages = ["src/transformer_lib"]
@@ -0,0 +1,21 @@
1
+ from .attention import (
2
+ SelfAttention,
3
+ MultiHeadAttention,
4
+ CausalAttention,
5
+ CrossAttention,
6
+ GlobalAttention,
7
+ MultiQueryAttention,
8
+ MultiHeadLatentAttention,
9
+ GroupedQueryAttention,
10
+ )
11
+
12
+ __all__ = [
13
+ "SelfAttention",
14
+ "MultiHeadAttention",
15
+ "CausalAttention",
16
+ "CrossAttention",
17
+ "GlobalAttention",
18
+ "MultiQueryAttention",
19
+ "MultiHeadLatentAttention",
20
+ "GroupedQueryAttention",
21
+ ]
@@ -0,0 +1,19 @@
1
+ from .self_attention import SelfAttention
2
+ from .multi_head_attention import MultiHeadAttention
3
+ from .causal_attention import CausalAttention
4
+ from .cross_attention import CrossAttention
5
+ from .global_attention import GlobalAttention
6
+ from .multi_q_attention import MultiQueryAttention
7
+ from .multi_head_latent_attention import MultiHeadLatentAttention
8
+ from .grouped_q_attention import GroupedQueryAttention
9
+
10
+ __all__ = [
11
+ "SelfAttention",
12
+ "MultiHeadAttention",
13
+ "CausalAttention",
14
+ "CrossAttention",
15
+ "GlobalAttention",
16
+ "MultiQueryAttention",
17
+ "MultiHeadLatentAttention",
18
+ "GroupedQueryAttention",
19
+ ]
@@ -0,0 +1,60 @@
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+
5
+ class CausalAttention(nn.Module):
6
+ def __init__(self, embed_dim, num_heads, max_seq_len, dropout=0.1):
7
+ super().__init__()
8
+ assert embed_dim % num_heads == 0, "embed_dim must be divisible by num_heads"
9
+ self.d_k = embed_dim // num_heads
10
+ self.num_heads = num_heads
11
+
12
+ # Linear layers for query, key, and value
13
+ self.query = nn.Linear(embed_dim, embed_dim)
14
+ self.key = nn.Linear(embed_dim, embed_dim)
15
+ self.value = nn.Linear(embed_dim, embed_dim)
16
+ self.out = nn.Linear(embed_dim, embed_dim)
17
+
18
+ self.dropout = nn.Dropout(dropout)
19
+
20
+ # Register a buffer for the causal mask(lower triangular matrix)
21
+ # shape: (1, 1, max_seq_len, max_seq_len)
22
+ self.register_buffer("mask", torch.tril(torch.ones(max_seq_len, max_seq_len)).view(1, 1, max_seq_len, max_seq_len))
23
+
24
+ def forward(self, x):
25
+ batch_size, seq_len, embed_dim = x.size()
26
+
27
+ # Project inputs to query, key, and value
28
+ Q = self.query(x) # (batch_size, seq_len, embed_dim)
29
+ K = self.key(x) # (batch_size, seq_len, embed_dim)
30
+ V = self.value(x) # (batch_size, seq_len, embed_dim)
31
+
32
+ # Reshape for multi-head attention
33
+ Q = Q.view(batch_size, seq_len, self.num_heads, self.d_k).transpose(1, 2) # (batch_size, num_heads, seq_len, d_k)
34
+ K = K.view(batch_size, seq_len, self.num_heads, self.d_k).transpose(1, 2) # (batch_size, num_heads, seq_len, d_k)
35
+ V = V.view(batch_size, seq_len, self.num_heads, self.d_k).transpose(1, 2) # (batch_size, num_heads, seq_len, d_k)
36
+
37
+ # Scaled dot-product attention
38
+ scores = torch.matmul(Q, K.transpose(-2, -1)) / (self.d_k ** 0.5) # (batch_size, num_heads, seq_len, seq_len)
39
+
40
+ # Apply causal mask
41
+ scores = scores.masked_fill(self.mask[:, :, :seq_len, :seq_len] == 0, float('-inf'))
42
+
43
+ # Softmax to get attention weights
44
+ attn_weights = F.softmax(scores, dim=-1) # (batch_size, num_heads, seq_len, seq_len)
45
+ attn_weights = self.dropout(attn_weights)
46
+
47
+ # Compute the output
48
+ output = torch.matmul(attn_weights, V) # (batch_size, num_heads, seq_len, d_k)
49
+
50
+ # Concatenate heads and project to output dimension
51
+ output = output.transpose(1, 2).contiguous().view(batch_size, seq_len, embed_dim) # (batch_size, seq_len, embed_dim)
52
+ output = self.out(output) # (batch_size, seq_len, embed_dim)
53
+
54
+ return output
55
+
56
+
57
+ """The Causal Attention mechanism is a variant of multi-head attention that restricts a model
58
+ from attending to future tokens, ensuring strict autoregressive generation. This is implemented by applying a
59
+ lower-triangular mask to the attention scores before the softmax function, effectively setting future positions to negative
60
+ infinity."""
@@ -0,0 +1,29 @@
1
+ import torch
2
+ import torch.nn as nn
3
+
4
+ class CrossAttention(nn.Module):
5
+ def __init__(self, embed_dim, num_heads):
6
+ super().__init__()
7
+ self.self_attn = nn.MultiheadAttention(embed_dim, num_heads, batch_first=True)
8
+ self.cross_attn = nn.MultiheadAttention(embed_dim, num_heads, batch_first=True)
9
+ self.norm1 = nn.LayerNorm(embed_dim)
10
+ self.norm2 = nn.LayerNorm(embed_dim)
11
+
12
+ def forward(self, target, source, tgt_mask=None, src_mask=None):
13
+ # Self-attention on the target sequence
14
+ attn_output, _ = self.self_attn(target, target, target, attn_mask=tgt_mask)
15
+ target = self.norm1(target + attn_output)
16
+
17
+ # Cross-attention with the souce sequence
18
+ cross_output, _ = self.cross_attn(target, source, source, attn_mask=src_mask)
19
+ output = self.norm2(target + cross_output)
20
+
21
+ return output
22
+
23
+
24
+
25
+ """Cross-attention
26
+
27
+ Allows tokens from one sequence attend to a different sequence, bringing information from different sources or
28
+ modalities together (for example, description and image). It uses queries from one module – usually encoder,
29
+ and keys and values from the other one (decoder)."""
@@ -0,0 +1,45 @@
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+
5
+ class GlobalAttention(nn.Module):
6
+ def __init__(self, encoder_dim, decoder_dim):
7
+ super(GlobalAttention, self).__init__()
8
+ # Linear layers to project encoder and decoder states
9
+ self.encoder_linear = nn.Linear(encoder_dim, decoder_dim)
10
+ self.decoder_linear = nn.Linear(decoder_dim, decoder_dim)
11
+ self.attn_linear = nn.Linear(decoder_dim, 1)
12
+
13
+ def forward(self, encoder_outputs, decoder_hidden):
14
+ """
15
+ encoder_outputs: (batch_size, seq_len, encoder_dim)
16
+ decoder_hidden: (batch_size, decoder_dim)
17
+ """
18
+ # Project encoder outputs
19
+ # energy: (batch_size, seq_len, decoder_dim)
20
+ energy = self.encoder_linear(encoder_outputs)
21
+
22
+ # Project decoder hidden state and expand to match sequence length
23
+ # decoder_hidden_expanded: (batch_size, 1, decoder_dim) -> (batch_size, seq_len, decoder_dim)
24
+ decoder_hidden_expanded = self.decoder_linear(decoder_hidden).unsqueeze(1)
25
+
26
+ # Compute alignment scores
27
+ # attn_energies: (batch_size, seq_len, 1)
28
+ attn_energies = self.attn_linear(torch.tanh(energy + decoder_hidden_expanded))
29
+
30
+ # Softmax to get attention weights
31
+ # attn_weights: (batch_size, seq_len, 1)
32
+ attn_weights = F.softmax(attn_energies, dim=1)
33
+
34
+ # Compute context vector as weighted sum of encoder outputs
35
+ # context: (batch_size, encoder_dim)
36
+ context = torch.bmm(attn_weights.transpose(1, 2), encoder_outputs).squeeze(1)
37
+
38
+ return context, attn_weights
39
+
40
+
41
+ """Global attention
42
+
43
+ It is also a type of sparse attention that allows some tokens to attend to every token in a sequence,
44
+ while others have limited attention. Every token can also attend to these special tokens.
45
+ It is often used with local attention to preserve the entire context handling. """
@@ -0,0 +1,65 @@
1
+ import torch
2
+ import torch.nn as nn
3
+ import math
4
+
5
+ class GroupedQueryAttention(nn.Module):
6
+ def __init__(self, d_model, num_query_heads, num_kv_heads):
7
+ super().__init__()
8
+ assert d_model % num_query_heads == 0, "d_model must be divisible by num_query_heads"
9
+ assert num_query_heads % num_kv_heads == 0, "num_query_heads must be divisible by num_kv_heads"
10
+
11
+ self.d_model = d_model
12
+ self.num_query_heads = num_query_heads
13
+ self.num_kv_heads = num_kv_heads
14
+ self.num_queries_per_kv = num_query_heads // num_kv_heads
15
+ self.head_dim = d_model // num_query_heads
16
+
17
+ # Query projection: standard size
18
+ self.q_proj = nn.Linear(d_model, d_model)
19
+
20
+ # Key/Value projections: reduced size (num_kv_heads * head_dim)
21
+ self.kv_dim = num_kv_heads * self.head_dim
22
+ self.k_proj = nn.Linear(d_model, self.kv_dim)
23
+ self.v_proj = nn.Linear(d_model, self.kv_dim)
24
+
25
+ # Output projection
26
+ self.out_proj = nn.Linear(d_model, d_model)
27
+
28
+ # Scaling factor
29
+ self.scale = math.sqrt(self.head_dim)
30
+
31
+ def forward(self, x, mask=None):
32
+ batch_size, seq_len, _ = x.shape
33
+
34
+ # Projections
35
+ Q = self.q_proj(x) # (batch, seq, d_model)
36
+ K = self.k_proj(x) # (batch, seq, kv_dim)
37
+ V = self.v_proj(x) # (batch, seq, kv_dim)
38
+
39
+ # Reshape for multi-head attention
40
+ # Q: (batch, num_query_heads, seq, head_dim)
41
+ Q = Q.view(batch_size, seq_len, self.num_query_heads, self.head_dim).transpose(1, 2)
42
+
43
+ # K/V: (batch, num_kv_heads, seq, head_dim)
44
+ K = K.view(batch_size, seq_len, self.num_kv_heads, self.head_dim).transpose(1, 2)
45
+ V = V.view(batch_size, seq_len, self.num_kv_heads, self.head_dim).transpose(1, 2)
46
+
47
+ # Expand K and V to match number of query heads
48
+ # Each KV head is shared by num_queries_per_kv query heads
49
+ K = K.repeat_interleave(self.num_queries_per_kv, dim=1)
50
+ V = V.repeat_interleave(self.num_queries_per_kv, dim=1)
51
+
52
+ # Attention scores
53
+ scores = torch.matmul(Q, K.transpose(-2, -1)) / self.scale
54
+
55
+ if mask is not None:
56
+ scores = scores.masked_fill(mask == 0, float('-inf'))
57
+
58
+ attn_weights = torch.softmax(scores, dim=-1)
59
+ attn_output = torch.matmul(attn_weights, V)
60
+
61
+ # Concatenate heads
62
+ attn_output = attn_output.transpose(1, 2).contiguous()
63
+ attn_output = attn_output.view(batch_size, seq_len, self.d_model)
64
+
65
+ return self.out_proj(attn_output)
@@ -0,0 +1,33 @@
1
+ import torch
2
+ from linear_attention_transformer import LinearAttentionTransformerLM
3
+
4
+
5
+ def linear_attention_example(num_tokens=20000, dim=512, heads=8, max_seq_len=8192, device=None):
6
+ if device is None:
7
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
8
+
9
+ model = LinearAttentionTransformerLM(
10
+ num_tokens=num_tokens,
11
+ dim=dim,
12
+ heads=heads,
13
+ depth=1,
14
+ max_seq_len=max_seq_len,
15
+ causal=True,
16
+ ).to(device)
17
+
18
+ x = torch.randint(0, num_tokens, (1, max_seq_len), device=device)
19
+ output = model(x)
20
+ return output
21
+
22
+
23
+ if __name__ == "__main__":
24
+ linear_attention_example()
25
+
26
+
27
+ """$ pip install linear-attention-transformer
28
+
29
+ Linear attention is a faster version of self-attention that avoids comparing
30
+ all token pairs. By restructuring the computation with kernel functions
31
+ (kernel functions are a way to compute similarity between vectors as if they were
32
+ transformed into another space, without actually performing that transformation),
33
+ it reduces complexity from O(N2) to O(N), making it much more efficient for long sequences."""
@@ -0,0 +1,46 @@
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+
5
+ class MultiHeadAttention(nn.Module):
6
+ def __init__(self, embed_dim, num_heads):
7
+ super(MultiHeadAttention, self).__init__()
8
+ assert embed_dim % num_heads == 0, "Embedding dimension must be divisible by number of heads"
9
+ self.embed_dim = embed_dim
10
+ self.num_heads = num_heads
11
+ self.head_dim = embed_dim // num_heads
12
+
13
+ # Linear Layers for query, key, and value
14
+ self.query_linear = nn.Linear(embed_dim, embed_dim)
15
+ self.key_linear = nn.Linear(embed_dim, embed_dim)
16
+ self.value_linear = nn.Linear(embed_dim, embed_dim)
17
+
18
+ # output projection Layer
19
+ self.out_linear = nn.Linear(embed_dim, embed_dim)
20
+
21
+ def forward(self, query, key, value, mask=None):
22
+ batch_size = query.size(0)
23
+
24
+ # project and reshape to (batch_size, num_heads, seq_length, head_dim)
25
+ query = self.query_linear(query).view(batch_size, -1, self.num_heads, self.head_dim).transpose(1, 2)
26
+ key = self.key_linear(key).view(batch_size, -1, self.num_heads, self.head_dim).transpose(1, 2)
27
+ value = self.value_linear(value).view(batch_size, -1, self.num_heads, self.head_dim).transpose(1, 2)
28
+
29
+ # Scaled Dot-Product Attention
30
+ scores = torch.matmul(query, key.transpose(-2, -1)) / (self.head_dim ** 0.5)
31
+ if mask is not None:
32
+ scores = scores.masked_fill(mask == 0, float('-inf'))
33
+ attn_weights = F.softmax(scores, dim=-1)
34
+ attn_output = torch.matmul(attn_weights, value)
35
+
36
+ # Concatenate heads and project
37
+ attn_output = attn_output.transpose(1, 2).contiguous().view(batch_size, -1, self.num_heads * self.head_dim)
38
+ return self.out_linear(attn_output)
39
+
40
+
41
+
42
+
43
+ """Multi-Head Attention (MHA)
44
+ Splits attention into multiple heads (one independent attention mechanism), so the model can learn different interaction patterns
45
+ in parallel. It became the defining feature of Transformers and LLMs, and it still remains the baseline for novel attention
46
+ variants"""
@@ -0,0 +1,48 @@
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+
5
+ class MultiHeadLatentAttention(nn.Module):
6
+ def __init__(self, d_model, num_heads, q_latent_dim, kv_latent_dim):
7
+ super().__init__()
8
+ self.num_heads = num_heads
9
+ self.head_dim = d_model // num_heads
10
+
11
+ # Query compression and decomposition
12
+ self.w_q_d = nn.Linear(d_model, q_latent_dim, bias=False)
13
+ self.w_qk = nn.Linear(q_latent_dim, num_heads * kv_latent_dim, bias=False)
14
+
15
+ # Key/Value compression and decomposition
16
+ self.w_kv_d = nn.Linear(d_model, kv_latent_dim, bias=False)
17
+ self.w_v_u = nn.Linear(kv_latent_dim, num_heads * self.head_dim, bias=False)
18
+
19
+ # Output projection
20
+ self.w_o = nn.Linear(num_heads * self.head_dim, d_model)
21
+
22
+ def forward(self, x):
23
+ batch_size, seq_len, d_model = x.shape
24
+
25
+ # 1. Project to latent space
26
+ c_q = self.w_q_d(x) # [B, S, q_latent_dim]
27
+ c_kv = self.w_kv_d(x) # [B, S, kv_latent_dim]
28
+
29
+ # 2. Compute attention scores via latent multiplication
30
+ # Reshape for multi-head: [B, S, num_heads, kv_latent_dim]
31
+ c_q_w_qk = self.w_qk(c_q).view(batch_size, seq_len, self.num_heads, -1)
32
+
33
+ # Scores: [B, num_heads, S, S]
34
+ scores = torch.matmul(
35
+ c_q_w_qk.transpose(1, 2),
36
+ c_kv.transpose(-2, -1)[:, None, ...]
37
+ ) / (self.head_dim ** 0.5)
38
+
39
+ attn_weights = torch.softmax(scores, dim=-1)
40
+
41
+ # 3. Restore Values and compute output
42
+ v = self.w_v_u(c_kv).view(batch_size, seq_len, self.num_heads, -1)
43
+
44
+ # [B, num_heads, S, head_dim]
45
+ output = torch.matmul(attn_weights, v.transpose(1, 2)).transpose(1, 2).contiguous()
46
+
47
+ # 4. Final projection
48
+ return self.w_o(output.view(batch_size, seq_len, -1))
@@ -0,0 +1,49 @@
1
+ import torch
2
+ import torch.nn as nn
3
+ import math
4
+
5
+ class MultiQueryAttention(nn.Module):
6
+ def __init__(self, embedding_dim, num_heads):
7
+ super().__init__()
8
+ assert embedding_dim % num_heads == 0, "embedding_dim must be divisible by num_heads"
9
+
10
+ self.embedding_dim = embedding_dim
11
+ self.num_heads = num_heads
12
+ self.head_dim = embedding_dim // num_heads
13
+
14
+ # Each head has its own Query projection
15
+ self.W_q = nn.Linear(embedding_dim, embedding_dim, bias=False)
16
+
17
+ # Single shared Key and Value projections for all heads
18
+ self.W_k = nn.Linear(embedding_dim, self.head_dim, bias=False)
19
+ self.W_v = nn.Linear(embedding_dim, self.head_dim, bias=False)
20
+
21
+ # Final output projection
22
+ self.W_o = nn.Linear(embedding_dim, embedding_dim, bias=False)
23
+
24
+ def forward(self, x):
25
+ batch_size, seq_len, _ = x.size()
26
+
27
+ # Compute Q, K, V
28
+ Q = self.W_q(x) # [B, S, E]
29
+ K = self.W_k(x) # [B, S, H]
30
+ V = self.W_v(x) # [B, S, H]
31
+
32
+ # Split Q into multiple heads: [B, S, E] -> [B, H, S, H]
33
+ Q = Q.view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2)
34
+
35
+ # Broadcast single K and V across all heads: [B, S, H] -> [B, 1, S, H]
36
+ K = K.unsqueeze(1)
37
+ V = V.unsqueeze(1)
38
+
39
+ # Scaled Dot-Product Attention
40
+ scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.head_dim)
41
+ attn_weights = torch.softmax(scores, dim=-1)
42
+
43
+ # Apply attention weights to V
44
+ context = torch.matmul(attn_weights, V) # [B, H, S, H]
45
+
46
+ # Merge heads: [B, H, S, H] -> [B, S, E]
47
+ context = context.transpose(1, 2).contiguous().view(batch_size, seq_len, self.embedding_dim)
48
+
49
+ return self.W_o(context)
@@ -0,0 +1,35 @@
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+
5
+ class SelfAttention(nn.Module):
6
+ def __init__(self, input_dim):
7
+ super().__init__()
8
+ self.input_dim = input_dim
9
+ self.query = nn.Linear(input_dim, input_dim)
10
+ self.key = nn.Linear(input_dim, input_dim)
11
+ self.value = nn.Linear(input_dim, input_dim)
12
+ self.softmax = nn.Softmax(dim=-1)
13
+
14
+ def forward(self, x, mask=None):
15
+ # x shape: (batch_size, seq_len, input_dim)
16
+ Q = self.query(x)
17
+ K = self.key(x)
18
+ V = self.value(x)
19
+
20
+ # Scaled dot-product attention
21
+ scores = torch.matmul(Q, K.transpose(-2, -1)) / (self.input_dim ** 0.5)
22
+
23
+ if mask is not None:
24
+ scores = scores.masked_fill(mask == 0, float('-inf'))
25
+
26
+ attention_weights = self.softmax(scores)
27
+ output = torch.matmul(attention_weights, V)
28
+ return output, attention_weights
29
+
30
+
31
+
32
+ """Self-attention:
33
+ Let's each token attend directly to other tokens in same sequence: the model compares every token with
34
+ the rest of the sequence and build a context-aware representation. This is what allows transformers to
35
+ capture relationships accross distanceand make it possible to modellong-range dependencies without recurrence."""
@@ -0,0 +1,101 @@
1
+ import torch
2
+ import pytest
3
+ from transformer_lib import (
4
+ SelfAttention,
5
+ MultiHeadAttention,
6
+ CausalAttention,
7
+ CrossAttention,
8
+ GlobalAttention,
9
+ MultiQueryAttention,
10
+ MultiHeadLatentAttention,
11
+ GroupedQueryAttention,
12
+ )
13
+
14
+
15
+ class TestSelfAttention:
16
+ def test_output_shape(self):
17
+ model = SelfAttention(input_dim=64)
18
+ x = torch.randn(2, 10, 64)
19
+ output, weights = model(x)
20
+ assert output.shape == (2, 10, 64)
21
+ assert weights.shape == (2, 10, 10)
22
+
23
+ def test_with_mask(self):
24
+ model = SelfAttention(input_dim=64)
25
+ x = torch.randn(2, 10, 64)
26
+ mask = torch.ones(2, 10, 10)
27
+ output, weights = model(x, mask=mask)
28
+ assert output.shape == (2, 10, 64)
29
+
30
+
31
+ class TestMultiHeadAttention:
32
+ def test_output_shape(self):
33
+ model = MultiHeadAttention(embed_dim=512, num_heads=8)
34
+ x = torch.randn(2, 10, 512)
35
+ out = model(x, x, x)
36
+ assert out.shape == (2, 10, 512)
37
+
38
+ def test_mask_applied(self):
39
+ model = MultiHeadAttention(embed_dim=64, num_heads=4)
40
+ x = torch.randn(2, 10, 64)
41
+ mask = torch.ones(2, 1, 10, 10)
42
+ out = model(x, x, x, mask=mask)
43
+ assert out.shape == (2, 10, 64)
44
+
45
+
46
+ class TestCausalAttention:
47
+ def test_output_shape(self):
48
+ model = CausalAttention(embed_dim=512, num_heads=8, max_seq_len=100)
49
+ x = torch.randn(2, 10, 512)
50
+ out = model(x)
51
+ assert out.shape == (2, 10, 512)
52
+
53
+
54
+ class TestCrossAttention:
55
+ def test_output_shape(self):
56
+ model = CrossAttention(embed_dim=512, num_heads=8)
57
+ target = torch.randn(2, 10, 512)
58
+ source = torch.randn(2, 20, 512)
59
+ out = model(target, source)
60
+ assert out.shape == (2, 10, 512)
61
+
62
+
63
+ class TestGlobalAttention:
64
+ def test_output_shape(self):
65
+ model = GlobalAttention(encoder_dim=256, decoder_dim=512)
66
+ encoder_outputs = torch.randn(2, 15, 256)
67
+ decoder_hidden = torch.randn(2, 512)
68
+ context, weights = model(encoder_outputs, decoder_hidden)
69
+ assert context.shape == (2, 256)
70
+ assert weights.shape == (2, 15, 1)
71
+
72
+
73
+ class TestMultiQueryAttention:
74
+ def test_output_shape(self):
75
+ model = MultiQueryAttention(embedding_dim=512, num_heads=8)
76
+ x = torch.randn(2, 10, 512)
77
+ out = model(x)
78
+ assert out.shape == (2, 10, 512)
79
+
80
+
81
+ class TestMultiHeadLatentAttention:
82
+ def test_output_shape(self):
83
+ model = MultiHeadLatentAttention(d_model=512, num_heads=8, q_latent_dim=128, kv_latent_dim=64)
84
+ x = torch.randn(2, 10, 512)
85
+ out = model(x)
86
+ assert out.shape == (2, 10, 512)
87
+
88
+
89
+ class TestGroupedQueryAttention:
90
+ def test_output_shape(self):
91
+ model = GroupedQueryAttention(d_model=512, num_query_heads=8, num_kv_heads=2)
92
+ x = torch.randn(2, 10, 512)
93
+ out = model(x)
94
+ assert out.shape == (2, 10, 512)
95
+
96
+ def test_with_mask(self):
97
+ model = GroupedQueryAttention(d_model=512, num_query_heads=8, num_kv_heads=2)
98
+ x = torch.randn(2, 10, 512)
99
+ mask = torch.ones(2, 1, 10, 10)
100
+ out = model(x, mask=mask)
101
+ assert out.shape == (2, 10, 512)