9OCR / scripts /export_onnx.py
ansarzeinulla's picture
Read full board diagrams, dynamic beam width, CSV metadata batch UI
1b69166
Raw
History Blame Contribute Delete
2.7 kB
"""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()