jvonrad commited on
Commit
f4fc57b
·
verified ·
1 Parent(s): 94c46f3

Upload src/xscript/data/pack.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. src/xscript/data/pack.py +97 -0
src/xscript/data/pack.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Tokenize text pools into uint16 token shards (one .bin per pool shard).
2
+
3
+ Documents are shuffled within each pool shard (seeded) and packed as
4
+ <bos> doc <eos> <bos> doc <eos> ... with no padding; the training loader
5
+ reads contiguous (seq_len+1)-token windows. Vocab is 65536 so uint16 is
6
+ exact.
7
+ """
8
+ import json
9
+ import multiprocessing as mp
10
+ from pathlib import Path
11
+
12
+ import numpy as np
13
+
14
+ from ..paths import pool_dir, shard_dir, tokenizer_dir, ensure
15
+
16
+ ENCODE_BATCH = 512
17
+
18
+
19
+ def _iter_pool_docs(path: Path):
20
+ import zstandard
21
+ with open(path, "rb") as raw:
22
+ reader = zstandard.ZstdDecompressor().stream_reader(raw)
23
+ import io
24
+ for line in io.TextIOWrapper(reader, encoding="utf-8"):
25
+ if line.strip():
26
+ yield json.loads(line)["text"]
27
+
28
+
29
+ def _pack_one(args) -> tuple[str, int]:
30
+ pool_shard, out_path, tok_dir, seed = args
31
+ from ..tok.wrapper import Tok
32
+ import random
33
+ tok = Tok(tok_dir)
34
+ docs = list(_iter_pool_docs(Path(pool_shard)))
35
+ random.Random(seed).shuffle(docs)
36
+ n = 0
37
+ with open(out_path, "wb") as out:
38
+ for i in range(0, len(docs), ENCODE_BATCH):
39
+ batch = tok.encode_batch(docs[i:i + ENCODE_BATCH], bos=True, eos=True)
40
+ flat = np.concatenate([np.asarray(ids, dtype=np.uint16) for ids in batch])
41
+ flat.tofile(out)
42
+ n += len(flat)
43
+ return str(out_path), n
44
+
45
+
46
+ def pack(lang: str, tok_name: str, workers: int = 8, seed: int = 1234,
47
+ max_tokens: float | None = None) -> dict:
48
+ src = pool_dir(lang)
49
+ all_shards = sorted(src.glob("pool_*.jsonl.zst"))
50
+ if not all_shards:
51
+ raise FileNotFoundError(f"no pool shards in {src} - run `xscript pool --lang {lang}`")
52
+ # While `xscript pool` is still running, its writer may have the highest-
53
+ # indexed shard open (only closed on the next roll/checkpoint) -- hold it
54
+ # back so we never read a truncated file mid-download. Safe to include
55
+ # once the pool has reached its byte budget; picked up on the next
56
+ # incremental `pack` call otherwise.
57
+ pool_done = False
58
+ stats_path = src / "stats.json"
59
+ if stats_path.exists():
60
+ pst = json.loads(stats_path.read_text())
61
+ pool_done = pst["text_bytes"] >= pst["budget_bytes"] * 0.99
62
+ shards = all_shards if pool_done else all_shards[:-1]
63
+ out = ensure(shard_dir(lang, tok_name))
64
+ tok_dir = str(tokenizer_dir(tok_name))
65
+
66
+ index_path = out / "index.json"
67
+ index = json.loads(index_path.read_text()) if index_path.exists() else {}
68
+ import zlib
69
+ jobs = []
70
+ for s in shards:
71
+ dst = out / (s.name.replace(".jsonl.zst", ".bin"))
72
+ if dst.name not in index:
73
+ jobs.append((str(s), str(dst), tok_dir, seed ^ zlib.crc32(s.name.encode())))
74
+
75
+ total = sum(index.values())
76
+ if jobs:
77
+ with mp.Pool(workers) as pool:
78
+ for dst, n in pool.imap_unordered(_pack_one, jobs):
79
+ if n == 0:
80
+ # Empty pool checkpoint shards contain no documents. Do
81
+ # not expose a zero-byte file to np.memmap in PackedStream.
82
+ Path(dst).unlink(missing_ok=True)
83
+ continue
84
+ index[Path(dst).name] = n
85
+ total += n
86
+ index_path.write_text(json.dumps(index, indent=2, sort_keys=True))
87
+ print(f"[pack] {lang}/{tok_name}: {Path(dst).name} = {n/1e6:.1f}M tokens "
88
+ f"(total {total/1e9:.2f}B)")
89
+ if max_tokens and total >= max_tokens:
90
+ print("[pack] reached max_tokens; stopping")
91
+ pool.terminate()
92
+ break
93
+ meta = {"lang": lang, "tokenizer": tok_name, "total_tokens": total,
94
+ "n_shards": len(index)}
95
+ (out / "meta.json").write_text(json.dumps(meta, indent=2))
96
+ print(f"[pack] {lang}/{tok_name}: {total/1e9:.3f}B tokens in {len(index)} shards")
97
+ return meta