vimeml-tiny-zh-v1 / source /scripts /benchmarks /build_candidate_handoff_v2.py
Voltline's picture
Release vimeml-tiny-zh-v1 (step 844730): FP32 bundle, INT8 Core ML packages, source snapshot
81cffe7 verified
Raw History Blame Contribute Delete
7.18 kB
"""Second Mac candidate handoff: new frozen tracks at n-best 20 and 50, plus pool checks.
Tracks and why they are exported:
- standard-v2-blind, homophone-v1: new frozen releases (n20 for comparability with the
registered protocol, n50 for the next benchmark version);
- standard-v1-development: n50 measures how many pool misses a larger n-best recovers;
n20 repeats the original export to check determinism;
- standard-v1-development-nocontext (n20): the same inputs with empty left context,
to test whether AzooKey pools depend on context with Zenzai off (free-running
incremental evaluation is valid only if they do not);
- incremental-proto1: segmented development prototype.
No language model runs during export. Commands: package, import.
"""
import argparse
import copy
import hashlib
import json
import shutil
import sys
import zipfile
from datetime import datetime, timezone
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT / 'src'))
sys.path.insert(0, str(ROOT / 'scripts/benchmarks'))
from vimeml.benchmarks.expanded_ime import CONVERTER
from vimeml.benchmarks.standard_ime import FORMAT, load_standard_export
from vimeml.training.data import write_json
from prepare_standard_ime_v2 import read, fresh
from standard_ime import track_files
BENCH = ROOT / 'artifacts/benchmarks'
VERSION = 'ime-candidates-v2'
TRACKS = {
'standard-v2-blind': (BENCH / 'ime-standard-ja-v2-blind-ai-expert', 'blind', (20, 50)),
'homophone-v1': (BENCH / 'ime-homophone-ja-v1-ai-expert', 'challenge', (20, 50)),
'standard-v1-development': (BENCH / 'ime-standard-ja-v1-ai-expert-r1', 'development', (20, 50)),
'standard-v1-development-nocontext': (BENCH / 'ime-standard-ja-v1-ai-expert-r1', 'development', (20,)),
'incremental-proto1': (BENCH / 'ime-incremental-ja-proto1', 'segmented', (20, 50)),
}
FILES = ('ajimee-input.json', 'case-map.json', 'azookey-candidates.json', 'converter-version.txt',
'dictionary-versions.txt', 'swift-version.txt', 'export-flags.txt', 'completed.txt', 'export.log')
def items(track):
release, split, _ = TRACKS[track]
rows = read(release / split / 'evaluation_items.json')
if track.endswith('-nocontext'):
rows = copy.deepcopy(rows)
for row in rows:
row['context_text'] = ''
return rows
def package(args):
out = args.output
fresh(out)
tracks = {}
for track, (release, split, n_best) in TRACKS.items():
track_files(out / track, items(track))
(out / track / 'evaluation_items.json').unlink()
(out / track / 'stats.json').unlink()
manifest = read(release / 'manifest.json')
tracks[track] = {'release': release.name, 'release_id': manifest.get('release_id'), 'split': split,
'n_best': list(n_best), 'cases': len(read(out / track / 'case-map.json')),
'input_sha256': hashlib.sha256((out / track / 'ajimee-input.json').read_bytes()).hexdigest()}
# LF: the Mac shell reads this line by line (CRLF made n_best "20\r").
(out / 'tracks.tsv').write_text(''.join(f'{t}\t{n}\n' for t, v in tracks.items() for n in v['n_best']),
encoding='utf-8', newline='\n')
write_json(out / 'manifest.json', {'version': VERSION, 'created_utc': datetime.now(timezone.utc).isoformat(),
'converter': CONVERTER, 'flags': 'typo off, Zenzai disabled, stable off',
'tracks': tracks, 'lm_scoring': False})
for name in ('export_azookey_tracks.sh', 'run_candidates_mac.sh'):
shutil.copyfile(ROOT / 'scripts/benchmarks' / name, out / name)
shutil.copyfile(ROOT / 'templates/benchmarks/candidates-v2-mac.md', out / 'README.md')
for source in ('wrime', 'jmultiwoz'):
shutil.copyfile(ROOT / f'datasets/ime-standard-ja-v1/{source}/LICENSE', out / f'LICENSE-{source}.txt')
(out / 'NOTICE.md').write_text(
(BENCH / 'ime-standard-ja-v2-blind-ai-expert/NOTICE.md').read_text(encoding='utf-8') + '\n' +
(BENCH / 'ime-homophone-ja-v1-ai-expert/NOTICE.md').read_text(encoding='utf-8'), encoding='utf-8', newline='\n')
with zipfile.ZipFile(out.with_suffix('.zip'), 'x', zipfile.ZIP_DEFLATED) as archive:
for path in sorted(out.rglob('*')):
if path.is_file():
archive.write(path, str(path.relative_to(out.parent)))
print(json.dumps(tracks, indent=1))
def import_results(args):
"""Each (track, n-best) becomes a loadable standard export; inputs must match the package byte for byte."""
handoff = read(args.package / 'manifest.json')
fresh(args.output)
with zipfile.ZipFile(args.archive) as archive:
names = archive.namelist()
for track, info in handoff['tracks'].items():
for n in info['n_best']:
payload = {}
for filename in FILES:
suffix = f'azookey-results/{track}/n{n}/{filename}'
matches = [name for name in names if name == suffix or name.endswith('/' + suffix)]
if len(matches) != 1:
raise ValueError(f'Expected exactly one {suffix}.')
payload[filename] = archive.read(matches[0])
for filename in ('ajimee-input.json', 'case-map.json'):
if payload[filename] != (args.package / track / filename).read_bytes():
raise ValueError(f'{track}/n{n}: returned {filename} differs from the package.')
dest = args.output / f'{track}-n{n}'
rows = items(track)
track_files(dest, rows)
(dest / 'ajimee-results').mkdir()
for filename, content in payload.items():
(dest / 'ajimee-results' / filename).write_bytes(content)
release, split, _ = TRACKS[track]
manifest = {**read(release / 'manifest.json'), 'format': FORMAT, 'status': 'complete', 'split': split,
'track': track, 'n_best': n, 'stats': read(dest / 'stats.json'),
'candidate_provenance': f'{VERSION} Mac export', 'handoff_created_utc': handoff['created_utc']}
write_json(dest / 'manifest.json', manifest)
_, _, loaded = load_standard_export(dest)
print(f'{track}-n{n}: {len(loaded)} cases loaded', flush=True)
def main():
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
subs = parser.add_subparsers(dest='command', required=True)
sub = subs.add_parser('package')
sub.add_argument('--output', type=Path, default=BENCH / f'{VERSION}-handoff')
sub = subs.add_parser('import')
sub.add_argument('--archive', type=Path, required=True)
sub.add_argument('--package', type=Path, default=BENCH / f'{VERSION}-handoff')
sub.add_argument('--output', type=Path, default=BENCH / f'{VERSION}-imported')
args = parser.parse_args()
{'package': package, 'import': import_results}[args.command](args)
if __name__ == '__main__':
main()