| """Layer 0: the binary input. |
| |
| No parameters. Owns the u8 id streams (train/val) and turns them into |
| model-ready batches: x (B,T) and y (B,T) int64 tensors, where y is the |
| same stream shifted by one id (next-token target). |
| |
| Independence: imports only numpy/torch. Knows nothing about layer 1+. |
| """ |
| from dataclasses import dataclass |
| from pathlib import Path |
| from typing import Optional |
|
|
| import numpy as np |
| import torch |
|
|
|
|
| @dataclass |
| class Layer0Config: |
| block_size: int = 1024 |
| batch_size: int = 32 |
| chunk_size: int = 65536 |
| seed: Optional[int] = 1337 |
|
|
|
|
| class Layer0: |
| """Read-only view of the binary; chunk-shuffle sampling.""" |
|
|
| def __init__(self, cfg, train_path="data/train.bin", val_path="data/val.bin"): |
| self.cfg = cfg |
| |
| self.train = np.memmap(train_path, dtype=np.uint8, mode="r") |
| self.val = np.memmap(val_path, dtype=np.uint8, mode="r") |
| self.n_train = int(self.train.size) |
| self.n_val = int(self.val.size) |
| self.n_chunks = self.n_train // cfg.chunk_size |
| self.rng = np.random.default_rng(cfg.seed) |
|
|
| def sample_batch(self): |
| """One training batch: random chunk, random offset, x and y shifted by 1.""" |
| B, T, C = self.cfg.batch_size, self.cfg.block_size, self.cfg.chunk_size |
| xs = np.empty((B, T + 1), dtype=np.uint8) |
| for b in range(B): |
| c = int(self.rng.integers(0, self.n_chunks)) |
| o = int(self.rng.integers(0, C - T)) |
| base = c * C + o |
| xs[b] = self.train[base: base + T + 1] |
| x = torch.tensor(xs[:, :T], dtype=torch.long) |
| y = torch.tensor(xs[:, 1:], dtype=torch.long) |
| return x, y |
|
|
| def val_block(self, i): |
| """The i-th contiguous block of the val stream (sequential, no shuffle).""" |
| T = self.cfg.block_size |
| base = i * T |
| assert base + T < self.n_val, f"val block {i} out of range" |
| seg = self.val[base: base + T + 1] |
| return (torch.tensor(seg[:T], dtype=torch.long), |
| torch.tensor(seg[1:], dtype=torch.long)) |
|
|
| @property |
| def n_val_blocks(self): |
| |
| return (self.n_val - self.cfg.block_size - 1) // self.cfg.block_size + 1 |
|
|
| @property |
| def steps_per_epoch(self): |
| return self.n_train // (self.cfg.batch_size * self.cfg.block_size) |
|
|
| |
|
|
| @property |
| def tape_len(self): |
| """Contiguous ids per row, truncated to whole windows.""" |
| return (self.n_train // self.cfg.batch_size) // self.cfg.block_size * self.cfg.block_size |
|
|
| @property |
| def n_seq_windows(self): |
| return self.tape_len // self.cfg.block_size |
|
|
| def seq_windows(self): |
| """Yield (x, y) for each T-window, all B rows advancing in lockstep. |
| |
| Row b walks tape b = train[b*tape_len : (b+1)*tape_len] in order. |
| Tapes are contiguous in the original stream, so the caller may carry |
| RNN state across windows and reset only at sweep end (the one real |
| discontinuity). Deterministic: no RNG. |
| """ |
| B, T = self.cfg.batch_size, self.cfg.block_size |
| rows = np.arange(B, dtype=np.int64) * self.tape_len |
| cols = np.arange(T + 1, dtype=np.int64) |
| idx = rows[:, None] + cols[None, :] |
| for w in range(self.n_seq_windows): |
| off = (idx + w * T).ravel() |
| xs = self.train[off].reshape(B, T + 1) |
| yield (torch.tensor(xs[:, :T], dtype=torch.long), |
| torch.tensor(xs[:, 1:], dtype=torch.long)) |
|
|
|
|
| def _self_test(): |
| PROJ = Path(__file__).resolve().parents[1] |
| import sys |
| sys.path.insert(0, str(PROJ / "scripts")) |
| import tokenizer as tk |
| tk.load_vocab() |
|
|
| cfg = Layer0Config(batch_size=8, block_size=256) |
| l0 = Layer0(cfg) |
| x, y = l0.sample_batch() |
|
|
| assert x.shape == (8, 256) and y.shape == (8, 256), "batch shape" |
| assert int(x.min()) >= 0 and int(x.max()) <= 108, "ids out of vocab" |
| assert torch.equal(y[:, :255], x[:, 1:]), "shift law violated" |
|
|
| v0, _ = l0.val_block(0) |
| assert torch.equal(v0, torch.tensor(l0.val[:256].astype(np.int64))), "val walk" |
|
|
| def render(row): |
| out = bytes(row.numpy().astype(np.uint8)).translate(tk.TABLE_DEC) |
| for s, t in tk.SENT_DEC: |
| out = out.replace(s, t) |
| return tk.display(out).decode() |
|
|
| print("x[0] (model input):") |
| print(render(x[0])) |
| print("y[0] (model target):") |
| print(render(y[0])) |
| print(f"n_train={l0.n_train:,} n_chunks={l0.n_chunks:,} " |
| f"steps/epoch={l0.steps_per_epoch:,} n_val_blocks={l0.n_val_blocks:,}") |
| print("layer0 self-test: PASS") |
|
|
|
|
| if __name__ == "__main__": |
| _self_test() |
|
|