| #!/usr/bin/env python3 |
| # Copyright (c) 2020-2023, ARM Limited. |
| # SPDX-License-Identifier: Apache-2.0 |
| import argparse |
| import os |
| import re |
| import traceback |
| |
| import numpy as np |
| |
| # Level | Level for Humans | Level Description |
| # -------|------------------|------------------------------------ |
| # 0 | DEBUG | [Default] Print all messages |
| # 1 | INFO | Filter out INFO messages |
| # 2 | WARNING | Filter out INFO & WARNING messages |
| # 3 | ERROR | Filter out all messages |
| # Filter tensorflow debug message except errors |
| os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2" |
| |
| # Flake8 E402 - ignore imports not at top of file to allow os.environ setting |
| import tensorflow as tf # noqa: E402 |
| from frameworks.write_test_json import write_test_json # noqa: E402 |
| from frameworks.arg_gen import ArgGen # noqa: E402 |
| from frameworks.tensor_gen import TGen # noqa: E402 |
| from frameworks.test_builder import TBuilder # noqa: E402 |
| from frameworks.test_gen_utils import ( # noqa: E402 |
| QuantType, |
| get_tf_dtype, |
| get_shape_str, |
| ) # noqa: E402 |
| |
| from tensorflow.lite.python.interpreter import OpResolverType # noqa: E402 |
| |
| # All of the supported frameworks |
| ALL_FRAMEWORKS = ["tf", "tflite"] |
| |
| # Lists of different data types |
| TYPE_F = [tf.float32] |
| TYPE_I = [tf.int32] |
| TYPE_FI = [tf.float32, tf.int32] |
| TYPE_B = [tf.bool] |
| TYPE_FIB = [tf.float32, tf.int32, tf.bool] |
| TYPE_H = [tf.float16] |
| TYPE_FH = [tf.float32, tf.float16] |
| TYPE_FHI = [tf.float32, tf.float16, tf.int32] |
| TYPE_FHIB = [tf.float32, tf.float16, tf.int32, tf.bool] |
| |
| # The list of operator tests |
| # Each dictionary entry for an op is a dictionary with the following required members: |
| # 'operands': tuple (number_of_placeholder_tensors, number_of_constant_tensors) |
| # 'build_fcn: tuple (Test builder function, Tensor generator function, |
| # Argument generator function) |
| # 'types': list of Tensorflow types that should be tested for this op |
| # OR |
| # a dictionary of {'framework_name': [type_list] } for cases where only |
| # a subset of the types should be tested in each framework. This can also |
| # be used to restrict an operator to a particular framework. |
| # |
| # And optional members: |
| # 'template': boolean (indicates that this is a templated op which gets further |
| # processing in createDynamicOpLists) |
| # 'bias': boolean indicating that there is a bias component to be generated |
| # 'qtypes': List of QuantType quantized types to generate for this op |
| # 'rank': tuple (lowest rank, highest rank). Dimension range of input tensor. |
| # 'custom_shapes': List of custom shapes for specific operators |
| |
| TF_OP_LIST = { |
| "add": { |
| "operands": (2, 0), |
| "build_fcn": (TBuilder.Add, TGen.tgBFuzz, ArgGen.agNone), |
| "types": { |
| "tf": TYPE_FI, |
| "tflite": list( |
| TYPE_FI + [QuantType.ALL_U8, QuantType.ALL_I8, QuantType.ALL_I16] |
| ), |
| }, |
| }, |
| "sub": { |
| "operands": (2, 0), |
| "build_fcn": (TBuilder.Sub, TGen.tgBFuzz, ArgGen.agNone), |
| "types": { |
| "tf": TYPE_FI, |
| "tflite": list(TYPE_FI + [QuantType.ALL_U8, QuantType.ALL_I8]), |
| # QuantType.ALL_I16 fail in TFLite conversion |
| }, |
| }, |
| "mul": { |
| "operands": (2, 0), |
| "build_fcn": (TBuilder.Mul, TGen.tgBFuzz, ArgGen.agNone), |
| "types": { |
| "tf": TYPE_FI, |
| "tflite": list( |
| TYPE_FI + [QuantType.ALL_U8, QuantType.ALL_I8, QuantType.ALL_I16] |
| ), |
| }, |
| }, |
| "exp": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.Exp, TGen.tgBasic, ArgGen.agNone), |
| "types": TYPE_F, |
| }, |
| "rcp": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.Rcp, TGen.tgBasic, ArgGen.agNone), |
| "types": TYPE_F, |
| }, |
| "relu": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.Relu, TGen.tgBasic, ArgGen.agNone), |
| "types": { |
| "tf": TYPE_F, |
| "tflite": list( |
| TYPE_F + [QuantType.ALL_U8, QuantType.ALL_I8, QuantType.ALL_I16] |
| ), |
| }, |
| }, |
| "relu1": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.Relu1, TGen.tgBasic, ArgGen.agNone), |
| "types": { |
| "tf": [], |
| "tflite": list(TYPE_F + [QuantType.ALL_U8, QuantType.ALL_I8]), |
| }, |
| }, |
| "relu0To1": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.Relu0To1, TGen.tgBasic, ArgGen.agNone), |
| "types": { |
| "tf": [], |
| "tflite": list(TYPE_F + [QuantType.ALL_U8, QuantType.ALL_I8]), |
| }, |
| }, |
| "relu6": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.Relu6, TGen.tgBasic, ArgGen.agNone), |
| "types": { |
| "tf": TYPE_F, |
| "tflite": list( |
| TYPE_F + [QuantType.ALL_U8, QuantType.ALL_I8, QuantType.ALL_I16] |
| ), |
| }, |
| }, |
| "leaky_relu": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.LeakyRelu, TGen.tgBasic, ArgGen.agFloat), |
| "types": { |
| "tf": TYPE_F, |
| "tflite": list( |
| TYPE_F + [QuantType.ALL_U8, QuantType.ALL_I8, QuantType.ALL_I16] |
| ), |
| }, |
| }, |
| "prelu": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.Prelu, TGen.tgBasic, ArgGen.agNone), |
| "types": { |
| "tflite": list(TYPE_F + [QuantType.ALL_U8, QuantType.ALL_I8]), |
| }, |
| }, |
| "gelu": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.Gelu, TGen.tgBasic, ArgGen.agNone), |
| "types": { |
| # Need compiler support for tf.Erf. |
| # "tf": TYPE_F, |
| "tflite": list( |
| # Only float32, int8 and uint8 supported currently |
| TYPE_F |
| + [QuantType.ALL_U8, QuantType.ALL_I8] |
| ), |
| }, |
| }, |
| "concat": { |
| "operands": (2, 0), |
| "build_fcn": (TBuilder.Concat, TGen.tgBasic, ArgGen.agAxes), |
| "types": TYPE_FI, |
| "rank": (0, 4), |
| "custom_shapes": { |
| "custom_shape_only": False, |
| "shape_list": [()], |
| }, |
| }, |
| "bitwise_and": { |
| "operands": (2, 0), |
| "build_fcn": (TBuilder.BitwiseAnd, TGen.tgBFuzz, ArgGen.agNone), |
| "types": {"tf": TYPE_I}, # Not supported in TF Lite |
| }, |
| "bitwise_or": { |
| "operands": (2, 0), |
| "build_fcn": (TBuilder.BitwiseOr, TGen.tgBFuzz, ArgGen.agNone), |
| "types": {"tf": TYPE_I}, # Not supported in TF Lite |
| }, |
| "bitwise_not": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.BitwiseNot, TGen.tgBFuzz, ArgGen.agNone), |
| "types": {"tf": TYPE_I}, # Not supported in TF Lite |
| }, |
| "bitwise_xor": { |
| "operands": (2, 0), |
| "build_fcn": (TBuilder.BitwiseXor, TGen.tgBFuzz, ArgGen.agNone), |
| "types": {"tf": TYPE_I}, # Not supported in TF Lite |
| }, |
| "logical_and": { |
| "operands": (2, 0), |
| "build_fcn": (TBuilder.LogicalAnd, TGen.tgBFuzz, ArgGen.agNone), |
| "types": TYPE_B, |
| }, |
| "logical_or": { |
| "operands": (2, 0), |
| "build_fcn": (TBuilder.LogicalOr, TGen.tgBFuzz, ArgGen.agNone), |
| "types": TYPE_B, |
| }, |
| "logical_not": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.LogicalNot, TGen.tgBFuzz, ArgGen.agNone), |
| "types": TYPE_B, |
| }, |
| "reduce_any": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.ReduceAny, TGen.tgBasic, ArgGen.agAxesListKeepdims), |
| "types": TYPE_B, |
| }, |
| "reduce_all": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.ReduceAll, TGen.tgBasic, ArgGen.agAxesListKeepdims), |
| "types": TYPE_B, |
| }, |
| "reduce_min": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.ReduceMin, TGen.tgBasic, ArgGen.agAxesListKeepdims), |
| "types": { |
| "tf": TYPE_FI, |
| "tflite": list(TYPE_FI + [QuantType.ALL_U8, QuantType.ALL_I8]), |
| }, |
| }, |
| "reduce_max": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.ReduceMax, TGen.tgBasic, ArgGen.agAxesListKeepdims), |
| "types": { |
| "tf": TYPE_FI, |
| "tflite": list(TYPE_FI + [QuantType.ALL_U8, QuantType.ALL_I8]), |
| }, |
| }, |
| "reduce_sum": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.ReduceSum, TGen.tgBasic, ArgGen.agAxesListKeepdims), |
| "types": { |
| "tf": TYPE_F, |
| # v2 converter doesn't recognize quantized reduce_sum |
| # "tflite": list(TYPE_F + [QuantType.ALL_U8, QuantType.ALL_I8]), |
| "tflite": TYPE_F, |
| }, |
| }, |
| "reduce_mean": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.ReduceMean, TGen.tgBasic, ArgGen.agAxesListKeepdims), |
| "types": { |
| "tf": TYPE_F, |
| "tflite": list( |
| TYPE_F + [QuantType.ALL_U8, QuantType.ALL_I8, QuantType.ALL_I16] |
| ), |
| }, |
| }, |
| "reduce_product": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.ReduceProduct, TGen.tgBasic, ArgGen.agAxesListKeepdims), |
| "types": TYPE_F, |
| }, |
| "min": { |
| "operands": (2, 0), |
| "build_fcn": (TBuilder.Min, TGen.tgBFuzz, ArgGen.agNone), |
| "types": TYPE_FI, |
| }, |
| "max": { |
| "operands": (2, 0), |
| "build_fcn": (TBuilder.Max, TGen.tgBFuzz, ArgGen.agNone), |
| "types": TYPE_FI, |
| }, |
| "pow": { |
| "operands": (2, 0), |
| "build_fcn": (TBuilder.Pow, TGen.tgBFuzz, ArgGen.agNone), |
| # Technically, integer is supported, but only for positive exponents. |
| # Needs a random argument generator. |
| "types": TYPE_F, |
| }, |
| "abs": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.Abs, TGen.tgBasic, ArgGen.agNone), |
| "types": TYPE_F, |
| }, |
| "ceil": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.Ceil, TGen.tgBasic, ArgGen.agNone), |
| "types": TYPE_F, |
| }, |
| "floor": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.Floor, TGen.tgBasic, ArgGen.agNone), |
| "types": TYPE_F, |
| }, |
| "log": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.Log, TGen.tgBasic, ArgGen.agNone), |
| "types": TYPE_F, |
| }, |
| "negate": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.Negate, TGen.tgBasic, ArgGen.agNone), |
| "types": TYPE_F, |
| }, |
| "rsqrt": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.Rsqrt, TGen.tgBasicPositive, ArgGen.agNone), |
| "types": { |
| "tf": TYPE_F, |
| "tflite": list(TYPE_F + [QuantType.ALL_I8]), |
| }, |
| }, |
| "sign": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.Sign, TGen.tgBasic, ArgGen.agNone), |
| "types": { |
| "tf": TYPE_F, |
| }, |
| }, |
| "sigmoid": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.Sigmoid, TGen.tgBasic, ArgGen.agNone), |
| "types": { |
| "tf": TYPE_F, |
| "tflite": list( |
| TYPE_F + [QuantType.ALL_U8, QuantType.ALL_I8, QuantType.ALL_I16] |
| ), |
| }, |
| }, |
| "tanh": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.Tanh, TGen.tgBasic, ArgGen.agNone), |
| "types": { |
| "tf": TYPE_F, |
| "tflite": list( |
| TYPE_F + [QuantType.ALL_U8, QuantType.ALL_I8, QuantType.ALL_I16] |
| ), |
| }, |
| }, |
| "erf": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.Erf, TGen.tgBasic, ArgGen.agNone), |
| "types": { |
| "tf": TYPE_F, |
| }, |
| }, |
| "sin": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.Sin, TGen.tgBasic, ArgGen.agNone), |
| "types": TYPE_F, |
| }, |
| "cos": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.Cos, TGen.tgBasic, ArgGen.agNone), |
| "types": TYPE_F, |
| }, |
| "atan2": { |
| "operands": (2, 0), |
| "build_fcn": (TBuilder.Atan2, TGen.tgBasic, ArgGen.agNone), |
| "types": { |
| "tflite": TYPE_F, |
| }, |
| }, |
| "square": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.Square, TGen.tgBasic, ArgGen.agNone), |
| "types": TYPE_F, |
| }, |
| "squared_difference": { |
| "operands": (2, 0), |
| "build_fcn": (TBuilder.SquaredDifference, TGen.tgBFuzz, ArgGen.agNone), |
| "types": { |
| "tf": TYPE_F, |
| "tflite": list(TYPE_FI + [QuantType.ALL_I8]), |
| }, |
| }, |
| "equal": { |
| "operands": (2, 0), |
| "build_fcn": (TBuilder.Equal, TGen.tgBFuzz, ArgGen.agNone), |
| "types": TYPE_FI, |
| }, |
| "greater_equal": { |
| "operands": (2, 0), |
| "build_fcn": (TBuilder.GreaterEqual, TGen.tgBFuzz, ArgGen.agNone), |
| "types": TYPE_FI, |
| }, |
| "greater": { |
| "operands": (2, 0), |
| "build_fcn": (TBuilder.Greater, TGen.tgBFuzz, ArgGen.agNone), |
| "types": TYPE_FI, |
| }, |
| "less": { |
| "operands": (2, 0), |
| "build_fcn": (TBuilder.Less, TGen.tgBFuzz, ArgGen.agNone), |
| "types": TYPE_FI, |
| }, |
| "less_equal": { |
| "operands": (2, 0), |
| "build_fcn": (TBuilder.LessEqual, TGen.tgBFuzz, ArgGen.agNone), |
| "types": TYPE_FI, |
| }, |
| "conv2d_TEMPLATE": { |
| "operands": (1, 1), |
| "build_fcn": (TBuilder.Conv2d, TGen.tgConv2d, ArgGen.agConv2d), |
| "types": { |
| "tf": [tf.float32], |
| "tflite": [ |
| tf.float32, |
| QuantType.CONV_U8_U8, |
| QuantType.CONV_I8_I8, |
| QuantType.CONV_I16_I8, |
| ], |
| }, |
| "template": True, |
| }, |
| "conv2d_relu_TEMPLATE": { |
| "operands": (1, 2), |
| "build_fcn": (TBuilder.Conv2dRelu, TGen.tgConv2d, ArgGen.agNone), |
| "types": { |
| "tf": [tf.float32], |
| "tflite": [ |
| tf.float32, |
| QuantType.CONV_U8_U8, |
| QuantType.CONV_I8_I8, |
| QuantType.CONV_I16_I8, |
| ], |
| }, |
| "template": True, |
| }, |
| "conv2d_relu6_TEMPLATE": { |
| "operands": (1, 2), |
| "build_fcn": (TBuilder.Conv2dRelu6, TGen.tgConv2d, ArgGen.agNone), |
| "types": { |
| "tf": [tf.float32], |
| "tflite": [ |
| tf.float32, |
| QuantType.CONV_U8_U8, |
| QuantType.CONV_I8_I8, |
| QuantType.CONV_I16_I8, |
| ], |
| }, |
| "template": True, |
| }, |
| "conv2d_relu_n1_to_1_TEMPLATE": { |
| "operands": (1, 2), |
| "build_fcn": (TBuilder.Conv2dReluN1To1, TGen.tgConv2d, ArgGen.agNone), |
| "types": { |
| "tf": [tf.float32], |
| "tflite": [ |
| tf.float32, |
| QuantType.CONV_U8_U8, |
| QuantType.CONV_I8_I8, |
| QuantType.CONV_I16_I8, |
| ], |
| }, |
| "template": True, |
| }, |
| # This test is converted as: |
| # tfl.conv2d(){fused_activation_function="NONE"} + tfl.tanh() |
| # TODO: anyway to generate tfl.conv2d(){fused_activation_function="TANH"}? |
| "conv2d_tanh_TEMPLATE": { |
| "operands": (1, 2), |
| "build_fcn": (TBuilder.Conv2dTanh, TGen.tgConv2d, ArgGen.agNone), |
| "types": { |
| "tf": [tf.float32], |
| "tflite": [ |
| tf.float32, |
| QuantType.CONV_U8_U8, |
| QuantType.CONV_I8_I8, |
| QuantType.CONV_I16_I8, |
| ], |
| }, |
| "template": True, |
| }, |
| "conv2d_bias_TEMPLATE": { |
| "operands": (1, 2), |
| "build_fcn": (TBuilder.Conv2dWithBias, TGen.tgConv2d, ArgGen.agConv2d), |
| "types": { |
| "tf": [tf.float32], |
| "tflite": [ |
| tf.float32, |
| QuantType.CONV_U8_U8, |
| QuantType.CONV_I8_I8, |
| QuantType.CONV_I16_I8, |
| ], |
| }, |
| "bias": True, |
| "template": True, |
| }, |
| "conv3d_TEMPLATE": { |
| "operands": (1, 1), |
| "build_fcn": (TBuilder.Conv3d, TGen.tgConv3d, ArgGen.agConv3d), |
| "types": { |
| "tf": [tf.float32], |
| "tflite": [ |
| tf.float32, |
| QuantType.CONV_U8_U8, |
| QuantType.CONV_I8_I8, |
| # Quantization to 16x8-bit not yet supported by tflite. |
| ], |
| }, |
| "template": True, |
| "rank": (1, 5), |
| }, |
| "conv3d_bias_TEMPLATE": { |
| "operands": (1, 2), |
| "build_fcn": (TBuilder.Conv3dWithBias, TGen.tgConv3d, ArgGen.agConv3d), |
| "types": { |
| "tf": [tf.float32], |
| "tflite": [ |
| tf.float32, |
| QuantType.CONV_U8_U8, |
| QuantType.CONV_I8_I8, |
| # Quantization to 16x8-bit not yet supported by tflite. |
| ], |
| }, |
| "bias": True, |
| "template": True, |
| "rank": (1, 5), |
| }, |
| "depthwise_conv2d_TEMPLATE": { |
| "operands": (1, 1), |
| "build_fcn": ( |
| TBuilder.DepthwiseConv2d, |
| TGen.tgDepthwiseConv2d, |
| ArgGen.agDepthwiseConv2d, |
| ), |
| "types": { |
| "tf": [tf.float32], |
| "tflite": [ |
| tf.float32, |
| QuantType.CONV_U8_U8, |
| QuantType.CONV_I8_I8, |
| QuantType.CONV_I16_I8, |
| ], |
| }, |
| "template": True, |
| }, |
| "depthwise_conv2d_bias_TEMPLATE": { |
| "operands": (1, 2), |
| "build_fcn": ( |
| TBuilder.DepthwiseConv2dWithBias, |
| TGen.tgDepthwiseConv2d, |
| ArgGen.agDepthwiseConv2d, |
| ), |
| "types": { |
| "tf": [tf.float32], |
| "tflite": [ |
| tf.float32, |
| QuantType.CONV_U8_U8, |
| QuantType.CONV_I8_I8, |
| QuantType.CONV_I16_I8, |
| ], |
| }, |
| "bias": True, |
| "template": True, |
| }, |
| "transpose_conv2d_TEMPLATE": { |
| "operands": (1, 1), |
| "build_fcn": ( |
| TBuilder.TransposeConv2d, |
| TGen.tgTransposeConv2d, |
| ArgGen.agTransposeConv2d, |
| ), |
| "types": { |
| "tf": [tf.float32], |
| "tflite": [ |
| tf.float32, |
| QuantType.CONV_U8_U8, |
| QuantType.CONV_I8_I8, |
| QuantType.CONV_I16_I8, |
| ], |
| }, |
| "template": True, |
| }, |
| "argmax": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.Argmax, TGen.tgBasic, ArgGen.agAxes), |
| "types": {"tf": TYPE_F}, |
| }, |
| "avg_pool2d": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.AvgPool2d, TGen.tgPooling, ArgGen.agPooling), |
| "types": { |
| "tf": TYPE_F, |
| "tflite": list( |
| TYPE_F + [QuantType.ALL_U8, QuantType.ALL_I8, QuantType.ALL_I16] |
| ), |
| }, |
| }, |
| "max_pool2d": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.MaxPool2d, TGen.tgPooling, ArgGen.agPooling), |
| "types": { |
| "tf": TYPE_F, |
| "tflite": list(TYPE_F + [QuantType.ALL_U8, QuantType.ALL_I8]), |
| # ALL_I16 not supported yet |
| # In tensorflow/compiler/mlir/lite/ir/tfl_ops.td, |
| # QI16 is missing from MaxPoolOperandAndResultConstraints |
| # If adding QI16 back this test can run through. |
| }, |
| }, |
| "reshape": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.Reshape, TGen.tgBasic, ArgGen.agReshape), |
| "types": TYPE_FI, |
| }, |
| "transpose": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.Transpose, TGen.tgBasic, ArgGen.agTranspose), |
| "types": TYPE_FI, |
| }, |
| "slice": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.Slice, TGen.tgBasic, ArgGen.agSlice), |
| "types": TYPE_FI, |
| }, |
| "strided_slice": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.StridedSlice, TGen.tgBasic, ArgGen.agStridedSlice), |
| "types": TYPE_FI, |
| }, |
| "select": { |
| "operands": (3, 0), |
| "build_fcn": (TBuilder.Select, TGen.tgSelect, ArgGen.agNone), |
| "types": TYPE_FI, |
| }, |
| "addn": { |
| "operands": (4, 0), |
| "build_fcn": (TBuilder.Addn, TGen.tgBasic, ArgGen.agNone), |
| "types": TYPE_FI, |
| }, |
| "concatv2": { |
| "operands": (4, 0), |
| "build_fcn": (TBuilder.Concatv2, TGen.tgBasic, ArgGen.agAxes), |
| "types": TYPE_FI, |
| "rank": (0, 4), |
| "custom_shapes": { |
| "custom_shape_only": False, |
| "shape_list": [()], |
| }, |
| }, |
| "stack": { |
| "operands": (4, 0), |
| "build_fcn": (TBuilder.Stack, TGen.tgBasic, ArgGen.agStack), |
| "types": TYPE_FI, |
| }, |
| "unstack": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.Unstack, TGen.tgPooling, ArgGen.agAxes), |
| "types": TYPE_F, |
| }, |
| "mirrorpad": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.MirrorPad, TGen.tgBasic, ArgGen.agMirrorPad), |
| "types": TYPE_FI, |
| }, |
| "pad": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.Pad, TGen.tgBasic, ArgGen.agPad), |
| "types": { |
| "tf": TYPE_F, |
| "tflite": list(TYPE_F + [QuantType.ALL_U8, QuantType.ALL_I8]), |
| }, |
| }, |
| "expand_dims": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.ExpandDims, TGen.tgBasic, ArgGen.agStack), |
| "types": TYPE_FI, |
| }, |
| "shape": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.Shape, TGen.tgBasic, ArgGen.agNone), |
| "types": TYPE_FI, |
| }, |
| "rank": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.Rank, TGen.tgBasic, ArgGen.agNone), |
| "types": TYPE_FI, |
| }, |
| "fill": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.Fill, TGen.tgBasic, ArgGen.agFill), |
| "types": TYPE_FI, |
| }, |
| "elu": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.Elu, TGen.tgBasic, ArgGen.agNone), |
| "types": TYPE_F, |
| }, |
| "softmax": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.Softmax, TGen.tgBasic, ArgGen.agNone), |
| "types": { |
| "tf": TYPE_F, |
| "tflite": list( |
| TYPE_F + [QuantType.ALL_U8, QuantType.ALL_I8, QuantType.ALL_I16] |
| ), |
| }, |
| }, |
| "log_softmax": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.LogSoftmax, TGen.tgBasic, ArgGen.agNone), |
| "types": TYPE_F, |
| }, |
| "matmul": { |
| "operands": (2, 0), |
| "build_fcn": (TBuilder.MatMul, TGen.tgMatmul, ArgGen.agNone), |
| "types": { |
| "tf": TYPE_F, |
| "tflite": list( |
| TYPE_F |
| + [QuantType.ALL_U8, QuantType.ALL_I8] |
| # 16 bits matmul fail to convert |
| ), |
| }, |
| }, |
| "add_scalar": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.AddScalar, TGen.tgBasic, ArgGen.agNone), |
| "types": TYPE_F, |
| }, |
| "add_1d": { |
| "operands": (2, 0), |
| "build_fcn": (TBuilder.Add1d, TGen.tgBasic, ArgGen.agNone), |
| "types": TYPE_F, |
| }, |
| "split": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.Split, TGen.tgBasic, ArgGen.agSplit), |
| "types": TYPE_FI, |
| }, |
| "tile": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.Tile, TGen.tgBasic, ArgGen.agTile), |
| "types": TYPE_FI, |
| }, |
| "reverse": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.Reverse, TGen.tgBasic, ArgGen.agAxes), |
| "types": {"tf": TYPE_FI}, |
| }, |
| "gather": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.Gather, TGen.tgBasic, ArgGen.agGather), |
| "types": TYPE_FI, |
| }, |
| "gather_nd": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.GatherNd, TGen.tgBasic, ArgGen.agGatherND), |
| "types": TYPE_FI, |
| }, |
| "scatter_nd": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.ScatterNd, TGen.tgBasic, ArgGen.agScatterND), |
| "types": TYPE_FI, |
| }, |
| "space_to_batch": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.SpaceToBatch, TGen.tgBasic, ArgGen.agSpaceToBatch), |
| "types": TYPE_F, |
| }, |
| "batch_to_space": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.BatchToSpace, TGen.tgBasic, ArgGen.agBatchToSpace), |
| "types": TYPE_F, |
| }, |
| "space_to_depth": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.SpaceToDepth, TGen.tgBasic, ArgGen.agSpaceToDepth), |
| "types": TYPE_F, |
| }, |
| "depth_to_space": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.DepthToSpace, TGen.tgBasic, ArgGen.agDepthToSpace), |
| "types": TYPE_F, |
| }, |
| "one_hot": { |
| "operands": (3, 1), |
| "build_fcn": (TBuilder.OneHot, TGen.tgOneHot, ArgGen.agOneHot), |
| "types": TYPE_FI, |
| }, |
| "fakequant": { |
| "operands": (1, 0), |
| "build_fcn": ( |
| TBuilder.Fakequant, |
| TGen.tgBasic, |
| ArgGen.agFakequant, |
| ), |
| "types": {"tf": TYPE_F}, |
| }, |
| "resize": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.Resize, TGen.tgPooling, ArgGen.agResize), |
| "types": { |
| "tf": TYPE_F, |
| "tflite": list( |
| TYPE_F + [QuantType.ALL_U8, QuantType.ALL_I8, QuantType.ALL_I16] |
| ), |
| }, |
| "custom_shapes": { |
| "custom_shape_only": False, |
| "shape_list": [(3, 1, 1, 7)], |
| }, |
| }, |
| "left_shift": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.LeftShift, TGen.tgBasic, ArgGen.agShift), |
| "types": {"tf": [tf.int32]}, |
| }, |
| "right_shift": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.RightShift, TGen.tgBasic, ArgGen.agShift), |
| "types": { |
| "tf": [ |
| tf.int32, |
| ] |
| }, |
| }, |
| "while": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.While, TGen.tgBasic, ArgGen.agNone), |
| "types": { |
| "tflite": list(TYPE_F), |
| }, |
| }, |
| "lstm": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.LSTM, TGen.tgRecurrent, ArgGen.agNone), |
| "types": { |
| "tflite": [ |
| tf.float32, |
| # tf.int32 |
| ] |
| }, |
| }, |
| "lstm_stateful": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.SLSTM, TGen.tgRecurrent, ArgGen.agNone), |
| "types": { |
| "tflite": [ |
| tf.float32, |
| ] |
| }, |
| }, |
| "gru": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.GRU, TGen.tgRecurrent, ArgGen.agNone), |
| "types": { |
| "tflite": [ |
| tf.float32, |
| # tf.int32 |
| ] |
| }, |
| }, |
| "rnn": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.RNN, TGen.tgRecurrent, ArgGen.agNone), |
| "types": { |
| "tflite": [ |
| tf.float32, |
| ] |
| }, |
| }, |
| "callonce": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.CallOnce, TGen.tgBasic, ArgGen.agNone), |
| "types": { |
| "tflite": [tf.float32], |
| }, |
| "custom_shapes": { |
| "custom_shape_only": True, |
| "shape_list": [(1,)], |
| }, |
| }, |
| "rfft2d": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.RFFT2d, TGen.tgRFFT2d, ArgGen.agRFFT2d), |
| "types": { |
| "tflite": TYPE_F, |
| }, |
| }, |
| "real": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.Real, TGen.tgComplexComponents, ArgGen.agNone), |
| "types": { |
| "tflite": [tf.complex64], |
| }, |
| }, |
| "imag": { |
| "operands": (1, 0), |
| "build_fcn": (TBuilder.Imag, TGen.tgComplexComponents, ArgGen.agNone), |
| "types": { |
| "tflite": [tf.complex64], |
| }, |
| }, |
| "broadcastto": { |
| "operands": (1, 1), |
| "build_fcn": (TBuilder.BroadcastTo, TGen.tgBroadcastTo, ArgGen.agNone), |
| "types": { |
| "tf": TYPE_FIB, |
| }, |
| }, |
| } |
| |
| # Shapes to be tested; default can be overwritten |
| shape_list = [ |
| (1,), |
| (64,), |
| (14, 19), |
| (13, 21, 3), |
| (1, 8, 16), |
| (1, 4, 4, 4), |
| (1, 8, 4, 17), |
| (1, 4, 8, 19), |
| (1, 32, 32, 8), |
| (1, 7, 7, 9), |
| (3, 1, 1, 7), |
| (2, 2, 7, 7, 2), |
| (1, 4, 8, 21, 17), |
| (3, 32, 16, 16, 5), |
| ] |
| |
| |
| def gen_rand_shapes(args): |
| """Overwrite the global shape list with a new list of random shapes""" |
| global shape_list |
| |
| rng = np.random.default_rng(args.random_seed) |
| |
| # Don't let things get too big... cap the maximum volume, but let |
| # an individual dimension be 1..47 |
| max_total_volume = 32 * 32 * 4 |
| |
| shape_list = [] |
| # Only iterate over ranks 2, 3, 4, and 5 |
| for rank in range(2, 6): |
| for n in range(args.random_shapes): |
| new_shape = rng.integers(1, 48, size=rank) |
| |
| # Set the batch dimension on 4D or 5D objects to 1 |
| if rank == 4 or rank == 5: |
| new_shape[0] = 1 |
| |
| # Limit the total shape volume and throw out any |
| # shapes that wouldn't leave at least size=2 in some non-batch dimension |
| volume = 1 |
| skip_shape = False |
| for i in range(rank): |
| |
| volume *= new_shape[i] |
| |
| # Reduce the shape, while it's larger than the maximum volume |
| while volume > max_total_volume: |
| new_shape[i] = new_shape[i] // 2 |
| volume = volume // 2 |
| |
| # Now an untenable dimension size? Skip this one. |
| if new_shape[i] < 1: |
| skip_shape = True |
| |
| if not skip_shape: |
| shape_list.append(tuple(new_shape)) |
| |
| |
| # Construct, run and save a whole tensorflow tf.function to a protobuf file |
| # or convert to .tflite if it's quantized unit test |
| def run_unit_test( |
| op_name, |
| args, |
| test_dir, |
| curr_shape, |
| addl_args, |
| dtype, |
| excluded_framework_list, |
| quantized_inference_dtype, |
| result_name, |
| seed, |
| ): |
| |
| try: |
| op = TF_OP_LIST[op_name] |
| op_fcn, tensor_gen_fcn, arg_gen_fcn = op["build_fcn"] |
| |
| # Get and seed a random number generator for this test |
| rng = np.random.default_rng(seed) |
| |
| # return placeholders=(str: name, np.array: value) |
| # consts=(str: name, np.array: value) |
| placeholders, consts = ( |
| tensor_gen_fcn(op, curr_shape, dtype, rng, False) |
| if tensor_gen_fcn.__name__ == "tgBFuzz" |
| else tensor_gen_fcn(op, curr_shape, dtype, rng) |
| ) |
| |
| # if test doesn't have any placeholders/consts, terminated |
| if len(placeholders) == 0 and len(consts) == 0: |
| return True |
| |
| if not args.quiet: |
| print(" {} ".format(test_dir)) |
| |
| try: |
| os.mkdir(test_dir) |
| except FileExistsError: |
| pass |
| |
| const_nodes = [value for name, value in consts] |
| |
| num_placeholders = len(placeholders) |
| # if test is quantized, create tensor quantization metadata info for |
| # each input tensor, based on different quantized type |
| if quantized_inference_dtype: |
| is_quantized = True |
| # TODO: support INT8 IFM x INT4 weight later |
| if quantized_inference_dtype == QuantType.ALL_U8: |
| qzero = [128] * num_placeholders |
| numpy_dtype = [np.uint8] * num_placeholders |
| tflite_inference_dtype = tf.uint8 |
| elif quantized_inference_dtype == QuantType.ALL_I8: |
| qzero = [0] * num_placeholders |
| numpy_dtype = [np.int8] * num_placeholders |
| tflite_inference_dtype = tf.int8 |
| elif quantized_inference_dtype == QuantType.ALL_I16: |
| qzero = [0] * num_placeholders |
| numpy_dtype = [np.int16] * num_placeholders |
| tflite_inference_dtype = tf.int16 |
| elif quantized_inference_dtype == QuantType.CONV_U8_U8: |
| assert ( |
| num_placeholders == 1 |
| ), "Unsupported number of placeholders for Convolution: {}".format( |
| num_placeholders |
| ) |
| qzero = [128] * num_placeholders |
| if num_placeholders == 2: |
| numpy_dtype = [np.uint8, np.uint8] |
| else: |
| numpy_dtype = [np.uint8, np.uint8, np.int32] |
| tflite_inference_dtype = tf.uint8 |
| elif quantized_inference_dtype == QuantType.CONV_I8_I8: |
| assert ( |
| num_placeholders == 1 |
| ), "Unsupported number of placeholders for Convolution: {}".format( |
| num_placeholders |
| ) |
| qzero = [0] * num_placeholders |
| if num_placeholders == 2: |
| numpy_dtype = [np.int8, np.int8] |
| else: |
| numpy_dtype = [np.int8, np.int8, np.int32] |
| tflite_inference_dtype = tf.int8 |
| elif quantized_inference_dtype == QuantType.CONV_I16_I8: |
| assert ( |
| num_placeholders == 1 |
| ), "Unsupported number of placeholders for Convolution: {}".format( |
| num_placeholders |
| ) |
| if num_placeholders == 2: |
| qzero = [0, 0] |
| numpy_dtype = [np.int16, np.int8] |
| else: |
| qzero = [0, 0, 0] |
| numpy_dtype = [ |
| np.int16, |
| np.int8, |
| np.int64, |
| ] # np.int64 to represent 40 bits accumulator |
| tflite_inference_dtype = tf.int16 |
| else: |
| raise Exception( |
| "Unsupported fakequant dtype: {}".format(quantized_inference_dtype) |
| ) |
| |
| else: |
| is_quantized = False |
| |
| tf_model_filename = None |
| tf_result_npy_filename = None |
| tf_result_name = None |
| |
| tflite_model_filename = None |
| tflite_result_npy_filename = None |
| tflite_result_name = None |
| |
| placeholder_names = [] |
| placeholder_vals = [] |
| placeholder_signatures = () |
| placeholder_npy_filenames = [] |
| placeholder_shapes = [] |
| |
| for idx, (name, val) in enumerate(placeholders): |
| placeholder_names.append(name) |
| placeholder_signatures = placeholder_signatures + ( |
| tf.TensorSpec(shape=val.shape, dtype=val.dtype, name=name), |
| ) |
| placeholder_npy_filenames.append("{}.npy".format(name.split(":")[0])) |
| placeholder_shapes.append(val.shape) |
| |
| # Get test builder class |
| fcn_node = op_fcn(*const_nodes, *addl_args, result_name) |
| concrete_function = tf.function(input_signature=placeholder_signatures)( |
| fcn_node.eval |
| ).get_concrete_function() |
| |
| if is_quantized: |
| |
| assert dtype is tf.float32, "quantized test must come from float32 graph" |
| |
| # 1. Quantize float placeholder npy to quantized to feed the graph |
| for idx, (name, val) in enumerate(placeholders): |
| |
| # we use np.amin()/np.amax() to determine dynamic range |
| # for quantized test |
| zeropoint = 0 |
| scale = 1.0 |
| if numpy_dtype[idx] != np.int64: |
| qmin = np.iinfo(numpy_dtype[idx]).min |
| qmax = np.iinfo(numpy_dtype[idx]).max |
| num_bits = np.iinfo(numpy_dtype[idx]).bits |
| # 40 bit is represented as np.int64 |
| else: |
| num_bits = 40 |
| qmin = -(1 << num_bits) |
| qmax = (1 << num_bits) - 1 |
| |
| min_val = np.amin(val) |
| max_val = np.amax(val) |
| |
| # for single value tensor, we set scale equal to the abs(value), |
| # and fix zeropoint to 128 |
| # if val > 0, it'll be represented as 129, |
| # where val = (129 - 128) * val |
| # if val < 0, it'll be represented as 127, |
| # where val = (127 - 128) * (-val) |
| # if val == 0, it'll be represted as 128, with range [-128.0, 128.0] |
| # and let quantized 1 represent the value |
| # also adjust effective min/max consequently |
| if max_val == min_val: |
| if max_val != 0: |
| scale = abs(max_val) |
| else: |
| scale = 1.0 |
| min_val = float(qmin - qzero[idx]) * scale |
| max_val = float(qmax - qzero[idx]) * scale |
| else: |
| scale = (max_val - min_val) / float(qmax - qmin) |
| if op_name == "squared_difference": |
| zeropoint = -int(round((-min_val) / scale)) + qmin |
| else: |
| zeropoint = int(round((-min_val) / scale)) + qmin |
| |
| # run through tf.fakequant first to assure quantization error aligned |
| fakequant_val = tf.quantization.fake_quant_with_min_max_args( |
| val, |
| min=min_val, |
| max=max_val, |
| num_bits=num_bits, |
| name="gen_quant_npy", |
| ) |
| |
| quant_val = np.round(fakequant_val / scale) + zeropoint |
| |
| # very few unit tests after TF hash may/2020, this quantized |
| # value for some reason exceed [0, 255] range |
| saved_val = np.clip(quant_val, qmin, qmax).astype(numpy_dtype[idx]) |
| |
| np.save( |
| os.path.join(test_dir, placeholder_npy_filenames[idx]), |
| saved_val, |
| False, |
| ) |
| |
| placeholder_vals.append(tf.convert_to_tensor(saved_val)) |
| |
| # 2. Convert the model to quantized TFLite flatbuffer |
| module = tf.Module() |
| converter = tf.lite.TFLiteConverter.from_concrete_functions( |
| [concrete_function], module |
| ) |
| converter.optimizations = [tf.lite.Optimize.DEFAULT] |
| converter.experimental_new_converter = True |
| |
| # use MLIR-based post-quantizer |
| converter.experimental_new_quantizer = True |
| |
| flag = ( |
| tf.lite.OpsSet.EXPERIMENTAL_TFLITE_BUILTINS_ACTIVATIONS_INT16_WEIGHTS_INT8 # noqa: E501 |
| ) |
| if tflite_inference_dtype == tf.int16: |
| converter.target_spec.supported_ops = [flag] |
| |
| # Generator function for integer quantization of TFLiteConverter |
| # which generates a few hundred input samples with the same order, type, and shape as the inputs, |
| # to calibrate/estimate the range of the floating-point inputs. |
| # For broadcast fuzzing tests, fuzzing needs to be disabled, otherwise, it causes a mismatch of |
| # tensor shapes of inputs. |
| def input_stats(): |
| for i in range(0, args.num_samples): |
| placeholders, _ = ( |
| tensor_gen_fcn(op, placeholder_shapes[0], dtype, rng, True) |
| if tensor_gen_fcn == "tgBFuzz" |
| else tensor_gen_fcn(op, placeholder_shapes[0], dtype, rng) |
| ) |
| yield [s[1] for s in placeholders] |
| |
| converter.representative_dataset = input_stats |
| converter.inference_input_type = tflite_inference_dtype |
| converter.inference_output_type = tflite_inference_dtype |
| |
| tflite_model = converter.convert() |
| |
| tflite_model_filename = "model.tflite" |
| |
| # Write out converted model to disk |
| with open(os.path.join(test_dir, tflite_model_filename), "wb") as f: |
| f.write(tflite_model) |
| |
| else: # is_quantized is False |
| |
| # 1. Saved out numpy array directly |
| for idx, (name, val) in enumerate(placeholders): |
| placeholder_vals.append(tf.convert_to_tensor(val)) |
| |
| # Complex tensors are expected to be repsesented by a |
| # single floating point tensor of shape [?, ..., ?, 2]. |
| if val.dtype == np.complex64: |
| val_shape = val.shape + (2,) |
| val = val.view(np.float32) |
| val = val.reshape(val_shape) |
| |
| np.save( |
| os.path.join(test_dir, placeholder_npy_filenames[idx]), val, False |
| ) |
| |
| # 2.a Saved out .pb if framework includes tensorflow |
| if "tf" not in excluded_framework_list: |
| # Write out graph as protobuf to disk |
| tf_model_filename = "model.pb" |
| tf.io.write_graph( |
| concrete_function.graph, test_dir, tf_model_filename, True |
| ) |
| |
| # 2.b Saved out .tflite if framework includes tflite |
| if "tflite" not in excluded_framework_list: |
| # Convert the model to TFLite flatbuffer |
| module = tf.Module() |
| |
| if op_name == "callonce" or op_name == "lstm_stateful": |
| converter = tf.lite.TFLiteConverter.from_concrete_functions( |
| [concrete_function], fcn_node |
| ) |
| else: |
| converter = tf.lite.TFLiteConverter.from_concrete_functions( |
| [concrete_function], module |
| ) |
| |
| converter.experimental_new_converter = True |
| |
| # Even it's non-quantized int32 test, this needs to be set to tf.float32 |
| converter.inference_input_type = tf.float32 |
| converter.inference_output_type = tf.float32 |
| tflite_model = converter.convert() |
| |
| # Write out converted model to disk |
| tflite_model_filename = "model.tflite" |
| with open(os.path.join(test_dir, tflite_model_filename), "wb") as f: |
| f.write(tflite_model) |
| |
| # Get TF reference result if .pb is specified |
| if tf_model_filename: |
| tf_result_npy_filename = "tf_result.npy" |
| tf_result = concrete_function(*placeholder_vals) |
| np.save(os.path.join(test_dir, tf_result_npy_filename), tf_result, False) |
| |
| tf_result_name = result_name |
| |
| # Get TFLite inference result if .tflite is specified |
| if tflite_model_filename: |
| tflite_result_npy_filename = "tflite_result.npy" |
| |
| ops_with_optimized_only_kernel = ["elu", "ceil", "gather", "rfft2d"] |
| |
| if args.tflite_kernel_mode == "optimized" or ( |
| op_name in ops_with_optimized_only_kernel |
| ): |
| interpreter = tf.lite.Interpreter( |
| model_path=os.path.join(test_dir, tflite_model_filename) |
| ) |
| elif args.tflite_kernel_mode == "reference": |
| interpreter = tf.lite.Interpreter( |
| model_path=os.path.join(test_dir, tflite_model_filename), |
| experimental_op_resolver_type=OpResolverType.BUILTIN_REF, |
| ) |
| else: |
| assert 0, "unknown tflite interpreter mode {}".format( |
| args.tflite_kernel_mode |
| ) |
| interpreter.allocate_tensors() |
| |
| input_details = interpreter.get_input_details() |
| output_details = interpreter.get_output_details() |
| |
| assert len(input_details) == len( |
| placeholder_vals |
| ), "number of placeholder mismatch" |
| |
| for idx, val in enumerate(placeholder_vals): |
| interpreter.set_tensor(input_details[idx]["index"], val.numpy()) |
| |
| interpreter.invoke() |
| tflite_result = interpreter.get_tensor(output_details[0]["index"]) |
| |
| np.save( |
| os.path.join(test_dir, tflite_result_npy_filename), tflite_result, False |
| ) |
| |
| # Result tensor name would change after converting to TFLite flatbuffer |
| # Overwrite the information from TFLite models directly. |
| # Assume single result tensor now |
| tflite_result_name = output_details[0]["name"] |
| |
| _, test_name = os.path.split(test_dir) |
| |
| # Write out test descriptor |
| write_test_json( |
| filename=os.path.join(test_dir, "test.json"), |
| tf_model_filename=tf_model_filename, |
| tf_result_npy_filename=tf_result_npy_filename, |
| tf_result_name=tf_result_name, |
| tflite_model_filename=tflite_model_filename, |
| tflite_result_npy_filename=tflite_result_npy_filename, |
| tflite_result_name=tflite_result_name, |
| ifm_name=placeholder_names, |
| ifm_file=placeholder_npy_filenames, |
| ifm_shape=placeholder_shapes, |
| framework_exclusions=excluded_framework_list, |
| quantized=is_quantized, |
| test_name=test_name, |
| ) |
| except Exception as e: |
| msg = "Error running task: {}".format(e) |
| print(msg) |
| print( |
| "".join( |
| traceback.format_exception(etype=type(e), value=e, tb=e.__traceback__) |
| ) |
| ) |
| return False |
| return True |
| |
| |
| def build_const_net( |
| args, |
| curr_shape, |
| op_name, |
| dtype, |
| excluded_framework_list, |
| quantized_inference_dtype, |
| result_name, |
| seed, |
| rng, |
| filter, |
| unit_test_args, |
| ): |
| |
| if quantized_inference_dtype: |
| quant_dtype = get_tf_dtype(quantized_inference_dtype) |
| test_dir = "test_{}_{}".format(op_name, get_shape_str(curr_shape, quant_dtype)) |
| else: |
| test_dir = "test_{}_{}".format(op_name, get_shape_str(curr_shape, dtype)) |
| test_dir = os.path.join(args.output_dir, test_dir) |
| |
| # If the operator has an additional function to generate arguments, call it |
| # here and iterate through the argument list that it generates |
| op = TF_OP_LIST[op_name] |
| op_fcn, tensor_gen_fcn, arg_gen_fcn = op["build_fcn"] |
| |
| try: |
| rank_lo, rank_hi = op["rank"] |
| except KeyError: |
| # Set testing rank to (1, 4) in default. |
| rank_lo = 1 |
| rank_hi = 4 |
| |
| if len(curr_shape) not in range(rank_lo, rank_hi + 1): |
| return |
| |
| addl_args_tuple = arg_gen_fcn(op, curr_shape, rng) |
| for desc, addl_args in addl_args_tuple: |
| # Only filter on the full test_name, not the output directory |
| _, test_name = os.path.split(test_dir + desc) |
| if not filter or filter.search(test_name): |
| unit_test_args.append( |
| [ |
| op_name, |
| args, |
| test_dir + desc, |
| curr_shape, |
| addl_args, |
| dtype, |
| excluded_framework_list, |
| quantized_inference_dtype, |
| result_name, |
| seed, |
| ] |
| ) |
| |
| |
| # python hash is not reproducible, create hash for our purpose |
| def op_name_hash(op_name): |
| result = 0xDEADBEEF |
| for ch in op_name: |
| if result & 1: |
| result = (ord(ch) << 24) ^ (result >> 1) ^ 0x82608EDB |
| else: |
| result = (ord(ch) << 24) ^ (result >> 1) |
| |
| return result |
| |
| |
| def generate_op_tests(args, op_name, shape_list, result_name, filter, unit_test_args): |
| |
| if not args.quiet: |
| print( |
| "Generating tests for {} ".format( |
| op_name |
| ) |
| ) |
| |
| op = TF_OP_LIST[op_name] |
| |
| # Seed the RNG so that we get the same random tests for each test each time |
| # If the number of tests for a given generation function changes, the tests |
| # for that operator may also change accordingly, but this will at least keep |
| # down churn across operators. |
| |
| bounded_hash_val = (args.random_seed + op_name_hash(op_name)) % np.iinfo( |
| np.int32 |
| ).max |
| rng = np.random.default_rng(bounded_hash_val) |
| |
| # this is a dictionary with 'tf' and 'tflite' as key |
| # and value being the data types we want to test under these framework |
| |
| if isinstance(op["types"], dict): |
| try: |
| tf_dtypes = op["types"]["tf"] |
| except KeyError: |
| tf_dtypes = [] |
| try: |
| tflite_dtypes = op["types"]["tflite"] |
| except KeyError: |
| tflite_dtypes = [] |
| elif isinstance(op["types"], list): |
| tf_dtypes = op["types"] |
| tflite_dtypes = op["types"] |
| |
| tf_nonquantized_dtypes = tf_dtypes # tf doesn't support quantized data types |
| tflite_quantized_dtypes = [] |
| tflite_nonquantized_dtypes = [] |
| for dtype in tflite_dtypes: |
| if isinstance(dtype, QuantType): |
| tflite_quantized_dtypes.append(dtype) |
| else: |
| tflite_nonquantized_dtypes.append(dtype) |
| |
| nonquantized_dtypes_set = set(tf_nonquantized_dtypes).union( |
| set(tflite_nonquantized_dtypes) |
| ) |
| nonquantized_dtypes = list(nonquantized_dtypes_set) |
| quantized_dtypes = tflite_quantized_dtypes |
| |
| # append custom_shapes or replace shape_list with custom_shapes |
| try: |
| custom_shapes = op["custom_shapes"] |
| if custom_shapes["custom_shape_only"]: |
| shape_list = custom_shapes["shape_list"] |
| else: |
| shape_list = shape_list.copy() |
| shape_list.extend(custom_shapes["shape_list"]) |
| except KeyError: |
| pass |
| |
| # populate non quantized unit test arguments |
| for dtype in nonquantized_dtypes: |
| |
| excluded_framework_set = set(ALL_FRAMEWORKS) |
| if dtype in tf_nonquantized_dtypes: |
| excluded_framework_set.remove("tf") |
| if dtype in tflite_nonquantized_dtypes: |
| excluded_framework_set.remove("tflite") |
| excluded_framework_list = list(excluded_framework_set) |
| |
| for curr_shape in shape_list: |
| build_const_net( |
| args, |
| curr_shape, |
| op_name, |
| dtype, |
| excluded_framework_list, |
| None, |
| result_name, |
| bounded_hash_val, |
| rng, |
| filter, |
| unit_test_args, |
| ) |
| |
| # populate quantized unit test arguments |
| # must exclude 'tf' and source dtype being tf.float32 |
| for dtype in quantized_dtypes: |
| for curr_shape in shape_list: |
| build_const_net( |
| args, |
| curr_shape, |
| op_name, |
| tf.float32, |
| ["tf"], |
| dtype, |
| result_name, |
| bounded_hash_val, |
| rng, |
| filter, |
| unit_test_args, |
| ) |
| |
| return unit_test_args |
| |
| |
| def createDynamicOpLists(): |
| """The templated operators are conv2d-style operators with a number of kernel |
| sizes. Since the operator is unchanged, we generate the range of kernel |
| sizes here in this loop and remove the original templates from the list. |
| |
| This could be expanded to non-conv2d-style operators in the future.""" |
| |
| # Dynamically create op lists for convolutions with a list of kernel sizes |
| KERNELS = [ |
| [1, 1], |
| [3, 3], |
| [5, 5], |
| ] |
| |
| # dim = [D, H, W] |
| KERNELS_3D = [ |
| [1, 1, 1], |
| [2, 3, 3], |
| [3, 5, 5], |
| ] |
| |
| TEMPLATE_LIST = [ |
| "conv2d", |
| "conv2d_bias", |
| "conv2d_relu", |
| "conv2d_relu6", |
| "conv2d_relu_n1_to_1", |
| "conv2d_tanh", |
| "depthwise_conv2d", |
| "depthwise_conv2d_bias", |
| "transpose_conv2d", |
| ] |
| |
| TEMPLATE_LIST_CONV3D = [ |
| "conv3d", |
| "conv3d_bias", |
| ] |
| |
| for t in TEMPLATE_LIST: |
| for k in KERNELS: |
| testName = "{}_{}x{}".format(t, k[0], k[1]) |
| TF_OP_LIST[testName] = TF_OP_LIST["{}_TEMPLATE".format(t)].copy() |
| TF_OP_LIST[testName]["filter"] = k |
| TF_OP_LIST[testName]["template"] = False |
| |
| # The existing operators don't support the dimension of kernel that is higher than 2. |
| for t in TEMPLATE_LIST_CONV3D: |
| for k in KERNELS_3D: |
| testName = "{}_{}x{}x{}".format(t, k[0], k[1], k[2]) |
| TF_OP_LIST[testName] = TF_OP_LIST["{}_TEMPLATE".format(t)].copy() |
| TF_OP_LIST[testName]["filter"] = k |
| TF_OP_LIST[testName]["template"] = False |
| |
| # Delete any templates after having created any dynamic ops |
| # This is a two-pass operation because it's bad practice to delete |
| # keys from dictionaries while iterating |
| keyList = [] |
| for k in TF_OP_LIST: |
| try: |
| if TF_OP_LIST[k]["template"]: |
| keyList.append(k) |
| continue |
| except KeyError: |
| pass |
| |
| for k in keyList: |
| del TF_OP_LIST[k] |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser() |
| parser.add_argument( |
| "--seed", dest="random_seed", default=42, type=int, help="Random seed" |
| ) |
| parser.add_argument( |
| "--random-shapes", |
| dest="random_shapes", |
| default=0, |
| type=int, |
| help=( |
| "Use N random shapes of each rank for generating tests," |
| "seeded with random seed" |
| ), |
| ) |
| parser.add_argument( |
| "-o", |
| "--output-dir", |
| dest="output_dir", |
| default=".", |
| type=str, |
| help="Test output directory path prefix", |
| ) |
| parser.add_argument( |
| "-q", |
| "--quiet", |
| dest="quiet", |
| default=False, |
| action="store_true", |
| help="Do not print test names", |
| ) |
| parser.add_argument( |
| "-j", "--jobs", dest="jobs", type=int, default=1, help="Number of parallel jobs" |
| ) |
| parser.add_argument( |
| "-m", |
| "--tflite-kernel-mode", |
| dest="tflite_kernel_mode", |
| type=str, |
| choices=["reference", "optimized"], |
| default="reference", |
| help="TFLite interpreter kernel mode", |
| ) |
| parser.add_argument( |
| "--num-samples", |
| dest="num_samples", |
| default=200, |
| type=int, |
| help="Number of input samples for post-training quantization", |
| ) |
| parser.add_argument( |
| "--filter", |
| dest="filter", |
| default="", |
| type=str, |
| help="Filter test names by this expression", |
| ) |
| args = parser.parse_args() |
| |
| # Turn the filter into a re object if present |
| filter = None |
| if args.filter != "": |
| filter = re.compile(args.filter) |
| |
| # Autodetect CPU count |
| if args.jobs <= 0: |
| args.jobs = os.cpu_count() |
| |
| # Disable TF info messages |
| os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3" |
| |
| try: |
| os.makedirs(args.output_dir) |
| except FileExistsError: |
| pass |
| |
| if args.random_shapes: |
| gen_rand_shapes(args) |
| |
| # Build dynamic ops |
| createDynamicOpLists() |
| |
| # Generate the test list and arguments to run_unit_test() |
| unit_test_args = [] |
| |
| for op in TF_OP_LIST: |
| generate_op_tests(args, op, shape_list, "result", filter, unit_test_args) |
| |
| errors = 0 |
| for t in unit_test_args: |
| if not run_unit_test(*t): |
| errors = errors + 1 |
| |
| if not args.quiet: |
| print("\nAll tasks done - with {} errors".format(errors)) |
| |
| return 1 if errors else 0 |
| |
| |
| if __name__ == "__main__": |
| exit(main()) |