sankath commited on
Commit
1a2dfe2
·
verified ·
1 Parent(s): 1dacc2a

Upload architecture.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. architecture.py +125 -0
architecture.py ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # architecture.py
2
+ # ============================================================================
3
+ # The Transformer building blocks. A Transformer block = self-attention (tokens
4
+ # share information) + a feed-forward network (each token is processed on its
5
+ # own), each wrapped in a residual connection and LayerNorm. These three classes
6
+ # are stacked by LogSentryLM in train_and_generate.py.
7
+ # ============================================================================
8
+ import torch
9
+ import torch.nn as nn
10
+ import torch.nn.functional as F
11
+ import math
12
+
13
+
14
+ # ============================================================================
15
+ # MULTI-HEAD SELF-ATTENTION
16
+ # Lets each token look at other tokens and pull in the information it needs.
17
+ # Each token forms a Query ("what am I looking for?"), a Key ("what do I
18
+ # offer?"), and a Value ("what I'll pass on"). A token attends to others by
19
+ # matching its Query against their Keys, then takes a weighted sum of Values.
20
+ # ============================================================================
21
+ class MultiHeadAttention(nn.Module):
22
+ def __init__(self, d_model, n_heads):
23
+ super().__init__()
24
+ # d_model must split evenly across the heads, since each head works on a
25
+ # d_model/n_heads slice of the vector.
26
+ assert d_model % n_heads == 0, "d_model must be divisible by n_heads"
27
+ self.d_model = d_model
28
+ self.n_heads = n_heads
29
+ self.d_k = d_model // n_heads # dimension PER head
30
+
31
+ # One linear layer produces Q, K and V at once (hence 3 * d_model), then
32
+ # we split the result. Doing it in a single matmul is faster than three.
33
+ self.qkv_projection = nn.Linear(d_model, 3 * d_model, bias=False)
34
+ # Mixes the heads' outputs back together into one d_model vector.
35
+ self.out_projection = nn.Linear(d_model, d_model, bias=False)
36
+
37
+ def forward(self, x, is_causal=True):
38
+ # x: (batch_size, seq_len, d_model)
39
+ batch_size, seq_len, d_model = x.shape
40
+
41
+ # Project to Q,K,V combined, then split along the last dim into three.
42
+ qkv = self.qkv_projection(x)
43
+ q, k, v = qkv.chunk(3, dim=-1) # each: (batch, seq_len, d_model)
44
+
45
+ # Reshape each into separate heads and move the head axis forward so the
46
+ # math runs independently per head:
47
+ # (batch, seq_len, d_model) -> (batch, n_heads, seq_len, d_k)
48
+ q = q.view(batch_size, seq_len, self.n_heads, self.d_k).transpose(1, 2)
49
+ k = k.view(batch_size, seq_len, self.n_heads, self.d_k).transpose(1, 2)
50
+ v = v.view(batch_size, seq_len, self.n_heads, self.d_k).transpose(1, 2)
51
+
52
+ # Attention scores = how much each token should attend to each other
53
+ # token = Q . K^T. Dividing by sqrt(d_k) keeps the numbers from getting
54
+ # too large (which would make softmax too "peaky" and hurt gradients).
55
+ scores = torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(self.d_k)
56
+
57
+ # CAUSAL MASK: for next-token prediction, a token may only attend to
58
+ # itself and EARLIER tokens — never the future (that would be cheating).
59
+ # tril keeps the lower triangle; the rest is set to -inf so that after
60
+ # softmax those positions get weight ~0.
61
+ if is_causal:
62
+ mask = torch.tril(torch.ones(seq_len, seq_len, device=x.device)).view(1, 1, seq_len, seq_len)
63
+ scores = scores.masked_fill(mask == 0, float('-inf'))
64
+
65
+ # Turn scores into attention weights that sum to 1 across the keys.
66
+ attention_weights = F.softmax(scores, dim=-1)
67
+
68
+ # Weighted sum of Values = the information each token gathers.
69
+ context = torch.matmul(attention_weights, v)
70
+
71
+ # Merge the heads back together: (batch, n_heads, seq_len, d_k) ->
72
+ # (batch, seq_len, d_model). .contiguous() makes memory layout valid for
73
+ # the following view().
74
+ context = context.transpose(1, 2).contiguous().view(batch_size, seq_len, d_model)
75
+
76
+ # Final linear mix across the combined heads.
77
+ return self.out_projection(context)
78
+
79
+
80
+ # ============================================================================
81
+ # FEED-FORWARD NETWORK
82
+ # After attention shares information ACROSS tokens, this processes EACH token
83
+ # independently. It expands to a wider hidden size, applies a non-linearity,
84
+ # then projects back — giving the model room to compute richer features.
85
+ # ============================================================================
86
+ class FeedForwardNetwork(nn.Module):
87
+ def __init__(self, d_model, d_ff, dropout=0.1):
88
+ super().__init__()
89
+ # d_ff is the wider inner dimension. Convention is d_ff = 4 * d_model
90
+ # (set by the caller), giving the network extra capacity to "think".
91
+ self.linear1 = nn.Linear(d_model, d_ff) # expand: d_model -> d_ff
92
+ # Dropout randomly zeros some activations during TRAINING only, which
93
+ # reduces overfitting. dropout=0.1 means 10% are dropped.
94
+ self.dropout = nn.Dropout(dropout)
95
+ self.linear2 = nn.Linear(d_ff, d_model) # project: d_ff -> d_model
96
+
97
+ def forward(self, x):
98
+ # GELU is a smooth activation function (a standard choice in Transformers).
99
+ return self.linear2(self.dropout(F.gelu(self.linear1(x))))
100
+
101
+
102
+ # ============================================================================
103
+ # TRANSFORMER BLOCK
104
+ # Combines attention + feed-forward with two key tricks:
105
+ # * Residual connections (x + sublayer(x)): help gradients flow and let each
106
+ # layer make small refinements instead of rebuilding from scratch.
107
+ # * Pre-norm (LayerNorm BEFORE each sublayer): the modern GPT-2 style; trains
108
+ # more stably than the original "post-norm" design.
109
+ # ============================================================================
110
+ class TransformerBlock(nn.Module):
111
+ def __init__(self, d_model, n_heads, dropout=0.1):
112
+ super().__init__()
113
+ self.attention = MultiHeadAttention(d_model, n_heads)
114
+ # d_ff = 4 * d_model is the standard feed-forward expansion ratio.
115
+ self.feed_forward = FeedForwardNetwork(d_model, d_ff=4 * d_model, dropout=dropout)
116
+ self.ln1 = nn.LayerNorm(d_model) # normalizes input to the attention
117
+ self.ln2 = nn.LayerNorm(d_model) # normalizes input to the feed-forward
118
+ self.dropout = nn.Dropout(dropout)
119
+
120
+ def forward(self, x, is_causal=True):
121
+ # Sub-layer 1: normalize -> attention -> dropout -> ADD back to input.
122
+ x = x + self.dropout(self.attention(self.ln1(x), is_causal=is_causal))
123
+ # Sub-layer 2: normalize -> feed-forward -> dropout -> ADD back to input.
124
+ x = x + self.dropout(self.feed_forward(self.ln2(x)))
125
+ return x