limoXD's picture
Release v0.2 Mozc-backed Japanese IME reranker
f11438f verified
Raw
History Blame Contribute Delete
5.92 kB
from __future__ import annotations
import argparse
import json
import sys
import time
from collections.abc import Callable, Sequence
from pathlib import Path
from typing import TextIO
import torch
from . import profiles as profile_config
from .benchmark_v2_runner import (
FrozenProfile,
V2BenchmarkConfig,
run_dev_selection,
run_external_evaluation,
write_v2_outputs,
)
from .deberta import DebertaCandidateScorer
from .domain import CandidateScorer
from .mozc import MozcDictionaryIndex
from .ud_gsd import CorpusArtifact, load_pinned_split
from .ud_pud import PUD_LICENSE, PUD_REPOSITORY, load_pinned_test
ArtifactLoader = Callable[[str, Path], CorpusArtifact]
def _parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(prog="deberta-ime-benchmark-v2")
subparsers = parser.add_subparsers(dest="stage", required=True)
dev = subparsers.add_parser("dev", help="select a profile on pinned GSD dev only")
external = subparsers.add_parser(
"external", help="evaluate a frozen profile on pinned Japanese-PUD"
)
for command in (dev, external):
command.add_argument("--index", type=Path, required=True)
command.add_argument("--data-dir", type=Path, default=Path("work/data"))
command.add_argument("--output-dir", type=Path, default=Path("outputs"))
command.add_argument("--stem", required=True)
command.add_argument("--limit", type=int, default=0, help="0 evaluates all rows")
command.add_argument("--pool-size", type=int, default=8)
command.add_argument("--bootstrap-samples", type=int, default=5000)
command.add_argument("--example-limit", type=int, default=20)
command.add_argument("--seed", type=int, default=20260810)
command.add_argument("--device", default="cpu")
command.add_argument("--model-cache-dir", type=Path)
command.add_argument("--offline", action="store_true")
command.add_argument("--threads", type=int, default=min(8, torch.get_num_threads()))
dev.add_argument("--context-mode", choices=("left_only", "bidirectional"), required=True)
external.add_argument(
"--profile", choices=tuple(profile_config.MOZC_PROFILES), required=True
)
return parser
def run(
argv: Sequence[str] | None = None,
*,
stdout: TextIO | None = None,
stderr: TextIO | None = None,
scorer: CandidateScorer | None = None,
artifact_loader: ArtifactLoader | None = None,
) -> int:
args = _parser().parse_args(argv)
output_stream = stdout or sys.stdout
error_stream = stderr or sys.stderr
if args.stage == "external" and profile_config.MOZC_PROFILE_STATE != "frozen":
error_stream.write("error: Mozc profiles are not frozen from GSD dev\n")
return 2
torch.set_num_threads(max(1, args.threads))
active_scorer = scorer or DebertaCandidateScorer(
device=args.device,
cache_dir=args.model_cache_dir,
local_files_only=args.offline,
)
model_load_seconds: float | None = None
loader = getattr(active_scorer, "load", None)
if callable(loader):
started = time.perf_counter()
loader()
model_load_seconds = time.perf_counter() - started
def progress(completed: int, total: int) -> None:
if completed == total or completed % 100 == 0:
error_stream.write(f"[{args.stage}] {completed}/{total}\n")
error_stream.flush()
active_loader = artifact_loader or _load_artifact
config = V2BenchmarkConfig(
pool_size=args.pool_size,
limit=args.limit or None,
seed=args.seed,
bootstrap_samples=args.bootstrap_samples,
example_limit=args.example_limit,
)
artifact = active_loader(args.stage, args.data_dir)
with MozcDictionaryIndex(args.index) as index:
if args.stage == "dev":
benchmark = run_dev_selection(
active_scorer,
index=index,
artifact=artifact,
context_mode=args.context_mode,
config=config,
model_load_seconds=model_load_seconds,
progress=progress,
)
else:
active_profile = profile_config.MOZC_PROFILES[args.profile]
benchmark = run_external_evaluation(
active_scorer,
index=index,
artifact=artifact,
dataset_repository=PUD_REPOSITORY,
dataset_license=PUD_LICENSE,
profile=FrozenProfile(
name=args.profile,
context_mode=(
"left_only" if args.profile == "incremental" else "bidirectional"
),
prior_weight=active_profile.prior_weight,
min_margin=active_profile.min_margin,
selected_on=(
f"UD Japanese-GSD dev {profile_config.MOZC_PROFILE_SELECTION_REVISION}"
),
),
config=config,
model_load_seconds=model_load_seconds,
progress=progress,
)
json_path, markdown_path = write_v2_outputs(
benchmark,
output_dir=args.output_dir,
stem=args.stem,
)
summary = {
"ok": True,
"stage": benchmark.report["stage"],
"json": str(json_path),
"markdown": str(markdown_path),
"coverage": benchmark.report["coverage"],
}
output_stream.write(json.dumps(summary, ensure_ascii=False, indent=2) + "\n")
return 0
def _load_artifact(stage: str, data_dir: Path) -> CorpusArtifact:
if stage == "dev":
return load_pinned_split("dev", data_dir / "ud-japanese-gsd")
return load_pinned_test(data_dir / "ud-japanese-pud")
def main() -> None:
raise SystemExit(run())
if __name__ == "__main__":
main()