| """ |
| Faz 1 / Adım 1 — Pre-tokenize + parquet shard (kaynak bazlı). |
| |
| Akış: HF streaming -> sc_tokenizer (SP) ile encode + EOS -> 2048'lik dizilere paketle |
| (doc sınırları arası carry-over) -> ~256MB parquet shard'lar + manifest.json. |
| Mixture EĞİTİMDE uygulanır; shard'lar source/lang etiketli tutulur. |
| |
| Kaynaklar (--source): |
| en_fineweb_edu : HuggingFaceFW/fineweb-edu (sample-10BT) lang=en |
| tr_fineweb2_hq : epfml/FineWeb2-HQ (tur_Latn) lang=tr |
| tr_tc100b : lumees/turkish-corpus-100b (data_dir=pretrain, ~103B, Apache) lang=tr [v1.5b ANA TR] |
| |
| Kullanım: |
| python kod/faz1_01_tokenize_shard.py --source en_fineweb_edu --target_tokens 5_000_000 --seqs_per_shard 1500 |
| """ |
| import os, sys, json, time, argparse |
| os.environ["HF_HUB_DISABLE_SYMLINKS_WARNING"] = "1" |
| sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) |
| import pyarrow as pa |
| import pyarrow.parquet as pq |
| from datasets import load_dataset |
| from sc_tokenizer import SCTokenizer |
| from decontam import Decontaminator |
|
|
| SOURCES = { |
| "en_fineweb_edu": ("HuggingFaceFW/fineweb-edu", "sample-10BT", "en"), |
| "tr_fineweb2_hq": ("epfml/FineWeb2-HQ", "tur_Latn", "tr"), |
| |
| "tr_tc100b": ("lumees/turkish-corpus-100b", None, "tr", "pretrain"), |
| |
| |
| "code_codeparrot": ("codeparrot/codeparrot-clean", None, "code"), |
| "math_openwebmath": ("open-web-math/open-web-math", None, "math"), |
| } |
|
|
| |
| TEXT_KEYS = ("text", "content", "code") |
|
|
|
|
| def text_of(rec): |
| for k in TEXT_KEYS: |
| v = rec.get(k) |
| if isinstance(v, str) and v: |
| return v |
| for v in rec.values(): |
| if isinstance(v, str) and len(v) > 0: |
| return v |
| return "" |
|
|
|
|
| def main(): |
| ap = argparse.ArgumentParser() |
| ap.add_argument("--source", required=True, choices=list(SOURCES)) |
| ap.add_argument("--target_tokens", type=lambda x: int(float(x)), default=5_000_000) |
| ap.add_argument("--seq_len", type=int, default=2048) |
| ap.add_argument("--seqs_per_shard", type=int, default=32768) |
| ap.add_argument("--out_dir", default="kod/data/shards") |
| ap.add_argument("--decontam", default="kod/data/decontam_13gram.pkl.gz", |
| help="13-gram index; yoksa filtre atlanır") |
| ap.add_argument("--n_shards", type=int, default=1, help="paralel worker sayısı (ds.shard ile parça böl)") |
| ap.add_argument("--shard_index", type=int, default=0, help="bu worker'ın parça indeksi (0..n_shards-1)") |
| args = ap.parse_args() |
|
|
| src = SOURCES[args.source] |
| repo, cfg, lang = src[0], src[1], src[2] |
| data_dir = src[3] if len(src) > 3 else None |
| outdir = os.path.join(args.out_dir, args.source) |
| if args.n_shards > 1: |
| outdir = os.path.join(outdir, f"w{args.shard_index:02d}") |
| os.makedirs(outdir, exist_ok=True) |
| per_target = args.target_tokens // max(1, args.n_shards) |
| tok = SCTokenizer() |
| dec = None |
| if args.decontam and os.path.exists(args.decontam): |
| dec = Decontaminator.load(args.decontam) |
| print(f"[decontam] {len(dec.grams):,} adet 13-gram yüklendi → kontamine doc'lar atlanacak") |
| else: |
| print("[decontam] index yok → filtre KAPALI (uyarı: eval sızıntısı kontrol edilmiyor)") |
| print(f"[{args.source} w{args.shard_index}/{args.n_shards}] repo={repo} dir={data_dir} lang={lang} " |
| f"| seq_len={args.seq_len} | hedef ~{per_target/1e6:.1f}M token | vocab={tok.vocab_size}") |
|
|
| ds = load_dataset(repo, name=cfg, data_dir=data_dir, split="train", streaming=True) |
| if args.n_shards > 1: |
| ds = ds.shard(num_shards=args.n_shards, index=args.shard_index) |
| buf, seqs, shard_idx = [], [], 0 |
| tot_tokens = tot_seqs = ndocs = n_skip = 0 |
| manifest = {"source": args.source, "repo": repo, "config": cfg, "data_dir": data_dir, "lang": lang, |
| "seq_len": args.seq_len, "shards": []} |
| t0 = time.perf_counter() |
|
|
| def flush(): |
| nonlocal seqs, shard_idx, tot_seqs |
| if not seqs: |
| return |
| fn = f"shard_{shard_idx:05d}.parquet" |
| table = pa.table({ |
| |
| "input_ids": pa.array(seqs, type=pa.list_(pa.uint16())), |
| "source": pa.array([args.source] * len(seqs)), |
| "lang": pa.array([lang] * len(seqs)), |
| }) |
| pq.write_table(table, os.path.join(outdir, fn), compression="zstd") |
| manifest["shards"].append({"file": fn, "n_seqs": len(seqs)}) |
| tot_seqs += len(seqs) |
| shard_idx += 1 |
| seqs = [] |
|
|
| for rec in ds: |
| ndocs += 1 |
| txt = text_of(rec) |
| if dec is not None and dec.is_contaminated(txt): |
| n_skip += 1 |
| continue |
| buf.extend(tok.encode(txt, add_eos=True)) |
| while len(buf) >= args.seq_len: |
| seqs.append(buf[:args.seq_len]); buf = buf[args.seq_len:] |
| tot_tokens += args.seq_len |
| if len(seqs) >= args.seqs_per_shard: |
| flush() |
| if tot_tokens >= per_target: |
| break |
| if ndocs % 5000 == 0: |
| dt = time.perf_counter() - t0 |
| print(f" {tot_tokens/1e6:6.2f}M tok | {ndocs} doc | {shard_idx} shard | " |
| f"{tot_tokens/1e6/max(1e-9,dt):.2f} M tok/s", flush=True) |
|
|
| flush() |
| manifest["n_seqs"] = tot_seqs |
| manifest["n_tokens"] = tot_seqs * args.seq_len |
| manifest["n_docs"] = ndocs |
| manifest["n_decontam_skipped"] = n_skip |
| manifest["decontam"] = bool(dec) |
| with open(os.path.join(outdir, "manifest.json"), "w", encoding="utf-8") as f: |
| json.dump(manifest, f, ensure_ascii=False, indent=2) |
| dt = time.perf_counter() - t0 |
| skip_pct = 100 * n_skip / max(1, ndocs) |
| print(f"[{args.source}] BİTTİ: {tot_seqs} dizi × {args.seq_len} = {tot_seqs*args.seq_len/1e6:.2f}M token " |
| f"| {shard_idx} shard | {ndocs} doc | {dt:.0f}s ({tot_tokens/1e6/max(1e-9,dt):.2f} M tok/s)") |
| print(f" dekontaminasyon: {n_skip} doc atlandı ({skip_pct:.2f}%) | -> {outdir}/") |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|