Download src/sgjm/training/data.py from adampippert/SGJM: direct link, hf CLI and curl.
- Browser
- Download file 6.65 kB
-
https://huggingface.co/adampippert/SGJM/resolve/main/src/sgjm/training/data.py
- Command line
-
hf download hf://adampippert/SGJM/src/sgjm/training/data.py
-
curl -L -o data.py https://huggingface.co/adampippert/SGJM/resolve/main/src/sgjm/training/data.py
6.65 kB
| from __future__ import annotations | |
| import os | |
| import random | |
| import urllib.error | |
| import urllib.request | |
| from dataclasses import dataclass | |
| from pathlib import Path | |
| TINYSHAKESPEARE_URL = ( | |
| "https://raw.githubusercontent.com/karpathy/char-rnn/master/data/" | |
| "tinyshakespeare/input.txt" | |
| ) | |
| def _cache_dir() -> Path: | |
| base = os.environ.get("SGJM_CACHE_DIR") | |
| if base: | |
| path = Path(base) | |
| else: | |
| path = Path.home() / ".cache" / "sgjm" | |
| path.mkdir(parents=True, exist_ok=True) | |
| return path | |
| def download_tinyshakespeare(force: bool = False) -> Path: | |
| target = _cache_dir() / "tinyshakespeare.txt" | |
| if target.exists() and not force: | |
| return target | |
| try: | |
| with urllib.request.urlopen(TINYSHAKESPEARE_URL, timeout=30) as resp: | |
| data = resp.read() | |
| except (urllib.error.URLError, TimeoutError) as e: | |
| raise RuntimeError( | |
| f"failed to download tinyshakespeare from {TINYSHAKESPEARE_URL}: {e}. " | |
| "Pre-download it manually and pass --data-path." | |
| ) from e | |
| target.write_bytes(data) | |
| return target | |
| def synthetic_corpus(n_bytes: int = 1 << 20, seed: int = 0) -> bytes: | |
| # Deterministic byte stream with both periodic structure and a 2nd-order | |
| # Markov component. Gives the model something learnable but non-trivial | |
| # without pulling external data. | |
| rng = random.Random(seed) | |
| pattern = bytes(rng.randrange(256) for _ in range(96)) | |
| period = len(pattern) | |
| out = bytearray(n_bytes) | |
| prev1 = 0 | |
| prev2 = 0 | |
| for i in range(n_bytes): | |
| mix = (pattern[i % period] ^ ((prev1 * 17 + prev2 * 31 + i * 7) & 0xFF)) | |
| out[i] = mix & 0xFF | |
| prev2 = prev1 | |
| prev1 = out[i] | |
| return bytes(out) | |
| def _python_stdlib_root() -> Path: | |
| """Return the root directory of the active Python stdlib.""" | |
| import sysconfig | |
| stdlib = sysconfig.get_path("stdlib") | |
| if stdlib and Path(stdlib).is_dir(): | |
| return Path(stdlib) | |
| import sys | |
| return Path(sys.prefix) / "lib" / f"python{sys.version_info.major}.{sys.version_info.minor}" | |
| _EXCLUDE_PARTS = {"test", "tests", "__pycache__", ".egg-info", "dist-info"} | |
| def _collect_py_files(root: Path) -> list[Path]: | |
| """Return sorted .py files under root, excluding test and cache directories.""" | |
| return sorted( | |
| p for p in root.rglob("*.py") | |
| if not any(part in _EXCLUDE_PARTS for part in p.parts) | |
| ) | |
| def _assemble_corpus(py_files: list[Path], n_bytes: int) -> bytes: | |
| sep = b"\n# ---\n" | |
| chunks: list[bytes] = [] | |
| total = 0 | |
| for p in py_files: | |
| try: | |
| chunk = p.read_bytes() | |
| except OSError: | |
| continue | |
| chunks.append(chunk) | |
| total += len(chunk) + len(sep) | |
| if total >= n_bytes: | |
| break | |
| return sep.join(chunks)[:n_bytes] | |
| def _site_packages_root() -> Path | None: | |
| """Return the site-packages directory for the active Python installation.""" | |
| import sysconfig | |
| sp = sysconfig.get_path("purelib") | |
| if sp and Path(sp).is_dir(): | |
| return Path(sp) | |
| return None | |
| def load_python_corpus( | |
| path: str | None = None, | |
| n_bytes: int = 1 << 20, | |
| extended: bool = False, | |
| ) -> bytes: | |
| """Collect .py files into a byte corpus. | |
| If `path` is given: collect from that directory. | |
| If `extended=True` (and no path): collect from Python stdlib + site-packages. | |
| Otherwise: collect from Python stdlib only. | |
| """ | |
| if path: | |
| root = Path(path) | |
| if not root.is_dir(): | |
| raise FileNotFoundError(f"Python corpus directory not found: {root}") | |
| py_files = _collect_py_files(root) | |
| if not py_files: | |
| raise ValueError(f"no .py files found under {root}") | |
| return _assemble_corpus(py_files, n_bytes) | |
| roots: list[Path] = [_python_stdlib_root()] | |
| if extended: | |
| sp = _site_packages_root() | |
| if sp: | |
| roots.append(sp) | |
| py_files: list[Path] = [] | |
| for r in roots: | |
| if r.is_dir(): | |
| py_files.extend(_collect_py_files(r)) | |
| if not py_files: | |
| raise ValueError("no .py files found in Python stdlib or site-packages") | |
| # Deduplicate (stdlib and site-packages can overlap) | |
| seen: set[Path] = set() | |
| unique: list[Path] = [] | |
| for p in py_files: | |
| if p not in seen: | |
| seen.add(p) | |
| unique.append(p) | |
| return _assemble_corpus(unique, n_bytes) | |
| def load_corpus( | |
| path: str | None = None, | |
| n_bytes: int = 1 << 20, | |
| seed: int = 0, | |
| source: str = "auto", | |
| ) -> bytes: | |
| """Load a byte corpus from `path`, a known source, or synthetic fallback. | |
| source: | |
| "auto" — use path if given, else synthetic | |
| "synthetic" — always synthetic, ignore path | |
| "tinyshakespeare" — download (and cache) Karpathy's tinyshakespeare | |
| "file" — require path; raise if missing | |
| "python" — collect .py files from path (or Python stdlib) | |
| """ | |
| if source == "synthetic": | |
| return synthetic_corpus(n_bytes, seed) | |
| if source == "tinyshakespeare": | |
| target = download_tinyshakespeare() | |
| return target.read_bytes() | |
| if source == "python": | |
| return load_python_corpus(path=path, n_bytes=n_bytes) | |
| if source == "python_extended": | |
| return load_python_corpus(path=path, n_bytes=n_bytes, extended=True) | |
| if source == "file": | |
| if not path or not os.path.exists(path): | |
| raise FileNotFoundError(f"data file not found: {path!r}") | |
| with open(path, "rb") as f: | |
| return f.read() | |
| # auto | |
| if path and os.path.exists(path): | |
| with open(path, "rb") as f: | |
| return f.read() | |
| return synthetic_corpus(n_bytes, seed) | |
| class ByteDataset: | |
| data: bytes | |
| seq_len: int | |
| def __post_init__(self) -> None: | |
| if len(self.data) <= self.seq_len + 1: | |
| raise ValueError( | |
| f"corpus too small ({len(self.data)} bytes) for seq_len={self.seq_len}" | |
| ) | |
| def __len__(self) -> int: | |
| return len(self.data) - self.seq_len - 1 | |
| def sample(self, rng: random.Random) -> tuple[list[int], list[int]]: | |
| i = rng.randrange(len(self)) | |
| chunk = self.data[i : i + self.seq_len + 1] | |
| return list(chunk[:-1]), list(chunk[1:]) | |
| def batch( | |
| self, | |
| batch_size: int, | |
| rng: random.Random, | |
| ) -> tuple[list[list[int]], list[list[int]]]: | |
| xs: list[list[int]] = [] | |
| ys: list[list[int]] = [] | |
| for _ in range(batch_size): | |
| x, y = self.sample(rng) | |
| xs.append(x) | |
| ys.append(y) | |
| return xs, ys | |