blob: ce9b253f2df2faae8c7bcca22129640ab4b95406 [file] [log] [blame]
Jeremy Johnson015c3552022-02-23 12:15:03 +00001#!/usr/bin/env python3
TatWai Chong6a46b252024-01-12 13:13:22 -08002# Copyright (c) 2020-2024, ARM Limited.
Jeremy Johnson015c3552022-02-23 12:15:03 +00003# SPDX-License-Identifier: Apache-2.0
4import argparse
Jeremy Johnson015c3552022-02-23 12:15:03 +00005import json
6import math
7import os
8import queue
9import re
10import sys
11import threading
12import traceback
13from datetime import datetime
14from enum import IntEnum
15from enum import unique
Eric Kunze97b00272023-07-20 10:52:56 -070016from pathlib import Path
Jeremy Johnson015c3552022-02-23 12:15:03 +000017
18import numpy as np
Jeremy Johnsone2b5e872023-09-14 17:02:09 +010019from checker.color_print import LogColors
20from checker.color_print import print_color
21from checker.color_print import set_print_in_color
Jeremy Johnson015c3552022-02-23 12:15:03 +000022from runner.run_command import run_sh_command
23from xunit.xunit import xunit_results
24from xunit.xunit import xunit_test
25
26
27def parse_args():
28 parser = argparse.ArgumentParser()
29 parser.add_argument(
Jared Smolensb7af4612022-03-21 19:41:52 -070030 "-t",
31 "--test",
32 dest="test",
33 default=[],
Eric Kunze3403ded2023-07-28 17:23:48 +000034 type=Path,
Jared Smolensb7af4612022-03-21 19:41:52 -070035 nargs="+",
36 help="Test(s) to run",
Jeremy Johnson015c3552022-02-23 12:15:03 +000037 )
38 parser.add_argument(
39 "-r",
40 "--recursive",
41 dest="recursive_tests",
42 action="store_true",
43 help="Recursively search for tests",
44 )
45 parser.add_argument(
46 "--tf-base-dir",
47 dest="tf_base_dir",
48 type=str,
49 required=True,
50 help="Tensorflow/MLIR base directory",
51 )
52 parser.add_argument(
53 "--tools-base-dir",
54 dest="tools_base_dir",
Eric Kunze3403ded2023-07-28 17:23:48 +000055 type=Path,
Jeremy Johnson015c3552022-02-23 12:15:03 +000056 required=True,
57 help="Reference model base directory",
58 )
59 parser.add_argument(
Tai Lya4d748b2023-03-28 22:06:56 +000060 "-p",
61 "--precise-mode",
62 dest="precise_mode",
63 action="store_true",
64 help="run in precise mode (FP64)",
65 )
66 parser.add_argument(
Jeremy Johnson015c3552022-02-23 12:15:03 +000067 "-v", "--verbose", dest="verbose", action="count", help="Verbose run"
68 )
69 parser.add_argument(
70 "-dref",
71 "--debug-ref-model",
72 dest="debug_ref_model",
73 action="store_true",
74 help="Enable TOSA Reference model debugging",
75 )
76 parser.add_argument(
77 "--tolerance",
78 dest="tolerance",
79 default=1e-3,
80 type=float,
81 help="Comparison tolerance b value",
82 )
83 parser.add_argument(
Jerry Gea793f462023-04-11 00:05:02 +000084 "--tosa_level",
85 dest="tosa_level",
86 default="EIGHTK",
87 type=str,
88 help="A TOSA level defines operator parameter ranges that an implementation shall support."
89 "Config tosa_level for running the reference model only. Default is EIGHTK",
90 )
91 parser.add_argument(
Jeremy Johnson015c3552022-02-23 12:15:03 +000092 "--no-compiler",
93 dest="no_compiler",
94 action="store_true",
95 help="Do not run TF MLIR/tfopt/TOSA compiler. Just run TOSA Reference model",
96 )
97 parser.add_argument(
98 "--no-ref-model",
99 dest="no_ref",
100 action="store_true",
101 help="Do not run TOSA reference model, just run TF MLIR/tfopt/TOSA compiler.",
102 )
103 parser.add_argument(
104 "--valgrind",
105 dest="valgrind",
106 action="store_true",
107 help="Enable valgrind on TOSA Reference Model",
108 )
109 parser.add_argument(
110 "-j", "--jobs", dest="jobs", type=int, default=1, help="Number of parallel jobs"
111 )
112 parser.add_argument(
113 "--no-color",
114 "--no-colour",
115 dest="no_color",
116 action="store_true",
117 help="Disable color output",
118 )
119 parser.add_argument(
120 "-f",
121 "--framework",
122 dest="framework",
123 default=[],
124 action="append",
125 help="Frameworks to test (tf, tflite)",
126 )
127 parser.add_argument(
128 "--override-exclusions",
129 dest="override_exclusions",
130 default=False,
131 action="store_true",
132 help="Ignore the framework exclusions listed in the test JSON",
133 )
134 parser.add_argument(
135 "--xunit-file",
136 dest="xunit_file",
137 type=str,
138 default="result.xml",
139 help="XUnit result output file",
140 )
141 parser.add_argument(
142 "--xunit-classname-prefix",
143 dest="xunit_classname_prefix",
144 default="TFUnitTests",
145 help="Prefix for xunit classname",
146 )
147 parser.add_argument(
148 "--hex-bool-hack",
149 dest="hex_bool_hack",
150 default=1,
151 type=int,
152 help=(
153 "Hack around bug in MLIR hex parsing for boolean types"
154 " by disabling hex encoding"
155 ),
156 )
157 parser.add_argument(
158 "--regression-mode",
159 dest="regression_mode",
160 default=False,
161 action="store_true",
162 help="Options to make the script more friendly for jenkins regressions",
163 )
164 parser.add_argument(
165 "--quantize-tolerance",
166 dest="quantize_tolerance",
167 default=0,
168 type=int,
169 help=(
170 "Tolerance when comparing TOSA reference model result"
171 " to TensorFlow Lite reference"
172 ),
173 )
174 parser.add_argument(
175 "--test-dir",
176 dest="test_dir",
Eric Kunze3403ded2023-07-28 17:23:48 +0000177 type=Path,
Jeremy Johnson015c3552022-02-23 12:15:03 +0000178 help="Path to prepend to paths in test.json",
179 )
180
181 parser.add_argument(
182 "-o", "--output", dest="output_file", help="Redirect script output to a file"
183 )
184
185 args = parser.parse_args()
186
187 # No easy way to both do array append and override a default value
188 if not args.framework:
189 args.framework = ["tf", "tflite"]
190
191 # Autodetect CPU count
192 if args.jobs <= 0:
193 args.jobs = os.cpu_count()
194
195 return args
196
197
198@unique
199class TestResult(IntEnum):
200 PASS = 0
201 COMPILER_ERROR = 1
202 REF_MODEL_ERROR = 2
203 REF_MODEL_UNPREDICTABLE = 3
204 REF_MODEL_RUNTIME_ERROR = 4
205 MISMATCH = 5
206 NOT_LOWERED = 6
207 INVALID_MLIR = 7
208 INTERNAL_ERROR = 8
209 SKIPPED = 9
210
211
212TestResultErrorStr = [
213 "",
214 "Compiler error",
215 "Reference model error",
216 "Reference model unpredictable",
217 "Reference model runtime error",
218 "Mismatch",
219 "Not lowered",
220 "Invalid MLIR",
221 "Internal error",
222 "",
223]
224
225
226def parse_compiler_output(compiler_stdout, compiler_stderr):
227 # Look for "has not been lowered yet, skipped" strings in stdout
228 expr = re.compile(".* has not been lowered yet, skipped.*")
229
230 for line in compiler_stdout.splitlines():
231 if expr.match(line):
232 return TestResult.NOT_LOWERED
233
234 return TestResult.PASS
235
236
237def parse_reference_model_output(ref_model_stdout, ref_model_stderr):
238 # Look for "has not been lowered yet, skipped" strings in stdout
239 unpredictable_expr = re.compile(r".*UNPREDICTABLE.*")
240 error_expr = re.compile(".* Graph result: ERROR.*")
241 unknown_expr = re.compile(".* Unknown graph status code.*")
242
243 for line in ref_model_stderr.splitlines():
244 if unpredictable_expr.match(line):
245 return TestResult.REF_MODEL_UNPREDICTABLE
246 elif error_expr.match(line):
247 return TestResult.REF_MODEL_ERROR
248 elif unknown_expr.match(line):
249 return TestResult.REF_MODEL_RUNTIME_ERROR
250
251 return TestResult.PASS
252
253
254# write a self-contained test descriptor in json format
255def write_reference_runner_json(
256 filename,
257 tosa_filename,
258 ifm_name,
259 ifm_file,
260 ofm_name,
261 ofm_file,
262 expected_failure=False,
263):
264 """Write a json test file so that it is fairly easy to pick up the test
265 and generate commands for third party tool"""
266 test_desc = dict()
267
268 test_desc["tosa_file"] = tosa_filename
269 test_desc["ifm_name"] = ifm_name
270 test_desc["ifm_file"] = ifm_file
271 test_desc["ofm_name"] = ofm_name
272 test_desc["ofm_file"] = ofm_file
273 test_desc["expected_failure"] = expected_failure
274
275 with open(filename, "w") as f:
276 json.dump(test_desc, f, indent=" ")
277
278
TatWai Chong6a46b252024-01-12 13:13:22 -0800279""" For dynamic shape model, apply 2 steps to perform compilation, shape inference,
280 and serialization."""
281
282
283def compile_dynamic_model(
284 args,
285 framework,
286 test_path,
287 test_name,
288 pre_opt_filename,
289 post_opt_filename,
290 tosa_mlir_filename,
291 compiler_cmd,
292 flatbuffer_dir_fullpath,
293 shape,
294):
295 try:
296 # 1. Compile the dynamic shape model with unknown shapes and tosa shape ops.
297 dyn_tosa_mlir_filename = str(test_path / f"output_{framework}.dyn.tosa.mlir")
298 compile_dynamic_cmd = compiler_cmd.copy()
299 compile_dynamic_cmd.extend(
300 [
301 "--verify-each",
302 post_opt_filename,
303 "-o",
304 dyn_tosa_mlir_filename,
305 ]
306 )
307 compiler_stdout, compiler_stderr = run_sh_command(
308 compile_dynamic_cmd, args.verbose, True
309 )
310
311 compiler_rc_1 = parse_compiler_output(compiler_stdout, compiler_stderr)
312
313 if compiler_rc_1 == TestResult.NOT_LOWERED:
314 print_color(
315 LogColors.RED,
316 f"Results NOT_LOWERED {test_name}, framework {framework}",
317 )
318 return (TestResult.NOT_LOWERED, 0.0, "", test_name)
319
320 def convert_shape_tuple_to_string(tup):
321 string = ""
322 for dim in tup:
323 string = string + str(dim) + ","
324 # skip the last `,` character.
325 return string[0:-1]
326
327 # 2. Resolve unknown shapes, and perform serialization.
328 if not isinstance(shape, tuple):
329 raise Exception("Only single input is supported currently")
330
331 arg0_argument = '"arg0=' + convert_shape_tuple_to_string(shape) + '"'
332
333 compile_and_shape_infer_cmd = compiler_cmd.copy()
334 compile_and_shape_infer_cmd.extend(
335 [
336 f"--tosa-input-shape={arg0_argument}",
337 "--tosa-infer-shapes",
338 dyn_tosa_mlir_filename,
339 "-o",
340 tosa_mlir_filename,
341 "--tosa-serialize",
342 f"--tosa-flatbuffer-filename={flatbuffer_dir_fullpath / f'{test_name}.tosa'}",
343 ]
344 )
345
346 # Convert list type to string type as double quote \" in list structure causes
347 # single quote \' residue in the final command.
348 compiler_stdout, compiler_stderr = run_sh_command(
349 " ".join(map(str, compile_and_shape_infer_cmd)), args.verbose, True
350 )
351
352 compiler_rc_2 = parse_compiler_output(compiler_stdout, compiler_stderr)
353
354 if compiler_rc_2 == TestResult.NOT_LOWERED:
355 print_color(
356 LogColors.RED,
357 f"Results NOT_LOWERED {test_name}, framework {framework}",
358 )
359 return (TestResult.NOT_LOWERED, 0.0, "", test_name)
360
361 except Exception as e:
362 if "same scale constraint" in str(e):
363 print_color(LogColors.RED, f"Results INVALID_MLIR {test_name}: {e}")
364 return (TestResult.INVALID_MLIR, 0.0, e, test_name)
365 else:
366 print_color(LogColors.RED, f"Results COMPILER_ERROR {test_name}: {e}")
367 return (TestResult.COMPILER_ERROR, 0.0, e, test_name)
368
369
Eric Kunze3403ded2023-07-28 17:23:48 +0000370def run_test(args, test_path, framework):
Eric Kunze97b00272023-07-20 10:52:56 -0700371 msg = ""
372
373 try:
374 with open(test_path / "test.json", "r") as f:
375 test_desc = json.load(f)
376 except Exception:
377 raise Exception(f"Could not load or parse test from {test_path / 'test.json'}")
378
Jeremy Johnson015c3552022-02-23 12:15:03 +0000379 test_name = None
Eric Kunze97b00272023-07-20 10:52:56 -0700380 if "name" in test_desc:
381 test_name = test_desc["name"]
382 else:
383 test_name = test_path.name
Jeremy Johnson015c3552022-02-23 12:15:03 +0000384 if not test_name:
Eric Kunze3403ded2023-07-28 17:23:48 +0000385 raise Exception(f"Could not parse test_name from {test_path}")
Jeremy Johnson015c3552022-02-23 12:15:03 +0000386
Eric Kunze3403ded2023-07-28 17:23:48 +0000387 print_color(LogColors.GREEN, f"## Running {framework} test {test_name}")
Jeremy Johnson015c3552022-02-23 12:15:03 +0000388
Jeremy Johnson015c3552022-02-23 12:15:03 +0000389 try:
390 if not args.override_exclusions:
391 for excl in test_desc["framework_exclusions"]:
392 if excl == framework:
393 print_color(LogColors.GREEN, "Results SKIPPED")
Eric Kunze3403ded2023-07-28 17:23:48 +0000394 return (TestResult.SKIPPED, 0.0, "", test_name)
Jeremy Johnson015c3552022-02-23 12:15:03 +0000395 except KeyError:
396 pass
397
Eric Kunze3403ded2023-07-28 17:23:48 +0000398 tf_tools_dir = Path(
399 f"{args.tf_base_dir}/bazel-bin/tensorflow/compiler/mlir"
400 ).resolve()
Jeremy Johnson015c3552022-02-23 12:15:03 +0000401
Eric Kunze3403ded2023-07-28 17:23:48 +0000402 pre_opt_filename = str(test_path / f"test_{framework}.preopt.mlir")
403 post_opt_filename = str(test_path / f"test_{framework}.postopt.mlir")
Jeremy Johnson015c3552022-02-23 12:15:03 +0000404 if args.test_dir:
405 test_path_prepend = args.test_dir
406 else:
Eric Kunze3403ded2023-07-28 17:23:48 +0000407 test_path_prepend = test_path
Jeremy Johnson015c3552022-02-23 12:15:03 +0000408
409 # 1. Framework to MLIR translator command
410 if framework == "tf":
411 if test_desc["tf_model_filename"].endswith(".mlir"):
412 pre_opt_filename = test_desc["tf_model_filename"]
413 translate_mlir_cmd = []
414 else:
415 translate_mlir_cmd = [
Eric Kunze3403ded2023-07-28 17:23:48 +0000416 str(tf_tools_dir / "tf-mlir-translate"),
Jeremy Johnson015c3552022-02-23 12:15:03 +0000417 "--graphdef-to-mlir",
418 "--tf-enable-shape-inference-on-import",
Eric Kunze3403ded2023-07-28 17:23:48 +0000419 f"--tf-output-arrays={test_desc['tf_result_name']}",
420 str(test_path_prepend / test_desc["tf_model_filename"]),
Jeremy Johnson015c3552022-02-23 12:15:03 +0000421 "-o",
422 pre_opt_filename,
423 ]
424 elif framework == "tflite":
425 if test_desc["tflite_model_filename"].endswith(".mlir"):
426 pre_opt_filename = test_desc["tflite_model_filename"]
427 translate_mlir_cmd = []
428 else:
429 translate_mlir_cmd = [
Eric Kunze3403ded2023-07-28 17:23:48 +0000430 str(tf_tools_dir / "lite" / "flatbuffer_translate"),
Jeremy Johnson015c3552022-02-23 12:15:03 +0000431 "--tflite-flatbuffer-to-mlir",
Eric Kunze3403ded2023-07-28 17:23:48 +0000432 str(test_path_prepend / test_desc["tflite_model_filename"]),
433 f"--output-arrays={test_desc['tflite_result_name']}",
Jeremy Johnson015c3552022-02-23 12:15:03 +0000434 "-o",
435 pre_opt_filename,
436 ]
437 else:
Eric Kunze3403ded2023-07-28 17:23:48 +0000438 raise Exception(f"Unknown framwork: {framework}")
Jeremy Johnson015c3552022-02-23 12:15:03 +0000439
440 # Any additional inputs to the translator?
441 input_tensor_prefix = "TosaInput_"
Eric Kunze3403ded2023-07-28 17:23:48 +0000442 flatbuffer_dir = f"flatbuffer-{framework}"
Jeremy Johnson015c3552022-02-23 12:15:03 +0000443 mlir_opts = []
444
445 # Temporary hack: MLIR's new hex encoding of large tensors does not work for
446 # boolean types
447 # for TF hash 8e8041d594a888eb67eafa5cc62627d7e9ca8082
Eric Kunze3403ded2023-07-28 17:23:48 +0000448 if str(test_path).endswith("_bool") and args.hex_bool_hack:
Jeremy Johnson015c3552022-02-23 12:15:03 +0000449 mlir_opts.append("--mlir-print-elementsattrs-with-hex-if-larger=-1")
450
451 try:
452 # specify input tensors if test is generated from .pb
453 if framework == "tf":
454 # Convert the shape to a mlir-friendly string
455 shapes = []
456 for curr_shape in test_desc["ifm_shape"]:
457 shape_str = ""
458 for dim in curr_shape:
459 shape_str = shape_str + str(dim) + ","
460 shapes.append(shape_str)
461
462 translate_mlir_cmd.extend(
463 ["--tf-input-arrays", ",".join(test_desc["ifm_name"])]
464 )
465 translate_mlir_cmd.extend(["--tf-input-shapes", ":".join(shapes)])
466
467 # Write the hard-coded placeholder input (reshaped as necesary) to
468 # the file that compiler specified.
469 reference_runner_ifm_name = []
470 for i in range(len(test_desc["ifm_file"])):
Eric Kunze3403ded2023-07-28 17:23:48 +0000471 ifm_tensor_name = f"{input_tensor_prefix}{i}"
Jeremy Johnson015c3552022-02-23 12:15:03 +0000472
473 assert test_desc["ifm_file"][i].endswith(".npy")
Eric Kunze3403ded2023-07-28 17:23:48 +0000474 ifm_np = np.load(test_path / test_desc["ifm_file"][i])
Jared Smolensb7af4612022-03-21 19:41:52 -0700475
476 # We sometimes encounter input shape/expected input shape mismatches
477 # due to a missing batch dimension on the input (e.g. a single 3D image).
478 #
479 # Make sure input numpy and input shape from descriptor match,
480 # expand_dims on the outer dimensions until the rank matches,
481 # then do the shape comparison.
482 while len(list(ifm_np.shape)) < len(test_desc["ifm_shape"][i]):
483 ifm_np = np.expand_dims(ifm_np, axis=0)
484
Luke Hutton714aa602023-02-08 19:45:26 +0000485 # After legalization, complex tensors are expected to be represented
486 # as a single floating point tensor of shape [?, ..., ?, 2].
487 expected_shape = test_desc["ifm_shape"][i]
Eric Kunze3403ded2023-07-28 17:23:48 +0000488 if str(test_path).endswith("c64"):
Luke Hutton714aa602023-02-08 19:45:26 +0000489 expected_shape.append(2)
490
491 assert list(ifm_np.shape) == expected_shape
Jeremy Johnson015c3552022-02-23 12:15:03 +0000492
493 reference_runner_ifm_name.append(ifm_tensor_name)
494
495 except KeyError:
496 # No additional inputs. Ignore.
497 pass
498
499 tf_opt_cmd = [
Eric Kunze3403ded2023-07-28 17:23:48 +0000500 str(tf_tools_dir / "tf-opt"),
Jeremy Johnson015c3552022-02-23 12:15:03 +0000501 "--tf-executor-to-functional-conversion",
502 "--verify-each",
503 pre_opt_filename,
504 "-o",
505 post_opt_filename,
506 ]
507
508 translate_mlir_cmd.extend(mlir_opts)
509 tf_opt_cmd.extend(mlir_opts)
510
Eric Kunze3403ded2023-07-28 17:23:48 +0000511 compiler_cmd = [str(tf_tools_dir / "tf-opt")]
Jeremy Johnson015c3552022-02-23 12:15:03 +0000512
513 if framework == "tf":
514 compiler_cmd.append("--tf-to-tosa-pipeline")
515 elif framework == "tflite":
516 compiler_cmd.append("--tfl-to-tosa-pipeline")
517 compiler_cmd.append("--tosa-strip-quant-types")
518
Eric Kunze3403ded2023-07-28 17:23:48 +0000519 tosa_mlir_filename = str(test_path / f"output_{framework}.tosa.mlir")
Jeremy Johnson015c3552022-02-23 12:15:03 +0000520
Eric Kunze3403ded2023-07-28 17:23:48 +0000521 flatbuffer_dir_fullpath = test_path / flatbuffer_dir
Jeremy Johnson015c3552022-02-23 12:15:03 +0000522
Eric Kunze3403ded2023-07-28 17:23:48 +0000523 flatbuffer_dir_fullpath.mkdir(exist_ok=True)
Jeremy Johnson015c3552022-02-23 12:15:03 +0000524
TatWai Chong6a46b252024-01-12 13:13:22 -0800525 compile_and_serialize_cmd = compiler_cmd.copy()
526 compile_and_serialize_cmd.extend(
Jeremy Johnson015c3552022-02-23 12:15:03 +0000527 [
528 "--verify-each",
529 post_opt_filename,
530 "-o",
531 tosa_mlir_filename,
532 "--tosa-serialize",
Eric Kunze3403ded2023-07-28 17:23:48 +0000533 f"--tosa-flatbuffer-filename={flatbuffer_dir_fullpath / f'{test_name}.tosa'}",
Jeremy Johnson015c3552022-02-23 12:15:03 +0000534 ]
535 )
536
537 if not args.no_compiler:
538 try:
539 if translate_mlir_cmd:
540 run_sh_command(translate_mlir_cmd, args.verbose, True)
541 if tf_opt_cmd:
542 run_sh_command(tf_opt_cmd, args.verbose, True)
543 except Exception as e:
Eric Kunze3403ded2023-07-28 17:23:48 +0000544 print_color(LogColors.RED, f"Results INVALID_MLIR {test_name}: {e}")
545 return (TestResult.INVALID_MLIR, 0.0, e, test_name)
Jeremy Johnson015c3552022-02-23 12:15:03 +0000546
TatWai Chong6a46b252024-01-12 13:13:22 -0800547 if "ifm_dynamic" in test_desc and test_desc["ifm_dynamic"] == 1:
548 compile_dynamic_model(
549 args,
550 framework,
551 test_path,
552 test_name,
553 pre_opt_filename,
554 post_opt_filename,
555 tosa_mlir_filename,
556 compiler_cmd,
557 flatbuffer_dir_fullpath,
558 ifm_np.shape,
Jeremy Johnson015c3552022-02-23 12:15:03 +0000559 )
TatWai Chong6a46b252024-01-12 13:13:22 -0800560 else:
561 try:
562 compiler_stdout, compiler_stderr = run_sh_command(
563 compile_and_serialize_cmd, args.verbose, True
Jeremy Johnson015c3552022-02-23 12:15:03 +0000564 )
TatWai Chong6a46b252024-01-12 13:13:22 -0800565 compiler_rc = parse_compiler_output(compiler_stdout, compiler_stderr)
566 if compiler_rc == TestResult.NOT_LOWERED:
567 print_color(
568 LogColors.RED,
569 f"Results NOT_LOWERED {test_name}, framework {framework}",
570 )
571 return (TestResult.NOT_LOWERED, 0.0, "", test_name)
Jeremy Johnson015c3552022-02-23 12:15:03 +0000572
TatWai Chong6a46b252024-01-12 13:13:22 -0800573 pass
Jeremy Johnson015c3552022-02-23 12:15:03 +0000574
TatWai Chong6a46b252024-01-12 13:13:22 -0800575 except Exception as e:
576 if "same scale constraint" in str(e):
577 print_color(LogColors.RED, f"Results INVALID_MLIR {test_name}: {e}")
578 return (TestResult.INVALID_MLIR, 0.0, e, test_name)
579 else:
580 print_color(
581 LogColors.RED, f"Results COMPILER_ERROR {test_name}: {e}"
582 )
583 return (TestResult.COMPILER_ERROR, 0.0, e, test_name)
Jeremy Johnson015c3552022-02-23 12:15:03 +0000584
585 if framework == "tf":
586 try:
Eric Kunze3403ded2023-07-28 17:23:48 +0000587 tf_result = np.load(test_path / test_desc["tf_result_npy_filename"])
Jeremy Johnson015c3552022-02-23 12:15:03 +0000588 except KeyError:
589 assert 0, "fail to load tf result numpy"
590 elif framework == "tflite":
591 try:
Eric Kunze3403ded2023-07-28 17:23:48 +0000592 tf_result = np.load(test_path / test_desc["tflite_result_npy_filename"])
Jeremy Johnson015c3552022-02-23 12:15:03 +0000593 except KeyError:
594 assert 0, "fail to load tflite result numpy"
595
Luke Hutton261b7b62023-01-10 14:50:31 +0000596 # TOSA has no notion of complex datatypes, it represents complex values using two
597 # fp32 output tensors representing real and imaginary values. When legalizing
598 # complex operations from frameworks, these two output tensors are combined into
599 # a single tensor of shape [?, ..., ?, 2] whereby each inner pair of values
600 # represents the real and imaginary parts of a complex value. This is completed
601 # by inserting reshape and concatenate TOSA operations during the legalization to
602 # maintain a one-to-one correspondance with framework outputs, thus simplifying
603 # legalization. Here tf_result should also match this format before being
604 # compared to the ref model output.
605 if tf_result.dtype == np.complex64:
606 ifm_shape = tf_result.shape + (2,)
607 tf_result = tf_result.view(np.float32)
608 tf_result = tf_result.reshape(ifm_shape)
609
Jeremy Johnson015c3552022-02-23 12:15:03 +0000610 # Generate test descriptor per flatbuffer generation
611 # Input .npy will be shared across different frameworks
612 # Output .npy will be generated in its corresponding flatbuffer
613 reference_runner_ifm_file = [
Eric Kunze3403ded2023-07-28 17:23:48 +0000614 str(Path("..") / ifm_file) for ifm_file in test_desc["ifm_file"]
Jeremy Johnson015c3552022-02-23 12:15:03 +0000615 ]
616
617 # Check if there's any operator in output graph.
618 empty_graph = True
619 with open(tosa_mlir_filename, "r") as f:
620 for line in f:
TatWai Chonge0247482023-06-01 15:02:24 -0700621 # TOSA assembly instructions all start with `tosa.`
622 if re.search(r"tosa\.", line):
Jeremy Johnson015c3552022-02-23 12:15:03 +0000623 empty_graph = False
624
625 break
626
627 # Fast-forward input tensor to output tensor if TOSA graph is empty.
628 if empty_graph:
629 reference_runner_ofm_name = reference_runner_ifm_name
630 else:
631 reference_runner_ofm_name = ["TosaOutput_0"]
632
633 write_reference_runner_json(
Eric Kunze3403ded2023-07-28 17:23:48 +0000634 filename=str(test_path / flatbuffer_dir / "desc.json"),
635 tosa_filename=f"{test_name}.tosa",
Jeremy Johnson015c3552022-02-23 12:15:03 +0000636 ifm_name=reference_runner_ifm_name,
637 ifm_file=reference_runner_ifm_file,
638 ofm_name=reference_runner_ofm_name,
639 ofm_file=["ref_model_output_0.npy"],
640 )
641
642 ref_model_cmd = [
Eric Kunze3403ded2023-07-28 17:23:48 +0000643 str(args.tools_base_dir / "build" / "reference_model" / "tosa_reference_model"),
644 f"--test_desc={test_path / flatbuffer_dir / 'desc.json'}",
Jeremy Johnson015c3552022-02-23 12:15:03 +0000645 ]
646
647 if args.debug_ref_model:
Eric Kunze286f8342022-06-22 11:30:23 -0700648 ref_model_cmd.extend(["-D ALL", "-l high"])
Jeremy Johnson015c3552022-02-23 12:15:03 +0000649
Tai Lya4d748b2023-03-28 22:06:56 +0000650 if args.precise_mode:
651 ref_model_cmd.extend(["--precise_mode=1"])
652
Jeremy Johnson015c3552022-02-23 12:15:03 +0000653 if args.valgrind:
654 ref_model_cmd = [
655 "valgrind",
656 "--show-leak-kinds=all",
657 "--log-fd=1",
658 "-q",
659 ] + ref_model_cmd
660
Eric Kunze3403ded2023-07-28 17:23:48 +0000661 ref_model_cmd = ref_model_cmd + [f"--tosa_level={args.tosa_level}"]
Jerry Gea793f462023-04-11 00:05:02 +0000662
Jeremy Johnson015c3552022-02-23 12:15:03 +0000663 # Clean out any ref_model result first
Eric Kunze3403ded2023-07-28 17:23:48 +0000664 for f in (test_path / flatbuffer_dir).glob("ref_model_*.npy"):
665 f.unlink()
Jeremy Johnson015c3552022-02-23 12:15:03 +0000666
Jared Smolensb7af4612022-03-21 19:41:52 -0700667 if args.no_ref:
668 return (TestResult.PASS, 0.0, msg)
669
670 try:
671 ref_model_stdout, ref_model_stderr = run_sh_command(
672 ref_model_cmd, args.verbose, True
673 )
674 ref_model_rc = parse_reference_model_output(ref_model_stdout, ref_model_stderr)
675 if ref_model_rc != TestResult.PASS:
676 return (ref_model_rc, 0.0, "")
677 except Exception as e:
678 ref_model_rc = parse_reference_model_output("", str(e))
679 if ref_model_rc != TestResult.PASS:
Jeremy Johnson015c3552022-02-23 12:15:03 +0000680 print_color(
681 LogColors.RED,
Eric Kunze3403ded2023-07-28 17:23:48 +0000682 f"Results {TestResultErrorStr[ref_model_rc]} {test_name}: {e}",
Jeremy Johnson015c3552022-02-23 12:15:03 +0000683 )
Jared Smolensb7af4612022-03-21 19:41:52 -0700684 return (ref_model_rc, 0.0, "")
Eric Kunze3403ded2023-07-28 17:23:48 +0000685 print_color(LogColors.RED, f"Results REF_MODEL_RUNTIME_ERROR {test_name}: {e}")
686 return (TestResult.REF_MODEL_RUNTIME_ERROR, 0.0, e, test_name)
Jeremy Johnson015c3552022-02-23 12:15:03 +0000687
Tai Lya4d748b2023-03-28 22:06:56 +0000688 if args.precise_mode == 1 and (
689 tf_result.dtype == np.float16 or tf_result.dtype == np.float32
690 ):
691 tf_result = tf_result.astype(np.float64)
692 elif tf_result.dtype == np.float16:
Jeremy Johnson015c3552022-02-23 12:15:03 +0000693 tf_result = tf_result.astype(np.float32)
Jerry Gec5291692024-01-02 22:29:08 +0000694 elif tf_result.dtype == np.int8:
695 tf_result = tf_result.astype(np.int8)
696 elif tf_result.dtype == np.uint8:
697 tf_result = tf_result.astype(np.uint8)
698 elif tf_result.dtype == np.int16 or tf_result.dtype == np.int64:
Jeremy Johnson015c3552022-02-23 12:15:03 +0000699 tf_result = tf_result.astype(np.int32)
700
Eric Kunze3403ded2023-07-28 17:23:48 +0000701 # For now, search for the first output from ref_model
702 ref_model_result_files = list((test_path / flatbuffer_dir).glob("ref_model_*.npy"))
Jeremy Johnson015c3552022-02-23 12:15:03 +0000703 ref_model_result = np.load(ref_model_result_files[0])
704
705 assert (
706 tf_result.dtype == ref_model_result.dtype
Eric Kunze3403ded2023-07-28 17:23:48 +0000707 ), f"Numpy type mismatch {tf_result.dtype} != {ref_model_result.dtype} when comparing result"
Jeremy Johnson015c3552022-02-23 12:15:03 +0000708
709 # Size comparison
710 # Size = 1 tensors can be equivalently represented as having rank 0 or rank
711 # >= 0, allow that special case
712 tf_result = np.squeeze(tf_result)
713 ref_model_result = np.squeeze(ref_model_result)
714
715 if np.shape(tf_result) != np.shape(ref_model_result):
Eric Kunze3403ded2023-07-28 17:23:48 +0000716 print_color(LogColors.RED, f"Results MISCOMPARE {test_name}")
717 msg = f"Shapes mismatch: Reference {np.shape(tf_result)} vs {np.shape(ref_model_result)}"
Jeremy Johnson015c3552022-02-23 12:15:03 +0000718 print(msg)
Eric Kunze3403ded2023-07-28 17:23:48 +0000719 return (TestResult.MISMATCH, 0.0, msg, test_name)
Jeremy Johnson015c3552022-02-23 12:15:03 +0000720
721 # for quantized test, allow +-(args.quantize_tolerance) error
722 if ref_model_result.dtype == np.int32:
723 assert tf_result.dtype == np.int32
724
725 if np.all(np.absolute(ref_model_result - tf_result) <= args.quantize_tolerance):
Eric Kunze3403ded2023-07-28 17:23:48 +0000726 print_color(LogColors.GREEN, f"Results PASS {test_name}")
Jeremy Johnson015c3552022-02-23 12:15:03 +0000727 else:
Eric Kunze3403ded2023-07-28 17:23:48 +0000728 print_color(LogColors.RED, f"Results MISCOMPARE {test_name}")
Jeremy Johnson015c3552022-02-23 12:15:03 +0000729
730 tolerance = args.quantize_tolerance + 1
731 while not np.all(
732 np.absolute(ref_model_result - tf_result) <= args.quantize_tolerance
733 ):
734 tolerance = tolerance + 1
735 if tolerance >= 10:
736 break
737
Eric Kunze3403ded2023-07-28 17:23:48 +0000738 msg = f"Result is within {tolerance} {test_path}"
Jeremy Johnson015c3552022-02-23 12:15:03 +0000739 print(msg)
740
741 np.set_printoptions(threshold=128)
Eric Kunze3403ded2023-07-28 17:23:48 +0000742 print(f"tf_result: {tf_result.shape}\n")
Jeremy Johnson015c3552022-02-23 12:15:03 +0000743 print(tf_result)
Eric Kunze3403ded2023-07-28 17:23:48 +0000744 print(f"ref_model_result: {ref_model_result.shape}\n")
Jeremy Johnson015c3552022-02-23 12:15:03 +0000745 print(ref_model_result)
746 # print(tf_result - ref_model_result)
Eric Kunze3403ded2023-07-28 17:23:48 +0000747 return (TestResult.MISMATCH, tolerance, msg, test_name)
Jeremy Johnson015c3552022-02-23 12:15:03 +0000748 else:
749 if np.allclose(
750 ref_model_result, tf_result, atol=args.tolerance, equal_nan=True
751 ):
Eric Kunze3403ded2023-07-28 17:23:48 +0000752 print_color(LogColors.GREEN, f"Results PASS {test_name}")
Jeremy Johnson015c3552022-02-23 12:15:03 +0000753 else:
Eric Kunze3403ded2023-07-28 17:23:48 +0000754 print_color(LogColors.RED, f"Results MISCOMPARE {test_name}")
Jeremy Johnson015c3552022-02-23 12:15:03 +0000755
756 # Many of these tests would match with a reasonable looser tolerence.
757 # Determine what would have worked.
758 tolerance = args.tolerance * 10.0
759 while not np.allclose(
760 ref_model_result, tf_result, atol=tolerance, equal_nan=True
761 ):
762 tolerance = tolerance * 10.0
763 if tolerance > 1.0e10:
764 tolerance = math.inf
765 break
766
Eric Kunze3403ded2023-07-28 17:23:48 +0000767 msg = f"Result is within {tolerance:.0e} {test_name}"
Jeremy Johnson015c3552022-02-23 12:15:03 +0000768 print(msg)
769
770 np.set_printoptions(precision=4, threshold=128)
Eric Kunze3403ded2023-07-28 17:23:48 +0000771 print(f"tf_result: {tf_result.shape}\n")
Jeremy Johnson015c3552022-02-23 12:15:03 +0000772 print(tf_result)
Eric Kunze3403ded2023-07-28 17:23:48 +0000773 print(f"ref_model_result: {ref_model_result.shape}\n")
Jeremy Johnson015c3552022-02-23 12:15:03 +0000774 print(ref_model_result)
775 # print(tf_result - ref_model_result)
Eric Kunze3403ded2023-07-28 17:23:48 +0000776 return (TestResult.MISMATCH, tolerance, msg, test_name)
Jeremy Johnson015c3552022-02-23 12:15:03 +0000777
Eric Kunze3403ded2023-07-28 17:23:48 +0000778 return (TestResult.PASS, args.tolerance, msg, test_name)
Jeremy Johnson015c3552022-02-23 12:15:03 +0000779
780
781def worker_thread(task_queue, args, result_queue):
782 while True:
783 try:
784 (test, framework) = task_queue.get(block=False)
785 except queue.Empty:
786 break
787
788 if test is None:
789 break
790
791 msg = ""
792 start_time = datetime.now()
793 try:
Eric Kunze3403ded2023-07-28 17:23:48 +0000794 (rc, tolerance, msg, test_name) = run_test(args, test, framework)
Jeremy Johnson015c3552022-02-23 12:15:03 +0000795 except Exception as e:
Eric Kunze3403ded2023-07-28 17:23:48 +0000796 print(f"Internal regression error: {e}")
Jeremy Johnson015c3552022-02-23 12:15:03 +0000797 print(
798 "".join(
799 traceback.format_exception(
800 etype=type(e), value=e, tb=e.__traceback__
801 )
802 )
803 )
804 rc = TestResult.INTERNAL_ERROR
805 tolerance = 0.0
806
807 end_time = datetime.now()
808
Eric Kunze3403ded2023-07-28 17:23:48 +0000809 result_queue.put(
810 (test, framework, rc, tolerance, msg, end_time - start_time, test_name)
811 )
Jeremy Johnson015c3552022-02-23 12:15:03 +0000812 task_queue.task_done()
813
814 return True
815
816
817def getTestsInDir(directory):
818 # Recursively find any tests in this directory
Eric Kunze3403ded2023-07-28 17:23:48 +0000819 if (directory / "test.json").is_file():
Jeremy Johnson015c3552022-02-23 12:15:03 +0000820 return [directory]
Eric Kunze3403ded2023-07-28 17:23:48 +0000821 elif directory.is_dir():
Jeremy Johnson015c3552022-02-23 12:15:03 +0000822 test_list = []
Eric Kunze3403ded2023-07-28 17:23:48 +0000823 for d in directory.glob("*"):
Jeremy Johnson015c3552022-02-23 12:15:03 +0000824 test_list.extend(getTestsInDir(d))
825 return test_list
826 else:
827 return []
828
829
830def main():
831 args = parse_args()
832
833 set_print_in_color(not args.no_color)
834
835 if args.output_file:
836 set_print_in_color(False)
837 sys.stdout = open(args.output_file, "w")
838
839 # Disable TF info messages
840 os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3"
841
842 task_queue = queue.Queue()
843 result_queue = queue.Queue()
844
845 threads = []
846
847 # Result counters for each of the TestResult return codes
848 results = [0] * len(TestResult)
849
850 for tdir in args.test:
Jeremy Johnson015c3552022-02-23 12:15:03 +0000851 if args.recursive_tests:
852 tdirList = getTestsInDir(tdir)
853 else:
854 tdirList = [tdir]
855
856 for t in tdirList:
857 for f in args.framework:
858 task_queue.put((t, f))
859
860 for i in range(args.jobs):
861 t = threading.Thread(
862 target=worker_thread, args=(task_queue, args, result_queue)
863 )
864 t.setDaemon(True)
865 t.start()
866 threads.append(t)
867
868 # Run until queue is empty
869 task_queue.join()
870
871 print_color(LogColors.BOLD_WHITE, "Result summary")
872
873 result_list = []
874 while True:
875 try:
Eric Kunze3403ded2023-07-28 17:23:48 +0000876 test, framework, rc, tol, msg, time_delta, test_name = result_queue.get(
877 block=False
878 )
Jeremy Johnson015c3552022-02-23 12:15:03 +0000879 except queue.Empty:
880 break
881
Eric Kunze3403ded2023-07-28 17:23:48 +0000882 result_list.append((test, framework, rc, tol, msg, time_delta, test_name))
Jeremy Johnson015c3552022-02-23 12:15:03 +0000883 results[rc] = results[rc] + 1
884
885 xunit_result = xunit_results()
886 xunit_suite = xunit_result.create_suite(args.xunit_classname_prefix)
887
888 # Sort by test name
Eric Kunze3403ded2023-07-28 17:23:48 +0000889 for test, framework, rc, tol, err_msg, time_delta, test_name in sorted(
Jeremy Johnson015c3552022-02-23 12:15:03 +0000890 result_list, key=lambda tup: tup[0]
891 ):
Jeremy Johnson015c3552022-02-23 12:15:03 +0000892 class_name = f"{args.xunit_classname_prefix}.{framework}"
893
894 xt = xunit_test(test_name, class_name)
895
896 msg = TestResultErrorStr[rc]
897
898 xt.time = str(
899 float(time_delta.seconds) + (float(time_delta.microseconds) * 1e-6)
900 )
901
902 if len(msg) > 0:
Eric Kunze3403ded2023-07-28 17:23:48 +0000903 print(f"{msg} on {framework} {test}")
Jeremy Johnson015c3552022-02-23 12:15:03 +0000904
905 # Add any more verbose messaging for the xml log
906 if err_msg:
Eric Kunze3403ded2023-07-28 17:23:48 +0000907 msg = f"{msg} {err_msg}"
Jeremy Johnson015c3552022-02-23 12:15:03 +0000908
909 if rc == TestResult.PASS:
910 pass
911 elif rc == TestResult.SKIPPED:
912 xt.skipped()
913 else:
914 xt.failed(msg)
915
916 xunit_suite.tests.append(xt)
917
918 result_queue.task_done()
919
920 xunit_result.write_results(args.xunit_file)
921
922 print("Totals: ", end="")
923 for result in TestResult:
Eric Kunze3403ded2023-07-28 17:23:48 +0000924 print(f"{results[result]} {result.name.lower()}, ", end="")
Jeremy Johnson015c3552022-02-23 12:15:03 +0000925 print()
926
927 if not args.regression_mode and (
928 results[TestResult.COMPILER_ERROR] > 0
929 or results[TestResult.REF_MODEL_ERROR] > 0
930 or results[TestResult.MISMATCH] > 0
931 ):
932 return 1
933
934 return 0
935
936
937if __name__ == "__main__":
938 exit(main())