File size: 6,663 Bytes
ec24fb0 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | """Choose the continued-pretraining mix from data/corpus/clean -> data/corpus/<name>/ (+ reports/<name>_stats.json).
Default mix of ~300M estimated Gemma tokens:
wikipedia all of it (encyclopedic, highest priority)
fineweb2 the rest of the Sinhala budget: 70% highest Sri Lanka/curriculum keyword score (sl_score), 30% random
nsina 20% of the budget, random news articles
english 10% of the budget, FineWeb-Edu sample (keeps general/English reasoning), not deduplicated against Sinhala
Cross-source near-duplicates (MinHash on word 5-gram shingles, Jaccard >= 0.8) are removed in priority order
wikipedia > fineweb2 > nsina. Writes 30 random Sinhala documents to sample_for_review.md for a human check.
Usage: python scripts/select_cpt_subset.py [--target-tokens 300e6] [--name cpt_v1]
"""
from __future__ import annotations
import argparse
import json
import random
import sys
from multiprocessing import Pool
from pathlib import Path
import numpy as np
import pandas as pd
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
sys.path.insert(0, str(Path(__file__).resolve().parent))
from build_cpt_corpus import norm # noqa: E402
from eval.common import ROOT, load_env # noqa: E402
TOK_PER_CHAR = {"si": 0.40, "en": 0.25} # Gemma tokenizer, measured on Sinhala MCQs / typical English
NUM_PERM = 64
ENGLISH = ("HuggingFaceFW/fineweb-edu", "sample/10BT/000_00000.parquet")
def minhash(text: str):
from datasketch import MinHash
w = norm(text).split()
m = MinHash(num_perm=NUM_PERM, seed=1)
m.update_batch([" ".join(w[i:i + 5]).encode() for i in range(max(len(w) - 4, 1))])
return m
def load_source(clean: Path, name: str) -> pd.DataFrame:
return pd.concat([pd.read_parquet(f) for f in sorted(clean.glob(f"{name}-*.parquet"))], ignore_index=True)
def take_chars(df: pd.DataFrame, chars: float) -> pd.DataFrame:
return df[df["n_chars"].cumsum() <= chars]
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--target-tokens", type=float, default=300e6)
ap.add_argument("--news-share", type=float, default=0.20)
ap.add_argument("--english-share", type=float, default=0.10)
ap.add_argument("--name", default="cpt_v1")
ap.add_argument("--workers", type=int, default=16)
ap.add_argument("--seed", type=int, default=1234)
args = ap.parse_args()
load_env()
from datasketch import MinHashLSH
from huggingface_hub import HfApi, hf_hub_download
clean, out = ROOT / "data" / "corpus" / "clean", ROOT / "data" / "corpus" / args.name
out.mkdir(parents=True, exist_ok=True)
rng = np.random.default_rng(args.seed)
si_chars = args.target_tokens * (1 - args.english_share) / TOK_PER_CHAR["si"]
margin = 1.15 # select extra, then trim after deduplication
wiki = load_source(clean, "wikipedia")
fw = load_source(clean, "fineweb2")
news = load_source(clean, "nsina")
news_chars = args.target_tokens * args.news_share / TOK_PER_CHAR["si"]
fw_chars = si_chars - wiki["n_chars"].sum() - news_chars
fw_top = take_chars(fw.sort_values("sl_score", ascending=False), 0.7 * fw_chars * margin)
fw_rest = fw.drop(fw_top.index).sample(frac=1, random_state=args.seed)
fw_sel = pd.concat([fw_top.assign(pick="top_sl_score"),
take_chars(fw_rest, 0.3 * fw_chars * margin).assign(pick="random")])
news_sel = take_chars(news.sample(frac=1, random_state=args.seed), news_chars * margin).assign(pick="random")
cand = pd.concat([wiki.assign(pick="all"), fw_sel, news_sel], ignore_index=True)
print(f"candidates: {len(cand):,} docs, {cand['n_chars'].sum() / 1e6:.0f}M chars", flush=True)
with Pool(args.workers) as pool:
sigs = pool.map(minhash, cand["text"].tolist(), chunksize=256)
lsh = MinHashLSH(threshold=0.8, num_perm=NUM_PERM)
keep = np.zeros(len(cand), dtype=bool)
for i, m in enumerate(sigs): # cand is ordered wikipedia, fineweb2, nsina = priority order
if not lsh.query(m):
lsh.insert(str(i), m)
keep[i] = True
removed = cand[~keep].groupby("source").size().to_dict()
cand = cand[keep]
parts = {
"wikipedia": cand[cand.source == "wikipedia"],
"fineweb2": pd.concat([take_chars(cand[(cand.source == "fineweb2") & (cand.pick == p)], s * fw_chars)
for p, s in (("top_sl_score", 0.7), ("random", 0.3))]),
"nsina": take_chars(cand[cand.source == "nsina"], news_chars),
}
# English: FineWeb-Edu sample
sha = HfApi().dataset_info(ENGLISH[0]).sha
path = hf_hub_download(ENGLISH[0], ENGLISH[1], repo_type="dataset", revision=sha,
local_dir=ROOT / "data" / "raw" / "corpus" / "fineweb-edu")
en = pd.read_parquet(path, columns=["id", "url", "text"]).sample(frac=1, random_state=args.seed)
en["n_chars"] = en["text"].str.len()
en = take_chars(en, args.target_tokens * args.english_share / TOK_PER_CHAR["en"])
parts["english"] = en.assign(source="fineweb_edu", sl_score=0.0, pick="random")
stats = {"target_tokens": args.target_tokens, "near_duplicates_removed": removed,
"english_revision": f"{ENGLISH[0]}@{sha}", "parts": {}}
for name, df in parts.items():
lang = "en" if name == "english" else "si"
df[["id", "source", "url", "text", "n_chars", "sl_score", "pick"]].to_parquet(out / f"{name}.parquet", index=False)
stats["parts"][name] = {"docs": len(df), "chars": int(df["n_chars"].sum()),
"est_gemma_tokens": int(df["n_chars"].sum() * TOK_PER_CHAR[lang]),
"picks": df["pick"].value_counts().to_dict()}
stats["total_est_gemma_tokens"] = sum(p["est_gemma_tokens"] for p in stats["parts"].values())
(ROOT / "reports" / f"{args.name}_stats.json").write_text(json.dumps(stats, indent=2))
print(json.dumps(stats, indent=2))
# Human review sample (stays under data/, git-ignored)
r = random.Random(args.seed)
lines = [f"# {args.name}: 30 random Sinhala documents for review", "",
"Check: is it real Sinhala prose? boilerplate/menus? machine-translated junk? exam material?", ""]
for name, k in (("wikipedia", 8), ("fineweb2", 14), ("nsina", 8)):
df = parts[name]
for i in r.sample(range(len(df)), k):
d = df.iloc[i]
lines += [f"## {name} / {d['pick']} / sl_score {d['sl_score']} / {d['url']}", "", d["text"][:1200], ""]
(out / "sample_for_review.md").write_text("\n".join(lines), encoding="utf-8")
print(f"review sample -> {out / 'sample_for_review.md'}")
if __name__ == "__main__":
main()
|