File size: 4,038 Bytes
0cc1b2b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | #!/usr/bin/env python3
"""Bundle the small tokenizer/config assets needed for offline reconstruction."""
from __future__ import annotations
import argparse
import json
import shutil
from pathlib import Path
from transformers import AutoConfig
from model_registry import CHECKPOINTS, LANGUAGES, MODES, MODEL_SPECS, model_spec
SCRIPT_DIR = Path(__file__).resolve().parent
TOKENIZER_CANDIDATES = (
"sentencepiece.bpe.model",
"spm.model",
"spiece.model",
"vocab.txt",
"tokenizer.json",
)
TOKENIZER_METADATA = (
"tokenizer_config.json",
"special_tokens_map.json",
"added_tokens.json",
)
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser()
parser.add_argument("--base-model-root", type=Path, required=True)
parser.add_argument(
"--model-root", type=Path, default=SCRIPT_DIR.parent / "models"
)
parser.add_argument("--model", action="append", choices=sorted(MODEL_SPECS))
return parser.parse_args()
def main() -> None:
args = parse_args()
base_root = args.base_model_root.resolve()
model_root = args.model_root.resolve()
selected = set(args.model or MODEL_SPECS)
for model_name in MODEL_SPECS:
if model_name not in selected:
continue
if MODEL_SPECS[model_name]["backend"] == "bge":
print(f"SKIP {model_name}: no releasable trained MLP head", flush=True)
continue
for language in LANGUAGES:
if not any(
CHECKPOINTS[(model_name, language, mode)]["available"] for mode in MODES
):
continue
spec = model_spec(model_name, language)
source = base_root / spec["local_base_dir"]
if not (source / "config.json").is_file():
raise FileNotFoundError(
f"Missing local base-model config for {model_name}/{language}: {source}"
)
destination = model_root / model_name / language / "base_model"
destination.mkdir(parents=True, exist_ok=True)
config = AutoConfig.from_pretrained(source, local_files_only=True)
config.save_pretrained(destination)
# Prefer the compact native tokenizer model. Saving a fast tokenizer
# with recent Transformers can expand it into a ~17 MB, million-line
# JSON file; the native assets are both smaller and fully sufficient.
tokenizer_asset = (
"tokenizer.json"
if model_name == "slavic-specific" and language == "slovenian"
else next(
(name for name in TOKENIZER_CANDIDATES if (source / name).is_file()),
None,
)
)
if tokenizer_asset is None:
raise FileNotFoundError(f"No tokenizer asset found in {source}")
keep = {tokenizer_asset, "config.json", "source.json"}
for name in TOKENIZER_METADATA:
if (source / name).is_file():
shutil.copy2(source / name, destination / name)
keep.add(name)
shutil.copy2(source / tokenizer_asset, destination / tokenizer_asset)
for candidate in (*TOKENIZER_CANDIDATES, *TOKENIZER_METADATA):
stale = destination / candidate
if stale.is_file() and candidate not in keep:
stale.unlink()
metadata = {
"model": model_name,
"language": language,
"upstream_base_model": spec["base_model"],
"purpose": "Tokenizer and architecture configuration for offline checkpoint reconstruction",
"contains_upstream_model_weights": False,
}
(destination / "source.json").write_text(
json.dumps(metadata, indent=2) + "\n", encoding="utf-8"
)
print(f"BUNDLED {model_name}/{language}: {destination}", flush=True)
if __name__ == "__main__":
main()
|