File size: 3,323 Bytes
262eb2c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
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()