From 4a6fb9bdb4188b53516f7df0fbaa3695cce76319 Mon Sep 17 00:00:00 2001 From: Jeremy Johnson Date: Tue, 26 Apr 2022 15:47:21 +0100 Subject: Update tensor ops ERROR_IF criteria Update to ref model to check ERROR_IF criteria for pooling and convolution ops to match specification Update to tosa_verif_build_tests to produce valid test ranges and new ERROR_IF tests Plus update pooling ops big kernel to 9 (from 6) for better testing coverage and set dilation to 1 and add out_pad bottom & right for transpose_conv2d to match specification Signed-off-by: Jeremy Johnson Change-Id: Ic5759872d40ae8d3f3d07043d9a0f2fa0244d72e --- reference_model/src/ops/tensor_ops.cc | 145 +++++++++++++++++++++---- verif/generator/tosa_arg_gen.py | 113 ++++++++++++-------- verif/generator/tosa_error_if.py | 196 +++++++++++++++++++++++++++++++--- verif/generator/tosa_test_gen.py | 114 +++++++++++++------- 4 files changed, 446 insertions(+), 122 deletions(-) diff --git a/reference_model/src/ops/tensor_ops.cc b/reference_model/src/ops/tensor_ops.cc index 02fdf01..4a56093 100644 --- a/reference_model/src/ops/tensor_ops.cc +++ b/reference_model/src/ops/tensor_ops.cc @@ -92,10 +92,22 @@ int check_pool2d_attribute(tosa::TosaPoolAttribute* attribute, return 1; } - if ((OH != (IH + pad_top + pad_bottom + stride_y - kernel_y) / stride_y) || - (OW != (IW + pad_left + pad_right + stride_x - kernel_x) / stride_x)) + int32_t full_H = IH + pad_top + pad_bottom - kernel_y; + int32_t full_W = IW + pad_left + pad_right - kernel_x; + + if ((full_H % stride_y != 0) || + (full_W % stride_x != 0)) { - msg = "Mismatch between output shape provided and expected output shape"; + msg = "Parameters must yield exact integer output dimensions"; + return 1; + } + + if ((OH != (full_H / stride_y) + 1) || + (OW != (full_W / stride_x) + 1)) + { + msg = "Mismatch between output shape provided and expected output shape (" + + std::to_string((full_H / stride_y) + 1) + "," + + std::to_string((full_W / stride_x) + 1) + ")"; return 1; } @@ -107,6 +119,8 @@ int check_conv_attribute_qinfo(tosa::TosaConvAttribute* attribute, uint32_t conv_dimension, std::vector input_shape, std::vector output_shape, + std::vector weights, + uint32_t offset_kernel, DType InDtype, DType WeightDtype, std::string& msg) @@ -156,6 +170,62 @@ int check_conv_attribute_qinfo(tosa::TosaConvAttribute* attribute, } } + ASSERT_MSG(conv_dimension == 2 || conv_dimension == 3, "Unsupported convolution dimension") + + int32_t offset_d = 1 ? conv_dimension == 3 : 0; + int32_t ID = conv_dimension == 3 ? input_shape[1] : 1; + int32_t IH = input_shape[1 + offset_d]; + int32_t IW = input_shape[2 + offset_d]; + int32_t OD = conv_dimension == 3 ? output_shape[1] : 1; + int32_t OH = output_shape[1 + offset_d]; + int32_t OW = output_shape[2 + offset_d]; + + int32_t stride_d = conv_dimension == 3 ? attribute->stride()[0] : 1; + int32_t stride_y = attribute->stride()[0 + offset_d]; + int32_t stride_x = attribute->stride()[1 + offset_d]; + int32_t kernel_d = conv_dimension == 3 ? weights[offset_kernel] : 1; + int32_t kernel_h = weights[offset_kernel + offset_d]; + int32_t kernel_w = weights[offset_kernel + 1 + offset_d]; + int32_t dilation_d = conv_dimension == 3 ? attribute->dilation()[0] : 1; + int32_t dilation_y = attribute->dilation()[0 + offset_d]; + int32_t dilation_x = attribute->dilation()[1 + offset_d]; + + offset_d *= 2; + int32_t pad_d0 = conv_dimension == 3 ? attribute->padding()[0] : 0; + int32_t pad_d1 = conv_dimension == 3 ? attribute->padding()[1] : 0; + int32_t pad_top = attribute->padding()[0 + offset_d]; + int32_t pad_bottom = attribute->padding()[1 + offset_d]; + int32_t pad_left = attribute->padding()[2 + offset_d]; + int32_t pad_right = attribute->padding()[3 + offset_d]; + + int32_t full_D = ID - 1 + pad_d0 + pad_d1 - (kernel_d - 1) * dilation_d; + int32_t full_H = IH - 1 + pad_top + pad_bottom - (kernel_h - 1) * dilation_y; + int32_t full_W = IW - 1 + pad_left + pad_right - (kernel_w - 1) * dilation_x; + + if ((full_H % stride_y != 0) || + (full_W % stride_x != 0) || + (full_D % stride_d != 0)) + { + msg = "Parameters must yield exact integer output dimensions"; + return 1; + } + + if ((OH != (full_H / stride_y) + 1) || + (OW != (full_W / stride_x) + 1) || + (OD != (full_D / stride_d) + 1)) + { + std::string msg_d = ""; + if (conv_dimension == 3) + { + msg_d += std::to_string((full_D / stride_d) + 1) + ","; + } + msg = "Mismatch between output shape provided and expected output shape (" + + msg_d + + std::to_string((full_H / stride_y) + 1) + "," + + std::to_string((full_W / stride_x) + 1) + ")"; + return 1; + } + if (qinfo) { if (InDtype != DType_INT8 && qinfo->input_zp() != 0) @@ -529,7 +599,7 @@ int OpConv2d::checkTensorAttributes() std::string msg; if (check_conv_attribute_qinfo(attribute, qinfo, 2 /* conv_dimension */, input->getShape(), output->getShape(), - InDtype, WeightDtype, msg)) + weight->getShape(), 1 /* offset_kernel */, InDtype, WeightDtype, msg)) { msg = "OpConv2d: " + msg; printNodeValidationError(msg.c_str()); @@ -715,7 +785,7 @@ int OpConv3d::checkTensorAttributes() std::string msg; if (check_conv_attribute_qinfo(attribute, qinfo, 3 /* conv_dimension */, input->getShape(), output->getShape(), - InDtype, WeightDtype, msg)) + weight->getShape(), 1 /* offset_kernel */, InDtype, WeightDtype, msg)) { msg = "OpConv3d: " + msg; printNodeValidationError(msg.c_str()); @@ -904,7 +974,7 @@ int OpDepthwiseConv2d::checkTensorAttributes() std::string msg; if (check_conv_attribute_qinfo(attribute, qinfo, 2 /* conv_dimension */, input->getShape(), output->getShape(), - InDtype, WeightDtype, msg)) + weight->getShape(), 0 /* offset_kernel */, InDtype, WeightDtype, msg)) { msg = "OpDepthwiseConv2d: " + msg; printNodeValidationError(msg.c_str()); @@ -1447,7 +1517,7 @@ int OpTransposeConv2d::checkTensorAttributes() bias = dynamic_cast*>(inputs[2]); output = dynamic_cast*>(outputs[0]); - if (attribute->outpad().size() != 2) + if (attribute->outpad().size() != 4) { printNodeValidationError("OpTransposeConv2d: illegal size for attribute outpad"); return 1; @@ -1489,11 +1559,12 @@ int OpTransposeConv2d::checkTensorAttributes() } } + // TODO: Remove dilation once schema updated for (int32_t i : attribute->dilation()) { - if (i < 1) + if (i != 1) { - printNodeValidationError("OpTransposeConv2d: At least one dilation is smaller than one"); + printNodeValidationError("OpTransposeConv2d: Dilation is deprecated and must be set to one"); return 1; } } @@ -1507,6 +1578,33 @@ int OpTransposeConv2d::checkTensorAttributes() } } + int32_t IH = input->getShape()[1]; + int32_t IW = input->getShape()[2]; + int32_t OH = output->getShape()[1]; + int32_t OW = output->getShape()[2]; + + int32_t stride_y = attribute->stride()[0]; + int32_t stride_x = attribute->stride()[1]; + int32_t kernel_h = weight->getShape()[1]; + int32_t kernel_w = weight->getShape()[2]; + + int32_t outpad_top = attribute->outpad()[0]; + int32_t outpad_bottom = attribute->outpad()[1]; + int32_t outpad_left = attribute->outpad()[2]; + int32_t outpad_right = attribute->outpad()[3]; + + int32_t H = (IH - 1) * stride_y - outpad_top - outpad_bottom + kernel_h; + int32_t W = (IW - 1) * stride_x - outpad_left - outpad_right + kernel_w; + + if ((OH != H) || (OW != W)) + { + std::string msg = "OpTransposeConv2d: Mismatch between output shape provided and expected output shape (" + + std::to_string(H) + "," + + std::to_string(W) + ")"; + printNodeValidationError(msg.c_str()); + return 1; + } + if (this->qinfo) { if (InDtype != DType_INT8) @@ -1542,12 +1640,13 @@ int OpTransposeConv2d::eval() int out_width = this->output->getShape()[2]; int out_channels = this->output->getShape()[3]; - int padding_top = this->attribute->outpad()[0]; - int padding_left = this->attribute->outpad()[1]; - int stride_h = this->attribute->stride()[0]; - int stride_w = this->attribute->stride()[1]; - int dilation_h = this->attribute->dilation()[0]; - int dilation_w = this->attribute->dilation()[1]; + int outpad_top = this->attribute->outpad()[0]; + int outpad_bottom = this->attribute->outpad()[1]; + int outpad_left = this->attribute->outpad()[2]; + int outpad_right = this->attribute->outpad()[3]; + + int stride_h = this->attribute->stride()[0]; + int stride_w = this->attribute->stride()[1]; ERROR_IF(in_batch != out_batch, "OpTransposeConv2d: tensor batch mismatch %d != %d", in_batch, out_batch); ERROR_IF(f_in_channels != in_channels, "OpTransposeConv2d: tensor input channel mismatch %d != %d", f_in_channels, @@ -1559,10 +1658,10 @@ int OpTransposeConv2d::eval() DEBUG_INFO(OP, "perform OpTransposeConv2d, input.shape=[%d,%d,%d,%d], weight.shape=[%d,%d,%d,%d], " - "output.shape=[%d,%d,%d,%d], stride=[%d,%d], dilation=[%d,%d], padding=[%d,%d]", - in_batch, in_height, in_width, in_channels, f_height, f_width, f_out_channels, f_in_channels, out_batch, - out_height, out_width, out_channels, stride_h, stride_w, dilation_h, dilation_w, padding_top, - padding_left); + "output.shape=[%d,%d,%d,%d], stride=[%d,%d], outpad=[%d,%d,%d,%d]", + in_batch, in_height, in_width, in_channels, f_height, f_width, f_out_channels, f_in_channels, + out_batch, out_height, out_width, out_channels, stride_h, stride_w, outpad_top, + outpad_bottom, outpad_left, outpad_right); TIn input_val = this->input->getTensor(); TWeight weight_val = this->weight->getTensor(); @@ -1595,16 +1694,16 @@ int OpTransposeConv2d::eval() { for (int iw = 0; iw < in_width; iw++) { - out_x_origin = iw * stride_w - padding_left; - out_y_origin = ih * stride_h - padding_top; + out_x_origin = iw * stride_w - outpad_left; + out_y_origin = ih * stride_h - outpad_top; for (int ic = 0; ic < in_channels; ic++) { for (int fh = 0; fh < f_height; fh++) { for (int fw = 0; fw < f_width; fw++) { - out_x = out_x_origin + fw * dilation_w; - out_y = out_y_origin + fh * dilation_h; + out_x = out_x_origin + fw; + out_y = out_y_origin + fh; for (int oc = 0; oc < out_channels; oc++) { if ((out_x >= 0 && out_x < out_width) && (out_y >= 0 && out_y < out_height)) diff --git a/verif/generator/tosa_arg_gen.py b/verif/generator/tosa_arg_gen.py index e3492cd..f63a7df 100644 --- a/verif/generator/tosa_arg_gen.py +++ b/verif/generator/tosa_arg_gen.py @@ -1031,7 +1031,9 @@ class TosaArgGen: # 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)] + # Stride must be greater than 1 to force non-integer error + startStride = 1 if error_name != ErrorIf.PoolingOutputShapeNonInteger else 2 + s_vals = [x for x in range(startStride, 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))] @@ -1055,7 +1057,7 @@ class TosaArgGen: # There are too many parameter combinations, so generate them sparsely, # very sparse for negative tests - sparsity_factor = 2 if error_name else 100 + sparsity_factor = 2 if error_name else 120 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: @@ -1084,16 +1086,37 @@ class TosaArgGen: 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], + remainders = [] + for index in range(k_rank): + pad_offset = index * 2 + remainders.append( + ( + ifm_shape[index + 1] + - 1 + + p[pad_offset] + + p[pad_offset + 1] + - (k[index] - 1) * d[index] + ) + % s[index] + ) + if ( + # the parameters must produce integer exact output + error_name != ErrorIf.ConvOutputShapeNonInteger + and max(remainders) == 0 + ) or ( + error_name == ErrorIf.ConvOutputShapeNonInteger + and max(remainders) > 0 + ): + 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 @@ -1116,17 +1139,16 @@ class TosaArgGen: 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))} + paddings = {x for x in itertools.product(*([p_vals] * 4))} 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)] + # Dilation is not supported by the specification for transpose conv2d + # TODO: Remove this completely when schema has been updated + d_vals = [1] dilations = {x for x in itertools.product(*([d_vals] * 2))} if not error_name: @@ -1134,16 +1156,14 @@ class TosaArgGen: if max(ifm_shape) < 64: bigPadding = 9 paddings.update( - {x for x in itertools.product(*([[0, bigPadding]] * 2))} + {x for x in itertools.product(*([[0, bigPadding]] * 4))} ) 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_factor = 2 if error_name else 10 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: @@ -1159,18 +1179,8 @@ class TosaArgGen: 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 + oh = (ifm_shape[1] - 1) * s[0] - p[0] - p[1] + filter_shape[1] + ow = (ifm_shape[2] - 1) * s[1] - p[2] - p[3] + filter_shape[2] os = [ifm_shape[0], oh, ow, filter_shape[0]] arg_list.append( ( @@ -1231,7 +1241,9 @@ class TosaArgGen: # 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)] + # Stride must be greater than 1 to force non-integer error + startStride = 1 if error_name != ErrorIf.PoolingOutputShapeNonInteger else 2 + s_vals = [x for x in range(startStride, 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))} @@ -1239,8 +1251,10 @@ class TosaArgGen: if testGen.args.oversize: # add some oversize argument values bigStride = 7 - strides.update({x for x in itertools.product(*([[1, bigStride]] * 2))}) - bigKernel = 6 + strides.update( + {x for x in itertools.product(*([[startStride, bigStride]] * 2))} + ) + bigKernel = 9 kernels.update({x for x in itertools.product(*([[2, bigKernel]] * 2))}) if max(shape) < 64: # padding must be less than the kernel size @@ -1289,16 +1303,27 @@ class TosaArgGen: 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], + remainder_h = (shape[1] + p[0] + p[1] - k[0]) % s[0] + remainder_w = (shape[2] + p[2] + p[3] - k[1]) % s[1] + if ( + # the parameters must produce integer exact output + error_name != ErrorIf.PoolingOutputShapeNonInteger + and remainder_h == 0 + and remainder_w == 0 + ) or ( + error_name == ErrorIf.PoolingOutputShapeNonInteger + and (remainder_h != 0 or remainder_w != 0) + ): + 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 diff --git a/verif/generator/tosa_error_if.py b/verif/generator/tosa_error_if.py index caf63e3..e7e758f 100644 --- a/verif/generator/tosa_error_if.py +++ b/verif/generator/tosa_error_if.py @@ -42,6 +42,9 @@ class ErrorIf(object): PadSmallerZero = "PadSmallerZero" PadLargerEqualKernel = "PadLargerEqualKernel" PoolingOutputShapeMismatch = "PoolingOutputShapeMismatch" + PoolingOutputShapeNonInteger = "PoolingOutputShapeNonInteger" + ConvOutputShapeMismatch = "ConvOutputShapeMismatch" + ConvOutputShapeNonInteger = "ConvOutputShapeNonInteger" ScaleNotTrue = "ScaleNotTrue" ScaleTrue = "ScaleTrue" TensorSizeInputOutputMismatch = "TensorSizeInputOutputMismatch" @@ -1225,6 +1228,20 @@ class TosaErrorValidator: } return info_dict + @staticmethod + def checkPoolingParams(kernel, stride, pad): + return ( + min(kernel) >= 1 + and min(stride) >= 1 + and min(pad) >= 0 + and not ( + pad[0] >= kernel[0] + or pad[1] >= kernel[0] + or pad[2] >= kernel[1] + or pad[3] >= kernel[1] + ) + ) + @staticmethod def evPoolingOutputShapeMismatch(check=False, **kwargs): error_name = ErrorIf.PoolingOutputShapeMismatch @@ -1252,25 +1269,11 @@ class TosaErrorValidator: # calculate correct height, width dimensions if stride_x != 0 and stride_y != 0: - y_correct = ( - IH + pad_top + pad_bottom + stride_y - kernel_y - ) // stride_y - x_correct = ( - IW + pad_left + pad_right + stride_x - kernel_x - ) // stride_x + y_correct = ((IH + pad_top + pad_bottom - kernel_y) // stride_y) + 1 + x_correct = ((IW + pad_left + pad_right - kernel_x) // stride_x) + 1 # ensure parameters are valid - params_valid = ( - min(kernel) >= 1 - and min(stride) >= 1 - and min(pad) >= 0 - and not ( - pad[0] >= kernel[0] - or pad[1] >= kernel[0] - or pad[2] >= kernel[1] - or pad[3] >= kernel[1] - ) - ) + params_valid = TosaErrorValidator.checkPoolingParams(kernel, stride, pad) if params_valid and (OH != y_correct or OW != x_correct): error_result = True @@ -1283,6 +1286,165 @@ class TosaErrorValidator: } return info_dict + @staticmethod + def evPoolingOutputShapeNonInteger(check=False, **kwargs): + error_name = ErrorIf.PoolingOutputShapeNonInteger + param_reqs = {"rank": None, "dtype": None, "shape": None} + error_result = False + error_reason = "Parameters do not yield exact integer output dimensions" + + if check: + pad = kwargs["pad"] + pad_top, pad_bottom, pad_left, pad_right = pad[0], pad[1], pad[2], pad[3] + + kernel = kwargs["kernel"] + kernel_y, kernel_x = kernel[0], kernel[1] + + input_shape = kwargs["input_shape"] + IH, IW = input_shape[1], input_shape[2] + + stride = kwargs["stride"] + stride_y, stride_x = stride[0], stride[1] + + # calculate remainder of height, width dimensions + if stride_x != 0 and stride_y != 0: + y_remainder = (IH + pad_top + pad_bottom - kernel_y) % stride_y + x_remainder = (IW + pad_left + pad_right - kernel_x) % stride_x + + # ensure parameters are valid + params_valid = TosaErrorValidator.checkPoolingParams(kernel, stride, pad) + if params_valid and (y_remainder != 0 or x_remainder != 0): + 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 checkConvParams(weight_shape, stride, pad, dilation): + return ( + # Check kernel sizes + min(weight_shape[1:-1]) >= 1 + and min(stride) >= 1 + and min(pad) >= 0 + and (dilation is None or min(dilation) >= 1) + ) + + @staticmethod + def evConvOutputShapeMismatch(check=False, **kwargs): + error_name = ErrorIf.ConvOutputShapeMismatch + param_reqs = {"rank": None, "dtype": None, "shape": None} + error_result = False + error_reason = ( + "Mismatch between output shape provided and expected output shape" + ) + + if check: + op = kwargs["op"] + pad = kwargs["pad"] + weight_shape = kwargs["weight_shape"] + input_shape = kwargs["input_shape"] + output_shape = kwargs["output_shape"] + dilation = kwargs["dilation"] if op["op"] != Op.TRANSPOSE_CONV2D else None + stride = kwargs["stride"] + + kernel_offset = 0 if op["op"] == Op.DEPTHWISE_CONV2D else 1 + + # calculate correct dimensions + dims_correct = [] + if min(stride) > 0: + for index in range(len(stride)): + pad_offset = index * 2 + if op["op"] == Op.TRANSPOSE_CONV2D: + dims_correct.append( + (input_shape[index + 1] - 1) * stride[index] + - pad[pad_offset] + - pad[pad_offset + 1] + + weight_shape[index + kernel_offset] + ) + else: + dims_correct.append( + ( + input_shape[index + 1] + - 1 + + pad[pad_offset] + + pad[pad_offset + 1] + - (weight_shape[index + kernel_offset] - 1) + * dilation[index] + ) + // stride[index] + + 1 + ) + + # ensure parameters are valid + params_valid = TosaErrorValidator.checkConvParams( + weight_shape, stride, pad, dilation + ) + + if params_valid and output_shape[1:-1] != dims_correct: + 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 evConvOutputShapeNonInteger(check=False, **kwargs): + error_name = ErrorIf.ConvOutputShapeNonInteger + param_reqs = {"rank": None, "dtype": None, "shape": None} + error_result = False + error_reason = "Parameters do not yield exact integer output dimensions" + + if check: + op = kwargs["op"] + pad = kwargs["pad"] + weight_shape = kwargs["weight_shape"] + input_shape = kwargs["input_shape"] + dilation = kwargs["dilation"] + stride = kwargs["stride"] + + kernel_offset = 0 if op["op"] == Op.DEPTHWISE_CONV2D else 1 + + # calculate correct height, width dimensions + remainders = [] + if min(stride) > 0: + for index in range(len(stride)): + pad_offset = index * 2 + remainders.append( + ( + input_shape[index + 1] + - 1 + + pad[pad_offset] + + pad[pad_offset + 1] + - (weight_shape[index + kernel_offset] - 1) + * dilation[index] + ) + % stride[index] + ) + + # ensure parameters are valid + params_valid = TosaErrorValidator.checkConvParams( + weight_shape, stride, pad, dilation + ) + if params_valid and max(remainders) > 0: + 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 evArgmaxOutputShapeMismatch(check=False, **kwargs): error_name = ErrorIf.ArgmaxOutputShapeMismatch diff --git a/verif/generator/tosa_test_gen.py b/verif/generator/tosa_test_gen.py index 38365d0..7c2b9de 100644 --- a/verif/generator/tosa_test_gen.py +++ b/verif/generator/tosa_test_gen.py @@ -630,6 +630,8 @@ class TosaTestGen: stride=strides, dilation=dilations, input_shape=ifm.shape, + weight_shape=filter.shape, + output_shape=result_tens.shape, ): return None @@ -692,6 +694,8 @@ class TosaTestGen: stride=strides, dilation=dilations, input_shape=ifm.shape, + weight_shape=filter.shape, + output_shape=result_tens.shape, ): return None @@ -715,7 +719,7 @@ class TosaTestGen: error_name=None, qinfo=None, ): - assert len(outpad) == 2 + assert len(outpad) == 4 result_tens = OutputShaper.transposeConv2DOp( self.ser, self.rng, ifm, output_shape, error_name ) @@ -753,8 +757,9 @@ class TosaTestGen: output_list=output_list, pad=outpad, stride=stride, - dilation=dilation, input_shape=ifm.shape, + weight_shape=filter.shape, + output_shape=result_tens.shape, ): return None @@ -816,6 +821,8 @@ class TosaTestGen: stride=strides, dilation=dilations, input_shape=ifm.shape, + weight_shape=filter.shape, + output_shape=result_tens.shape, ): return None @@ -2393,6 +2400,7 @@ class TosaTestGen: TosaErrorValidator.evOutputZeroPointNotZero, TosaErrorValidator.evPadLargerEqualKernel, TosaErrorValidator.evPoolingOutputShapeMismatch, + TosaErrorValidator.evPoolingOutputShapeNonInteger, ), }, # Templated operator. Filled in by createDynamicOpLists @@ -2420,6 +2428,8 @@ class TosaTestGen: TosaErrorValidator.evStrideSmallerOne, TosaErrorValidator.evDilationSmallerOne, TosaErrorValidator.evWrongRank, + TosaErrorValidator.evConvOutputShapeMismatch, + TosaErrorValidator.evConvOutputShapeNonInteger, ), "template": True, }, @@ -2448,6 +2458,8 @@ class TosaTestGen: TosaErrorValidator.evStrideSmallerOne, TosaErrorValidator.evDilationSmallerOne, TosaErrorValidator.evWrongRank, + TosaErrorValidator.evConvOutputShapeMismatch, + TosaErrorValidator.evConvOutputShapeNonInteger, ), "template": True, }, @@ -2477,6 +2489,8 @@ class TosaTestGen: TosaErrorValidator.evStrideSmallerOne, TosaErrorValidator.evDilationSmallerOne, TosaErrorValidator.evWrongRank, + TosaErrorValidator.evConvOutputShapeMismatch, + TosaErrorValidator.evConvOutputShapeNonInteger, ), "template": True, }, @@ -2546,6 +2560,7 @@ class TosaTestGen: TosaErrorValidator.evWrongOutputList, TosaErrorValidator.evPadLargerEqualKernel, TosaErrorValidator.evPoolingOutputShapeMismatch, + TosaErrorValidator.evPoolingOutputShapeNonInteger, ), }, # Templated operator. Filled in by createDynamicOpLists @@ -2574,8 +2589,8 @@ class TosaTestGen: TosaErrorValidator.evWeightZeroPointNotZero, TosaErrorValidator.evPadSmallerZero, TosaErrorValidator.evStrideSmallerOne, - TosaErrorValidator.evDilationSmallerOne, TosaErrorValidator.evWrongRank, + TosaErrorValidator.evConvOutputShapeMismatch, ), "template": True, }, @@ -3887,30 +3902,30 @@ class OutputShaper: # Filter: OHWI # OFM: NHWC - if len(padding) == 2: - # Expand padding to 4 parameters in the case of transpose_conv2d - # From H,W to T,B,L,R - padding = [padding[0], padding[0], padding[1], padding[1]] - h = ( ifm.shape[1] - - filter.shape[1] - - (filter.shape[1] - 1) * (dilations[0] - 1) + - 1 + padding[0] + padding[1] + - (filter.shape[1] - 1) * dilations[0] ) // strides[0] + 1 w = ( ifm.shape[2] - - filter.shape[2] - - (filter.shape[2] - 1) * (dilations[1] - 1) + - 1 + padding[2] + padding[3] + - (filter.shape[2] - 1) * dilations[1] ) // strides[1] + 1 - # Avoid illegal dimensions, which can be generated in error_if tests - h = max(h, 1) - w = max(w, 1) + if error_name == ErrorIf.ConvOutputShapeMismatch: + choices = [1, 2, 3] + change = rng.choice(choices) + # increment in multiples of stride to not hit non-integer error case + if change in [1, 3]: + h = h + (rng.choice(choices) * strides[0]) + if change in [2, 3]: + w = w + (rng.choice(choices) * strides[1]) ofm_shape = [ifm.shape[0], h, w, filter.shape[0]] @@ -3941,32 +3956,38 @@ class OutputShaper: d = ( ifm.shape[1] - - filter.shape[1] - - (filter.shape[1] - 1) * (dilations[0] - 1) + - 1 + padding[0] + padding[1] + - (filter.shape[1] - 1) * dilations[0] ) // strides[0] + 1 h = ( ifm.shape[2] - - filter.shape[2] - - (filter.shape[2] - 1) * (dilations[1] - 1) + - 1 + padding[2] + padding[3] + - (filter.shape[2] - 1) * dilations[1] ) // strides[1] + 1 w = ( ifm.shape[3] - - filter.shape[3] - - (filter.shape[3] - 1) * (dilations[2] - 1) + - 1 + padding[4] + padding[5] + - (filter.shape[3] - 1) * dilations[2] ) // strides[2] + 1 - # Avoid illegal dimensions, which can be generated in error_if tests - d = max(d, 1) - h = max(h, 1) - w = max(w, 1) + if error_name == ErrorIf.ConvOutputShapeMismatch: + choices = [1, 2, 3, 4] + change = rng.choice(choices) + # increment in multiples of stride to not hit non-integer error case + if change in [1, 4]: + d = d + (rng.choice(choices) * strides[0]) + if change in [2, 4]: + h = h + (rng.choice(choices) * strides[1]) + if change in [3, 4]: + w = w + (rng.choice(choices) * strides[2]) ofm_shape = [ifm.shape[0], d, h, w, filter.shape[0]] @@ -3995,25 +4016,31 @@ class OutputShaper: # IFM: NHWC # Filter: HWCM # OFM: NHW C*M + h = ( ifm.shape[1] - - filter.shape[0] - - (filter.shape[0] - 1) * (dilations[0] - 1) + - 1 + padding[0] + padding[1] + - (filter.shape[0] - 1) * dilations[0] ) // strides[0] + 1 w = ( ifm.shape[2] - - filter.shape[1] - - (filter.shape[1] - 1) * (dilations[1] - 1) + - 1 + padding[2] + padding[3] + - (filter.shape[1] - 1) * dilations[1] ) // strides[1] + 1 - # Avoid illegal dimensions, which can be generated in error_if tests - h = max(h, 1) - w = max(w, 1) + if error_name == ErrorIf.ConvOutputShapeMismatch: + choices = [1, 2, 3] + change = rng.choice(choices) + # increment in multiples of stride to not hit non-integer error case + if change in [1, 3]: + h = h + (rng.choice(choices) * strides[0]) + if change in [2, 3]: + w = w + (rng.choice(choices) * strides[1]) ofm_shape = [ifm.shape[0], h, w, filter.shape[2] * filter.shape[3]] @@ -4043,14 +4070,17 @@ class OutputShaper: h = 1 w = 1 else: - h = (ifm.shape[1] + pad[0] + pad[1] + stride[0] - kernel[0]) // stride[0] - w = (ifm.shape[2] + pad[2] + pad[3] + stride[1] - kernel[1]) // stride[1] + h = (ifm.shape[1] + pad[0] + pad[1] - kernel[0]) // stride[0] + 1 + w = (ifm.shape[2] + pad[2] + pad[3] - kernel[1]) // stride[1] + 1 if error_name == ErrorIf.PoolingOutputShapeMismatch: - choices = [1, 2, 3, 4, 5] - h = h + rng.choice(choices) - w = w + rng.choice(choices) - + choices = [1, 2, 3] + change = rng.choice(choices) + # increment in multiples of stride to not hit non-integer error case + if change in [1, 3]: + h = h + (rng.choice(choices) * stride[0]) + if change in [2, 3]: + w = w + (rng.choice(choices) * stride[1]) ofm_shape = [ifm.shape[0], h, w, ifm.shape[3]] if error_name == ErrorIf.WrongOutputType: @@ -4468,6 +4498,14 @@ class OutputShaper: @staticmethod def transposeConv2DOp(ser, rng, ifm, output_shape, error_name=None): + if error_name == ErrorIf.ConvOutputShapeMismatch: + choices = [1, 2, 3] + change = rng.choice(choices) + if change in [1, 3]: + output_shape[1] = output_shape[1] + rng.choice(choices) + if change in [2, 3]: + output_shape[2] = output_shape[2] + rng.choice(choices) + if ifm.dtype == DType.INT8: out_dtype = DType.INT32 elif ifm.dtype == DType.INT16: -- cgit v1.2.1