Download diffuthink/v2/data.py from OthmaneW/StoryPatch-context-v3: direct link, hf CLI and curl.
- Browser
- Download file 7.02 kB
-
https://huggingface.co/OthmaneW/StoryPatch-context-v3/resolve/main/diffuthink/v2/data.py
- Command line
-
hf download hf://OthmaneW/StoryPatch-context-v3/diffuthink/v2/data.py
-
curl -L -o data.py https://huggingface.co/OthmaneW/StoryPatch-context-v3/resolve/main/diffuthink/v2/data.py
7.02 kB
| """Pinned story download, document splits and a tokenizer trained on train only.""" | |
| import hashlib | |
| import json | |
| import shutil | |
| from pathlib import Path | |
| import requests | |
| import numpy as np | |
| from tokenizers import Tokenizer, models, trainers, pre_tokenizers, decoders | |
| from .model import SPECIAL_TOKENS, PAD, BOS, EOS | |
| def digest(path): | |
| h = hashlib.sha256() | |
| with open(path, "rb") as f: | |
| for chunk in iter(lambda: f.read(1024 * 1024), b""): | |
| h.update(chunk) | |
| return h.hexdigest() | |
| def download_stories(revision, filename, count, seen): | |
| url = f"https://huggingface.co/datasets/roneneldan/TinyStories/resolve/{revision}/{filename}" | |
| stories, buffer = [], "" | |
| with requests.get(url, stream=True, timeout=(30, 120)) as response: | |
| response.raise_for_status() | |
| response.encoding = "utf-8" | |
| for chunk in response.iter_content(1024 * 128, decode_unicode=True): | |
| buffer += chunk | |
| while "<|endoftext|>" in buffer: | |
| story, buffer = buffer.split("<|endoftext|>", 1) | |
| story = story.strip() | |
| key = hashlib.sha256(" ".join(story.split()).encode()).hexdigest() | |
| if len(story) >= 100 and key not in seen: | |
| seen.add(key) | |
| stories.append(story) | |
| if len(stories) % 5000 == 0: | |
| print(f"{filename}: {len(stories)} stories", flush=True) | |
| if len(stories) == count: | |
| return stories | |
| raise ValueError(f"Only found {len(stories)} unique stories, requested {count}") | |
| def prepare(args): | |
| if min(args.stories, args.heldout, args.tokenizer_stories) < 1 or args.length < 4 or not 261 <= args.vocab_size <= 65535: | |
| raise ValueError("Positive document counts, length >=4 and vocabulary 261..65535 required") | |
| out = Path(args.output) | |
| if (out / "manifest.json").exists(): | |
| raise ValueError("Prepared data already exists; use a new output directory") | |
| out.mkdir(parents=True, exist_ok=True) | |
| metadata = requests.get("https://huggingface.co/api/datasets/roneneldan/TinyStories", timeout=30) | |
| metadata.raise_for_status() | |
| revision = args.revision or metadata.json()["sha"] | |
| seen = set() | |
| # Held-out source first: reject exact document duplicates from training. | |
| heldout = download_stories(revision, "TinyStories-valid.txt", args.heldout * 2, seen) | |
| train = download_stories(revision, "TinyStories-train.txt", args.stories, seen) | |
| splits = {"train": train, "validation": heldout[::2], "test": heldout[1::2]} | |
| existing = getattr(args, "tokenizer", None) | |
| if existing: | |
| tokenizer = Tokenizer.from_file(existing) | |
| if any(tokenizer.token_to_id(t) != i for i, t in enumerate(SPECIAL_TOKENS)): | |
| raise ValueError("Incompatible special-token IDs") | |
| shutil.copy2(existing, out / "tokenizer.json") | |
| else: | |
| tokenizer = Tokenizer(models.BPE(unk_token="[UNK]")) | |
| tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False) | |
| tokenizer.decoder = decoders.ByteLevel() | |
| tokenizer.train_from_iterator(train[:args.tokenizer_stories], trainers.BpeTrainer(vocab_size=args.vocab_size, | |
| special_tokens=SPECIAL_TOKENS, initial_alphabet=pre_tokenizers.ByteLevel.alphabet(), min_frequency=2)) | |
| tokenizer.save(str(out / "tokenizer.json")) | |
| manifest = {"dataset": "roneneldan/TinyStories", "revision": revision, "license": "cdla-sharing-1.0", | |
| "source": "https://huggingface.co/datasets/roneneldan/TinyStories", "synthetic": True, | |
| "selection": "First unique documents in pinned source order; alternating validation/test documents", | |
| "tokenizer_train_documents": min(args.tokenizer_stories, len(train)), "vocab_size": tokenizer.get_vocab_size(), | |
| "max_length": args.length, "tokenizer_sha256": digest(out / "tokenizer.json"), "splits": {}} | |
| if existing: | |
| manifest["tokenizer_source"] = str(existing) | |
| for name, stories in splits.items(): | |
| # Nonoverlapping windows within documents, never across split boundaries. | |
| tokens = windows = complete = 0 | |
| raw_path = out / f"{name}.rows.tmp" | |
| with raw_path.open("wb") as stream: | |
| for start in range(0, len(stories), 1000): | |
| rows = [] | |
| for encoded in tokenizer.encode_batch(stories[start:start+1000]): | |
| ids = encoded.ids | |
| tokens += len(ids) | |
| complete += int(len(ids) <= args.length - 2) | |
| for offset in range(0, len(ids), args.length - 2): | |
| segment = [BOS] + ids[offset:offset + args.length - 2] | |
| if offset + args.length - 2 >= len(ids): | |
| segment += [EOS] | |
| rows.append(segment + [PAD] * (args.length - len(segment))) | |
| np.asarray(rows, dtype=np.uint16).tofile(stream) | |
| windows += len(rows) | |
| path = out / f"{name}.npy" | |
| raw = np.memmap(raw_path, dtype=np.uint16, mode="r", shape=(windows, args.length)) | |
| np.save(path, raw) | |
| del raw | |
| raw_path.unlink() | |
| manifest["splits"][name] = {"documents": len(stories), "windows": windows, "text_tokens": tokens, | |
| "complete_story_windows": complete, | |
| "documents_sha256": hashlib.sha256("\n<|endoftext|>\n".join(stories).encode()).hexdigest(), "array_sha256": digest(path)} | |
| print(name, manifest["splits"][name], flush=True) | |
| (out / "manifest.json").write_text(json.dumps(manifest, indent=2), encoding="utf-8") | |
| def load_arrays(directory): | |
| directory = Path(directory) | |
| manifest = json.loads((directory / "manifest.json").read_text(encoding="utf-8")) | |
| if digest(directory / "tokenizer.json") != manifest["tokenizer_sha256"]: | |
| raise ValueError("Tokenizer fingerprint mismatch") | |
| arrays = {} | |
| for name in ("train", "validation", "test"): | |
| path = directory / f"{name}.npy" | |
| if digest(path) != manifest["splits"][name]["array_sha256"]: | |
| raise ValueError(f"Corrupted {name} array") | |
| arrays[name] = np.load(path, mmap_mode="r") | |
| array=arrays[name] | |
| error=None | |
| if array.ndim!=2 or array.shape[1]!=manifest["max_length"] or array.dtype!=np.uint16: | |
| error=f"Invalid {name} array shape or dtype" | |
| else: | |
| for start in range(0,len(array),4096): | |
| chunk=array[start:start+4096] | |
| if np.any(chunk[:,0]!=BOS) or np.any(chunk>=manifest["vocab_size"]): | |
| error=f"Invalid {name} token IDs or BOS" | |
| break | |
| if np.any((chunk[:,:-1]==PAD)&(chunk[:,1:]!=PAD)): | |
| error=f"{name} must be right-padded for causal attention" | |
| break | |
| if error: | |
| for opened in arrays.values():opened._mmap.close() | |
| raise ValueError(error) | |
| return arrays, manifest | |