sankath commited on
Commit
cd1ec5e
·
verified ·
1 Parent(s): 871c414

Upload architecture.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. architecture.py +22 -69
architecture.py CHANGED
@@ -1,125 +1,78 @@
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
 
1
+ # file: architecture.py
 
 
 
 
 
 
2
  import torch
3
  import torch.nn as nn
4
  import torch.nn.functional as F
5
  import math
6
 
7
 
 
 
 
 
 
 
 
8
  class MultiHeadAttention(nn.Module):
9
+ """Scaled dot-product self-attention with a fused QKV projection."""
10
+
11
  def __init__(self, d_model, n_heads):
12
  super().__init__()
 
 
13
  assert d_model % n_heads == 0, "d_model must be divisible by n_heads"
14
  self.d_model = d_model
15
  self.n_heads = n_heads
16
+ self.d_k = d_model // n_heads
17
 
18
+ # One matmul for Q, K and V is cheaper than three separate projections
 
19
  self.qkv_projection = nn.Linear(d_model, 3 * d_model, bias=False)
 
20
  self.out_projection = nn.Linear(d_model, d_model, bias=False)
21
 
22
  def forward(self, x, is_causal=True):
 
23
  batch_size, seq_len, d_model = x.shape
 
 
24
  qkv = self.qkv_projection(x)
25
+ q, k, v = qkv.chunk(3, dim=-1)
26
 
27
+ # (B, T, d_model) -> (B, heads, T, d_k) so each head runs independently
 
 
28
  q = q.view(batch_size, seq_len, self.n_heads, self.d_k).transpose(1, 2)
29
  k = k.view(batch_size, seq_len, self.n_heads, self.d_k).transpose(1, 2)
30
  v = v.view(batch_size, seq_len, self.n_heads, self.d_k).transpose(1, 2)
31
 
32
+ # Scale by sqrt(d_k) — keeps the scores from blowing up and the softmax stable
 
 
33
  scores = torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(self.d_k)
34
 
 
 
 
 
35
  if is_causal:
36
+ # Block attention to future tokens — required for next-token prediction,
37
+ # otherwise the model would just read the answer.
38
  mask = torch.tril(torch.ones(seq_len, seq_len, device=x.device)).view(1, 1, seq_len, seq_len)
39
  scores = scores.masked_fill(mask == 0, float('-inf'))
40
 
 
41
  attention_weights = F.softmax(scores, dim=-1)
 
 
42
  context = torch.matmul(attention_weights, v)
43
 
44
+ # Merge heads back: (B, heads, T, d_k) -> (B, T, d_model)
 
 
45
  context = context.transpose(1, 2).contiguous().view(batch_size, seq_len, d_model)
 
 
46
  return self.out_projection(context)
47
 
48
 
 
 
 
 
 
 
49
  class FeedForwardNetwork(nn.Module):
50
+ """Per-token MLP: expand to d_ff, non-linearity, project back."""
51
+
52
  def __init__(self, d_model, d_ff, dropout=0.1):
53
  super().__init__()
54
+ self.linear1 = nn.Linear(d_model, d_ff)
 
 
 
 
55
  self.dropout = nn.Dropout(dropout)
56
+ self.linear2 = nn.Linear(d_ff, d_model)
57
 
58
  def forward(self, x):
 
59
  return self.linear2(self.dropout(F.gelu(self.linear1(x))))
60
 
61
 
 
 
 
 
 
 
 
 
62
  class TransformerBlock(nn.Module):
63
+ """One decoder block: attention + feed-forward, pre-norm with residuals."""
64
+
65
  def __init__(self, d_model, n_heads, dropout=0.1):
66
  super().__init__()
67
  self.attention = MultiHeadAttention(d_model, n_heads)
68
+ self.feed_forward = FeedForwardNetwork(d_model, d_ff=4 * d_model, dropout=dropout) # 4x is the usual ratio
69
+ self.ln1 = nn.LayerNorm(d_model)
70
+ self.ln2 = nn.LayerNorm(d_model)
 
71
  self.dropout = nn.Dropout(dropout)
72
 
73
  def forward(self, x, is_causal=True):
74
+ # Pre-norm (LayerNorm before each sub-layer) trains more stably than post-norm.
75
+ # The `x + ...` residuals let gradients flow and each layer refine rather than replace.
76
  x = x + self.dropout(self.attention(self.ln1(x), is_causal=is_causal))
 
77
  x = x + self.dropout(self.feed_forward(self.ln2(x)))
78
  return x