from __future__ import annotations import argparse import json import math from pathlib import Path from transformers import AutoTokenizer from .data import discover_files, iter_documents def parser() -> argparse.ArgumentParser: value = argparse.ArgumentParser(description="Scan training data and count its tokens") value.add_argument("--data", required=True, help="File or directory containing training data") value.add_argument("--tokenizer", default="assets/tokenizer", help="Local tokenizer path or Hub ID") value.add_argument("--text-field", default="text", help="Text column in structured files") value.add_argument("--max-documents", type=int, default=0, help="Stop after N documents; 0 scans all") value.add_argument("--min-parameters", type=int, default=1_000_000) value.add_argument("--max-parameters", type=int, default=50_000_000) value.add_argument("--tokens-per-parameter", type=float, default=20.0) value.add_argument("--output", default="artifacts/data_report.json") return value def scan(args: argparse.Namespace) -> dict: files = discover_files(args.data) tokenizer = AutoTokenizer.from_pretrained(args.tokenizer, use_fast=True) documents = characters = bytes_total = tokens = 0 minimum = math.inf maximum = 0 for text in iter_documents(files, args.text_field): count = len(tokenizer.encode(text, add_special_tokens=False)) documents += 1 characters += len(text) bytes_total += len(text.encode("utf-8")) tokens += count minimum = min(minimum, count) maximum = max(maximum, count) if args.max_documents and documents >= args.max_documents: break if documents == 0: raise ValueError("The scan found no non-empty documents") raw_target = round(tokens / args.tokens_per_parameter) suggested = min(args.max_parameters, max(args.min_parameters, raw_target)) report = { "data_path": str(Path(args.data).expanduser().resolve()), "files": len(files), "documents_scanned": documents, "characters": characters, "utf8_bytes": bytes_total, "tokens": tokens, "tokens_per_document": tokens / documents, "minimum_document_tokens": int(minimum), "maximum_document_tokens": maximum, "tokenizer": args.tokenizer, "tokenizer_vocab_size": len(tokenizer), "target_rule": { "tokens_per_parameter": args.tokens_per_parameter, "unclamped_parameters": raw_target, "minimum_parameters": args.min_parameters, "maximum_parameters": args.max_parameters, "suggested_parameters": suggested, "status": "inside_range" if raw_target == suggested else "clamped_to_requested_range" }, "warning": "The token-to-parameter value is a planning input, not a proven LiquidWVY scaling law. Change --tokens-per-parameter for your compute and reuse goals." } destination = Path(args.output) destination.parent.mkdir(parents=True, exist_ok=True) destination.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8") return report def main() -> None: args = parser().parse_args() print(json.dumps(scan(args), indent=2)) if __name__ == "__main__": main()