fractus-cte-atom / scripts /build_atom_corpus.py
thefinalboss's picture
Publish Fractus CTE-Atom: Atomizer spans on the HF engine, vocab 266, from scratch.
d74aa69 verified
Raw History Blame Contribute Delete
3.98 kB
#!/usr/bin/env python
"""Retokenize raw text with the Atomizer adapted to Fractus.
Does not read GPT-2 ``.pt`` corpora. Those ids are vocab 50257 and are
invalid for this head. Input is jsonl / txt / jsonl.gz. Output is an int16
memmap plus a small json sidecar (vocab, span, pack mode, token count).
Usage:
python scripts/build_atom_corpus.py --src data/raw --out data/atom_corpus
"""
from __future__ import annotations
import argparse
import gzip
import json
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import numpy as np
from fractus.atom_tokenizer import VOCAB_SIZE, AtomFractusTokenizer
def extract_text(entry: dict) -> str:
for key in ("text", "content", "output", "completion", "response"):
val = entry.get(key)
if isinstance(val, str) and val.strip():
return val
msgs = entry.get("messages") or entry.get("conversations")
if isinstance(msgs, list):
parts = []
for msg in msgs:
if isinstance(msg, dict):
parts.append(str(msg.get("content") or msg.get("value") or ""))
elif isinstance(msg, str):
parts.append(msg)
return "\n".join(p for p in parts if p)
return ""
def iter_texts(path: str):
opener = gzip.open if path.endswith(".gz") else open
if path.endswith(".txt"):
with opener(path, "rt", encoding="utf-8", errors="replace") as handle:
yield handle.read()
return
with opener(path, "rt", encoding="utf-8", errors="replace") as handle:
for line in handle:
line = line.strip()
if not line:
continue
try:
entry = json.loads(line)
except json.JSONDecodeError:
yield line
continue
if isinstance(entry, str):
yield entry
elif isinstance(entry, dict):
text = extract_text(entry)
if text:
yield text
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--src", required=True, help="file or directory of txt/jsonl")
ap.add_argument("--out", required=True, help="output prefix (writes .i16 and .json)")
ap.add_argument("--max-span-bytes", type=int, default=32)
ap.add_argument("--pack-mode", default="linguistic")
ap.add_argument("--min-chars", type=int, default=8)
args = ap.parse_args()
paths = []
if os.path.isdir(args.src):
for root, _, files in os.walk(args.src):
for name in files:
if name.endswith((".jsonl", ".jsonl.gz", ".txt", ".txt.gz")):
paths.append(os.path.join(root, name))
else:
paths.append(args.src)
if not paths:
raise SystemExit(f"no raw text under {args.src}")
tok = AtomFractusTokenizer(max_span_bytes=args.max_span_bytes, pack_mode=args.pack_mode)
os.makedirs(os.path.dirname(os.path.abspath(args.out)) or ".", exist_ok=True)
bin_path = args.out if args.out.endswith(".i16") else args.out + ".i16"
ids = []
docs = 0
for path in paths:
for text in iter_texts(path):
if len(text) < args.min_chars:
continue
ids.extend(tok.encode(text))
docs += 1
arr = np.asarray(ids, dtype=np.int16)
arr.tofile(bin_path)
meta = {
"tokenizer": AtomFractusTokenizer.VERSION,
"vocab_size": VOCAB_SIZE,
"max_span_bytes": args.max_span_bytes,
"pack_mode": args.pack_mode,
"docs": docs,
"tokens": int(arr.size),
"bin": os.path.basename(bin_path),
"dtype": "int16",
"note": "fresh Atom id stream. Do not mix with GPT-2 50257 shards.",
}
with open(os.path.splitext(bin_path)[0] + ".json", "w", encoding="utf-8") as handle:
json.dump(meta, handle, indent=2)
print(json.dumps(meta))
if __name__ == "__main__":
main()