Download src/data/prepare_text.py from Frankenstein-Labs/Cortex-0: direct link, hf CLI and curl.
- Browser
- Download file 4.81 kB
-
https://huggingface.co/Frankenstein-Labs/Cortex-0/resolve/main/src/data/prepare_text.py
- Command line
-
hf download hf://Frankenstein-Labs/Cortex-0/src/data/prepare_text.py
-
curl -L -o prepare_text.py https://huggingface.co/Frankenstein-Labs/Cortex-0/resolve/main/src/data/prepare_text.py
4.81 kB
| """Phase 3 — préparation du corpus texte tokenisé pour l'entraînement. | |
| Produit deux tableaux binaires `uint16` (le vocabulaire 32 000 tient sur 16 bits) : | |
| `train.bin` et `val.bin`, plus un `meta.json` (provenance, tailles, dtypes). | |
| Chaque document est terminé par `<|eos|>` : le modèle apprend où s'arrêtent les | |
| documents. Aucun modèle pré-entraîné n'est utilisé — seulement du texte brut. | |
| Usage : | |
| python -m src.data.prepare_text --preset wikipedia_fr --max-mb 200 \\ | |
| --out-dir data/processed/wikipedia_fr | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| from collections.abc import Iterable, Iterator | |
| from pathlib import Path | |
| import numpy as np | |
| from src.tokenizer import CortexTokenizer, load_tokenizer | |
| from src.tokenizer.special_tokens import EOS_ID | |
| from src.tokenizer.train_tokenizer import CORPUS_PRESETS, iter_hf_corpus, iter_local_corpus | |
| ROOT = Path(__file__).resolve().parents[2] | |
| DTYPE = np.uint16 | |
| def tokenize_corpus( | |
| texts: Iterable[str], | |
| tokenizer: CortexTokenizer, | |
| add_eos: bool = True, | |
| batch_size: int = 1000, | |
| ) -> np.ndarray: | |
| """Concatène les documents tokenisés (+ `<|eos|>` entre eux) en un tableau `uint16`. | |
| Les documents sont encodés par **lots** (``encode_batch``, implémenté en Rust), | |
| ce qui est nettement plus rapide qu'un appel Python par document. | |
| """ | |
| out: list[int] = [] | |
| batch: list[str] = [] | |
| def flush() -> None: | |
| if not batch: | |
| return | |
| for ids in tokenizer.raw.encode_batch(batch): | |
| out.extend(ids.ids) | |
| if add_eos: | |
| out.append(EOS_ID) | |
| batch.clear() | |
| for text in texts: | |
| batch.append(text) | |
| if len(batch) >= batch_size: | |
| flush() | |
| flush() | |
| arr = np.asarray(out, dtype=np.int64) | |
| if arr.size and arr.max() > np.iinfo(DTYPE).max: | |
| raise ValueError("id de token > 65535 : dtype uint16 insuffisant") | |
| return arr.astype(DTYPE) | |
| def split_train_val(arr: np.ndarray, val_frac: float = 0.01) -> tuple[np.ndarray, np.ndarray]: | |
| """Découpe séquentielle (pas de fuite : la val est la fin du flux).""" | |
| if not 0.0 < val_frac < 1.0: | |
| raise ValueError("val_frac doit être dans ]0, 1[") | |
| n_val = max(1, int(len(arr) * val_frac)) | |
| return arr[:-n_val], arr[-n_val:] | |
| def save_bin(arr: np.ndarray, path: Path) -> None: | |
| path = Path(path) | |
| path.parent.mkdir(parents=True, exist_ok=True) | |
| arr.tofile(path) | |
| def load_bin(path: Path, mode: str = "r") -> np.memmap: | |
| """Charge un tableau tokenisé en mémoire virtuelle (aucune copie RAM).""" | |
| return np.memmap(path, dtype=DTYPE, mode=mode) | |
| def prepare( | |
| texts: Iterable[str], | |
| out_dir: Path, | |
| tokenizer: CortexTokenizer, | |
| val_frac: float = 0.01, | |
| extra_meta: dict | None = None, | |
| ) -> dict: | |
| """Tokenise, découpe, écrit `train.bin`/`val.bin`/`meta.json` et retourne les métadonnées.""" | |
| out_dir = Path(out_dir) | |
| out_dir.mkdir(parents=True, exist_ok=True) | |
| arr = tokenize_corpus(texts, tokenizer, add_eos=True) | |
| train, val = split_train_val(arr, val_frac) | |
| save_bin(train, out_dir / "train.bin") | |
| save_bin(val, out_dir / "val.bin") | |
| meta = { | |
| "tokenizer_vocab_size": tokenizer.vocab_size, | |
| "dtype": "uint16", | |
| "add_eos": True, | |
| "val_frac": val_frac, | |
| "n_train_tokens": int(train.size), | |
| "n_val_tokens": int(val.size), | |
| "n_total_tokens": int(arr.size), | |
| } | |
| if extra_meta: | |
| meta.update(extra_meta) | |
| (out_dir / "meta.json").write_text(json.dumps(meta, ensure_ascii=False, indent=2), encoding="utf-8") | |
| return meta | |
| def _iter_source(args) -> Iterator[str]: | |
| max_bytes = int(args.max_mb * 1024 * 1024) | |
| if args.local_corpus: | |
| return iter_local_corpus(args.local_corpus, max_bytes=max_bytes) | |
| return iter_hf_corpus(args.preset, max_bytes=max_bytes) | |
| def main() -> None: | |
| parser = argparse.ArgumentParser(description="Préparer un corpus texte tokenisé.") | |
| src = parser.add_mutually_exclusive_group(required=True) | |
| src.add_argument("--local-corpus", type=Path) | |
| src.add_argument("--preset", choices=sorted(CORPUS_PRESETS)) | |
| parser.add_argument("--max-mb", type=float, default=200.0) | |
| parser.add_argument("--val-frac", type=float, default=0.01) | |
| parser.add_argument("--out-dir", type=Path, required=True) | |
| parser.add_argument("--tokenizer-dir", type=Path, default=None) | |
| args = parser.parse_args() | |
| tokenizer = load_tokenizer(args.tokenizer_dir) | |
| meta = prepare( | |
| _iter_source(args), | |
| args.out_dir, | |
| tokenizer, | |
| val_frac=args.val_frac, | |
| extra_meta={"preset": args.preset, "max_mb": args.max_mb}, | |
| ) | |
| print(json.dumps(meta, ensure_ascii=False, indent=2)) | |
| if __name__ == "__main__": | |
| main() | |