rogermt commited on
Commit
dbddc7d
·
verified ·
1 Parent(s): b8d7ec5

Add fix_identity.py utility for removing Identity node waste

Browse files
Files changed (1) hide show
  1. medal-solvers/fix_identity.py +125 -0
medal-solvers/fix_identity.py ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """fix_identity.py — Remove wasteful Identity nodes from ONNX models.
2
+
3
+ The Kaggle scorer counts ALL intermediate tensors except 'input' and 'output'.
4
+ If a model uses Identity as the last node (Pad → t_N → Identity → output),
5
+ the t_N tensor ([1,10,30,30] = 36KB) is counted as memory waste.
6
+
7
+ Fix: Rename the penultimate node's output to 'output' and remove Identity.
8
+
9
+ Usage:
10
+ python fix_identity.py --model optimized/task028.onnx
11
+ python fix_identity.py --dir optimized/ # Fix all .onnx in directory
12
+ """
13
+ import onnx
14
+ import os
15
+ import argparse
16
+ import math
17
+ import numpy as np
18
+
19
+
20
+ def fix_identity(model_path, output_path=None):
21
+ """Remove trailing Identity node from ONNX model.
22
+
23
+ Returns True if fix was applied, False if no fix needed.
24
+ """
25
+ if output_path is None:
26
+ output_path = model_path
27
+
28
+ model = onnx.load(model_path)
29
+
30
+ # Check if last node is Identity outputting to 'output'
31
+ if len(model.graph.node) == 0:
32
+ return False
33
+
34
+ last_node = model.graph.node[-1]
35
+ if last_node.op_type != 'Identity':
36
+ return False
37
+ if 'output' not in list(last_node.output):
38
+ return False
39
+
40
+ # Get the intermediate name
41
+ intermediate_name = last_node.input[0]
42
+
43
+ # Find the node producing this intermediate and rename its output
44
+ found = False
45
+ for node in model.graph.node[:-1]:
46
+ if intermediate_name in list(node.output):
47
+ idx = list(node.output).index(intermediate_name)
48
+ node.output[idx] = 'output'
49
+ found = True
50
+ break
51
+
52
+ if not found:
53
+ return False
54
+
55
+ # Remove Identity node
56
+ del model.graph.node[-1]
57
+
58
+ # Apply strict shape inference fix
59
+ del model.graph.value_info[:]
60
+ model = onnx.shape_inference.infer_shapes(model, strict_mode=True)
61
+
62
+ onnx.save(model, output_path)
63
+ return True
64
+
65
+
66
+ def estimate_savings(model_path):
67
+ """Estimate memory savings from removing Identity."""
68
+ model = onnx.load(model_path)
69
+
70
+ if len(model.graph.node) == 0:
71
+ return 0
72
+
73
+ last_node = model.graph.node[-1]
74
+ if last_node.op_type != 'Identity' or 'output' not in list(last_node.output):
75
+ return 0
76
+
77
+ intermediate_name = last_node.input[0]
78
+
79
+ for vi in model.graph.value_info:
80
+ if vi.name == intermediate_name:
81
+ if vi.type.HasField('tensor_type') and vi.type.tensor_type.HasField('shape'):
82
+ dims = [d.dim_value for d in vi.type.tensor_type.shape.dim]
83
+ dt = onnx.helper.tensor_dtype_to_np_dtype(vi.type.tensor_type.elem_type)
84
+ return int(np.prod(dims)) * np.dtype(dt).itemsize
85
+
86
+ # Assume [1,10,30,30] float32 if not found in value_info
87
+ return 36000
88
+
89
+
90
+ if __name__ == '__main__':
91
+ parser = argparse.ArgumentParser(description='Remove Identity node waste from ONNX models')
92
+ parser.add_argument('--model', help='Single model to fix')
93
+ parser.add_argument('--dir', help='Directory of models to fix')
94
+ parser.add_argument('--dry-run', action='store_true', help='Show savings without modifying')
95
+ args = parser.parse_args()
96
+
97
+ models = []
98
+ if args.model:
99
+ models.append(args.model)
100
+ elif args.dir:
101
+ for f in sorted(os.listdir(args.dir)):
102
+ if f.endswith('.onnx'):
103
+ models.append(os.path.join(args.dir, f))
104
+ else:
105
+ parser.print_help()
106
+ exit(1)
107
+
108
+ total_savings = 0
109
+ for model_path in models:
110
+ savings = estimate_savings(model_path)
111
+ if savings > 0:
112
+ if args.dry_run:
113
+ print(f" {model_path}: would save {savings:,} bytes")
114
+ else:
115
+ if fix_identity(model_path):
116
+ print(f" {model_path}: FIXED (saved {savings:,} bytes)")
117
+ else:
118
+ print(f" {model_path}: fix failed")
119
+ total_savings += savings
120
+ else:
121
+ print(f" {model_path}: no Identity to fix")
122
+
123
+ if total_savings > 0:
124
+ score_improvement = math.log(1 + total_savings / 50000) # rough estimate
125
+ print(f"\n Total memory savings: {total_savings:,} bytes")