Download layer_1.py from basically-experimental/Notio-3.7M-RNN-v1: direct link, hf CLI and curl.
- Browser
- Download file 3.61 kB
-
https://huggingface.co/basically-experimental/Notio-3.7M-RNN-v1/resolve/main/layer_1.py
- Command line
-
hf download hf://basically-experimental/Notio-3.7M-RNN-v1/layer_1.py
-
curl -L -o layer_1.py https://huggingface.co/basically-experimental/Notio-3.7M-RNN-v1/resolve/main/layer_1.py
3.61 kB
| """Layer 1: the embedding - id -> vector. | |
| wte: token table (vocab_size, d_model) "what" | |
| wpe: position table (block_size, d_model) "where" | |
| x = wte(ids) + wpe(pos), then optional dropout. | |
| Independence: imports only torch. Knows nothing about layer 0 (data) | |
| or anything above it. Exposes tie_lm_head for the (later) output layer. | |
| """ | |
| from dataclasses import dataclass | |
| import torch | |
| import torch.nn as nn | |
| class Layer1Config: | |
| vocab_size: int = 109 | |
| block_size: int = 1024 | |
| d_model: int = 512 | |
| init_std: float = 0.02 | |
| dropout: float = 0.0 | |
| class Layer1(nn.Module): | |
| def __init__(self, cfg: Layer1Config): | |
| super().__init__() | |
| self.cfg = cfg | |
| self.wte = nn.Embedding(cfg.vocab_size, cfg.d_model) | |
| self.wpe = nn.Embedding(cfg.block_size, cfg.d_model) | |
| self.drop = nn.Dropout(cfg.dropout) | |
| self.init_weights() | |
| def init_weights(self): | |
| nn.init.normal_(self.wte.weight, mean=0.0, std=self.cfg.init_std) | |
| nn.init.normal_(self.wpe.weight, mean=0.0, std=self.cfg.init_std) | |
| def forward(self, ids, pos_offset=0): | |
| """ids: (B, T) long -> (B, T, d_model). pos_offset: position embedding | |
| start, used by stateful generation so wpe advances with the stream.""" | |
| B, T = ids.shape | |
| assert pos_offset + T <= self.cfg.block_size, \ | |
| f"pos_offset+T={pos_offset + T} exceeds block_size {self.cfg.block_size}" | |
| pos = torch.arange(T, device=ids.device) + pos_offset | |
| return self.drop(self.wte(ids) + self.wpe(pos)) | |
| def tie_lm_head(self, head): | |
| """Share wte with the (later) output head: head.weight IS wte.weight.""" | |
| head.weight = self.wte.weight | |
| return head | |
| def n_params(self): | |
| return sum(p.numel() for p in self.parameters()) | |
| def _self_test(): | |
| torch.manual_seed(0) | |
| cfg = Layer1Config() | |
| l1 = Layer1(cfg) | |
| B, T = 4, 16 | |
| ids = torch.randint(0, cfg.vocab_size, (B, T)) | |
| x = l1(ids) | |
| assert x.shape == (B, T, cfg.d_model), "embed shape" | |
| # same id everywhere -> the wte part is constant across positions | |
| same = torch.full((1, T), 42, dtype=torch.long) | |
| xs = l1(same) | |
| for p in range(T - 1): | |
| d = (xs[0, p] - l1.wpe.weight[p]) - (xs[0, p + 1] - l1.wpe.weight[p + 1]) | |
| assert d.abs().max().item() < 1e-6, "wte part varies with position" | |
| # same position, different ids -> the wpe part is constant | |
| for b in range(B - 1): | |
| d = (x[b, 0] - l1.wte(ids[b, 0])) - (x[b + 1, 0] - l1.wte(ids[b + 1, 0])) | |
| assert d.abs().max().item() < 1e-6, "wpe part varies with token" | |
| # gradients flow through both tables | |
| x.sum().backward() | |
| assert l1.wte.weight.grad is not None and l1.wte.weight.grad.abs().sum().item() > 0 | |
| assert l1.wpe.weight.grad is not None and l1.wpe.weight.grad.abs().sum().item() > 0 | |
| # param count: 109*256 + 256*256 | |
| assert l1.n_params == cfg.vocab_size * cfg.d_model + cfg.block_size * cfg.d_model == 93440 | |
| # weight tying is a true share, not a copy | |
| head = nn.Linear(cfg.d_model, cfg.vocab_size, bias=False) | |
| l1.tie_lm_head(head) | |
| assert head.weight is l1.wte.weight, "tie is not a shared tensor" | |
| # block_size guard | |
| try: | |
| l1(torch.zeros((1, cfg.block_size + 1), dtype=torch.long)) | |
| raise SystemExit("expected assertion for T > block_size") | |
| except AssertionError: | |
| pass | |
| print(f"wte {tuple(l1.wte.weight.shape)} wpe {tuple(l1.wpe.weight.shape)} " | |
| f"params {l1.n_params:,}") | |
| print("layer1 self-test: PASS") | |
| if __name__ == "__main__": | |
| _self_test() | |