Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
TatWai Chong | 6a46b25 | 2024-01-12 13:13:22 -0800 | [diff] [blame] | 2 | # Copyright (c) 2020-2024, ARM Limited. |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 3 | # SPDX-License-Identifier: Apache-2.0 |
| 4 | import argparse |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 5 | import json |
| 6 | import math |
| 7 | import os |
| 8 | import queue |
| 9 | import re |
| 10 | import sys |
| 11 | import threading |
| 12 | import traceback |
| 13 | from datetime import datetime |
| 14 | from enum import IntEnum |
| 15 | from enum import unique |
Eric Kunze | 97b0027 | 2023-07-20 10:52:56 -0700 | [diff] [blame] | 16 | from pathlib import Path |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 17 | |
| 18 | import numpy as np |
Jeremy Johnson | e2b5e87 | 2023-09-14 17:02:09 +0100 | [diff] [blame] | 19 | from checker.color_print import LogColors |
| 20 | from checker.color_print import print_color |
| 21 | from checker.color_print import set_print_in_color |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 22 | from runner.run_command import run_sh_command |
| 23 | from xunit.xunit import xunit_results |
| 24 | from xunit.xunit import xunit_test |
| 25 | |
| 26 | |
| 27 | def parse_args(): |
| 28 | parser = argparse.ArgumentParser() |
| 29 | parser.add_argument( |
Jared Smolens | b7af461 | 2022-03-21 19:41:52 -0700 | [diff] [blame] | 30 | "-t", |
| 31 | "--test", |
| 32 | dest="test", |
| 33 | default=[], |
Eric Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 34 | type=Path, |
Jared Smolens | b7af461 | 2022-03-21 19:41:52 -0700 | [diff] [blame] | 35 | nargs="+", |
| 36 | help="Test(s) to run", |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 37 | ) |
| 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 Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 55 | type=Path, |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 56 | required=True, |
| 57 | help="Reference model base directory", |
| 58 | ) |
| 59 | parser.add_argument( |
Tai Ly | a4d748b | 2023-03-28 22:06:56 +0000 | [diff] [blame] | 60 | "-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 Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 67 | "-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 Ge | a793f46 | 2023-04-11 00:05:02 +0000 | [diff] [blame] | 84 | "--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 Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 92 | "--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 Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 177 | type=Path, |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 178 | 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 |
| 199 | class 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 | |
| 212 | TestResultErrorStr = [ |
| 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 | |
| 226 | def 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 | |
| 237 | def 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 |
| 255 | def write_reference_runner_json( |
| 256 | filename, |
| 257 | tosa_filename, |
| 258 | ifm_name, |
| 259 | ifm_file, |
| 260 | ofm_name, |
| 261 | ofm_file, |
Jerry Ge | d5b1512 | 2024-03-26 20:51:48 +0000 | [diff] [blame] | 262 | variable_name, |
| 263 | variable_file, |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 264 | 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 Ge | d5b1512 | 2024-03-26 20:51:48 +0000 | [diff] [blame] | 275 | test_desc["variable_name"] = variable_name |
| 276 | test_desc["variable_file"] = variable_file |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 277 | test_desc["expected_failure"] = expected_failure |
| 278 | |
| 279 | with open(filename, "w") as f: |
| 280 | json.dump(test_desc, f, indent=" ") |
| 281 | |
| 282 | |
TatWai Chong | 6a46b25 | 2024-01-12 13:13:22 -0800 | [diff] [blame] | 283 | """ For dynamic shape model, apply 2 steps to perform compilation, shape inference, |
| 284 | and serialization.""" |
| 285 | |
| 286 | |
| 287 | def 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 Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 374 | def run_test(args, test_path, framework): |
Eric Kunze | 97b0027 | 2023-07-20 10:52:56 -0700 | [diff] [blame] | 375 | 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 Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 383 | test_name = None |
Eric Kunze | 97b0027 | 2023-07-20 10:52:56 -0700 | [diff] [blame] | 384 | if "name" in test_desc: |
| 385 | test_name = test_desc["name"] |
| 386 | else: |
| 387 | test_name = test_path.name |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 388 | if not test_name: |
Eric Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 389 | raise Exception(f"Could not parse test_name from {test_path}") |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 390 | |
Eric Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 391 | print_color(LogColors.GREEN, f"## Running {framework} test {test_name}") |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 392 | |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 393 | 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 Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 398 | return (TestResult.SKIPPED, 0.0, "", test_name) |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 399 | except KeyError: |
| 400 | pass |
| 401 | |
Eric Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 402 | tf_tools_dir = Path( |
| 403 | f"{args.tf_base_dir}/bazel-bin/tensorflow/compiler/mlir" |
| 404 | ).resolve() |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 405 | |
Eric Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 406 | pre_opt_filename = str(test_path / f"test_{framework}.preopt.mlir") |
| 407 | post_opt_filename = str(test_path / f"test_{framework}.postopt.mlir") |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 408 | if args.test_dir: |
| 409 | test_path_prepend = args.test_dir |
| 410 | else: |
Eric Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 411 | test_path_prepend = test_path |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 412 | |
| 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 Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 420 | str(tf_tools_dir / "tf-mlir-translate"), |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 421 | "--graphdef-to-mlir", |
| 422 | "--tf-enable-shape-inference-on-import", |
Eric Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 423 | f"--tf-output-arrays={test_desc['tf_result_name']}", |
| 424 | str(test_path_prepend / test_desc["tf_model_filename"]), |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 425 | "-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 Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 434 | str(tf_tools_dir / "lite" / "flatbuffer_translate"), |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 435 | "--tflite-flatbuffer-to-mlir", |
Eric Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 436 | str(test_path_prepend / test_desc["tflite_model_filename"]), |
| 437 | f"--output-arrays={test_desc['tflite_result_name']}", |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 438 | "-o", |
| 439 | pre_opt_filename, |
| 440 | ] |
| 441 | else: |
Eric Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 442 | raise Exception(f"Unknown framwork: {framework}") |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 443 | |
| 444 | # Any additional inputs to the translator? |
| 445 | input_tensor_prefix = "TosaInput_" |
Eric Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 446 | flatbuffer_dir = f"flatbuffer-{framework}" |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 447 | 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 Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 452 | if str(test_path).endswith("_bool") and args.hex_bool_hack: |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 453 | 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 Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 475 | ifm_tensor_name = f"{input_tensor_prefix}{i}" |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 476 | |
| 477 | assert test_desc["ifm_file"][i].endswith(".npy") |
Eric Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 478 | ifm_np = np.load(test_path / test_desc["ifm_file"][i]) |
Jared Smolens | b7af461 | 2022-03-21 19:41:52 -0700 | [diff] [blame] | 479 | |
| 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 Hutton | 714aa60 | 2023-02-08 19:45:26 +0000 | [diff] [blame] | 489 | # 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 Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 492 | if str(test_path).endswith("c64"): |
Luke Hutton | 714aa60 | 2023-02-08 19:45:26 +0000 | [diff] [blame] | 493 | expected_shape.append(2) |
| 494 | |
| 495 | assert list(ifm_np.shape) == expected_shape |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 496 | |
| 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 Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 504 | str(tf_tools_dir / "tf-opt"), |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 505 | "--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 Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 515 | compiler_cmd = [str(tf_tools_dir / "tf-opt")] |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 516 | |
| 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 Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 523 | tosa_mlir_filename = str(test_path / f"output_{framework}.tosa.mlir") |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 524 | |
Eric Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 525 | flatbuffer_dir_fullpath = test_path / flatbuffer_dir |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 526 | |
Eric Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 527 | flatbuffer_dir_fullpath.mkdir(exist_ok=True) |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 528 | |
TatWai Chong | 6a46b25 | 2024-01-12 13:13:22 -0800 | [diff] [blame] | 529 | compile_and_serialize_cmd = compiler_cmd.copy() |
| 530 | compile_and_serialize_cmd.extend( |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 531 | [ |
| 532 | "--verify-each", |
| 533 | post_opt_filename, |
| 534 | "-o", |
| 535 | tosa_mlir_filename, |
| 536 | "--tosa-serialize", |
Eric Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 537 | f"--tosa-flatbuffer-filename={flatbuffer_dir_fullpath / f'{test_name}.tosa'}", |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 538 | ] |
| 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 Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 548 | print_color(LogColors.RED, f"Results INVALID_MLIR {test_name}: {e}") |
| 549 | return (TestResult.INVALID_MLIR, 0.0, e, test_name) |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 550 | |
TatWai Chong | 6a46b25 | 2024-01-12 13:13:22 -0800 | [diff] [blame] | 551 | 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 Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 563 | ) |
TatWai Chong | 6a46b25 | 2024-01-12 13:13:22 -0800 | [diff] [blame] | 564 | else: |
| 565 | try: |
| 566 | compiler_stdout, compiler_stderr = run_sh_command( |
| 567 | compile_and_serialize_cmd, args.verbose, True |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 568 | ) |
TatWai Chong | 6a46b25 | 2024-01-12 13:13:22 -0800 | [diff] [blame] | 569 | 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 Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 576 | |
TatWai Chong | 6a46b25 | 2024-01-12 13:13:22 -0800 | [diff] [blame] | 577 | pass |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 578 | |
TatWai Chong | 6a46b25 | 2024-01-12 13:13:22 -0800 | [diff] [blame] | 579 | 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 Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 588 | |
| 589 | if framework == "tf": |
| 590 | try: |
Eric Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 591 | tf_result = np.load(test_path / test_desc["tf_result_npy_filename"]) |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 592 | except KeyError: |
| 593 | assert 0, "fail to load tf result numpy" |
| 594 | elif framework == "tflite": |
| 595 | try: |
Eric Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 596 | tf_result = np.load(test_path / test_desc["tflite_result_npy_filename"]) |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 597 | except KeyError: |
| 598 | assert 0, "fail to load tflite result numpy" |
| 599 | |
Luke Hutton | 261b7b6 | 2023-01-10 14:50:31 +0000 | [diff] [blame] | 600 | # 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 Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 614 | # 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 Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 618 | str(Path("..") / ifm_file) for ifm_file in test_desc["ifm_file"] |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 619 | ] |
| 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 Chong | e024748 | 2023-06-01 15:02:24 -0700 | [diff] [blame] | 625 | # TOSA assembly instructions all start with `tosa.` |
| 626 | if re.search(r"tosa\.", line): |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 627 | 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 Ge | d5b1512 | 2024-03-26 20:51:48 +0000 | [diff] [blame] | 637 | 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 Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 650 | write_reference_runner_json( |
Eric Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 651 | filename=str(test_path / flatbuffer_dir / "desc.json"), |
| 652 | tosa_filename=f"{test_name}.tosa", |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 653 | 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 Ge | d5b1512 | 2024-03-26 20:51:48 +0000 | [diff] [blame] | 657 | variable_name=reference_runner_variable_name, |
| 658 | variable_file=reference_runner_variable_file, |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 659 | ) |
| 660 | |
| 661 | ref_model_cmd = [ |
Eric Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 662 | str(args.tools_base_dir / "build" / "reference_model" / "tosa_reference_model"), |
| 663 | f"--test_desc={test_path / flatbuffer_dir / 'desc.json'}", |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 664 | ] |
| 665 | |
| 666 | if args.debug_ref_model: |
Eric Kunze | 286f834 | 2022-06-22 11:30:23 -0700 | [diff] [blame] | 667 | ref_model_cmd.extend(["-D ALL", "-l high"]) |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 668 | |
Tai Ly | a4d748b | 2023-03-28 22:06:56 +0000 | [diff] [blame] | 669 | if args.precise_mode: |
| 670 | ref_model_cmd.extend(["--precise_mode=1"]) |
| 671 | |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 672 | 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 Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 680 | ref_model_cmd = ref_model_cmd + [f"--tosa_level={args.tosa_level}"] |
Jerry Ge | a793f46 | 2023-04-11 00:05:02 +0000 | [diff] [blame] | 681 | |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 682 | # Clean out any ref_model result first |
Eric Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 683 | for f in (test_path / flatbuffer_dir).glob("ref_model_*.npy"): |
| 684 | f.unlink() |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 685 | |
Jared Smolens | b7af461 | 2022-03-21 19:41:52 -0700 | [diff] [blame] | 686 | 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 Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 699 | print_color( |
| 700 | LogColors.RED, |
Eric Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 701 | f"Results {TestResultErrorStr[ref_model_rc]} {test_name}: {e}", |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 702 | ) |
Jared Smolens | b7af461 | 2022-03-21 19:41:52 -0700 | [diff] [blame] | 703 | return (ref_model_rc, 0.0, "") |
Eric Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 704 | 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 Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 706 | |
Tai Ly | a4d748b | 2023-03-28 22:06:56 +0000 | [diff] [blame] | 707 | 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 Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 712 | tf_result = tf_result.astype(np.float32) |
Jerry Ge | c529169 | 2024-01-02 22:29:08 +0000 | [diff] [blame] | 713 | 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 Ge | 20ab3df | 2024-01-26 16:56:55 +0000 | [diff] [blame] | 717 | 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 Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 722 | tf_result = tf_result.astype(np.int32) |
| 723 | |
Eric Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 724 | # 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 Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 726 | ref_model_result = np.load(ref_model_result_files[0]) |
| 727 | |
| 728 | assert ( |
| 729 | tf_result.dtype == ref_model_result.dtype |
Eric Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 730 | ), f"Numpy type mismatch {tf_result.dtype} != {ref_model_result.dtype} when comparing result" |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 731 | |
| 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 Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 739 | 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 Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 741 | print(msg) |
Eric Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 742 | return (TestResult.MISMATCH, 0.0, msg, test_name) |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 743 | |
| 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 Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 749 | print_color(LogColors.GREEN, f"Results PASS {test_name}") |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 750 | else: |
Eric Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 751 | print_color(LogColors.RED, f"Results MISCOMPARE {test_name}") |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 752 | |
| 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 Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 761 | msg = f"Result is within {tolerance} {test_path}" |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 762 | print(msg) |
| 763 | |
| 764 | np.set_printoptions(threshold=128) |
Eric Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 765 | print(f"tf_result: {tf_result.shape}\n") |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 766 | print(tf_result) |
Eric Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 767 | print(f"ref_model_result: {ref_model_result.shape}\n") |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 768 | print(ref_model_result) |
| 769 | # print(tf_result - ref_model_result) |
Eric Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 770 | return (TestResult.MISMATCH, tolerance, msg, test_name) |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 771 | else: |
| 772 | if np.allclose( |
| 773 | ref_model_result, tf_result, atol=args.tolerance, equal_nan=True |
| 774 | ): |
Eric Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 775 | print_color(LogColors.GREEN, f"Results PASS {test_name}") |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 776 | else: |
Eric Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 777 | print_color(LogColors.RED, f"Results MISCOMPARE {test_name}") |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 778 | |
| 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 Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 790 | msg = f"Result is within {tolerance:.0e} {test_name}" |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 791 | print(msg) |
| 792 | |
| 793 | np.set_printoptions(precision=4, threshold=128) |
Eric Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 794 | print(f"tf_result: {tf_result.shape}\n") |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 795 | print(tf_result) |
Eric Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 796 | print(f"ref_model_result: {ref_model_result.shape}\n") |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 797 | print(ref_model_result) |
| 798 | # print(tf_result - ref_model_result) |
Eric Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 799 | return (TestResult.MISMATCH, tolerance, msg, test_name) |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 800 | |
Eric Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 801 | return (TestResult.PASS, args.tolerance, msg, test_name) |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 802 | |
| 803 | |
| 804 | def 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 Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 817 | (rc, tolerance, msg, test_name) = run_test(args, test, framework) |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 818 | except Exception as e: |
Eric Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 819 | print(f"Internal regression error: {e}") |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 820 | 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 Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 832 | result_queue.put( |
| 833 | (test, framework, rc, tolerance, msg, end_time - start_time, test_name) |
| 834 | ) |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 835 | task_queue.task_done() |
| 836 | |
| 837 | return True |
| 838 | |
| 839 | |
| 840 | def getTestsInDir(directory): |
| 841 | # Recursively find any tests in this directory |
Eric Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 842 | if (directory / "test.json").is_file(): |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 843 | return [directory] |
Eric Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 844 | elif directory.is_dir(): |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 845 | test_list = [] |
Eric Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 846 | for d in directory.glob("*"): |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 847 | test_list.extend(getTestsInDir(d)) |
| 848 | return test_list |
| 849 | else: |
| 850 | return [] |
| 851 | |
| 852 | |
| 853 | def 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 Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 874 | 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 Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 899 | test, framework, rc, tol, msg, time_delta, test_name = result_queue.get( |
| 900 | block=False |
| 901 | ) |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 902 | except queue.Empty: |
| 903 | break |
| 904 | |
Eric Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 905 | result_list.append((test, framework, rc, tol, msg, time_delta, test_name)) |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 906 | 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 Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 912 | for test, framework, rc, tol, err_msg, time_delta, test_name in sorted( |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 913 | result_list, key=lambda tup: tup[0] |
| 914 | ): |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 915 | 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 Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 926 | print(f"{msg} on {framework} {test}") |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 927 | |
| 928 | # Add any more verbose messaging for the xml log |
| 929 | if err_msg: |
Eric Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 930 | msg = f"{msg} {err_msg}" |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 931 | |
| 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 Kunze | 3403ded | 2023-07-28 17:23:48 +0000 | [diff] [blame] | 947 | print(f"{results[result]} {result.name.lower()}, ", end="") |
Jeremy Johnson | 015c355 | 2022-02-23 12:15:03 +0000 | [diff] [blame] | 948 | 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 | |
| 960 | if __name__ == "__main__": |
| 961 | exit(main()) |