Skip to content

Commit 26d7a78

Browse files
[microNPU] Add relu6 relu_n1_to_1 test cases for Ethos-U
Tests are extended with cases with activations relu6 and relu_n1_to_1. Does not fuse min and max operations with requantize if there are different scales as it is not supported on NPU.
1 parent a4156cd commit 26d7a78

File tree

3 files changed

+103
-43
lines changed

3 files changed

+103
-43
lines changed

python/tvm/relay/op/contrib/ethosu.py

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -688,15 +688,13 @@ def __init__(self, func_body: Call, operator_type: str, is_quantized_operation:
688688
clip = None
689689
requantize = None
690690

691-
if is_quantized_operation:
692-
if str(current_call.op) == "clip":
693-
clip = current_call
694-
current_call = clip.args[0]
695-
else:
696-
if str(current_call.op) == "qnn.requantize":
697-
requantize = current_call
698-
clip = current_call.args[0]
699-
current_call = clip.args[0]
691+
if str(current_call.op) == "clip":
692+
clip = current_call
693+
current_call = clip.args[0]
694+
elif str(current_call.op) == "qnn.requantize":
695+
requantize = current_call
696+
clip = current_call.args[0]
697+
current_call = clip.args[0]
700698
binary_op = current_call
701699

702700
layout = "NHWC"
@@ -929,6 +927,9 @@ def is_valid(self):
929927
[self.ifm, self.ifm2, self.ofm], supported_dtypes=[np.uint8, np.int8]
930928
):
931929
return False
930+
# MIN with different scales is not supported on NPU.
931+
if self.ifm.q_params.scale_f32 != self.ofm.q_params.scale_f32:
932+
return False
932933
return True
933934

934935

@@ -938,12 +939,21 @@ def minimum_pattern() -> tvm.relay.dataflow_pattern.DFPattern:
938939
"""
939940
minimum = is_op("minimum")(wildcard(), wildcard())
940941
optional_min_clip = is_op("clip")(minimum)
941-
optional_min_clip = is_op("qnn.requantize")(
942-
optional_min_clip, is_constant(), is_constant(), is_constant(), is_constant()
943-
)
944942
return minimum | optional_min_clip
945943

946944

945+
def minimum_clip_requantize_pattern() -> tvm.relay.dataflow_pattern.DFPattern:
946+
"""
947+
This function creates the pattern for minimum with fused RELU activation.
948+
"""
949+
pattern = is_op("minimum")(wildcard(), wildcard())
950+
pattern = is_op("clip")(pattern)
951+
pattern = is_op("qnn.requantize")(
952+
pattern, is_constant(), is_constant(), is_constant(), is_constant()
953+
)
954+
return pattern
955+
956+
947957
class MaxParams(BinaryElementwiseParams):
948958
"""
949959
This class will parse a call to a ethosu.binary_elementwise Max composite function
@@ -967,6 +977,9 @@ def is_valid(self):
967977
[self.ifm, self.ifm2, self.ofm], supported_dtypes=[np.uint8, np.int8]
968978
):
969979
return False
980+
# MAX with different scales is not supported on NPU.
981+
if self.ifm.q_params.scale_f32 != self.ofm.q_params.scale_f32:
982+
return False
970983
return True
971984

972985

