THE CODE READER / PYTHON
Download raw .py ↓torch_core.py
Your snippet, in context. Explore the file or visualize its recorded example.
1"""Differentiable counterparts and a small encoder-decoder transformer.
2
3Install numpy and torch. Run: python torch_core.py
4No nn.Transformer wrapper: the attention and block operations are explicit.
5"""
6from __future__ import annotations
7
8import math
9import numpy as np
10import torch
11from torch import Tensor, nn
12from torch.nn import functional as F
13
14# region embeddings
15def embedding_demo() -> tuple[Tensor, nn.Embedding, Tensor]:
16 vocab = {"<pad>": 0, "<bos>": 1, "<eos>": 2, "the": 3,
17 "small": 4, "robot": 5, "moves": 6, "<unk>": 7}
18 tokens = "the small robot moves".split()
19 ids = torch.tensor([vocab.get(token, vocab["<unk>"]) for token in tokens])
20 # Match the NumPy weights exactly, rather than assuming RNGs match.
21 weights = torch.from_numpy(np.random.default_rng(0).normal(size=(len(vocab), 6)))
22 table = nn.Embedding.from_pretrained(weights, freeze=False)
23 x = table(ids)
24 torch.testing.assert_close(x[2], table.weight[vocab["robot"]])
25 return ids, table, x
26# endregion embeddings
27
28# region positions
29def positions(length: int, width: int, start: int = 0, device: torch.device | str | None = None, dtype: torch.dtype = torch.float32) -> Tensor:
30 if width <= 0 or width % 2:
31 raise ValueError("This example uses a positive, even model width")
32 pos = torch.arange(start, start + length, device=device, dtype=dtype)[:, None]
33 frequency = 10000.0 ** (-torch.arange(0, width, 2, device=device, dtype=dtype) / width)
34 angles = pos * frequency[None, :]
35 pe = torch.empty(length, width, device=device, dtype=dtype)
36 pe[:, 0::2] = torch.sin(angles)
37 pe[:, 1::2] = torch.cos(angles)
38 return pe
39# endregion positions
40
41# region attention
42def attention(q: Tensor, k: Tensor, v: Tensor, allowed: Tensor | None = None) -> tuple[Tensor, Tensor]:
43 """(..., T, dk), (..., S, dk), (..., S, dv). True means visible."""
44 if q.shape[-1] != k.shape[-1] or k.shape[-2] != v.shape[-2]:
45 raise ValueError("Q/K feature widths and K/V source lengths must match")
46 scores = (q @ k.transpose(-2, -1)) / math.sqrt(q.shape[-1])
47 if allowed is not None:
48 visible = torch.broadcast_to(allowed.to(device=q.device, dtype=torch.bool), scores.shape)
49 if not visible.any(dim=-1).all():
50 raise ValueError("Every query needs at least one visible key")
51 scores = scores.masked_fill(~visible, float("-inf"))
52 weights = scores.softmax(dim=-1)
53 return weights @ v, weights
54# endregion attention
55
56# region masks
57def attention_masks(source_ids: Tensor, target_ids: Tensor, pad: int = 0) -> tuple[Tensor, Tensor]:
58 source_visible = (source_ids != pad)[:, None, None, :]
59 target_visible = (target_ids != pad)[:, None, None, :]
60 length = target_ids.shape[1]
61 index = torch.arange(length, device=target_ids.device)
62 causal = index[None, :] <= index[:, None]
63 target_visible = target_visible & causal[None, None, :, :]
64 return source_visible, target_visible
65# endregion masks
66
67# region heads
68class MultiHead(nn.Module):
69 def __init__(self, width: int, heads: int, seed: int = 0) -> None:
70 super().__init__()
71 if heads <= 0 or width % heads:
72 raise ValueError("Model width must be divisible by head count")
73 self.heads, self.head_width = heads, width // heads
74 self.wq, self.wk, self.wv, self.wo = [nn.Linear(width, width, bias=False) for _ in range(4)]
75 # Shared initialization makes the two libraries directly comparable.
76 rng = np.random.default_rng(seed)
77 with torch.no_grad():
78 for layer in [self.wq, self.wk, self.wv, self.wo]:
79 w = rng.normal(size=(width, width)) / np.sqrt(width)
80 layer.weight.copy_(torch.tensor(w.T, dtype=layer.weight.dtype))
81
82 def split(self, x: Tensor) -> Tensor:
83 batch, length, _ = x.shape
84 return x.reshape(batch, length, self.heads, self.head_width).transpose(1, 2)
85
86 def forward(self, query: Tensor, context: Tensor, allowed: Tensor | None = None) -> tuple[Tensor, Tensor]:
87 q, k, v = self.split(self.wq(query)), self.split(self.wk(context)), self.split(self.wv(context))
88 heads, weights = attention(q, k, v, allowed)
89 batch, _, length, _ = heads.shape
90 joined = heads.transpose(1, 2).reshape(batch, length, -1)
91 return self.wo(joined), weights
92# endregion heads
93
94# region norm_ffn
95def layer_norm(x: Tensor, gamma: Tensor | None = None, beta: Tensor | None = None, eps: float = 1e-5) -> Tensor:
96 # normalized_shape=(D,) means statistics over features of EACH token.
97 return F.layer_norm(x, (x.shape[-1],), weight=gamma, bias=beta, eps=eps)
98
99
100def feed_forward(x: Tensor, w1: Tensor, b1: Tensor, w2: Tensor, b2: Tensor) -> Tensor:
101 hidden = torch.relu(x @ w1 + b1)
102 return hidden @ w2 + b2
103# endregion norm_ffn
104
105# region encoder
106class EncoderBlock(nn.Module):
107 def __init__(self, width: int = 32, heads: int = 4, inner: int = 64, seed: int = 10) -> None:
108 super().__init__()
109 self.attn = MultiHead(width, heads, seed)
110 self.norm1, self.norm2 = nn.LayerNorm(width), nn.LayerNorm(width)
111 self.ffn = nn.Sequential(nn.Linear(width, inner), nn.ReLU(), nn.Linear(inner, width))
112
113 def forward(self, x: Tensor, source_visible: Tensor) -> Tensor:
114 normalized = self.norm1(x)
115 x = x + self.attn(normalized, normalized, source_visible)[0]
116 return x + self.ffn(self.norm2(x))
117# endregion encoder
118
119# region decoder
120class DecoderBlock(nn.Module):
121 def __init__(self, width: int = 32, heads: int = 4, inner: int = 64, seed: int = 20) -> None:
122 super().__init__()
123 self.self_attn = MultiHead(width, heads, seed)
124 self.cross_attn = MultiHead(width, heads, seed + 1)
125 self.norm1, self.norm2, self.norm3 = [nn.LayerNorm(width) for _ in range(3)]
126 self.ffn = nn.Sequential(nn.Linear(width, inner), nn.ReLU(), nn.Linear(inner, width))
127
128 def forward(self, y: Tensor, memory: Tensor, target_visible: Tensor, source_visible: Tensor) -> Tensor:
129 normalized = self.norm1(y)
130 y = y + self.self_attn(normalized, normalized, target_visible)[0]
131 y = y + self.cross_attn(self.norm2(y), memory, source_visible)[0]
132 return y + self.ffn(self.norm3(y))
133# endregion decoder
134
135# region architectures
136def architecture_demo() -> tuple[Tensor, Tensor]:
137 source = torch.tensor([[4, 7, 9], [6, 8, 0]])
138 prefix = torch.tensor([[1, 4, 7], [1, 6, 8]])
139 width, vocabulary = 32, 12
140 embedding = nn.Embedding(vocabulary, width)
141 source_mask, target_mask = attention_masks(source, prefix)
142 x = embedding(source) + positions(source.shape[1], width)
143 y = embedding(prefix) + positions(prefix.shape[1], width)
144
145 # Encoder-only: pool only real source positions for classification.
146 encoded = layer_norm(EncoderBlock()(x, source_mask))
147 real = (source != 0)[..., None]
148 pooled = (encoded * real).sum(1) / real.sum(1)
149 class_logits = nn.Linear(width, 3)(pooled)
150
151 # Decoder-only: same two-sublayer structure, with CAUSAL visibility.
152 causal_hidden = layer_norm(EncoderBlock(seed=30)(y, target_mask))
153 next_token_logits = nn.Linear(width, vocabulary)(causal_hidden)
154 return class_logits, next_token_logits # (2, 3), (2, 3, 12)
155# endregion architectures
156
157# region model
158class TinyTransformer(nn.Module):
159 def __init__(self, vocabulary: int = 12, width: int = 32, heads: int = 4, inner: int = 64) -> None:
160 super().__init__()
161 self.embedding = nn.Embedding(vocabulary, width, padding_idx=0)
162 self.encoder = EncoderBlock(width, heads, inner)
163 self.decoder = DecoderBlock(width, heads, inner)
164 self.encoder_norm, self.decoder_norm = nn.LayerNorm(width), nn.LayerNorm(width)
165 self.readout = nn.Linear(width, vocabulary)
166
167 def embed(self, ids: Tensor) -> Tensor:
168 x = self.embedding(ids)
169 return x + positions(ids.shape[1], x.shape[-1], device=x.device, dtype=x.dtype)
170
171 def encode(self, source: Tensor) -> tuple[Tensor, Tensor]:
172 visible = (source != 0)[:, None, None, :]
173 memory = self.encoder_norm(self.encoder(self.embed(source), visible))
174 return memory, visible
175
176 def decode(self, target_prefix: Tensor, memory: Tensor, source_visible: Tensor) -> Tensor:
177 _, target_visible = attention_masks(target_prefix, target_prefix)
178 y = self.decoder(self.embed(target_prefix), memory, target_visible, source_visible)
179 return self.readout(self.decoder_norm(y))
180
181 def forward(self, source: Tensor, target_prefix: Tensor) -> Tensor:
182 memory, visible = self.encode(source)
183 return self.decode(target_prefix, memory, visible)
184# endregion model
185
186# region generate
187@torch.no_grad()
188def generate(model: TinyTransformer, source: Tensor, max_new_tokens: int = 4, bos: int = 1, eos: int = 2, pad: int = 0) -> Tensor:
189 was_training = model.training
190 model.eval()
191 try:
192 memory, visible = model.encode(source)
193 prefix = torch.full((source.shape[0], 1), bos, dtype=torch.long, device=source.device)
194 finished = torch.zeros(source.shape[0], dtype=torch.bool, device=source.device)
195 for _ in range(max_new_tokens):
196 logits = model.decode(prefix, memory, visible)[:, -1, :]
197 token = logits.argmax(dim=-1) # Greedy decoding for this experiment.
198 token = torch.where(finished, pad, token)
199 prefix = torch.cat([prefix, token[:, None]], dim=1)
200 finished |= token == eos
201 if finished.all():
202 break
203 return prefix[:, 1:]
204 finally:
205 model.train(was_training)
206# endregion generate
207
208if __name__ == "__main__":
209 torch.manual_seed(7)
210 model = TinyTransformer()
211 source = torch.tensor([[4, 7, 9], [6, 8, 0]])
212 prefix = torch.tensor([[1, 4, 7], [1, 6, 8]])
213 print("Vocabulary logits:", tuple(model(source, prefix).shape))