From a0e03f3f9a44d79367e2624f571009e4657c775a Mon Sep 17 00:00:00 2001 From: Jeremy Johnson Date: Mon, 13 Jun 2022 17:48:09 +0100 Subject: Update RESIZE operator in test generator for spec updates * Add common screen aspect ratios with borders to random pool of tests * Add up/downscaling to random pool Signed-off-by: Jeremy Johnson Change-Id: Iee8e3f5ed6bd5c941816474df20a7fd433646d6b Signed-off-by: James Ward --- verif/conformance/test_select.py | 5 +- verif/conformance/tosa_base_profile_ops_info.json | 4 +- verif/generator/tosa_arg_gen.py | 338 +++++++++++-------- verif/generator/tosa_error_if.py | 381 +++++++++++----------- verif/generator/tosa_test_gen.py | 130 +++++--- verif/generator/tosa_utils.py | 3 + verif/generator/tosa_verif_build_tests.py | 11 +- 7 files changed, 489 insertions(+), 383 deletions(-) diff --git a/verif/conformance/test_select.py b/verif/conformance/test_select.py index 1013b6e..8b60fbb 100644 --- a/verif/conformance/test_select.py +++ b/verif/conformance/test_select.py @@ -616,11 +616,10 @@ class ResizeOperator(Operator): "shape", "type", "mode", - "shift", - "output_dims", "output_type", - "stride", + "scale", "offset", + "border", ] diff --git a/verif/conformance/tosa_base_profile_ops_info.json b/verif/conformance/tosa_base_profile_ops_info.json index b9f392c..d05b881 100644 --- a/verif/conformance/tosa_base_profile_ops_info.json +++ b/verif/conformance/tosa_base_profile_ops_info.json @@ -2000,7 +2000,9 @@ "--target-shape", "1,3,16383,1", "--target-dtype", - "int8" + "int8", + "--max-resize-output-dim", + "9500" ] ], "params": {}, diff --git a/verif/generator/tosa_arg_gen.py b/verif/generator/tosa_arg_gen.py index 8e00fab..2181735 100644 --- a/verif/generator/tosa_arg_gen.py +++ b/verif/generator/tosa_arg_gen.py @@ -6,6 +6,7 @@ import math import numpy as np from generator.tosa_error_if import ErrorIf from generator.tosa_error_if import TosaErrorIfArgGen +from generator.tosa_utils import MAX_RESIZE_DIMENSION from serializer.tosa_serializer import DTypeNames from tosa.DType import DType from tosa.Op import Op @@ -1609,10 +1610,107 @@ class TosaArgGen: @staticmethod def agResize(testGen, opName, shapeList, dtype, error_name=None): arg_list = [] - ifm_shape = shapeList[0] - for mode in [ResizeMode.NEAREST, ResizeMode.BILINEAR]: + def get_aspect_ratio_resize_params(): + common_aspect_ratios = ((3, 2), (16, 9), (4, 3)) + aspect_ratio = testGen.rng.choice(common_aspect_ratios) + invert = testGen.rng.choice((False, True)) + letterbox = testGen.rng.choice((False, True)) + + scale_y_n = aspect_ratio[0] if invert else aspect_ratio[1] + scale_x_n = aspect_ratio[1] if invert else aspect_ratio[0] + scale_y_d = scale_x_d = 1 + offset_x = offset_y = 0 + + if letterbox: + max_border = scale_y_n + border_y = testGen.randInt(low=0, high=max_border) + border_x = 0 + else: + # Pillarboxing + border_y = 0 + max_border = scale_x_n + border_x = testGen.randInt(low=0, high=max_border) + + scale = (scale_y_n, scale_y_d, scale_x_n, scale_x_d) + offset = (offset_y, offset_x) + border = (border_y, border_x) + + return scale, offset, border + + def get_upscale_downscale_params(): + valid_params = False + while not valid_params: + upscale = testGen.rng.choice((False, True)) + + # True if sampling begins from (0,0). Otherwise (-0.5,-0.5) + origin_sampling = testGen.rng.choice((False, True)) + + if upscale: + shift = testGen.randInt(low=1, high=4) + scale_x_d = scale_y_d = 1 + scale_x_n = scale_y_n = ( + 1 << shift if origin_sampling else 2 << shift + ) + border_x = border_y = 0 if origin_sampling else (1 << shift) - 1 + offset_x = offset_y = 0 if origin_sampling else -(1 << shift) + 1 + else: + scale_x_n = 1 + scale_y_n = 1 + + # Return list of valid scale_*_d values (max value 4) given input dim shape + def get_valid_denom(ifm_dim): + return [x for x in range(1, 5) if ifm_dim % x == 1] + + # Generate list of valid downscale values and choose one randomly + valid_scale_y_ds = get_valid_denom(ifm_shape[1]) + valid_scale_x_ds = get_valid_denom(ifm_shape[2]) + + if not valid_scale_y_ds and not valid_scale_x_ds: + # Bad parameters, skip + continue + + if not valid_scale_y_ds: + scale_y_d = 1 + else: + scale_y_d = testGen.rng.choice(valid_scale_y_ds) + + if not valid_scale_x_ds: + scale_x_d = 1 + else: + scale_x_d = testGen.rng.choice(valid_scale_x_ds) + + border_x = border_y = 0 + offset_y = testGen.randInt(0, 16 * scale_y_n) + offset_x = testGen.randInt(0, 16 * scale_x_n) + valid_params = True + + scale = (scale_y_n, scale_y_d, scale_x_n, scale_x_d) + offset = (offset_y, offset_x) + border = (border_y, border_x) + return scale, offset, border + + def get_rand_params(): + # Scale + scale_y_n = testGen.randInt(low=1, high=(1 << 11)) + scale_x_n = testGen.randInt(low=1, high=(1 << 11)) + + scale_y_d = testGen.randInt(low=1, high=(16 * scale_y_n)) + scale_x_d = testGen.randInt(low=1, high=(16 * scale_x_n)) + + # Offsets and border within the scale + offset_y = testGen.randInt(low=-scale_y_n, high=(16 * scale_y_n)) + offset_x = testGen.randInt(low=-scale_x_n, high=(16 * scale_x_n)) + border_y = testGen.randInt(low=(-16 * scale_y_n), high=scale_y_n) + border_x = testGen.randInt(low=(-16 * scale_x_n), high=scale_x_n) + + scale = (scale_y_n, scale_y_d, scale_x_n, scale_x_d) + offset = (offset_y, offset_x) + border = (border_y, border_x) + return scale, offset, border + + for mode in [ResizeMode.NEAREST, ResizeMode.BILINEAR]: # Exclude illegal {mode, type} configurations. Pick legal output types if mode == ResizeMode.NEAREST and dtype == DType.INT8: outputDTypeList = [DType.INT8] @@ -1631,114 +1729,98 @@ class TosaArgGen: else: continue + arg_str = "mode{}_out{}_sc{}x{}x{}x{}_off{}x{}_bor{}x{}" + for outputDType in outputDTypeList: - for perm in range(testGen.args.num_rand_permutations): - # Randomly generate legal output dimensions and shift - # and then compute the stride and offset based on them - # A output_dim of 1 will cause offset to exceed allowed range - # so minimum value 2 produced below - output_dims = [testGen.randInt(1) + 1, testGen.randInt(1) + 1] - while (float(ifm_shape[1]) / float(output_dims[0])) >= 16: - output_dims[0] += 1 - while (float(ifm_shape[2]) / float(output_dims[1])) >= 16: - output_dims[1] += 1 - - in_center_h = (ifm_shape[1] - 1) / 2.0 - in_center_w = (ifm_shape[2] - 1) / 2.0 - out_center_h = (output_dims[0] - 1) / 2.0 - out_center_w = (output_dims[1] - 1) / 2.0 - - fp_stride_y = float(ifm_shape[1]) / float(output_dims[0]) - fp_stride_x = float(ifm_shape[2]) / float(output_dims[1]) - fp_offset_y = in_center_h - fp_stride_y * out_center_h - fp_offset_x = in_center_w - fp_stride_x * out_center_w - - if outputDType == DType.FLOAT: - float_op = True - arg_str = ( - "mode{}_shift{}_odim{}x{}_out{}" - "_st{:.2f}x{:.2f}_off{:.2f}x{:.2f}" + perm = 0 + while perm < testGen.args.num_rand_permutations: + # Random choice of type of params we are testing + _rnd_param_fn = testGen.rng.choice( + ( + get_rand_params, + get_upscale_downscale_params, + get_aspect_ratio_resize_params, ) - shift = 0 - stride = [0, 0] - offset = [0, 0] - stride_fp = [fp_stride_y, fp_stride_x] - offset_fp = [fp_offset_y, fp_offset_x] + ) + scale, offset, border = _rnd_param_fn() - else: - float_op = False - arg_str = "mode{}_shift{}_odim{}x{}_out{}_st{}x{}_off{}x{}" - shift = testGen.randInt(1, 12) - # Now search for a shift value (1 to 11) that will produce - # a valid and predictable resize operation - count = 0 - while count < 12: - unit = float(1 << shift) - stride_y = int(round(fp_stride_y * unit)) - stride_x = int(round(fp_stride_x * unit)) - offset_y = int(round(fp_offset_y * unit)) - offset_x = int(round(fp_offset_x * unit)) - - if ( - stride_y <= 0 - or stride_x <= 0 - or stride_y >= (16 << shift) - or stride_x >= (16 << shift) - or offset_y >= (16 << shift) - or offset_x >= (16 << shift) - or offset_y <= (-16 << shift) - or offset_x <= (-16 << shift) - ): - # Change the shift value and check again - count += 1 - shift = (shift % 11) + 1 - continue - - def RESIZE_REQUIRE_CALC( - length_in, length_out, stride, offset, shift - ): - # Perform the pseudo loop to look for out of bounds - for pos in range(0, length_out): - a = pos * stride + offset - ia = a >> shift - ia0 = max(ia, 0) - ia1 = min(ia + 1, length_in - 1) - if ia0 > ia1: - # Found a problem value - break - return ia0, ia1 - - iy0, iy1 = RESIZE_REQUIRE_CALC( - ifm_shape[1], output_dims[0], stride_y, offset_y, shift - ) - ix0, ix1 = RESIZE_REQUIRE_CALC( - ifm_shape[2], output_dims[1], stride_x, offset_x, shift - ) - if ix0 > ix1 or iy0 > iy1: - # Change the shift value and check again - count += 1 - shift = (shift % 11) + 1 - continue - break - - if count >= 12: - # Couldn't find a good set of values for this test, skip it + # Expand params for bounds-checking + (scale_y_n, scale_y_d, scale_x_n, scale_x_d) = scale + (offset_y, offset_x) = offset + (border_y, border_x) = border + + # Make sure output dimensions OH and OW are integers + partial_output_y = ( + (ifm_shape[1] - 1) * scale_y_n - offset_y + border_y + ) + partial_output_x = ( + (ifm_shape[2] - 1) * scale_x_n - offset_x + border_x + ) + if error_name == ErrorIf.ResizeOutputShapeNonInteger: + if ( + partial_output_y % scale_y_d == 0 + and partial_output_x % scale_x_d == 0 + ): + # Skip this test as it doesn't produce NonInteger output + perm += 1 continue + else: + while partial_output_y % scale_y_d != 0: + scale_y_d -= 1 + while partial_output_x % scale_x_d != 0: + scale_x_d -= 1 + + output_y = partial_output_y // scale_y_d + 1 + output_x = partial_output_x // scale_x_d + 1 - stride = [stride_y, stride_x] - offset = [offset_y, offset_x] + if ( + output_y >= testGen.args.max_resize_output_dim + or output_x >= testGen.args.max_resize_output_dim + ) and error_name is None: + # Skip positive test if output dim will be too high + # Avoid high test latency and OOM issues + perm += 1 + continue - stride_fp = [0.0, 0.0] - offset_fp = [0.0, 0.0] + if ( + output_y <= 0 + or output_y >= MAX_RESIZE_DIMENSION + or output_x <= 0 + or output_x >= MAX_RESIZE_DIMENSION + ): + # Output dimensions out of scope + if error_name is not None and perm > 0: + # As long as we have one ERROR_IF test, don't worry + # about creating all the other permutations + perm += 1 + continue + + if error_name == ErrorIf.ResizeOutputShapeMismatch and ( + ( + output_y + scale_y_d >= MAX_RESIZE_DIMENSION + and output_y - scale_y_d < 1 + ) + or ( + output_x + scale_x_d >= MAX_RESIZE_DIMENSION + and output_x - scale_x_d < 1 + ) + ): + # Can't create a negative test with these params as it + # will create invalid output size + if perm > 0: + perm += 1 + continue + + scale = [scale_y_n, scale_y_d, scale_x_n, scale_x_d] + offset = [offset_y, offset_x] + border = [border_y, border_x] # Common for all data types if error_name is not None: ( - shift, - stride, - stride_fp, + scale, offset, - offset_fp, + border, outputDTypeNew, ) = TosaErrorIfArgGen.eiResizeErrorIf( testGen, @@ -1747,42 +1829,42 @@ class TosaArgGen: dtype, shapeList, outputDType, - shift, - stride, - stride_fp, + scale, offset, - offset_fp, + border, ) else: outputDTypeNew = outputDType - arg_list.append( - ( - arg_str.format( - "N" if mode == ResizeMode.NEAREST else "B", - shift, - output_dims[0], - output_dims[1], - testGen.typeStr(outputDTypeNew), - stride_fp[0] if float_op else stride[0], - stride_fp[1] if float_op else stride[1], - offset_fp[0] if float_op else offset[0], - offset_fp[1] if float_op else offset[1], - ), - [ - mode, - stride, - offset, - shift, - stride_fp, - offset_fp, - output_dims, - dtype, - outputDTypeNew, - ], - ) + arg_to_append = ( + arg_str.format( + "N" if mode == ResizeMode.NEAREST else "B", + testGen.typeStr(outputDTypeNew), + scale[0], + scale[1], + scale[2], + scale[3], + offset[0], + offset[1], + border[0], + border[1], + ), + [ + mode, + scale, + offset, + border, + dtype, + outputDTypeNew, + ], ) + if arg_to_append in arg_list: + # Skip already generated test params + continue + # Valid permutation + perm += 1 + arg_list.append(arg_to_append) return arg_list @staticmethod diff --git a/verif/generator/tosa_error_if.py b/verif/generator/tosa_error_if.py index b331a42..1651d95 100644 --- a/verif/generator/tosa_error_if.py +++ b/verif/generator/tosa_error_if.py @@ -1,6 +1,7 @@ # Copyright (c) 2021-2022, ARM Limited. # SPDX-License-Identifier: Apache-2.0 import numpy as np +from generator.tosa_utils import MAX_RESIZE_DIMENSION from generator.tosa_utils import product from generator.tosa_utils import usableDTypes from generator.tosa_utils import valueToName @@ -11,14 +12,15 @@ from tosa.ResizeMode import ResizeMode class ErrorIf(object): MaxDimExceeded = "MaxDimExceeded" - StrideSmallerEqualZero = "StrideSmallerEqualZero" - StrideLargerEqualMax = "StrideLargerEqualMax" - StrideLargerDimension = "StrideLargerDimension" - OffsetSmallerEqualMin = "OffsetSmallerEqualMin" + ScaleSmallerEqualZero = "ScaleSmallerEqualZero" + ScaleNLargerMax = "ScaleNLargerMax" + ScaleDLargerMax = "ScaleDLargerMax" + OffsetSmallerMin = "OffsetSmallerMin" OffsetLargerEqualMax = "OffsetLargerEqualMax" - ShiftNotZero = "ShiftNotZero" - ShiftSmallerOne = "ShiftSmallerOne" - ShiftLargerEleven = "ShiftLargerEleven" + BorderSmallerMin = "BorderSmallerMin" + BorderLargerEqualMax = "BorderLargerEqualMax" + ResizeOutputShapeMismatch = "ResizeOutputShapeMismatch" + ResizeOutputShapeNonInteger = "ResizeOutputShapeNonInteger" WrongInputType = "WrongInputType" WrongOutputType = "WrongOutputType" WrongInputList = "WrongInputList" @@ -81,63 +83,33 @@ class TosaErrorIfArgGen: dtype, shapeList, outputDType, - shift, - stride, - stride_fp, + scale, offset, - offset_fp, + border, ): - - if outputDType == DType.FLOAT: - if error_name == ErrorIf.StrideSmallerEqualZero: - stride_fp = testGen.rng.random(size=[2]) - 2 - elif error_name == ErrorIf.ShiftNotZero: - shift = testGen.rng.integers(1, 5) - elif error_name == ErrorIf.StrideLargerDimension: - shape = shapeList[0] - transform_height = testGen.rng.choice([False, True]) - if transform_height: - stride_fp[0] = shape[1] + testGen.rng.integers(1, 10) - else: - stride_fp[1] = shape[2] + testGen.rng.integers(1, 10) - else: - if error_name == ErrorIf.StrideSmallerEqualZero: - stride = np.int16(testGen.rng.integers(-1, 1, size=[2])) - elif error_name == ErrorIf.ShiftSmallerOne: - shift = testGen.rng.integers(-3, 1) - if shift <= 0: - stride = [ - (16 >> -shift) - 1, - (16 >> -shift) - 1, - ] # avoids other ERROR_IF checks - offset = [ - (16 >> -shift) - 1, - (16 >> -shift) - 1, - ] # avoids other ERROR_IF checks - else: - stride = [ - (16 << shift) - 1, - (16 << shift) - 1, - ] # avoids other ERROR_IF checks - offset = [ - (16 << shift) - 1, - (16 << shift) - 1, - ] # avoids other ERROR_IF checks - elif error_name == ErrorIf.ShiftLargerEleven: - shift = np.int16(testGen.rng.integers(12, 15)) - elif error_name == ErrorIf.StrideLargerDimension: - shape = shapeList[0] - transform_height = testGen.rng.choice([False, True]) - if transform_height: - stride[0] = shape[1] + testGen.rng.integers(1, 10) - else: - stride[1] = shape[2] + testGen.rng.integers(1, 10) - elif error_name == ErrorIf.StrideLargerEqualMax: - stride = [(16 << shift) + 1, (16 << shift) + 1] - elif error_name == ErrorIf.OffsetLargerEqualMax: - offset = [(16 << shift) + 1, (16 << shift) + 1] - elif error_name == ErrorIf.OffsetSmallerEqualMin: - offset = [(-16 << shift) - 1, (-16 << shift) - 1] + if error_name == ErrorIf.ScaleSmallerEqualZero: + index = testGen.randInt(low=0, high=4) + scale[index] = testGen.rng.choice([-2, -1, 0]) + elif error_name == ErrorIf.ScaleNLargerMax: + index = testGen.rng.choice([0, 2]) + scale[index] = (1 << 11) + testGen.rng.choice([1, 2, 3]) + elif error_name == ErrorIf.ScaleDLargerMax: + index = testGen.rng.choice([1, 3]) + scale[index] = 16 * scale[index - 1] + testGen.rng.choice([0, 1, 2]) + + if error_name == ErrorIf.OffsetLargerEqualMax: + index = testGen.rng.choice([0, 1]) + offset[index] = 16 * scale[index * 2] + testGen.rng.choice([0, 1, 2]) + elif error_name == ErrorIf.OffsetSmallerMin: + index = testGen.rng.choice([0, 1]) + offset[index] = -scale[index * 2] - testGen.rng.choice([1, 2, 3]) + + if error_name == ErrorIf.BorderLargerEqualMax: + index = testGen.rng.choice([0, 1]) + border[index] = scale[index * 2] + testGen.rng.choice([0, 1, 2]) + elif error_name == ErrorIf.BorderSmallerMin: + index = testGen.rng.choice([0, 1]) + border[index] = -16 * scale[index * 2] - testGen.rng.choice([1, 2, 3]) if error_name == ErrorIf.WrongOutputType: if mode == ResizeMode.NEAREST and dtype == DType.INT8: @@ -182,7 +154,7 @@ class TosaErrorIfArgGen: ) outputDType = testGen.rng.choice(a=incorrect_types) - return shift, stride, stride_fp, offset, offset_fp, outputDType + return scale, offset, border, outputDType @staticmethod def eiPoolingErrorIf(testGen, error_name, stride, pad, kernel): @@ -630,18 +602,16 @@ class TosaErrorValidator: "shape": [[1, 16584, 5, 1], [1, 2, 16499, 4]], } error_result = False - error_reason = ( - "At least one maximum dimension is greater than or equal to 16384" - ) + error_reason = f"At least one maximum dimension is greater than or equal to {MAX_RESIZE_DIMENSION}" if check: input_shape = kwargs["input_shape"] - output_shape = kwargs["output_shape"] # Note this is just (OH, OW) + output_shape = kwargs["output_shape"] if ( - (input_shape[1] >= 16384) - or (input_shape[2] >= 16384) - or (output_shape[0] >= 16384) - or (output_shape[1] >= 16384) + (input_shape[1] >= MAX_RESIZE_DIMENSION) + or (input_shape[2] >= MAX_RESIZE_DIMENSION) + or (output_shape[1] >= MAX_RESIZE_DIMENSION) + or (output_shape[2] >= MAX_RESIZE_DIMENSION) ): error_result = True @@ -711,27 +681,16 @@ class TosaErrorValidator: return info_dict @staticmethod - def evStrideSmallerEqualZero(check=False, **kwargs): - error_name = ErrorIf.StrideSmallerEqualZero + def evScaleSmallerEqualZero(check=False, **kwargs): + error_name = ErrorIf.ScaleSmallerEqualZero param_reqs = {"rank": None, "dtype": None, "shape": None} error_result = False - error_reason = "Stride value smaller than or equal zero" + error_reason = "Scale value smaller than or equal zero" if check: - input_dtype = kwargs["input_dtype"] - output_dtype = kwargs["output_dtype"] - if input_dtype != DType.FLOAT and output_dtype == DType.FLOAT: - stride = kwargs["stride"] # Work around wrong input/output type tests - elif output_dtype == DType.FLOAT: - stride = kwargs["stride_fp"] - elif input_dtype == DType.FLOAT and output_dtype != DType.FLOAT: - stride = kwargs[ - "stride_fp" - ] # Work around wrong input/output type tests - else: - stride = kwargs["stride"] + scale = kwargs["scale"] - if min(stride) <= 0: + if min(scale) <= 0: error_result = True info_dict = { @@ -743,25 +702,17 @@ class TosaErrorValidator: return info_dict @staticmethod - def evStrideLargerEqualMax(check=False, **kwargs): - error_name = ErrorIf.StrideLargerEqualMax - param_reqs = {"rank": None, "dtype": [DType.INT8, DType.INT16], "shape": None} + def evScaleNLargerMax(check=False, **kwargs): + error_name = ErrorIf.ScaleNLargerMax + param_reqs = {"rank": None, "dtype": None, "shape": None} error_result = False - error_reason = "Stride value larger than or equal to maximum value" + error_reason = "Scale N value larger than maximum value" if check: - shift = kwargs["shift"] - input_dtype = kwargs["input_dtype"] - stride = kwargs["stride"] - if input_dtype in [DType.INT8, DType.INT16]: - if shift >= 0 and ( - stride[0] >= (16 << shift) or stride[1] >= (16 << shift) - ): - error_result = True - elif shift < 0 and ( - stride[0] >= (16 >> -shift) or stride[1] >= (16 >> -shift) - ): - error_result = True + scale = kwargs["scale"] + + if scale[0] > (1 << 11) or scale[2] > (1 << 11): + error_result = True info_dict = { "error_name": error_name, @@ -772,21 +723,17 @@ class TosaErrorValidator: return info_dict @staticmethod - def evStrideLargerDimension(check=False, **kwargs): - error_name = ErrorIf.StrideLargerDimension - param_reqs = {"rank": None, "dtype": [DType.FLOAT], "shape": None} + def evScaleDLargerMax(check=False, **kwargs): + error_name = ErrorIf.ScaleDLargerMax + param_reqs = {"rank": None, "dtype": None, "shape": None} error_result = False - error_reason = "Stride value larger than or equal to H/W dimension" + error_reason = "Scale D value larger than maximum value" if check: - shape = kwargs["input_shape"] - input_dtype = kwargs["input_dtype"] - stride = kwargs["stride_fp"] + scale = kwargs["scale"] - if ( - input_dtype == DType.FLOAT - and (stride[0] > shape[1]) - or (stride[1] > shape[2]) + if (scale[0] > 0 and scale[1] >= (16 * scale[0])) or ( + scale[2] > 0 and scale[3] >= (16 * scale[2]) ): error_result = True @@ -799,27 +746,19 @@ class TosaErrorValidator: return info_dict @staticmethod - def evOffsetSmallerEqualMin(check=False, **kwargs): - error_name = ErrorIf.OffsetSmallerEqualMin - param_reqs = {"rank": None, "dtype": [DType.INT8, DType.INT16], "shape": None} + def evOffsetSmallerMin(check=False, **kwargs): + error_name = ErrorIf.OffsetSmallerMin + param_reqs = {"rank": None, "dtype": None, "shape": None} error_result = False - error_reason = "Offset value smaller than or equal to minimum value" + error_reason = "Offset value smaller than minimum value" if check: - shift = kwargs["shift"] - output_dtype = kwargs["output_dtype"] - if output_dtype == DType.FLOAT: - offset = kwargs["offset_fp"] - else: - offset = kwargs["offset"] + scale = kwargs["scale"] + offset = kwargs["offset"] - if shift >= 0 and ( - offset[0] <= (-16 << shift) or offset[1] <= (-16 << shift) - ): + if scale[0] > 0 and scale[0] <= (1 << 11) and (offset[0] < -scale[0]): error_result = True - elif shift < 0 and ( - offset[0] <= (-16 >> -shift) or offset[1] <= (-16 >> -shift) - ): + elif scale[2] > 0 and scale[2] <= (1 << 11) and (offset[1] < -scale[2]): error_result = True info_dict = { @@ -833,28 +772,18 @@ class TosaErrorValidator: @staticmethod def evOffsetLargerEqualMax(check=False, **kwargs): error_name = ErrorIf.OffsetLargerEqualMax - param_reqs = {"rank": None, "dtype": [DType.INT8, DType.INT16], "shape": None} + param_reqs = {"rank": None, "dtype": None, "shape": None} error_result = False error_reason = "Offset value larger than or equal to maximum value" if check: - shift = kwargs["shift"] - output_dtype = kwargs["output_dtype"] - if output_dtype == DType.FLOAT: - offset = kwargs["offset_fp"] - else: - offset = kwargs["offset"] - - if shift >= 0: - if offset[0] >= (16 << shift) or offset[1] >= (16 << shift): - error_result = True + scale = kwargs["scale"] + offset = kwargs["offset"] - if shift >= 0 and ( - offset[0] >= (16 << shift) or offset[1] >= (16 << shift) - ): + if scale[0] > 0 and scale[0] <= (1 << 11) and (offset[0] >= 16 * scale[0]): error_result = True - elif shift < 0 and ( - offset[0] >= (16 >> -shift) or offset[1] >= (16 >> -shift) + elif ( + scale[2] > 0 and scale[2] <= (1 << 11) and (offset[1] >= 16 * scale[2]) ): error_result = True @@ -867,20 +796,26 @@ class TosaErrorValidator: return info_dict @staticmethod - def evShiftNotZero(check=False, **kwargs): - error_name = ErrorIf.ShiftNotZero - param_reqs = {"rank": None, "dtype": [DType.FLOAT], "shape": None} + def evBorderSmallerMin(check=False, **kwargs): + error_name = ErrorIf.BorderSmallerMin + param_reqs = {"rank": None, "dtype": None, "shape": None} error_result = False - error_reason = "Shift value must be zero for float input" + error_reason = "Border value smaller than minimum value" if check: - shift = kwargs["shift"] - input_dtype = kwargs["input_dtype"] - output_dtype = kwargs["output_dtype"] + scale = kwargs["scale"] + border = kwargs["border"] + if ( - input_dtype == DType.FLOAT - and output_dtype == DType.FLOAT - and shift != 0 + scale[0] > 0 + and scale[0] <= (1 << 11) + and (border[0] < (-16 * scale[0])) + ): + error_result = True + elif ( + scale[2] > 0 + and scale[2] <= (1 << 11) + and (border[1] < (-16 * scale[2])) ): error_result = True @@ -893,17 +828,19 @@ class TosaErrorValidator: return info_dict @staticmethod - def evShiftSmallerOne(check=False, **kwargs): - error_name = ErrorIf.ShiftSmallerOne - param_reqs = {"rank": None, "dtype": [DType.INT8, DType.INT16], "shape": None} + def evBorderLargerEqualMax(check=False, **kwargs): + error_name = ErrorIf.BorderLargerEqualMax + param_reqs = {"rank": None, "dtype": None, "shape": None} error_result = False - error_reason = "Shift value smaller than one" + error_reason = "Border value larger than or equal to maximum value" if check: - shift = kwargs["shift"] - input_dtype = kwargs["input_dtype"] - output_dtype = kwargs["output_dtype"] - if shift < 1 and input_dtype != DType.FLOAT and output_dtype != DType.FLOAT: + scale = kwargs["scale"] + border = kwargs["border"] + + if scale[0] > 0 and scale[0] <= (1 << 11) and (border[0] >= scale[0]): + error_result = True + elif scale[2] > 0 and scale[2] <= (1 << 11) and (border[1] >= scale[2]): error_result = True info_dict = { @@ -915,16 +852,90 @@ class TosaErrorValidator: return info_dict @staticmethod - def evShiftLargerEleven(check=False, **kwargs): - error_name = ErrorIf.ShiftLargerEleven - param_reqs = {"rank": None, "dtype": [DType.INT8, DType.INT16], "shape": None} + def checkResizeParams(scale, offset, border): + return ( + min(scale) > 0 + and max(scale[0], scale[2]) <= (1 << 11) + and scale[1] < 16 * scale[0] + and scale[3] < 16 * scale[2] + and offset[0] >= -scale[0] + and offset[1] >= -scale[2] + and offset[0] < 16 * scale[0] + and offset[1] < 16 * scale[2] + and border[0] >= -16 * scale[0] + and border[1] >= -16 * scale[2] + and border[0] < scale[0] + and border[1] < scale[2] + ) + + @staticmethod + def evResizeOutputShapeMismatch(check=False, **kwargs): + error_name = ErrorIf.ResizeOutputShapeMismatch + param_reqs = {"rank": None, "dtype": None, "shape": None} error_result = False - error_reason = "Shift value larger than eleven" + error_reason = ( + "Mismatch between output shape provided and expected output shape" + ) if check: - shift = kwargs["shift"] - if shift > 11: - error_result = True + input_shape = kwargs["input_shape"] + output_shape = kwargs["output_shape"] + scale = kwargs["scale"] + offset = kwargs["offset"] + border = kwargs["border"] + + # Ensure parameters are valid + params_valid = TosaErrorValidator.checkResizeParams(scale, offset, border) + + if ( + params_valid + and max(output_shape) < MAX_RESIZE_DIMENSION + and max(input_shape) < MAX_RESIZE_DIMENSION + ): + output_y = ( + (input_shape[1] - 1) * scale[0] - offset[0] + border[0] + ) // scale[1] + 1 + output_x = ( + (input_shape[2] - 1) * scale[2] - offset[1] + border[1] + ) // scale[3] + 1 + + if [output_y, output_x] != output_shape[1:-1]: + error_result = True + + info_dict = { + "error_name": error_name, + "error_result": error_result, + "error_reason": error_reason, + "param_reqs": param_reqs, + } + return info_dict + + @staticmethod + def evResizeOutputShapeNonInteger(check=False, **kwargs): + error_name = ErrorIf.ResizeOutputShapeNonInteger + param_reqs = {"rank": None, "dtype": None, "shape": None} + error_result = False + error_reason = "Parameters do not yield exact integer output dimensions" + + if check: + input_shape = kwargs["input_shape"] + scale = kwargs["scale"] + offset = kwargs["offset"] + border = kwargs["border"] + + # Ensure parameters are valid + params_valid = TosaErrorValidator.checkResizeParams(scale, offset, border) + + if params_valid: + remainder_y = ( + (input_shape[1] - 1) * scale[0] - offset[0] + border[0] + ) % scale[1] + remainder_x = ( + (input_shape[2] - 1) * scale[2] - offset[1] + border[1] + ) % scale[3] + + if max(remainder_y, remainder_x) > 0: + error_result = True info_dict = { "error_name": error_name, @@ -2191,44 +2202,24 @@ class TosaInvalidValidator: input_dtype = kwargs["input_dtype"] args = kwargs["args"] mode = args[0] - output_dtype = args[8] + output_dtype = args[5] if mode == ResizeMode.BILINEAR: # Invalid output data type / Invalid input datatype return ( not (input_dtype == DType.INT8 and output_dtype == DType.INT32) - or not (input_dtype == DType.INT16 and output_dtype == DType.INT48) - or not (input_dtype == DType.FLOAT and output_dtype == DType.FLOAT) - or (input_dtype not in [DType.INT8, DType.INT32, DType.FLOAT]) + and not (input_dtype == DType.INT16 and output_dtype == DType.INT48) + and not (input_dtype == DType.FLOAT and output_dtype == DType.FLOAT) ) elif mode == ResizeMode.NEAREST: # Invalid output data type / Invalid input datatype return (input_dtype != output_dtype) or ( - input_dtype not in [DType.INT8, DType.INT32, DType.FLOAT] + input_dtype not in [DType.INT8, DType.INT16, DType.FLOAT] ) else: # Invalid resize mode return True - @staticmethod - def ivBadStride(**kwargs): - input_dtype = kwargs["input_dtype"] - args = kwargs["args"] - stride_x = args[1][0] - stride_y = args[1][1] - stride_fp_x = args[4][0] - stride_fp_y = args[4][1] - - if input_dtype == DType.FLOAT: - if stride_fp_x <= 0 or stride_fp_y <= 0: - # Negative or zero stride - return True - else: - if stride_x <= 0 or stride_y <= 0: - # Negative or zero stride - return True - return False - @staticmethod def ivHeightWidthInvalid(**kwargs): opName = kwargs["opName"] diff --git a/verif/generator/tosa_test_gen.py b/verif/generator/tosa_test_gen.py index 583e1ed..eeb0ac7 100644 --- a/verif/generator/tosa_test_gen.py +++ b/verif/generator/tosa_test_gen.py @@ -13,6 +13,7 @@ from generator.tosa_error_if import ErrorIf from generator.tosa_error_if import TosaErrorIfArgGen from generator.tosa_error_if import TosaErrorValidator from generator.tosa_error_if import TosaInvalidValidator +from generator.tosa_utils import MAX_RESIZE_DIMENSION from generator.tosa_utils import usableDTypes from tosa.DType import DType from tosa.Op import Op @@ -1450,12 +1451,9 @@ class TosaTestGen: op, input, mode, - stride, + scale, offset, - shift, - stride_fp, - offset_fp, - output_dims, + border, input_dtype, output_dtype, validator_fcns, @@ -1466,12 +1464,9 @@ class TosaTestGen: self.rng, input, mode, - stride, + scale, offset, - shift, - stride_fp, - offset_fp, - output_dims, + border, input_dtype, output_dtype, error_name, @@ -1492,15 +1487,13 @@ class TosaTestGen: error_name, op=op, mode=mode, - shift=shift, + scale=scale, input_dtype=input_dtype, output_dtype=output_dtype, input_shape=input.shape, - output_shape=output_dims, + output_shape=result_tens.shape, offset=offset, - offset_fp=offset_fp, - stride=stride, - stride_fp=stride_fp, + border=border, input_list=input_list, output_list=output_list, result_tensor=result_tens, @@ -1510,9 +1503,7 @@ class TosaTestGen: attr = ts.TosaSerializerAttribute() - attr.ResizeAttribute( - output_dims, stride, offset, shift, stride_fp, offset_fp, mode - ) + attr.ResizeAttribute(scale, offset, border, mode) self.ser.addOperator(op["op"], input_list, output_list, attr) return result_tens @@ -3619,18 +3610,16 @@ class TosaTestGen: "types": [DType.INT8, DType.INT16, DType.FLOAT], "invalid_test_validators": ( TosaInvalidValidator.ivWrongDataTypeOrModeResize, - TosaInvalidValidator.ivBadStride, ), "error_if_validators": ( TosaErrorValidator.evMaxDimExceeded, - TosaErrorValidator.evStrideSmallerEqualZero, - TosaErrorValidator.evStrideLargerDimension, - TosaErrorValidator.evStrideLargerEqualMax, - TosaErrorValidator.evOffsetSmallerEqualMin, + TosaErrorValidator.evScaleSmallerEqualZero, + TosaErrorValidator.evScaleNLargerMax, + TosaErrorValidator.evScaleDLargerMax, + TosaErrorValidator.evOffsetSmallerMin, TosaErrorValidator.evOffsetLargerEqualMax, - TosaErrorValidator.evShiftNotZero, - TosaErrorValidator.evShiftSmallerOne, - TosaErrorValidator.evShiftLargerEleven, + TosaErrorValidator.evBorderSmallerMin, + TosaErrorValidator.evBorderLargerEqualMax, TosaErrorValidator.evWrongInputType, TosaErrorValidator.evWrongOutputType, TosaErrorValidator.evWrongRank, @@ -3638,6 +3627,8 @@ class TosaTestGen: TosaErrorValidator.evWrongOutputList, TosaErrorValidator.evBatchMismatch, TosaErrorValidator.evChannelMismatch, + TosaErrorValidator.evResizeOutputShapeMismatch, + TosaErrorValidator.evResizeOutputShapeNonInteger, ), }, # Type conversion @@ -4470,45 +4461,76 @@ class OutputShaper: rng, input, mode, - stride, + scale, offset, - shift, - stride_fp, - offset_fp, - output_dims, + border, input_dtype, output_dtype, error_name=None, ): + # Calculate OH, OW + scale_y_n = scale[0] + scale_y_d = scale[1] + scale_x_n = scale[2] + scale_x_d = scale[3] + if error_name == ErrorIf.ScaleSmallerEqualZero: + scale_y_n = max(scale_y_n, 1) + scale_y_d = max(scale_y_d, 1) + scale_x_n = max(scale_x_n, 1) + scale_x_d = max(scale_x_d, 1) + + oh = ((input.shape[1] - 1) * scale_y_n - offset[0] + border[0]) // scale_y_d + 1 + ow = ((input.shape[2] - 1) * scale_x_n - offset[1] + border[1]) // scale_x_d + 1 + + if error_name is not None: + # Make sure the output tensor is valid, which can occur when + # scale, offset or border have been changed for ERROR_IFs + oh = max(oh, 1) + ow = max(ow, 1) + if error_name != ErrorIf.MaxDimExceeded: + oh = min(oh, MAX_RESIZE_DIMENSION - 1) + ow = min(ow, MAX_RESIZE_DIMENSION - 1) + + if error_name == ErrorIf.ResizeOutputShapeMismatch: + choices = [1, 2, 3] + change = rng.choice(choices) + # increment in multiples of scale_y/x_d so we don't hit non-integer error case + if change in [1, 3]: + if oh + scale_y_d >= MAX_RESIZE_DIMENSION: + oh -= scale_y_d + assert oh > 0 # Should have been caught in agResize + else: + oh += scale_y_d + if change in [2, 3]: + if ow + scale_x_d >= MAX_RESIZE_DIMENSION: + ow -= scale_x_d + assert ow > 0 # Should have been caught in agResize + else: + ow += scale_x_d + if error_name == ErrorIf.WrongRank: output_dims = [ input.shape[0], - output_dims[0], - output_dims[0], + oh, + ow, + input.shape[0], + ] + elif error_name == ErrorIf.BatchMismatch: + output_dims = [ + input.shape[0] + rng.integers(1, 10), + oh, + ow, + input.shape[3], + ] + elif error_name == ErrorIf.ChannelMismatch: + output_dims = [ input.shape[0], + oh, + ow, + input.shape[3] + rng.integers(1, 10), ] else: - if error_name == ErrorIf.BatchMismatch: - output_dims = [ - input.shape[0] + rng.integers(1, 10), - output_dims[0], - output_dims[1], - input.shape[3], - ] - elif error_name == ErrorIf.ChannelMismatch: - output_dims = [ - input.shape[0], - output_dims[0], - output_dims[1], - input.shape[3] + rng.integers(1, 10), - ] - else: - output_dims = [ - input.shape[0], - output_dims[0], - output_dims[1], - input.shape[3], - ] + output_dims = [input.shape[0], oh, ow, input.shape[3]] return serializer.addOutput(output_dims, output_dtype) diff --git a/verif/generator/tosa_utils.py b/verif/generator/tosa_utils.py index a4ef31a..6a689d0 100644 --- a/verif/generator/tosa_utils.py +++ b/verif/generator/tosa_utils.py @@ -2,6 +2,9 @@ # SPDX-License-Identifier: Apache-2.0 from tosa.DType import DType +# Maximum dimension size for output and inputs for RESIZE +MAX_RESIZE_DIMENSION = 16384 + def valueToName(item, value): """Get the name of an attribute with the given value. diff --git a/verif/generator/tosa_verif_build_tests.py b/verif/generator/tosa_verif_build_tests.py index 69322cc..6ee873f 100644 --- a/verif/generator/tosa_verif_build_tests.py +++ b/verif/generator/tosa_verif_build_tests.py @@ -123,6 +123,14 @@ def parseArgs(): help="Number of random permutations for a given shape/rank for randomly-sampled parameter spaces", ) + parser.add_argument( + "--max-resize-output-dim", + dest="max_resize_output_dim", + default=1000, + type=int, + help="Upper limit on width and height output dimensions for `resize` op. Default: 1000", + ) + # Targetting a specific shape/rank/dtype parser.add_argument( "--target-shape", @@ -212,12 +220,11 @@ def main(): for opName, testStr, dtype, error, shapeList, testArgs in testList: # Check for and skip duplicate tests if testStr in testStrings: + print(f"Skipping duplicate test: {testStr}") continue else: testStrings.append(testStr) - if args.verbose: - print(testStr) results.append( ttg.serializeTest(opName, testStr, dtype, error, shapeList, testArgs) ) -- cgit v1.2.1