Download evaluation/evaluate.py from jyliuAI/diffusion_baseline: direct link, hf CLI and curl.
- Browser
- Download file 6 kB
-
https://huggingface.co/jyliuAI/diffusion_baseline/resolve/main/evaluation/evaluate.py
- Command line
-
hf download hf://jyliuAI/diffusion_baseline/evaluation/evaluate.py
-
curl -L -o evaluate.py https://huggingface.co/jyliuAI/diffusion_baseline/resolve/main/evaluation/evaluate.py
6 kB
| """CLI for atomically evaluating canonical sample JSONL artifacts.""" | |
| from __future__ import annotations | |
| import argparse | |
| from dataclasses import asdict | |
| import json | |
| from pathlib import Path | |
| from typing import Sequence | |
| from dlb.io import atomic_json_write, sha256_file, validate_samples | |
| from dlb.schema import SampleRecord | |
| from .generative_perplexity import ( | |
| compute_gen_ppl, | |
| load_offline_gpt2_large, | |
| resolve_gpt2_assets, | |
| ) | |
| from .self_bleu import SelfBleuConfig, compute_self_bleu | |
| from .unigram_entropy import mean_unigram_entropy | |
| PRODUCTION_SAMPLE_COUNT = 1024 | |
| METRIC_NAMES = ("gen_ppl", "entropy", "self_bleu") | |
| DATASET_PADDING_IDS = {"lm1b": frozenset({0}), "owt": frozenset()} | |
| def parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace: | |
| parser = argparse.ArgumentParser(description=__doc__) | |
| parser.add_argument("--samples", type=Path, required=True) | |
| parser.add_argument("--metrics", default=",".join(METRIC_NAMES)) | |
| parser.add_argument("--output", type=Path, required=True) | |
| parser.add_argument("--root", type=Path, default=Path(__file__).resolve().parents[1]) | |
| parser.add_argument("--dataset", choices=tuple(DATASET_PADDING_IDS)) | |
| parser.add_argument("--special-id", type=int, action="append", default=[]) | |
| parser.add_argument("--batch-size", type=int, default=8) | |
| parser.add_argument("--context-length", type=int, default=1024) | |
| parser.add_argument("--device", default="cuda") | |
| parser.add_argument( | |
| "--allow-partial", | |
| action="store_true", | |
| help="allow a non-production sample count and label the artifact partial", | |
| ) | |
| return parser.parse_args(argv) | |
| def _metrics(value: str) -> tuple[str, ...]: | |
| names = tuple(part.strip() for part in value.split(",") if part.strip()) | |
| if not names: | |
| raise ValueError("at least one metric is required") | |
| unknown = sorted(set(names) - set(METRIC_NAMES)) | |
| if unknown: | |
| raise ValueError(f"unknown metrics: {', '.join(unknown)}") | |
| if len(set(names)) != len(names): | |
| raise ValueError("metric names must not be duplicated") | |
| return names | |
| def _load_records(path: Path) -> list[SampleRecord]: | |
| records = [] | |
| with path.open("r", encoding="utf-8") as source: | |
| for line in source: | |
| records.append(SampleRecord.model_validate(json.loads(line))) | |
| return records | |
| def _display_path(path: Path, root: Path) -> str: | |
| try: | |
| return path.resolve().relative_to(root.resolve()).as_posix() | |
| except ValueError: | |
| return path.resolve().as_posix() | |
| def evaluate(args: argparse.Namespace) -> dict[str, object]: | |
| root = args.root.resolve() | |
| metric_names = _metrics(args.metrics) | |
| expected = None if args.allow_partial else PRODUCTION_SAMPLE_COUNT | |
| sample_count = validate_samples(args.samples, expected=expected) | |
| if sample_count < 1: | |
| raise ValueError("sample artifact is empty") | |
| partial = sample_count != PRODUCTION_SAMPLE_COUNT | |
| if partial and not args.allow_partial: | |
| raise ValueError("partial input requires --allow-partial") | |
| if not partial and args.special_id: | |
| raise ValueError( | |
| "production evaluation forbids --special-id; dataset padding policy is fixed" | |
| ) | |
| if not partial and "entropy" in metric_names and args.dataset is None: | |
| raise ValueError("production entropy evaluation requires --dataset") | |
| records = _load_records(args.samples) | |
| texts = [record.text for record in records] | |
| result_metrics: dict[str, object] = {} | |
| if "gen_ppl" in metric_names: | |
| assets = resolve_gpt2_assets(root) | |
| model, tokenizer = load_offline_gpt2_large(assets, device=args.device) | |
| ppl = compute_gen_ppl( | |
| texts, | |
| model, | |
| tokenizer, | |
| batch_size=args.batch_size, | |
| max_length=args.context_length, | |
| model_revision=assets.model_revision, | |
| tokenizer_revision=assets.tokenizer_revision, | |
| device=args.device, | |
| ) | |
| result_metrics["generative_perplexity"] = { | |
| **asdict(ppl), | |
| "model_snapshot": _display_path(assets.model_path, root), | |
| "tokenizer_snapshot": _display_path(assets.tokenizer_path, root), | |
| "offline": True, | |
| } | |
| excluded = set() | |
| exclusion_source = "none_partial" | |
| special_token_policy = "partial_no_token_exclusions" | |
| if args.dataset is not None: | |
| excluded.update(DATASET_PADDING_IDS[args.dataset]) | |
| exclusion_source = "dataset_contract" | |
| special_token_policy = "exclude_documented_padding_only_preserve_bos_eos" | |
| if partial and args.special_id: | |
| excluded.update(args.special_id) | |
| exclusion_source = "custom_partial_cli" | |
| special_token_policy = "custom_partial_not_baseline_comparable" | |
| if "entropy" in metric_names: | |
| entropy = mean_unigram_entropy(records, excluded) | |
| result_metrics["unigram_entropy"] = { | |
| **asdict(entropy), | |
| "special_token_policy": special_token_policy, | |
| "exclusion_source": exclusion_source, | |
| } | |
| if "self_bleu" in metric_names: | |
| result_metrics["self_bleu"] = asdict( | |
| compute_self_bleu(texts, SelfBleuConfig()) | |
| ) | |
| return { | |
| "schema_version": 1, | |
| "samples": _display_path(args.samples, root), | |
| "samples_sha256": sha256_file(args.samples), | |
| "sample_count": sample_count, | |
| "production_sample_count": PRODUCTION_SAMPLE_COUNT, | |
| "partial": partial, | |
| "dataset": args.dataset, | |
| "requested_metrics": list(metric_names), | |
| "metrics": result_metrics, | |
| } | |
| def main(argv: Sequence[str] | None = None) -> int: | |
| args = parse_args(argv) | |
| document = evaluate(args) | |
| atomic_json_write(args.output, document) | |
| print( | |
| f"WROTE {args.output} samples={document['sample_count']} " | |
| f"partial={str(document['partial']).lower()}" | |
| ) | |
| return 0 | |
| if __name__ == "__main__": | |
| raise SystemExit(main()) | |