Download tinydoc/export.py from harryfrz/tinydoc-5m: direct link, hf CLI and curl.
- Browser
- Download file 4.18 kB
-
https://huggingface.co/harryfrz/tinydoc-5m/resolve/main/tinydoc/export.py
- Command line
-
hf download hf://harryfrz/tinydoc-5m/tinydoc/export.py
-
curl -L -o export.py https://huggingface.co/harryfrz/tinydoc-5m/resolve/main/tinydoc/export.py
4.18 kB
| """Export trained components to ONNX for CPU inference with ONNX Runtime, and check parity. | |
| python -m tinydoc.export --weights runs --out models | |
| """ | |
| import argparse | |
| from pathlib import Path | |
| import numpy as np | |
| import torch | |
| from .features import MAX_CHARS, NUM_FEATS | |
| from .models import build, count_params | |
| OPSET = 17 | |
| def sample_inputs(name): | |
| g = torch.Generator().manual_seed(0) | |
| if name == "detector": | |
| return (torch.rand(1, 3, 640, 480, generator=g),), ["image"], ["kernel_logits"], {"image": {0: "batch", 2: "height", 3: "width"}} | |
| if name == "recognizer": | |
| x = torch.rand(2, 1, 32, 320, generator=g) | |
| mask = torch.zeros(2, 80, dtype=torch.bool) | |
| mask[1, 60:] = True | |
| return (x, mask), ["image", "step_mask"], ["logits"], {"image": {0: "batch", 3: "width"}, "step_mask": {0: "batch", 1: "steps"}} | |
| if name == "orientation": | |
| return (torch.rand(4, 1, 192, 192, generator=g),), ["patches"], ["logits"], {"patches": {0: "batch"}} | |
| n = 37 | |
| return ((torch.randint(1, 100, (1, n, MAX_CHARS), generator=g), torch.rand(1, n, NUM_FEATS, generator=g), | |
| torch.rand(1, n, 4, generator=g) * 30, torch.ones(1, n)), | |
| ["char_ids", "feats", "rel_boxes", "valid"], ["field_logits", "rel_logits", "doc_logits"], | |
| {"char_ids": {0: "batch", 1: "segments"}, "feats": {0: "batch", 1: "segments"}, | |
| "rel_boxes": {0: "batch", 1: "segments"}, "valid": {0: "batch", 1: "segments"}}) | |
| def alt_inputs(name): | |
| """Different shapes from the export sample, to prove the dynamic axes really are dynamic.""" | |
| g = torch.Generator().manual_seed(1) | |
| if name == "detector": | |
| return (torch.rand(1, 3, 320, 704, generator=g),) | |
| if name == "recognizer": | |
| return torch.rand(3, 1, 32, 512, generator=g), torch.zeros(3, 128, dtype=torch.bool) | |
| if name == "orientation": | |
| return (torch.rand(7, 1, 192, 192, generator=g),) | |
| n = 120 | |
| return (torch.randint(1, 100, (1, n, MAX_CHARS), generator=g), torch.rand(1, n, NUM_FEATS, generator=g), | |
| torch.rand(1, n, 4, generator=g) * 30, torch.ones(1, n)) | |
| def export_one(name, weights, out_dir, random_init=False): | |
| import onnxruntime as ort | |
| ck = None if random_init else torch.load(Path(weights) / name / "best.pt", map_location="cpu", weights_only=False) | |
| model = build(name, **((ck or {}).get("arch") or {})) | |
| if ck: | |
| model.load_state_dict(ck["model"]) | |
| model.eval() | |
| args, in_names, out_names, dyn = sample_inputs(name) | |
| path = Path(out_dir) / f"{name}.onnx" | |
| torch.onnx.export(model, args, str(path), input_names=in_names, output_names=out_names, | |
| dynamic_axes=dyn, opset_version=OPSET, dynamo=False) | |
| import onnx | |
| proto = onnx.load(str(path)) | |
| meta = proto.metadata_props.add() | |
| meta.key, meta.value = "trained_parameters", str(count_params(model)) | |
| onnx.save(proto, str(path)) | |
| sess = ort.InferenceSession(str(path), providers=["CPUExecutionProvider"]) | |
| worst = 0.0 | |
| for inputs in (args, alt_inputs(name)): | |
| with torch.no_grad(): | |
| ref = model(*inputs) | |
| ref = ref if isinstance(ref, tuple) else (ref,) | |
| got = sess.run(None, {n: t.numpy() for n, t in zip(in_names, inputs)}) | |
| worst = max(worst, max(float(np.abs(r.numpy() - g).max()) for r, g in zip(ref, got))) | |
| size = path.stat().st_size / 1e6 | |
| print(f" {name:12s} params={count_params(model):>9,d} {size:5.1f} MB max |torch - onnx| = {worst:.2e}") | |
| return worst | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--weights", default="runs") | |
| ap.add_argument("--out", default="models") | |
| ap.add_argument("--random-init", action="store_true", help="export untrained weights (pipeline testing)") | |
| args = ap.parse_args() | |
| Path(args.out).mkdir(parents=True, exist_ok=True) | |
| worst = max(export_one(n, args.weights, args.out, args.random_init) for n in ("detector", "recognizer", "orientation", "extractor")) | |
| if worst > 1e-3: | |
| raise SystemExit(f"ONNX parity check failed (max diff {worst:.2e})") | |
| print(f"exported to {args.out}/ (parity ok)") | |
| if __name__ == "__main__": | |
| main() | |