@@ -976,12 +989,21 @@ def maximum_pattern() -> tvm.relay.dataflow_pattern.DFPattern:
976989
"""
977990
maximum = is_op("maximum")(wildcard(), wildcard())
978991
optional_max_clip = is_op("clip")(maximum)
979-
optional_max_clip = is_op("qnn.requantize")(
980-
optional_max_clip, is_constant(), is_constant(), is_constant(), is_constant()
981-
)
982992
return maximum | optional_max_clip
983993

984994

995+
def maximum_clip_requantize_pattern() -> tvm.relay.dataflow_pattern.DFPattern:
996+
"""
997+
This function creates the pattern for maximum with fused RELU activation.
998+
"""
999+
pattern = is_op("maximum")(wildcard(), wildcard())
1000+
pattern = is_op("clip")(pattern)
1001+
pattern = is_op("qnn.requantize")(
1002+
pattern, is_constant(), is_constant(), is_constant(), is_constant()
1003+
)
1004+
return pattern
1005+
1006+
9851007
class ShlParams(BinaryElementwiseParams):
9861008
"""
9871009
This class will parse a call to a ethosu.binary_elementwise Shl composite function
@@ -1820,11 +1842,21 @@ def pattern_table() -> List[Tuple[str, tvm.relay.dataflow_pattern.DFPattern, Cal
18201842
qnn_mul_pattern(),
18211843
lambda pat: MulParams(pat).is_valid(),
18221844
),
1845+
(
1846+
MinParams.composite_name,
1847+
minimum_clip_requantize_pattern(),
1848+
lambda pat: MinParams(pat).is_valid(),
1849+
),
18231850
(
18241851
MinParams.composite_name,
18251852
minimum_pattern(),
18261853
lambda pat: MinParams(pat).is_valid(),
18271854
),
1855+
(
1856+
MaxParams.composite_name,
1857+
maximum_clip_requantize_pattern(),
1858+
lambda pat: MaxParams(pat).is_valid(),
1859+
),
18281860
(
18291861
MaxParams.composite_name,
18301862
maximum_pattern(),

tests/python/contrib/test_ethosu/test_codegen.py

Lines changed: 46 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,16 @@
3737
ACCEL_TYPES = ["ethos-u55-256", "ethos-u55-128", "ethos-u55-64", "ethos-u55-32", "ethos-u65-256"]
3838

3939

40+
def relu_n1_to_1(x):
41+
"""
42+
The specific pattern will be replaced into RELU_N1_TO_1 by tflite.
43+
"""
44+
return tf.math.maximum(-1.0, tf.math.minimum(x, 1.0))
45+
46+
47+
ACTIVATIONS = [None, tf.nn.relu, tf.nn.relu6, relu_n1_to_1]
48+
49+
4050
def is_u55_accel_type(accel_type):
4151
return "u55" in accel_type
4252

@@ -46,7 +56,7 @@ def is_u55_accel_type(accel_type):
4656
@pytest.mark.parametrize("kernel_shape", [(3, 2), (1, 3)])
4757
@pytest.mark.parametrize("strides, dilation", [((1, 1), (2, 1)), ((3, 2), (1, 1))])
4858
@pytest.mark.parametrize("padding", ["SAME", "VALID"])
49-
@pytest.mark.parametrize("activation", ["NONE", "RELU"])
59+
@pytest.mark.parametrize("activation", ACTIVATIONS)
5060
def test_ethosu_conv2d_single(
5161
ifm_shape,
5262
kernel_shape,
@@ -72,8 +82,8 @@ def conv2d(x):
7282
padding=padding,
7383
dilations=dilation,
7484
)
75-
if activation == "RELU":
76-
op = tf.nn.relu(op)
85+
if activation:
86+
op = activation(op)
7787
return op
7888

7989
infra.compare_tvm_with_tflite(conv2d, [ifm_shape], accel_type)
@@ -114,7 +124,7 @@ def conv2d(x):
114124
@pytest.mark.parametrize("strides, dilation", [((1, 1), (2, 1)), ((3, 2), (1, 1))])
115125
@pytest.mark.parametrize("padding", ["SAME", "VALID"])
116126
@pytest.mark.parametrize("accel_type", ACCEL_TYPES + ["ethos-u65-512"])
117-
@pytest.mark.parametrize("activation", ["NONE", "RELU"])
127+
@pytest.mark.parametrize("activation", ACTIVATIONS)
118128
def test_ethosu_conv2d_double(
119129
ifm_shape,
120130
kernel_shape,
@@ -150,22 +160,28 @@ def conv2d_double(x):
150160
padding=padding,
151161
dilations=dilation,
152162
)
153-
if activation == "RELU":
154-
op2 = tf.nn.relu(op2)
163+
if activation:
164+
op2 = activation(op2)
155165
return op2
156166

157167
infra.compare_tvm_with_tflite(conv2d_double, [ifm_shape], accel_type)
158168

159169

160170
@pytest.mark.parametrize("weight_min, weight_max", [(0.0, 1e-11), (-1e10, 1e10)])
161-
def test_out_of_range_scaling(weight_min, weight_max):
171+
# relu6 and relu_n1_to_1 operations are excluded from activations since tflite results are different.
172+
# In the tflite model, a rather large scale is generated, so in some cases in tflite result is -128 in ethosu 127.
173+
@pytest.mark.parametrize("activation", [None, tf.nn.relu])
174+
def test_out_of_range_scaling(
175+
weight_min,
176+
weight_max,
177+
activation,
178+
):
162179
np.random.seed(0)
163180
ifm_shape = (1, 6, 6, 2)
164181
strides = (1, 1)
165182
kernel_shape = (1, 1)
166183
dilation = (1, 1)
167184
padding = "SAME"
168-
activation = "RELU"
169185
accel_type = "ethos-u55-128"
170186

171187
@tf.function
@@ -186,8 +202,8 @@ def conv_invalid_scale(x):
186202
padding=padding,
187203
dilations=dilation,
188204
)
189-
if activation == "RELU":
190-
op = tf.nn.relu(op)
205+
if activation:
206+
op = activation(op)
191207
return op
192208

193209
infra.compare_tvm_with_tflite(conv_invalid_scale, [ifm_shape], accel_type)
@@ -196,19 +212,20 @@ def conv_invalid_scale(x):
196212
@pytest.mark.parametrize("accel_type", ACCEL_TYPES)
197213
@pytest.mark.parametrize("ifm_shape", [(1, 55, 55, 3), (1, 23, 32, 7)])
198214
@pytest.mark.parametrize(
199-
"kernel_shape, activation_function",
200-
[((3, 3), "RELU"), ((1, 2), "NONE")],
215+
"kernel_shape",
216+
[(3, 3), (1, 2)],
201217
)
202218
@pytest.mark.parametrize("padding", ["SAME", "VALID"])
203219
@pytest.mark.parametrize("strides, dilation", [((1, 1), (2, 2)), ((3, 2), (1, 1))])
220+
@pytest.mark.parametrize("activation", ACTIVATIONS)
204221
def test_tflite_depthwise_conv2d(
205222
accel_type,
206223
ifm_shape,
207224
kernel_shape,
208225
padding,
209226
strides,
210227
dilation,
211-
activation_function,
228+
activation,
212229
):
213230
np.random.seed(0)
214231

@@ -221,8 +238,8 @@ def depthwise_conv2d(x):
221238
op = tf.nn.depthwise_conv2d(
222239
x, weight, strides=tf_strides, padding=padding, dilations=dilation
223240
)
224-
if activation_function == "RELU":
225-
op = tf.nn.relu(op)
241+
if activation:
242+
op = activation(op)
226243
return op
227244

228245
infra.compare_tvm_with_tflite(depthwise_conv2d, [ifm_shape], accel_type)
@@ -265,17 +282,18 @@ def depthwise_conv2d(x):
265282
@pytest.mark.parametrize("pooling_type", ["MAX", "AVG"])
266283
@pytest.mark.parametrize("ifm_shape", [[1, 3, 4, 3], [1, 4, 5, 2]])
267284
@pytest.mark.parametrize(
268-
"pool_shape, strides, activation_function, padding",
269-
[([1, 2], [1, 2], "NONE", "SAME"), ([2, 3], [2, 3], "RELU", "VALID")],
285+
"pool_shape, strides, padding",
286+
[([1, 2], [1, 2], "SAME"), ([2, 3], [2, 3], "VALID")],
270287
)
288+
@pytest.mark.parametrize("activation", ACTIVATIONS)
271289
def test_ethosu_pooling(
272290
accel_type,
273291
ifm_shape,
274292
pooling_type,
275293
strides,
276294
pool_shape,
277-
activation_function,
278295
padding,
296+
activation,
279297
):
280298
np.random.seed(0)
281299

@@ -285,8 +303,8 @@ def pooling(x):
285303
op = tf.nn.max_pool(x, pool_shape, strides, padding)
286304
elif pooling_type == "AVG":
287305
op = tf.nn.avg_pool(x, pool_shape, strides, padding)
288-
if activation_function == "RELU":
289-
op = tf.nn.relu(op)
306+
if activation:
307+
op = activation(op)
290308
return op
291309

292310
infra.compare_tvm_with_tflite(pooling, [ifm_shape], accel_type)
@@ -303,13 +321,13 @@ def pooling(x):
303321
([1, 4, 4], [4, 1]),
304322
],
305323
)
306-
@pytest.mark.parametrize("activation_function", ["NONE", "RELU"])
324+
@pytest.mark.parametrize("activation", ACTIVATIONS)
307325
def test_ethosu_binary_elementwise(
308326
accel_type,
309327
operator_type,
310328
ifm_shape,
311329
ifm2_shape,
312-
activation_function,
330+
activation,
313331
):
314332
np.random.seed(0)
315333

