aiflow-math-ink-06-intermediate / scripts /build_math_ink_06_litert_colab_bundle.py
cwLeeDev's picture
Separate public research conversion from private P mobile release
5ee3a5e verified
Raw
History Blame Contribute Delete
8.42 kB
"""AIFlow Math Ink 0.6 LiteRT ๋ณ€ํ™˜์— ํ•„์š”ํ•œ ์ตœ์†Œ Colab ZIP์„ ๊ฒฐ์ •์ ์œผ๋กœ ๋งŒ๋“ ๋‹ค."""
from __future__ import annotations
import argparse
from datetime import datetime, timezone
from hashlib import sha256
import json
from pathlib import Path, PurePosixPath
from zipfile import ZIP_DEFLATED, ZIP_STORED, ZipFile, ZipInfo
PROJECT_ROOT = Path(__file__).parents[1]
MANIFEST_NAME_06 = "LITERT_COLAB_BUNDLE_MANIFEST.json"
def _sha256_bytes06(value: bytes) -> str:
"""ํ•„์š” ๋ณ€์ˆ˜: file bytes. ์ž‘๋™ ์›๋ฆฌ: bundle entry์™€ manifest ๊ฒ€์ฆ์šฉ SHA-256์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค."""
return sha256(value).hexdigest()
def _bundle_files06(
base_checkpoint: Path, adapter_checkpoint: Path, representative_inputs: Path,
) -> dict[str, Path]:
"""ํ•„์š” ๋ณ€์ˆ˜: seed-17 composite artifact. ์ž‘๋™ ์›๋ฆฌ: converter๊ฐ€ importํ•˜๋Š” ๋‹ซํžŒ ์ตœ์†Œ ํŒŒ์ผ ์ง‘ํ•ฉ์„ ๋งคํ•‘ํ•œ๋‹ค."""
return {
"pyproject.toml": PROJECT_ROOT / "pyproject.toml",
"src/math_grid_drawer/__init__.py": PROJECT_ROOT / "research/colab/litert_bundle_package_init.py",
"src/math_grid_drawer/research/__init__.py": PROJECT_ROOT / "src/math_grid_drawer/research/__init__.py",
"src/math_grid_drawer/research/ink06_canonical.py": PROJECT_ROOT / "src/math_grid_drawer/research/ink06_canonical.py",
"src/math_grid_drawer/research/ink06_export.py": PROJECT_ROOT / "src/math_grid_drawer/research/ink06_export.py",
"src/math_grid_drawer/research/math_ink_06.py": PROJECT_ROOT / "src/math_grid_drawer/research/math_ink_06.py",
"src/math_grid_drawer/research/raster_skeleton06.py": PROJECT_ROOT / "src/math_grid_drawer/research/raster_skeleton06.py",
"src/math_grid_drawer/research/skeleton_adapter06.py": PROJECT_ROOT / "src/math_grid_drawer/research/skeleton_adapter06.py",
"src/math_grid_drawer/research/trajectory_sequence.py": PROJECT_ROOT / "src/math_grid_drawer/research/trajectory_sequence.py",
"scripts/export_math_ink_06_litert.py": PROJECT_ROOT / "scripts/export_math_ink_06_litert.py",
"artifacts/base_378.pt": base_checkpoint,
"artifacts/online_adapter.pt": adapter_checkpoint,
"artifacts/representative_inputs.pt": representative_inputs,
}
def build_litert_colab_bundle06(
base_checkpoint: Path,
adapter_checkpoint: Path,
representative_inputs: Path,
output: Path,
) -> dict:
"""ํ•„์š” ๋ณ€์ˆ˜: composite checkpointยท๋Œ€ํ‘œ ์ž…๋ ฅยทZIP ์ถœ๋ ฅ. ์ž‘๋™ ์›๋ฆฌ: ๊ฒฝ๋กœยทํฌ๊ธฐยทํ•ด์‹œ๊ฐ€ ๊ณ ์ •๋œ Linux ๋ณ€ํ™˜ bundle์„ ๋งŒ๋“ ๋‹ค."""
files = _bundle_files06(base_checkpoint, adapter_checkpoint, representative_inputs)
missing = [name for name, path in files.items() if not path.is_file()]
if missing:
raise FileNotFoundError(f"LiteRT bundle ํ•„์ˆ˜ ํŒŒ์ผ์ด ์—†์Šต๋‹ˆ๋‹ค: {missing}")
entries = []
payloads: dict[str, bytes] = {}
for name, path in files.items():
payload = path.read_bytes()
payloads[name] = payload
entries.append({
"path": name, "bytes": len(payload), "sha256": _sha256_bytes06(payload),
})
manifest = {
"schema": "aiflow-math-ink-06-litert-colab-bundle-v1",
"generated_at": datetime.now(timezone.utc).isoformat(),
"track": "R_public_conversion_only",
"p_student_included": False,
"product_bundle": False,
"seed": 17,
"litert_torch_version": "0.9.1",
"representative_samples": 76,
"raster_output_contract": {
"outputs": [
{"index": 0, "name": "exact_logits", "shape": [1, 378]},
{"index": 1, "name": "coordinates", "shape": [1, 4, 128, 2]},
{"index": 2, "name": "state_logits", "shape": [1, 4, 128, 3]},
{"index": 3, "name": "progress", "shape": [1, 4, 128]},
{"index": 4, "name": "hypothesis_scores", "shape": [1, 4]},
],
"direct_raster_label_shortcut": False,
},
"files": entries,
"product_validation": False,
}
output.parent.mkdir(parents=True, exist_ok=True)
temporary = output.with_suffix(output.suffix + ".part")
with ZipFile(temporary, "w", allowZip64=True) as bundle:
for name, payload in payloads.items():
compression = ZIP_STORED if PurePosixPath(name).suffix in {".pt"} else ZIP_DEFLATED
info = ZipInfo(name, date_time=(1980, 1, 1, 0, 0, 0))
info.compress_type = compression
info.external_attr = 0o644 << 16
bundle.writestr(info, payload)
info = ZipInfo(MANIFEST_NAME_06, date_time=(1980, 1, 1, 0, 0, 0))
info.compress_type = ZIP_DEFLATED
info.external_attr = 0o644 << 16
bundle.writestr(
info, json.dumps(manifest, ensure_ascii=False, indent=2).encode("utf-8") + b"\n",
)
temporary.replace(output)
manifest["bundle"] = str(output)
manifest["bundle_bytes"] = output.stat().st_size
manifest["bundle_sha256"] = sha256(output.read_bytes()).hexdigest()
return manifest
def verify_litert_colab_bundle06(bundle_path: Path) -> dict:
"""ํ•„์š” ๋ณ€์ˆ˜: ์ƒ์„ฑ ZIP. ์ž‘๋™ ์›๋ฆฌ: ์ถ”์ถœ ์—†์ด ๋ชจ๋“  entry์˜ ์•ˆ์ „ ๊ฒฝ๋กœยทํฌ๊ธฐยทSHA-256์„ ์žฌ๊ฒ€์ฆํ•œ๋‹ค."""
failures = []
with ZipFile(bundle_path) as bundle:
manifest = json.loads(bundle.read(MANIFEST_NAME_06).decode("utf-8"))
if (
manifest.get("track") != "R_public_conversion_only"
or manifest.get("p_student_included") is not False
or manifest.get("product_bundle") is not False
):
failures.append({"path": MANIFEST_NAME_06, "reason": "research_track_contract"})
contract = manifest.get("raster_output_contract") or {}
output_names = [
str(row.get("name") or "")
for row in contract.get("outputs", [])
]
if output_names != [
"exact_logits",
"coordinates",
"state_logits",
"progress",
"hypothesis_scores",
]:
failures.append({"path": MANIFEST_NAME_06, "reason": "raster_output_contract"})
if contract.get("direct_raster_label_shortcut") is not False:
failures.append({"path": MANIFEST_NAME_06, "reason": "raster_shortcut_contract"})
names = set(bundle.namelist())
for row in manifest["files"]:
name = str(row["path"])
path = PurePosixPath(name)
if path.is_absolute() or ".." in path.parts:
failures.append({"path": name, "reason": "unsafe_path"})
elif name not in names:
failures.append({"path": name, "reason": "missing"})
else:
payload = bundle.read(name)
if len(payload) != int(row["bytes"]):
failures.append({"path": name, "reason": "bytes"})
elif _sha256_bytes06(payload) != str(row["sha256"]):
failures.append({"path": name, "reason": "sha256"})
return {
"schema": manifest["schema"], "files": len(manifest["files"]),
"failures": failures, "passed": not failures, "product_validation": False,
}
def main() -> None:
"""ํ•„์š” ๋ณ€์ˆ˜: CLI artifact ๊ฒฝ๋กœ. ์ž‘๋™ ์›๋ฆฌ: bundle ์ƒ์„ฑ ์งํ›„ archive ์ž์ฒด ๊ฒ€์ฆ๊นŒ์ง€ ์ˆ˜ํ–‰ํ•œ๋‹ค."""
parser = argparse.ArgumentParser(description="Build Math Ink 0.6 LiteRT Colab bundle")
parser.add_argument("--base-checkpoint", type=Path, required=True)
parser.add_argument("--adapter-checkpoint", type=Path, required=True)
parser.add_argument("--representative-inputs", type=Path, required=True)
parser.add_argument("--output", type=Path, required=True)
args = parser.parse_args()
report = build_litert_colab_bundle06(
args.base_checkpoint, args.adapter_checkpoint, args.representative_inputs, args.output,
)
report["verification"] = verify_litert_colab_bundle06(args.output)
if not report["verification"]["passed"]:
raise ValueError(f"LiteRT Colab bundle ๊ฒ€์ฆ ์‹คํŒจ: {report['verification']['failures']}")
report_path = args.output.with_suffix(args.output.suffix + ".manifest.json")
report_path.write_text(
json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8",
)
print(json.dumps(report, ensure_ascii=False, indent=2))
if __name__ == "__main__":
main()