Spaces:
Sleeping
Sleeping
| """Export the trained .pt checkpoints to ONNX for torch-free serving. | |
| python scripts/export_onnx.py | |
| Writes, next to each source checkpoint: | |
| checkpoints/best.onnx + checkpoints/best.classes.json | |
| checkpoints/diagram/best.onnx + checkpoints/diagram/best.classes.json | |
| ONNX carries no Python metadata, so the class list (which maps output index | |
| -> move/kazan string) is saved as a JSON sidecar the serving code reads. The | |
| graph has a dynamic batch axis so one session handles any number of cells. | |
| """ | |
| import argparse | |
| import json | |
| from pathlib import Path | |
| import onnx | |
| import torch | |
| from togyz.model import build_model | |
| from togyz.preprocess import TARGET_H, TARGET_W | |
| OPSET = 17 | |
| def export_one(ckpt_path: Path) -> Path: | |
| ckpt = torch.load(ckpt_path, map_location="cpu", weights_only=True) | |
| classes = ckpt["classes"] | |
| model = build_model(len(classes)) | |
| model.load_state_dict(ckpt["model_state"]) | |
| model.eval() | |
| onnx_path = ckpt_path.with_suffix(".onnx") | |
| classes_path = ckpt_path.with_suffix(".classes.json") | |
| # Legacy TorchScript exporter (dynamo=False) embeds weights directly, so | |
| # the result is a single self-contained .onnx (no sidecar .data file) - | |
| # much cleaner to commit to the HuggingFace Space via Git LFS. | |
| dummy = torch.zeros(1, 1, TARGET_H, TARGET_W) | |
| torch.onnx.export( | |
| model, | |
| dummy, | |
| str(onnx_path), | |
| input_names=["input"], | |
| output_names=["logits"], | |
| dynamic_axes={"input": {0: "batch"}, "logits": {0: "batch"}}, | |
| opset_version=OPSET, | |
| dynamo=False, | |
| ) | |
| # Belt and braces: if any external data slipped out, fold it back in so the | |
| # .onnx is guaranteed standalone, then drop the stray .data file. | |
| model_proto = onnx.load(str(onnx_path)) | |
| onnx.save_model(model_proto, str(onnx_path), save_as_external_data=False) | |
| data_file = onnx_path.with_suffix(".onnx.data") | |
| if data_file.exists(): | |
| data_file.unlink() | |
| classes_path.write_text(json.dumps(classes)) | |
| size_mb = onnx_path.stat().st_size / 1e6 | |
| print(f"{ckpt_path} -> {onnx_path} ({len(classes)} classes, {size_mb:.1f} MB)") | |
| return onnx_path | |
| def main() -> None: | |
| parser = argparse.ArgumentParser(description=__doc__) | |
| parser.add_argument( | |
| "checkpoints", | |
| nargs="*", | |
| default=["checkpoints/best.pt", "checkpoints/diagram/best.pt"], | |
| help="checkpoint(s) to export (default: moves + diagram best.pt)", | |
| ) | |
| args = parser.parse_args() | |
| for path in args.checkpoints: | |
| p = Path(path) | |
| if p.exists(): | |
| export_one(p) | |
| else: | |
| print(f"skip: {p} not found") | |
| if __name__ == "__main__": | |
| main() | |