blob: 82e3aade2b65c25edca7a1b23d48bd7449bcd1f3 [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,
Jerry Ged5b15122024-03-26 20:51:48 +0000262 variable_name,
263 variable_file,
Jeremy Johnson015c3552022-02-23 12:15:03 +0000264 expected_failure=False,
265):
266 """Write a json test file so that it is fairly easy to pick up the test
267 and generate commands for third party tool"""
268 test_desc = dict()
269
270 test_desc["tosa_file"] = tosa_filename
271 test_desc["ifm_name"] = ifm_name
272 test_desc["ifm_file"] = ifm_file
273 test_desc["ofm_name"] = ofm_name
274 test_desc["ofm_file"] = ofm_file
Jerry Ged5b15122024-03-26 20:51:48 +0000275 test_desc["variable_name"] = variable_name
276 test_desc["variable_file"] = variable_file
Jeremy Johnson015c3552022-02-23 12:15:03 +0000277 test_desc["expected_failure"] = expected_failure
278
279 with open(filename, "w") as f:
280 json.dump(test_desc, f, indent=" ")
281
282
TatWai Chong6a46b252024-01-12 13:13:22 -0800283""" For dynamic shape model, apply 2 steps to perform compilation, shape inference,
284 and serialization."""
285
286
287def compile_dynamic_model(
288 args,
289 framework,
290 test_path,
291 test_name,
292 pre_opt_filename,
293 post_opt_filename,
294 tosa_mlir_filename,
295 compiler_cmd,
296 flatbuffer_dir_fullpath,
297 shape,
298):
299 try:
300 # 1. Compile the dynamic shape model with unknown shapes and tosa shape ops.
301 dyn_tosa_mlir_filename = str(test_path / f"output_{framework}.dyn.tosa.mlir")
302 compile_dynamic_cmd = compiler_cmd.copy()
303 compile_dynamic_cmd.extend(
304 [
305 "--verify-each",
306 post_opt_filename,
307 "-o",
308 dyn_tosa_mlir_filename,
309 ]
310 )
311 compiler_stdout, compiler_stderr = run_sh_command(
312 compile_dynamic_cmd, args.verbose, True
313 )
314
315 compiler_rc_1 = parse_compiler_output(compiler_stdout, compiler_stderr)
316
317 if compiler_rc_1 == TestResult.NOT_LOWERED:
318 print_color(
319 LogColors.RED,
320 f"Results NOT_LOWERED {test_name}, framework {framework}",
321 )
322 return (TestResult.NOT_LOWERED, 0.0, "", test_name)
323
324 def convert_shape_tuple_to_string(tup):
325 string = ""
326 for dim in tup:
327 string = string + str(dim) + ","
328 # skip the last `,` character.
329 return string[0:-1]
330
331 # 2. Resolve unknown shapes, and perform serialization.
332 if not isinstance(shape, tuple):
333 raise Exception("Only single input is supported currently")
334
335 arg0_argument = '"arg0=' + convert_shape_tuple_to_string(shape) + '"'
336
337 compile_and_shape_infer_cmd = compiler_cmd.copy()
338 compile_and_shape_infer_cmd.extend(
339 [
340 f"--tosa-input-shape={arg0_argument}",
341 "--tosa-infer-shapes",
342 dyn_tosa_mlir_filename,
343 "-o",
344 tosa_mlir_filename,
345 "--tosa-serialize",
346 f"--tosa-flatbuffer-filename={flatbuffer_dir_fullpath / f'{test_name}.tosa'}",
347 ]
348 )
349
350 # Convert list type to string type as double quote \" in list structure causes
351 # single quote \' residue in the final command.
352 compiler_stdout, compiler_stderr = run_sh_command(
353 " ".join(map(str, compile_and_shape_infer_cmd)), args.verbose, True
354 )
355
356 compiler_rc_2 = parse_compiler_output(compiler_stdout, compiler_stderr)
357
358 if compiler_rc_2 == TestResult.NOT_LOWERED:
359 print_color(
360 LogColors.RED,
361 f"Results NOT_LOWERED {test_name}, framework {framework}",
362 )
363 return (TestResult.NOT_LOWERED, 0.0, "", test_name)
364
365 except Exception as e:
366 if "same scale constraint" in str(e):
367 print_color(LogColors.RED, f"Results INVALID_MLIR {test_name}: {e}")
368 return (TestResult.INVALID_MLIR, 0.0, e, test_name)
369 else:
370 print_color(LogColors.RED, f"Results COMPILER_ERROR {test_name}: {e}")
371 return (TestResult.COMPILER_ERROR, 0.0, e, test_name)
372
373
Eric Kunze3403ded2023-07-28 17:23:48 +0000374def run_test(args, test_path, framework):
Eric Kunze97b00272023-07-20 10:52:56 -0700375 msg = ""
376
377 try:
378 with open(test_path / "test.json", "r") as f:
379 test_desc = json.load(f)
380 except Exception:
381 raise Exception(f"Could not load or parse test from {test_path / 'test.json'}")
382
Jeremy Johnson015c3552022-02-23 12:15:03 +0000383 test_name = None
Eric Kunze97b00272023-07-20 10:52:56 -0700384 if "name" in test_desc:
385 test_name = test_desc["name"]
386 else:
387 test_name = test_path.name
Jeremy Johnson015c3552022-02-23 12:15:03 +0000388 if not test_name:
Eric Kunze3403ded2023-07-28 17:23:48 +0000389 raise Exception(f"Could not parse test_name from {test_path}")
Jeremy Johnson015c3552022-02-23 12:15:03 +0000390
Eric Kunze3403ded2023-07-28 17:23:48 +0000391 print_color(LogColors.GREEN, f"## Running {framework} test {test_name}")
Jeremy Johnson015c3552022-02-23 12:15:03 +0000392
Jeremy Johnson015c3552022-02-23 12:15:03 +0000393 try:
394 if not args.override_exclusions:
395 for excl in test_desc["framework_exclusions"]:
396 if excl == framework:
397 print_color(LogColors.GREEN, "Results SKIPPED")
Eric Kunze3403ded2023-07-28 17:23:48 +0000398 return (TestResult.SKIPPED, 0.0, "", test_name)
Jeremy Johnson015c3552022-02-23 12:15:03 +0000399 except KeyError:
400 pass
401
Eric Kunze3403ded2023-07-28 17:23:48 +0000402 tf_tools_dir = Path(
403 f"{args.tf_base_dir}/bazel-bin/tensorflow/compiler/mlir"
404 ).resolve()
Jeremy Johnson015c3552022-02-23 12:15:03 +0000405
Eric Kunze3403ded2023-07-28 17:23:48 +0000406 pre_opt_filename = str(test_path / f"test_{framework}.preopt.mlir")
407 post_opt_filename = str(test_path / f"test_{framework}.postopt.mlir")
Jeremy Johnson015c3552022-02-23 12:15:03 +0000408 if args.test_dir:
409 test_path_prepend = args.test_dir
410 else:
Eric Kunze3403ded2023-07-28 17:23:48 +0000411 test_path_prepend = test_path
Jeremy Johnson015c3552022-02-23 12:15:03 +0000412
413 # 1. Framework to MLIR translator command
414 if framework == "tf":
415 if test_desc["tf_model_filename"].endswith(".mlir"):
416 pre_opt_filename = test_desc["tf_model_filename"]
417 translate_mlir_cmd = []
418 else:
419 translate_mlir_cmd = [
Eric Kunze3403ded2023-07-28 17:23:48 +0000420 str(tf_tools_dir / "tf-mlir-translate"),
Jeremy Johnson015c3552022-02-23 12:15:03 +0000421 "--graphdef-to-mlir",
422 "--tf-enable-shape-inference-on-import",
Eric Kunze3403ded2023-07-28 17:23:48 +0000423 f"--tf-output-arrays={test_desc['tf_result_name']}",
424 str(test_path_prepend / test_desc["tf_model_filename"]),
Jeremy Johnson015c3552022-02-23 12:15:03 +0000425 "-o",
426 pre_opt_filename,
427 ]
428 elif framework == "tflite":
429 if test_desc["tflite_model_filename"].endswith(".mlir"):
430 pre_opt_filename = test_desc["tflite_model_filename"]
431 translate_mlir_cmd = []
432 else:
433 translate_mlir_cmd = [
Eric Kunze3403ded2023-07-28 17:23:48 +0000434 str(tf_tools_dir / "lite" / "flatbuffer_translate"),
Jeremy Johnson015c3552022-02-23 12:15:03 +0000435 "--tflite-flatbuffer-to-mlir",
Eric Kunze3403ded2023-07-28 17:23:48 +0000436 str(test_path_prepend / test_desc["tflite_model_filename"]),
437 f"--output-arrays={test_desc['tflite_result_name']}",
Jeremy Johnson015c3552022-02-23 12:15:03 +0000438 "-o",
439 pre_opt_filename,
440 ]
441 else:
Eric Kunze3403ded2023-07-28 17:23:48 +0000442 raise Exception(f"Unknown framwork: {framework}")
Jeremy Johnson015c3552022-02-23 12:15:03 +0000443
444 # Any additional inputs to the translator?
445 input_tensor_prefix = "TosaInput_"
Eric Kunze3403ded2023-07-28 17:23:48 +0000446 flatbuffer_dir = f"flatbuffer-{framework}"
Jeremy Johnson015c3552022-02-23 12:15:03 +0000447 mlir_opts = []
448
449 # Temporary hack: MLIR's new hex encoding of large tensors does not work for
450 # boolean types
451 # for TF hash 8e8041d594a888eb67eafa5cc62627d7e9ca8082
Eric Kunze3403ded2023-07-28 17:23:48 +0000452 if str(test_path).endswith("_bool") and args.hex_bool_hack:
Jeremy Johnson015c3552022-02-23 12:15:03 +0000453 mlir_opts.append("--mlir-print-elementsattrs-with-hex-if-larger=-1")
454
455 try:
456 # specify input tensors if test is generated from .pb
457 if framework == "tf":
458 # Convert the shape to a mlir-friendly string
459 shapes = []
460 for curr_shape in test_desc["ifm_shape"]:
461 shape_str = ""
462 for dim in curr_shape:
463 shape_str = shape_str + str(dim) + ","
464 shapes.append(shape_str)
465
466 translate_mlir_cmd.extend(
467 ["--tf-input-arrays", ",".join(test_desc["ifm_name"])]
468 )
469 translate_mlir_cmd.extend(["--tf-input-shapes", ":".join(shapes)])
470
471 # Write the hard-coded placeholder input (reshaped as necesary) to
472 # the file that compiler specified.
473 reference_runner_ifm_name = []
474 for i in range(len(test_desc["ifm_file"])):
Eric Kunze3403ded2023-07-28 17:23:48 +0000475 ifm_tensor_name = f"{input_tensor_prefix}{i}"
Jeremy Johnson015c3552022-02-23 12:15:03 +0000476
477 assert test_desc["ifm_file"][i].endswith(".npy")
Eric Kunze3403ded2023-07-28 17:23:48 +0000478 ifm_np = np.load(test_path / test_desc["ifm_file"][i])
Jared Smolensb7af4612022-03-21 19:41:52 -0700479
480 # We sometimes encounter input shape/expected input shape mismatches
481 # due to a missing batch dimension on the input (e.g. a single 3D image).
482 #
483 # Make sure input numpy and input shape from descriptor match,
484 # expand_dims on the outer dimensions until the rank matches,
485 # then do the shape comparison.
486 while len(list(ifm_np.shape)) < len(test_desc["ifm_shape"][i]):
487 ifm_np = np.expand_dims(ifm_np, axis=0)
488
Luke Hutton714aa602023-02-08 19:45:26 +0000489 # After legalization, complex tensors are expected to be represented
490 # as a single floating point tensor of shape [?, ..., ?, 2].
491 expected_shape = test_desc["ifm_shape"][i]
Eric Kunze3403ded2023-07-28 17:23:48 +0000492 if str(test_path).endswith("c64"):
Luke Hutton714aa602023-02-08 19:45:26 +0000493 expected_shape.append(2)
494
495 assert list(ifm_np.shape) == expected_shape
Jeremy Johnson015c3552022-02-23 12:15:03 +0000496
497 reference_runner_ifm_name.append(ifm_tensor_name)
498
499 except KeyError:
500 # No additional inputs. Ignore.
501 pass
502
503 tf_opt_cmd = [
Eric Kunze3403ded2023-07-28 17:23:48 +0000504 str(tf_tools_dir / "tf-opt"),
Jeremy Johnson015c3552022-02-23 12:15:03 +0000505 "--tf-executor-to-functional-conversion",
506 "--verify-each",
507 pre_opt_filename,
508 "-o",
509 post_opt_filename,
510 ]
511
512 translate_mlir_cmd.extend(mlir_opts)
513 tf_opt_cmd.extend(mlir_opts)
514
Eric Kunze3403ded2023-07-28 17:23:48 +0000515 compiler_cmd = [str(tf_tools_dir / "tf-opt")]
Jeremy Johnson015c3552022-02-23 12:15:03 +0000516
517 if framework == "tf":
518 compiler_cmd.append("--tf-to-tosa-pipeline")
519 elif framework == "tflite":
520 compiler_cmd.append("--tfl-to-tosa-pipeline")
521 compiler_cmd.append("--tosa-strip-quant-types")
522
Eric Kunze3403ded2023-07-28 17:23:48 +0000523 tosa_mlir_filename = str(test_path / f"output_{framework}.tosa.mlir")
Jeremy Johnson015c3552022-02-23 12:15:03 +0000524
Eric Kunze3403ded2023-07-28 17:23:48 +0000525 flatbuffer_dir_fullpath = test_path / flatbuffer_dir
Jeremy Johnson015c3552022-02-23 12:15:03 +0000526
Eric Kunze3403ded2023-07-28 17:23:48 +0000527 flatbuffer_dir_fullpath.mkdir(exist_ok=True)
Jeremy Johnson015c3552022-02-23 12:15:03 +0000528
TatWai Chong6a46b252024-01-12 13:13:22 -0800529 compile_and_serialize_cmd = compiler_cmd.copy()
530 compile_and_serialize_cmd.extend(
Jeremy Johnson015c3552022-02-23 12:15:03 +0000531 [
532 "--verify-each",
533 post_opt_filename,
534 "-o",
535 tosa_mlir_filename,
536 "--tosa-serialize",
Eric Kunze3403ded2023-07-28 17:23:48 +0000537 f"--tosa-flatbuffer-filename={flatbuffer_dir_fullpath / f'{test_name}.tosa'}",
Jeremy Johnson015c3552022-02-23 12:15:03 +0000538 ]
539 )
540
541 if not args.no_compiler:
542 try:
543 if translate_mlir_cmd:
544 run_sh_command(translate_mlir_cmd, args.verbose, True)
545 if tf_opt_cmd:
546 run_sh_command(tf_opt_cmd, args.verbose, True)
547 except Exception as e:
Eric Kunze3403ded2023-07-28 17:23:48 +0000548 print_color(LogColors.RED, f"Results INVALID_MLIR {test_name}: {e}")
549 return (TestResult.INVALID_MLIR, 0.0, e, test_name)
Jeremy Johnson015c3552022-02-23 12:15:03 +0000550
TatWai Chong6a46b252024-01-12 13:13:22 -0800551 if "ifm_dynamic" in test_desc and test_desc["ifm_dynamic"] == 1:
552 compile_dynamic_model(
553 args,
554 framework,
555 test_path,
556 test_name,
557 pre_opt_filename,
558 post_opt_filename,
559 tosa_mlir_filename,
560 compiler_cmd,
561 flatbuffer_dir_fullpath,
562 ifm_np.shape,
Jeremy Johnson015c3552022-02-23 12:15:03 +0000563 )
TatWai Chong6a46b252024-01-12 13:13:22 -0800564 else:
565 try:
566 compiler_stdout, compiler_stderr = run_sh_command(
567 compile_and_serialize_cmd, args.verbose, True
Jeremy Johnson015c3552022-02-23 12:15:03 +0000568 )
TatWai Chong6a46b252024-01-12 13:13:22 -0800569 compiler_rc = parse_compiler_output(compiler_stdout, compiler_stderr)
570 if compiler_rc == TestResult.NOT_LOWERED:
571 print_color(
572 LogColors.RED,
573 f"Results NOT_LOWERED {test_name}, framework {framework}",
574 )
575 return (TestResult.NOT_LOWERED, 0.0, "", test_name)
Jeremy Johnson015c3552022-02-23 12:15:03 +0000576
TatWai Chong6a46b252024-01-12 13:13:22 -0800577 pass
Jeremy Johnson015c3552022-02-23 12:15:03 +0000578
TatWai Chong6a46b252024-01-12 13:13:22 -0800579 except Exception as e:
580 if "same scale constraint" in str(e):
581 print_color(LogColors.RED, f"Results INVALID_MLIR {test_name}: {e}")
582 return (TestResult.INVALID_MLIR, 0.0, e, test_name)
583 else:
584 print_color(
585 LogColors.RED, f"Results COMPILER_ERROR {test_name}: {e}"
586 )
587 return (TestResult.COMPILER_ERROR, 0.0, e, test_name)
Jeremy Johnson015c3552022-02-23 12:15:03 +0000588
589 if framework == "tf":
590 try:
Eric Kunze3403ded2023-07-28 17:23:48 +0000591 tf_result = np.load(test_path / test_desc["tf_result_npy_filename"])
Jeremy Johnson015c3552022-02-23 12:15:03 +0000592 except KeyError:
593 assert 0, "fail to load tf result numpy"
594 elif framework == "tflite":
595 try:
Eric Kunze3403ded2023-07-28 17:23:48 +0000596 tf_result = np.load(test_path / test_desc["tflite_result_npy_filename"])
Jeremy Johnson015c3552022-02-23 12:15:03 +0000597 except KeyError:
598 assert 0, "fail to load tflite result numpy"
599
Luke Hutton261b7b62023-01-10 14:50:31 +0000600 # TOSA has no notion of complex datatypes, it represents complex values using two
601 # fp32 output tensors representing real and imaginary values. When legalizing
602 # complex operations from frameworks, these two output tensors are combined into
603 # a single tensor of shape [?, ..., ?, 2] whereby each inner pair of values
604 # represents the real and imaginary parts of a complex value. This is completed
605 # by inserting reshape and concatenate TOSA operations during the legalization to
606 # maintain a one-to-one correspondance with framework outputs, thus simplifying
607 # legalization. Here tf_result should also match this format before being
608 # compared to the ref model output.
609 if tf_result.dtype == np.complex64:
610 ifm_shape = tf_result.shape + (2,)
611 tf_result = tf_result.view(np.float32)
612 tf_result = tf_result.reshape(ifm_shape)
613
Jeremy Johnson015c3552022-02-23 12:15:03 +0000614 # Generate test descriptor per flatbuffer generation
615 # Input .npy will be shared across different frameworks
616 # Output .npy will be generated in its corresponding flatbuffer
617 reference_runner_ifm_file = [
Eric Kunze3403ded2023-07-28 17:23:48 +0000618 str(Path("..") / ifm_file) for ifm_file in test_desc["ifm_file"]
Jeremy Johnson015c3552022-02-23 12:15:03 +0000619 ]
620
621 # Check if there's any operator in output graph.
622 empty_graph = True
623 with open(tosa_mlir_filename, "r") as f:
624 for line in f:
TatWai Chonge0247482023-06-01 15:02:24 -0700625 # TOSA assembly instructions all start with `tosa.`
626 if re.search(r"tosa\.", line):
Jeremy Johnson015c3552022-02-23 12:15:03 +0000627 empty_graph = False
628
629 break
630
631 # Fast-forward input tensor to output tensor if TOSA graph is empty.
632 if empty_graph:
633 reference_runner_ofm_name = reference_runner_ifm_name
634 else:
635 reference_runner_ofm_name = ["TosaOutput_0"]
636
Jerry Ged5b15122024-03-26 20:51:48 +0000637 if "num_variables" in test_desc:
638 num_variable = test_desc["num_variables"]
639 else:
640 num_variable = 0
641 reference_runner_variable_name = []
642 reference_runner_variable_file = []
643
644 for i in range(num_variable):
645 variable_name_str = "Variable_" + str(i)
646 variable_file_str = "variable_output_" + str(i) + ".npy"
647 reference_runner_variable_name.append(variable_name_str)
648 reference_runner_variable_file.append(variable_file_str)
649
Jeremy Johnson015c3552022-02-23 12:15:03 +0000650 write_reference_runner_json(
Eric Kunze3403ded2023-07-28 17:23:48 +0000651 filename=str(test_path / flatbuffer_dir / "desc.json"),
652 tosa_filename=f"{test_name}.tosa",
Jeremy Johnson015c3552022-02-23 12:15:03 +0000653 ifm_name=reference_runner_ifm_name,
654 ifm_file=reference_runner_ifm_file,
655 ofm_name=reference_runner_ofm_name,
656 ofm_file=["ref_model_output_0.npy"],
Jerry Ged5b15122024-03-26 20:51:48 +0000657 variable_name=reference_runner_variable_name,
658 variable_file=reference_runner_variable_file,
Jeremy Johnson015c3552022-02-23 12:15:03 +0000659 )
660
661 ref_model_cmd = [
Eric Kunze3403ded2023-07-28 17:23:48 +0000662 str(args.tools_base_dir / "build" / "reference_model" / "tosa_reference_model"),
663 f"--test_desc={test_path / flatbuffer_dir / 'desc.json'}",
Jeremy Johnson015c3552022-02-23 12:15:03 +0000664 ]
665
666 if args.debug_ref_model:
Eric Kunze286f8342022-06-22 11:30:23 -0700667 ref_model_cmd.extend(["-D ALL", "-l high"])
Jeremy Johnson015c3552022-02-23 12:15:03 +0000668
Tai Lya4d748b2023-03-28 22:06:56 +0000669 if args.precise_mode:
670 ref_model_cmd.extend(["--precise_mode=1"])
671
Jeremy Johnson015c3552022-02-23 12:15:03 +0000672 if args.valgrind:
673 ref_model_cmd = [
674 "valgrind",
675 "--show-leak-kinds=all",
676 "--log-fd=1",
677 "-q",
678 ] + ref_model_cmd
679
Eric Kunze3403ded2023-07-28 17:23:48 +0000680 ref_model_cmd = ref_model_cmd + [f"--tosa_level={args.tosa_level}"]
Jerry Gea793f462023-04-11 00:05:02 +0000681
Jeremy Johnson015c3552022-02-23 12:15:03 +0000682 # Clean out any ref_model result first
Eric Kunze3403ded2023-07-28 17:23:48 +0000683 for f in (test_path / flatbuffer_dir).glob("ref_model_*.npy"):
684 f.unlink()
Jeremy Johnson015c3552022-02-23 12:15:03 +0000685
Jared Smolensb7af4612022-03-21 19:41:52 -0700686 if args.no_ref:
687 return (TestResult.PASS, 0.0, msg)
688
689 try:
690 ref_model_stdout, ref_model_stderr = run_sh_command(
691 ref_model_cmd, args.verbose, True
692 )
693 ref_model_rc = parse_reference_model_output(ref_model_stdout, ref_model_stderr)
694 if ref_model_rc != TestResult.PASS:
695 return (ref_model_rc, 0.0, "")
696 except Exception as e:
697 ref_model_rc = parse_reference_model_output("", str(e))
698 if ref_model_rc != TestResult.PASS:
Jeremy Johnson015c3552022-02-23 12:15:03 +0000699 print_color(
700 LogColors.RED,
Eric Kunze3403ded2023-07-28 17:23:48 +0000701 f"Results {TestResultErrorStr[ref_model_rc]} {test_name}: {e}",
Jeremy Johnson015c3552022-02-23 12:15:03 +0000702 )
Jared Smolensb7af4612022-03-21 19:41:52 -0700703 return (ref_model_rc, 0.0, "")
Eric Kunze3403ded2023-07-28 17:23:48 +0000704 print_color(LogColors.RED, f"Results REF_MODEL_RUNTIME_ERROR {test_name}: {e}")
705 return (TestResult.REF_MODEL_RUNTIME_ERROR, 0.0, e, test_name)
Jeremy Johnson015c3552022-02-23 12:15:03 +0000706
Tai Lya4d748b2023-03-28 22:06:56 +0000707 if args.precise_mode == 1 and (
708 tf_result.dtype == np.float16 or tf_result.dtype == np.float32
709 ):
710 tf_result = tf_result.astype(np.float64)
711 elif tf_result.dtype == np.float16:
Jeremy Johnson015c3552022-02-23 12:15:03 +0000712 tf_result = tf_result.astype(np.float32)
Jerry Gec5291692024-01-02 22:29:08 +0000713 elif tf_result.dtype == np.int8:
714 tf_result = tf_result.astype(np.int8)
715 elif tf_result.dtype == np.uint8:
716 tf_result = tf_result.astype(np.uint8)
Jerry Ge20ab3df2024-01-26 16:56:55 +0000717 elif tf_result.dtype == np.int16:
718 tf_result = tf_result.astype(np.int16)
719 elif tf_result.dtype == np.uint16:
720 tf_result = tf_result.astype(np.uint16)
721 elif tf_result.dtype == np.int64:
Jeremy Johnson015c3552022-02-23 12:15:03 +0000722 tf_result = tf_result.astype(np.int32)
723
Eric Kunze3403ded2023-07-28 17:23:48 +0000724 # For now, search for the first output from ref_model
725 ref_model_result_files = list((test_path / flatbuffer_dir).glob("ref_model_*.npy"))
Jeremy Johnson015c3552022-02-23 12:15:03 +0000726 ref_model_result = np.load(ref_model_result_files[0])
727
728 assert (
729 tf_result.dtype == ref_model_result.dtype
Eric Kunze3403ded2023-07-28 17:23:48 +0000730 ), f"Numpy type mismatch {tf_result.dtype} != {ref_model_result.dtype} when comparing result"
Jeremy Johnson015c3552022-02-23 12:15:03 +0000731
732 # Size comparison
733 # Size = 1 tensors can be equivalently represented as having rank 0 or rank
734 # >= 0, allow that special case
735 tf_result = np.squeeze(tf_result)
736 ref_model_result = np.squeeze(ref_model_result)
737
738 if np.shape(tf_result) != np.shape(ref_model_result):
Eric Kunze3403ded2023-07-28 17:23:48 +0000739 print_color(LogColors.RED, f"Results MISCOMPARE {test_name}")
740 msg = f"Shapes mismatch: Reference {np.shape(tf_result)} vs {np.shape(ref_model_result)}"
Jeremy Johnson015c3552022-02-23 12:15:03 +0000741 print(msg)
Eric Kunze3403ded2023-07-28 17:23:48 +0000742 return (TestResult.MISMATCH, 0.0, msg, test_name)
Jeremy Johnson015c3552022-02-23 12:15:03 +0000743
744 # for quantized test, allow +-(args.quantize_tolerance) error
745 if ref_model_result.dtype == np.int32:
746 assert tf_result.dtype == np.int32
747
748 if np.all(np.absolute(ref_model_result - tf_result) <= args.quantize_tolerance):
Eric Kunze3403ded2023-07-28 17:23:48 +0000749 print_color(LogColors.GREEN, f"Results PASS {test_name}")
Jeremy Johnson015c3552022-02-23 12:15:03 +0000750 else:
Eric Kunze3403ded2023-07-28 17:23:48 +0000751 print_color(LogColors.RED, f"Results MISCOMPARE {test_name}")
Jeremy Johnson015c3552022-02-23 12:15:03 +0000752
753 tolerance = args.quantize_tolerance + 1
754 while not np.all(
755 np.absolute(ref_model_result - tf_result) <= args.quantize_tolerance
756 ):
757 tolerance = tolerance + 1
758 if tolerance >= 10:
759 break
760
Eric Kunze3403ded2023-07-28 17:23:48 +0000761 msg = f"Result is within {tolerance} {test_path}"
Jeremy Johnson015c3552022-02-23 12:15:03 +0000762 print(msg)
763
764 np.set_printoptions(threshold=128)
Eric Kunze3403ded2023-07-28 17:23:48 +0000765 print(f"tf_result: {tf_result.shape}\n")
Jeremy Johnson015c3552022-02-23 12:15:03 +0000766 print(tf_result)
Eric Kunze3403ded2023-07-28 17:23:48 +0000767 print(f"ref_model_result: {ref_model_result.shape}\n")
Jeremy Johnson015c3552022-02-23 12:15:03 +0000768 print(ref_model_result)
769 # print(tf_result - ref_model_result)
Eric Kunze3403ded2023-07-28 17:23:48 +0000770 return (TestResult.MISMATCH, tolerance, msg, test_name)
Jeremy Johnson015c3552022-02-23 12:15:03 +0000771 else:
772 if np.allclose(
773 ref_model_result, tf_result, atol=args.tolerance, equal_nan=True
774 ):
Eric Kunze3403ded2023-07-28 17:23:48 +0000775 print_color(LogColors.GREEN, f"Results PASS {test_name}")
Jeremy Johnson015c3552022-02-23 12:15:03 +0000776 else:
Eric Kunze3403ded2023-07-28 17:23:48 +0000777 print_color(LogColors.RED, f"Results MISCOMPARE {test_name}")
Jeremy Johnson015c3552022-02-23 12:15:03 +0000778
779 # Many of these tests would match with a reasonable looser tolerence.
780 # Determine what would have worked.
781 tolerance = args.tolerance * 10.0
782 while not np.allclose(
783 ref_model_result, tf_result, atol=tolerance, equal_nan=True
784 ):
785 tolerance = tolerance * 10.0
786 if tolerance > 1.0e10:
787 tolerance = math.inf
788 break
789
Eric Kunze3403ded2023-07-28 17:23:48 +0000790 msg = f"Result is within {tolerance:.0e} {test_name}"
Jeremy Johnson015c3552022-02-23 12:15:03 +0000791 print(msg)
792
793 np.set_printoptions(precision=4, threshold=128)
Eric Kunze3403ded2023-07-28 17:23:48 +0000794 print(f"tf_result: {tf_result.shape}\n")
Jeremy Johnson015c3552022-02-23 12:15:03 +0000795 print(tf_result)
Eric Kunze3403ded2023-07-28 17:23:48 +0000796 print(f"ref_model_result: {ref_model_result.shape}\n")
Jeremy Johnson015c3552022-02-23 12:15:03 +0000797 print(ref_model_result)
798 # print(tf_result - ref_model_result)
Eric Kunze3403ded2023-07-28 17:23:48 +0000799 return (TestResult.MISMATCH, tolerance, msg, test_name)
Jeremy Johnson015c3552022-02-23 12:15:03 +0000800
Eric Kunze3403ded2023-07-28 17:23:48 +0000801 return (TestResult.PASS, args.tolerance, msg, test_name)
Jeremy Johnson015c3552022-02-23 12:15:03 +0000802
803
804def worker_thread(task_queue, args, result_queue):
805 while True:
806 try:
807 (test, framework) = task_queue.get(block=False)
808 except queue.Empty:
809 break
810
811 if test is None:
812 break
813
814 msg = ""
815 start_time = datetime.now()
816 try:
Eric Kunze3403ded2023-07-28 17:23:48 +0000817 (rc, tolerance, msg, test_name) = run_test(args, test, framework)
Jeremy Johnson015c3552022-02-23 12:15:03 +0000818 except Exception as e:
Eric Kunze3403ded2023-07-28 17:23:48 +0000819 print(f"Internal regression error: {e}")
Jeremy Johnson015c3552022-02-23 12:15:03 +0000820 print(
821 "".join(
822 traceback.format_exception(
823 etype=type(e), value=e, tb=e.__traceback__
824 )
825 )
826 )
827 rc = TestResult.INTERNAL_ERROR
828 tolerance = 0.0
829
830 end_time = datetime.now()
831
Eric Kunze3403ded2023-07-28 17:23:48 +0000832 result_queue.put(
833 (test, framework, rc, tolerance, msg, end_time - start_time, test_name)
834 )
Jeremy Johnson015c3552022-02-23 12:15:03 +0000835 task_queue.task_done()
836
837 return True
838
839
840def getTestsInDir(directory):
841 # Recursively find any tests in this directory
Eric Kunze3403ded2023-07-28 17:23:48 +0000842 if (directory / "test.json").is_file():
Jeremy Johnson015c3552022-02-23 12:15:03 +0000843 return [directory]
Eric Kunze3403ded2023-07-28 17:23:48 +0000844 elif directory.is_dir():
Jeremy Johnson015c3552022-02-23 12:15:03 +0000845 test_list = []
Eric Kunze3403ded2023-07-28 17:23:48 +0000846 for d in directory.glob("*"):
Jeremy Johnson015c3552022-02-23 12:15:03 +0000847 test_list.extend(getTestsInDir(d))
848 return test_list
849 else:
850 return []
851
852
853def main():
854 args = parse_args()
855
856 set_print_in_color(not args.no_color)
857
858 if args.output_file:
859 set_print_in_color(False)
860 sys.stdout = open(args.output_file, "w")
861
862 # Disable TF info messages
863 os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3"
864
865 task_queue = queue.Queue()
866 result_queue = queue.Queue()
867
868 threads = []
869
870 # Result counters for each of the TestResult return codes
871 results = [0] * len(TestResult)
872
873 for tdir in args.test:
Jeremy Johnson015c3552022-02-23 12:15:03 +0000874 if args.recursive_tests:
875 tdirList = getTestsInDir(tdir)
876 else:
877 tdirList = [tdir]
878
879 for t in tdirList:
880 for f in args.framework:
881 task_queue.put((t, f))
882
883 for i in range(args.jobs):
884 t = threading.Thread(
885 target=worker_thread, args=(task_queue, args, result_queue)
886 )
887 t.setDaemon(True)
888 t.start()
889 threads.append(t)
890
891 # Run until queue is empty
892 task_queue.join()
893
894 print_color(LogColors.BOLD_WHITE, "Result summary")
895
896 result_list = []
897 while True:
898 try:
Eric Kunze3403ded2023-07-28 17:23:48 +0000899 test, framework, rc, tol, msg, time_delta, test_name = result_queue.get(
900 block=False
901 )
Jeremy Johnson015c3552022-02-23 12:15:03 +0000902 except queue.Empty:
903 break
904
Eric Kunze3403ded2023-07-28 17:23:48 +0000905 result_list.append((test, framework, rc, tol, msg, time_delta, test_name))
Jeremy Johnson015c3552022-02-23 12:15:03 +0000906 results[rc] = results[rc] + 1
907
908 xunit_result = xunit_results()
909 xunit_suite = xunit_result.create_suite(args.xunit_classname_prefix)
910
911 # Sort by test name
Eric Kunze3403ded2023-07-28 17:23:48 +0000912 for test, framework, rc, tol, err_msg, time_delta, test_name in sorted(
Jeremy Johnson015c3552022-02-23 12:15:03 +0000913 result_list, key=lambda tup: tup[0]
914 ):
Jeremy Johnson015c3552022-02-23 12:15:03 +0000915 class_name = f"{args.xunit_classname_prefix}.{framework}"
916
917 xt = xunit_test(test_name, class_name)
918
919 msg = TestResultErrorStr[rc]
920
921 xt.time = str(
922 float(time_delta.seconds) + (float(time_delta.microseconds) * 1e-6)
923 )
924
925 if len(msg) > 0:
Eric Kunze3403ded2023-07-28 17:23:48 +0000926 print(f"{msg} on {framework} {test}")
Jeremy Johnson015c3552022-02-23 12:15:03 +0000927
928 # Add any more verbose messaging for the xml log
929 if err_msg:
Eric Kunze3403ded2023-07-28 17:23:48 +0000930 msg = f"{msg} {err_msg}"
Jeremy Johnson015c3552022-02-23 12:15:03 +0000931
932 if rc == TestResult.PASS:
933 pass
934 elif rc == TestResult.SKIPPED:
935 xt.skipped()
936 else:
937 xt.failed(msg)
938
939 xunit_suite.tests.append(xt)
940
941 result_queue.task_done()
942
943 xunit_result.write_results(args.xunit_file)
944
945 print("Totals: ", end="")
946 for result in TestResult:
Eric Kunze3403ded2023-07-28 17:23:48 +0000947 print(f"{results[result]} {result.name.lower()}, ", end="")
Jeremy Johnson015c3552022-02-23 12:15:03 +0000948 print()
949
950 if not args.regression_mode and (
951 results[TestResult.COMPILER_ERROR] > 0
952 or results[TestResult.REF_MODEL_ERROR] > 0
953 or results[TestResult.MISMATCH] > 0
954 ):
955 return 1
956
957 return 0
958
959
960if __name__ == "__main__":
961 exit(main())