import hashlib import json import os import random import tempfile from pathlib import Path import pyarrow.parquet as pq import torch import torch.distributed as dist from filelock import FileLock def parquet_files(data_dir, split): if not data_dir: raise ValueError("A local dataset directory is required.") root = Path(data_dir).expanduser() if not root.is_dir(): raise FileNotFoundError(f"Local dataset directory does not exist: {root}") files = sorted((root / "data").glob(f"{split}-*.parquet")) if not files: raise FileNotFoundError(f"No {split} parquet shards under {root / 'data'}") return files def file_sha256(path): path = Path(path) before = path.stat() digest = hashlib.sha256() with path.open("rb") as handle: for block in iter(lambda: handle.read(1024 * 1024), b""): digest.update(block) after = path.stat() if (before.st_size, before.st_mtime_ns) != (after.st_size, after.st_mtime_ns): raise RuntimeError(f"File changed while hashing: {path}") return digest.hexdigest() def atomic_save(value, path): path = Path(path) path.parent.mkdir(parents=True, exist_ok=True) with tempfile.TemporaryDirectory(prefix=".pending-", dir=path.parent) as tmp: pending = Path(tmp) / path.name torch.save(value, pending) os.replace(pending, path) def _metadata(data_dir, split, n, seed, seq_len): return { "version": 1, "split": split, "seed": int(seed), "seq_len": int(seq_len), "segments": int(n), "encoding": "utf8-ignore+LF;byte-offset=2;overlap=1", "files": [{"name": p.name, "sha256": file_sha256(p)} for p in parquet_files(data_dir, split)], } def _tensor_sha256(t): return hashlib.sha256(t.numpy().astype(" 257 or value.get("tensor_sha256") != _tensor_sha256(t) ): raise ValueError(f"Cache tensor failed integrity validation: {path}") return t def _segments(data_dir, split, seed, seq_len): files = parquet_files(data_dir, split) rng = random.Random(seed) buf = bytearray() while True: order = list(files) rng.shuffle(order) nonempty = False for path in order: for rb in pq.ParquetFile(path).iter_batches(batch_size=256, columns=["text"]): for text in rb.column(0).to_pylist(): if not text: continue nonempty = True buf.extend(text.encode("utf-8", errors="ignore")) buf.extend(b"\n") while len(buf) >= seq_len + 1: yield torch.frombuffer(bytearray(buf[:seq_len + 1]), dtype=torch.uint8).long().add_(2) del buf[:seq_len] if not nonempty: raise ValueError(f"No nonempty text in the {split} split.") @torch.no_grad() def build_eval_cache(data_dir, cache_dir, split, n, seed, seq_len, rank=0, world_size=1, distributed=False): if n <= 0 or seq_len <= 0: raise ValueError("Cache size and sequence length must be positive.") root = Path(cache_dir) path = root / f"slimpj_{split}_L{seq_len}_N{n}.pt" status, tensor = [None], None if not distributed or rank == 0: try: root.mkdir(parents=True, exist_ok=True) with FileLock(str(path) + ".lock", timeout=300): metadata = _metadata(data_dir, split, n, seed, seq_len) if path.exists(): tensor = _load_cache(path, metadata) else: stream = iter(_segments(data_dir, split, seed, seq_len)) tensor = torch.stack([next(stream).to(torch.int16) for _ in range(n)]) if _metadata(data_dir, split, n, seed, seq_len) != metadata: raise RuntimeError("Dataset changed during cache generation.") atomic_save({"t": tensor, "metadata": metadata, "tensor_sha256": _tensor_sha256(tensor)}, path) status[0] = {"metadata": metadata} except Exception as exc: if not distributed: raise status[0] = {"error": f"{type(exc).__name__}: {exc}"} if distributed: dist.broadcast_object_list(status, src=0) if "error" in status[0]: raise RuntimeError(status[0]["error"]) error = None if rank != 0: try: tensor = _load_cache(path, status[0]["metadata"]) except Exception as exc: error = f"Rank {rank}: {type(exc).__name__}: {exc}" errors = [None] * world_size dist.all_gather_object(errors, error) if any(errors): raise RuntimeError("; ".join(x for x in errors if x)) return tensor def hankel_kernel(length, eps=1e-6): s = torch.arange(2, 2 * length + 1, dtype=torch.float32, device="cpu") a = 2 / (s ** 3 - s + eps) return torch.cat([a[length - 1:], torch.zeros(1), a[:length - 1]]) def hmv(g_f, x): length, p = x.shape padded = torch.zeros(p, 2 * length, dtype=x.dtype, device=x.device) padded[:, :length] = x.flip(0).T return torch.fft.irfft(torch.fft.rfft(padded, dim=-1) * g_f[None], n=2 * length, dim=-1)[:, :length].T def _basis_digest(sigma, phi): digest = hashlib.sha256() for tensor in (sigma, phi): digest.update(tensor.numpy().astype("