LUNA / Base /scripts /audit_5b_pretrain.py
ASTERIZER
LUNA 100M: cloud-ready training pipeline
ad68b7f
Raw
History Blame Contribute Delete
20.9 kB
# -*- coding: utf-8 -*-
"""
COMPREHENSIVE AUDIT of litdata_pretrain_final (5B tokens)
Checks:
1. Binary integrity β€” all 308 chunks readable, headers valid, correct dims
2. Token range validation β€” all tokens in [0, vocab_size), no garbage
3. EOS placement β€” correct document boundaries
4. Duplicate detection β€” sample chunks for near-duplicate documents
5. Token distribution β€” check for anomalous frequency spikes (noise)
6. Decode quality β€” random sample of 50 documents, decode + inspect
7. Training readiness β€” correct block_size, total tokens match config
This script is READ-ONLY. It does NOT modify data.
"""
import json
import os
import re
import time
import hashlib
from pathlib import Path
from collections import Counter, defaultdict
import numpy as np
ROOT = Path(__file__).resolve().parent.parent.parent
FINAL_DIR = ROOT / "Base" / "data" / "litdata_pretrain_final"
TOKENIZER_PATH = str(ROOT / "Base" / "checkpoints" / "EleutherAI" / "pythia-160m" / "tokenizer.json")
BLOCK_SIZE = 1025
DTYPE = np.int32
EOS_TOKEN_ID = 0
VOCAB_SIZE = 50277
# How many chunks to deeply scan for duplicates (all 308 is fine for audit)
MAX_CHUNKS_DEEP = 308
# Number of random documents to decode and display
DECODE_SAMPLES = 50
# Min hash length for near-duplicate detection (chars)
HASH_WINDOW = 200
def read_chunk(filepath):
"""Read a single chunk binary file. Returns (blocks, num_blocks)."""
with open(filepath, "rb") as f:
raw = f.read()
# Parse header
num_blocks = np.frombuffer(raw[:4], dtype=np.uint32)[0]
header_size = 4 + (num_blocks + 1) * 4 # 1 uint32 + (num_blocks+1) offsets
data_bytes = raw[header_size:]
expected_tokens = num_blocks * BLOCK_SIZE
expected_bytes = expected_tokens * DTYPE().itemsize
tokens = np.frombuffer(data_bytes[:expected_bytes], dtype=DTYPE)
return tokens, int(num_blocks)
def extract_documents(tokens):
"""Split a token array at EOS boundaries into individual documents."""
eos_positions = np.where(tokens == EOS_TOKEN_ID)[0]
docs = []
start = 0
for eos_pos in eos_positions:
if eos_pos > start:
doc_tokens = tokens[start:eos_pos]
if len(doc_tokens) > 0:
docs.append(doc_tokens)
start = eos_pos + 1
# Trailing tokens (no final EOS β€” partial doc carried across chunks)
if start < len(tokens):
remaining = tokens[start:]
if len(remaining) > 5: # ignore tiny fragments
docs.append(remaining)
return docs
def token_hash(doc_tokens, window=200):
"""Hash first N tokens for near-duplicate detection."""
key = doc_tokens[:window].tobytes()
return hashlib.md5(key).hexdigest()
def main():
t_start = time.time()
# Load index
with open(FINAL_DIR / "index.json") as f:
index = json.load(f)
chunks_meta = index["chunks"]
config = index.get("config", {})
num_chunks = len(chunks_meta)
print(f"{'='*75}")
print(f" COMPREHENSIVE AUDIT β€” litdata_pretrain_final")
print(f"{'='*75}")
print(f" Chunks in index: {num_chunks}")
print(f" Config: {config}")
print(f" Expected BLOCK_SIZE: {BLOCK_SIZE}")
print(f" Expected VOCAB_SIZE: {VOCAB_SIZE}")
print(f" Expected EOS: {EOS_TOKEN_ID}")
print()
# ═════════════════════════════════════════════════════════════════════
# CHECK 1: Binary integrity + token range
# ═════════════════════════════════════════════════════════════════════
print(f" CHECK 1: BINARY INTEGRITY + TOKEN RANGE")
print(f" {'-'*60}")
total_tokens = 0
total_blocks = 0
missing_files = []
corrupt_chunks = []
out_of_range_chunks = []
eos_counts = []
chunk_token_counts = []
# Global token frequency (sample every 10th chunk for speed)
global_freq = Counter()
FREQ_SAMPLE_INTERVAL = 3 # sample every 3rd chunk
# For duplicate detection
doc_hashes = defaultdict(list) # hash -> [(chunk_idx, doc_idx)]
total_docs = 0
doc_lengths = []
# For decode sampling: collect random docs
sample_docs = []
np.random.seed(42)
for ci, meta in enumerate(chunks_meta):
filename = meta["filename"]
filepath = FINAL_DIR / filename
expected_dim = meta["dim"]
if not filepath.exists():
missing_files.append(filename)
print(f" MISSING: {filename}")
continue
try:
tokens, num_blocks = read_chunk(filepath)
except Exception as e:
corrupt_chunks.append((filename, str(e)))
print(f" CORRUPT: {filename} β€” {e}")
continue
actual_dim = len(tokens)
if actual_dim != expected_dim:
corrupt_chunks.append((filename, f"dim mismatch: expected {expected_dim}, got {actual_dim}"))
print(f" DIM MISMATCH: {filename} expected {expected_dim}, got {actual_dim}")
total_tokens += actual_dim
total_blocks += num_blocks
chunk_token_counts.append(actual_dim)
# Token range check
min_tok = int(tokens.min())
max_tok = int(tokens.max())
if min_tok < 0 or max_tok >= VOCAB_SIZE:
out_of_range_chunks.append((filename, min_tok, max_tok))
print(f" OUT OF RANGE: {filename} min={min_tok} max={max_tok}")
# EOS count
eos_count = int(np.sum(tokens == EOS_TOKEN_ID))
eos_counts.append(eos_count)
# Token frequency (sampled)
if ci % FREQ_SAMPLE_INTERVAL == 0:
unique, counts = np.unique(tokens, return_counts=True)
for tok, cnt in zip(unique, counts):
global_freq[int(tok)] += int(cnt)
# Extract documents for duplicate + quality check
if ci < MAX_CHUNKS_DEEP:
docs = extract_documents(tokens)
for di, doc in enumerate(docs):
total_docs += 1
doc_lengths.append(len(doc))
if len(doc) >= 50:
h = token_hash(doc)
doc_hashes[h].append((ci, di))
# Random sample for decode
if len(sample_docs) < DECODE_SAMPLES and np.random.random() < 0.0005:
sample_docs.append(doc)
if (ci + 1) % 50 == 0:
print(f" Scanned {ci+1}/{num_chunks} chunks... ({total_tokens:,} tokens)")
print(f" Scanned all {num_chunks} chunks: {total_tokens:,} total tokens")
print()
# Results for Check 1
issues = []
if missing_files:
issues.append(f"MISSING FILES: {len(missing_files)}")
if corrupt_chunks:
issues.append(f"CORRUPT CHUNKS: {len(corrupt_chunks)}")
if out_of_range_chunks:
issues.append(f"OUT-OF-RANGE TOKENS: {len(out_of_range_chunks)} chunks")
if not issues:
print(f" βœ“ All {num_chunks} chunks intact, all tokens in [0, {VOCAB_SIZE})")
else:
for issue in issues:
print(f" βœ— {issue}")
print()
# ═════════════════════════════════════════════════════════════════════
# CHECK 2: EOS + DOCUMENT BOUNDARIES
# ═════════════════════════════════════════════════════════════════════
print(f" CHECK 2: EOS & DOCUMENT BOUNDARIES")
print(f" {'-'*60}")
total_eos = sum(eos_counts)
avg_eos = total_eos / max(num_chunks, 1)
min_eos = min(eos_counts) if eos_counts else 0
max_eos = max(eos_counts) if eos_counts else 0
print(f" Total EOS tokens: {total_eos:,}")
print(f" Avg EOS per chunk: {avg_eos:.1f}")
print(f" Min/Max EOS per chunk: {min_eos} / {max_eos}")
print(f" Total documents found: {total_docs:,}")
if doc_lengths:
dl = np.array(doc_lengths)
print(f" Doc length (tokens): min={int(dl.min())}, median={int(np.median(dl))}, "
f"mean={int(dl.mean())}, max={int(dl.max())}")
tiny_docs = int(np.sum(dl < 20))
short_docs = int(np.sum(dl < 50))
long_docs = int(np.sum(dl > 50000))
print(f" Tiny docs (<20 tok): {tiny_docs:,} ({100*tiny_docs/len(dl):.2f}%)")
print(f" Short docs (<50 tok): {short_docs:,} ({100*short_docs/len(dl):.2f}%)")
print(f" Very long docs (>50K tok): {long_docs:,}")
# Flag if too many tiny docs (noise)
if doc_lengths and tiny_docs / len(dl) > 0.05:
print(f" ⚠ WARNING: {100*tiny_docs/len(dl):.1f}% tiny docs β€” possible noise")
else:
print(f" βœ“ Document boundaries look healthy")
print()
# ═════════════════════════════════════════════════════════════════════
# CHECK 3: DUPLICATE DETECTION
# ═════════════════════════════════════════════════════════════════════
print(f" CHECK 3: NEAR-DUPLICATE DETECTION")
print(f" {'-'*60}")
dup_groups = {h: locs for h, locs in doc_hashes.items() if len(locs) > 1}
dup_doc_count = sum(len(locs) - 1 for locs in dup_groups.values())
print(f" Unique doc hashes: {len(doc_hashes):,}")
print(f" Duplicate groups: {len(dup_groups):,}")
print(f" Duplicate docs (extra copies): {dup_doc_count:,}")
dup_pct = 100 * dup_doc_count / max(total_docs, 1)
print(f" Duplication rate: {dup_pct:.3f}%")
if dup_pct > 5.0:
print(f" ⚠ WARNING: High duplication rate ({dup_pct:.1f}%). Consider deduplication.")
elif dup_pct > 1.0:
print(f" ⚠ MODERATE: {dup_pct:.2f}% duplicates. Acceptable but not ideal.")
else:
print(f" βœ“ Very low duplication ({dup_pct:.3f}%). Excellent.")
# Show a few duplicate examples
if dup_groups:
print(f"\n Top 5 duplicate groups (by copy count):")
sorted_dups = sorted(dup_groups.items(), key=lambda x: -len(x[1]))[:5]
for h, locs in sorted_dups:
print(f" hash={h[:12]}... : {len(locs)} copies in chunks {[l[0] for l in locs[:6]]}")
print()
# ═════════════════════════════════════════════════════════════════════
# CHECK 4: TOKEN DISTRIBUTION (anomaly detection)
# ═════════════════════════════════════════════════════════════════════
print(f" CHECK 4: TOKEN DISTRIBUTION ANALYSIS")
print(f" {'-'*60}")
total_sampled = sum(global_freq.values())
print(f" Sampled tokens: {total_sampled:,} (from every {FREQ_SAMPLE_INTERVAL}rd chunk)")
# Top 20 most frequent tokens
most_common = global_freq.most_common(30)
print(f" Top 30 tokens by frequency:")
for tok_id, count in most_common:
pct = 100 * count / total_sampled
print(f" token {tok_id:>6}: {count:>12,} ({pct:>5.2f}%)")
# Check for suspicious spikes
# If any single non-EOS token is >10% of all tokens, it's suspicious
suspicious = []
for tok_id, count in most_common:
pct = count / total_sampled
if tok_id != EOS_TOKEN_ID and pct > 0.10:
suspicious.append((tok_id, pct))
if suspicious:
print(f"\n ⚠ SUSPICIOUS: These non-EOS tokens appear in >10% of data:")
for tok_id, pct in suspicious:
print(f" token {tok_id}: {100*pct:.2f}%")
else:
print(f"\n βœ“ No anomalous token frequency spikes detected")
# Vocab coverage
unique_tokens = len(global_freq)
coverage_pct = 100 * unique_tokens / VOCAB_SIZE
print(f" Unique tokens seen: {unique_tokens:,} / {VOCAB_SIZE:,} ({coverage_pct:.1f}% vocab coverage)")
# Check for dead zones (large ranges of unused tokens)
used_set = set(global_freq.keys())
unused_ranges = []
start_unused = None
for i in range(VOCAB_SIZE):
if i not in used_set:
if start_unused is None:
start_unused = i
else:
if start_unused is not None:
gap = i - start_unused
if gap > 500:
unused_ranges.append((start_unused, i - 1, gap))
start_unused = None
if unused_ranges:
print(f" Large unused token ranges (>500):")
for s, e, g in unused_ranges[:5]:
print(f" tokens {s}-{e} ({g} unused)")
print()
# ═════════════════════════════════════════════════════════════════════
# CHECK 5: DECODE QUALITY β€” sample random documents
# ═════════════════════════════════════════════════════════════════════
print(f" CHECK 5: DECODED DOCUMENT SAMPLES")
print(f" {'-'*60}")
# If we didn't get enough samples randomly, grab from specific chunks
if len(sample_docs) < DECODE_SAMPLES:
# Grab from spread-out chunks
sample_chunks = np.linspace(0, num_chunks - 1, min(DECODE_SAMPLES - len(sample_docs), 25), dtype=int)
for sci in sample_chunks:
if len(sample_docs) >= DECODE_SAMPLES:
break
meta = chunks_meta[sci]
filepath = FINAL_DIR / meta["filename"]
try:
tokens, _ = read_chunk(filepath)
docs = extract_documents(tokens)
if docs:
# Pick a random doc from this chunk
idx = np.random.randint(0, len(docs))
sample_docs.append(docs[idx])
except:
pass
# Now decode
from tokenizers import Tokenizer
tokenizer = Tokenizer.from_file(TOKENIZER_PATH)
quality_issues = 0
noise_docs = 0
non_english_docs = 0
RE_CJK = re.compile(r'[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]{3,}')
RE_ARABIC = re.compile(r'[\u0600-\u06ff]{5,}')
RE_CYRILLIC = re.compile(r'[\u0400-\u04ff]{5,}')
for si, doc_tokens in enumerate(sample_docs[:DECODE_SAMPLES]):
text = tokenizer.decode(doc_tokens.tolist(), skip_special_tokens=False)
# Quality checks
words = text.split()
word_count = len(words)
alpha_ratio = sum(c.isalpha() for c in text) / max(len(text), 1)
unique_words = len(set(w.lower() for w in words)) / max(word_count, 1)
is_noisy = False
is_non_english = False
flags = []
if alpha_ratio < 0.50:
flags.append(f"low-alpha({alpha_ratio:.2f})")
is_noisy = True
if word_count > 30 and unique_words < 0.15:
flags.append(f"repetitive({unique_words:.2f})")
is_noisy = True
if RE_CJK.search(text) or RE_ARABIC.search(text) or RE_CYRILLIC.search(text):
flags.append("non-English")
is_non_english = True
if word_count < 10:
flags.append("very-short")
is_noisy = True
if is_noisy:
noise_docs += 1
if is_non_english:
non_english_docs += 1
if flags:
quality_issues += 1
# Print preview for flagged + a few clean ones
if flags or si < 5 or si % 10 == 0:
preview = text[:300].replace('\n', ' ↡ ')
status = f"⚠ {','.join(flags)}" if flags else "βœ“ clean"
print(f"\n Sample {si+1}/{len(sample_docs[:DECODE_SAMPLES])} [{status}] ({word_count} words, alpha={alpha_ratio:.2f}, unique={unique_words:.2f})")
print(f" \"{preview}...\"")
print(f"\n DECODE SUMMARY:")
print(f" Samples checked: {min(len(sample_docs), DECODE_SAMPLES)}")
print(f" Clean: {min(len(sample_docs), DECODE_SAMPLES) - quality_issues}")
print(f" Noisy: {noise_docs}")
print(f" Non-English: {non_english_docs}")
print(f" Quality issues: {quality_issues}")
if quality_issues / max(len(sample_docs), 1) > 0.1:
print(f" ⚠ WARNING: >10% of sampled docs have quality issues")
else:
print(f" βœ“ Sample quality looks good")
print()
# ═════════════════════════════════════════════════════════════════════
# CHECK 6: TRAINING READINESS
# ═════════════════════════════════════════════════════════════════════
print(f" CHECK 6: TRAINING READINESS")
print(f" {'-'*60}")
index_total = sum(c["dim"] for c in chunks_meta)
print(f" Index total tokens: {index_total:,}")
print(f" Actual total tokens: {total_tokens:,}")
if index_total == total_tokens:
print(f" βœ“ Index matches actual data perfectly")
else:
print(f" ⚠ MISMATCH: index says {index_total:,} but files have {total_tokens:,}")
# Check all blocks are BLOCK_SIZE aligned
misaligned = [c["filename"] for c in chunks_meta if c["dim"] % BLOCK_SIZE != 0]
if misaligned:
print(f" ⚠ {len(misaligned)} chunks not BLOCK_SIZE-aligned: {misaligned[:5]}")
else:
print(f" βœ“ All chunks perfectly BLOCK_SIZE-aligned ({BLOCK_SIZE})")
# Config check
if config.get("block_size") == BLOCK_SIZE:
print(f" βœ“ Config block_size matches: {BLOCK_SIZE}")
else:
print(f" ⚠ Config block_size: {config.get('block_size')} (expected {BLOCK_SIZE})")
if config.get("vocab_size") == VOCAB_SIZE:
print(f" βœ“ Config vocab_size matches: {VOCAB_SIZE}")
else:
print(f" ⚠ Config vocab_size: {config.get('vocab_size')} (expected {VOCAB_SIZE})")
# Tokens per epoch for batch size calculation
print(f"\n TRAINING PARAMETERS:")
seq_len = 1024
steps = total_tokens // (120 * seq_len) # global_batch=120
print(f" Total tokens: {total_tokens:,}")
print(f" Global batch size 120 Γ— seq 1024 = {120*1024:,} tokens/step")
print(f" Total steps for 1 epoch: {steps:,}")
print(f" At ~10 steps/sec (A100): ~{steps/10/60:.0f} min β‰ˆ {steps/10/3600:.1f} hours")
print()
# ═════════════════════════════════════════════════════════════════════
# OVERALL VERDICT
# ═════════════════════════════════════════════════════════════════════
all_issues = []
if missing_files: all_issues.append(f"{len(missing_files)} missing files")
if corrupt_chunks: all_issues.append(f"{len(corrupt_chunks)} corrupt chunks")
if out_of_range_chunks: all_issues.append(f"{len(out_of_range_chunks)} out-of-range chunks")
if dup_pct > 5.0: all_issues.append(f"High duplication: {dup_pct:.1f}%")
if suspicious: all_issues.append("Suspicious token spikes")
if doc_lengths and tiny_docs / len(dl) > 0.05: all_issues.append("Too many tiny docs")
if quality_issues / max(len(sample_docs), 1) > 0.1: all_issues.append("Quality issues in samples")
elapsed = time.time() - t_start
print(f"{'='*75}")
print(f" AUDIT VERDICT")
print(f"{'='*75}")
if not all_issues:
print(f" βœ“ ALL CHECKS PASSED β€” Dataset is TRAINING-READY")
print(f" {total_tokens:,} clean tokens across {num_chunks} chunks")
print(f" No corruption, minimal duplicates, good quality")
else:
print(f" ⚠ ISSUES FOUND:")
for issue in all_issues:
print(f" - {issue}")
print(f"\n Audit completed in {elapsed:.1f}s")
print(f"{'='*75}")
if __name__ == "__main__":
main()