import itertools import json import random from pathlib import Path from huggingface_hub import hf_hub_download from tokenizers import Tokenizer import torch from torch.utils.data import Dataset # official DeepSeek-V3 BPE tokenizer — downloaded once from HF _tokenizer_path = hf_hub_download('deepseek-ai/DeepSeek-V3', 'tokenizer.json') _tok = Tokenizer.from_file(_tokenizer_path) vocab_size = _tok.get_vocab_size() categories = ('story', 'code', 'math') def encode(text): return _tok.encode(text).ids def decode(ids): return _tok.decode(ids) def token_text(token_id): # the raw BPE token string; Ġ marks a leading space, Ċ a newline return _tok.id_to_token(token_id).replace('Ġ', ' ').replace('Ċ', '⏎') def make_math(n, max_val=1000, seed=0): # synthetic arithmetic strings eg '13 + 27 = 40\n' rng = random.Random(seed) out = [] for _ in range(n): op = rng.choice(['+', '-', '*']) a, b = rng.randint(0, max_val), rng.randint(0, max_val) c = {'+': a + b, '-': a - b, '*': a * b}[op] out.append(f'{a} {op} {b} = {c}\n') return out def _load_or_cache(name, n, text_field, cache_file): # download once via HF streaming, then cache as JSON for reproducible reruns cache_path = Path('data') / cache_file if cache_path.exists(): return json.loads(cache_path.read_text()) from datasets import load_dataset ds = load_dataset(name, split='train', streaming=True) texts = [ex[text_field] for ex in itertools.islice(ds, n)] cache_path.parent.mkdir(exist_ok=True) cache_path.write_text(json.dumps(texts)) return texts def load_stories(n): return _load_or_cache('roneneldan/TinyStories', n, 'text', f'stories_{n}.json') def load_code(n, max_bytes_per_sample=2000): texts = _load_or_cache('codeparrot/codeparrot-clean-valid', n, 'content', f'code_{n}.json') return [t[:max_bytes_per_sample] for t in texts] # build one byte stream per category and slide a window over each separately, # so every (input, target) pair has a single, well-defined category. that # per-token category is what the heatmap aggregates over later class MixedDataset(Dataset): def __init__(self, samples_per_cat, seq_len, stride): self.inputs = [] self.targets = [] self.cat_ids = [] streams = [ ''.join(load_stories(samples_per_cat)), ''.join(load_code(samples_per_cat // 5)), ''.join(make_math(samples_per_cat * 10)), ] for cat_id, text in enumerate(streams): tokens = torch.tensor(encode(text), dtype=torch.long) for i in range(0, len(tokens) - seq_len, stride): self.inputs.append(tokens[i : i + seq_len]) self.targets.append(tokens[i + 1 : i + seq_len + 1]) self.cat_ids.append(cat_id) def __len__(self): return len(self.inputs) def __getitem__(self, i): return self.inputs[i], self.targets[i], self.cat_ids[i] # windows over one text stream per category. stride = seq_len gives # non-overlapping windows, so the same tokens are not seen many times. heavy # window overlap (small stride) is what let the earlier model memorize class WindowDataset(Dataset): def __init__(self, streams, seq_len, stride): self.inputs = [] self.targets = [] self.cat_ids = [] for cat_id, text in enumerate(streams): tokens = torch.tensor(encode(text), dtype=torch.long) for i in range(0, len(tokens) - seq_len, stride): self.inputs.append(tokens[i : i + seq_len]) self.targets.append(tokens[i + 1 : i + seq_len + 1]) self.cat_ids.append(cat_id) def __len__(self): return len(self.inputs) def __getitem__(self, i): return self.inputs[i], self.targets[i], self.cat_ids[i] # split whole documents (not windows) into train / val, so no story, code file, # or equation appears in both. the earlier random split over overlapping windows # leaked train content into val, which made the val loss look far too good def build_train_val_datasets(samples_per_cat, seq_len, val_fraction=0.1): documents_per_cat = [ load_stories(samples_per_cat), load_code(samples_per_cat // 5), make_math(samples_per_cat * 10), ] train_streams = [] val_streams = [] for documents in documents_per_cat: num_val = max(1, int(val_fraction * len(documents))) train_streams.append(''.join(documents[:-num_val])) val_streams.append(''.join(documents[-num_val:])) train_dataset = WindowDataset(train_streams, seq_len, stride=seq_len) val_dataset = WindowDataset(val_streams, seq_len, stride=seq_len) return train_dataset, val_dataset if __name__ == '__main__': print(make_math(5))