From e5e2676409a936431f87d31fb74d825257b20804 Mon Sep 17 00:00:00 2001 From: Eric Kunze Date: Tue, 13 Oct 2020 16:11:07 -0700 Subject: Initial checkin of TOSA reference_model and tests Change-Id: I2f8e7fa63e2ae40203e57d2cc8814bde3b312cb6 Signed-off-by: Eric Kunze --- verif/tosa_test_gen.py | 2301 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2301 insertions(+) create mode 100644 verif/tosa_test_gen.py (limited to 'verif/tosa_test_gen.py') diff --git a/verif/tosa_test_gen.py b/verif/tosa_test_gen.py new file mode 100644 index 0000000..dc2d803 --- /dev/null +++ b/verif/tosa_test_gen.py @@ -0,0 +1,2301 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2020, ARM Limited. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import numpy as np +import argparse +import sys +import re +import os +import subprocess +import shlex +import json +import glob +import math +import queue +import threading +import traceback +import math + +from enum import IntEnum, Enum, unique + +import tosa_serializer as ts +from tosa_serializer import * +import tosa + +# Convenience variables to the flatc-generated types that should be enums, but aren't +DType = tosa.DType.DType() +Usage = tosa.Usage.Usage() +Format = tosa.Format.Format() +Op = tosa.Op.Op() +ResizeMode = tosa.ResizeMode.ResizeMode() + +class TosaQuantGen: + '''QuantizedInfo random generator helper functions. Specify with 'qgen': in the operator defintion''' + def __init__(self): + pass + + @staticmethod + def needsQinfo(op, dtype): + if dtype == DType.AINT8 or dtype == DType.INT8: + return True + return False + + @staticmethod + def qgUnary(testGen, op, dtype): + qinfo = ts.TosaSerializerQuantInfo() + if TosaQuantGen.needsQinfo(op, dtype): + qinfo.UnaryQuantInfo(testGen.randInt(), testGen.randInt()) + else: + qinfo.UnaryQuantInfo(0, 0) + return qinfo + + @staticmethod + def qgConv(testGen, op, dtype): + qinfo = ts.TosaSerializerQuantInfo() + if TosaQuantGen.needsQinfo(op, dtype): + qinfo.ConvQuantInfo(testGen.randInt(), testGen.randInt()) + else: + qinfo.ConvQuantInfo(0, 0) + return qinfo + + @staticmethod + def qgMatmul(testGen, op, dtype): + qinfo = ts.TosaSerializerQuantInfo() + if TosaQuantGen.needsQinfo(op, dtype): + qinfo.MatMulQuantInfo(testGen.randInt(), testGen.randInt()) + else: + qinfo.MatMulQuantInfo(0, 0) + return qinfo + + @staticmethod + def qgPad(testGen, op, dtype): + qinfo = ts.TosaSerializerQuantInfo() + if TosaQuantGen.needsQinfo(op, dtype): + qinfo.PadQuantInfo(testGen.randInt()) + else: + qinfo.PadQuantInfo(0) + 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)) + + assert(multiplier <= (1 << scaleBits)) + assert(shift >= 0 and shift <= 63) + + 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): + pl, const = opName['operands'] + shape = testGen.makeShape(rank) + + shape_list = [] + for i in range(pl + const): + shape_list.append(shape.copy()) + + return shape_list + + @staticmethod + def tgNHWC(testGen, opName, rank): + pl, const = opName['operands'] + + 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 + + shape_list = [] + for i in range(pl + const): + shape_list.append(shape.copy()) + + return shape_list + + @staticmethod + def tgBroadcastFuzz(testGen, op, rank): + shape = testGen.makeShape(rank) + + pl, const = op['operands'] + + shape_list = [] + + # Choose one of the inputs to broadcast + bcast_idx = testGen.randInt(0, 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) + shape_bcast[fuzz_idx] = 1 + + shape_list.append(shape_bcast) + + return shape_list + + @staticmethod + def tgConv2D(testGen, op, rank): + pl, const = op['operands'] + + 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 + + # 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 tgTransposeConv2D(testGen, op, rank): + pl, const = op['operands'] + + 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 + + # 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]]) + + return [ifm_shape, filter_shape] + + @staticmethod + def tgDepthwiseConv2D(testGen, op, rank): + pl, const = op['operands'] + + 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 + + # 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): + pl, const = op['operands'] + + assert(rank == 2) + assert(pl == 2 and const == 0) + + input_shape = testGen.makeShape(rank) + filter_oc = testGen.makeShape(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): + pl, const = op['operands'] + + assert(rank == 2) + assert(pl == 2 and const == 0) + + a_shape = testGen.makeShape(rank) + b_oc = testGen.makeShape(1)[0] + b_shape = np.asarray([a_shape[1], b_oc]) + + return [a_shape, b_shape] + +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): + '''A trivial argument generator for operators that don't take any + non-tensor arguments''' + return [('', [])] + + @staticmethod + def agAxis(testGen, opName, shapeList, dtype): + '''Build the axis argument for operators that take a single axis''' + axes = [] + + shape = shapeList[0] + + for a in range(0, len(shape)): + axes.append(('axis_{}'.format(a), [a])) + return axes + + @staticmethod + def agConv2D(testGen, opName, shapeList, dtype): + arg_list = [] + + ifm_shape = shapeList[0] + filter_shape = shapeList[1] + + # Must be rank 4 + assert(len(ifm_shape) == 4) + assert(len(filter_shape) == 4) + + maxStride = testGen.args.max_conv_stride + maxPadding = testGen.args.max_conv_padding + 1 + maxDilation = testGen.args.max_conv_dilation + + # Strides, padding, dilations + for stride in range(0, maxStride ** 2): + for padding in range(0, (maxPadding) ** 4): + for dilation in range(0, maxDilation ** 2): + + s = [stride // maxStride + 1, + stride % maxStride + 1] + p = [(padding // (maxPadding * 4)) % maxPadding, + (padding // (maxPadding * 2)) % maxPadding, + (padding // (maxPadding * 1)) % maxPadding, + padding % maxPadding] + d = [ dilation // maxDilation + 1, + dilation % maxDilation + 1] + + # 4 padding parameters for regular conv2d + arg_list.append(('st{}{}_pad{}{}{}{}_dilat{}{}'.format(s[0], s[1], + p[0], p[1], p[2], p[3], + d[0], d[1]), + [ s, p, d ])) + return arg_list + + @staticmethod + def agTransposeConv2D(testGen, opName, shapeList, dtype): + arg_list = [] + + ifm_shape = shapeList[0] + filter_shape = shapeList[1] + + # Must be rank 4 + assert(len(ifm_shape) == 4) + assert(len(filter_shape) == 4) + + maxStride = testGen.args.max_conv_stride + maxPadding = testGen.args.max_conv_padding + 1 + maxDilation = testGen.args.max_conv_dilation + + # Strides, padding, dilations + for stride in range(0, maxStride ** 2): + for out_padding in range(0, (maxPadding) ** 2): + for dilation in range(0, maxDilation ** 2): + + s = [stride // maxStride + 1, + stride % maxStride + 1] + p = [(out_padding // (maxPadding * 1)) % maxPadding, + out_padding % maxPadding] + d = [ dilation // maxDilation + 1, + dilation % maxDilation + 1] + + 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 + + # Output shape + os = [ ifm_shape[0], oh, ow, filter_shape[0] ] + + arg_list.append(('st{}{}_outpad{}{}_dilat{}{}_os{}x{}x{}x{}'.format(s[0], s[1], + p[0], p[1], + d[0], d[1], + os[0], os[1], os[2], os[3]), + [ s, p, d, os ])) + + return arg_list + + @staticmethod + def agPad(testGen, opName, shapeList, dtype): + arg_list = [] + rank = len(shapeList[0]) + + # Exhaustively test combinations of 0/1 padding on each side of each dimension + # This process might need some revision for >1 padding, but use rank**2 as a bitmask + # for now + for v in range(rank ** 2): + + # Create a flat arraypadding4D + paddings = np.zeros((rank * 2), dtype=np.int32) + + # Fill in the 1's + for r in (range(rank * 2)): + if (v >> r) & 1: + paddings[r] = 1 + + # Reshape back to a 2D array + paddings = paddings.reshape((rank, 2)) + + arg_list.append(('pad{0:b}'.format(v), [ paddings ])) + + return arg_list + + @staticmethod + def agPooling(testGen, opName, shapeList, dtype): + arg_list = [] + + shape = shapeList[0] + assert(len(shape) == 4) + + maxStride = testGen.args.max_pooling_stride + maxKernel = testGen.args.max_pooling_kernel + maxPadding = testGen.args.max_pooling_padding + 1 + + for kernel in range(0, maxKernel ** 2): + for stride in range(0, maxStride ** 2): + for padding in range(0, maxPadding ** 4): + s = [stride // maxStride + 1, + stride % maxStride + 1] + k = [(kernel // maxKernel) + 2, + (kernel % maxKernel) + 2] + p = [(padding // (maxPadding * 4)) % maxPadding, + (padding // (maxPadding * 2)) % maxPadding, + (padding // (maxPadding * 1)) % maxPadding, + padding % maxPadding] + + arg_list.append(('st{}{}_kern{}{}_pad{}{}{}{}'.format(s[0], s[1], + k[0], k[1], + p[0], p[1], p[2], p[3]), + [k, s, p])) + return arg_list + + @staticmethod + def agCast(testGen, opName, shapeList, inDtype): + arg_list = [] + + # Enumerate the output types here + if 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 ] + 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): + arg_list = [] + + # Enumerate the output types here + for dtype in [ DType.AINT8, DType.INT16, DType.INT32 ]: + for scale32 in [ False, True ]: + for double_round in [ False, True ]: + for per_channel in [ False, True ]: + + if inDtype == DType.INT48 and scale32: + # Illegal condition. Must be scale32=False + 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 + + # 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))): + if (val % i) == 0: + factors.append(i) + + return factors + + @staticmethod + def agReshape(testGen, opName, shapeList, dtype): + 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, 6) + newShape = [] + if (len(factors) < newRank): + continue + + remainingElements = totalElements + shuffledFactors = testGen.rng.permutation(factors) + for i in range(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 + + arg_list.append(('perm{}_rank{}'.format(p, newRank), [newShape])) + + return arg_list + + + @staticmethod + def agTranspose(testGen, opName, shapeList, dtype): + arg_list = [] + + ifm_shape = shapeList[0] + + perms = range(len(ifm_shape)) + for p in range(testGen.args.num_rand_permutations): + perms = np.int32(testGen.rng.permutation(perms)).tolist() + + # Avoid duplicates + found = False + for name, other_perm in arg_list: + if other_perm[0] == perms: + found = True + break + + if not found: + arg_list.append(('perm{}'.format(p), [perms])) + + return arg_list + + @staticmethod + def agSlice(testGen, opName, shapeList, dtype): + arg_list = [] + + ifm_shape = shapeList[0] + rank = len(ifm_shape) + + for p in range(testGen.args.num_rand_permutations): + begin = [] + size = [] + + valid=True + + for i in range(rank): + if ifm_shape[i] > 1: + begin.append(testGen.randInt(0, ifm_shape[i])) + size.append(testGen.randInt(0, ifm_shape[i] - begin[i])) + + # Invalid slice size? + if size[i] == 0: + valid = False + else: + begin.append(0) + size.append(1) + + if valid: + arg_list.append(('perm{}'.format(p), [begin, size])) + return arg_list + + @staticmethod + def agTile(testGen, opName, shapeList, dtype): + 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): + multiples.append(testGen.randInt(1, 4)) + + arg_list.append(('perm{}'.format(p), [multiples])) + + return arg_list + + @staticmethod + def agResize(testGen, opName, shapeList, dtype): + arg_list = [] + + ifm_shape = shapeList[0] + + for m in [ResizeMode.NEAREST, ResizeMode.BILINEAR]: + + # Exclude illegal {mode, type} configurations. Pick legal output types + if m == ResizeMode.NEAREST and dtype == DType.INT8: + outputDTypeList = [ DType.INT32 ] + elif m == ResizeMode.NEAREST and dtype == DType.INT16: + outputDTypeList = [ DType.INT16 ] + elif m == ResizeMode.BILINEAR and dtype == DType.INT8: + outputDTypeList = [ DType.INT8 ] + elif m == ResizeMode.BILINEAR and dtype == DType.INT16: + outputDTypeList = [ DType.INT48 ] + 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 + output_dims = [ testGen.randInt(), testGen.randInt() ] + + shift = testGen.randInt(1, 11) + + stride = [ (ifm_shape[1] << shift) // output_dims[0], + (ifm_shape[2] << shift) // output_dims[1] ] + + offset = [ testGen.randInt(-stride[0], (ifm_shape[1] << shift) - (output_dims[0] - 1) * stride[0]), + testGen.randInt(-stride[1], (ifm_shape[2] << shift) - (output_dims[1] - 1) * stride[1]) ] + + arg_list.append(('mode{}_shift{}_odim{}x{}_out{}_st{}x{}_off{}x{}'.format(m, shift, output_dims[0], output_dims[1], + testGen.typeStr(outputDType), stride[0], stride[1], + offset[0], offset[1]), + [m, stride, offset, shift, output_dims, outputDType])) + + return arg_list + + def agCondIf(testGen, opName, shapeList, dtype): + # 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): + # 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 + +class TosaTestGen: + def __init__(self, args): + self.args = args + self.basePath = args.output_dir + self.random_seed = args.random_seed + self.ser = None + self.rng = np.random.default_rng(self.random_seed) + self.createDynamicOpLists() + self.initOpListDefaults() + self.quantGen = TosaQuantGen() + # Force makeShape to do a specific starting shape + self.targetted_shape = None + + def createSerializer(self, opName, testPath): + self.testPath = os.path.join(opName, testPath) + + fullPath = os.path.join(self.basePath, self.testPath) + os.makedirs(fullPath, exist_ok=True) + self.ser = ts.TosaSerializer(fullPath) + + def getSerializer(self): + return self.ser + + def serialize(self, testName): + with open(os.path.join(self.basePath, self.testPath, '{}.tosa'.format(testName)), 'wb') as fd: + fd.write(self.ser.serialize()) + + with open(os.path.join(self.basePath, self.testPath, 'desc.json'), 'w') as fd: + fd.write(self.ser.writeJson('{}.tosa'.format(testName))) + + def getRandTensor(self, shape, dtype): + RAND_SHIFT_FACTOR = 0.5 + RAND_SCALE_FACTOR = 4.0 + + if dtype == DType.BOOL: + np_dt = np.bool + return np.bool_(self.rng.choice(a=[False, True], size=shape)) + elif dtype == DType.AINT8: + return np.int32(self.rng.integers(low=0, high=256, size=shape)) + elif dtype == DType.INT4: + return np.int32(self.rng.integers(low=-7, high=8, size=shape)) + elif dtype == DType.INT8: + return np.int32(self.rng.integers(low=-127, high=128, size=shape)) + elif dtype == DType.INT16: + return np.int32(self.rng.integers(low=-32768, high=32768, size=shape)) + elif dtype == DType.INT32: + return np.int32(self.rng.integers(low=-(1 << 31), high=(1 << 31), size=shape)) + elif dtype == DType.INT48: + return np.int64(self.rng.integers(low=-(1 << 47), high=(1 << 47), size=shape)) + elif dtype == DType.FLOAT: + return np.float32(self.rng.random(size=shape) - RAND_SHIFT_FACTOR * RAND_SCALE_FACTOR) + else: + raise Exception('Unrecognized Dtype: {}'.format(dtype)) + + def buildPlaceholderTensors(self, shape_list, dtype): + placeholders = [] + + for shape in shape_list: + arr = self.getRandTensor(shape, dtype) + placeholders.append(self.ser.addPlaceholder(shape, dtype, Usage.ACTIVATION, [], arr)) + + return placeholders + + def buildConstTensors(self, shape_list, dtype): + consts = [] + + for shape in shape_list: + arr = self.getRandTensor(shape, dtype) + consts.append(self.ser.addConst(shape, dtype, Usage.ACTIVATION, [], arr)) + + return consts + + def makeShape(self, rank): + if self.targetted_shape: + return np.int32(self.targetted_shape) + return np.int32(self.rng.integers(low=self.args.tensor_shape_range[0], + high=self.args.tensor_shape_range[1], + size=rank)) + + def setTargetShape(self, shape): + self.targetted_shape = shape + + def randInt(self, low=0, high=256): + return np.int32(self.rng.integers(low=low, high=high, size=1))[0] + + def getRandNumberDType(self, dtype): + if dtype == DType.FLOAT: + return self.rng.random() + elif dtype == DType.BOOL: + return self.rng.choice([False, True]) + elif dtype == DType.INT4: + low, high = (-7, 8) + elif dtype == DType.AINT8: + low, high = (0, 256) + elif dtype == DType.INT8: + low, high = (-127, 128) + elif dtype == DType.INT16: + low, high = (-32768, 32768) + elif dtype == DType.INT32: + low, high = (-(1<<31), (1<<31)) + elif dtype == DType.INT48: + low, high = (-(1<<47), (1<<47)) + # Special size + return np.int64(self.rng.integers(low, high, size=1))[0] + else: + raise Exception('Unknown dtype: {}'.format(dtype)) + + return np.int32(self.rng.integers(low, high, size=1))[0] + + def shapeStr(self, shape): + + sStr = [] + # Convert to strings + for i in shape: + sStr.append(str(i)) + + return 'x'.join(sStr) + + def typeStr(self, t): + if t == DType.BOOL: + return 'b' + elif t == DType.AINT8: + return 'a8' + elif t == DType.INT4: + return 'i4' + elif t == DType.INT8: + return 'i8' + elif t == DType.INT16: + return 'i16' + elif t == DType.INT32: + return 'i32' + elif t == DType.INT48: + return 'i48' + elif t == DType.FLOAT: + return 'float' + else: + raise Exception('Unknown dtype, cannot convert to string: {}'.format(t)) + + def typeWidth(self, t): + ''' Get the datatype width for integer types''' + if t == DType.AINT8: + return 8 + elif t == DType.UINT8: + return 8 + elif t == DType.INT4: + return 4 + elif t == DType.INT8: + return 8 + elif t == DType.INT16: + return 16 + elif t == DType.INT32: + return 32 + elif t == DType.INT48: + return 48 + else: + raise Exception('Unknown dtype, cannot convert to string: {}'.format(t)) + + # Argument generators + # Returns a list of tuples (stringDescriptor, [build_fcn_arg_list]) + # Where the string descriptor is used to generate the test name and + # The build_fcn_arg_list is expanded and passed to the operator test + # build function + + + def build_unary(self, op, a, qinfo = None): + result_tens = OutputShaper.unaryOp(self.ser, a) + self.ser.addOperator(op, [a.name], [result_tens.name], None, qinfo) + return result_tens + + def build_binary_broadcast(self, op, a, b): + result_tens = OutputShaper.binaryBroadcastOp(self.ser, a, b) + self.ser.addOperator(op, [a.name, b.name], [result_tens.name]) + return result_tens + + def build_binary_nonbroadcast(self, op, a, b): + result_tens = OutputShaper.binaryNonBroadcastOp(self.ser, a, b) + self.ser.addOperator(op, [a.name, b.name], [result_tens.name]) + return result_tens + + def build_mul(self, op, a, b): + result_tens = OutputShaper.binaryBroadcastOp(self.ser, a, b) + + # Special for multiply: + # Force the result to INT32 for INT types + if a.dtype != DType.FLOAT: + result_tens.setDtype(DType.INT32) + + self.ser.addOperator(op, [a.name, b.name], [result_tens.name]) + return result_tens + + def build_table(self, op, a): + # Constant size, random values + table_arr = self.getRandTensor([513], DType.INT16) + table_tens = self.ser.addConst(table_arr.shape, DType.INT16, Usage.INDEX, [], table_arr) + + result_tens = OutputShaper.tableOp(self.ser, a, table_tens) + self.ser.addOperator(op, [a.name, table_tens.name], [result_tens.name], None) + + return result_tens + + def build_select(self, op, cond, a, b): + + # Replace the cond tensor with a boolean tensor since it probably + # has the wrong dtype + t = self.buildPlaceholderTensors([cond.shape], DType.BOOL) + cond = t[0] + + result_tens = OutputShaper.selectOp(self.ser, cond, a, b) + self.ser.addOperator(op, [cond.name, a.name, b.name], [result_tens.name]) + + return result_tens + + def build_comparison(self, op, a, b): + result_tens = OutputShaper.binaryComparisonOp(self.ser, a, b) + self.ser.addOperator(op, [a.name, b.name], [result_tens.name]) + return result_tens + + def build_argmax(self, op, a, axis): + result_tens = OutputShaper.argmaxOp(self.ser, a, axis) + + attr = ts.TosaSerializerAttribute() + attr.AxisAttribute(axis) + + self.ser.addOperator(op, [a.name], [result_tens.name], attr) + return result_tens + + def build_pool2d(self, op, input, kernel, stride, pad, qinfo = None): + result_tens = OutputShaper.pool2dOp(self.ser, input, kernel, stride, pad) + + attr = ts.TosaSerializerAttribute() + attr.Pool2dAttribute(kernel, stride, pad) + input.addFormat(Format.NHWC) + + self.ser.addOperator(op, [input.name], [result_tens.name], attr, qinfo) + return result_tens + + def build_conv2d(self, op, ifm, filter, bias, strides, padding, dilations, qinfo): + assert(len(padding) == 4) + result_tens = OutputShaper.conv2dOp(self.ser, ifm, filter, strides, padding, dilations) + + attr = ts.TosaSerializerAttribute() + attr.Conv2dAttribute(padding, strides, dilations) + + ifm.addFormat(Format.NHWC) + # Update the filter ordering + filter.addUsage(Usage.WEIGHT) + filter.addFormat(Format.OHWI) + + self.ser.addOperator(op, [ifm.name, filter.name, bias.name], [result_tens.name], attr, qinfo) + return result_tens + + def build_transpose_conv2d(self, op, ifm, filter, stride, outpad, dilation, output_shape, qinfo): + assert(len(outpad) == 2) + result_tens = OutputShaper.transposeConv2DOp(self.ser, ifm, output_shape) + + attr = ts.TosaSerializerAttribute() + attr.TransposeConv2DAttribute(outpad, stride, dilation, output_shape) + + ifm.addFormat(Format.NHWC) + # Update the filter ordering + filter.addUsage(Usage.WEIGHT) + filter.addFormat(Format.OHWI) + + # Create bias here since the acc_t depends on (but isn't the same as) the input dtype + # The bias is OC + if ifm.dtype == DType.AINT8 or ifm.dtype == DType.INT8: + bias_type = DType.INT32 + elif ifm.dtype == DType.INT16: + bias_type = DType.INT48 + elif ifm.dtype == DType.FLOAT: + bias_type = DType.FLOAT + else: + raise Exception('Unsupported dtype for transpose_conv2d: {}'.format(ifm.dtype)) + + bias_arr = self.getRandTensor([filter.shape[0]], bias_type) + bias_tens = self.ser.addConst([filter.shape[0]], bias_type, [], [], bias_arr) + + self.ser.addOperator(op, [ifm.name, filter.name, bias_tens.name], [result_tens.name], attr, qinfo) + return result_tens + + def build_depthwise_conv2d(self, op, ifm, filter, bias, strides, padding, dilations, qinfo): + result_tens = OutputShaper.depthwiseConv2dOp(self.ser, ifm, filter, strides, padding, dilations) + + attr = ts.TosaSerializerAttribute() + attr.Conv2dAttribute(padding, strides, dilations) + + ifm.addFormat(Format.NHWC) + filter.addUsage(Usage.WEIGHT) + filter.addFormat(Format.HWIM) + + self.ser.addOperator(op, [ifm.name, filter.name, bias.name], [result_tens.name], attr, qinfo) + return result_tens + + def build_fully_connected(self, op, ifm, filter, bias, qinfo): + result_tens = OutputShaper.fullyConnectedOp(self.ser, ifm, filter) + + filter.addUsage(Usage.WEIGHT) + self.ser.addOperator(op, [ifm.name, filter.name, bias.name], [result_tens.name], None, qinfo) + return result_tens + + def build_matmul(self, op, a, b, qinfo): + result_tens = OutputShaper.matmulOp(self.ser, a, b) + self.ser.addOperator(op, [a.name, b.name], [result_tens.name], None, qinfo) + return result_tens + + def build_reduce(self, op, a, axis): + result_tens = OutputShaper.reduceOp(self.ser, a, axis) + + attr = ts.TosaSerializerAttribute() + attr.AxisAttribute(axis) + + self.ser.addOperator(op, [a.name], result_tens.name, attr) + return result_tens + + def build_clamp(self, op, a): + result_tens = OutputShaper.unaryOp(self.ser, a) + + attr = ts.TosaSerializerAttribute() + + # Get two random ints + v = [self.randInt(), self.randInt()] + + if a.dtype == DType.FLOAT: + attr.ClampAttribute(0, 0, min(v), max(v)) + else: + attr.ClampAttribute(min(v), max(v), 0, 0) + + self.ser.addOperator(op, [a.name], [result_tens.name], attr) + return result_tens + + def build_leaky_relu(self, op, a): + result_tens = OutputShaper.unaryOp(self.ser, a) + attr = ts.TosaSerializerAttribute() + + attr.LeakyReluAttribute(self.getRandNumberDType(DType.FLOAT)) + + self.ser.addOperator(op, [a.name], [result_tens.name], attr) + return result_tens + + # Needs an additional type/input + def build_prelu(self, op, a): + result_tens = OutputShaper.unaryOp(self.ser, a) + + self.ser.addOperator(op, [a.name], [result_tens.name]) + return result_tens + + def build_relun(self, op, a): + result_tens = OutputShaper.unaryOp(self.ser, a) + + attr = ts.TosaSerializerAttribute() + + if a.dtype == DType.FLOAT: + attr.ReluNAttribute(0, self.getRandNumberDType(a.dtype)) + else: + attr.ReluNAttribute(self.getRandNumberDType(a.dtype), 0) + + self.ser.addOperator(op, [a.name], [result_tens.name], attr) + return result_tens + + def build_sigmoid(self, op, a): + result_tens = OutputShaper.unaryOp(self.ser, a) + self.ser.addOperator(op, [a.name], [result_tens.name]) + return result_tens + + def build_tanh(self, op, a): + result_tens = OutputShaper.unaryOp(self.ser, a) + self.ser.addOperator(op, [a.name], [result_tens.name]) + return result_tens + + def build_concat(self, op, a, b, axis): + result_tens = OutputShaper.concatOp(self.ser, a, b, axis) + + attr = ts.TosaSerializerAttribute() + attr.AxisAttribute(axis) + + self.ser.addOperator(op, [a.name, b.name], [result_tens.name], attr) + + def build_pad(self, op, a, padding, qinfo): + result_tens = OutputShaper.padOp(self.ser, a, padding) + + # Need to turn the padding array into a TOSA tensor here. + # This is one of the few tensor operands that does not get + # randomly generated + padding_tens = self.ser.addConst(padding.shape, DType.INT32, [], [], padding) + + self.ser.addOperator(op, [a.name, padding_tens.name], [result_tens.name], None, qinfo) + + def build_reshape(self, op, a, newShape): + result_tens = OutputShaper.reshapeOp(self.ser, a, newShape) + + attr = ts.TosaSerializerAttribute() + attr.ReshapeAttribute(newShape) + + self.ser.addOperator(op, [a.name], [result_tens.name], attr) + return result_tens + + def build_reverse(self, op, a, axis): + result_tens = OutputShaper.unaryOp(self.ser, a) + + attr = ts.TosaSerializerAttribute() + attr.AxisAttribute(axis) + + self.ser.addOperator(op, [a.name], [result_tens.name], attr) + return result_tens + + def build_transpose(self, op, a, perms): + result_tens = OutputShaper.transposeOp(self.ser, a, perms) + + perms_tens = self.ser.addConst([len(perms)], DType.INT32, Usage.ACTIVATION, [], np.int32(perms)) + + self.ser.addOperator(op, [a.name, perms_tens.name], [result_tens.name]) + return result_tens + + def build_slice(self, op, a, begin, size): + result_tens = OutputShaper.sliceOp(self.ser, a, begin, size) + + attr = ts.TosaSerializerAttribute() + attr.SliceAttribute(begin, size) + + self.ser.addOperator(op, [a.name], [result_tens.name], attr) + return result_tens + + def build_tile(self, op, a, multiples): + result_tens = OutputShaper.tileOp(self.ser, a, multiples) + + attr = ts.TosaSerializerAttribute() + attr.TileAttribute(multiples) + + self.ser.addOperator(op, [a.name], [result_tens.name], attr) + return result_tens + + + def build_gather(self, op, values, axis): + + # Create a new indicies tensor + # here with data that doesn't exceed the dimensions of the values tensor + + max_val = values.shape[axis] + indicies_arr = np.int32(self.rng.integers(low=0, high=max_val, size=[self.randInt(1, max_val + 1)])) + indicies = self.ser.addConst(indicies_arr.shape, DType.INT32, Usage.INDEX, [], indicies_arr) + + result_tens = OutputShaper.gatherOp(self.ser, values, indicies, axis) + + attr = ts.TosaSerializerAttribute() + attr.AxisAttribute(axis) + + self.ser.addOperator(op, [values.name, indicies.name], [result_tens.name], attr) + + return result_tens + + def build_resize(self, op, input, mode, stride, offset, shift, output_dims, output_dtype): + result_tens = OutputShaper.resizeOp(self.ser, input, mode, stride, offset, shift, output_dims, output_dtype) + + attr = ts.TosaSerializerAttribute() + attr.ResizeAttribute(output_dims, stride, offset, shift, mode) + + self.ser.addOperator(op, [input.name], [result_tens.name], attr) + return result_tens + + def build_identityn(self, op, val, val2): + + result_tens = OutputShaper.unaryOp(self.ser, val) + result_tens2 = OutputShaper.unaryOp(self.ser, val2) + self.ser.addOperator(op, [val.name, val2.name], [result_tens.name, result_tens2.name]) + return result_tens + + def build_placeholder(self, op, val): + # Add an identity op to avoid warning in the reference model + return self.build_unary(Op.IDENTITY, val) + + # Type Conversion + def build_cast(self, op, val, out_dtype): + result_tens = OutputShaper.typeConversionOp(self.ser, val, out_dtype) + self.ser.addOperator(op, [val.name], [result_tens.name]) + return result_tens + + def build_rescale(self, op, val, out_dtype, scale32, double_round, per_channel): + result_tens = OutputShaper.typeConversionOp(self.ser, val, out_dtype) + + if per_channel: + nc = val.shape[-1] + else: + nc = 1 + + in_type_width = self.typeWidth(val.dtype) + out_type_width = self.typeWidth(out_dtype) + + if val.dtype == DType.AINT8: + input_zp = self.randInt() + in_type_width = in_type_width + 1 + else: + input_zp = 0 + + if out_dtype == DType.AINT8: + output_zp = self.randInt() + out_type_width = out_type_width + 1 + else: + output_zp = 0 + + # Calculate scale based on: + # scale = a *(2^output_width)/(2^input_width)) + + a = np.float32(self.rng.random(size=[nc])) + scale_arr = a * np.float32((1 << out_type_width) / (1 << in_type_width)) + + if scale32: + pass + # Cap the scaling at 2^15 - 1 for scale16 + scale_arr = np.clip(scale_arr, 1.0 / (1 << 31), (1 << 31) - 1) + else: + # Cap the scaling at 2^15 - 1 for scale16 + scale_arr = np.clip(scale_arr, 1.0 / (1 << 31), 32767.0) + + #print('{} {} -> {}'.format(out_type_width, in_type_width, scale_arr)) + + multiplier_arr = np.int32(np.zeros(shape=[nc])) + shift_arr = np.int32(np.zeros(shape=[nc])) + + for i in range(nc): + multiplier_arr[i], shift_arr[i] = TosaQuantGen.computeMultiplierAndShift(scale_arr[i], scale32) + + #print('multiplier {} shift {} inzp {} outzp {}'.format(multiplier_arr, shift_arr, input_zp, output_zp)) + + attr = ts.TosaSerializerAttribute() + attr.RescaleAttribute(input_zp, + output_zp, + multiplier_arr, + shift_arr, + scale32, + double_round, + + per_channel) + + self.ser.addOperator(op, [val.name], [result_tens.name], attr) + return result_tens + + def build_cond_if_const(self, op, then_tens, else_tens, cond): + # For cond_if with constants, we're supplied with then/else tensors that we ignore + # (except for the generated shap) and the condition. Build Then/Else blocks + # and fill them with const nodes for the body. + + # Condition tensor + cond_tens = self.ser.addConst([], DType.BOOL, Usage.ACTIVATION, [], [cond]) + + # Make then/else tensors + out_shape = then_tens.shape + then_arr = np.int32(self.rng.integers(0, 255, size=out_shape)) + else_arr = np.int32(self.rng.integers(0, 255, size=out_shape)) + + # And the result tensor based on any of the outputs + result_tens = self.ser.addOutput(out_shape, DType.INT32, Usage.ACTIVATION, []) + + # Create the attribute with the names of the then/else blocks + then_block = 'THEN_BLOCK' + else_block = 'ELSE_BLOCK' + attr = ts.TosaSerializerAttribute() + attr.CondIfAttribute(then_block, else_block) + + # Finally, build the op and the two blocks + self.ser.addOperator(op, [cond_tens.name], [result_tens.name], attr) + + self.ser.startBasicBlock(then_block) + # Build the actual then/else tensors inside their blocks + then_tens = self.ser.addConst(out_shape, DType.INT32, Usage.ACTIVATION, [], then_arr) + self.ser.addOutputTensor(then_tens) + + self.ser.startBasicBlock(else_block) + else_tens = self.ser.addConst(out_shape, DType.INT32, Usage.ACTIVATION, [], else_arr) + self.ser.addOutputTensor(else_tens) + + return result_tens + + def build_cond_if_binary(self, op, a, b, cond): + # For cond_if with a binary op in the then/else blocks, take a and b and + # alternately add or subtract them based on the condition + + # Condition tensor + cond_tens = self.ser.addConst([], DType.BOOL, Usage.ACTIVATION, [], [cond]) + + result_tens = self.ser.addOutput(a.shape, a.dtype, Usage.ACTIVATION, []) + self.ser.currBasicBlock.addOutput(result_tens.name) + + # Create the attribute with the names of the then/else blocks + then_block = 'THEN_BLOCK' + else_block = 'ELSE_BLOCK' + attr = ts.TosaSerializerAttribute() + attr.CondIfAttribute(then_block, else_block) + + # Finally, build the op and the two blocks + self.ser.addOperator(op, [cond_tens.name, a.name, b.name], [result_tens.name], attr) + + self.ser.startBasicBlock(then_block) + self.ser.addInputTensor(a) + self.ser.addInputTensor(b) + then_tens = self.ser.addOutput(a.shape, a.dtype, a.usage, a.dformat) + self.ser.addOperator(Op.ADD, [a.name, b.name], [then_tens.name]) + + self.ser.startBasicBlock(else_block) + self.ser.addInputTensor(a) + self.ser.addInputTensor(b) + else_tens = self.ser.addOutput(a.shape, a.dtype, a.usage, a.dformat) + self.ser.addOperator(Op.SUB, [a.name, b.name], [else_tens.name]) + + return result_tens + + def build_while_loop(self, op, a, iter_val): + iter = self.ser.addPlaceholder([], DType.INT32, Usage.ACTIVATION, [], [np.int32(iter_val)]) + + cond_block = 'COND_BLOCK' + body_block = 'BODY_BLOCK' + + attr = ts.TosaSerializerAttribute() + attr.WhileLoopAttribute(cond_block, body_block) + + # Accumulator tensor + #acc = self.ser.addOutput(a.shape, a.dtype, a.usage, a.dformat) + acc_init_val = np.int32(np.zeros(a.shape)) + acc = self.ser.addPlaceholder(a.shape, a.dtype, a.usage, a.dformat, acc_init_val) + + # Intermediate/output tensors for everything going through the loop + iter_out = self.ser.addIntermediate(iter.shape, iter.dtype, iter.usage, iter.dformat) + a_out = self.ser.addIntermediate(a.shape, a.dtype, a.usage, a.dformat) + acc_out = self.ser.addIntermediate(acc.shape, acc.dtype, acc.usage, acc.dformat) + + # While_loop operator + self.ser.addOperator(op, + [iter.name, a.name, acc.name], + [iter_out.name, a_out.name, acc_out.name], attr) + + # COND block (input: iter, output: cond_tens ) + self.ser.startBasicBlock(cond_block) + self.ser.addInputTensor(iter) + self.ser.addInputTensor(a) + self.ser.addInputTensor(acc) + zero_tens = self.ser.addConst([], DType.INT32, [], [], [np.int32(0)]) + cond_tens = self.ser.addOutput([], DType.BOOL, [], []) + self.ser.addOperator(Op.GREATER, [iter.name, zero_tens.name], + [cond_tens.name]) + + # BODY block (input: a, acc, iter, output: a, acc, iter) + # Note that local intermediate tensors need to be declared here for the outputs + self.ser.startBasicBlock(body_block) + self.ser.addInputTensor(iter) + self.ser.addInputTensor(a) + self.ser.addInputTensor(acc) + one_tens = self.ser.addConst([], DType.INT32, [], [], [np.int32(1)]) + iter_body_out = self.ser.addIntermediate(iter.shape, iter.dtype, iter.usage, iter.dformat) + acc_body_out = self.ser.addIntermediate(acc.shape, acc.dtype, acc.usage, acc.dformat) + self.ser.addOperator(Op.ADD, [a.name, acc.name], [acc_body_out.name]) + self.ser.addOperator(Op.SUB, [iter.name, one_tens.name], [iter_body_out.name]) + self.ser.addOutputTensor(iter_body_out) + self.ser.addOutputTensor(a) + self.ser.addOutputTensor(acc_body_out) + + return acc_out + + + def genOpTestList(self, opName, shapeFilter=[None], rankFilter=None, dtypeFilter=None): + + try: + op = self.TOSA_OP_LIST[opName] + except KeyError as e: + raise Exception('Cannot find op with name {}'.format(opName)) + + # Initialize a new random number generator + self.rng = np.random.default_rng(self.random_seed) + + build_fcn, tgen_fcn, agen_fcn = op['build_fcn'] + + # Generate the lists of arguments + rmin, rmax = op['rank'] + + # Test list consists of a tuple of: + # (opName, testNameStr, dtype, shapeList, argumentsList) + testList = [] + + if not shapeFilter: + shapeFilter = [None] + + for r in range(rmin, rmax + 1): + + # Filter out the rank? + if rankFilter is not None and r not in rankFilter: + continue + + for t in op['types']: + + # Filter tests based on dtype? + if dtypeFilter is not None: + if t not in dtypeFilter: + continue + + # Create the placeholder and const tensors + for shape in shapeFilter: + # A None shape chooses a random shape of a given rank + + # Filter out by rank + if shape is not None and len(shape) != r: + continue + + self.setTargetShape(shape) + shapeList = tgen_fcn(self, op, r) + + shapeStr = self.shapeStr(shapeList[0]) + typeStr = self.typeStr(t) + + # Argument lists consists of tuples of the (str, []) string representation and the build function argument list + argList = [] + if agen_fcn: + argList = agen_fcn(self, opName, shapeList, t) + else: + argList = [('', [])] + + for argStr, args in argList: + if argStr: + testStr = '{}_{}_{}_{}'.format(opName, shapeStr, typeStr, argStr) + else: + testStr = '{}_{}_{}'.format(opName, shapeStr, typeStr) + + testList.append((opName, testStr, t, shapeList, args)) + + return testList + + def serializeTest(self, opName, testStr, dtype, shapeList, testArgs): + try: + op = self.TOSA_OP_LIST[opName] + except KeyError as e: + raise Exception('Cannot find op with name {}'.format(opName)) + + # Create a serializer + self.createSerializer(opName, testStr) + + build_fcn, tgen_fcn, agen_fcn = op['build_fcn'] + pCount, cCount = op['operands'] + + try: + qgen = op['qgen'] + except KeyError: + qgen = None + + # Build the random tensor operands and the test + tens = [] + tens.extend(self.buildPlaceholderTensors(shapeList[0:pCount], dtype)) + tens.extend(self.buildConstTensors(shapeList[pCount:], dtype)) + + if qgen is not None: + qinfo = qgen(self, op, dtype) + else: + qinfo = None + + try: + if qinfo is not None: + resultName = build_fcn(self, op['op'], *tens, *testArgs, qinfo) + else: + resultName = build_fcn(self, op['op'], *tens, *testArgs) + except TypeError as e: + print('build_fcn: {}\nTensors: {}\nArgs: {}\n'.format(build_fcn, tens, testArgs)) + raise e + + # Save the serialized test + self.serialize('test') + + def createDynamicOpLists(self): + + # Dynamically create op lists for convolutions with a list of kernel sizes + KERNELS = [ [1, 1], [2, 2], [3, 3], [5, 5], [3, 1], [1, 3] ] + + for k in KERNELS: + testName = 'conv2d_{}x{}'.format(k[0], k[1]) + self.TOSA_OP_LIST[testName] = self.TOSA_OP_LIST['conv2d_TEMPLATE'].copy() + self.TOSA_OP_LIST[testName]['filter'] = k + self.TOSA_OP_LIST[testName]['template'] = False + + testName = 'depthwise_conv2d_{}x{}'.format(k[0], k[1]) + self.TOSA_OP_LIST[testName] = self.TOSA_OP_LIST['depthwise_conv2d_TEMPLATE'].copy() + self.TOSA_OP_LIST[testName]['filter'] = k + self.TOSA_OP_LIST[testName]['template'] = False + + testName = 'transpose_conv2d_{}x{}'.format(k[0], k[1]) + self.TOSA_OP_LIST[testName] = self.TOSA_OP_LIST['transpose_conv2d_TEMPLATE'].copy() + self.TOSA_OP_LIST[testName]['filter'] = k + self.TOSA_OP_LIST[testName]['template'] = False + + # Delete any templates after having created any dynamic ops + # This is a two-pass operation because it's bad practice to delete + # keys from dictionaries while iterating + keyList = [] + for k in self.TOSA_OP_LIST: + try: + if self.TOSA_OP_LIST[k]['template'] == True: + keyList.append(k) + continue + except KeyError: + pass + + for k in keyList: + del self.TOSA_OP_LIST[k] + + def initOpListDefaults(self): + '''Fill in default fields for ops if they aren't already specified. + Look for missing required fields (datastructure linting).''' + for op in self.TOSA_OP_LIST: + + # Required fields + try: + pl, c = self.TOSA_OP_LIST[op]['operands'] + except (KeyError, ValueError, TypeError): + raise Exception('Op {} is missing a valid operand tuple in TOSA_OP_LIST'.format(op)) + + try: + fcn, tgen, arggen = self.TOSA_OP_LIST[op]['build_fcn'] + except (KeyError, ValueError, TypeError): + raise Exception('Op {} is missing a valid build_fcn tuple in TOSA_OP_LIST'.format(op)) + + try: + types = self.TOSA_OP_LIST[op]['types'] + except KeyError as e: + raise Exception('Op {} is missing a valid type list in TOSA_OP_LIST'.format(op)) + + try: + opcode = self.TOSA_OP_LIST[op]['op'] + except KeyError as e: + raise Exception('Op {} is missing the Op field in TOSA_OP_LIST'.format(op)) + + # Put in default rank range, if missing + try: + rank = self.TOSA_OP_LIST[op]['rank'] + except KeyError: + self.TOSA_OP_LIST[op]['rank'] = self.DEFAULT_RANK_RANGE + + # Tensor operator list + # 'op': op name + # 'operands': tuple of (placeholder, const) operands + # 'rank': optional, restricts rank to tuple inclusive of (min, max), if not specified, defaults to (1, 4) + # 'build_fcn': tuple of the function to (build_operator(), TensorGen function, ArgGen enum) + # 'types': array of datatypes to be tested + TYPE_FP = [ DType.FLOAT ] + + # Type with an aint8 + TYPE_INT = [ DType.AINT8, DType.INT16, DType.INT32 ] # Most operators support AINT8 instead of INT8, excludes INT4 + TYPE_INT_FP = [ DType.AINT8, DType.INT16, DType.INT32, DType.FLOAT ] # Most operators support AINT8 instead of INT8, excludes INT4 + + # Types with an int8 + TYPE_PURE_INT = [ DType.INT8, DType.INT16, DType.INT32 ] # Note: excludes INT4 + TYPE_PURE_INT_FP = [ DType.INT8, DType.INT16, DType.INT32, DType.FLOAT ] # Note: excludes INT4 + TYPE_BOOL = [ DType.BOOL ] + TYPE_FI32 = [ DType.FLOAT, DType.INT32 ] + TYPE_FIB = [ DType.FLOAT, DType.AINT8, DType.INT8, DType.INT16, DType.INT32, DType.BOOL ] + TYPE_FI16 = [ DType.FLOAT, DType.INT16 ] + + TYPE_NARROW_INT_FP = [ DType.AINT8, DType.INT16, DType.FLOAT ] + + DEFAULT_RANK_RANGE = (1, 4) + + TOSA_OP_LIST = { + # Binary ops + 'add': + { 'op': Op.ADD, + 'operands': (2, 0), + 'build_fcn': (build_binary_broadcast, TosaTensorGen.tgBroadcastFuzz, None), + 'types': TYPE_FI32 }, + + 'arithmetic_right_shift': + { 'op': Op.ARITHMETIC_RIGHT_SHIFT, + 'operands': (2, 0), + 'build_fcn': (build_binary_broadcast, TosaTensorGen.tgBroadcastFuzz, None), + 'types': TYPE_PURE_INT }, + + 'bitwise_and': + { 'op': Op.BITWISE_AND, + 'operands': (2, 0), + 'build_fcn': (build_binary_broadcast, TosaTensorGen.tgBroadcastFuzz, None), + 'types': TYPE_INT }, + + 'bitwise_or': + { 'op': Op.BITWISE_OR, + 'operands': (2, 0), + 'build_fcn': (build_binary_broadcast, TosaTensorGen.tgBroadcastFuzz, None), + 'types': TYPE_INT }, + + 'bitwise_xor': + { 'op': Op.BITWISE_XOR, + 'operands': (2, 0), + 'build_fcn': (build_binary_broadcast, TosaTensorGen.tgBroadcastFuzz, None), + 'types': TYPE_INT }, + + 'logical_and': + { 'op': Op.LOGICAL_AND, + 'operands': (2, 0), + 'build_fcn': (build_binary_broadcast, TosaTensorGen.tgBroadcastFuzz, None), + 'types': TYPE_BOOL }, + + 'logical_left_shift': + { 'op': Op.LOGICAL_LEFT_SHIFT, + 'operands': (2, 0), + 'build_fcn': (build_binary_broadcast, TosaTensorGen.tgBroadcastFuzz, None), + 'types': TYPE_PURE_INT }, + + 'logical_right_shift': + { 'op': Op.LOGICAL_RIGHT_SHIFT, + 'operands': (2, 0), + 'build_fcn': (build_binary_broadcast, TosaTensorGen.tgBroadcastFuzz, None), + 'types': TYPE_PURE_INT }, + + 'logical_or': + { 'op': Op.LOGICAL_OR, + 'operands': (2, 0), + 'build_fcn': (build_binary_broadcast, TosaTensorGen.tgBroadcastFuzz, None), + 'types': TYPE_BOOL }, + + 'logical_xor': + { 'op': Op.LOGICAL_XOR, + 'operands': (2, 0), + 'build_fcn': (build_binary_broadcast, TosaTensorGen.tgBroadcastFuzz, None), + 'types': TYPE_BOOL }, + + 'max': + { 'op': Op.MAXIMUM, + 'operands': (2, 0), + 'build_fcn': (build_binary_broadcast, TosaTensorGen.tgBroadcastFuzz, None), + 'types': TYPE_FI32 }, + + 'min': + { 'op': Op.MINIMUM, + 'operands': (2, 0), + 'build_fcn': (build_binary_broadcast, TosaTensorGen.tgBroadcastFuzz, None), + 'types': TYPE_FI32 }, + + 'mul': + { 'op': Op.MUL, + 'operands': (2, 0), + 'build_fcn': (build_mul, TosaTensorGen.tgBroadcastFuzz, None), + 'types': TYPE_PURE_INT_FP }, + + 'pow': + { 'op': Op.POW, + 'operands': (2, 0), + 'build_fcn': (build_binary_broadcast, TosaTensorGen.tgBasic, None), + 'types': TYPE_FP }, + + 'sub': + { 'op': Op.SUB, + 'operands': (2, 0), + 'build_fcn': (build_binary_broadcast, TosaTensorGen.tgBroadcastFuzz, None), + 'types': TYPE_FI32 }, + + 'table': + { 'op': Op.TABLE, + # Use the automatic generation functions to create the input array + # but create the table tensor in the build function, as it may be + # a different type from the input + 'operands': (1, 0), + 'build_fcn': (build_table, TosaTensorGen.tgBasic, None), + 'types': [ DType.INT16 ] }, + + 'argmax': + { 'op': Op.ARGMAX, + 'operands': (1, 0), + 'build_fcn': (build_argmax, TosaTensorGen.tgBasic, TosaArgGen.agAxis), + 'types': TYPE_FP }, + + # Templated operator. Filled in by createDynamicOpLists + 'conv2d_TEMPLATE': + { 'op': Op.CONV2D, + 'operands': (1, 2), + 'rank': (4, 4), + 'build_fcn': (build_conv2d, TosaTensorGen.tgConv2D, TosaArgGen.agConv2D), + 'qgen': TosaQuantGen.qgConv, + 'types': TYPE_FP, + 'template': True }, + + # Templated operator. Filled in by createDynamicOpLists + 'depthwise_conv2d_TEMPLATE': + { 'op': Op.DEPTHWISE_CONV2D, + 'operands': (1, 2), + 'filter': [1, 1], + 'rank': (4, 4), + 'build_fcn': (build_depthwise_conv2d, TosaTensorGen.tgDepthwiseConv2D, TosaArgGen.agConv2D), + 'qgen': TosaQuantGen.qgConv, + 'types': TYPE_FP, + 'template': True }, + + # Templated operator. Filled in by createDynamicOpLists + 'transpose_conv2d_TEMPLATE': + { 'op': Op.TRANSPOSE_CONV2D, + 'operands': (1, 1), + 'rank': (4, 4), + 'build_fcn': (build_transpose_conv2d, TosaTensorGen.tgTransposeConv2D, TosaArgGen.agTransposeConv2D), + 'qgen': TosaQuantGen.qgConv, + 'types': TYPE_FP, + 'template': True }, + + 'fully_connected': + { 'op': Op.FULLY_CONNECTED, + 'operands': (2, 0), + 'rank': (2, 2), + 'build_fcn': (build_fully_connected, TosaTensorGen.tgFullyConnected, None), + 'qgen': TosaQuantGen.qgConv, + 'types': TYPE_FP }, + + 'matmul': + { 'op': Op.MATMUL, + 'operands': (2, 0), + 'rank': (2, 2), + 'build_fcn': (build_matmul, TosaTensorGen.tgMatmul, None), + 'qgen': TosaQuantGen.qgMatmul, + 'types': TYPE_NARROW_INT_FP }, + + # Unary operators + 'abs': + { 'op': Op.ABS, + 'operands': (1, 0), + 'build_fcn': (build_unary, TosaTensorGen.tgBasic, None), + 'types': TYPE_FI32 }, + + 'bitwise_not': + { 'op': Op.BITWISE_NOT, + 'operands': (1, 0), + 'build_fcn': (build_unary, TosaTensorGen.tgBasic, None), + 'types': TYPE_INT }, + + 'ceil': + { 'op': Op.CEIL, + 'operands': (1, 0), + 'build_fcn': (build_unary, TosaTensorGen.tgBasic, None), + 'types': TYPE_FP }, + + 'clz': + { 'op': Op.CLZ, + 'operands': (1, 0), + 'build_fcn': (build_unary, TosaTensorGen.tgBasic, None), + 'types': [ DType.INT32 ] }, + + 'exp': + { 'op': Op.EXP, + 'operands': (1, 0), + 'build_fcn': (build_unary, TosaTensorGen.tgBasic, None), + 'types': TYPE_FP }, + + 'floor': + { 'op': Op.FLOOR, + 'operands': (1, 0), + 'build_fcn': (build_unary, TosaTensorGen.tgBasic, None), + 'types': TYPE_FP }, + + 'log': + { 'op': Op.LOG, + 'operands': (1, 0), + 'build_fcn': (build_unary, TosaTensorGen.tgBasic, None), + 'types': TYPE_FP }, + + 'floor': + { 'op': Op.FLOOR, + 'operands': (1, 0), + 'build_fcn': (build_unary, TosaTensorGen.tgBasic, None), + 'types': TYPE_FP }, + + 'logical_not': + { 'op': Op.LOGICAL_NOT, + 'operands': (1, 0), + 'build_fcn': (build_unary, TosaTensorGen.tgBasic, None), + 'types': TYPE_BOOL }, + + 'negate': + { 'op': Op.NEGATE, + 'operands': (1, 0), + 'build_fcn': (build_unary, TosaTensorGen.tgBasic, None), + 'qgen': TosaQuantGen.qgUnary, + 'types': TYPE_INT_FP }, + + 'reciprocal': + { 'op': Op.RECIPROCAL, + 'operands': (1, 0), + 'build_fcn': (build_unary, TosaTensorGen.tgBasic, None), + 'types': TYPE_FP }, + + 'rsqrt': + { 'op': Op.RSQRT, + 'operands': (1, 0), + 'build_fcn': (build_unary, TosaTensorGen.tgBasic, None), + 'types': TYPE_FP }, + + # Ternary operators + 'select': + { 'op': Op.SELECT, + 'operands': (3, 0), + 'build_fcn': (build_select, TosaTensorGen.tgBroadcastFuzz, None), + 'types': TYPE_FIB }, + + # Comparison operators + 'equal': + { 'op': Op.EQUAL, + 'operands': (2, 0), + 'build_fcn': (build_comparison, TosaTensorGen.tgBroadcastFuzz, None), + 'types': TYPE_FI32 }, + + 'greater_equal': + { 'op': Op.GREATER_EQUAL, + 'operands': (2, 0), + 'build_fcn': (build_comparison, TosaTensorGen.tgBroadcastFuzz, None), + 'types': TYPE_FI32 }, + + 'greater': + { 'op': Op.GREATER, + 'operands': (2, 0), + 'build_fcn': (build_comparison, TosaTensorGen.tgBroadcastFuzz, None), + 'types': TYPE_FI32 }, + + # Pooling operators + 'avg_pool2d': + { 'op': Op.AVG_POOL2D, + 'operands': (1, 0), + 'rank': (4, 4), + 'build_fcn': (build_pool2d, TosaTensorGen.tgNHWC, TosaArgGen.agPooling), + 'qgen': TosaQuantGen.qgUnary, + 'types': TYPE_NARROW_INT_FP }, + + + 'max_pool2d': + { 'op': Op.MAX_POOL2D, + 'operands': (1, 0), + 'rank': (4, 4), + 'build_fcn': (build_pool2d, TosaTensorGen.tgNHWC, TosaArgGen.agPooling), + 'types': TYPE_NARROW_INT_FP }, + + # Reduce operators + 'reduce_any': + { 'op': Op.REDUCE_ANY, + 'operands': (1, 0), + 'build_fcn': (build_reduce, TosaTensorGen.tgBasic, TosaArgGen.agAxis), + 'types': TYPE_BOOL }, + + 'reduce_all': + { 'op': Op.REDUCE_ALL, + 'operands': (1, 0), + 'build_fcn': (build_reduce, TosaTensorGen.tgBasic, TosaArgGen.agAxis), + 'types': TYPE_BOOL }, + + 'reduce_max': + { 'op': Op.REDUCE_MAX, + 'operands': (1, 0), + 'build_fcn': (build_reduce, TosaTensorGen.tgBasic, TosaArgGen.agAxis), + 'types': TYPE_INT_FP }, + + 'reduce_min': + { 'op': Op.REDUCE_MAX, + 'operands': (1, 0), + 'build_fcn': (build_reduce, TosaTensorGen.tgBasic, TosaArgGen.agAxis), + 'types': TYPE_INT_FP }, + + 'reduce_product': + { 'op': Op.REDUCE_PRODUCT, + 'operands': (1, 0), + 'build_fcn': (build_reduce, TosaTensorGen.tgBasic, TosaArgGen.agAxis), + 'types': TYPE_FP }, + + 'reduce_sum': + { 'op': Op.REDUCE_SUM, + 'operands': (1, 0), + 'build_fcn': (build_reduce, TosaTensorGen.tgBasic, TosaArgGen.agAxis), + 'types': TYPE_FI32 }, + + # Activation functions + 'clamp': + { 'op': Op.CLAMP, + 'operands': (1, 0), + 'build_fcn': (build_clamp, TosaTensorGen.tgBasic, None), + 'types': TYPE_NARROW_INT_FP }, + + 'relun': + { 'op': Op.RELUN, + 'operands': (1, 0), + 'build_fcn': (build_relun, TosaTensorGen.tgBasic, None), + 'types': TYPE_FI32 }, + + 'sigmoid': + { 'op': Op.SIGMOID, + 'operands': (1, 0), + 'build_fcn': (build_sigmoid, TosaTensorGen.tgBasic, None), + 'types': TYPE_FP }, + + 'tanh': + { 'op': Op.TANH, + 'operands': (1, 0), + 'build_fcn': (build_tanh, TosaTensorGen.tgBasic, None), + 'types': TYPE_FP }, + + # Data layout operators + 'concat': + { 'op': Op.CONCAT, + 'operands': (2, 0), + 'build_fcn': (build_concat, TosaTensorGen.tgBasic, TosaArgGen.agAxis), + 'types': TYPE_FIB }, + + 'pad': + { 'op': Op.PAD, + 'operands': (1, 0), + 'build_fcn': (build_pad, TosaTensorGen.tgBasic, TosaArgGen.agPad), + 'qgen': TosaQuantGen.qgPad, + 'types': TYPE_FIB }, + + 'reshape': + { 'op': Op.RESHAPE, + 'operands': (1, 0), + 'build_fcn': (build_reshape, TosaTensorGen.tgBasic, TosaArgGen.agReshape), + 'types': TYPE_FIB }, + + 'reverse': + { 'op': Op.REVERSE, + 'operands': (1, 0), + 'build_fcn': (build_reverse, TosaTensorGen.tgBasic, TosaArgGen.agAxis), + 'types': TYPE_FIB }, + + 'slice': + { 'op': Op.SLICE, + 'operands': (1, 0), + 'build_fcn': (build_slice, TosaTensorGen.tgBasic, TosaArgGen.agSlice), + 'types': TYPE_FIB }, + + 'tile': + { 'op': Op.TILE, + 'operands': (1, 0), + 'build_fcn': (build_tile, TosaTensorGen.tgBasic, TosaArgGen.agTile), + 'types': TYPE_FIB }, + + 'transpose': + { 'op': Op.TRANSPOSE, + 'operands': (1, 0), + 'rank': (2, 4), # Do not allow tranpose on rank=1 + 'build_fcn': (build_transpose, TosaTensorGen.tgBasic, TosaArgGen.agTranspose), + 'types': TYPE_FIB }, + + # Scatter/Gather + 'gather': + { 'op': Op.GATHER, + 'operands': (1, 0), + 'build_fcn': (build_gather, TosaTensorGen.tgBasic, TosaArgGen.agAxis), + 'types': TYPE_INT }, + + + # Image operations + 'resize': + { 'op': Op.RESIZE, + 'operands': (1, 0), + 'rank': (4, 4), + 'build_fcn': ( build_resize, TosaTensorGen.tgNHWC, TosaArgGen.agResize), + 'types': [ DType.INT8, DType.INT16 ] }, + + + # Data nodes + 'placeholder': + { 'op': Op.PLACEHOLDER, + 'operands': (1, 0), + 'build_fcn': ( build_placeholder, TosaTensorGen.tgBasic, None), + 'types': TYPE_FIB }, + + 'const': + { 'op': Op.CONST, + 'operands': (1, 0), + 'build_fcn': ( build_placeholder, TosaTensorGen.tgBasic, None), + 'types': TYPE_FIB }, + + + 'identity': + { 'op': Op.IDENTITY, + 'operands': (1, 0), + 'build_fcn': ( build_unary, TosaTensorGen.tgBasic, None), + 'types': TYPE_FIB }, + + + 'identityn': + { 'op': Op.IDENTITYN, + 'operands': (2, 0), + 'build_fcn': ( build_identityn, TosaTensorGen.tgBasic, None), + 'types': TYPE_FIB }, + + # Type conversion + 'cast': + { 'op': Op.CAST, + 'operands': (1, 0), + 'build_fcn': ( build_cast, TosaTensorGen.tgBasic, TosaArgGen.agCast ), + 'types': [ DType.FLOAT, DType.INT8, DType.INT16, DType.INT32, DType.BOOL ] }, + + 'rescale': + { 'op': Op.RESCALE, + 'operands': (1, 0), + 'build_fcn': ( build_rescale, TosaTensorGen.tgBasic, TosaArgGen.agRescale ), + 'types': [ DType.AINT8, DType.INT16, DType.INT32, DType.INT48 ] }, + + # Custom + # Not implemented. + + # Control flow + + # Two varients of cond_if, one that generates one of two constant tensors (no + # inputs to the basic blocks, one output) and another that either adds or subtracts two tensors + # (two inputs to the basic blocks, one output) + 'cond_if_const': + { 'op': Op.COND_IF, + 'operands': (0, 2), + 'build_fcn': ( build_cond_if_const, TosaTensorGen.tgBasic, TosaArgGen.agCondIf ), + 'types': [ DType.BOOL ] }, + + 'cond_if_binary': + { 'op': Op.COND_IF, + 'operands': (2, 0), + 'build_fcn': ( build_cond_if_binary, TosaTensorGen.tgBasic, TosaArgGen.agCondIf ), + 'types': TYPE_FI32 }, + + # while_loop + 'while_loop': + { 'op': Op.WHILE_LOOP, + 'operands': (0, 1), + 'build_fcn': ( build_while_loop, TosaTensorGen.tgBasic, TosaArgGen.agWhileLoop ), + 'types': [DType.INT32] }, + + + } + +class OutputShaper: + # Methods in this class compute the expected output shape and datatype + # for common classes of operations + def __init__(self): + pass + + # These methods return arguments that can be used for + # creating a new output tensor + @staticmethod + def binaryBroadcastOp(ser, a, b): + assert(len(a.shape) == len(b.shape)) + assert(a.dtype == b.dtype) + + shape = [] + for i in range(len(a.shape)): + if a.shape[i] == 1: + shape.append(b.shape[i]) + else: + shape.append(a.shape[i]) + + return ser.addOutput(shape, a.dtype, a.usage, a.dformat) + + @staticmethod + def binaryNonBroadcastOp(ser, a, b): + assert(len(a.shape) == len(b.shape)) + assert(a.dtype == b.dtype) + + shape = [] + for i in range(len(a.shape)): + assert(a.shape[i] == b.shape[i]) + shape.append(a.shape[i]) + + return ser.addOutput(shape, a.dtype, a.usage, a.dformat) + + @staticmethod + def unaryOp(ser, a): + return ser.addOutput(a.shape, a.dtype, a.usage, a.dformat) + + @staticmethod + def selectOp(ser, cond, a, b): + assert(len(a.shape) == len(b.shape) and len(a.shape) == len(cond.shape)) + assert(a.dtype == b.dtype) + + shape = [] + for i in range(len(a.shape)): + shape.append(max(cond.shape[i], a.shape[i], b.shape[i])) + + return ser.addOutput(shape, a.dtype, a.usage, a.dformat) + + @staticmethod + def binaryComparisonOp(ser, a, b): + assert(len(a.shape) == len(b.shape)) + assert(a.dtype == b.dtype) + + # Do broadcast + shape = [] + for i in range(len(a.shape)): + if a.shape[i] == 1: + shape.append(b.shape[i]) + else: + shape.append(a.shape[i]) + + # Force the output type to bool + return ser.addOutput(shape, DType.BOOL, a.usage, a.dformat) + + @staticmethod + def reduceOp(ser, a, axis): + + shape = a.shape.copy() + + shape[axis] = 1 + + return ser.addOutput(shape, a.dtype, a.usage, a.dformat) + + @staticmethod + def argmaxOp(ser, a, axis): + shape = a.shape.copy() + del shape[axis] + return ser.addOutput(shape, DType.INT32, a.usage, a.dformat) + + @staticmethod + def conv2dOp(ser, ifm, filter, strides, padding, dilations): + + # IFM: NHWC + # 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) + \ + padding[0] + padding[1]) // strides[0] + 1 + + w = (ifm.shape[2] - filter.shape[2] - (filter.shape[2] - 1) * (dilations[1] - 1) + \ + padding[2] + padding[3]) // strides[1] + 1 + + if h <= 0 or w <= 0: + # Invalid test parameters? + h = 0 + w = 0 + ser.setExpectedFailure(True, 'Invalid combination of conv2d parameters') + + ofm_shape = [ifm.shape[0], h, w, filter.shape[0]] + + if ifm.dtype == DType.AINT8 or ifm.dtype == DType.INT8: + out_dtype = DType.INT32 + elif ifm.dtype == DType.INT16: + out_dtype = DType.INT48 + elif ifm.dtype == DType.FLOAT: + out_dtype = DType.FLOAT + else: + raise Exception('Unsupported input dtype: {}'.format(ifm.dtype)) + + return ser.addOutput(ofm_shape, out_dtype, ifm.usage, ifm.dformat) + + @staticmethod + def depthwiseConv2dOp(ser, ifm, filter, strides, padding, dilations): + # IFM: NHWC + # Filter: HWCM + # OFM: NHW C*M + h = (ifm.shape[1] - filter.shape[0] - (filter.shape[0] - 1) * (dilations[0] - 1) + \ + padding[0] + padding[1]) // strides[0] + 1 + + w = (ifm.shape[2] - filter.shape[1] - (filter.shape[1] - 1) * (dilations[1] - 1) + \ + padding[2] + padding[3]) // strides[1] + 1 + + if h <= 0 or w <= 0: + # Invalid test parameters? + h = 0 + w = 0 + ser.setExpectedFailure(True, 'Invalid combination of conv2d parameters') + + ofm_shape = [ifm.shape[0], h, w, filter.shape[2] * filter.shape[3]] + + if ifm.dtype == DType.AINT8 or ifm.dtype == DType.INT8: + out_dtype = DType.INT32 + elif ifm.dtype == DType.INT16: + out_dtype = DType.INT48 + elif ifm.dtype == DType.FLOAT: + out_dtype = DType.FLOAT + else: + raise Exception('Unsupported input dtype: {}'.format(ifm.dtype)) + + return ser.addOutput(ofm_shape, out_dtype, ifm.usage, ifm.dformat) + + + @staticmethod + def pool2dOp(ser, ifm, kernel, stride, pad): + # input: NHWC + 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] + + if h <= 0 or w <= 0: + # Invalid test parameters? + h = 0 + w = 0 + ser.setExpectedFailure(True, 'Invalid combination of pooling parameters') + + ofm_shape = [ifm.shape[0], h, w, ifm.shape[3]] + return ser.addOutput(ofm_shape, ifm.dtype, ifm.usage, ifm.dformat) + + @staticmethod + def fullyConnectedOp(ser, input, filter): + # input: N, IC + # filter: OC, IC + # output: N, OC + + output_shape = [input.shape[0], filter.shape[0]] + + if input.dtype == DType.AINT8 or input.dtype == DType.INT8: + out_dtype = DType.INT32 + elif input.dtype == DType.INT16: + out_dtype = DType.INT48 + elif input.dtype == DType.FLOAT: + out_dtype = DType.FLOAT + else: + raise Exception('Unsupported input dtype: {}'.format(input.dtype)) + + return ser.addOutput(output_shape, out_dtype, input.usage, input.dformat) + + @staticmethod + def matmulOp(ser, a, b): + # a: M, K + # b: K, N + # out: M, N + + output_shape = [a.shape[0], b.shape[1]] + + + if a.dtype == DType.AINT8: + out_dtype = DType.INT32 + elif a.dtype == DType.INT16: + out_dtype = DType.INT48 + elif a.dtype == DType.FLOAT: + out_dtype = DType.FLOAT + else: + raise Exception('UNsupported input dtype for matmul: {}'.format(a.dtype)) + + return ser.addOutput(output_shape, out_dtype, a.usage, a.dformat) + + @staticmethod + def concatOp(ser, a, b, axis): + + output_shape = a.shape.copy() + output_shape[axis] = a.shape[axis] + b.shape[axis] + + return ser.addOutput(output_shape, a.dtype, a.usage, a.dformat) + + @staticmethod + def padOp(ser, a, padding): + + output_shape = a.shape.copy() + + for i in range(len(output_shape)): + output_shape[i] = padding[i][0] + padding[i][1] + output_shape[i] + + return ser.addOutput(output_shape, a.dtype, a.usage, a.dformat) + + @staticmethod + def reshapeOp(ser, a, shape): + output_shape = shape.copy() + + totalElements = 1 + for i in a.shape: + totalElements *= i + + # If there are any -1 elements, figure out what that dimension must be + totalOutputElements = 1 + for i in output_shape: + if i != -1: + totalOutputElements *= i + + # And fill it in + for i in range(len(output_shape)): + if output_shape[i] == -1: + output_shape[i] = totalElements // totalOutputElements + + return ser.addOutput(output_shape, a.dtype, a.usage, a.dformat) + + @staticmethod + def sliceOp(ser, a, begin, size): + + output_shape = size.copy() + return ser.addOutput(output_shape, a.dtype, a.usage, a.dformat) + + @staticmethod + def tileOp(ser, a, multiples): + + output_shape = a.shape.copy() + assert(len(multiples) == len(output_shape)) + + for i in range(len(output_shape)): + output_shape[i] = a.shape[i] * multiples[i] + + return ser.addOutput(output_shape, a.dtype, a.usage, a.dformat) + + @staticmethod + def transposeOp(ser, a, perms): + output_shape = a.shape.copy() + assert(len(perms) == len(output_shape)) + + for i in range(len(output_shape)): + output_shape[i] = a.shape[perms[i]] + + return ser.addOutput(output_shape, a.dtype, a.usage, a.dformat) + + @staticmethod + def gatherOp(ser, values, indicies, axis): + # indicies minus the axis + values - the indexes used to look up values. + output_shape = [*values.shape[0:axis], indicies.shape[0], *values.shape[axis+1:]] + + return ser.addOutput(output_shape, values.dtype, indicies.usage, indicies.dformat) + + @staticmethod + def tableOp(ser, input, table): + # Same shape as the input, but with the type of the table. + return ser.addOutput(input.shape, DType.INT32, input.usage, input.dformat) + + @staticmethod + def resizeOp(ser, input, mode, stride, offset, shift, output_dims, output_dtype): + + output_dims = [input.shape[0], output_dims[0], output_dims[1], input.shape[3]] + + if stride[0] <= 0 or stride[1] <= 0: + ser.setExpectedFailure(True, 'Negative or zero stride') + + return ser.addOutput(output_dims, output_dtype, input.usage, input.dformat) + + @staticmethod + def typeConversionOp(ser, val, out_dtype): + return ser.addOutput(val.shape, out_dtype, val.usage, val.dformat) + + @staticmethod + def transposeConv2DOp(ser, ifm, output_shape): + if ifm.dtype == DType.AINT8 or ifm.dtype == DType.INT8: + out_dtype = DType.INT32 + elif ifm.dtype == DType.INT16: + out_dtype = DType.INT48 + elif ifm.dtype == DType.FLOAT: + out_dtype = DType.FLOAT + else: + raise Exception('Unsupported input dtype: {}'.format(ifm.dtype)) + + if output_shape[1] <= 0 or output_shape[2] <= 0: + ser.setExpectedFailure(True, 'Negative output shape') + + return ser.addOutput(output_shape, out_dtype, ifm.usage, ifm.dformat) -- cgit v1.2.1