Spaces:
Paused
Paused
| """ | |
| Pretraining Data Pipeline for Baby GLM-5.2 | |
| ============================================ | |
| Streams, tokenizes, and writes 3.3B tokens to binary files for pretraining. | |
| Data Sources: | |
| 1. FineWeb-Edu -- 2.0B tokens (high-quality web text) | |
| 2. Wikipedia EN -- 0.7B tokens (encyclopedic knowledge) | |
| 3. Project Gutenberg -- 0.4B tokens (literary text) | |
| 4. StarCoder Python -- 0.2B tokens (code, optional) | |
| Output: | |
| data/train.bin -- ~3.267B tokens, binary uint16 memmap | |
| data/val.bin -- ~0.033B tokens, binary uint16 memmap | |
| data/meta.json -- tokenizer info, vocab size, token counts | |
| Usage: | |
| python scripts/dataprep_pretrain.py # Full 3.3B token run | |
| python scripts/dataprep_pretrain.py --total_tokens 10000000 # Quick 10M test | |
| python scripts/dataprep_pretrain.py --no_code # Skip code data | |
| Estimated runtime: 2-4 hours on CPU with good internet (full run) | |
| Estimated disk: ~7 GB for output .bin files | |
| """ | |
| import argparse | |
| import json | |
| import os | |
| import time | |
| import numpy as np | |
| import tiktoken | |
| from tqdm import tqdm | |
| # ============================================================================= | |
| # Configuration | |
| # ============================================================================= | |
| DEFAULT_OUTPUT_DIR = "./data" | |
| DEFAULT_TOKENIZER = "gpt2" | |
| DEFAULT_TOTAL_TOKENS = 3_300_000_000 # 3.3B tokens (Chinchilla+ for 120M params) | |
| DEFAULT_VAL_RATIO = 0.01 # 1% validation split | |
| DEFAULT_WRITE_CHUNK = 1_000_000 # Flush to disk every 1M tokens | |
| # Data source allocation (fraction of total tokens) | |
| SOURCE_ALLOCATIONS = { | |
| "fineweb_edu": 0.606, # ~2.0B tokens | |
| "wikipedia": 0.212, # ~0.7B tokens | |
| "gutenberg": 0.121, # ~0.4B tokens | |
| "starcoder": 0.061, # ~0.2B tokens (optional) | |
| } | |
| # End-of-text token for GPT-2 tokenizer | |
| GPT2_EOT = 50256 | |
| # ============================================================================= | |
| # Dataset Iterators | |
| # ============================================================================= | |
| # Each iterator yields raw text strings from a HuggingFace dataset stream. | |
| # Streaming avoids holding the full dataset in memory. | |
| # ============================================================================= | |
| def iter_fineweb_edu(): | |
| """Stream high-quality educational web text from FineWeb-Edu.""" | |
| from datasets import load_dataset | |
| ds = load_dataset( | |
| "HuggingFaceFW/fineweb-edu", | |
| name="sample-10BT", | |
| split="train", | |
| streaming=True, | |
| ) | |
| for example in ds: | |
| text = example.get("text", "") | |
| if text: | |
| yield text | |
| def iter_wikipedia(): | |
| """Stream English Wikipedia articles (script-free wikimedia version).""" | |
| from datasets import load_dataset | |
| ds = load_dataset( | |
| "wikimedia/wikipedia", | |
| "20231101.en", | |
| split="train", | |
| streaming=True, | |
| ) | |
| for example in ds: | |
| text = example.get("text", "") | |
| if text: | |
| yield text | |
| def iter_gutenberg(): | |
| """Stream public domain books from Project Gutenberg (PG-19 subset).""" | |
| from datasets import load_dataset | |
| ds = load_dataset( | |
| "emozilla/pg19", | |
| split="train", | |
| streaming=True, | |
| ) | |
| for example in ds: | |
| # pg19 uses "text" for the full book text, with "short_book_title" as metadata | |
| text = example.get("text", "") | |
| if text: | |
| yield text | |
| def iter_starcoder(): | |
| """ | |
| Streams Python code from The Stack v2 (non-gated subset). | |
| To use the full StarCoder dataset instead, first authenticate: | |
| huggingface-cli login | |
| Then change 'bigcode/the-stack-v2-train-smol-ids' below to 'bigcode/starcoderdata'. | |
| """ | |
| from datasets import load_dataset | |
| try: | |
| # Try the non-gated smol subset first | |
| ds = load_dataset( | |
| "bigcode/the-stack-smol", | |
| data_dir="data/python", | |
| split="train", | |
| streaming=True, | |
| ) | |
| for example in ds: | |
| text = example.get("content", "") | |
| if text: | |
| yield text | |
| except Exception: | |
| # Fallback: use codeparrot's cleaned Python dataset | |
| ds = load_dataset( | |
| "codeparrot/codeparrot-clean", | |
| split="train", | |
| streaming=True, | |
| ) | |
| for example in ds: | |
| text = example.get("content", "") | |
| if text: | |
| yield text | |
| # Map source names to their iterators | |
| SOURCE_ITERATORS = { | |
| "fineweb_edu": iter_fineweb_edu, | |
| "wikipedia": iter_wikipedia, | |
| "gutenberg": iter_gutenberg, | |
| "starcoder": iter_starcoder, | |
| } | |
| # ============================================================================= | |
| # Tokenization & Writing | |
| # ============================================================================= | |
| def tokenize_and_write( | |
| source_name, | |
| text_iterator, | |
| target_tokens, | |
| encoder, | |
| eot_token, | |
| train_file, | |
| val_file, | |
| val_ratio, | |
| write_chunk, | |
| dtype, | |
| ): | |
| """ | |
| Tokenize text from an iterator and write tokens to train/val binary files. | |
| Each document is separated by an EOT token. Documents are randomly assigned | |
| to val split with probability val_ratio. | |
| Args: | |
| source_name: Name of the data source (for logging) | |
| text_iterator: Iterator yielding text strings | |
| target_tokens: Number of tokens to collect from this source | |
| encoder: tiktoken encoder | |
| eot_token: End-of-text token ID | |
| train_file: Open file handle for train.bin | |
| val_file: Open file handle for val.bin | |
| val_ratio: Fraction of documents for validation | |
| write_chunk: Buffer size before flushing to disk | |
| dtype: numpy dtype for token storage (uint16 or uint32) | |
| Returns: | |
| (train_tokens_written, val_tokens_written) | |
| """ | |
| train_buffer = [] | |
| val_buffer = [] | |
| train_total = 0 | |
| val_total = 0 | |
| doc_count = 0 | |
| rng = np.random.default_rng(seed=42 + hash(source_name) % 10000) | |
| pbar = tqdm( | |
| total=target_tokens, | |
| unit="tok", | |
| unit_scale=True, | |
| desc=f" {source_name}", | |
| bar_format=" {desc}: {percentage:3.0f}% |{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}]", | |
| ) | |
| for text in text_iterator: | |
| # Tokenize the document | |
| tokens = encoder.encode_ordinary(text) | |
| tokens.append(eot_token) # Separate documents with EOT | |
| # Randomly assign entire documents to train or val | |
| is_val = rng.random() < val_ratio | |
| if is_val: | |
| val_buffer.extend(tokens) | |
| val_total += len(tokens) | |
| # Flush val buffer | |
| if len(val_buffer) >= write_chunk: | |
| val_file.write(np.array(val_buffer, dtype=dtype).tobytes()) | |
| val_buffer = [] | |
| else: | |
| train_buffer.extend(tokens) | |
| train_total += len(tokens) | |
| # Flush train buffer | |
| if len(train_buffer) >= write_chunk: | |
| train_file.write(np.array(train_buffer, dtype=dtype).tobytes()) | |
| train_buffer = [] | |
| doc_count += 1 | |
| pbar.update(len(tokens)) | |
| # Check if the target has been reached | |
| if train_total + val_total >= target_tokens: | |
| break | |
| # Flush remaining buffers | |
| if train_buffer: | |
| train_file.write(np.array(train_buffer, dtype=dtype).tobytes()) | |
| if val_buffer: | |
| val_file.write(np.array(val_buffer, dtype=dtype).tobytes()) | |
| pbar.close() | |
| print(f" {source_name}: {doc_count:,} docs | " | |
| f"train {train_total:,} + val {val_total:,} = {train_total + val_total:,} tokens") | |
| return train_total, val_total | |
| # ============================================================================= | |
| # Main Pipeline | |
| # ============================================================================= | |
| def main(): | |
| parser = argparse.ArgumentParser( | |
| description="Pretraining Data Pipeline for Baby GLM-5.2", | |
| formatter_class=argparse.RawDescriptionHelpFormatter, | |
| ) | |
| parser.add_argument("--output_dir", type=str, default=DEFAULT_OUTPUT_DIR, | |
| help="Output directory for .bin and .json files") | |
| parser.add_argument("--total_tokens", type=int, default=DEFAULT_TOTAL_TOKENS, | |
| help="Total tokens to collect across all sources") | |
| parser.add_argument("--val_ratio", type=float, default=DEFAULT_VAL_RATIO, | |
| help="Fraction of documents for validation split") | |
| parser.add_argument("--write_chunk", type=int, default=DEFAULT_WRITE_CHUNK, | |
| help="Buffer size (tokens) before flushing to disk") | |
| parser.add_argument("--no_code", action="store_true", | |
| help="Exclude code data (StarCoder)") | |
| args = parser.parse_args() | |
| # --- Setup --- | |
| os.makedirs(args.output_dir, exist_ok=True) | |
| train_path = os.path.join(args.output_dir, "train.bin") | |
| val_path = os.path.join(args.output_dir, "val.bin") | |
| meta_path = os.path.join(args.output_dir, "meta.json") | |
| # --- Tokenizer --- | |
| encoder = tiktoken.get_encoding(DEFAULT_TOKENIZER) | |
| vocab_size = encoder.n_vocab # 50257 for GPT-2 | |
| eot_token = GPT2_EOT | |
| # Determine dtype: uint16 if vocab fits, uint32 otherwise | |
| if vocab_size <= 65535: | |
| dtype = np.uint16 | |
| dtype_str = "uint16" | |
| else: | |
| dtype = np.uint32 | |
| dtype_str = "uint32" | |
| # --- Compute per-source token targets --- | |
| include_code = not args.no_code | |
| active_sources = {k: v for k, v in SOURCE_ALLOCATIONS.items() | |
| if k != "starcoder" or include_code} | |
| # Renormalize allocations if code is excluded | |
| total_alloc = sum(active_sources.values()) | |
| source_targets = { | |
| k: int(args.total_tokens * v / total_alloc) | |
| for k, v in active_sources.items() | |
| } | |
| # --- Print Plan --- | |
| print(f"\n{'='*70}") | |
| print(f" Pretraining Data Pipeline for Baby GLM-5.2") | |
| print(f"{'='*70}") | |
| print(f" Tokenizer: {DEFAULT_TOKENIZER} (vocab_size={vocab_size})") | |
| print(f" Token dtype: {dtype_str}") | |
| print(f" Total target: {args.total_tokens:,} tokens") | |
| print(f" Val ratio: {args.val_ratio:.1%}") | |
| print(f" Output dir: {os.path.abspath(args.output_dir)}") | |
| print(f" Code data: {'Yes' if include_code else 'No'}") | |
| print(f"\n Source Allocation:") | |
| for name, target in source_targets.items(): | |
| print(f" {name:20s} {target:>14,} tokens ({target/args.total_tokens:.1%})") | |
| print(f"{'='*70}\n") | |
| # --- Process Each Source --- | |
| total_train = 0 | |
| total_val = 0 | |
| t0 = time.time() | |
| with open(train_path, "wb") as train_file, open(val_path, "wb") as val_file: | |
| for source_name, target in source_targets.items(): | |
| print(f"\n [{source_name}] Streaming {target:,} tokens...") | |
| iterator_fn = SOURCE_ITERATORS[source_name] | |
| try: | |
| train_written, val_written = tokenize_and_write( | |
| source_name=source_name, | |
| text_iterator=iterator_fn(), | |
| target_tokens=target, | |
| encoder=encoder, | |
| eot_token=eot_token, | |
| train_file=train_file, | |
| val_file=val_file, | |
| val_ratio=args.val_ratio, | |
| write_chunk=args.write_chunk, | |
| dtype=dtype, | |
| ) | |
| total_train += train_written | |
| total_val += val_written | |
| except Exception as e: | |
| print(f" [WARN] Error streaming {source_name}: {e}") | |
| print(f" Skipping this source and continuing...") | |
| continue | |
| elapsed = time.time() - t0 | |
| # --- Write Metadata --- | |
| meta = { | |
| "tokenizer": DEFAULT_TOKENIZER, | |
| "vocab_size": vocab_size, | |
| "eot_token": eot_token, | |
| "dtype": dtype_str, | |
| "train_tokens": total_train, | |
| "val_tokens": total_val, | |
| "total_tokens": total_train + total_val, | |
| "sources": list(source_targets.keys()), | |
| "val_ratio": args.val_ratio, | |
| } | |
| with open(meta_path, "w") as f: | |
| json.dump(meta, f, indent=2) | |
| # --- Summary --- | |
| train_size_gb = os.path.getsize(train_path) / 1e9 | |
| val_size_gb = os.path.getsize(val_path) / 1e9 | |
| print(f"\n{'='*70}") | |
| print(f" [DONE] Data preparation complete!") | |
| print(f"{'='*70}") | |
| print(f" Time elapsed: {elapsed/3600:.1f} hours ({elapsed:.0f}s)") | |
| print(f" Train tokens: {total_train:,}") | |
| print(f" Val tokens: {total_val:,}") | |
| print(f" Total tokens: {total_train + total_val:,}") | |
| print(f" train.bin: {train_size_gb:.2f} GB") | |
| print(f" val.bin: {val_size_gb:.2f} GB") | |
| print(f" meta.json: {meta_path}") | |
| print(f"\n Next step: python train_glm5.py --data_dir {args.output_dir}") | |
| print(f"{'='*70}\n") | |
| if __name__ == "__main__": | |
| main() | |