aboutsummaryrefslogtreecommitdiff
path: root/verif/tosa_test_gen.py
diff options
context:
space:
mode:
Diffstat (limited to 'verif/tosa_test_gen.py')
-rw-r--r--verif/tosa_test_gen.py2301
1 files changed, 2301 insertions, 0 deletions
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)