-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathexport_resnet.py
More file actions
96 lines (76 loc) · 3.06 KB
/
Copy pathexport_resnet.py
File metadata and controls
96 lines (76 loc) · 3.06 KB
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
"""Exports pretrained ResNet18 model from TorchVision to custom format of tiny inference engine."""
import numpy as np
import onnx
import struct
import torch
from graphlib import TopologicalSorter
from pathlib import Path
from torchvision.models import resnet18, ResNet18_Weights
from typing import Any
def torch_to_onnx(model_dir: Path) -> None:
weights = ResNet18_Weights.DEFAULT
model = resnet18(weights=weights)
model.eval()
# export model to ONNX
with torch.no_grad():
onnx_program = torch.onnx.export(model, torch.zeros([1, 3, 224, 224]), dynamo=True)
onnx_program.save(model_dir / "model.onnx")
with open(model_dir / "label.txt", "w") as f:
f.write(",".join(weights.meta["categories"]))
def get_attribute_args(node: Any) -> list:
res = []
if node.op_type == "Conv":
for n in ["pads", "strides"]:
for a in node.attribute:
if a.name == n:
res.append(str(a.ints[0]))
if node.op_type == "MaxPool":
for n in ["kernel_shape", "pads", "strides"]:
for a in node.attribute:
if a.name == n:
res.append(str(a.ints[0]))
return res
def save_tensor(fn: Path, t: np.ndarray) -> None:
with open(fn, "wb") as f:
# num dimensions
f.write(struct.pack('i', len(t.shape)))
# shape
f.write(struct.pack('i' * len(t.shape), *t.shape))
# data
arr = t.flatten().tolist()
f.write(struct.pack('f' * len(arr), *arr))
def export(model_dir: Path) -> None:
print("Exporting", model_dir)
onnx_model = onnx.load(model_dir / "model.onnx")
# weights
weight_names = set()
for node in onnx_model.graph.initializer:
save_tensor(model_dir / f"{node.name}.bin", onnx.numpy_helper.to_array(node))
weight_names.add(node.name)
if len(onnx_model.graph.input) != 1 or len(onnx_model.graph.output) != 1:
raise Exception("Not supported (single input/output required)")
model_def = {onnx_model.graph.input[0].name: f"{onnx_model.graph.input[0].name:}=Identity(__INPUT__)",
"__OUTPUT__": f"__OUTPUT__=Identity({onnx_model.graph.output[0].name})"}
ts = TopologicalSorter()
ts.add("__OUTPUT__", onnx_model.graph.output[0].name)
for node in onnx_model.graph.node:
if len(node.output) != 1:
raise Exception("Not supported (single node output required)")
output_node = node.output[0]
input_nodes = [e for e in node.input if e not in weight_names]
ts.add(output_node, *input_nodes)
args = list(node.input) + get_attribute_args(node)
model_def[output_node] = f"{output_node}={node.op_type}({','.join(args)})"
with open(model_dir / "model.txt", "w") as f:
for var_name in ts.static_order():
print(model_def[var_name])
f.write(model_def[var_name])
f.write("\n")
print()
def main():
# load model
model_dir = Path(__file__).parent / "model"
torch_to_onnx(model_dir)
export(model_dir)
if __name__ == "__main__":
main()