forked from Samsung/ONE
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathoperation.py
executable file
·209 lines (172 loc) · 8.37 KB
/
operation.py
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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
#!/usr/bin/python
# Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import tflite.Conv2DOptions
import tflite.Pool2DOptions
import tflite.BuiltinOptions
import tflite.Tensor
from tensor_wrapping import Tensor
import math
# NOTICE
# - an internal class. do not import outside this file.
# - REF: https://stackoverflow.com/questions/551038/private-implementation-class-in-python
class _OperationComputeMethod(object):
'''
NOTE: How to count operations of convolution(and also pooling)?
If we know operations of output's one element, we can calculate total output's operations.
For example, consider output Shape[3,3]
[ e11 e12 e13 ]
[ e21 e22 e23 ]
[ e31 e32 e33 ]
If we know operations for calculation of e11, we can know total operations of output(e11, e12, ... e33)
by operations of e11 * 9(total number of elements)
So we only need to know how to calculate operations of e11.
For this, just think how to conv operation to the output's element
If input_channel is 1, we can only think of kernel_size(kernel_w and kernel_h).
For example, consider input Shape[3,3] and kernel Shape[2,2]
[ i11 i12 i13 ] [ k11 k12 ] [ o11 o12 o13 ]
[ i21 i22 i23 ] * [ k21 k22 ] = [ o21 o22 o23 ]
[ i31 i32 i33 ] [ o31 o32 o33 ]
Conv operation: for o11, i11 * k11 + i21 * k21 + i12 * k12 + i22 * k22 = o11
On above conv operation, mul operations are done at 4 times(== kernel_w * kernel_h)
and add operations are dont at 3 times(== kernel_w * kernel_h - 1)
and also, bias will be done and it will be counted on add operations.
Anyway, we can calculate total operations on this way. This can apply to the way of pooling.
'''
def ComputeOperationForConv2D(self, tf_operator, inputs, outputs):
assert (
tf_operator.BuiltinOptionsType() == tflite.BuiltinOptions.BuiltinOptions()
.Conv2DOptions)
# NOTE: Assume that conv2d operator always take 3 tensors as inputs
# and both width and height are the same.
# operator_inputs[]: [input_tensor, weight_tensor, bias_tensor]
# operator_outputs[]: [output_tensor]
# tflite's tensor shape: [N,H,W,C]
input_tensor = inputs[0].tf_tensor
weight_tensor = inputs[1].tf_tensor
output_tensor = outputs[0].tf_tensor
# kernel_ops = (kernel_w * kernel_h * input_channel * 2(multiply and add))
kernel_ops = (
weight_tensor.Shape(2) * weight_tensor.Shape(1) * input_tensor.Shape(3))
# total ops
# = batch_size * output_channel * output_width * output_height * kernel_ops
total_ops = (output_tensor.Shape(0) * output_tensor.Shape(3) *
output_tensor.Shape(2) * output_tensor.Shape(1))
add_instr_num = (total_ops * (kernel_ops + 1)) # bias
mul_instr_num = (total_ops * (kernel_ops))
nonlinear_instr_num = 0
return (add_instr_num, mul_instr_num, nonlinear_instr_num)
# NOTE: Reference the comment 'NOTE' of ComputeOperationForConv2D
def ComputeOperationForPooling(self, tf_operator, inputs, outputs):
assert (
tf_operator.BuiltinOptionsType() == tflite.BuiltinOptions.BuiltinOptions()
.Pool2DOptions)
dummy_input_tensor = inputs[0].tf_tensor
output_tensor = outputs[0].tf_tensor
pool2d_options = tflite.Pool2DOptions.Pool2DOptions()
pool2d_options.Init(tf_operator.BuiltinOptions().Bytes,
tf_operator.BuiltinOptions().Pos)
# kernel_ops = kernel_w * kernel_h
kernel_ops = (pool2d_options.FilterWidth() * pool2d_options.FilterHeight())
# total ops
# = batch_size * output_channel * output_width * output_height *
# kernel_ops(kernel_w * kernel_h)
total_ops = (output_tensor.Shape(0) * output_tensor.Shape(3) *
output_tensor.Shape(2) * output_tensor.Shape(1))
add_instr_num = (total_ops * kernel_ops - 1)
mul_instr_num = (total_ops * kernel_ops)
nonlinear_instr_num = 0
return (add_instr_num, mul_instr_num, nonlinear_instr_num)
def ComputeOperationForSoftmax(self, tf_operator, inputs, outputs):
assert (
tf_operator.BuiltinOptionsType() == tflite.BuiltinOptions.BuiltinOptions()
.SoftmaxOptions)
input_tensor = inputs[0].tf_tensor
dummy_batch_size = input_tensor.Shape(0)
input_dim = input_tensor.Shape(1)
# Softmax(x_i) = exp(x_i) / sum of exp(x)
add_instr_num = input_dim - 1 # sum of exp(x)
mul_instr_num = input_dim # /
nonlinear_instr_num = input_dim + input_dim # sum of exp(x) and exp(x_i)
return (add_instr_num, mul_instr_num, nonlinear_instr_num)
def ComputeOperationForFullyConnected(self, tf_operator, inputs, outputs):
assert (
tf_operator.BuiltinOptionsType() == tflite.BuiltinOptions.BuiltinOptions()
.FullyConnectedOptions)
# NOTE: Assume that fully_connected operator always take 3 tensors as inputs
# and its X tensor's shape is [1, 1, 1, input_dim] with
# its output Y [1, output_dim]
input_tensor = inputs[0].tf_tensor
output_tensor = outputs[0].tf_tensor
# ops_per_element
# = input_dim(multiplication) + input_dim-1(addition) + 1(bias)
# total_ops
# = ops_per_elem * output_dim
add_instr_num = (input_tensor.Shape(3) * output_tensor.Shape(1))
mul_instr_num = (input_tensor.Shape(3) * output_tensor.Shape(1))
nonlinear_instr_num = 0
return (add_instr_num, mul_instr_num, nonlinear_instr_num)
def ComputeOperationForNothing(self, tf_operator, inputs, outputs):
add_instr_num = 0
mul_instr_num = 0
nonlinear_instr_num = 0
return (add_instr_num, mul_instr_num, nonlinear_instr_num)
def NYI_ComputeOperation(self, tf_operator, inputs, outputs):
pass
operation_to_method_map = {
# Inceptionv3
"CONV_2D": ComputeOperationForConv2D,
"AVERAGE_POOL_2D": ComputeOperationForPooling,
"MAX_POOL_2D": ComputeOperationForPooling,
"SOFTMAX": ComputeOperationForSoftmax,
"FULLY_CONNECTED": ComputeOperationForFullyConnected,
"CONCATENATION": ComputeOperationForNothing,
# Extension
"TOPK_V2": NYI_ComputeOperation,
"SUB": NYI_ComputeOperation,
"STRIDED_SLICE": NYI_ComputeOperation,
"RESHAPE": NYI_ComputeOperation,
"GATHER": NYI_ComputeOperation,
"RESIZE_BILINEAR": NYI_ComputeOperation,
"CAST": NYI_ComputeOperation,
"ADD": NYI_ComputeOperation,
"MUL": NYI_ComputeOperation,
"DIV": NYI_ComputeOperation,
"CUSTOM(TensorFlowMax)": NYI_ComputeOperation,
"CUSTOM": NYI_ComputeOperation,
}
class Operation(object):
def __init__(self, tf_operator, operator_str, inputs, outputs):
self.tf_operator = tf_operator
self.operator_str = operator_str
self.inputs = inputs
self.outputs = outputs
self.add_instr_num = 0
self.mul_instr_num = 0
self.nonlinear_instr_num = 0
self.can_compute = True
self.Compute()
def Compute(self):
comp_map = _OperationComputeMethod().operation_to_method_map
if not self.operator_str in comp_map.keys():
self.can_compute = False
return
method = comp_map[self.operator_str]
if method.__name__ == _OperationComputeMethod().NYI_ComputeOperation.__name__:
self.can_compute = False
return
self.add_instr_num, self.mul_instr_num, self.nonlinear_instr_num = method(
_OperationComputeMethod(), self.tf_operator, self.inputs, self.outputs)
def TotalInstrNum(self):
return (self.add_instr_num + self.mul_instr_num + self.nonlinear_instr_num)