ATOM (atom-ai): code snapshot (100fae0 + batched-train), README with measured numbers, curated checkpoints (part 4)
b9301f1 verified Download src/io/batched_stream.py from thefinalboss/atom-ai: direct link, hf CLI and curl.
- Browser
- Download file 9.31 kB
-
https://huggingface.co/thefinalboss/atom-ai/resolve/main/src/io/batched_stream.py
- Command line
-
hf download hf://thefinalboss/atom-ai/src/io/batched_stream.py
-
curl -L -o batched_stream.py https://huggingface.co/thefinalboss/atom-ai/resolve/main/src/io/batched_stream.py
9.31 kB
| """B parallel byte streams over a (possibly multi-GB) memory-mapped corpus. | |
| docs/BATCHED_TRAINING.md. Bytes only: no tokenizer, no vocabulary. The shard | |
| files (sorted glob, same order as ``resolve_shard_paths``) are viewed as ONE | |
| circular byte string, exactly the continuum ``StreamingPacketSource`` produces | |
| with ``--loop-shards`` (shard ends and the epoch wrap are plain byte | |
| transitions; in byte-tick mode the shard-end flush emits nothing). | |
| Stream b sits at an absolute, ever-increasing position ``pos[b]`` (the corpus | |
| byte is ``pos mod total``). Each ``next()`` returns, for every stream, the | |
| current byte, its 320-d byte-tick packet features and the target (next) byte, | |
| then advances every stream by one byte. | |
| Features are the ``Atomizer`` byte-tick features (``max_span_bytes == 1``) | |
| computed vectorised: everything that depends only on the byte comes from a | |
| 256-row table built with the scalar ``Atomizer`` code itself; the stream- | |
| dependent parts are the phase (``pos mod phase_period``), ``previous_length`` | |
| (0 on the first packet of a stream context, then 1), the context similarity and | |
| the 16-value rolling context sketch (EMA 0.85, low nibble). From a fresh | |
| context this equals ``Atomizer.encode_bytes`` feature for feature (tested). | |
| Resumable: ``state_dict()`` holds the corpus signature, the positions and the | |
| per-stream feature context (exact resume). | |
| """ | |
| from __future__ import annotations | |
| import math | |
| from pathlib import Path | |
| from typing import Sequence | |
| import numpy as np | |
| import torch | |
| from .atomizer import Atomizer | |
| # Column layout of the 320 features: hist 0:256, first 256:272, last 272:288, | |
| # stats 288:304, context 304:320. | |
| _STATS = 288 | |
| _COL_SIN, _COL_COS, _COL_PREV, _COL_SIM = _STATS + 10, _STATS + 11, _STATS + 12, _STATS + 13 | |
| _CTX = 304 | |
| class ByteCorpus: | |
| """Ordered shard files as one circular, memory-mapped byte string.""" | |
| def __init__(self, paths: Sequence[str | Path]) -> None: | |
| self.paths = [Path(p).resolve() for p in paths] | |
| if not self.paths: | |
| raise ValueError("ByteCorpus needs at least one file") | |
| self.sizes = [p.stat().st_size for p in self.paths] | |
| if any(s == 0 for s in self.sizes): | |
| empty = [str(p) for p, s in zip(self.paths, self.sizes) if s == 0] | |
| raise ValueError(f"empty shard(s): {empty}") | |
| self.maps = [np.memmap(p, dtype=np.uint8, mode="r") for p in self.paths] | |
| self.starts = np.cumsum([0] + self.sizes[:-1]).astype(np.int64) | |
| self.total = int(sum(self.sizes)) | |
| if self.total < 2: | |
| raise ValueError("corpus must hold at least 2 bytes") | |
| def signature(self) -> dict: | |
| return {"paths": [str(p) for p in self.paths], "sizes": list(self.sizes), "total": self.total} | |
| def read(self, pos: int, n: int) -> np.ndarray: | |
| """n bytes starting at absolute position ``pos`` (wraps around the corpus).""" | |
| out = np.empty(n, dtype=np.uint8) | |
| g = int(pos) % self.total | |
| done = 0 | |
| while done < n: | |
| i = int(np.searchsorted(self.starts, g, side="right") - 1) | |
| off = g - int(self.starts[i]) | |
| take = min(n - done, self.sizes[i] - off) | |
| out[done:done + take] = self.maps[i][off:off + take] | |
| done += take | |
| g = (g + take) % self.total | |
| return out | |
| def byte_tick_feature_table(pack_mode: str = "linguistic") -> torch.Tensor: | |
| """[256, 320] static byte-tick features (context-free part), built with the | |
| scalar Atomizer: stream-dependent columns are overwritten per tick.""" | |
| at = Atomizer(max_span_bytes=1, pack_mode=pack_mode) | |
| rows = [] | |
| for b in range(256): | |
| at.reset() | |
| packets = at.encode_bytes(bytes([b]), reset=True, flush=True) | |
| assert len(packets) == 1 | |
| rows.append(packets[0].features) | |
| return torch.stack(rows) | |
| class BatchedByteStream: | |
| """B byte streams with vectorised byte-tick features. | |
| ``offsets``: starting absolute positions (default: evenly spaced over the | |
| corpus). ``warmup``: bytes before each non-zero offset replayed through the | |
| feature context only (0.85^64 ≈ 3e-5, so 64 bytes ≈ a full replay). | |
| """ | |
| def __init__(self, corpus: ByteCorpus, batch_size: int, *, offsets: Sequence[int] | None = None, | |
| chunk: int = 4096, pack_mode: str = "linguistic", phase_period: float = 256.0, | |
| warmup: int = 64, device: str | torch.device = "cpu") -> None: | |
| self.corpus = corpus | |
| self.B = int(batch_size) | |
| self.chunk = max(1, int(chunk)) | |
| self.pack_mode = pack_mode | |
| self.phase_period = float(phase_period) | |
| self.device = torch.device(device) | |
| if offsets is None: | |
| offsets = [(b * corpus.total) // self.B for b in range(self.B)] | |
| if len(offsets) != self.B: | |
| raise ValueError("need one offset per stream") | |
| self.table = byte_tick_feature_table(pack_mode).to(self.device) | |
| # sin/cos of the phase, built with math.* exactly like Atomizer._make_features. | |
| per = int(self.phase_period) | |
| if float(per) != self.phase_period: | |
| raise ValueError("phase_period must be an integer") | |
| ph = [2.0 * math.pi * (p % self.phase_period) / self.phase_period for p in range(per)] | |
| self.sin_t = torch.tensor([math.sin(x) for x in ph], dtype=torch.float32, device=self.device) | |
| self.cos_t = torch.tensor([math.cos(x) for x in ph], dtype=torch.float32, device=self.device) | |
| self.pos = np.asarray([int(o) for o in offsets], dtype=np.int64) | |
| self.ctx = torch.zeros(self.B, 16, dtype=torch.float32, device=self.device) | |
| self.prev_len = torch.zeros(self.B, dtype=torch.float32, device=self.device) | |
| self._buf: torch.Tensor | None = None | |
| self._buf_i = 0 | |
| self._ph = None # device copy of pos mod phase_period (no per-tick host->device copy) | |
| if warmup > 0: | |
| self._warmup(int(warmup)) | |
| # --------------------------------------------------------------- context | |
| def _warmup(self, n: int) -> None: | |
| start = self.pos.copy() | |
| for b in range(self.B): | |
| k = int(min(n, start[b])) | |
| if k <= 0: | |
| continue | |
| raw = torch.from_numpy(self.corpus.read(int(start[b]) - k, k).astype(np.int64)) | |
| for v in raw.tolist(): | |
| self._ctx_update_one(b, v) | |
| self._buf = None | |
| def _ctx_update_one(self, b: int, v: int) -> None: | |
| g = torch.zeros(16, dtype=torch.float32, device=self.device) | |
| g[v & 15] = 1.0 | |
| self.ctx[b].mul_(0.85).add_(0.15 * g) | |
| self.prev_len[b] = 1.0 | |
| def _refill(self) -> None: | |
| n = self.chunk + 1 | |
| arr = np.stack([self.corpus.read(int(p), n) for p in self.pos]) | |
| self._buf = torch.from_numpy(arr.astype(np.int64)).to(self.device) | |
| self._buf_i = 0 | |
| # ------------------------------------------------------------------ next | |
| def next(self) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: | |
| if self._buf is None or self._buf_i >= self.chunk: | |
| self._refill() | |
| i = self._buf_i | |
| cur = self._buf[:, i] | |
| tgt = self._buf[:, i + 1] | |
| feats = self.table.index_select(0, cur).clone() | |
| if self._ph is None: | |
| self._ph = torch.from_numpy(self.pos % int(self.phase_period)).to(self.device) | |
| ph = self._ph | |
| feats[:, _COL_SIN] = self.sin_t[ph] | |
| feats[:, _COL_COS] = self.cos_t[ph] | |
| feats[:, _COL_PREV] = self.prev_len | |
| low = cur & 15 | |
| dot = self.ctx.gather(1, low.view(-1, 1)).squeeze(1).double() | |
| denom = self.ctx.norm(dim=1).double() | |
| sim = torch.where(denom > 1e-12, dot / denom.clamp_min(1e-300), torch.zeros_like(dot)) | |
| feats[:, _COL_SIM] = ((sim + 1.0) / 2.0).float() | |
| feats[:, _CTX:] = self.ctx | |
| grouped = torch.zeros_like(self.ctx).scatter_(1, low.view(-1, 1), 1.0) | |
| self.ctx = self.ctx.mul(0.85).add(0.15 * grouped) | |
| self.prev_len = torch.ones_like(self.prev_len) | |
| self.pos += 1 | |
| self._ph = (ph + 1) % int(self.phase_period) | |
| self._buf_i += 1 | |
| return cur, feats, tgt | |
| __next__ = next | |
| def __iter__(self): | |
| return self | |
| # ------------------------------------------------------------ checkpoint | |
| def state_dict(self) -> dict: | |
| return { | |
| "corpus": self.corpus.signature(), "B": self.B, "pack_mode": self.pack_mode, | |
| "phase_period": self.phase_period, "pos": self.pos.tolist(), | |
| "ctx": self.ctx.detach().cpu().clone(), "prev_len": self.prev_len.detach().cpu().clone(), | |
| } | |
| def load_state_dict(self, sd: dict, *, strict_corpus: bool = True) -> None: | |
| if int(sd["B"]) != self.B: | |
| raise ValueError(f"stream state has B={sd['B']}, loader has B={self.B}") | |
| sig = self.corpus.signature() | |
| if strict_corpus and (sd["corpus"]["sizes"] != sig["sizes"]): | |
| raise ValueError("corpus changed since the checkpoint (sizes differ); pass strict_corpus=False to accept") | |
| self.pos = np.asarray(sd["pos"], dtype=np.int64) | |
| self.ctx = sd["ctx"].to(self.device).clone() | |
| self.prev_len = sd["prev_len"].to(self.device).clone() | |
| self._ph = None | |
| self._buf = None | |
| self._buf_i = 0 | |