Download ar/data/readonce.py from AjayRangoji/AR: direct link, hf CLI and curl.
- Browser
- Download file 4.19 kB
-
https://huggingface.co/AjayRangoji/AR/resolve/main/ar/data/readonce.py
- Command line
-
hf download hf://AjayRangoji/AR/ar/data/readonce.py
-
curl -L -o readonce.py https://huggingface.co/AjayRangoji/AR/resolve/main/ar/data/readonce.py
4.19 kB
| """Read-once data loader (Stage 2). | |
| Each file is cut into windows of seq_len+1 tokens that overlap by exactly one token | |
| (x = first seq_len tokens, y = the same window shifted by one), so every token is a | |
| prediction target once. Each file's windows are shuffled once with a fixed seed, and a | |
| fixed schedule says which file every sample comes from, following the mix weights. | |
| Everything is computed from (seed, step, slot), so a restart at any step continues | |
| exactly where training stopped, with no saved loader state. A window is only reused | |
| after its whole file has been read once. | |
| """ | |
| import numpy as np | |
| # 524,288 tokens / 4096 = 128 sequences: the most any node count uses in one step. | |
| # Each step owns 128 sample slots; node counts that use fewer just skip the rest. | |
| SAMPLES_PER_STEP = 128 | |
| class ReadOnceDataset: | |
| def __init__(self, bin_paths, seq_len, weights, start_step, end_step, eot_token_id, seed=1234): | |
| assert end_step > start_step, "empty stage" | |
| self.seq_len = seq_len | |
| self.start_step = start_step | |
| self.eot = eot_token_id | |
| self.data = [np.memmap(p, dtype=np.uint32, mode="r") for p in bin_paths] | |
| self.n_windows = [(len(d) - 1) // seq_len for d in self.data] | |
| assert all(n > 0 for n in self.n_windows), "a file is shorter than one window" | |
| w = np.asarray(weights, dtype=np.float64) | |
| assert len(w) == len(self.data) and (w >= 0).all() and w.sum() > 0, "bad weights" | |
| w = w / w.sum() | |
| # each file's windows in one fixed shuffled order (int32 keeps RAM small) | |
| self.perm = [np.random.default_rng(seed + 1000 + f).permutation(n).astype(np.int32) | |
| for f, n in enumerate(self.n_windows)] | |
| # which file each sample of the stage reads, and how many earlier samples used that file | |
| self.total = (end_step - start_step) * SAMPLES_PER_STEP | |
| self.src = np.random.default_rng(seed).choice(len(w), size=self.total, p=w).astype(np.int16) | |
| self.occ = np.empty(self.total, dtype=np.int64) | |
| for f in range(len(w)): | |
| idx = np.flatnonzero(self.src == f) | |
| self.occ[idx] = np.arange(len(idx)) | |
| def slot(micro, i, rank, world, batch_size): | |
| """Position of one sequence inside its step's 128 slots; unique per (micro, i, rank).""" | |
| s = (micro * batch_size + i) * world + rank | |
| assert s < SAMPLES_PER_STEP, "more than 128 sequences per step: raise SAMPLES_PER_STEP" | |
| return s | |
| def window(self, step, slot): | |
| """(file index, window index, tokens) for one sample; tokens has seq_len+1 entries.""" | |
| k = (step - self.start_step) * SAMPLES_PER_STEP + slot | |
| assert 0 <= k < self.total, f"step {step} is outside this stage" | |
| f = int(self.src[k]) | |
| w = int(self.perm[f][self.occ[k] % self.n_windows[f]]) | |
| start = w * self.seq_len | |
| tokens = np.asarray(self.data[f][start : start + self.seq_len + 1], dtype=np.int64) | |
| return f, w, tokens | |
| def get_numpy(self, step, micro, batch_size, rank, world): | |
| """One micro-batch as numpy arrays: inputs, targets, document ids.""" | |
| x = np.empty((batch_size, self.seq_len), dtype=np.int64) | |
| y = np.empty((batch_size, self.seq_len), dtype=np.int64) | |
| seg = np.empty((batch_size, self.seq_len), dtype=np.int32) | |
| for i in range(batch_size): | |
| _, _, chunk = self.window(step, self.slot(micro, i, rank, world, batch_size)) | |
| x[i], y[i] = chunk[:-1], chunk[1:] | |
| # document id goes up AFTER each end-of-text, so the EOT stays with its own document | |
| is_eot = (x[i] == self.eot).astype(np.int32) | |
| seg[i] = np.cumsum(is_eot) - is_eot | |
| return x, y, seg | |
| def get_batch(self, step, micro, batch_size, rank, world, device="cuda"): | |
| """Same as get_numpy, as torch tensors on `device`.""" | |
| import torch | |
| out = [] | |
| for a in self.get_numpy(step, micro, batch_size, rank, world): | |
| t = torch.from_numpy(a) | |
| if torch.device(device).type == "cuda": | |
| t = t.pin_memory().to(device, non_blocking=True) | |
| out.append(t) | |
| return tuple(out) | |