File size: 9,489 Bytes
29f25be | 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 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | """Package the selected V2.1 FP32 bundle, INT8 Core ML model and public metrics."""
import argparse
import hashlib
import json
import re
import shutil
import subprocess
from datetime import datetime, timezone
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
BUNDLE = ROOT / "artifacts/deployment/tiny-ja-v2.1-extend5-bundle-v1"
COREML = ROOT / "artifacts/deployment/tiny-ja-v2.1-extend5-int8-b32-v1"
TEMPLATES = ROOT / "templates/huggingface-v21"
def read(path):
return json.loads(Path(path).read_text(encoding="utf-8"))
def write(path, value):
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(value, ensure_ascii=False, indent=2, allow_nan=False) + "\n",
encoding="utf-8", newline="\n")
def sha(path):
with Path(path).open("rb") as stream:
return hashlib.file_digest(stream, "sha256").hexdigest()
def git(*args):
return subprocess.check_output(["git", *args], cwd=ROOT).decode("utf-8").strip()
def evaluation_summary(bundle, coreml):
reports, ranking = {}, {}
def report(relative):
path = ROOT / relative
reports[relative] = sha(path)
return read(path)
for role, expected in {"ajimee": (200, 144), "development": (137, 122),
"expanded-development": (2000, 1481)}.items():
data = report(f"outputs/deployment/v21-int8-{role}-v1/metrics.json")
if (data["model"]["checkpoint_sha256"] != bundle["checkpoint_sha256"]
or data["model"]["tokenizer_sha256"] != bundle["files"]["tokenizer.model"]["sha256"]
or data["model"]["coreml_manifest_sha256"] != coreml):
raise ValueError("Saved quality report belongs to another model.")
metrics = data["metrics"]["all"]["lm_context_sum"]
if (metrics["cases"], metrics["top1_correct"]) != expected:
raise ValueError("Metrics differ from the V2.1 model card.")
ranking[role] = {
"original_order": data["metrics"]["all"]["azookey"],
"fp32_lm": data["baseline_metrics"]["all"]["lm_context_sum"],
"int8_lm": metrics,
"fp32_comparison": {k: v for k, v in data["comparison"].items() if k != "changes"},
"labels_formal_gold": data.get("labels_formal_gold"),
}
alignment = {}
for kind in ("fp32", "int8"):
data = report(f"outputs/deployment/v21-{kind}-alignment-v1/alignment.json")
alignment[kind] = {"strict_passed": data["passed"], "tolerances": data["tolerances"],
"logits": data["logits"], "invariants": data["invariants"]}
acceptance = report("outputs/deployment/v21-closeout-v1/acceptance.json")
device = acceptance["physical_keyboard_extension"].copy()
device.pop("manual_confirmation", None)
return {"format": "vimeml_public_evaluation_v2", "model_version": "2.1-extend5-step40000-int8-b32-v1",
"checkpoint_sha256": bundle["checkpoint_sha256"], "coreml_manifest_sha256": coreml,
"training": {"full_validation_bpc_fp32": 3.0802686334, "test_used_for_selection": False,
"v20_epochs": 4, "restart1_epochs": 2, "extend5_stopped_step": 161095,
"selected_extend5_step": 40000},
"ranking": ranking, "alignment": alignment,
"fixed_generation": acceptance["same_pool_quality"]["fixed_generation"],
"quantization_diagnostics": report("outputs/deployment/v21-quantization-analysis-v1/analysis.json"),
"physical_keyboard_extension": device,
"limitations": acceptance["failed_or_unresolved"],
"experiment_decision": "V2 series complete; current INT8 task-quality loss and observed UI tail accepted.",
"source_reports_sha256": reports,
"scope": "Saved fixed-pool metrics and bounded device workload; draft expanded labels, no blind scoring. "
"No benchmark texts, per-case scores, user inputs or raw device traces distributed."}
def prepare(output, repo_id):
if not re.fullmatch(r"[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+", repo_id):
raise ValueError("Use OWNER/REPO.")
if output.exists():
raise ValueError("Output exists; use a new release directory.")
if git("status", "--porcelain"):
raise ValueError("Commit the source before creating the release snapshot.")
bundle, model = read(BUNDLE / "manifest.json"), read(COREML / "manifest.json")
bundle_sha, coreml_sha = sha(BUNDLE / "manifest.json"), sha(COREML / "manifest.json")
if (bundle["format"] != "vimeml_inference_bundle_v2" or bundle["checkpoint_step"] != 40000
or bundle["parameter_count"] != 12537920 or not bundle["tied_lm_head"]
or model["kind"] != "linear8_fp32_compute" or model["minimum_ios"] != 18
or model["package_bytes"] != 14330856
or model["bundle"]["bundle_manifest_sha256"] != bundle_sha):
raise ValueError("Expected selected V2.1 step40000 and its INT8 package.")
summary = evaluation_summary(bundle, coreml_sha)
commit, known = git("rev-parse", "HEAD"), {}
output.mkdir(parents=True, exist_ok=False)
def copy(source, name, digest=None, size=None):
if source.is_symlink() or not source.is_file() or (size is not None and source.stat().st_size != size):
raise ValueError(f"Invalid source file: {source}")
target = output / name
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copyfile(source, target)
if target.stat().st_size != source.stat().st_size:
raise ValueError("Copy size mismatch.")
if digest:
known[name] = digest
for name, entry in bundle["files"].items():
copy(BUNDLE / name, f"inference/{name}", entry["sha256"], entry["bytes"])
copy(BUNDLE / "manifest.json", "inference/manifest.json", bundle_sha)
for name, entry in model["package_files"].items():
copy(COREML / "model.mlpackage" / name, f"coreml/ios18-int8-block32/model.mlpackage/{name}",
entry.get("sha256"), entry["bytes"])
copy(COREML / "manifest.json", "coreml/ios18-int8-block32/manifest.json", coreml_sha)
source_files = subprocess.check_output(
["git", "ls-files", "-z", "--", "src", "scripts", "configs", "templates/huggingface-v21",
"requirements.txt", "requirements-autodl.txt", "requirements-evaluation.txt", "LICENSE"], cwd=ROOT
).decode("utf-8").split("\0")
for name in filter(None, source_files):
copy(ROOT / name, f"source/{name}")
(output / "source/README.md").write_text(
f"VimeML source/configuration from Git commit {commit}. GPL-2.0; see LICENSE.\n"
"V2 inference uses vimeml.deployment.v2.BundleLM and the original RMSNorm/SwiGLU architecture.\n"
"Corpus texts, optimizer state, benchmark pools, device traces and the Vime client are excluded.\n",
encoding="utf-8", newline="\n")
copy(ROOT / "LICENSE", "LICENSE")
for name in ("infer.py", "verify_release.py"):
copy(TEMPLATES / name, name)
card = (TEMPLATES / "README.md").read_text(encoding="utf-8")
for key, value in {"SOURCE_COMMIT": commit, "BUNDLE_SHA": bundle_sha, "COREML_SHA": coreml_sha,
"TOKENIZER_SHA": bundle["files"]["tokenizer.model"]["sha256"],
"CHECKPOINT_SHA": bundle["checkpoint_sha256"]}.items():
card = card.replace(f"@@{key}@@", value)
if "@@" in card:
raise ValueError("Unresolved model card field.")
(output / "README.md").write_text(card, encoding="utf-8", newline="\n")
write(output / "evaluation/summary.json", summary)
# Reuse frozen bundle hashes; hash each new release file at most once here.
files = {p.relative_to(output).as_posix(): {"bytes": p.stat().st_size,
"sha256": known.get(p.relative_to(output).as_posix()) or sha(p)}
for p in sorted(output.rglob("*")) if p.is_file()}
release = {"format": "vimeml_hf_release_v1", "repo_id": repo_id, "license": "gpl-2.0",
"model_version": "2.1", "architecture": "tiny_gpt_v2",
"source_repository": "https://github.com/Voltline/VimeML", "source_commit": commit,
"created_utc": datetime.now(timezone.utc).isoformat(),
"bundle_manifest_sha256": bundle_sha, "coreml_manifest_sha256": coreml_sha,
"files": files, "policy": "FP32 inference and weight-only INT8; original manifests preserved. "
"Frozen bundle digests reused, new files inventoried once; no training state or credentials."}
write(output / "RELEASE.json", release)
sums = {**{name: entry["sha256"] for name, entry in files.items()},
"RELEASE.json": sha(output / "RELEASE.json")}
(output / "SHA256SUMS.txt").write_text(
"".join(f"{digest} {name}\n" for name, digest in sorted(sums.items())), encoding="utf-8", newline="\n")
print(json.dumps({"release": str(output), "files": len(files) + 2,
"bytes": sum(p.stat().st_size for p in output.rglob("*") if p.is_file()),
"source_commit": commit}, indent=2))
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--output", type=Path, required=True)
parser.add_argument("--repo-id", default="Voltline/vimeml-tiny-ja-v2.1")
args = parser.parse_args()
prepare(args.output.resolve(), args.repo_id)
|