# Map-style reader over the built mix, designed so that resumption is an index advance, not a replay. # Why this file exists in this shape (docs/01-plan.md §5, "the resume trap"): `transformers.Trainer` can # resume an `IterableDataset` only by *decoding and discarding* the batches before the checkpoint step, # which costs O(steps) of I/O after every interruption -- up to 3,815 steps of nothing. A map-style # reader over a concatenated token store makes the same thing a one-line offset. §3.1's "same position" # is therefore a number in the checkpoint, not a feeling. # # Sampling policy, stated so it can be reproduced: # stream = the uint16 id arrays of manifest["shards"] in manifest order, concatenated # sample i = stream[i*L : (i+1)*L] with L = seq_len, no padding, documents packed back to back # order = a single global permutation of sample indices from numpy's PCG64(seed) # # Packing without a document boundary means a few windows per shard splice two unrelated documents and # let attention cross the seam. That is what TinyLlama, SmolLM and GPT-2-era pretraining all do; a # block-diagonal mask would cost activation memory we do not have at bs 2x2048 on a T4. Recorded as a # known, deliberate deviation rather than a silence. # # The permutation is over ~490k indices (1B tokens / 2048), i.e. ~2 MB, so a *global* shuffle is free # here -- no need for the windowed approximations that streaming readers settle for. # # torch is imported lazily-ish: the byte-level part of this module is usable in a CPU-only check. import bisect import json import os import struct import numpy as np def read_shard_index(path): """(vocab, n_docs, n_tokens, offset_bytes, ids_bytes) for one shard without loading its ids.""" with open(path, "rb") as f: vocab, n_docs, n_tokens, flags = struct.unpack("