matilda-jev-fp4 / kev /data.py
yue-maincode's picture
Upload validated MATILDA JEV FP4 model and Decision Index scores
c69aaec verified
Raw History Blame Contribute Delete
4.98 kB
"""Build and validate decision JSONL folds.
`kev-data build RECIPE OUTPUT` builds frozen folds from the archived sources
(see kev.build and configs/data/*.toml).
`kev-data validate train.jsonl dev.jsonl ...` checks every row against the
Example schema, summarizes suites/question types, and rejects ID or
(dataset, family) overlap between the given files.
"""
import argparse
import json
import math
import os
from collections import Counter
from pathlib import Path
from kev.evaluate import options, read_rows
from kev.model import BASE_MODEL, MAX_OPTIONS
from kev.types import Example
def validate_row(row: Example) -> list[str]:
errors: list[str] = []
for key in ("id", "suite", "family"):
if not isinstance(row.get(key), str) or not row.get(key):
errors.append(f"missing {key}")
if not isinstance(row.get("source"), dict):
errors.append("missing source")
question = row.get("question")
if not isinstance(question, dict) or question.get("type") not in ("choice", "noul", "score"):
return errors + ["question.type must be choice, noul or score"]
if question["type"] == "choice" and (not isinstance(question.get("criteria"), dict) or not question["criteria"]):
return errors + ["choice questions need a nonempty criteria object"]
if question["type"] == "score" and (not isinstance(question.get("criteria"), list) or len(question["criteria"]) < 2):
return errors + ["score questions need at least two criteria levels"]
labels = options(question)
if len(labels) > MAX_OPTIONS:
errors.append(f"more than {MAX_OPTIONS} options")
target, label = row.get("target"), row.get("label")
if isinstance(target, list):
if len(target) != len(labels) or any(not isinstance(p, (int, float)) or not math.isfinite(p) or p < 0 for p in target) \
or abs(sum(target) - 1) > 1e-6:
errors.append("soft target must be a distribution over the options")
elif question["type"] == "noul":
if isinstance(target, bool):
pass
elif not isinstance(target, (int, float)) or not 0 <= float(target) <= 1:
errors.append("noul target must be a bool or probability")
elif not any(type(target) is type(value) and target == value for value in labels):
errors.append("target is not an option")
if not any(type(label) is type(value) and label == value for value in labels):
errors.append("hard label is not an option")
return errors
def validate(paths: list[Path]) -> bool:
from kev.train import check_partitions
ok = True
partitions: dict[str, list[Example]] = {}
for path in paths:
rows = read_rows(path)
partitions[str(path)] = rows
failures = Counter[str]()
for row in rows:
for error in validate_row(row):
failures[error] += 1
summary = {
"rows": len(rows),
"suites": dict(Counter(row.get("suite") for row in rows).most_common(15)),
"types": dict(Counter(row["question"]["type"] for row in rows if isinstance(row.get("question"), dict))),
"images": sum(bool(row.get("images")) for row in rows),
"errors": dict(failures),
}
print(json.dumps({str(path): summary}, indent=2, ensure_ascii=False))
ok = ok and not failures
if len(partitions) > 1:
try:
check_partitions(partitions)
print("No ID or family overlap between files.")
except ValueError as error:
print(f"Overlap: {error}")
ok = False
return ok
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
commands = parser.add_subparsers(dest="command", required=True)
check = commands.add_parser("validate", help="Schema, summary and overlap checks for JSONL folds")
check.add_argument("paths", type=Path, nargs="+")
make = commands.add_parser("build", help="Build frozen folds from a recipe TOML")
make.add_argument("recipe", type=Path)
make.add_argument("output", type=Path, help="New directory for the build")
make.add_argument("--base-model", default=BASE_MODEL, help="Tokenizer used to count prompt tokens")
make.add_argument("--workers", type=int, default=min(16, os.cpu_count() or 1))
make.add_argument("--limit", type=int, help="Read at most this many questions per file (quick pipeline check)")
args = parser.parse_args()
if args.command == "validate" and not validate(args.paths):
raise SystemExit(1)
if args.command == "build":
from kev.build import build
manifest = build(args.recipe, args.output, args.base_model, args.workers, args.limit)
print(json.dumps({"summary": manifest["summary"], "parts": manifest["parts"]}, indent=2, ensure_ascii=False))
print(f"Wrote {args.output}")
if __name__ == "__main__":
main()