@@ -325,8 +343,8 @@ def binary_elementwise(lhs, rhs):
325343
op = tf.math.minimum(lhs, rhs)
326344
elif operator_type == "MAX":
327345
op = tf.math.maximum(lhs, rhs)
328-
if activation_function == "RELU":
329-
op = tf.nn.relu(op)
346+
if activation:
347+
op = activation(op)
330348
return op
331349

332350
infra.compare_tvm_with_tflite(
@@ -1113,13 +1131,13 @@ def leaky_relu_func(x):
11131131
@pytest.mark.parametrize("ifm_shape", [(1, 14), (1, 151)])
11141132
@pytest.mark.parametrize("ofm_channels", [32, 64])
11151133
@pytest.mark.parametrize("use_bias", [True, False])
1116-
@pytest.mark.parametrize("activation_function", ["RELU", "NONE"])
1134+
@pytest.mark.parametrize("activation", ACTIVATIONS)
11171135
def test_tflite_fully_connected(
11181136
accel_type,
11191137
ifm_shape,
11201138
ofm_channels,
11211139
use_bias,
1122-
activation_function,
1140+
activation,
11231141
):
11241142
np.random.seed(0)
11251143

@@ -1134,8 +1152,8 @@ def fully_connected(x):
11341152
x = tf.matmul(x, w)
11351153
if use_bias:
11361154
x = tf.nn.bias_add(x, bias)
1137-
if activation_function:
1138-
x = tf.nn.relu(x)
1155+
if activation:
1156+
x = activation(x)
11391157
return x
11401158

11411159
infra.compare_tvm_with_tflite(

tests/python/contrib/test_ethosu/test_legalize.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -899,6 +899,11 @@ def verify(ext_func):
899899
elif operator_type == "MIN":
900900
rewriter = legalize.MinRewriter()
901901
pattern_table = [
902+
(
903+
ethosu.MinParams.composite_name,
904+
ethosu.minimum_clip_requantize_pattern(),
905+
lambda pat: ethosu.MinParams(pat).is_valid(),
906+
),
902907
(
903908
ethosu.MinParams.composite_name,
904909
ethosu.minimum_pattern(),
@@ -908,6 +913,11 @@ def verify(ext_func):
908913
elif operator_type == "MAX":
909914
rewriter = legalize.MaxRewriter()
910915
pattern_table = [
916+
(
917+
ethosu.MaxParams.composite_name,
918+
ethosu.maximum_clip_requantize_pattern(),
919+
lambda pat: ethosu.MaxParams(pat).is_valid(),
920+
),
911921
(
912922
ethosu.MaxParams.composite_name,
913923
ethosu.maximum_pattern(),

0 commit comments

Comments
 (0)