"""Prepare frozen C1 conversion labels from explicitly allowed train/validation TXT. CPU only. Never discovers evaluation directories or reads test/blind inputs. Shards are resumable only under the exact same source/code/dictionary contract. """ import argparse from collections import Counter, defaultdict from concurrent.futures import ProcessPoolExecutor, as_completed from datetime import datetime, timezone import hashlib import importlib.metadata import json import os from pathlib import Path import random import sqlite3 import struct import sys import time ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT / "src")) import numpy as np from vimeml.posttraining.data import ConversionCodec, FORMAT, TOKENIZER_SHA, shifted from vimeml.posttraining.readings import DualReading, hiragana from vimeml.training.data import file_sha, write_json VERSION = "c1-dual-orthographic-v1" CORPUS = ROOT / "outputs/corpus-fast-v1" STORE = ROOT / "artifacts/token-data/corpus-v4-8k" TOKENIZER = ROOT / "artifacts/tokenizers/v4-a/ja-unigram-8192/tokenizer.model" EXPECTED = { "train": "9c1b4585adaf0518dc0275e7f8086e967db26f9797fb571e1ff481aeadf2ff8f", "validation": "f188bea10a8b464dd385d8e0aa310225de07e2a0a170e90ce63503c150bf4cdd", } TRACKS = ("standard-development", "legacy-regression", "ajimee", "historical-development") def digest(value): return hashlib.sha256(value.encode("utf-8")).hexdigest() def case_key(context, reading, output): return digest(json.dumps([context, hiragana(reading), output], ensure_ascii=False, separators=(",", ":"))) def initialize(validation_hashes, cases, benchmark_parents): global ANALYZER, CODEC, VALIDATION_HASHES, CASES, BENCHMARK_PARENTS ANALYZER, CODEC = DualReading(), ConversionCodec(TOKENIZER) VALIDATION_HASHES = set(validation_hashes) CASES, BENCHMARK_PARENTS = set(cases), set(benchmark_parents) def shard(task): split, start, stop, probability, directory, signature = task directory = Path(directory) stem = f"{split}-{start:09d}-{stop:09d}" done = directory / f"{stem}.json" data = directory / f"{stem}.jsonl" if done.exists(): result = json.loads(done.read_text(encoding="utf-8")) if result["signature"] != signature or file_sha(data) != result["sha256"]: raise ValueError("Incompatible or damaged completed shard") return result positions = np.memmap(STORE / f"{split}.rows.bin", mode="r", dtype="= probability: continue counts["sampled_parents"] += 1 if split == "train" and parent in VALIDATION_HASHES: counts["reject_validation_parent"] += 1 continue if parent in BENCHMARK_PARENTS: counts["reject_known_nonblind_parent"] += 1 continue example, reason = ANALYZER.example(text, int(parent[16:32], 16)) if example is None: counts["reject_" + reason] += 1 continue fingerprint = case_key(example["context"], example["reading"], example["output"]) if fingerprint in CASES: counts["reject_known_nonblind_case"] += 1 continue encoded = CODEC.encode(example["context"], example["reading"], example["output"]) if encoded is None: counts["reject_target_reading_overflow"] += 1 continue ids, first, removed = encoded check = shifted(ids, first) assert sum(x != -100 for x in check["labels"]) == len(ids) - first example.update(parent_sha256=parent, split=split, row=row, byte_offset=offset, source_id=int(sources[row]), document_id=None, case_sha256=fingerprint, context_crop_characters=removed, ids=ids, first_output=first, orthographic_rule=VERSION, review="dual_dictionary_agreement_not_human_gold") target.write(json.dumps(example, ensure_ascii=False, separators=(",", ":")) + "\n") counts["accepted_pool"] += 1 temporary.replace(data) result = {"split": split, "start": start, "stop": stop, "data": str(data), "signature": signature, "sha256": file_sha(data), "counts": dict(counts), "seconds": time.monotonic() - began} write_json(done, result) return result def dictionary_contract(): import unidic_lite import sudachidict_core binaries = sorted(Path(unidic_lite.DICDIR).glob("*.dic")) binaries += sorted((Path(sudachidict_core.__file__).parent / "resources").glob("*.dic")) if not binaries: raise ValueError("No dictionary binaries") return {"versions": {n: importlib.metadata.version(n) for n in ( "fugashi", "unidic-lite", "SudachiPy", "SudachiDict-core", "sentencepiece")}, "binaries": {str(p): file_sha(p) for p in binaries}} def merge(directory, results, split, requested): """Deduplicate, select by stable hash within kana/kanji strata, then pack.""" db = sqlite3.connect(directory / f"{split}.selection.sqlite") db.execute("PRAGMA journal_mode=OFF") db.execute("PRAGMA synchronous=OFF") db.execute("DROP TABLE IF EXISTS candidates") db.execute("CREATE TABLE candidates (parent TEXT PRIMARY KEY, fingerprint TEXT UNIQUE, shard INTEGER, line INTEGER, kana INTEGER, priority INTEGER)") shards = sorted((x for x in results if x["split"] == split), key=lambda x: x["start"]) offered = 0 for shard_id, info in enumerate(shards): batch = [] with Path(info["data"]).open(encoding="utf-8") as f: for line, raw in enumerate(f): ex = json.loads(raw) offered += 1 batch.append((ex["parent_sha256"], ex["case_sha256"], shard_id, line, not ex["has_kanji"], int(ex["parent_sha256"][32:48], 16) >> 1)) if len(batch) == 10000: db.executemany("INSERT OR IGNORE INTO candidates VALUES (?,?,?,?,?,?)", batch) batch.clear() db.executemany("INSERT OR IGNORE INTO candidates VALUES (?,?,?,?,?,?)", batch) db.commit() available = dict(db.execute("SELECT kana, count(*) FROM candidates GROUP BY kana")) total = min(requested, available.get(0, 0) + available.get(1, 0), int(available.get(0, 0) / .8)) kana = min(available.get(1, 0), total // 5) kanji = min(available.get(0, 0), total - kana) selected = defaultdict(set) db.execute("CREATE INDEX selected_priority ON candidates(kana,priority)") for kind, limit in ((0, kanji), (1, kana)): for sid, line in db.execute("SELECT shard,line FROM candidates WHERE kana=? ORDER BY priority,parent LIMIT ?", (kind, limit)): selected[sid].add(line) db.close() stats, histograms, examples = Counter(), defaultdict(Counter), [] rng = random.Random(42) offset = 0 with (directory / f"{split}.tokens.bin").open("wb") as tokens, \ (directory / f"{split}.offsets.bin").open("wb") as offsets, \ (directory / f"{split}.out-start.bin").open("wb") as starts, \ (directory / f"{split}.reading-dropout.bin").open("wb") as dropouts, \ (directory / f"{split}.records.jsonl").open("w", encoding="utf-8", newline="\n") as records: offsets.write(struct.pack("