diff options
Diffstat (limited to 'verif/generator/tosa_arg_gen.py')
-rw-r--r-- | verif/generator/tosa_arg_gen.py | 1809 |
1 files changed, 1809 insertions, 0 deletions
diff --git a/verif/generator/tosa_arg_gen.py b/verif/generator/tosa_arg_gen.py new file mode 100644 index 0000000..e3492cd --- /dev/null +++ b/verif/generator/tosa_arg_gen.py @@ -0,0 +1,1809 @@ +# Copyright (c) 2021-2022, ARM Limited. +# SPDX-License-Identifier: Apache-2.0 +import itertools +import math + +import numpy as np +import serializer.tosa_serializer as ts +from generator.tosa_error_if import ErrorIf +from generator.tosa_error_if import TosaErrorIfArgGen +from serializer.tosa_serializer import DTypeNames +from tosa.DType import DType +from tosa.Op import Op +from tosa.ResizeMode import ResizeMode + +# DTypeNames, DType, Op and ResizeMode are convenience variables to the +# flatc-generated types that should be enums, but aren't + + +class TosaQuantGen: + """QuantizedInfo random generator helper functions. + + Specify with 'qgen': in the operator defintion. + """ + + def __init__(self): + pass + + @staticmethod + def getQinfo(testGen, dtype, error_name=None): + + if dtype == DType.INT8: + return testGen.randInt(-128, 128) + elif dtype == DType.UINT8: + return testGen.randInt(0, 256) + elif error_name in [ + ErrorIf.InputZeroPointNotZero, + ErrorIf.WeightZeroPointNotZero, + ErrorIf.OutputZeroPointNotZero, + ]: + zero_point = testGen.randInt(-128, 128) + if zero_point == 0: + zero_point = 1 + return zero_point + return 0 + + @staticmethod + def qgUnary(testGen, op, dtype, error_name=None): + qinfo = ts.TosaSerializerQuantInfo() + if error_name == ErrorIf.InputZeroPointNotZero: + qinfo.UnaryQuantInfo( + TosaQuantGen.getQinfo(testGen, dtype, error_name), + TosaQuantGen.getQinfo(testGen, dtype), + ) + elif error_name == ErrorIf.OutputZeroPointNotZero: + qinfo.UnaryQuantInfo( + TosaQuantGen.getQinfo(testGen, dtype), + TosaQuantGen.getQinfo(testGen, dtype, error_name), + ) + else: + qinfo.UnaryQuantInfo( + TosaQuantGen.getQinfo(testGen, dtype), + TosaQuantGen.getQinfo(testGen, dtype), + ) + return qinfo + + @staticmethod + def qgConv(testGen, op, dtype_or_dtypeList, error_name=None): + qinfo = ts.TosaSerializerQuantInfo() + if isinstance(dtype_or_dtypeList, list): + # a list of [input, weights, accumulator] dtypes + dtypeList = dtype_or_dtypeList + else: + # an int, [input, weights, accumulator] dtypes are the same + dtypeList = [dtype_or_dtypeList] * 3 + + if error_name == ErrorIf.InputZeroPointNotZero: + input_zp = TosaQuantGen.getQinfo(testGen, dtypeList[0], error_name) + weights_zp = TosaQuantGen.getQinfo(testGen, dtypeList[1]) + elif error_name == ErrorIf.WeightZeroPointNotZero: + input_zp = TosaQuantGen.getQinfo(testGen, dtypeList[0]) + weights_zp = TosaQuantGen.getQinfo(testGen, dtypeList[1], error_name) + else: + input_zp = TosaQuantGen.getQinfo(testGen, dtypeList[0]) + weights_zp = TosaQuantGen.getQinfo(testGen, dtypeList[1]) + + qinfo.ConvQuantInfo(input_zp, weights_zp) + return qinfo + + @staticmethod + def qgMatmul(testGen, op, dtype, error_name=None): + qinfo = ts.TosaSerializerQuantInfo() + if error_name == ErrorIf.InputZeroPointNotZero: + qinfo.MatMulQuantInfo( + TosaQuantGen.getQinfo(testGen, dtype, error_name), + TosaQuantGen.getQinfo(testGen, dtype, error_name), + ) + else: + qinfo.MatMulQuantInfo( + TosaQuantGen.getQinfo(testGen, dtype), + TosaQuantGen.getQinfo(testGen, dtype), + ) + return qinfo + + @staticmethod + def qgPad(testGen, op, dtype, error_name=None): + qinfo = ts.TosaSerializerQuantInfo() + if error_name == ErrorIf.InputZeroPointNotZero: + qinfo.PadQuantInfo(TosaQuantGen.getQinfo(testGen, dtype, error_name)) + else: + qinfo.PadQuantInfo(TosaQuantGen.getQinfo(testGen, dtype)) + return qinfo + + @staticmethod + def computeMultiplierAndShift(scaleFp, scale32): + # Derived from computeMultiplierAndShiftTosaScale32 + # Provide a floating-point scaling factor and the scale32 parameter + # to compute the multiplier and shift + + if scale32: + scaleBits = 31 + else: + scaleBits = 15 + + m, shift = math.frexp(scaleFp) + + if scaleFp < 0.0: + m = -m + + multiplier = round(m * (1 << scaleBits)) + assert multiplier <= (1 << scaleBits) + + if multiplier == (1 << scaleBits): + multiplier = multiplier // 2 + shift = shift + 1 + + shift = (-shift) + scaleBits + # print('scalefp {} scaleBits {} m {} mult {} shift {}'.format( + # scaleFp, scaleBits, m, multiplier, shift)) + + # Adjust multiplier such that shift is in allowed value range. + if shift == 0: + multiplier = multiplier // 4 + shift = shift + 2 + elif shift == 1: + multiplier = multiplier // 2 + shift = shift + 1 + elif shift == 63: + multiplier = multiplier * 2 + shift = shift - 1 + + assert multiplier <= (1 << scaleBits) + assert shift >= 2 and shift <= 62 + + return multiplier, shift + + +class TosaTensorGen: + """Tensor generators create a shape list for the placeholder and const tensor + data operands for the operator. + + The actual random data is generated separately for each test. + """ + + def __init__(self): + pass + + @staticmethod + def tgBasic(testGen, opName, rank, error_name=None): + pl, const = opName["operands"] + shape = testGen.makeShape(rank) + + # Constrict the overall size of the shape when creating ERROR_IF tests + if error_name: + shape = TosaErrorIfArgGen.eiRestrictDimensions(shape) + + shape_list = [] + for i in range(pl + const): + shape_list.append(shape.copy()) + + if error_name == ErrorIf.RankMismatch: + if rank == 1 and i != 1: + shape = testGen.makeShape(rank + testGen.rng.choice([1, 2, 3])) + elif i != 1: + shape = testGen.makeShape(rank + testGen.rng.choice([-1, 1])) + + return shape_list + + @staticmethod + def tgNHWC(testGen, opName, rank, error_name=None): + pl, const = opName["operands"] + + if error_name != ErrorIf.WrongRank: + assert rank == 4 + + shape = testGen.makeShape(rank) + + # Constrict the batch size? + if testGen.args.max_batch_size: + shape[0] = (shape[0] % testGen.args.max_batch_size) + 1 + + # Constrict the overall size of the shape when creating ERROR_IF tests + if error_name and error_name != ErrorIf.MaxDimExceeded: + shape = TosaErrorIfArgGen.eiRestrictDimensions(shape) + + shape_list = [] + for i in range(pl + const): + shape_list.append(shape.copy()) + + return shape_list + + @staticmethod + def tgScatter(testGen, opName, rank, error_name=None): + pl, const = opName["operands"] + + assert pl == 2 + assert const == 0 + if error_name != ErrorIf.WrongRank: + assert rank == 3 + + values_in_shape = testGen.makeShape(rank) + + # ignore max batch size if target shape is set + if testGen.args.max_batch_size and not testGen.args.target_shapes: + values_in_shape[0] = (values_in_shape[0] % testGen.args.max_batch_size) + 1 + + W = testGen.randInt( + testGen.args.tensor_shape_range[0], testGen.args.tensor_shape_range[1] + ) + # Constrict W if one dimension is too large to keep tensor size reasonable + if max(values_in_shape) > 5000: + W = testGen.randInt(0, 16) + + input_shape = [values_in_shape[0], W, values_in_shape[2]] + + shape_list = [] + shape_list.append(values_in_shape.copy()) + shape_list.append(input_shape.copy()) + + return shape_list + + @staticmethod + def tgBroadcastFuzz(testGen, op, rank, error_name=None): + shape = testGen.makeShape(rank) + + pl, const = op["operands"] + + shape_list = [] + + # Choose one of the inputs to broadcast + # Note: Simplifies OutputShaper code if we don't change first shape for errors + bcast_idx = testGen.randInt(0 if error_name is None else 1, pl + const) + for i in range(pl + const): + shape_bcast = shape.copy() + + # If the chosen input, pick a random index to broadcast + if i == bcast_idx: + fuzz_idx = testGen.randInt(0, rank) + if error_name == ErrorIf.DimensionMismatch: + shape_bcast[fuzz_idx] += 1 + elif error_name == ErrorIf.RankMismatch: + # Add one rank to the shape (or more for rank of 1) + extra_ranks = testGen.rng.choice([1, 2, 3]) if rank == 1 else 1 + shape_bcast = np.concatenate( + (shape_bcast, testGen.makeShape(extra_ranks)) + ) + if rank != 1: + # Either keep the extra rank, or remove it + new_len = testGen.rng.choice([-2, len(shape_bcast)]) + shape_bcast = shape_bcast[:new_len] + else: + shape_bcast[fuzz_idx] = 1 + + shape_list.append(shape_bcast) + + return shape_list + + @staticmethod + def tgConv2D(testGen, op, rank, error_name=None): + pl, const = op["operands"] + + if error_name != ErrorIf.WrongRank: + assert rank == 4 + + # IFM dimensions are NHWC + ifm_shape = testGen.makeShape(rank) + + # Constrict the batch size? + if testGen.args.max_batch_size: + ifm_shape[0] = (ifm_shape[0] % testGen.args.max_batch_size) + 1 + + # Constrict the overall size of the shape when creating ERROR_IF tests + if error_name: + ifm_shape = TosaErrorIfArgGen.eiRestrictDimensions( + ifm_shape, max_dim=24, max_items=10000 + ) + + # Get the filter height/width from the operator parameters + filter_hw = op["filter"] + + # Generate a random OFM depth + ofm_depth = testGen.makeShape(1)[0] + + # The filter dimensions are OHWI + filter_shape = np.asarray([ofm_depth, filter_hw[0], filter_hw[1], ifm_shape[3]]) + + # The bias is OC + bias_shape = np.asarray([ofm_depth]) + + return [ifm_shape, filter_shape, bias_shape] + + @staticmethod + def tgConv3D(testGen, op, rank, error_name=None): + pl, const = op["operands"] + + if error_name != ErrorIf.WrongRank: + assert rank == 5 + + # IFM dimensions are NDHWC + ifm_shape = testGen.makeShape(rank) + + # Constrict the batch size? + if testGen.args.max_batch_size: + ifm_shape[0] = (ifm_shape[0] % testGen.args.max_batch_size) + 1 + + # Constrict the overall size of the shape when creating ERROR_IF tests + if error_name: + ifm_shape = TosaErrorIfArgGen.eiRestrictDimensions( + ifm_shape, max_dim=24, max_items=10000 + ) + + # Get the filter depth/height/width from the operator parameters + filter_dhw = op["filter"] + + # Generate a random OFM channel + ofm_channel = testGen.makeShape(1)[0] + + # The filter dimensions are ODHWI + filter_shape = np.asarray( + [ofm_channel, filter_dhw[0], filter_dhw[1], filter_dhw[2], ifm_shape[4]] + ) + + # The bias is OC + bias_shape = np.asarray([ofm_channel]) + + return [ifm_shape, filter_shape, bias_shape] + + @staticmethod + def tgTransposeConv2D(testGen, op, rank, error_name=None): + pl, const = op["operands"] + + if error_name != ErrorIf.WrongRank: + assert rank == 4 + + # IFM dimensions are NHWC + ifm_shape = testGen.makeShape(rank) + + # Constrict the batch size? + if testGen.args.max_batch_size: + ifm_shape[0] = (ifm_shape[0] % testGen.args.max_batch_size) + 1 + + # Constrict the overall size of the shape when creating ERROR_IF tests + if error_name: + ifm_shape = TosaErrorIfArgGen.eiRestrictDimensions( + ifm_shape, max_dim=24, max_items=10000 + ) + + # Get the filter height/width from the operator parameters + filter_hw = op["filter"] + + # Generate a random OFM depth + ofm_depth = testGen.makeShape(1)[0] + + # The filter dimensions are OHWI + filter_shape = np.asarray([ofm_depth, filter_hw[0], filter_hw[1], ifm_shape[3]]) + + # The bias is OC + bias_shape = np.asarray([ofm_depth]) + + return [ifm_shape, filter_shape, bias_shape] + + @staticmethod + def tgDepthwiseConv2D(testGen, op, rank, error_name=None): + pl, const = op["operands"] + + if error_name != ErrorIf.WrongRank: + assert rank == 4 + assert pl == 1 and const == 2 + + # IFM dimensions are NHWC + ifm_shape = testGen.makeShape(rank) + + # Constrict the batch size? + if testGen.args.max_batch_size: + ifm_shape[0] = (ifm_shape[0] % testGen.args.max_batch_size) + 1 + + # Constrict the overall size of the shape when creating ERROR_IF tests + if error_name: + ifm_shape = TosaErrorIfArgGen.eiRestrictDimensions( + ifm_shape, max_dim=24, max_items=10000 + ) + + # Get the filter height/width from the operator parameters + # Filter is KH, HW, C, M + filter_hw = op["filter"] + + # Generate a random OFM depth, but don't let it get too big because + # the output depth is M * C + filter_m = ( + testGen.makeShape(1)[0] % (testGen.args.tensor_shape_range[1] // 4) + ) + 1 + + # The filter dimensions are HWCM + filter_shape = np.asarray([filter_hw[0], filter_hw[1], ifm_shape[3], filter_m]) + + # The bias is M * C + bias_shape = np.asarray([ifm_shape[3] * filter_m]) + + return [ifm_shape, filter_shape, bias_shape] + + @staticmethod + def tgFullyConnected(testGen, op, rank, error_name=None): + pl, const = op["operands"] + + if error_name != ErrorIf.WrongRank: + assert rank == 2 + + input_shape = testGen.makeShape(rank) + + # Constrict the overall size of the shape when creating ERROR_IF tests + if error_name: + input_shape = TosaErrorIfArgGen.eiRestrictDimensions(input_shape) + + filter_oc = testGen.rng.integers( + low=testGen.args.tensor_shape_range[0], + high=testGen.args.tensor_shape_range[1], + size=1, + )[0] + filter_shape = np.asarray([filter_oc, input_shape[1]]) + + bias_shape = np.asarray([filter_oc]) + + return [input_shape, filter_shape, bias_shape] + + @staticmethod + def tgMatmul(testGen, op, rank, error_name=None): + pl, const = op["operands"] + + if error_name != ErrorIf.WrongRank: + assert rank == 3 + assert pl == 2 and const == 0 + + a_shape = testGen.makeShape(rank) + + # Constrict the overall size of the shape when creating ERROR_IF tests + if error_name: + a_shape = TosaErrorIfArgGen.eiRestrictDimensions(a_shape) + + # Get a random number for b_oc even if target shape is defined + b_oc = np.int32( + testGen.rng.integers( + low=testGen.args.tensor_shape_range[0], + high=testGen.args.tensor_shape_range[1], + size=1, + ) + )[0] + # If N or H is large let b_oc be 1 to reduce output tensor size + if max(a_shape) > 1000: + b_oc = 1 + + b_shape = np.asarray([a_shape[0], a_shape[2], b_oc]) + return [a_shape, b_shape] + + @staticmethod + def tgConcat(testGen, opName, rank, error_name=None): + pl, const = opName["operands"] + shape = testGen.makeShape(rank) + + # Create extra tensors to concat. + # Take into account value of pl when getting maximum number of concats + num_tensors = testGen.randInt(0, 4) + shape_list = [] + for i in range(pl + const + num_tensors): + if error_name == ErrorIf.ConcatInputRankMismatch and i != 0: + remove = testGen.rng.choice([True, False]) + wrongShape = shape.copy() + + if remove and len(shape) > 1: + wrongShape = wrongShape[1:] + else: + wrongShape = list(wrongShape) + wrongShape.append(testGen.rng.integers(1, 10)) + + shape_list.append(wrongShape) + else: + shape_list.append(shape.copy()) + + return shape_list + + @staticmethod + def tgConcatConstInput(testGen, shapeList, axis, error_name=None): + if error_name in [ + ErrorIf.AxisSmallerZero, + ErrorIf.AxisLargerRank, + ErrorIf.ConcatInputRankMismatch, + ]: + return shapeList + + # Split concat shape along axis to allow for multiple const inputs + # without making too many large tensors + if len(shapeList) == 2 or shapeList[0][axis] < len(shapeList): + # If axis can't be split we still need to invalidate other dimensions + if error_name == ErrorIf.ConcatInputDimMismatch: + for shape in shapeList[1:]: + # Negative test shapeLists are created individually for each test, + # so no need to copy the shape before altering it. + shape[(axis + 1) % len(shape)] += testGen.rng.integers(5, 10) + return shapeList + + # Create copy of shape we are going to split (so we don't alter shapeList) + shape = shapeList[0].copy() + # Add original shape as first input + new_shapeList = [shape.copy()] + length_on_axis = shape[axis] + remaining_length = length_on_axis + for i in range(len(shapeList) - 2): + # Calculate split on axis and remaining value + split_shape_val = int(shape[axis] / 2) + remaining_length = remaining_length - split_shape_val + + # Append new shape, and set remaining shape + shape[axis] = split_shape_val + new_shapeList.append(shape.copy()) + + # invalidate dimensions + if error_name == ErrorIf.ConcatInputDimMismatch: + shape[(axis + 1) % len(shape)] += testGen.rng.integers(5, 10) + else: + shape[axis] = remaining_length + + if i == len(shapeList) - 3: + new_shapeList.append(shape.copy()) + + return new_shapeList + + +class TosaTensorValuesGen: + """Tensor Value generators create the random data for each test.""" + + def __init__(self): + pass + + @staticmethod + def tvgDefault(testGen, op, dtypeList, shapeList, testArgs, qinfo, error_name=None): + pCount, cCount = op["operands"] + + tens = [] + tens.extend( + testGen.buildPlaceholderTensors(shapeList[0:pCount], dtypeList[0:pCount]) + ) + tens.extend(testGen.buildConstTensors(shapeList[pCount:], dtypeList[pCount:])) + + return tens + + @staticmethod + def tvgNegate(testGen, op, dtypeList, shapeList, testArgs, qinfo, error_name=None): + if dtypeList[0] != DType.FLOAT and error_name is None: + pCount, cCount = op["operands"] + assert ( + pCount == 1 and cCount == 0 + ), "Op.NEGATE must have 1 placeholders, 0 consts" + # Must create tensors with values within negatable ranges + if dtypeList[0] == DType.INT8: + # Must be within int8, adjustable by input_zp and then negatable + # and be within int8 + # For use: qinfo.ints[0][1] = input_zp, qinfo.ints[1][1] = output_zp + max_val = min(127, 127 + qinfo.ints[0][1]) + min_val = max(-127, -127 + qinfo.ints[0][1]) + elif dtypeList[0] == DType.INT16: + max_val = 32767 + min_val = -max_val + else: + assert ( + dtypeList[0] == DType.INT32 + ), "Op.NEGATE found with unsupported input type" + max_val = (1 << 31) - 1 + min_val = -max_val + arr = np.int32( + testGen.rng.integers(low=min_val, high=(max_val + 1), size=shapeList[0]) + ) + placeholders = [] + placeholders.append( + testGen.ser.addPlaceholder(shapeList[0], dtypeList[0], arr) + ) + return placeholders + else: + return TosaTensorValuesGen.tvgDefault( + testGen, op, dtypeList, shapeList, testArgs, qinfo, error_name + ) + + @staticmethod + def tvgAddSub(testGen, op, dtypeList, shapeList, testArgs, qinfo, error_name=None): + if dtypeList[0] == DType.INT32 and error_name is None: + # Make sure the operation does not cause value saturation - where + # the number wraps due to limited number of bits to store the answer + pCount, cCount = op["operands"] + assert ( + pCount == 2 and cCount == 0 + ), "Op.ADD / Op.SUB must have 2 placeholders, 0 consts" + placeholders = [] + add = op["op"] == Op.ADD + a_arr = testGen.getRandTensor(shapeList[0], dtypeList[0]) + b_arr = testGen.getRandTensor(shapeList[1], dtypeList[1]) + if add: + res_arr = np.add(a_arr, b_arr, dtype=np.int64) + else: + res_arr = np.subtract(a_arr, b_arr, dtype=np.int64) + + # Work out the saturation limits + max_i32 = (1 << 31) - 1 + min_i32 = -(1 << 31) + max_arr = np.full(shapeList[1], max_i32) + min_arr = np.full(shapeList[1], min_i32) + + # Find how much values exceed the maximum/minimums + sat_max_arr = np.maximum(res_arr - max_arr, 0) + sat_min_arr = np.minimum(res_arr - min_arr, 0) + + if not add: + # Swap saturation values and negate values as we need to perform opposite operations + sat_max_arr, sat_min_arr = -sat_min_arr, -sat_max_arr + + # Create new array of unsaturated values by clipping values as needed + b_unsat_arr = b_arr + if (sat_max_arr != 0).any(): + # Clip values that cause saturation + b_unsat_arr = np.subtract(b_unsat_arr, sat_max_arr, dtype=np.int32) + # Reduce axes in unsaturated tensor to match original tensor + for axis, dim in enumerate(b_arr.shape): + if dim != b_unsat_arr.shape[axis]: + assert ( + dim == 1 + ), "Op.ADD / SUB dimension must be 1 or matching to be broadcastable" + b_unsat_arr = np.amin(b_unsat_arr, axis=axis, keepdims=True) + + if (sat_min_arr != 0).any(): + # Clip values that cause saturation + b_unsat_arr = np.subtract(b_unsat_arr, sat_min_arr, dtype=np.int32) + # Reduce axes in unsaturated tensor to match original tensor + for axis, dim in enumerate(b_arr.shape): + if dim != b_unsat_arr.shape[axis]: + assert ( + dim == 1 + ), "Op.ADD / SUB dimension must be 1 or matching to be broadcastable" + b_unsat_arr = np.amax(b_unsat_arr, axis=axis, keepdims=True) + + placeholders.append( + testGen.ser.addPlaceholder(shapeList[0], dtypeList[0], a_arr) + ) + placeholders.append( + testGen.ser.addPlaceholder(shapeList[1], dtypeList[1], b_unsat_arr) + ) + + return placeholders + else: + return TosaTensorValuesGen.tvgDefault( + testGen, op, dtypeList, shapeList, testArgs, qinfo, error_name + ) + + @staticmethod + def tvgCondIfWhileLoop( + testGen, op, dtypeList, shapeList, testArgs, qinfo, error_name=None + ): + if dtypeList[0] in ( + DType.INT32, + DType.INT16, + DType.INT8, + ): + # Limit input tensors with cond_if_binary or while_loop to stop + # saturation of add/sub ops with int32 and keep all logical shift + # values between 0 to 31 for int16 or int8 + pCount, cCount = op["operands"] + pRemain = pCount + placeholders = [] + for idx, shape in enumerate(shapeList[:]): + if dtypeList[0] == DType.INT32: + arr = testGen.getRandTensor(shapeList[idx], DType.INT16) + else: + arr = np.int32( + testGen.rng.integers(low=0, high=32, size=shapeList[idx]) + ) + if pRemain > 0: + placeholders.append( + testGen.ser.addPlaceholder(shape, dtypeList[idx], arr) + ) + pRemain -= 1 + else: + placeholders.append( + testGen.ser.addConst(shape, dtypeList[idx], arr) + ) + + return placeholders + else: + return TosaTensorValuesGen.tvgDefault( + testGen, op, dtypeList, shapeList, testArgs, qinfo, error_name + ) + + @staticmethod + def tvgArithmeticRightShift( + testGen, op, dtypeList, shapeList, testArgs, qinfo, error_name=None + ): + pCount, cCount = op["operands"] + # Force value of operand[1] to be within [0, num_bits] + assert ( + pCount == 2 and cCount == 0 + ), "Op.ArithmeticRightShift must have 2 placeholders, 0 consts" + + placeholders = [] + for idx, shape in enumerate(shapeList[:]): + if idx == 1: + if dtypeList[idx] == DType.INT8: + arr = np.int32(testGen.rng.integers(low=0, high=8, size=shape)) + elif dtypeList[idx] == DType.INT16: + arr = np.int32(testGen.rng.integers(low=0, high=16, size=shape)) + elif dtypeList[idx] == DType.INT32: + arr = np.int32(testGen.rng.integers(low=0, high=32, size=shape)) + elif error_name == ErrorIf.WrongInputType: + arr = np.int32(testGen.rng.integers(low=0, high=8, size=shape)) + else: + raise Exception("OpArithmeticRightShift: invalid input dtype") + else: + arr = testGen.getRandTensor(shape, dtypeList[idx]) + placeholders.append(testGen.ser.addPlaceholder(shape, dtypeList[idx], arr)) + + return placeholders + + @staticmethod + def tvgSelect(testGen, op, dtypeList, shapeList, testArgs, qinfo, error_name=None): + # Set datatype of condition tensor to boolean + dtypeList[0] = DType.BOOL + + return TosaTensorValuesGen.tvgDefault( + testGen, op, dtypeList, shapeList, testArgs, qinfo, error_name + ) + + @staticmethod + def tvgIntDiv(testGen, op, dtypeList, shapeList, testArgs, qinfo, error_name=None): + if error_name is None: + pCount, cCount = op["operands"] + assert ( + pCount == 2 and cCount == 0 + ), "Op.INTDIV must have 2 placeholders, 0 consts" + + placeholders = [] + + # Two invalid cases for Op.INTDIV: + # 1. divisor == 0 + # 2. dividend == -(1<<31) and divisor == -1 + while True: + dividend_arr = testGen.getRandTensor(shapeList[0], dtypeList[0]) + divisor_arr = testGen.getRandTensor(shapeList[1], dtypeList[1]) + + if (divisor_arr == 0).any(): + continue + + if (dividend_arr == -(2**31)).any() and (divisor_arr == -1).any(): + continue + + break + + placeholders.append( + testGen.ser.addPlaceholder(shapeList[0], dtypeList[0], dividend_arr) + ) + placeholders.append( + testGen.ser.addPlaceholder(shapeList[1], dtypeList[1], divisor_arr) + ) + + return placeholders + else: + return TosaTensorValuesGen.tvgDefault( + testGen, op, dtypeList, shapeList, testArgs, qinfo, error_name + ) + + @staticmethod + def tvgMul(testGen, op, dtypeList, shapeList, testArgs, qinfo, error_name=None): + if error_name is None: + pCount, cCount = op["operands"] + assert ( + pCount == 2 and cCount == 0 + ), "Op.MUL must have 2 placeholders, 0 consts" + + tens = [] + if dtypeList[0] == DType.FLOAT: + tens.extend(testGen.buildPlaceholderTensors(shapeList[:], dtypeList[:])) + else: + placeholders = [] + + # Make sure multiply result in int32 range + shift = testArgs[0] + if dtypeList[0] == DType.INT8: + num_bits = 8 + elif dtypeList[0] == DType.INT16: + num_bits = 16 + elif dtypeList[0] == DType.INT32: + num_bits = 32 + elif error_name == ErrorIf.WrongInputType: + num_bits = 8 + else: + raise Exception("OpMul: invalid input dtype") + + for idx, shape in enumerate(shapeList[:]): + low = -(2 ** (num_bits - 1)) + high = (2 ** (num_bits - 1)) - 1 + + a_arr = np.int32( + testGen.rng.integers(low=low, high=high, size=shapeList[0]) + ) + b_arr = np.int32( + testGen.rng.integers(low=low, high=high, size=shapeList[1]) + ) + + i = 0 + while True: + + a_arr_64 = a_arr.astype(np.int64) + b_arr_64 = b_arr.astype(np.int64) + + if shift > 0: + rounding = 1 << (shift - 1) + result_arr = ((a_arr_64 * b_arr_64) + rounding) >> shift + else: + result_arr = a_arr_64 * b_arr_64 + + if (result_arr > -(2**31)).all() and ( + result_arr <= ((2**31) - 1) + ).all(): + break + + i = i + 1 + a_arr = a_arr // 2 + b_arr = b_arr // 2 + + placeholders.append( + testGen.ser.addPlaceholder(shapeList[0], dtypeList[0], a_arr) + ) + placeholders.append( + testGen.ser.addPlaceholder(shapeList[1], dtypeList[1], b_arr) + ) + + tens.extend(placeholders) + + return tens + else: + return TosaTensorValuesGen.tvgDefault( + testGen, op, dtypeList, shapeList, testArgs, qinfo, error_name + ) + + @staticmethod + def tvgConcat(testGen, op, dtypeList, shapeList, testArgs, qinfo, error_name=None): + count = len(shapeList) - testGen.args.num_const_inputs_concat + if count < 1: + count = 1 + if testGen.args.num_const_inputs_concat == 0: + count = len(shapeList) + + # Ensure axis is an int + testArgs[0] = int(testArgs[0]) + + shapeList = TosaTensorGen.tgConcatConstInput( + testGen, shapeList, testArgs[0], error_name + ) + + tens = [] + tens.extend( + testGen.buildPlaceholderTensors(shapeList[0:count], dtypeList[0:count]) + ) + tens.extend(testGen.buildConstTensors(shapeList[count:], dtypeList[count:])) + + return tens + + @staticmethod + def tvgLogicalShift( + testGen, op, dtypeList, shapeList, testArgs, qinfo, error_name=None + ): + pCount, cCount = op["operands"] + assert ( + pCount == 2 and cCount == 0 + ), "Op.LOGICAL_LEFT_SHIFT or Op.LOGICAL_RIGHT_SHIFT must have 2 placeholders, 0 consts" + values_arr = testGen.getRandTensor(shapeList[0], dtypeList[0]) + shift_arr = np.int32(testGen.rng.integers(low=0, high=32, size=shapeList[1])) + placeholders = [] + placeholders.append( + testGen.ser.addPlaceholder(shapeList[0], dtypeList[0], values_arr) + ) + placeholders.append( + testGen.ser.addPlaceholder(shapeList[1], dtypeList[1], shift_arr) + ) + + return placeholders + + @staticmethod + def tvgEqual(testGen, op, dtypeList, shapeList, testArgs, qinfo, error_name=None): + if error_name is None: + pCount, cCount = op["operands"] + assert ( + pCount == 2 and cCount == 0 + ), "Op.EQUAL must have 2 placeholders, 0 consts" + a_arr = testGen.getRandTensor(shapeList[0], dtypeList[0]) + b_arr = testGen.getRandTensor(shapeList[1], dtypeList[1]) + # Using random numbers means that it will be very unlikely that + # there are any matching (equal) values, therefore force that + # there are twice the number of matching values as the tensor rank + for num in range(0, len(shapeList[0]) * 2): + a_index = [] + b_index = [] + # Choose an index in each axis for the whole shape + for axis in range(0, len(shapeList[0])): + # Index can be up to the largest dimension in both shapes + index = np.int32( + testGen.rng.integers( + 0, max(shapeList[0][axis], shapeList[1][axis]) + ) + ) + # Reduce the index down to a shape's dim for broadcasting + a_index.append(min(shapeList[0][axis] - 1, index)) + b_index.append(min(shapeList[1][axis] - 1, index)) + + a_arr[tuple(a_index)] = b_arr[tuple(b_index)] + + placeholders = [] + placeholders.append( + testGen.ser.addPlaceholder(shapeList[0], dtypeList[0], a_arr) + ) + placeholders.append( + testGen.ser.addPlaceholder(shapeList[1], dtypeList[1], b_arr) + ) + return placeholders + else: + return TosaTensorValuesGen.tvgDefault( + testGen, op, dtypeList, shapeList, testArgs, qinfo, error_name + ) + + @staticmethod + def tvgReduceSum( + testGen, op, dtypeList, shapeList, testArgs, qinfo, error_name=None + ): + if dtypeList[0] == DType.INT32: + pCount, cCount = op["operands"] + assert ( + pCount == 1 and cCount == 0 + ), "Op.REDUCE_SUM must have 1 placeholders, 0 consts" + # Limit values so that the sum cannot exceed the range of an int32 during + # summation of any axis + range_val = int((1 << 31) / max(shapeList[0])) + values_arr = np.int32( + testGen.rng.integers(low=-range_val, high=range_val, size=shapeList[0]) + ) + placeholders = [] + placeholders.append( + testGen.ser.addPlaceholder(shapeList[0], dtypeList[0], values_arr) + ) + return placeholders + else: + return TosaTensorValuesGen.tvgDefault( + testGen, op, dtypeList, shapeList, testArgs, qinfo, error_name + ) + + +class TosaArgGen: + """Argument generators create exhaustive or random lists of attributes for + operators that take attributes or other parameters. + + The return value is a list of (descriptive_name, [arglist]) tuples where + the descriptive_name is appended to the test name and the arglist is expanded + as arguments to the operator build function. + """ + + def __init__(self): + pass + + @staticmethod + def agNone(testGen, opName, shapeList, dtype, error_name=None): + """A trivial argument generator for operators that don't take any + non-tensor arguments""" + return [("", [])] + + @staticmethod + def agAxis(testGen, opName, shapeList, dtype, error_name=None): + """Build the axis argument for operators that take a single axis""" + axes = [] + shape = shapeList[0] + + if error_name == ErrorIf.AxisSmallerZero: + small_axis = testGen.rng.integers(-5, 0) + axes.append(("axis{}".format(small_axis), [small_axis])) + elif error_name == ErrorIf.AxisLargerRank: + large_axis = testGen.rng.integers(len(shape) + 1, len(shape) + 10) + axes.append(("axis{}".format(large_axis), [large_axis])) + else: + for a in range(0, len(shape)): + axes.append(("axis{}".format(a), [a])) + + return axes + + @staticmethod + def agConv(testGen, opName, shapeList, dtype, error_name=None): + arg_list = [] + + ifm_shape = shapeList[0] + filter_shape = shapeList[1] + # determine the kernel shape from operator name (e.g. "conv2d_3x3" => [3,3]) + k = [int(x) for x in opName.split("_")[-1].split("x")] + + # Check the rank + rank = 5 if opName.startswith("conv3d") else 4 + if error_name != ErrorIf.WrongRank: + assert len(ifm_shape) == rank + assert len(filter_shape) == rank + + # kernel rank omits batch and channels + k_rank = rank - 2 + assert len(k) == k_rank + + # Generate comprehensive argument lists + # - except for named errors, which use specific invalid value(s) + if error_name == ErrorIf.PadSmallerZero: + p_vals = [testGen.rng.choice(range(-5, 0))] + else: + p_vals = [x for x in range(0, testGen.args.max_conv_padding + 1)] + paddings = {x for x in itertools.product(*([p_vals] * k_rank * 2))} + if error_name == ErrorIf.StrideSmallerOne: + # Can't use stride=0, as it is used to derive output shape, as a divisor + s_vals = [testGen.rng.choice(range(-5, 0))] + else: + s_vals = [x for x in range(1, testGen.args.max_conv_stride + 1)] + strides = {x for x in itertools.product(*([s_vals] * k_rank))} + if error_name == ErrorIf.DilationSmallerOne: + d_vals = [testGen.rng.choice(range(-5, 1))] + else: + d_vals = [x for x in range(1, testGen.args.max_conv_dilation + 1)] + dilations = {x for x in itertools.product(*([d_vals] * k_rank))} + + if not error_name and testGen.args.oversize: + # add some oversize argument values + if max(ifm_shape) < 64: + bigPadding = 9 + paddings.update( + {x for x in itertools.product(*([[0, bigPadding]] * (k_rank * 2)))} + ) + bigStride = 8 + strides.update({x for x in itertools.product(*([[1, bigStride]] * k_rank))}) + bigDilation = 7 + dilations.update( + {x for x in itertools.product(*([[1, bigDilation]] * k_rank))} + ) + + # There are too many parameter combinations, so generate them sparsely, + # very sparse for negative tests + sparsity_factor = 2 if error_name else 100 + sparsity = len(paddings) * len(strides) * len(dilations) // sparsity_factor + 1 + # If there are only a small number of tests, just select them all + if sparsity < 13: + sparsity = 1 + # To get a variety of parameter combinations sparsity should not be a + # multiple of 2, 3 or 5 + while sparsity % 2 == 0 or sparsity % 3 == 0 or sparsity % 5 == 0: + sparsity += 1 + + n = 0 + for s in sorted(list(strides)): + for p in sorted(list(paddings)): + for d in sorted(list(dilations)): + if ( + n % sparsity == 0 + # padding must not exceed the kernel size ? + # and p[0] < k[0] and p[1] < k[0] + # and p[2] < k[1] and p[3] < k[1] + # and (k_rank < 3 or (p[4] < k[2] and p[5] < k[2])) + # the padded shape must exceed the kernel size + and (ifm_shape[1] + p[0] + p[1]) > k[0] + and (ifm_shape[2] + p[2] + p[3]) > k[1] + and (k_rank < 3 or ((ifm_shape[3] + p[4] + p[5]) > k[2])) + # the padded shape must exceed the dilation + and (ifm_shape[1] + p[0] + p[1]) > d[0] + and (ifm_shape[2] + p[2] + p[3]) > d[1] + and (k_rank < 3 or ((ifm_shape[3] + p[4] + p[5]) > d[2])) + ): + arg_list.append( + ( + "st{}_pad{}_dilat{}".format( + "".join([str(x) for x in s]), + "".join([str(x) for x in p]), + "".join([str(x) for x in d]), + ), + [s, p, d], + ) + ) + n += 1 + + return arg_list + + @staticmethod + def agTransposeConv2D(testGen, opName, shapeList, dtype, error_name=None): + arg_list = [] + + ifm_shape = shapeList[0] + filter_shape = shapeList[1] + + # Must be rank 4 + if error_name != ErrorIf.WrongRank: + assert len(ifm_shape) == 4 + assert len(filter_shape) == 4 + + # Generate comprehensive argument lists + # - except for named errors, which use specific invalid value(s) + if error_name == ErrorIf.PadSmallerZero: + p_vals = [testGen.rng.choice(range(-5, 0))] + else: + p_vals = [x for x in range(0, testGen.args.max_conv_padding + 1)] + paddings = {x for x in itertools.product(*([p_vals] * 2))} + if error_name == ErrorIf.StrideSmallerOne: + # Can't use stride=0, as it is used to derive output shape, as a divisor + s_vals = [testGen.rng.choice(range(-5, 0))] + else: + s_vals = [x for x in range(1, testGen.args.max_conv_stride + 1)] + strides = {x for x in itertools.product(*([s_vals] * 2))} + if error_name == ErrorIf.DilationSmallerOne: + d_vals = [testGen.rng.choice(range(-5, 1))] + else: + d_vals = [x for x in range(1, testGen.args.max_conv_dilation + 1)] + dilations = {x for x in itertools.product(*([d_vals] * 2))} + + if not error_name: + # add some oversize argument values + if max(ifm_shape) < 64: + bigPadding = 9 + paddings.update( + {x for x in itertools.product(*([[0, bigPadding]] * 2))} + ) + bigStride = 8 + strides.update({x for x in itertools.product(*([[1, bigStride]] * 2))}) + bigDilation = 7 + dilations.update({x for x in itertools.product(*([[1, bigDilation]] * 2))}) + + # There are too many parameter combinations, so generate them sparsely, + # very sparse for negative tests + sparsity_factor = 2 if error_name else 100 + sparsity = len(paddings) * len(strides) * len(dilations) // sparsity_factor + 1 + # If there are only a small number of tests, just select them all + if sparsity < 13: + sparsity = 1 + # To get a variety of parameter combinations sparsity should not be a + # multiple of 2, 3 or 5 + while sparsity % 2 == 0 or sparsity % 3 == 0 or sparsity % 5 == 0: + sparsity += 1 + + n = 0 + for s in sorted(list(strides)): + for p in sorted(list(paddings)): + for d in sorted(list(dilations)): + if n % sparsity == 0: + # Determine the output shape + oh = ( + ifm_shape[1] + - filter_shape[1] + - (filter_shape[1] - 1) * (d[0] - 1) + + 2 * p[0] + ) // s[0] + 1 + ow = ( + ifm_shape[2] + - filter_shape[2] + - (filter_shape[2] - 1) * (d[1] - 1) + + 2 * p[1] + ) // s[1] + 1 + os = [ifm_shape[0], oh, ow, filter_shape[0]] + arg_list.append( + ( + "st{}_pad{}_dilat{}_os{}".format( + "".join([str(x) for x in s]), + "".join([str(x) for x in p]), + "".join([str(x) for x in d]), + "x".join([str(x) for x in os]), + ), + [s, p, d, os], + ) + ) + n += 1 + + return arg_list + + @staticmethod + def agPad(testGen, opName, shapeList, dtype, error_name=None): + arg_list = [] + rank = len(shapeList[0]) + + # Exhaustively test combinations of padding on each side of each dimension + # - the range of padding values is defined by pad_min and pad_max + # - for padding >9, the name format needs to be more distinctive + pad_min, pad_max = 0, 1 + pad_values = [x for x in range(pad_min, pad_max + 1)] + if error_name == ErrorIf.PadSmallerZero: + pad_values = [x for x in range(-2, 0)] + axis_pad_values = [x for x in itertools.product(pad_values, pad_values)] + shape_pad_values = itertools.product(*([axis_pad_values] * rank)) + + if dtype in [DType.BOOL, DType.INT8, DType.INT16, DType.INT32]: + pad_const_int = testGen.getRandNumberDType(dtype) + pad_const_fp = 0 + elif dtype == DType.FLOAT: + pad_const_int = 0 + pad_const_fp = testGen.getRandNumberDType(dtype) + else: + return [] + + for paddings in shape_pad_values: + name = "pad" + for r in range(rank): + before, after = paddings[r] + name = f"{name}{before}{after}" + arg_list.append((name, [np.array(paddings), pad_const_int, pad_const_fp])) + + return arg_list + + @staticmethod + def agPooling(testGen, opName, shapeList, dtype, error_name=None): + arg_list = [] + + shape = shapeList[0] + if error_name != ErrorIf.WrongRank: + assert len(shape) == 4 + + # Generate comprehensive argument lists + p_vals = [x for x in range(0, testGen.args.max_pooling_padding + 1)] + paddings = {x for x in itertools.product(*([p_vals] * 4))} + s_vals = [x for x in range(1, testGen.args.max_pooling_stride + 1)] + strides = {x for x in itertools.product(*([s_vals] * 2))} + k_vals = [x for x in range(2, testGen.args.max_pooling_kernel + 1)] + kernels = {x for x in itertools.product(*([k_vals] * 2))} + + if testGen.args.oversize: + # add some oversize argument values + bigStride = 7 + strides.update({x for x in itertools.product(*([[1, bigStride]] * 2))}) + bigKernel = 6 + kernels.update({x for x in itertools.product(*([[2, bigKernel]] * 2))}) + if max(shape) < 64: + # padding must be less than the kernel size + bigPadding = bigKernel - 1 + paddings.update( + {x for x in itertools.product(*([[0, bigPadding]] * 4))} + ) + + # There are too many parameter combinations, so generate them sparsely, + # very sparse for negative tests + sparsity_factor = 2 if error_name else 500 + sparsity = len(paddings) * len(strides) * len(kernels) // sparsity_factor + 1 + + n = 0 + for s in sorted(list(strides)): + for p in sorted(list(paddings)): + for k in sorted(list(kernels)): + if error_name in [ + ErrorIf.StrideSmallerOne, + ErrorIf.KernelSmallerOne, + ErrorIf.PadSmallerZero, + ErrorIf.PadLargerEqualKernel, + ]: + sNew, pNew, kNew = TosaErrorIfArgGen.eiPoolingErrorIf( + testGen, error_name, s, p, k + ) + if None not in [sNew, pNew, kNew] and n % sparsity == 0: + arg_list.append( + ( + "st{}_kern{}_pad{}".format( + "".join([str(x) for x in sNew]), + "".join([str(x) for x in kNew]), + "".join([str(x) for x in pNew]), + ), + [sNew, pNew, kNew], + ) + ) + elif ( + n % sparsity == 0 + # padding must not exceed the kernel size + and p[0] < k[0] + and p[1] < k[0] + and p[2] < k[1] + and p[3] < k[1] + # the padded shape must exceed the kernel size + and (shape[1] + p[0] + p[1]) > k[0] + and (shape[2] + p[2] + p[3]) > k[1] + ): + arg_list.append( + ( + "st{}_kern{}_pad{}".format( + "".join([str(x) for x in s]), + "".join([str(x) for x in k]), + "".join([str(x) for x in p]), + ), + [s, p, k], + ) + ) + n += 1 + + return arg_list + + @staticmethod + def agCast(testGen, opName, shapeList, inDtype, error_name=None): + arg_list = [] + + # Enumerate the output types here + if error_name == ErrorIf.WrongOutputType: + dtypeList = TosaErrorIfArgGen.eiCastErrorIf(testGen, inDtype) + elif inDtype == DType.INT8: + dtypeList = [DType.BOOL, DType.INT16, DType.INT32, DType.FLOAT] + elif inDtype == DType.INT16: + dtypeList = [DType.BOOL, DType.INT8, DType.INT32, DType.FLOAT] + elif inDtype == DType.INT32: + dtypeList = [DType.BOOL, DType.INT8, DType.INT16, DType.FLOAT] + elif inDtype == DType.BOOL: + dtypeList = [DType.INT8, DType.INT16, DType.INT32] + elif inDtype == DType.FLOAT: + dtypeList = [DType.INT8, DType.INT16, DType.INT32] + elif error_name == ErrorIf.WrongInputType: + # Pick some potentially correct output type for incorrect input type + dtypeList = [DType.BOOL, DType.INT8, DType.INT16, DType.FLOAT] + else: + raise Exception("Unexpected input dtype: {}".format(inDtype)) + + for dtype in dtypeList: + arg_list.append(("out{}".format(DTypeNames[dtype]), [dtype])) + + return arg_list + + @staticmethod + def agRescale(testGen, opName, shapeList, inDtype, error_name=None): + arg_list = [] + + # Enumerate the output types here + for dtype in [DType.UINT8, DType.INT8, DType.INT16, DType.INT32]: + if ( + dtype in [DType.UINT8, DType.INT8] + and error_name == ErrorIf.OutputZeroPointNotZero + ): + continue + if ( + inDtype == DType.UINT8 + and dtype != DType.INT8 + and error_name != ErrorIf.WrongOutputType + ): + # The only output dtype for UINT8 is INT8, skip all other combinations + continue + if ( + inDtype != DType.INT8 + and dtype == DType.UINT8 + and error_name != ErrorIf.WrongOutputType + ): + # The only input dtype for UINT8 is INT8, skip all other combinations + continue + if ( + error_name == ErrorIf.WrongOutputType + and not TosaErrorIfArgGen.eiRescaleWrongOutputType(inDtype, dtype) + ): + continue + + for scale32 in [False, True]: + if error_name == ErrorIf.ScaleTrue and not scale32: + continue + elif error_name == ErrorIf.ScaleNotTrue and scale32: + continue + for double_round in [False, True]: + if error_name == ErrorIf.ScaleNotTrue and not double_round: + continue + for per_channel in [False, True]: + + if ( + inDtype == DType.INT48 + and scale32 + and error_name != ErrorIf.ScaleTrue + ): + # Illegal condition. Must be scale32=False + continue + if ( + double_round + and not scale32 + and error_name != ErrorIf.ScaleNotTrue + ): + # Illegal condition. ERROR_IF(!scale32 && double_round) + continue + + arg_list.append( + ( + "out{}_sc{}_dr{}_pc{}".format( + DTypeNames[dtype], + int(scale32), + int(double_round), + int(per_channel), + ), + [dtype, scale32, double_round, per_channel], + ) + ) + + return arg_list + + @staticmethod + def agMul(testGen, opName, shapeList, dtype, error_name=None): + arg_list = [] + + if dtype is DType.INT32: + for p in range(testGen.args.num_rand_permutations): + + shift = testGen.randInt(0, 32) + + arg_list.append(("perm{}_shift{}".format(p, shift), [shift])) + else: + arg_list.append(("perm0_shift0", [0])) + + return arg_list + + @staticmethod + def agArithmeticRightShift(testGen, opName, shapeList, dtype, error_name=None): + arg_list = [] + + arg_list.append(("roundTrue", [True])) + arg_list.append(("roundFalse", [False])) + + return arg_list + + # Helper function for reshape. Gets some factors of a larger number. + @staticmethod + def getFactors(val, start=1): + factors = [] + + for i in range(start, int(np.sqrt(val)) + 1): + if (val % i) == 0: + factors.append(i) + + return factors + + @staticmethod + def agReshape(testGen, opName, shapeList, dtype, error_name=None): + arg_list = [] + + origShape = shapeList[0] + + totalElements = 1 + for s in origShape: + totalElements *= s + + # This code is NOT fast. Fortunately, the numbers are fairly small. + factors = TosaArgGen.getFactors(totalElements) + + for p in range(testGen.args.num_rand_permutations): + newRank = testGen.randInt(1, 7) + if len(factors) < newRank: + continue + + found = True + # escape_counter breaks while loop if it continues on for too long + escape_counter = 0 + while found: + newShape = [] + # Generate newShape ensuring it isn't a duplicate + remainingElements = totalElements + shuffledFactors = testGen.rng.permutation(factors) + for i in range(1, newRank): + # pick rank-1 factors + newShape.append(shuffledFactors[0]) + remainingElements = remainingElements // shuffledFactors[0] + shuffledFactors = testGen.rng.permutation( + TosaArgGen.getFactors(remainingElements) + ) + newShape.append(remainingElements) + + # Toss in a -1 sometimes + minusOne = testGen.randInt(0, newRank * 4) + if minusOne < newRank: + newShape[minusOne] = -1 + + # Check for duplicates + found = False + for name, other_shape in arg_list: + if other_shape[0] == newShape: + found = True + break + + escape_counter += 1 + if escape_counter >= 100: + break + + if not found: + arg_list.append(("perm{}_rank{}".format(p, newRank), [newShape])) + + return arg_list + + @staticmethod + def agTranspose(testGen, opName, shapeList, dtype, error_name=None): + arg_list = [] + + ifm_shape = shapeList[0] + + if error_name == ErrorIf.IndexOutsideBounds: + incorrect_large_index = range(len(ifm_shape) + 1, 2 * len(ifm_shape) + 1) + incorrect_small_index = range(-len(ifm_shape), 0) + permutations = [p for p in itertools.permutations(incorrect_large_index)] + permutations.extend( + [p for p in itertools.permutations(incorrect_small_index)] + ) + elif error_name == ErrorIf.IndexUsedTwice: + # Create list with a duplicated index + perm_range = list(range(len(ifm_shape))) + index_choice = testGen.rng.choice(range(len(perm_range))) + perm_range[(index_choice + 1) % len(perm_range)] = perm_range[index_choice] + permutations = [p for p in itertools.permutations(perm_range)] + + else: + # Get all permutations + permutations = [p for p in itertools.permutations(range(len(ifm_shape)))] + + # Limit to possible permutations from shape dimension or argument setting + limit = min(len(permutations), testGen.args.num_rand_permutations) + + # Get random permutation generator that uses all permutations + random_permutations = testGen.rng.permutation(permutations) + + # Create list of required amount of permutations + arg_list = [ + ("perm{}".format(p), [random_permutations[p].tolist()]) + for p in range(limit) + ] + return arg_list + + @staticmethod + def agSlice(testGen, opName, shapeList, dtype, error_name=None): + arg_list = [] + + ifm_shape = shapeList[0] + rank = len(ifm_shape) + + for p in range(testGen.args.num_rand_permutations): + start = [] + size = [] + + valid = True + + for i in range(rank): + if ifm_shape[i] > 1: + start.append(testGen.randInt(0, ifm_shape[i])) + size.append(testGen.randInt(0, ifm_shape[i] - start[i])) + + # Invalid slice size? + if size[i] == 0: + valid = False + else: + start.append(0) + size.append(1) + + if valid: + # If ERROR_IF test required then incorrect start, size will be returned + start, size = TosaErrorIfArgGen.eiSliceErrorIf( + testGen, error_name, ifm_shape, start, size + ) + arg_list.append(("perm{}".format(p), [start, size])) + return arg_list + + @staticmethod + def agTile(testGen, opName, shapeList, dtype, error_name=None): + arg_list = [] + + ifm_shape = shapeList[0] + rank = len(ifm_shape) + + for p in range(testGen.args.num_rand_permutations): + + # Pick a few random, but small multiple values + # because otherwise this has a tendency to generate + # enormous tensors + multiples = [] + for i in range(rank): + if ifm_shape[i] > 1000: + # Multiple of 1 if ifm_shape dimension is large to reduce + # tensor size + multiples.append(1) + elif max(ifm_shape) > 1000: + multiples.append(2) + else: + multiples.append(testGen.randInt(1, 4)) + arg_list.append(("perm{}".format(p), [multiples])) + + return arg_list + + @staticmethod + def agResize(testGen, opName, shapeList, dtype, error_name=None): + arg_list = [] + + ifm_shape = shapeList[0] + 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] + elif mode == ResizeMode.NEAREST and dtype == DType.INT16: + outputDTypeList = [DType.INT16] + elif mode == ResizeMode.BILINEAR and dtype == DType.INT8: + outputDTypeList = [DType.INT32] + elif mode == ResizeMode.BILINEAR and dtype == DType.INT16: + outputDTypeList = [DType.INT48] + elif dtype == DType.FLOAT: + outputDTypeList = [DType.FLOAT] + elif error_name == ErrorIf.WrongInputType: + # If an incorrect input type is used then we set a 'correct' + # output type to avoid other errors + outputDTypeList = [DType.INT8, DType.INT16, DType.INT32] + else: + continue + + 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}" + ) + shift = 0 + stride = [0, 0] + offset = [0, 0] + stride_fp = [fp_stride_y, fp_stride_x] + offset_fp = [fp_offset_y, fp_offset_x] + + 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 + continue + + stride = [stride_y, stride_x] + offset = [offset_y, offset_x] + + stride_fp = [0.0, 0.0] + offset_fp = [0.0, 0.0] + + # Common for all data types + if error_name is not None: + ( + shift, + stride, + stride_fp, + offset, + offset_fp, + outputDTypeNew, + ) = TosaErrorIfArgGen.eiResizeErrorIf( + testGen, + error_name, + mode, + dtype, + shapeList, + outputDType, + shift, + stride, + stride_fp, + offset, + offset_fp, + ) + 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, + ], + ) + ) + + return arg_list + + @staticmethod + def agTable(testGen, opName, shapeList, dtype, error_name=None): + arg_list = [] + + if dtype == DType.INT8: + table = np.int32( + testGen.rng.integers(low=-128, high=128, size=[256]) + ).tolist() + else: # INT16 + table = np.int32( + testGen.rng.integers(low=-32768, high=32768, size=[513]) + ).tolist() + + arg_list.append( + ( + "", + [table], + ) + ) + return arg_list + + def agCondIf(testGen, opName, shapeList, dtype, error_name=None): + # CondIf generates the condition values here. + # Convert to tensors in the build function, along with the + # then and else blocks + arg_list = [] + + for c in [False, True]: + arg_list.append(("cond{}".format(int(c)), [c])) + + return arg_list + + def agWhileLoop(testGen, opName, shapeList, dtype, error_name=None): + # While loop: 0 iterations, 1, more than 1 + arg_list = [] + + for iter in [0, 1, 4]: + arg_list.append(("iter{}".format(iter), [iter])) + + return arg_list |