blob: b9cca18d2dfc3a7c0036dc03aae885c329578574 [file] [log] [blame]
#!/usr/bin/env python3
# Copyright (c) 2020-2021, 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
import itertools
from enum import IntEnum, Enum, unique
from tosa_ref_run import TosaReturnCode
# Include the ../thirdparty/serialization_lib/python directory in PYTHONPATH
parent_dir = os.path.dirname(os.path.realpath(__file__))
sys.path.append(
os.path.join(parent_dir, "..", "thirdparty", "serialization_lib", "python")
)
import tosa_serializer as ts
from tosa_serializer import *
import tosa
from tosa_error_if import ErrorIf
# Convenience variables to the flatc-generated types that should be enums, but aren't
DType = tosa.DType.DType()
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 getQinfo(testGen, dtype, error_name=None):
if dtype == DType.INT8:
return testGen.randInt(-128, 128)
elif dtype == DType.UINT8:
return testGen.randInt(0, 256)
elif error_name in [ErrorIf.InputZeroPointNotZero, ErrorIf.WeightZeroPointNotZero, ErrorIf.OutputZeroPointNotZero]:
zero_point = testGen.randInt(-128, 128)
if zero_point == 0:
zero_point = 1
return zero_point
return 0
@staticmethod
def qgUnary(testGen, op, dtype, error_name=None):
qinfo = ts.TosaSerializerQuantInfo()
if error_name == ErrorIf.InputZeroPointNotZero:
qinfo.UnaryQuantInfo(
TosaQuantGen.getQinfo(testGen, dtype, error_name), TosaQuantGen.getQinfo(testGen, dtype)
)
elif error_name == ErrorIf.OutputZeroPointNotZero:
qinfo.UnaryQuantInfo(
TosaQuantGen.getQinfo(testGen, dtype), TosaQuantGen.getQinfo(testGen, dtype, error_name)
)
else:
qinfo.UnaryQuantInfo(
TosaQuantGen.getQinfo(testGen, dtype), TosaQuantGen.getQinfo(testGen, dtype)
)
return qinfo
@staticmethod
def qgConv(testGen, op, dtype_or_dtypeList, error_name=None):
qinfo = ts.TosaSerializerQuantInfo()
if isinstance(dtype_or_dtypeList, list):
# a list of [input, weights, accumulator] dtypes
dtypeList = dtype_or_dtypeList
else:
# an int, [input, weights, accumulator] dtypes are the same
dtypeList = [dtype_or_dtypeList] * 3
if error_name == ErrorIf.InputZeroPointNotZero:
input_zp = TosaQuantGen.getQinfo(testGen, dtypeList[0], error_name)
weights_zp = TosaQuantGen.getQinfo(testGen, dtypeList[1])
elif error_name == ErrorIf.WeightZeroPointNotZero:
input_zp = TosaQuantGen.getQinfo(testGen, dtypeList[0])
weights_zp = TosaQuantGen.getQinfo(testGen, dtypeList[1], error_name)
else:
input_zp = TosaQuantGen.getQinfo(testGen, dtypeList[0])
weights_zp = TosaQuantGen.getQinfo(testGen, dtypeList[1])
qinfo.ConvQuantInfo(input_zp, weights_zp)
return qinfo
@staticmethod
def qgMatmul(testGen, op, dtype, error_name=None):
qinfo = ts.TosaSerializerQuantInfo()
if error_name == ErrorIf.InputZeroPointNotZero:
qinfo.MatMulQuantInfo(
TosaQuantGen.getQinfo(testGen, dtype, error_name), TosaQuantGen.getQinfo(testGen, dtype, error_name)
)
else:
qinfo.MatMulQuantInfo(
TosaQuantGen.getQinfo(testGen, dtype), TosaQuantGen.getQinfo(testGen, dtype)
)
return qinfo
@staticmethod
def qgPad(testGen, op, dtype, error_name=None):
qinfo = ts.TosaSerializerQuantInfo()
if error_name == ErrorIf.InputZeroPointNotZero:
qinfo.PadQuantInfo(TosaQuantGen.getQinfo(testGen, dtype, error_name))
else:
qinfo.PadQuantInfo(TosaQuantGen.getQinfo(testGen, dtype))
return qinfo
@staticmethod
def computeMultiplierAndShift(scaleFp, scale32):
# Derived from computeMultiplierAndShiftTosaScale32
# Provide a floating-point scaling factor and the scale32 parameter
# to compute the multiplier and shift
if scale32:
scaleBits = 31
else:
scaleBits = 15
m, shift = math.frexp(scaleFp)
if scaleFp < 0.0:
m = -m
multiplier = round(m * (1 << scaleBits))
assert multiplier <= (1 << scaleBits)
if multiplier == (1 << scaleBits):
multiplier = multiplier // 2
shift = shift + 1
shift = (-shift) + scaleBits
#print('scalefp {} scaleBits {} m {} mult {} shift {}'.format(scaleFp, scaleBits, m, multiplier, shift))
# Adjust multiplier such that shift is in allowed value range.
if shift == 0:
multiplier = multiplier // 4
shift = shift + 2
elif shift == 1:
multiplier = multiplier // 2
shift = shift + 1
elif shift == 63:
multiplier = multiplier * 2
shift = shift - 1
assert multiplier <= (1 << scaleBits)
assert shift >= 2 and shift <= 62
return multiplier, shift
class TosaTensorGen:
"""Tensor generators create a shape list for the placeholder and const tensor
data operands for the operator. The actual random data is generated separately for each test."""
def __init__(self):
pass
@staticmethod
def tgBasic(testGen, opName, rank, error_name=None):
pl, const = opName["operands"]
shape = testGen.makeShape(rank)
# Constrict dimension size for large ranks when creating WrongRank tests
shape = TosaErrorIfArgGen.eiRestrictDimension(shape, error_name)
shape_list = []
for i in range(pl + const):
shape_list.append(shape.copy())
if error_name == ErrorIf.RankMismatch:
if rank == 1 and i != 1:
shape = testGen.makeShape(rank + testGen.rng.choice([1, 2, 3]))
elif i != 1:
shape = testGen.makeShape(rank + testGen.rng.choice([-1, 1]))
return shape_list
@staticmethod
def tgNHWC(testGen, opName, rank, error_name=None):
pl, const = opName["operands"]
if error_name != ErrorIf.WrongRank:
assert rank == 4
shape = testGen.makeShape(rank)
# Constrict the batch size?
if testGen.args.max_batch_size:
shape[0] = (shape[0] % testGen.args.max_batch_size) + 1
# Constrict dimension size for large ranks when creating WrongRank tests
shape = TosaErrorIfArgGen.eiRestrictDimension(shape, error_name)
shape_list = []
for i in range(pl + const):
shape_list.append(shape.copy())
return shape_list
@staticmethod
def tgScatter(testGen, opName, rank, error_name=None):
pl, const = opName["operands"]
assert pl == 2
assert const == 0
assert rank == 3
values_in_shape = testGen.makeShape(rank)
# ignore max batch size if target shape is set
if testGen.args.max_batch_size and not testGen.args.target_shapes:
values_in_shape[0] = (values_in_shape[0] % testGen.args.max_batch_size) + 1
W = testGen.randInt(
testGen.args.tensor_shape_range[0], testGen.args.tensor_shape_range[1]
)
# Constrict W if one dimension is too large to keep tensor size reasonable
if max(values_in_shape) > 5000:
W = testGen.randInt(0, 16)
input_shape = [values_in_shape[0], W, values_in_shape[2]]
shape_list = []
shape_list.append(values_in_shape.copy())
shape_list.append(input_shape.copy())
return shape_list
@staticmethod
def tgBroadcastFuzz(testGen, op, rank, error_name=None):
shape = testGen.makeShape(rank)
pl, const = op["operands"]
shape_list = []
# Choose one of the inputs to broadcast
bcast_idx = testGen.randInt(0, pl + const)
for i in range(pl + const):
shape_bcast = shape.copy()
if error_name == ErrorIf.RankMismatch:
bcast_idx = -1 # Turn off broadcast because we are not testing it
if rank == 1 and i != 1:
shape_bcast = testGen.makeShape(rank + testGen.rng.choice([1, 2, 3]))
elif i != 1:
shape_bcast = testGen.makeShape(rank + testGen.rng.choice([-1, 1]))
# 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, error_name=None):
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 tgConv3D(testGen, op, rank, error_name=None):
pl, const = op["operands"]
assert rank == 5
# IFM dimensions are NDHWC
ifm_shape = testGen.makeShape(rank)
# Constrict the batch size?
if testGen.args.max_batch_size:
ifm_shape[0] = (ifm_shape[0] % testGen.args.max_batch_size) + 1
# Get the filter depth/height/width from the operator parameters
filter_dhw = op["filter"]
# Generate a random OFM channel
ofm_channel = testGen.makeShape(1)[0]
# The filter dimensions are ODHWI
filter_shape = np.asarray(
[ofm_channel, filter_dhw[0], filter_dhw[1], filter_dhw[2], ifm_shape[4]]
)
# The bias is OC
bias_shape = np.asarray([ofm_channel])
return [ifm_shape, filter_shape, bias_shape]
@staticmethod
def tgTransposeConv2D(testGen, op, rank, error_name=None):
pl, const = op["operands"]
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 tgDepthwiseConv2D(testGen, op, rank, error_name=None):
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, error_name=None):
pl, const = op["operands"]
if error_name != ErrorIf.WrongRank:
assert rank == 2
input_shape = testGen.makeShape(rank)
# Constrict dimension size for large ranks when creating WrongRank tests
shape = TosaErrorIfArgGen.eiRestrictDimension(input_shape, error_name)
filter_oc = testGen.rng.integers(
low=testGen.args.tensor_shape_range[0],
high=testGen.args.tensor_shape_range[1],
size=1,
)[0]
filter_shape = np.asarray([filter_oc, input_shape[1]])
bias_shape = np.asarray([filter_oc])
return [input_shape, filter_shape, bias_shape]
@staticmethod
def tgMatmul(testGen, op, rank, error_name=None):
pl, const = op["operands"]
if error_name != ErrorIf.WrongRank:
assert rank == 3
assert pl == 2 and const == 0
a_shape = testGen.makeShape(rank)
# Constrict dimension size for large ranks when creating WrongRank tests
shape = TosaErrorIfArgGen.eiRestrictDimension(a_shape, error_name)
# Get a random number for b_oc even if target shape is defined
b_oc = np.int32(
testGen.rng.integers(
low=testGen.args.tensor_shape_range[0],
high=testGen.args.tensor_shape_range[1],
size=1,
)
)[0]
# If N or H is large let b_oc be 1 to reduce output tensor size
if max(a_shape) > 1000:
b_oc = 1
b_shape = np.asarray([a_shape[0], a_shape[2], b_oc])
return [a_shape, b_shape]
@staticmethod
def tgConcat(testGen, opName, rank, error_name=None):
pl, const = opName["operands"]
shape = testGen.makeShape(rank)
# Create extra tensors to concat.
# Take into account value of pl when getting maximum number of concats
num_tensors = testGen.randInt(0, 4)
shape_list = []
for i in range(pl + const + num_tensors):
shape_list.append(shape.copy())
return shape_list
@staticmethod
def tgConcatConstInput(testGen, shapeList, axis, error_name=None):
# Split concat shape along axis to allow for multiple const inputs
# without making too many large tensors
if len(shapeList) == 2 or shapeList[0][axis] < len(shapeList):
return shapeList
# Create copy of shape we are going to split (so we don't alter shapeList)
shape = shapeList[0].copy()
# Add original shape as first input
new_shapeList = [shape.copy()]
length_on_axis = shape[axis]
remaining_length = length_on_axis
for i in range(len(shapeList) - 2):
# Calculate split on axis and remaining value
split_shape_val = int(shape[axis] / 2)
remaining_length = remaining_length - split_shape_val
# Append new shape, and set remaining shape
shape[axis] = split_shape_val
new_shapeList.append(shape.copy())
shape[axis] = remaining_length
if i == len(shapeList) - 3:
new_shapeList.append(shape.copy())
return new_shapeList
class TosaArgGen:
"""Argument generators create exhaustive or random lists of attributes for operators that take
attributes or other parameters. The return value is a list of (descriptive_name, [arglist])
tuples where the descriptive_name is appended to the test name and the arglist is expanded
as arguments to the operator build function."""
def __init__(self):
pass
@staticmethod
def agNone(testGen, opName, shapeList, dtype, error_name=None):
"""A trivial argument generator for operators that don't take any
non-tensor arguments"""
return [("", [])]
@staticmethod
def agAxis(testGen, opName, shapeList, dtype, error_name=None):
"""Build the axis argument for operators that take a single axis"""
axes = []
shape = shapeList[0]
if error_name == ErrorIf.AxisSmallerZero:
small_axis = testGen.rng.integers(-5, 0)
axes.append(("axis{}".format(small_axis), [small_axis]))
elif error_name == ErrorIf.AxisLargerRank:
large_axis = testGen.rng.integers(len(shape) + 1, len(shape) + 10)
axes.append(("axis{}".format(large_axis), [large_axis]))
else:
for a in range(0, len(shape)):
axes.append(("axis{}".format(a), [a]))
return axes
@staticmethod
def agConv(testGen, opName, shapeList, dtype, error_name=None):
arg_list = []
ifm_shape = shapeList[0]
filter_shape = shapeList[1]
# determine the kernel shape from the operator name (e.g. "conv2d_3x3" => [3,3])
k = [int(x) for x in opName.split("_")[-1].split("x")]
# Check the rank
rank = 5 if opName.startswith("conv3d") else 4
assert len(ifm_shape) == rank
assert len(filter_shape) == rank
# kernel rank omits batch and channels
k_rank = rank - 2
# Generate comprehensive argument lists
p_vals = [x for x in range(0, testGen.args.max_conv_padding + 1)]
paddings = {x for x in itertools.product(*([p_vals] * k_rank * 2))}
s_vals = [x for x in range(1, testGen.args.max_conv_stride + 1)]
strides = {x for x in itertools.product(*([s_vals] * k_rank))}
d_vals = [x for x in range(1, testGen.args.max_conv_dilation + 1)]
dilations = {x for x in itertools.product(*([d_vals] * k_rank))}
# add some oversize argument values
if max(ifm_shape) < 64:
bigPadding = 9
paddings.update({x for x in itertools.product(*([[0, bigPadding]] * (k_rank * 2)))})
bigStride = 8
strides.update({x for x in itertools.product(*([[1, bigStride]] * k_rank))})
bigDilation = 7
dilations.update({x for x in itertools.product(*([[1, bigDilation]] * k_rank))})
# There are too many parameter combinations, so generate them sparsely
# To get a variety of parameter combinations sparsity should not be a multiple of 2, 3 or 5
sparsity = len(paddings) * len(strides) * len(dilations) // 100 + 1
if sparsity < 13:
sparsity = 1
while sparsity % 2 == 0 or sparsity % 3 == 0 or sparsity % 5 == 0:
sparsity += 1
n = 0
for s in sorted(list(strides)):
for p in sorted(list(paddings)):
for d in sorted(list(dilations)):
if (n % sparsity == 0
# padding must not exceed the kernel size ?
# and p[0] < k[0] and p[1] < k[0] and p[2] < k[1] and p[3] < k[1]
# and (k_rank < 3 or (p[4] < k[2] and p[5] < k[2]))
# the padded shape must exceed the kernel size
and (ifm_shape[1] + p[0] + p[1]) > k[0] and (ifm_shape[2] + p[2] + p[3]) > k[1]
and (k_rank < 3 or ((ifm_shape[3] + p[4] + p[5]) > k[2]))
# the padded shape must exceed the dilation
and (ifm_shape[1] + p[0] + p[1]) > d[0] and (ifm_shape[2] + p[2] + p[3]) > d[1]
and (k_rank < 3 or ((ifm_shape[3] + p[4] + p[5]) > d[2]))
):
arg_list.append(
(
"st{}_pad{}_dilat{}".format(
"".join([str(x) for x in s]),
"".join([str(x) for x in p]),
"".join([str(x) for x in d]),
),
[s, p, d],
)
)
n += 1
return arg_list
@staticmethod
def agTransposeConv2D(testGen, opName, shapeList, dtype, error_name=None):
arg_list = []
ifm_shape = shapeList[0]
filter_shape = shapeList[1]
# Must be rank 4
assert len(ifm_shape) == 4
assert len(filter_shape) == 4
# Generate comprehensive argument lists
p_vals = [x for x in range(0, testGen.args.max_conv_padding + 1)]
paddings = {x for x in itertools.product(*([p_vals] * 2))}
s_vals = [x for x in range(1, testGen.args.max_conv_stride + 1)]
strides = {x for x in itertools.product(*([s_vals] * 2))}
d_vals = [x for x in range(1, testGen.args.max_conv_dilation + 1)]
dilations = {x for x in itertools.product(*([d_vals] * 2))}
# add some oversize argument values
if max(ifm_shape) < 64:
bigPadding = 9
paddings.update({x for x in itertools.product(*([[0, bigPadding]] * 2))})
bigStride = 8
strides.update({x for x in itertools.product(*([[1, bigStride]] * 2))})
bigDilation = 7
dilations.update({x for x in itertools.product(*([[1, bigDilation]] * 2))})
# There are too many parameter combinations, so generate them sparsely
# To get a variety of parameter combinations sparsity should not be a multiple of 2, 3 or 5
sparsity = len(paddings) * len(strides) * len(dilations) // 100 + 1
if sparsity < 13:
sparsity = 1
while sparsity % 2 == 0 or sparsity % 3 == 0 or sparsity % 5 == 0:
sparsity += 1
n = 0
for s in sorted(list(strides)):
for p in sorted(list(paddings)):
for d in sorted(list(dilations)):
if n % sparsity == 0:
# Determine the output shape
oh = (
ifm_shape[1]
- filter_shape[1]
- (filter_shape[1] - 1) * (d[0] - 1)
+ 2 * p[0]
) // s[0] + 1
ow = (
ifm_shape[2]
- filter_shape[2]
- (filter_shape[2] - 1) * (d[1] - 1)
+ 2 * p[1]
) // s[1] + 1
os = [ifm_shape[0], oh, ow, filter_shape[0]]
arg_list.append(
(
"st{}_pad{}_dilat{}_os{}".format(
"".join([str(x) for x in s]),
"".join([str(x) for x in p]),
"".join([str(x) for x in d]),
"x".join([str(x) for x in os]),
),
[s, p, d, os],
)
)
n += 1
return arg_list
@staticmethod
def agPad(testGen, opName, shapeList, dtype, error_name=None):
arg_list = []
rank = len(shapeList[0])
# Exhaustively test combinations of padding on each side of each dimension
# - the range of padding values is defined by pad_min and pad_max
# - for padding >9, the name format needs to be more distinctive
pad_min, pad_max = 0, 1
pad_values = [x for x in range(pad_min, pad_max + 1)]
if error_name == ErrorIf.PadSmallerZero:
pad_values = [x for x in range(-2, 0)]
axis_pad_values = [x for x in itertools.product(pad_values, pad_values)]
shape_pad_values = itertools.product(*([axis_pad_values] * rank))
if dtype in [DType.BOOL, DType.INT8, DType.INT16, DType.INT32]:
pad_const_int = testGen.getRandNumberDType(dtype)
pad_const_fp = 0
elif dtype == DType.FLOAT:
pad_const_int = 0
pad_const_fp = testGen.getRandNumberDType(dtype)
else:
return []
for paddings in shape_pad_values:
name = "pad"
for r in range(rank):
before, after = paddings[r]
name = f"{name}{before}{after}"
arg_list.append((name, [np.array(paddings), pad_const_int, pad_const_fp]))
return arg_list
@staticmethod
def agPooling(testGen, opName, shapeList, dtype, error_name=None):
arg_list = []
shape = shapeList[0]
if error_name != ErrorIf.WrongRank:
assert len(shape) == 4
# Generate comprehensive argument lists
p_vals = [x for x in range(0, testGen.args.max_pooling_padding + 1)]
paddings = {x for x in itertools.product(*([p_vals] * 4))}
s_vals = [x for x in range(1, testGen.args.max_pooling_stride + 1)]
strides = {x for x in itertools.product(*([s_vals] * 2))}
k_vals = [x for x in range(2, testGen.args.max_pooling_kernel + 2)]
kernels = {x for x in itertools.product(*([k_vals] * 2))}
# add some oversize argument values
bigStride = 7
strides.update({x for x in itertools.product(*([[1, bigStride]] * 2))})
bigKernel = 6
kernels.update({x for x in itertools.product(*([[2, bigKernel]] * 2))})
if max(shape) < 64:
# padding must be less than the kernel size
bigPadding = bigKernel - 1
paddings.update({x for x in itertools.product(*([[0, bigPadding]] * 4))})
# There are too many parameter combinations, so generate them sparsely
sparsity = len(paddings) * len(strides) * len(kernels) // 500 + 1
n = 0
for s in sorted(list(strides)):
for p in sorted(list(paddings)):
for k in sorted(list(kernels)):
if error_name in [ErrorIf.StrideSmallerOne, ErrorIf.KernelSmallerOne, ErrorIf.PadSmallerZero, ErrorIf.PadLargerEqualKernel]:
sNew, pNew, kNew = TosaErrorIfArgGen.eiPoolingErrorIf(testGen, error_name, s, p, k)
if None not in [sNew, pNew, kNew] and n % sparsity == 0:
arg_list.append(
(
"st{}_kern{}_pad{}".format(
"".join([str(x) for x in sNew]),
"".join([str(x) for x in kNew]),
"".join([str(x) for x in pNew]),
),
[sNew, pNew, kNew],
)
)
elif (n % sparsity == 0
# padding must not exceed the kernel size
and p[0] < k[0] and p[1] < k[0] and p[2] < k[1] and p[3] < k[1]
# the padded shape must exceed the kernel size
and (shape[1] + p[0] + p[1]) > k[0] and (shape[2] + p[2] + p[3]) > k[1]
):
arg_list.append(
(
"st{}_kern{}_pad{}".format(
"".join([str(x) for x in s]),
"".join([str(x) for x in k]),
"".join([str(x) for x in p]),
),
[s, p, k],
)
)
n += 1
return arg_list
@staticmethod
def agCast(testGen, opName, shapeList, inDtype, error_name=None):
arg_list = []
# Enumerate the output types here
if 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, error_name=None):
arg_list = []
# Enumerate the output types here
for dtype in [DType.UINT8, DType.INT8, DType.INT16, DType.INT32]:
if dtype in [DType.UINT8, DType.INT8] and error_name == ErrorIf.OutputZeroPointNotZero:
continue
if inDtype == DType.UINT8 and dtype != DType.INT8 and error_name != ErrorIf.WrongOutputType:
# The only output dtype for UINT8 is INT8, skip all other combinations
continue
if inDtype != DType.INT8 and dtype == DType.UINT8 and error_name != ErrorIf.WrongOutputType:
# The only input dtype for UINT8 is INT8, skip all other combinations
continue
if error_name == ErrorIf.WrongOutputType and not TosaErrorIfArgGen.eiRescaleWrongOutputType(inDtype, dtype):
continue
for scale32 in [False, True]:
if error_name == ErrorIf.ScaleTrue and scale32 == False:
continue
elif error_name == ErrorIf.ScaleNotTrue and scale32 == True:
continue
for double_round in [False, True]:
if error_name == ErrorIf.ScaleNotTrue and double_round == False:
continue
for per_channel in [False, True]:
if inDtype == DType.INT48 and scale32 and error_name != ErrorIf.ScaleTrue:
# Illegal condition. Must be scale32=False
continue
if double_round and not scale32 and error_name != ErrorIf.ScaleNotTrue:
# Illegal condition. ERROR_IF(!scale32 && double_round)
continue
arg_list.append(
(
"out{}_sc{}_dr{}_pc{}".format(
DTypeNames[dtype],
int(scale32),
int(double_round),
int(per_channel),
),
[dtype, scale32, double_round, per_channel],
)
)
return arg_list
@staticmethod
def agMul(testGen, opName, shapeList, dtype, error_name=None):
arg_list = []
if dtype is DType.INT32:
for p in range(testGen.args.num_rand_permutations):
shift = testGen.randInt(0, 32)
arg_list.append(("perm{}_shift{}".format(p, shift), [shift]))
else:
arg_list.append(("perm0_shift0", [0]))
return arg_list
@staticmethod
def agArithmeticRightShift(testGen, opName, shapeList, dtype, error_name=None):
arg_list = []
arg_list.append(("roundTrue", [True]))
arg_list.append(("roundFalse", [False]))
return arg_list
# Helper function for reshape. Gets some factors of a larger number.
@staticmethod
def getFactors(val, start=1):
factors = []
for i in range(start, int(np.sqrt(val)) + 1):
if (val % i) == 0:
factors.append(i)
return factors
@staticmethod
def agReshape(testGen, opName, shapeList, dtype, error_name=None):
arg_list = []
origShape = shapeList[0]
totalElements = 1
for s in origShape:
totalElements *= s
# This code is NOT fast. Fortunately, the numbers are fairly small.
factors = TosaArgGen.getFactors(totalElements)
for p in range(testGen.args.num_rand_permutations):
newRank = testGen.randInt(1, 7)
if len(factors) < newRank:
continue
found = True
# escape_counter breaks while loop if it continues on for too long
escape_counter = 0
while found:
newShape = []
# Generate newShape ensuring it isn't a duplicate
remainingElements = totalElements
shuffledFactors = testGen.rng.permutation(factors)
for i in range(1, newRank):
# pick rank-1 factors
newShape.append(shuffledFactors[0])
remainingElements = remainingElements // shuffledFactors[0]
shuffledFactors = testGen.rng.permutation(
TosaArgGen.getFactors(remainingElements)
)
newShape.append(remainingElements)
# Toss in a -1 sometimes
minusOne = testGen.randInt(0, newRank * 4)
if minusOne < newRank:
newShape[minusOne] = -1
# Check for duplicates
found = False
for name, other_shape in arg_list:
if other_shape[0] == newShape:
found = True
break
escape_counter += 1
if escape_counter >= 100:
break
if not found:
arg_list.append(("perm{}_rank{}".format(p, newRank), [newShape]))
return arg_list
@staticmethod
def agTranspose(testGen, opName, shapeList, dtype, error_name=None):
arg_list = []
ifm_shape = shapeList[0]
if error_name == ErrorIf.IndexOutsideBounds:
incorrect_large_index = range(len(ifm_shape)+1, 2*len(ifm_shape)+1)
incorrect_small_index = range(-len(ifm_shape), 0)
permutations = [p for p in itertools.permutations(incorrect_large_index)]
permutations.extend([p for p in itertools.permutations(incorrect_small_index)])
elif error_name == ErrorIf.IndexUsedTwice:
# Create list with a duplicated index
perm_range = list(range(len(ifm_shape)))
index_choice = testGen.rng.choice(range(len(perm_range)))
perm_range[(index_choice + 1) % len(perm_range)] = perm_range[index_choice]
permutations = [p for p in itertools.permutations(perm_range)]
else:
# Get all permutations
permutations = [p for p in itertools.permutations(range(len(ifm_shape)))]
# Limit to possible permutations from shape dimension or argument setting
limit = min(len(permutations), testGen.args.num_rand_permutations)
# Get random permutation generator that uses all permutations
random_permutations = testGen.rng.permutation(permutations)
# Create list of required amount of permutations
arg_list = [
("perm{}".format(p), [random_permutations[p].tolist()])
for p in range(limit)
]
return arg_list
@staticmethod
def agSlice(testGen, opName, shapeList, dtype, error_name=None):
arg_list = []
ifm_shape = shapeList[0]
rank = len(ifm_shape)
for p in range(testGen.args.num_rand_permutations):
start = []
size = []
valid = True
for i in range(rank):
if ifm_shape[i] > 1:
start.append(testGen.randInt(0, ifm_shape[i]))
size.append(testGen.randInt(0, ifm_shape[i] - start[i]))
# Invalid slice size?
if size[i] == 0:
valid = False
else:
start.append(0)
size.append(1)
if valid:
# If ERROR_IF test required then incorrect start, size will be returned
start, size = TosaErrorIfArgGen.eiSliceErrorIf(testGen, error_name, ifm_shape, start, size)
arg_list.append(("perm{}".format(p), [start, size]))
return arg_list
@staticmethod
def agTile(testGen, opName, shapeList, dtype, error_name=None):
arg_list = []
ifm_shape = shapeList[0]
rank = len(ifm_shape)
for p in range(testGen.args.num_rand_permutations):
# Pick a few random, but small multiple values
# because otherwise this has a tendency to generate
# enormous tensors
multiples = []
for i in range(rank):
if ifm_shape[i] > 1000:
# Multiple of 1 if ifm_shape dimension is large to reduce tensor size
multiples.append(1)
elif max(ifm_shape) > 1000:
multiples.append(2)
else:
multiples.append(testGen.randInt(1, 4))
arg_list.append(("perm{}".format(p), [multiples]))
return arg_list
@staticmethod
def agResize(testGen, opName, shapeList, dtype, error_name=None):
arg_list = []
ifm_shape = shapeList[0]
for mode in [ResizeMode.NEAREST, ResizeMode.BILINEAR]:
# Exclude illegal {mode, type} configurations. Pick legal output types
if mode == ResizeMode.NEAREST and dtype == DType.INT8:
outputDTypeList = [DType.INT8]
elif mode == ResizeMode.NEAREST and dtype == DType.INT16:
outputDTypeList = [DType.INT16]
elif mode == ResizeMode.BILINEAR and dtype == DType.INT8:
outputDTypeList = [DType.INT32]
elif mode == ResizeMode.BILINEAR and dtype == DType.INT16:
outputDTypeList = [DType.INT48]
elif dtype == DType.FLOAT:
outputDTypeList = [DType.FLOAT]
elif error_name == ErrorIf.WrongInputType:
# If an incorrect input type is used then we set a 'correct'
# output type to avoid other errors
outputDTypeList = [DType.INT8, DType.INT16, DType.INT32]
else:
continue
for outputDType in outputDTypeList:
for perm in range(testGen.args.num_rand_permutations):
# Randomly generate legal output dimensions and shift
# and then compute the stride and offset based on them
# A output_dim of 1 will cause offset to exceed allowed range
# so minimum value 2 produced below
output_dims = [testGen.randInt(1) + 1, testGen.randInt(1) + 1]
while ((float(ifm_shape[1]) / float(output_dims[0])) >= 16):
output_dims[0] += 1
while ((float(ifm_shape[2]) / float(output_dims[1])) >= 16):
output_dims[1] += 1
in_center_h = (ifm_shape[1] - 1) / 2.0
in_center_w = (ifm_shape[2] - 1) / 2.0
out_center_h = (output_dims[0] - 1) / 2.0
out_center_w = (output_dims[1] - 1) / 2.0
fp_stride_y = float(ifm_shape[1]) / float(output_dims[0])
fp_stride_x = float(ifm_shape[2]) / float(output_dims[1])
fp_offset_y = in_center_h - fp_stride_y * out_center_h
fp_offset_x = in_center_w - fp_stride_x * out_center_w
if outputDType == DType.FLOAT:
shift = 0
stride = [0, 0]
offset = [0, 0]
stride_fp = [fp_stride_y, fp_stride_x]
offset_fp = [fp_offset_y, fp_offset_x]
if error_name is not None:
shift, stride, stride_fp, offset, offset_fp, outputDTypeNew = TosaErrorIfArgGen.eiResizeErrorIf(
testGen,
error_name,
mode,
dtype,
shapeList,
outputDType,
shift,
stride,
stride_fp,
offset,
offset_fp
)
else:
outputDTypeNew = outputDType
arg_list.append(
(
"mode{}_odim{}x{}_out{}_st{:.2f}x{:.2f}_off{:.2f}x{:.2f}".format(
"N" if mode == ResizeMode.NEAREST else "B",
output_dims[0],
output_dims[1],
testGen.typeStr(outputDTypeNew),
stride_fp[0],
stride_fp[1],
offset_fp[0],
offset_fp[1],
),
[
mode,
stride,
offset,
shift,
stride_fp,
offset_fp,
output_dims,
dtype,
outputDTypeNew,
],
)
)
else:
shift = 11
unit = float(1 << shift)
stride_y = int(round(fp_stride_y * unit))
stride_x = int(round(fp_stride_x * unit))
offset_y = int(round(fp_offset_y * unit))
offset_x = int(round(fp_offset_x * unit))
while (
stride_y >= (16 << shift)
or stride_x >= (16 << shift)
or offset_y >= (16 << shift)
or offset_x >= (16 << shift)
or offset_y <= (-16 << shift)
or offset_x <= (-16 << shift)
):
shift = shift - 1
unit = float(1 << shift)
stride_y = int(round(fp_stride_y * unit))
stride_x = int(round(fp_stride_x * unit))
offset_y = int(round(fp_offset_y * unit))
offset_x = int(round(fp_offset_x * unit))
stride = [stride_y, stride_x]
offset = [offset_y, offset_x]
stride_fp = [0.0, 0.0]
offset_fp = [0.0, 0.0]
if error_name is not None:
shift, stride, stride_fp, offset, offset_fp, outputDTypeNew = TosaErrorIfArgGen.eiResizeErrorIf(
testGen,
error_name,
mode,
dtype,
shapeList,
outputDType,
shift,
stride,
stride_fp,
offset,
offset_fp
)
else:
outputDTypeNew = outputDType
arg_list.append(
(
"mode{}_shift{}_odim{}x{}_out{}_st{}x{}_off{}x{}".format(
"N" if mode == ResizeMode.NEAREST else "B",
shift,
output_dims[0],
output_dims[1],
testGen.typeStr(outputDTypeNew),
stride[0],
stride[1],
offset[0],
offset[1],
),
[
mode,
stride,
offset,
shift,
stride_fp,
offset_fp,
output_dims,
dtype,
outputDTypeNew,
],
)
)
return arg_list
@staticmethod
def agTable(testGen, opName, shapeList, dtype, error_name=None):
arg_list = []
if dtype == DType.INT8:
table = np.int32(
testGen.rng.integers(low=-128, high=128, size=[256])
).tolist()
else: # INT16
table = np.int32(
testGen.rng.integers(low=-32768, high=32768, size=[513])
).tolist()
arg_list.append(
(
"",
[table],
)
)
return arg_list
def agCondIf(testGen, opName, shapeList, dtype, error_name=None):
# CondIf generates the condition values here.
# Convert to tensors in the build function, along with the
# then and else blocks
arg_list = []
for c in [False, True]:
arg_list.append(("cond{}".format(int(c)), [c]))
return arg_list
def agWhileLoop(testGen, opName, shapeList, dtype, error_name=None):
# While loop: 0 iterations, 1, more than 1
arg_list = []
for iter in [0, 1, 4]:
arg_list.append(("iter{}".format(iter), [iter]))
return arg_list
class TosaErrorIfArgGen:
@staticmethod
def eiResizeErrorIf(testGen, error_name, mode, dtype, shapeList, outputDType, shift, stride, stride_fp, offset, offset_fp):
if outputDType == DType.FLOAT:
if error_name == ErrorIf.StrideSmallerEqualZero:
stride_fp = testGen.rng.random(size=[2]) - 2
elif error_name == ErrorIf.ShiftNotZero:
shift = testGen.rng.integers(1, 5)
elif error_name == ErrorIf.StrideLargerDimension:
shape = shapeList[0]
transform_height = testGen.rng.choice([False, True])
if transform_height:
stride_fp[0] = shape[1] + testGen.rng.integers(1, 10)
else:
stride_fp[1] = shape[2] + testGen.rng.integers(1, 10)
else:
if error_name == ErrorIf.StrideSmallerEqualZero:
stride = np.int16(testGen.rng.integers(-1, 1, size=[2]))
elif error_name == ErrorIf.ShiftSmallerOne:
shift = testGen.rng.integers(-3, 1)
if shift <= 0:
stride = [(16 >> -shift) - 1, (16 >> -shift) - 1] # avoids other ERROR_IF checks
offset = [(16 >> -shift) - 1, (16 >> -shift) - 1] # avoids other ERROR_IF checks
else:
stride = [(16 << shift) - 1, (16 << shift) - 1] # avoids other ERROR_IF checks
offset = [(16 << shift) - 1, (16 << shift) - 1] # avoids other ERROR_IF checks
elif error_name == ErrorIf.ShiftLargerEleven:
shift = np.int16(testGen.rng.integers(12, 15))
elif error_name == ErrorIf.StrideLargerDimension:
shape = shapeList[0]
transform_height = testGen.rng.choice([False, True])
if transform_height:
stride[0] = shape[1] + testGen.rng.integers(1, 10)
else:
stride[1] = shape[2] + testGen.rng.integers(1, 10)
elif error_name == ErrorIf.StrideLargerEqualMax:
stride = [(16 << shift) + 1, (16 << shift) + 1]
elif error_name == ErrorIf.OffsetLargerEqualMax:
offset = [(16 << shift) + 1, (16 << shift) + 1]
elif error_name == ErrorIf.OffsetSmallerEqualMin:
offset = [(-16 << shift) - 1, (-16 << shift) - 1]
if error_name == ErrorIf.WrongOutputType:
if mode == ResizeMode.NEAREST and dtype == DType.INT8:
incorrect_types = (DType.INT4, DType.INT16, DType.INT32, DType.INT48, DType.FLOAT)
elif mode == ResizeMode.NEAREST and dtype == DType.INT16:
incorrect_types = (DType.INT4, DType.INT8, DType.INT32, DType.INT48, DType.FLOAT)
elif mode == ResizeMode.BILINEAR and dtype == DType.INT8:
incorrect_types = (DType.INT4, DType.INT8, DType.INT16, DType.INT48, DType.FLOAT)
elif mode == ResizeMode.BILINEAR and dtype == DType.INT16:
incorrect_types = (DType.INT4, DType.INT8, DType.INT16, DType.INT32, DType.FLOAT)
elif dtype == DType.FLOAT:
incorrect_types = (DType.INT4, DType.INT8, DType.INT16, DType.INT32, DType.INT48)
outputDType = testGen.rng.choice(a=incorrect_types)
return shift, stride, stride_fp, offset, offset_fp, outputDType
@staticmethod
def eiPoolingErrorIf(testGen, error_name, stride, pad, kernel):
if (error_name == ErrorIf.StrideSmallerOne
# padding must not exceed the kernel size
and pad[0] < kernel[0] and pad[1] < kernel[0] and pad[2] < kernel[1] and pad[3] < kernel[1]):
wrongStride = (testGen.rng.choice([0, -1, -2, -3]), testGen.rng.choice([0, -1, -2, -3]))
return wrongStride, pad, kernel
elif error_name == ErrorIf.PadSmallerZero:
wrongPad = (testGen.rng.choice([-1, -2, -3]),
testGen.rng.choice([-1, -2, -3]),
testGen.rng.choice([-1, -2, -3]),
testGen.rng.choice([-1, -2, -3]))
return stride, wrongPad, kernel
elif error_name == ErrorIf.KernelSmallerOne:
wrongKernel = (testGen.rng.choice([0, -1, -2, -3]), testGen.rng.choice([0, -1, -2, -3]))
return stride, pad, wrongKernel
elif error_name == ErrorIf.PadLargerEqualKernel:
wrongPad = (testGen.rng.choice([kernel[0], kernel[0]+1, kernel[0]+2]),
testGen.rng.choice([kernel[0], kernel[0]+1, kernel[0]+2]),
testGen.rng.choice([kernel[1], kernel[1]+1, kernel[1]+2]),
testGen.rng.choice([kernel[1], kernel[1]+1, kernel[1]+2]))
return stride, wrongPad, kernel
else:
return None, None, None
@staticmethod
def eiRescaleWrongOutputType(input_dtype, output_dtype):
if input_dtype == DType.INT8:
if output_dtype not in [DType.UINT8, DType.INT8, DType.INT16, DType.INT32]:
return True
if input_dtype in [DType.INT16, DType.INT32]:
if output_dtype not in [DType.INT8, DType.INT16, DType.INT32]:
return True
elif input_dtype == DType.INT48:
if output_dtype not in [DType.INT8, DType.INT16, DType.INT32]:
return True
elif input_dtype == DType.UINT8:
if output_dtype != DType.INT8:
return True
return False
@staticmethod
def eiInvalidateInputOutputList(testGen, error_name, input_list, output_list):
# Mess up input/output tensors for ERROR_IF checks
if error_name == "WrongInputList":
add_input = testGen.rng.choice([True, False])
if add_input:
input_list.append('eiDummyInput')
else:
input_list = input_list[:-1]
if error_name == "WrongOutputList":
add_output = testGen.rng.choice([True, False])
if add_output:
output_list.append('eiDummyOutput')
else:
output_list = []
return input_list, output_list
@staticmethod
def eiRestrictDimension(shape, error_name):
# Restrict dimension size if rank is large for WrongRank Error_If
# This will keep the test sizes reasonably small
if error_name == ErrorIf.WrongRank:
if len(shape) > 4:
shape[4] = 1
return shape
def eiSliceErrorIf(testGen, error_name, input_shape, start, size):
if error_name == ErrorIf.StartSmallerZero:
newStart = []
for i in range(len(input_shape)):
newStart.append(testGen.rng.choice([-3, -2, -1]))
return newStart, size
elif error_name == ErrorIf.SizeSmallerEqualZero:
newSize = []
for i in range(len(input_shape)):
newSize.append(testGen.rng.choice([-3, -2, -1, 0]))
return start, newSize
elif error_name == ErrorIf.StartSizeOutsideBounds:
newStart, newSize = [], []
for i in range(len(input_shape)):
newStart.append(input_shape[i]-1)
newSize.append(testGen.rng.choice([2, 3, 4]))
return newStart, newSize
elif error_name == ErrorIf.InputSizeStartLengthMismatch:
remove = testGen.rng.choice([True, False])
if remove:
newStart = start[1:]
newSize = size[1:]
else:
newStart = start
newStart.append(1)
newSize = size
newSize.append(1)
return newStart, newSize
else:
return start, size
class TosaErrorValidator:
@staticmethod
def evValidateErrorIfs(serializer, validator_fcns, error_name, **kwargs):
# Check ERROR_IF statements
for val_fcn in validator_fcns:
val_result = val_fcn(True, **kwargs)
validator_name = val_result['error_name']
error_result = val_result['error_result']
error_reason = val_result['error_reason']
if error_result:
if error_name == validator_name:
serializer.setExpectedReturnCode(2, error_reason)
else:
print(f"Multiple ERROR_IF checks hit \nError required: {error_name}, Error_produced: {validator_name}")
return None # Return None to delete test if wrong ERROR_IF is hit
else:
if error_name == validator_name:
print(f"No ERROR_IF hit for {error_name}")
return None
@staticmethod
def evWrongInputType(check=False, **kwargs):
all_dtypes = {DType.BOOL, DType.INT4, DType.INT8, DType.INT16, DType.INT32, DType.INT48, DType.FLOAT}
# Find the unsupported input data types
assert 'op' in kwargs
op = kwargs['op']
input_dtypes = op['types']
allowed_input_dtypes = {t[0] if isinstance(t, list) else t for t in input_dtypes}
wrong_input_dtypes = list(all_dtypes - allowed_input_dtypes)
error_name = ErrorIf.WrongInputType
param_reqs = {"rank": None, "dtype": wrong_input_dtypes, "shape": None}
error_result = False
error_reason = "Input data type not supported for this operator"
if check:
input_dtype = kwargs['input_dtype']
if op['op'] == Op.FULLY_CONNECTED:
if input_dtype not in allowed_input_dtypes:
error_result = True
elif input_dtype not in input_dtypes:
error_result = True
info_dict = {
"error_name": error_name,
"error_result": error_result,
"error_reason": error_reason,
"param_reqs": param_reqs
}
return info_dict
@staticmethod
def evWrongOutputType(check=False, **kwargs):
error_name = ErrorIf.WrongOutputType
param_reqs = {"rank": None, "dtype": None, "shape": None}
error_result = False
error_reason = "Output data type not supported for this configuration of operator"
if check:
input_dtype = kwargs['input_dtype']
output_dtype = kwargs['output_dtype']
op = kwargs['op']
if op['op'] == Op.RESIZE:
mode = kwargs['mode']
if (
(mode == ResizeMode.NEAREST and input_dtype == DType.INT8 and output_dtype != DType.INT8) or
(mode == ResizeMode.NEAREST and input_dtype == DType.INT16 and output_dtype != DType.INT16) or
(mode == ResizeMode.BILINEAR and input_dtype == DType.INT8 and output_dtype != DType.INT32) or
(mode == ResizeMode.BILINEAR and input_dtype == DType.INT16 and output_dtype != DType.INT48) or
(input_dtype == DType.FLOAT and output_dtype != DType.FLOAT)
):
error_result = True
elif op['op'] == Op.RESCALE:
if input_dtype == DType.INT8:
if output_dtype not in [DType.UINT8, DType.INT8, DType.INT16, DType.INT32]:
error_result = True
if input_dtype in [DType.INT16, DType.INT32]:
if output_dtype not in [DType.INT8, DType.INT16, DType.INT32]:
error_result = True
elif input_dtype == DType.INT48:
if output_dtype not in [DType.INT8, DType.INT16, DType.INT32]:
error_result = True
elif input_dtype == DType.UINT8:
if output_dtype != DType.INT8:
error_result = True
elif op['op'] in [Op.FULLY_CONNECTED, Op.MATMUL]:
if (
(input_dtype == DType.INT8 and output_dtype != DType.INT32) or
(input_dtype == DType.INT16 and output_dtype != DType.INT48) or
(input_dtype == DType.FLOAT and output_dtype != DType.FLOAT)
):
error_result = True
elif op['op'] == Op.ARGMAX:
if input_dtype in [DType.INT8, DType.INT16, DType.FLOAT] and output_dtype != DType.INT32:
error_result = True
else:
if output_dtype != input_dtype:
error_result = True
info_dict = {
"error_name": error_name,
"error_result": error_result,
"error_reason": error_reason,
"param_reqs": param_reqs
}
return info_dict
@staticmethod
def evWrongRank(check=False, **kwargs):
all_ranks = (1, 2, 3, 4, 5)
# Make a list of incorrect ranks
assert 'op' in kwargs
op = kwargs['op']
rmin, rmax = op['rank']
rank_range = range(rmin, rmax + 1)
incorrect_ranks = list(set(all_ranks) - set(rank_range))
# Remove small incorrect ranks to avoid index errors
incorrect_ranks = [rank for rank in incorrect_ranks if rank > rmin]
# Set minimum incorrect rank to 3 to avoid index error
if op['op'] in [Op.RESIZE]:
incorrect_ranks = [3, 5]
error_name = ErrorIf.WrongRank
param_reqs = {"rank": incorrect_ranks, "dtype": None, "shape": None}
error_result = False
error_reason = "Rank not supported for this operator"
if check:
input_shape = kwargs['input_shape']
if op['op'] in [Op.RESIZE, Op.AVG_POOL2D, Op.MAX_POOL2D] and len(input_shape) != 4:
error_result = True
elif op['op'] == Op.FULLY_CONNECTED and len(input_shape) != 2:
error_result = True
elif op['op'] == Op.MATMUL and len(input_shape) != 3:
error_result = True
else:
if len(input_shape) not in rank_range:
error_result = True
info_dict = {
"error_name": error_name,
"error_result": error_result,
"error_reason": error_reason,
"param_reqs": param_reqs
}
return info_dict
@staticmethod
def evWrongInputList(check=False, **kwargs):
error_name = ErrorIf.WrongInputList
param_reqs = {"rank": None, "dtype": None, "shape": None}
error_result = False
error_reason = "Op input list does not match expected input"
if check:
op = kwargs['op']
input_list = kwargs['input_list']
num_operands = kwargs['num_operands']
if len(input_list) != num_operands:
error_result = True
info_dict = {
"error_name": error_name,
"error_result": error_result,
"error_reason": error_reason,
"param_reqs": param_reqs
}
return info_dict
@staticmethod
def evWrongOutputList(check=False, **kwargs):
error_name = ErrorIf.WrongOutputList
param_reqs = {"rank": None, "dtype": None, "shape": None}
error_result = False
error_reason = "Op output list does not match expected output"
if check:
output_list = kwargs['output_list']
# Note this will be incorrect if an operator returns more than one output
if len(output_list) != 1:
error_result = True
info_dict = {
"error_name": error_name,
"error_result": error_result,
"error_reason": error_reason,
"param_reqs": param_reqs
}
return info_dict
@staticmethod
def evMaxDimExceeded(check=False, **kwargs):
error_name = ErrorIf.MaxDimExceeded
param_reqs = {
"rank": [4,4],
"dtype": [DType.INT8],
"shape": [[1, 16584, 5, 1], [1, 2, 16499, 4]]
}
error_result = False
error_reason = "At least one maximum dimension is larger than 16384"
if check:
input_shape = kwargs['input_shape']
output_shape = kwargs['output_shape'] # Note this is just (OH, OW)
if ((input_shape[1] > 16384) or
(input_shape[2] > 16384) or
(output_shape[0] > 16384) or
(output_shape[1] > 16384)):
error_result = True
info_dict = {
"error_name": error_name,
"error_result": error_result,
"error_reason": error_reason,
"param_reqs": param_reqs
}
return info_dict
@staticmethod
def evBatchMismatch(check=False, **kwargs):
error_name = ErrorIf.BatchMismatch
param_reqs = {"rank": [4,4], "dtype": None, "shape": None}
error_result = False
error_reason = "Input batch size not equal to output batch size"
assert 'op' in kwargs
op = kwargs['op']
rmin, rmax = op['rank']
rank_range = range(rmin, rmax + 1)
if check:
input_shape = kwargs['input_shape']
output_shape = kwargs['result_tensor'].shape # Note this is just (N, OH, OW, C)
if (len(input_shape) in rank_range) and (input_shape[0] != output_shape[0]):
error_result = True
info_dict = {
"error_name": error_name,
"error_result": error_result,
"error_reason": error_reason,
"param_reqs": param_reqs
}
return info_dict
@staticmethod
def evChannelMismatch(check=False, **kwargs):
error_name = ErrorIf.ChannelMismatch
param_reqs = {"rank": [4,4], "dtype": None, "shape": None}
error_result = False
error_reason = "Input channel size not equal to output channel size"
assert 'op' in kwargs
op = kwargs['op']
rmin, rmax = op['rank']
rank_range = range(rmin, rmax + 1)
if check:
input_shape = kwargs['input_shape']
output_shape = kwargs['result_tensor'].shape # Note this is just (N, OH, OW, C)
if (len(input_shape) in rank_range) and (input_shape[3] != output_shape[3]):
error_result = True
info_dict = {
"error_name": error_name,
"error_result": error_result,
"error_reason": error_reason,
"param_reqs": param_reqs
}
return info_dict
@staticmethod
def evStrideSmallerEqualZero(check=False, **kwargs):
error_name = ErrorIf.StrideSmallerEqualZero
param_reqs = {"rank": None, "dtype": None, "shape": None}
error_result = False
error_reason = "Stride value smaller than or equal zero"
if check:
input_dtype = kwargs['input_dtype']
output_dtype = kwargs['output_dtype']
if input_dtype != DType.FLOAT and output_dtype == DType.FLOAT:
stride = kwargs['stride'] # Work around wrong input/output type tests
elif output_dtype == DType.FLOAT:
stride = kwargs['stride_fp']
elif input_dtype == DType.FLOAT and output_dtype != DType.FLOAT:
stride = kwargs['stride_fp'] # Work around wrong input/output type tests
else:
stride = kwargs['stride']
if min(stride) <= 0:
error_result = True
info_dict = {
"error_name": error_name,
"error_result": error_result,
"error_reason": error_reason,
"param_reqs": param_reqs
}
return info_dict
@staticmethod
def evStrideLargerEqualMax(check=False, **kwargs):
error_name = ErrorIf.StrideLargerEqualMax
param_reqs = {"rank": None, "dtype": [DType.INT8, DType.INT16], "shape": None}
error_result = False
error_reason = "Stride value larger than or equal to maximum value"
if check:
shift = kwargs['shift']
input_dtype = kwargs['input_dtype']
stride = kwargs['stride']
if input_dtype in [DType.INT8, DType.INT16]:
if shift >= 0 and (stride[0] >= (16 << shift) or stride[1] >= (16 << shift)):
error_result = True
elif shift < 0 and (stride[0] >= (16 >> -shift) or stride[1] >= (16 >> -shift)):
error_result = True
info_dict = {
"error_name": error_name,
"error_result": error_result,
"error_reason": error_reason,
"param_reqs": param_reqs
}
return info_dict
@staticmethod
def evStrideLargerDimension(check=False, **kwargs):
error_name = ErrorIf.StrideLargerDimension
param_reqs = {"rank": None, "dtype": [DType.FLOAT], "shape": None}
error_result = False
error_reason = "Stride value larger than or equal to H/W dimension"
if check:
shape = kwargs['input_shape']
input_dtype = kwargs['input_dtype']
stride = kwargs['stride_fp']
if input_dtype == DType.FLOAT and (stride[0] > shape[1]) or (stride[1] > shape[2]):
error_result = True
info_dict = {
"error_name": error_name,
"error_result": error_result,
"error_reason": error_reason,
"param_reqs": param_reqs
}
return info_dict
@staticmethod
def evOffsetSmallerEqualMin(check=False, **kwargs):
error_name = ErrorIf.OffsetSmallerEqualMin
param_reqs = {"rank": None, "dtype": [DType.INT8, DType.INT16], "shape": None}
error_result = False
error_reason = "Offset value smaller than or equal to minimum value"
if check:
shift = kwargs['shift']
output_dtype = kwargs['output_dtype']
if output_dtype == DType.FLOAT:
offset = kwargs['offset_fp']
else:
offset = kwargs['offset']
if shift >= 0 and (offset[0] <= (-16 << shift) or offset[1] <= (-16 << shift)):
error_result = True
elif shift < 0 and (offset[0] <= (-16 >> -shift) or offset[1] <= (-16 >> -shift)):
error_result = True
info_dict = {
"error_name": error_name,
"error_result": error_result,
"error_reason": error_reason,
"param_reqs": param_reqs
}
return info_dict
@staticmethod
def evOffsetLargerEqualMax(check=False, **kwargs):
error_name = ErrorIf.OffsetLargerEqualMax
param_reqs = {"rank": None, "dtype": [DType.INT8, DType.INT16], "shape": None}
error_result = False
error_reason = "Offset value larger than or equal to maximum value"
if check:
shift = kwargs['shift']
output_dtype = kwargs['output_dtype']
if output_dtype == DType.FLOAT:
offset = kwargs['offset_fp']
else:
offset = kwargs['offset']
if shift >= 0:
if offset[0] >= (16 << shift) or offset[1] >= (16 << shift):
error_result = True
if shift >= 0 and (offset[0] >= (16 << shift) or offset[1] >= (16 << shift)):
error_result = True
elif shift < 0 and (offset[0] >= (16 >> -shift) or offset[1] >= (16 >> -shift)):
error_result = True
info_dict = {
"error_name": error_name,
"error_result": error_result,
"error_reason": error_reason,
"param_reqs": param_reqs
}
return info_dict
@staticmethod
def evShiftNotZero(check=False, **kwargs):
error_name = ErrorIf.ShiftNotZero
param_reqs = {"rank": None, "dtype": [DType.FLOAT], "shape": None}
error_result = False
error_reason = "Shift value must be zero for float input"
if check:
shift = kwargs['shift']
input_dtype = kwargs['input_dtype']
output_dtype = kwargs['output_dtype']
if input_dtype == DType.FLOAT and output_dtype == DType.FLOAT and shift != 0:
error_result = True
info_dict = {
"error_name": error_name,
"error_result": error_result,
"error_reason": error_reason,
"param_reqs": param_reqs
}
return info_dict
@staticmethod
def evShiftSmallerOne(check=False, **kwargs):
error_name = ErrorIf.ShiftSmallerOne
param_reqs = {"rank": None, "dtype": [DType.INT8, DType.INT16], "shape": None}
error_result = False
error_reason = "Shift value smaller than one"
if check:
shift = kwargs['shift']
input_dtype = kwargs['input_dtype']
output_dtype = kwargs['output_dtype']
if shift < 1 and input_dtype != DType.FLOAT and output_dtype != DType.FLOAT:
error_result = True
info_dict = {
"error_name": error_name,
"error_result": error_result,
"error_reason": error_reason,
"param_reqs": param_reqs
}
return info_dict
@staticmethod
def evShiftLargerEleven(check=False, **kwargs):
error_name = ErrorIf.ShiftLargerEleven
param_reqs = {"rank": None, "dtype": [DType.INT8, DType.INT16], "shape": None}
error_result = False
error_reason = "Shift value larger than eleven"
if check:
shift = kwargs['shift']
if shift > 11:
error_result = True
info_dict = {
"error_name": error_name,
"error_result": error_result,
"error_reason": error_reason,
"param_reqs": param_reqs
}
return info_dict
@staticmethod
def evRankMismatch(check=False, **kwargs):
error_name = ErrorIf.RankMismatch
param_reqs = {"rank": None, "dtype": None, "shape": None}
error_result = False
error_reason = "Input Rank does not match output rank"
if check:
input1_shape = kwargs['input1'].shape
input2_shape = kwargs['input2'].shape
output_shape = kwargs['result_tensor'].shape
if (len(input1_shape) != len(output_shape)) or (len(input2_shape) != len(output_shape)):
error_result = True
info_dict = {
"error_name": error_name,
"error_result": error_result,
"error_reason": error_reason,
"param_reqs": param_reqs
}
return info_dict
@staticmethod
def evInputZeroPointNotZero(check=False, **kwargs):
op = kwargs['op']
inputDtypes = op['types'].copy()
# If inputDtypes is a list then only the first two elements are INT8 inputs
if isinstance(inputDtypes, list):
inputDtypes = inputDtypes[2:]
if DType.INT8 in inputDtypes:
inputDtypes.remove(DType.INT8)
if DType.UINT8 in inputDtypes:
inputDtypes.remove(DType.UINT8)
error_name = ErrorIf.InputZeroPointNotZero
param_reqs = {
"rank": None,
"dtype": inputDtypes,
"shape": None
}
error_result = False
error_reason = "Input DType not INT8 and zero point not 0"
if check:
input_dtype = kwargs['input_dtype']
if isinstance(kwargs['qinfo'], tuple):
qinfo = kwargs['qinfo']
input_zero_point = qinfo[0]
else:
# For use: qinfo.ints[0][1] = input_zp, qinfo.ints[1][1] = output_zp
qinfo = kwargs['qinfo'].ints
input_zero_point = qinfo[0][1]
if op['op'] == Op.MATMUL:
input1_dtype = kwargs['input_dtype']
input2_dtype = kwargs['input2_dtype']
qinfo = kwargs['qinfo'].ints
input1_zero_point = qinfo[0][1]
input2_zero_point = qinfo[1][1]
if (input1_dtype != DType.INT8 and input1_zero_point != 0) or (input2_dtype != DType.INT8 and input2_zero_point != 0):
error_result = True
else:
if input_dtype not in [DType.INT8, DType.UINT8] and input_zero_point != 0:
error_result = True
info_dict = {
"error_name": error_name,
"error_result": error_result,
"error_reason": error_reason,
"param_reqs": param_reqs
}
return info_dict
@staticmethod
def evWeightZeroPointNotZero(check=False, **kwargs):
op = kwargs['op']
# exclude inputs with INT8 weights
inputDtypes = [t for t in op['types']
if not isinstance(t, list) or t[1] != DType.INT8]
error_name = ErrorIf.WeightZeroPointNotZero
param_reqs = {
"rank": None,
"dtype": inputDtypes,
"shape": None
}
error_result = False
error_reason = "Weight DType not INT8 and zero point not 0"
if check:
weight_dtype = kwargs['weight_dtype']
# For use: qinfo.ints[0][1] = input_zp, qinfo.ints[1][1] = weight_zp
qinfo = kwargs['qinfo'].ints
weight_zero_point = qinfo[1][1]
if weight_dtype != DType.INT8 and weight_zero_point != 0:
error_result = True
info_dict = {
"error_name": error_name,
"error_result": error_result,
"error_reason": error_reason,
"param_reqs": param_reqs
}
return info_dict
@staticmethod
def evOutputZeroPointNotZero(check=False, **kwargs):
op = kwargs['op']
inputDtypes = op['types'].copy()
if DType.INT8 in inputDtypes:
inputDtypes.remove(DType.INT8)
if DType.UINT8 in inputDtypes:
inputDtypes.remove(DType.UINT8)
error_name = ErrorIf.OutputZeroPointNotZero
param_reqs = {
"rank": None,
"dtype": inputDtypes,
"shape": None
}
error_result = False
error_reason = "Output DType not INT8 and zero point not 0"
if check:
input_dtype = kwargs['input_dtype']
output_dtype = kwargs['output_dtype']
if isinstance(kwargs['qinfo'], tuple):
qinfo = kwargs['qinfo']
output_zero_point = qinfo[1]
else:
# For use: qinfo.ints[0][1] = input_zp, qinfo.ints[1][1] = output_zp
qinfo = kwargs['qinfo'].ints
output_zero_point = qinfo[1][1]
if op['op'] == Op.AVG_POOL2D:
if input_dtype != DType.INT8 and output_zero_point != 0:
error_result = True
elif output_dtype not in [DType.INT8, DType.UINT8] and output_zero_point != 0:
error_result = True
info_dict = {
"error_name": error_name,
"error_result": error_result,
"error_reason": error_reason,
"param_reqs": param_reqs
}
return info_dict
@staticmethod
def evAxisSmallerZero(check=False, **kwargs):
error_name = ErrorIf.AxisSmallerZero
param_reqs = {"rank": None, "dtype": None, "shape": None}
error_result = False
error_reason = "Axis smaller than zero"
if check:
axis = kwargs['axis']
if axis < 0:
error_result = True
info_dict = {
"error_name": error_name,
"error_result": error_result,
"error_reason": error_reason,
"param_reqs": param_reqs
}
return info_dict
@staticmethod
def evAxisLargerRank(check=False, **kwargs):
error_name = ErrorIf.AxisLargerRank
param_reqs = {"rank": None, "dtype": None, "shape": None}
error_result = False
error_reason = "Axis larger than rank"
if check:
axis = kwargs['axis']
shape = kwargs['input_shape']
if axis > len(shape):
error_result = True
info_dict = {
"error_name": error_name,
"error_result": error_result,
"error_reason": error_reason,
"param_reqs": param_reqs
}
return info_dict
@staticmethod
def evShapeOfAxisNotOne(check=False, **kwargs):
error_name = ErrorIf.ShapeOfAxisNotOne
param_reqs = {"rank": None, "dtype": None, "shape": None}
error_result = False
error_reason = "shape[axis] is not equal to 1"
if check:
axis = kwargs['axis']
shape = kwargs['output_shape']
if (0 <= axis < len(shape)) and shape[axis] != 1:
error_result = True
info_dict = {
"error_name": error_name,
"error_result": error_result,
"error_reason": error_reason,
"param_reqs": param_reqs
}
return info_dict
@staticmethod
def evPadSmallerZero(check=False, **kwargs):
error_name = ErrorIf.PadSmallerZero
param_reqs = {"rank": None, "dtype": None, "shape": None}
error_result = False
error_reason = "At least one pad is smaller than zero"
if check:
op = kwargs['op']
pad = kwargs['pad']
if op['op'] == Op.PAD:
for padding in pad:
if min(padding) < 0:
error_result = True
else:
if min(pad) < 0:
error_result = True
info_dict = {
"error_name": error_name,
"error_result": error_result,
"error_reason": error_reason,
"param_reqs": param_reqs
}
return info_dict
@staticmethod
def evPadLargerEqualKernel(check=False, **kwargs):
error_name = ErrorIf.PadLargerEqualKernel
param_reqs = {"rank": None, "dtype": None, "shape": None}
error_result = False
error_reason = "At least one pad is larger than kernel dimension"
if check:
pad = kwargs['pad']
kernel = kwargs['kernel']
if min(pad) > 0 and min(kernel) > 1:
if pad[0] >= kernel[0] or pad[1] >= kernel[0] or pad[2] >= kernel[1] or pad[3] >= kernel[1]:
error_result = True
info_dict = {
"error_name": error_name,
"error_result": error_result,
"error_reason": error_reason,
"param_reqs": param_reqs
}
return info_dict
@staticmethod
def evPoolingOutputShapeMismatch(check=False, **kwargs):
error_name = ErrorIf.PoolingOutputShapeMismatch
param_reqs = {"rank": None, "dtype": None, "shape": None}
error_result = False
error_reason = "Mismatch between output shape provided and expected output shape"
if check:
pad = kwargs['pad']
pad_top, pad_bottom, pad_left, pad_right = pad[0], pad[1], pad[2], pad[3]
kernel = kwargs['kernel']
kernel_y, kernel_x = kernel[0], kernel[1]
input_shape = kwargs['input_shape']
IH, IW = input_shape[1], input_shape[2]
output_shape = kwargs['output_shape']
OH, OW = output_shape[1], output_shape[2]
stride = kwargs['stride']
stride_y, stride_x = stride[0], stride[1]
# calculate correct height, width dimensions
if stride_x != 0 and stride_y != 0:
y_correct = (IH + pad_top + pad_bottom + stride_y - kernel_y) // stride_y
x_correct = (IW + pad_left + pad_right + stride_x - kernel_x) // stride_x
# ensure parameters are valid
params_valid = (min(kernel) >= 1 and min(stride) >= 1 and min(pad) >= 0
and not (pad[0] >= kernel[0] or pad[1] >= kernel[0] or pad[2] >= kernel[1] or pad[3] >= kernel[1]))
if params_valid and (OH != y_correct or OW != x_correct):
error_result = True
info_dict = {
"error_name": error_name,
"error_result": error_result,
"error_reason": error_reason,
"param_reqs": param_reqs
}
return info_dict
@staticmethod
def evArgmaxOutputShapeMismatch(check=False, **kwargs):
error_name = ErrorIf.ArgmaxOutputShapeMismatch
param_reqs = {"rank": [2,4], "dtype": None, "shape": None}
error_result = False
error_reason = "Mismatch between output shape provided and expected output shape"
if check:
output_shape = kwargs['output_shape']
input_shape = kwargs['input_shape']
axis = kwargs['axis']
dimension_match = True
axis_shift = 0
# Check that rank is correct before trying to check dimensions
if (len(input_shape) - 1) == len(output_shape):
for i in range(len(input_shape)):
if i == axis:
axis_shift = 1
continue
if input_shape[i] != output_shape[i - axis_shift]:
dimension_match = False
if not dimension_match:
error_result = True
info_dict = {
"error_name": error_name,
"error_result": error_result,
"error_reason": error_reason,
"param_reqs": param_reqs
}
return info_dict
@staticmethod
def evArgmaxOutputRankMismatch(check=False, **kwargs):
error_name = ErrorIf.ArgmaxOutputRankMismatch
param_reqs = {"rank": None, "dtype": None, "shape": None}
error_result = False
error_reason = "Mismatch between output shape provided and expected output shape"
if check:
output_shape = kwargs['output_shape']
input_shape = kwargs['input_shape']
axis = kwargs['axis']
valid_params = axis >= 0 and axis < len(input_shape)
if valid_params and (len(input_shape) - 1) != len(output_shape):
error_result = True
info_dict = {
"error_name": error_name,
"error_result": error_result,
"error_reason": error_reason,
"param_reqs": param_reqs
}
return info_dict
@staticmethod
def evKernelSmallerOne(check=False, **kwargs):
error_name = ErrorIf.KernelSmallerOne
param_reqs = {"rank": None, "dtype": None, "shape": None}
error_result = False
error_reason = "At least one kernel dimension is smaller than zero"
if check:
kernel = kwargs['kernel']
if min(kernel) < 1:
error_result = True
info_dict = {
"error_name": error_name,
"error_result": error_result,
"error_reason": error_reason,
"param_reqs": param_reqs
}
return info_dict
@staticmethod
def evStrideSmallerOne(check=False, **kwargs):
error_name = ErrorIf.StrideSmallerOne
param_reqs = {"rank": None, "dtype": None, "shape": None}
error_result = False
error_reason = "At least one stride dimension is smaller than zero"
if check:
stride = kwargs['stride']
if min(stride) < 1:
error_result = True
info_dict = {
"error_name": error_name,
"error_result": error_result,
"error_reason": error_reason,
"param_reqs": param_reqs
}
return info_dict
@staticmethod
def evScaleTrue(check=False, **kwargs):
error_name = ErrorIf.ScaleTrue
param_reqs = {"rank": None, "dtype": [DType.INT48], "shape": None}
error_result = False
error_reason = "Scale set to true but input type is INT48"
if check:
input_dtype = kwargs['input_dtype']
scale32 = kwargs['scale32']
if scale32 and input_dtype == DType.INT48:
error_result = True
info_dict = {
"error_name": error_name,
"error_result": error_result,
"error_reason": error_reason,
"param_reqs": param_reqs
}
return info_dict
@staticmethod
def evScaleNotTrue(check=False, **kwargs):
error_name = ErrorIf.ScaleNotTrue
param_reqs = {"rank": None, "dtype": None, "shape": None}
error_result = False
error_reason = "Scale set to false but double round set to true"
if check:
scale32 = kwargs['scale32']
double_round = kwargs['double_round']
if not scale32 and double_round:
error_result = True
info_dict = {
"error_name": error_name,
"error_result": error_result,
"error_reason": error_reason,
"param_reqs": param_reqs
}
return info_dict
@staticmethod
def evTensorSizeInputOutputMismatch(check=False, **kwargs):
error_name = ErrorIf.TensorSizeInputOutputMismatch
param_reqs = {"rank": None, "dtype": None, "shape": None}
error_result = False
error_reason = "Input tensor size does not match output tensor size"
if check:
input_shape = kwargs['input_shape']
output_shape = kwargs['output_shape']
input_size = np.prod(input_shape)
output_size = np.prod(output_shape)
if input_size != output_size:
error_result = True
info_dict = {
"error_name": error_name,
"error_result": error_result,
"error_reason": error_reason,
"param_reqs": param_reqs
}
return info_dict
@staticmethod
def evStartSmallerZero(check=False, **kwargs):
error_name = ErrorIf.StartSmallerZero
param_reqs = {"rank": None, "dtype": None, "shape": None}
error_result = False
error_reason = "Starting point smaller than zero"
if check:
input_shape = kwargs['input_shape']
start = kwargs['start']
rank = len(input_shape)
if len(start) == rank:
for index in range(rank):
if start[index] < 0:
error_result = True
info_dict = {
"error_name": error_name,
"error_result": error_result,
"error_reason": error_reason,
"param_reqs": param_reqs
}
return info_dict
@staticmethod
def evSizeSmallerEqualZero(check=False, **kwargs):
error_name = ErrorIf.SizeSmallerEqualZero
param_reqs = {"rank": None, "dtype": None, "shape": None}
error_result = False
error_reason = "Size smaller than or equal to zero"
if check:
input_shape = kwargs['input_shape']
size = kwargs['size']
rank = len(input_shape)
if len(size) == rank:
for index in range(rank):
if size[index] <= 0:
error_result = True
info_dict = {
"error_name": error_name,
"error_result": error_result,
"error_reason": error_reason,
"param_reqs": param_reqs
}
return info_dict
@staticmethod
def evStartSizeOutsideBounds(check=False, **kwargs):
error_name = ErrorIf.StartSizeOutsideBounds
param_reqs = {"rank": None, "dtype": None, "shape": None}
error_result = False
error_reason = "starting point plus size larger than input dimension"
if check:
input_shape = kwargs['input_shape']
start = kwargs['start']
size = kwargs['size']
rank = len(input_shape)
if len(start) == rank and len(size) == rank:
for index in range(rank):
if start[index] + size[index] > input_shape[index]:
error_result = True
info_dict = {
"error_name": error_name,
"error_result": error_result,
"error_reason": error_reason,
"param_reqs": param_reqs
}
return info_dict
@staticmethod
def evSizeOutputShapeMismatch(check=False, **kwargs):
error_name = ErrorIf.SizeOutputShapeMismatch
param_reqs = {"rank": None, "dtype": None, "shape": None}
error_result = False
error_reason = "Size does not match output dimension"
if check:
input_shape = kwargs['input_shape']
output_shape = kwargs['output_shape']
size = kwargs['size']
rank = len(input_shape)
if len(size) == rank:
for index in range(rank):
if size[index] != output_shape[index]:
error_result = True
info_dict = {
"error_name": error_name,
"error_result": error_result,
"error_reason": error_reason,
"param_reqs": param_reqs
}
return info_dict
@staticmethod
def evInputSizeStartLengthMismatch(check=False, **kwargs):
error_name = ErrorIf.InputSizeStartLengthMismatch
param_reqs = {"rank": None, "dtype": None, "shape": None}
error_result = False
error_reason = "rank of input not equal to length of start or size"
if check:
input_shape = kwargs['input_shape']
start = kwargs['start']
size = kwargs['size']
rank = len(input_shape)
if rank != len(start) or rank != len(size):
error_result = True
info_dict = {
"error_name": error_name,
"error_result": error_result,
"error_reason": error_reason,
"param_reqs": param_reqs
}
return info_dict
@staticmethod
def evIndexOutsideBounds(check=False, **kwargs):
error_name = ErrorIf.IndexOutsideBounds
param_reqs = {"rank": None, "dtype": None, "shape": None}
error_result = False
error_reason = "Index outside of allowed bounds"
if check:
input_shape = kwargs['input_shape']
perms = kwargs['perms']
rank = len(input_shape)
for index in perms:
if index < 0 or index > rank:
error_result = True
info_dict = {
"error_name": error_name,
"error_result": error_result,
"error_reason": error_reason,
"param_reqs": param_reqs
}
return info_dict
@staticmethod
def evIndexUsedTwice(check=False, **kwargs):
error_name = ErrorIf.IndexUsedTwice
param_reqs = {"rank": [2,4], "dtype": None, "shape": None}
error_result = False
error_reason = "Index used multiple times"
if check:
input_shape = kwargs['input_shape']
perms = kwargs['perms']
rank = len(input_shape)
unique_indices = []
for index in perms:
if index in unique_indices:
error_result = True
else:
unique_indices.append(index)
info_dict = {
"error_name": error_name,
"error_result": error_result,
"error_reason": error_reason,
"param_reqs": param_reqs
}
return info_dict
class TosaInvalidValidator:
@staticmethod
def ivWrongDataTypeOrModeResize(**kwargs):
input_dtype = kwargs["input_dtype"]
args = kwargs["args"]
mode = args[0]
stride = args[1]
stride_fp = args[4]
output_dtype = args[8]
if mode == ResizeMode.BILINEAR:
# Invalid output data type / Invalid input datatype
return (
not (input_dtype == DType.INT8 and output_dtype == DType.INT32) or
not (input_dtype == DType.INT16 and output_dtype == DType.INT48) or
not (input_dtype == DType.FLOAT and output_dtype == DType.FLOAT) or
(input_dtype not in [DType.INT8, DType.INT32, DType.FLOAT])
)
elif mode == ResizeMode.NEAREST:
# Invalid output data type / Invalid input datatype
return (
(input_dtype != output_dtype) or
(input_dtype not in [DType.INT8, DType.INT32, DType.FLOAT])
)
else:
# Invalid resize mode
return True
@staticmethod
def ivBadStride(**kwargs):
input_dtype = kwargs["input_dtype"]
args = kwargs["args"]
stride_x = args[1][0]
stride_y = args[1][1]
stride_fp_x = args[4][0]
stride_fp_y = args[4][1]
if input_dtype == DType.FLOAT:
if stride_fp_x <= 0 or stride_fp_y <= 0:
# Negative or zero stride
return True
else:
if stride_x <= 0 or stride_y <= 0:
# Negative or zero stride
return True
return False
@staticmethod
def ivHeightWidthSmallerZero(**kwargs):
opName = kwargs['opName']
inputShapes = kwargs['shapeList']
input = inputShapes[0]
if not opName.endswith("pool2d"):
filter = inputShapes[1]
args = kwargs['args']
strides = args[0]
padding = args[1]
dilations = args[2]
if opName.endswith("pool2d"):
kernel = args[2]
if opName.startswith('conv2d'):
h = (
input[1]
- filter[1]
- (filter[1] - 1) * (dilations[0] - 1)
+ padding[0]
+ padding[1]
) // strides[0] + 1
w = (
input[2]
- filter[2]
- (filter[2] - 1) * (dilations[1] - 1)
+ padding[2]
+ padding[3]
) // strides[1] + 1
elif opName.startswith("depthwise_conv2d"):
h = (
input[1]
- filter[0]
- (filter[0] - 1) * (dilations[0] - 1)
+ padding[0]
+ padding[1]
) // strides[0] + 1
w = (
input[2]
- filter[1]
- (filter[1] - 1) * (dilations[1] - 1)
+ padding[2]
+ padding[3]
) // strides[1] + 1
elif opName.endswith("pool2d"):
h = (input[1] + padding[0] + padding[1] + strides[0] - kernel[0]) // strides[0]
w = (input[2] + padding[2] + padding[3] + strides[1] - kernel[1]) // strides[1]
else:
assert False, "Unrecognized Op"
if h <= 0 or w <= 0:
# Invalid parameter combination
return True
return False
@staticmethod
def ivNonPositiveOutputShape(**kwargs):
args = kwargs['args']
output_shape = args[3]
if output_shape[1] <= 0 or output_shape[2] <= 0:
# Negative output shape
return True
return False
class TosaTestGen:
# Maximum rank of tensor supported by test generator.
TOSA_TENSOR_MAX_RANK = 6
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 resetRNG(self, seed=None):
if seed == None:
seed = self.random_seed + 1
self.rng = np.random.default_rng(seed)
def getRandTensor(self, shape, dtype):
if dtype == DType.BOOL:
np_dt = np.bool
return np.bool_(self.rng.choice(a=[False, True], size=shape))
# TOSA specific INT4 weight range from -7 to 7
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=-128, high=128, size=shape))
elif dtype == DType.UINT8:
return np.int32(self.rng.integers(low=0, high=256, 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))
else:
raise Exception("Unrecognized Dtype: {}".format(dtype))
def buildPlaceholderTensors(self, shape_list, dtype_list):
placeholders = []
assert len(shape_list) == len(dtype_list)
for idx, shape in enumerate(shape_list):
arr = self.getRandTensor(shape, dtype_list[idx])
placeholders.append(self.ser.addPlaceholder(shape, dtype_list[idx], arr))
return placeholders
def buildConstTensors(self, shape_list, dtype_list):
consts = []
assert len(shape_list) == len(dtype_list)
for idx, shape in enumerate(shape_list):
arr = self.getRandTensor(shape, dtype_list[idx])
consts.append(self.ser.addConst(shape, dtype_list[idx], 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])
# TOSA specific INT4 weight range from -7 to 7
elif dtype == DType.INT4:
low, high = (-7, 8)
elif dtype == DType.INT8:
low, high = (-128, 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 isinstance(t, list):
assert len(t) >= 2
return "{}x{}".format(self.typeStr(t[0]), self.typeStr(t[1]))
else:
if t == DType.BOOL:
return "b"
elif t == DType.INT4:
return "i4"
elif t == DType.INT8:
return "i8"
elif t == DType.UINT8:
return "u8"
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.INT4:
return 4
elif t == DType.INT8:
return 8
elif t == DType.UINT8:
return 8
elif t == DType.INT16:
return 16
elif t == DType.INT32:
return 32
elif t == DType.INT48:
return 48
elif t == DType.FLOAT:
return 32
elif t == DType.BOOL:
return 1
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, validator_fcns=None, error_name=None, qinfo=None):
result_tens = OutputShaper.unaryOp(self.ser, self.rng, a, error_name)
# build_placeholder returns an int, ABS/other ops does not
if isinstance(op, int):
self.ser.addOperator(op, a.name, result_tens.name, None, qinfo)
return result_tens
elif op['op'] == Op.IDENTITY:
self.ser.addOperator(op['op'], a.name, result_tens.name, None, qinfo)
return result_tens
# Ensure new output type has correct qinfo
if error_name == ErrorIf.WrongOutputType:
if result_tens.dtype not in [DType.INT8, DType.UINT8]:
qinfo = ts.TosaSerializerQuantInfo()
qinfo.UnaryQuantInfo(
TosaQuantGen.getQinfo(self, a.dtype), TosaQuantGen.getQinfo(self, result_tens.dtype)
)
# Invalidate Input/Output list for error if checks.
input_list = [a.name]
output_list = [result_tens.name]
pCount, cCount = op["operands"]
num_operands = pCount + cCount
input_list, output_list = TosaErrorIfArgGen.eiInvalidateInputOutputList(self, error_name, input_list, output_list)
TosaErrorValidator.evValidateErrorIfs(
self.ser,
validator_fcns,
error_name,
op=op,
input_dtype=a.dtype,
output_dtype=result_tens.dtype,
qinfo = qinfo,
result_tensor = result_tens,
input_list=input_list,
output_list=output_list,
num_operands=num_operands,
)
self.ser.addOperator(op['op'], input_list, output_list, None, qinfo)
return result_tens
def build_binary_broadcast(self, op, a, b, validator_fcns, error_name=None):
result_tens = OutputShaper.binaryBroadcastOp(self.ser, self.rng, a, b, error_name)
# Invalidate Input/Output list for error if checks.
input_list = [a.name, b.name]
output_list = [result_tens.name]
pCount, cCount = op["operands"]
num_operands = pCount + cCount
input_list, output_list = TosaErrorIfArgGen.eiInvalidateInputOutputList(self, error_name, input_list, output_list)
TosaErrorValidator.evValidateErrorIfs(
self.ser,
validator_fcns,
error_name,
op=op,
input1 = a,
input2 = b,
input_dtype = a.dtype,
output_dtype = result_tens.dtype,
result_tensor = result_tens,
input_list=input_list,
output_list=output_list,
num_operands=num_operands,
)
self.ser.addOperator(op['op'], input_list, output_list)
return result_tens
def build_binary_nonbroadcast(self, op, a, b):
result_tens = OutputShaper.binaryNonBroadcastOp(self.ser, a, b)
self.ser.addOperator(op['op'], [a.name, b.name], [result_tens.name])
return result_tens
def build_arithmetic_right_shift(self, op, a, b, round):
result_tens = OutputShaper.binaryBroadcastOp(self.ser, self.rng, a, b)
attr = ts.TosaSerializerAttribute()
attr.ArithmeticRightShiftAttribute(round)
self.ser.addOperator(op['op'], [a.name, b.name], [result_tens.name], attr)
return result_tens
def build_mul(self, op, a, b, shift):
result_tens = OutputShaper.binaryBroadcastOp(self.ser, self.rng, a, b)
# Special for multiply:
# Force the result to INT32 for INT types
if a.dtype != DType.FLOAT:
result_tens.setDtype(DType.INT32)
attr = ts.TosaSerializerAttribute()
attr.MulAttribute(shift)
self.ser.addOperator(op['op'], [a.name, b.name], [result_tens.name], attr)
return result_tens
def build_table(self, op, a, table):
result_tens = OutputShaper.tableOp(self.ser, a)
attr = ts.TosaSerializerAttribute()
attr.TableAttribute(table)
self.ser.addOperator(op['op'], [a.name], [result_tens.name], attr)
return result_tens
def build_select(self, op, cond, a, b):
result_tens = OutputShaper.selectOp(self.ser, cond, a, b)
self.ser.addOperator(op['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['op'], [a.name, b.name], [result_tens.name])
return result_tens
def build_argmax(self, op, a, axis, validator_fcns, error_name):
result_tens = OutputShaper.argmaxOp(self.ser, self.rng, a, axis, error_name)
# Invalidate Input/Output list for error if checks.
input_list = [a.name]
output_list = [result_tens.name]
pCount, cCount = op["operands"]
num_operands = pCount + cCount
input_list, output_list = TosaErrorIfArgGen.eiInvalidateInputOutputList(self, error_name, input_list, output_list)
TosaErrorValidator.evValidateErrorIfs(
self.ser,
validator_fcns,
error_name,
op=op,
axis=axis,
input_shape = a.shape,
input_dtype = a.dtype,
output_shape = result_tens.shape,
output_dtype = result_tens.dtype,
result_tensor = result_tens,
input_list=input_list,
output_list=output_list,
num_operands=num_operands,
)
attr = ts.TosaSerializerAttribute()
attr.AxisAttribute(axis)
self.ser.addOperator(op['op'], input_list, output_list, attr)
return result_tens
def build_pool2d(self, op, input, stride, pad, kernel, validator_fcns=None, error_name=None, qinfo=None):
result_tens = OutputShaper.pool2dOp(self.ser, self.rng, input, kernel, stride, pad, error_name)
# Ensure new output type has correct qinfo
if error_name == ErrorIf.WrongInputType:
if input.dtype not in [DType.INT8, DType.UINT8]:
qinfo = ts.TosaSerializerQuantInfo()
qinfo.UnaryQuantInfo(
TosaQuantGen.getQinfo(self, input.dtype), TosaQuantGen.getQinfo(self, result_tens.dtype)
)
# Invalidate Input/Output list for error if checks.
input_list = [input.name]
output_list = [result_tens.name]
pCount, cCount = op["operands"]
num_operands = pCount + cCount
input_list, output_list = TosaErrorIfArgGen.eiInvalidateInputOutputList(self, error_name, input_list, output_list)
TosaErrorValidator.evValidateErrorIfs(
self.ser,
validator_fcns,
error_name,
op=op,
input_shape=input.shape,
input_dtype=input.dtype,
output_shape=result_tens.shape,
output_dtype=result_tens.dtype,
kernel=kernel,
stride=stride,
pad=pad,
qinfo = qinfo,
result_tensor = result_tens,
input_list=input_list,
output_list=output_list,
num_operands=num_operands,
)
attr = ts.TosaSerializerAttribute()
attr.PoolAttribute(kernel, stride, pad)
self.ser.addOperator(op['op'], input_list, output_list, 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.ConvAttribute(padding, strides, dilations)
self.ser.addOperator(
op['op'], [ifm.name, filter.name, bias.name], [result_tens.name], attr, qinfo
)
return result_tens
def build_conv3d(self, op, ifm, filter, bias, strides, padding, dilations, qinfo):
assert len(padding) == 6
result_tens = OutputShaper.conv3dOp(
self.ser, ifm, filter, strides, padding, dilations
)
attr = ts.TosaSerializerAttribute()
attr.ConvAttribute(padding, strides, dilations)
self.ser.addOperator(
op['op'], [ifm.name, filter.name, bias.name], [result_tens.name], attr, qinfo
)
return result_tens
def build_transpose_conv2d(
self, op, ifm, filter, bias, stride, outpad, dilation, output_shape, qinfo
):
assert len(outpad) == 2
result_tens = OutputShaper.transposeConv2DOp(self.ser, ifm, output_shape)
attr = ts.TosaSerializerAttribute()
attr.TransposeConvAttribute(outpad, stride, dilation, output_shape)
self.ser.addOperator(
op['op'], [ifm.name, filter.name, bias.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.ConvAttribute(padding, strides, dilations)
self.ser.addOperator(
op['op'], [ifm.name, filter.name, bias.name], [result_tens.name], attr, qinfo
)
return result_tens
def build_fully_connected(self, op, ifm, filter, bias, validator_fcns=None, error_name=None, qinfo=None):
result_tens = OutputShaper.fullyConnectedOp(self.ser, self.rng, ifm, filter, error_name)
# Invalidate Input/Output list for error if checks.
input_list = [ifm.name, filter.name, bias.name]
output_list = [result_tens.name]
pCount, cCount = op["operands"]
num_operands = pCount + cCount
input_list, output_list = TosaErrorIfArgGen.eiInvalidateInputOutputList(self, error_name, input_list, output_list)
TosaErrorValidator.evValidateErrorIfs(
self.ser,
validator_fcns,
error_name,
op=op,
input_shape=ifm.shape,
input_dtype=ifm.dtype,
weight_dtype=filter.dtype,
output_shape=result_tens.shape,
output_dtype=result_tens.dtype,
qinfo = qinfo,
result_tensor = result_tens,
input_list=input_list,
output_list=output_list,
num_operands=num_operands,
)
self.ser.addOperator(
op['op'], input_list, output_list, None, qinfo
)
return result_tens
def build_matmul(self, op, a, b, validator_fcns=None, error_name=None, qinfo=None):
result_tens = OutputShaper.matmulOp(self.ser, self.rng, a, b, error_name)
# Invalidate Input/Output list for error if checks.
input_list = [a.name, b.name]
output_list = [result_tens.name]
pCount, cCount = op["operands"]
num_operands = pCount + cCount
input_list, output_list = TosaErrorIfArgGen.eiInvalidateInputOutputList(self, error_name, input_list, output_list)
TosaErrorValidator.evValidateErrorIfs(
self.ser,
validator_fcns,
error_name,
op=op,
input_shape=a.shape,
input_dtype=a.dtype,
input2_shape=b.shape,
input2_dtype=b.dtype,
output_shape=result_tens.shape,
output_dtype=result_tens.dtype,
qinfo = qinfo,
result_tensor = result_tens,
input_list=input_list,
output_list=output_list,
num_operands=num_operands,
)
self.ser.addOperator(op['op'], input_list, output_list, None, qinfo)
return result_tens
def build_reduce(self, op, a, axis, validator_fcns, error_name=None):
result_tens = OutputShaper.reduceOp(self.ser, self.rng, a, axis, error_name)
# Invalidate Input/Output list for error if checks.
input_list = [a.name]
output_list = [result_tens.name]
pCount, cCount = op["operands"]
num_operands = pCount + cCount
input_list, output_list = TosaErrorIfArgGen.eiInvalidateInputOutputList(self, error_name, input_list, output_list)
TosaErrorValidator.evValidateErrorIfs(
self.ser,
validator_fcns,
error_name,
op=op,
axis = axis,
input_shape = a.shape,
output_shape = result_tens.shape,
input_dtype = a.dtype,
output_dtype = result_tens.dtype,
result_tensor = result_tens,
input_list=input_list,
output_list=output_list,
num_operands=num_operands,
)
attr = ts.TosaSerializerAttribute()
attr.AxisAttribute(axis)
self.ser.addOperator(op['op'], input_list, output_list, attr)
return result_tens
def build_clamp(self, op, a):
result_tens = OutputShaper.unaryOp(self.ser, self.rng, a, None)
attr = ts.TosaSerializerAttribute()
v = [self.getRandNumberDType(a.dtype), self.getRandNumberDType(a.dtype)]
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['op'], [a.name], [result_tens.name], attr)
return result_tens
def build_leaky_relu(self, op, a):
result_tens = OutputShaper.unaryOp(self.ser, self.rng, a, None)
attr = ts.TosaSerializerAttribute()
attr.LeakyReluAttribute(self.getRandNumberDType(DType.FLOAT))
self.ser.addOperator(op['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, self.rng, a, None)
self.ser.addOperator(op['op'], [a.name], [result_tens.name])
return result_tens
def build_sigmoid(self, op, a):
result_tens = OutputShaper.unaryOp(self.ser, self.rng, a, None)
self.ser.addOperator(op['op'], [a.name], [result_tens.name])
return result_tens
def build_tanh(self, op, a):
result_tens = OutputShaper.unaryOp(self.ser, self.rng, a, None)
self.ser.addOperator(op['op'], [a.name], [result_tens.name])
return result_tens
def build_concat(self, op, *a):
assert type(a[-1]) == int
# To store variable length list of input tensors we need to store axis along with it
axis = a[-1]
a = a[:-1]
result_tens = OutputShaper.concatOp(self.ser, axis, *a)
attr = ts.TosaSerializerAttribute()
attr.AxisAttribute(axis)
input_tensor_names = []
for tensor in a:
input_tensor_names.append(tensor.name)
self.ser.addOperator(op['op'], input_tensor_names, [result_tens.name], attr)
return result_tens
def build_pad(self, op, a, padding, pad_const_int, pad_const_float, validator_fcns=None, error_name=None, qinfo=None):
result_tens = OutputShaper.padOp(self.ser, self.rng, a, padding, error_name)
attr = ts.TosaSerializerAttribute()
attr.PadAttribute(padding.flatten(), pad_const_int, pad_const_float)
# Invalidate Input/Output list for error if checks.
input_list = [a.name]
output_list = [result_tens.name]
pCount, cCount = op["operands"]
num_operands = pCount + cCount
input_list, output_list = TosaErrorIfArgGen.eiInvalidateInputOutputList(self, error_name, input_list, output_list)
TosaErrorValidator.evValidateErrorIfs(
self.ser,
validator_fcns,
error_name,
op=op,
input_shape = a.shape,
output_shape = result_tens.shape,
input_dtype = a.dtype,
output_dtype = result_tens.dtype,
pad=padding,
qinfo=qinfo,
result_tensor = result_tens,
input_list=input_list,
output_list=output_list,
num_operands=num_operands,
)
self.ser.addOperator(
op['op'], input_list, output_list, attr, qinfo
)
return result_tens
def build_reshape(self, op, a, newShape, validator_fcns=None, error_name=None):
result_tens = OutputShaper.reshapeOp(self.ser, self.rng, a, newShape, error_name)
# Invalidate Input/Output list for error if checks.
input_list = [a.name]
output_list = [result_tens.name]
pCount, cCount = op["operands"]
num_operands = pCount + cCount
input_list, output_list = TosaErrorIfArgGen.eiInvalidateInputOutputList(self, error_name, input_list, output_list)
TosaErrorValidator.evValidateErrorIfs(
self.ser,
validator_fcns,
error_name,
op=op,
input_shape = a.shape,
output_shape = result_tens.shape,
input_dtype = a.dtype,
output_dtype = result_tens.dtype,
result_tensor = result_tens,
input_list=input_list,
output_list=output_list,
num_operands=num_operands,
)
attr = ts.TosaSerializerAttribute()
attr.ReshapeAttribute(newShape)
self.ser.addOperator(op['op'], input_list, output_list, attr)
return result_tens
def build_reverse(self, op, a, axis):
result_tens = OutputShaper.unaryOp(self.ser, self.rng, a, None)
attr = ts.TosaSerializerAttribute()
attr.AxisAttribute(axis)
self.ser.addOperator(op['op'], [a.name], [result_tens.name], attr)
return result_tens
def build_transpose(self, op, a, perms, validator_fcns=None, error_name=None):
result_tens = OutputShaper.transposeOp(self.ser, self.rng, a, perms, error_name)
attr = ts.TosaSerializerAttribute()
attr.TransposeAttribute(perms)
# Invalidate Input/Output list for error if checks.
input_list = [a.name]
output_list = [result_tens.name]
pCount, cCount = op["operands"]
num_operands = pCount + cCount
input_list, output_list = TosaErrorIfArgGen.eiInvalidateInputOutputList(self, error_name, input_list, output_list)
TosaErrorValidator.evValidateErrorIfs(
self.ser,
validator_fcns,
error_name,
op=op,
input_shape = a.shape,
output_shape = result_tens.shape,
perms=perms,
input_dtype = a.dtype,
output_dtype = result_tens.dtype,
result_tensor = result_tens,
input_list=input_list,
output_list=output_list,
num_operands=num_operands,
)
self.ser.addOperator(op['op'], input_list, output_list, attr)
return result_tens
def build_slice(self, op, a, start, size, validator_fcns=None, error_name=None):
result_tens = OutputShaper.sliceOp(self.ser, self.rng, a, start, size, error_name)
# Invalidate Input/Output list for error if checks.
input_list = [a.name]
output_list = [result_tens.name]
pCount, cCount = op["operands"]
num_operands = pCount + cCount
input_list, output_list = TosaErrorIfArgGen.eiInvalidateInputOutputList(self, error_name, input_list, output_list)
TosaErrorValidator.evValidateErrorIfs(
self.ser,
validator_fcns,
error_name,
op=op,
input_shape = a.shape,
output_shape = result_tens.shape,
input_dtype = a.dtype,
output_dtype = result_tens.dtype,
start=start,
size=size,
result_tensor = result_tens,
input_list=input_list,
output_list=output_list,
num_operands=num_operands,
)
attr = ts.TosaSerializerAttribute()
attr.SliceAttribute(start, size)
self.ser.addOperator(op['op'], input_list, output_list, 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['op'], [a.name], [result_tens.name], attr)
return result_tens
def build_gather(self, op, values):
# Create a new indicies tensor
# here with data that doesn't exceed the dimensions of the values tensor
K = values.shape[1] # K
W = self.randInt(
self.args.tensor_shape_range[0], self.args.tensor_shape_range[1]
) # W
indicies_arr = np.int32(
self.rng.integers(low=0, high=K, size=[values.shape[0], W])
) # (N, W)
indicies = self.ser.addConst(indicies_arr.shape, DType.INT32, indicies_arr)
result_tens = OutputShaper.gatherOp(self.ser, values, indicies)
self.ser.addOperator(op['op'], [values.name, indicies.name], [result_tens.name])
return result_tens
def build_scatter(self, op, values_in, input):
# Create a new indicies tensor
# here with data that doesn't exceed the dimensions of the values_in tensor
K = values_in.shape[1] # K
W = input.shape[1] # W
indicies_arr = np.int32(
self.rng.integers(low=0, high=K, size=[values_in.shape[0], W])
) # (N, W)
indicies = self.ser.addConst(indicies_arr.shape, DType.INT32, indicies_arr)
result_tens = OutputShaper.scatterOp(self.ser, values_in, indicies, input)
self.ser.addOperator(
op['op'], [values_in.name, indicies.name, input.name], [result_tens.name]
)
return result_tens
def build_resize(
self,
op,
input,
mode,
stride,
offset,
shift,
stride_fp,
offset_fp,
output_dims,
input_dtype,
output_dtype,
validator_fcns,
error_name = None,
):
result_tens = OutputShaper.resizeOp(
self.ser,
self.rng,
input,
mode,
stride,
offset,
shift,
stride_fp,
offset_fp,
output_dims,
input_dtype,
output_dtype,
error_name
)
# Invalidate Input/Output list for error if checks.
input_list = [input.name]
output_list = [result_tens.name]
pCount, cCount = op["operands"]
num_operands = pCount + cCount
input_list, output_list = TosaErrorIfArgGen.eiInvalidateInputOutputList(self, error_name, input_list, output_list)
TosaErrorValidator.evValidateErrorIfs(
self.ser,
validator_fcns,
error_name,
op=op,
mode=mode,
shift=shift,
input_dtype=input_dtype,
output_dtype=output_dtype,
input_shape=input.shape,
output_shape=output_dims,
offset=offset,
offset_fp=offset_fp,
stride=stride,
stride_fp=stride_fp,
input_list=input_list,
output_list=output_list,
result_tensor=result_tens,
num_operands=num_operands,
)
attr = ts.TosaSerializerAttribute()
attr.ResizeAttribute(
output_dims, stride, offset, shift, stride_fp, offset_fp, mode
)
self.ser.addOperator(op['op'], input_list, output_list, attr)
return result_tens
def build_identityn(self, op, val, val2):
result_tens = OutputShaper.unaryOp(self.ser, self.rng, val, None)
result_tens2 = OutputShaper.unaryOp(self.ser, self.rng, val2, None)
self.ser.addOperator(
op, [val.name, val2.name], [result_tens.name, result_tens2.name]
)
return result_tens
def build_const(self, op, val):
self.ser.addOutputTensor(val)
return val
# Type Conversion
def build_cast(self, op, val, out_dtype):
result_tens = OutputShaper.typeConversionOp(self.ser, val, out_dtype)
self.ser.addOperator(op['op'], [val.name], [result_tens.name])
return result_tens
def build_rescale(self, op, val, out_dtype, scale32, double_round, per_channel, validator_fcns, error_name):
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.INT8:
input_zp = self.randInt(-128, 128)
in_type_width = in_type_width + 1
elif val.dtype == DType.UINT8:
input_zp = self.randInt(0, 256)
in_type_width = in_type_width + 1
elif error_name == ErrorIf.InputZeroPointNotZero:
input_zp = self.randInt(-128, 128)
if input_zp == 0:
input_zp = input_zp + self.rng.integers(1, 10)
in_type_width = in_type_width + 1
else:
input_zp = 0
if out_dtype == DType.INT8:
output_zp = self.randInt(-128, 128)
out_type_width = out_type_width + 1
elif out_dtype == DType.UINT8:
output_zp = self.randInt(0, 256)
out_type_width = out_type_width + 1
elif error_name == ErrorIf.OutputZeroPointNotZero:
output_zp = self.randInt(-128, 128)
if output_zp == 0:
output_zp = output_zp + self.rng.integers(1, 10)
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^31 - 1 for scale32
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))
# Invalidate Input/Output list for error if checks.
input_list = [val.name]
output_list = [result_tens.name]
pCount, cCount = op["operands"]
num_operands = pCount + cCount
input_list, output_list = TosaErrorIfArgGen.eiInvalidateInputOutputList(self, error_name, input_list, output_list)
qinfo = (input_zp, output_zp)
TosaErrorValidator.evValidateErrorIfs(
self.ser,
validator_fcns,
error_name,
op=op,
input_dtype=val.dtype,
output_dtype=out_dtype,
input_shape=val.shape,
qinfo=qinfo,
scale32 = scale32,
double_round = double_round,
input_list=input_list,
output_list=output_list,
result_tensor=result_tens,
num_operands=num_operands,
)
attr = ts.TosaSerializerAttribute()
attr.RescaleAttribute(
input_zp,
output_zp,
multiplier_arr,
shift_arr,
scale32,
double_round,
per_channel,
)
self.ser.addOperator(op['op'], input_list, output_list, 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, [cond])
# Make then/else tensors
out_shape = then_tens.shape
then_arr = np.int32(self.rng.integers(0, 256, size=out_shape))
else_arr = np.int32(self.rng.integers(0, 256, size=out_shape))
# And the result tensor based on any of the outputs
result_tens = self.ser.addOutput(out_shape, DType.INT32)
# 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['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, then_arr)
self.ser.addOutputTensor(then_tens)
self.ser.startBasicBlock(else_block)
else_tens = self.ser.addConst(out_shape, DType.INT32, 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, [cond])
result_tens = self.ser.addOutput(a.shape, a.dtype)
# 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['op'], [cond_tens.name, a.name, b.name], [result_tens.name], attr
)
if a.dtype in (DType.FLOAT, DType.INT32):
then_op, else_op = Op.ADD, Op.SUB
elif a.dtype in (DType.INT8, DType.INT16):
then_op, else_op = Op.LOGICAL_RIGHT_SHIFT, Op.LOGICAL_LEFT_SHIFT
else:
assert False, f"No tests for DType: {a.dtype}"
for block, op in ((then_block, then_op), (else_block, else_op)):
self.ser.startBasicBlock(block)
self.ser.addInputTensor(a)
self.ser.addInputTensor(b)
tens = self.ser.addOutput(a.shape, a.dtype)
self.ser.addOperator(op, [a.name, b.name], [tens.name])
return result_tens
def build_while_loop(self, op, a, iter_val):
iter = self.ser.addPlaceholder([], DType.INT32, [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)
acc_init_val = np.int32(np.zeros(a.shape))
acc = self.ser.addPlaceholder(a.shape, a.dtype, acc_init_val)
# Intermediate/output tensors for everything going through the loop
iter_out = self.ser.addIntermediate(iter.shape, iter.dtype)
a_out = self.ser.addIntermediate(a.shape, a.dtype)
acc_out = self.ser.addIntermediate(acc.shape, acc.dtype)
# While_loop operator
self.ser.addOperator(
op['op'],
[iter.name, a.name, acc.name],
[iter_out.name, a_out.name, acc_out.name],
attr,
)
self.ser.addOutputTensor(acc_out)
# 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)
acc_body_out = self.ser.addIntermediate(acc.shape, acc.dtype)
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 create_filter_lists(self, op, shapeFilter, rankFilter, dtypeFilter, testType, validator=None):
# Create a default testing rank range, 1-4 inclusive to keep test sizes reasonably small.
default_test_rank_range = range(1, 5)
if not shapeFilter:
shapeFilter = [None]
# Calculate the filters based on what is requested and what the operator allows
rmin, rmax = op["rank"]
if rankFilter is not None:
cleanRankFilter = []
# Ensure rankFilter values are allowed by operator
for rank in rankFilter:
if rank >= rmin and rank <= rmax:
cleanRankFilter.append(rank)
elif rankFilter is None and shapeFilter[0] is None:
# Ensure default behaviour is bounded by default range or by operator,
# whichever is the smaller range of ranks.
opRankRange = range(rmin, rmax + 1)
cleanRankFilter = opRankRange if len(opRankRange) <= len(default_test_rank_range) else default_test_rank_range
else:
cleanRankFilter = range(rmin, rmax + 1)
dtypes = op["types"]
if dtypeFilter is not None:
cleanDtypeFilter = []
# Create list of operator dtypes filtered by requested dtypes
for dtype in dtypes:
if dtype in dtypeFilter or (isinstance(dtype, list) and dtype[0] in dtypeFilter):
cleanDtypeFilter.append(dtype)
else:
cleanDtypeFilter = dtypes
if testType == 'positive':
filterDict = {
'shapeFilter': shapeFilter,
'rankFilter': cleanRankFilter,
'dtypeFilter': cleanDtypeFilter
}
return filterDict
elif testType == 'negative':
if validator is not None:
validator_info = validator(check=False, op=op)
else:
return None
error_arguments = validator_info['param_reqs']
#Set parameters as required
if error_arguments['rank'] != None:
rankFilter = error_arguments['rank']
else:
rankFilter = cleanRankFilter
if error_arguments['dtype'] != None:
dtypeFilter = error_arguments['dtype']
else:
dtypeFilter = cleanDtypeFilter
if error_arguments['shape'] != None:
shapeFilter = error_arguments['shape']
else:
shapeFilter = shapeFilter[:2] # Reduce number of shapes to keep test numbers small
filterDict = {
'shapeFilter': shapeFilter,
'rankFilter': rankFilter,
'dtypeFilter': dtypeFilter
}
return filterDict
def genOpTestList(
self, opName, shapeFilter=[None], rankFilter=None, dtypeFilter=None, testType='positive'
):
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"]
# Test list consists of a tuple of:
# (opName, testNameStr, dtype, shapeList, argumentsList)
testList = []
if testType == 'negative' and "error_if_validators" in op:
error_if_validators = op["error_if_validators"]
else:
error_if_validators = [None]
for validator in error_if_validators:
if validator is not None:
error_name = validator(check=False, op=op)['error_name']
else:
error_name = None
filterDict = self.create_filter_lists(op, shapeFilter, rankFilter, dtypeFilter, testType, validator)
if filterDict == None:
return []
cleanRankFilter = filterDict['rankFilter']
cleanDtypeFilter = filterDict['dtypeFilter']
cleanShapeFilter = filterDict['shapeFilter']
#print(f"Filters: S {shapeFilter}, R {cleanRankFilter}, T {cleanDtypeFilter}")
for r in cleanRankFilter:
if opName.startswith("conv3d"):
assert r == 5, "conv3d test must have input rank == 5"
for t in cleanDtypeFilter:
for shape in cleanShapeFilter:
# Filter out by rank
if shape is not None and len(shape) != r:
continue
self.setTargetShape(shape)
shapeList = tgen_fcn(self, op, r, error_name)
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, error_name)
else:
argList = [("", [])]
for argStr, args in argList:
if testType == 'positive':
if argStr:
testStr = "{}_{}_{}_{}".format(
opName, shapeStr, typeStr, argStr
)
else:
testStr = "{}_{}_{}".format(opName, shapeStr, typeStr)
elif testType == 'negative':
if argStr:
testStr = "{}_ERRORIF_{}_{}_{}_{}".format(
opName, error_name, shapeStr, typeStr, argStr
)
else:
testStr = "{}_ERRORIF_{}_{}_{}".format(opName, error_name, shapeStr, typeStr)
testList.append((opName, testStr, t, error_name, shapeList, args))
if testType == 'positive':
# Remove tests which are expected to fail but don't correlate to a ERROR_IF statement
if "invalid_test_validators" in op:
invalid_test_validators = op["invalid_test_validators"]
clean_testList = []
for test in testList:
for validator_fcn in invalid_test_validators:
remove_test = False
if validator_fcn(opName=test[0], input_dtype=test[2], shapeList=test[4], args=test[5]):
remove_test = True
if not remove_test:
clean_testList.append(test)
testList = clean_testList
return testList
def serializeTest(self, opName, testStr, dtype_or_dtypeList, error_name, 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"]
if "error_if_validators" in op:
error_if_validators = op["error_if_validators"]
else:
error_if_validators = None
pCount, cCount = op["operands"]
num_operands = pCount + cCount
if isinstance(dtype_or_dtypeList, list):
dtypeList = dtype_or_dtypeList
elif op["op"] == Op.CONCAT:
dtypeList = [dtype_or_dtypeList] * len(shapeList)
else:
dtypeList = [dtype_or_dtypeList] * (num_operands)
if op["op"] != Op.CONCAT:
assert (
len(shapeList) == num_operands
), "shapeList length {} must match number of operands {}".format(
len(shapeList), num_operands
)
assert (
len(dtypeList) == num_operands
), "dtypeList length {} must match number of operands {}".format(
len(dtypeList), num_operands
)
try:
qgen = op["qgen"]
except KeyError:
qgen = None
# Build the random tensor operands and the test
tens = []
tens = self.generate_tensors(op, dtypeList, shapeList, testArgs, error_name)
if qgen is not None:
qinfo = qgen(self, op, dtype_or_dtypeList, error_name)
else:
qinfo = None
try:
if error_if_validators is None:
if qinfo is not None:
resultName = build_fcn(self, op, *tens, *testArgs, qinfo)
else:
resultName = build_fcn(self, op, *tens, *testArgs)
else:
if qinfo is not None:
resultName = build_fcn(self, op, *tens, *testArgs, error_if_validators, error_name, qinfo)
else:
resultName = build_fcn(self, op, *tens, *testArgs, error_if_validators, error_name)
except TypeError as e:
print(
"build_fcn: {}\nTensors: {}\nArgs: {}\n".format(
build_fcn, tens, testArgs
)
)
raise e
if resultName is None:
print("Invalid ERROR_IF tests created")
# Save the serialized test
self.serialize("test")
def generate_tensors(self, op, dtypeList, shapeList, testArgs, error_name=None):
pCount, cCount = op["operands"]
tens = []
if (op["op"] == Op.ADD or op["op"] == Op.SUB) and dtypeList[0] == DType.INT32 and error_name == None:
# Make sure the operation does not cause value saturation - where
# the number wraps due to limited number of bits to store the answer
assert (
pCount == 2 and cCount == 0
), "Op.ADD / Op.SUB must have 2 placeholders, 0 consts"
placeholders = []
add = (op["op"] == Op.ADD)
a_arr = self.getRandTensor(shapeList[0], dtypeList[0])
b_arr = self.getRandTensor(shapeList[1], dtypeList[1])
if add:
res_arr = np.add(a_arr, b_arr, dtype=np.int64)
else:
res_arr = np.subtract(a_arr, b_arr, dtype=np.int64)
# Work out the saturation limits
max_i32 = (1 << 31)-1
min_i32 = -(1 << 31)
max_arr = np.full(shapeList[1], max_i32)
min_arr = np.full(shapeList[1], min_i32)
# Find how much values exceed the maximum/minimums
sat_max_arr = np.maximum(res_arr - max_arr, 0)
sat_min_arr = np.minimum(res_arr - min_arr, 0)
if not add:
# Swap saturation values and negate values as we need to perform opposite operations
sat_max_arr, sat_min_arr = -sat_min_arr, -sat_max_arr
# Create new array of unsaturated values by clipping values as needed
b_unsat_arr = b_arr
if (sat_max_arr != 0).any():
# Clip values that cause saturation
b_unsat_arr = np.subtract(b_unsat_arr, sat_max_arr, dtype=np.int32)
# Reduce axes in unsaturated tensor to match original tensor
for axis, dim in enumerate(b_arr.shape):
if dim != b_unsat_arr.shape[axis]:
assert ( dim == 1 ), "Op.ADD / SUB dimension must be 1 or matching to be broadcastable"
b_unsat_arr = np.amin(b_unsat_arr, axis=axis, keepdims=True)
if (sat_min_arr != 0).any():
# Clip values that cause saturation
b_unsat_arr = np.subtract(b_unsat_arr, sat_min_arr, dtype=np.int32)
# Reduce axes in unsaturated tensor to match original tensor
for axis, dim in enumerate(b_arr.shape):
if dim != b_unsat_arr.shape[axis]:
assert ( dim == 1 ), "Op.ADD / SUB dimension must be 1 or matching to be broadcastable"
b_unsat_arr = np.amax(b_unsat_arr, axis=axis, keepdims=True)
placeholders.append(
self.ser.addPlaceholder(shapeList[0], dtypeList[0], a_arr)
)
placeholders.append(
self.ser.addPlaceholder(shapeList[1], dtypeList[1], b_unsat_arr)
)
tens.extend(placeholders)
elif (op["op"] == Op.COND_IF or op["op"] == Op.WHILE_LOOP) and dtypeList[0] == DType.INT32:
# Limit input tensors with cond_if_binary or while_loop to stop
# saturation of add/sub ops
pRemain = pCount
placeholders = []
for idx, shape in enumerate(shapeList[:]):
arr = self.getRandTensor(shapeList[idx], DType.INT16)
if pRemain > 0:
placeholders.append(self.ser.addPlaceholder(shape, dtypeList[idx], arr))
pRemain -= 1
else:
placeholders.append(self.ser.addConst(shape, dtypeList[idx], arr))
tens.extend(placeholders)
elif op["op"] == Op.ARITHMETIC_RIGHT_SHIFT:
# Force value of operand[1] to be within [0, num_bits]
assert (
pCount == 2 and cCount == 0
), "Op.ArithmeticRightShift must have 2 placeholders, 0 consts"
placeholders = []
for idx, shape in enumerate(shapeList[:]):
if idx == 1:
if dtypeList[idx] == DType.INT8:
arr = np.int32(self.rng.integers(low=0, high=8, size=shape))
elif dtypeList[idx] == DType.INT16:
arr = np.int32(self.rng.integers(low=0, high=16, size=shape))
elif dtypeList[idx] == DType.INT32:
arr = np.int32(self.rng.integers(low=0, high=32, size=shape))
else:
raise Exception("OpArithmeticRightShift: invalid input dtype")
else:
arr = self.getRandTensor(shape, dtypeList[idx])
placeholders.append(self.ser.addPlaceholder(shape, dtypeList[idx], arr))
tens.extend(placeholders)
elif op["op"] == Op.SELECT:
# Set datatype of condition tensor to boolean
dtypeList[0] = DType.BOOL
tens.extend(
self.buildPlaceholderTensors(shapeList[0:pCount], dtypeList[0:pCount])
)
tens.extend(self.buildConstTensors(shapeList[pCount:], dtypeList[pCount:]))
elif op["op"] == Op.INTDIV and error_name == None:
assert (
pCount == 2 and cCount == 0
), "Op.INTDIV must have 2 placeholders, 0 consts"
placeholders = []
# Two invalid cases for Op.INTDIV:
# 1. divisor == 0
# 2. dividend == -(1<<31) and divisor == -1
while True:
dividend_arr = self.getRandTensor(shapeList[0], dtypeList[0])
divisor_arr = self.getRandTensor(shapeList[1], dtypeList[1])
if (divisor_arr == 0).any():
continue
if (dividend_arr == -(2 ** 31)).any() and (divisor_arr == -1).any():
continue
break
placeholders.append(
self.ser.addPlaceholder(shapeList[0], dtypeList[0], dividend_arr)
)
placeholders.append(
self.ser.addPlaceholder(shapeList[1], dtypeList[1], divisor_arr)
)
tens.extend(placeholders)
elif op["op"] == Op.MUL:
assert (
pCount == 2 and cCount == 0
), "Op.MUL must have 2 placeholders, 0 consts"
if dtypeList[0] == DType.FLOAT:
tens.extend(self.buildPlaceholderTensors(shapeList[:], dtypeList[:]))
else:
placeholders = []
# Make sure multiply result in int32 range
shift = testArgs[0]
if dtypeList[0] == DType.INT8:
num_bits = 8
elif dtypeList[0] == DType.INT16:
num_bits = 16
elif dtypeList[0] == DType.INT32:
num_bits = 32
else:
raise Exception("OpMul: invalid input dtype")
for idx, shape in enumerate(shapeList[:]):
low = -(2 ** (num_bits - 1))
high = (2 ** (num_bits - 1)) - 1
a_arr = np.int32(
self.rng.integers(low=low, high=high, size=shapeList[0])
)
b_arr = np.int32(
self.rng.integers(low=low, high=high, size=shapeList[1])
)
i = 0
while True:
a_arr_64 = a_arr.astype(np.int64)
b_arr_64 = b_arr.astype(np.int64)
if shift > 0:
rounding = 1 << (shift - 1)
result_arr = ((a_arr_64 * b_arr_64) + rounding) >> shift
else:
result_arr = a_arr_64 * b_arr_64
if (result_arr > -(2 ** 31)).all() and (
result_arr <= ((2 ** 31) - 1)
).all():
break
i = i + 1
a_arr = a_arr // 2
b_arr = b_arr // 2
placeholders.append(
self.ser.addPlaceholder(shapeList[0], dtypeList[0], a_arr)
)
placeholders.append(
self.ser.addPlaceholder(shapeList[1], dtypeList[1], b_arr)
)
tens.extend(placeholders)
elif op["op"] == Op.CONCAT:
count = len(shapeList) - self.args.num_const_inputs_concat
if count < 1:
count = 1
if self.args.num_const_inputs_concat == 0:
count = len(shapeList)
shapeList = TosaTensorGen.tgConcatConstInput(self, shapeList, testArgs[0])
tens.extend(
self.buildPlaceholderTensors(shapeList[0:count], dtypeList[0:count])
)
tens.extend(self.buildConstTensors(shapeList[count:], dtypeList[count:]))
else:
tens.extend(
self.buildPlaceholderTensors(shapeList[0:pCount], dtypeList[0:pCount])
)
tens.extend(self.buildConstTensors(shapeList[pCount:], dtypeList[pCount:]))
return tens
def createDynamicOpLists(self):
# Dynamically create op lists for convolutions with a list of kernel sizes
KERNELS_2D = [[1, 1], [2, 2], [3, 3], [5, 5], [3, 1], [1, 3]]
for k in KERNELS_2D:
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
KERNELS_3D = [[1, 1, 1], [2, 1, 1], [1, 2, 1], [1, 1, 2]]
for k in KERNELS_3D:
testName = "conv3d_{}x{}x{}".format(k[0], k[1], k[2])
self.TOSA_OP_LIST[testName] = self.TOSA_OP_LIST["conv3d_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_INT = [DType.INT8, DType.INT16, DType.INT32] # Excludes INT4
TYPE_INT_FP = [DType.INT8, DType.INT16, DType.INT32, DType.FLOAT] # Excludes INT4
TYPE_BOOL = [DType.BOOL]
TYPE_FI32 = [DType.FLOAT, DType.INT32]
TYPE_FIB = [DType.FLOAT, DType.INT8, DType.INT16, DType.INT32, DType.BOOL]
TYPE_FI16 = [DType.FLOAT, DType.INT16]
TYPE_NARROW_INT_FP = [DType.INT8, DType.INT16, DType.FLOAT]
TYPE_CONV = [
[DType.INT8, DType.INT4, DType.INT32],
[DType.INT8, DType.INT8, DType.INT32],
[DType.INT16, DType.INT8, DType.INT48],
DType.FLOAT,
]
DEFAULT_RANK_RANGE = (1, TOSA_TENSOR_MAX_RANK)
TOSA_OP_LIST = {
# Tensor operators
"argmax": {
"op": Op.ARGMAX,
"operands": (1, 0),
"rank": (1, 4),
"build_fcn": (build_argmax, TosaTensorGen.tgBasic, TosaArgGen.agAxis),
"types": TYPE_NARROW_INT_FP,
"error_if_validators": (TosaErrorValidator.evAxisSmallerZero, TosaErrorValidator.evAxisLargerRank, TosaErrorValidator.evArgmaxOutputRankMismatch,
TosaErrorValidator.evArgmaxOutputShapeMismatch, TosaErrorValidator.evWrongRank, TosaErrorValidator.evWrongInputType,
TosaErrorValidator.evWrongOutputType, TosaErrorValidator.evWrongInputList, TosaErrorValidator.evWrongOutputList)
},
"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,
"invalid_test_validators": (TosaInvalidValidator.ivHeightWidthSmallerZero,),
"error_if_validators": (TosaErrorValidator.evKernelSmallerOne, TosaErrorValidator.evStrideSmallerOne, TosaErrorValidator.evPadSmallerZero,
TosaErrorValidator.evWrongRank, TosaErrorValidator.evWrongInputType, TosaErrorValidator.evWrongOutputType, TosaErrorValidator.evWrongInputList,
TosaErrorValidator.evWrongOutputList, TosaErrorValidator.evInputZeroPointNotZero, TosaErrorValidator.evOutputZeroPointNotZero,
TosaErrorValidator.evPadLargerEqualKernel, TosaErrorValidator.evPoolingOutputShapeMismatch)
},
# Templated operator. Filled in by createDynamicOpLists
"conv2d_TEMPLATE": {
"op": Op.CONV2D,
"operands": (1, 2),
"rank": (4, 4),
"build_fcn": (build_conv2d, TosaTensorGen.tgConv2D, TosaArgGen.agConv),
"qgen": TosaQuantGen.qgConv,
"types": TYPE_CONV,
"invalid_test_validators": (TosaInvalidValidator.ivHeightWidthSmallerZero,),
"template": True,
},
# Templated operator. Filled in by createDynamicOpLists
"conv3d_TEMPLATE": {
"op": Op.CONV3D,
"operands": (1, 2),
"rank": (5, 5),
"build_fcn": (build_conv3d, TosaTensorGen.tgConv3D, TosaArgGen.agConv),
"qgen": TosaQuantGen.qgConv,
"types": TYPE_CONV,
"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.agConv,
),
"qgen": TosaQuantGen.qgConv,
"types": TYPE_CONV,
"invalid_test_validators": (TosaInvalidValidator.ivHeightWidthSmallerZero,),
"template": True,
},
"fully_connected": {
"op": Op.FULLY_CONNECTED,
"operands": (1, 2),
"rank": (2, 2),
"build_fcn": (build_fully_connected, TosaTensorGen.tgFullyConnected, None),
"qgen": TosaQuantGen.qgConv,
"types": TYPE_CONV,
"error_if_validators": (TosaErrorValidator.evInputZeroPointNotZero, TosaErrorValidator.evWeightZeroPointNotZero, TosaErrorValidator.evWrongRank,
TosaErrorValidator.evWrongInputType, TosaErrorValidator.evWrongOutputType, TosaErrorValidator.evWrongInputList, TosaErrorValidator.evWrongOutputList)
},
"matmul": {
"op": Op.MATMUL,
"operands": (2, 0),
"rank": (3, 3),
"build_fcn": (build_matmul, TosaTensorGen.tgMatmul, None),
"qgen": TosaQuantGen.qgMatmul,
"types": TYPE_NARROW_INT_FP,
"error_if_validators": (TosaErrorValidator.evInputZeroPointNotZero, TosaErrorValidator.evWrongRank, TosaErrorValidator.evWrongInputType,
TosaErrorValidator.evWrongOutputType, TosaErrorValidator.evWrongInputList, TosaErrorValidator.evWrongOutputList)
},
"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,
"invalid_test_validators": (TosaInvalidValidator.ivHeightWidthSmallerZero,),
"error_if_validators": (TosaErrorValidator.evKernelSmallerOne, TosaErrorValidator.evStrideSmallerOne, TosaErrorValidator.evPadSmallerZero,
TosaErrorValidator.evWrongRank, TosaErrorValidator.evWrongInputType, TosaErrorValidator.evWrongOutputType, TosaErrorValidator.evWrongInputList,
TosaErrorValidator.evWrongOutputList, TosaErrorValidator.evPadLargerEqualKernel, TosaErrorValidator.evPoolingOutputShapeMismatch)
},
# Templated operator. Filled in by createDynamicOpLists
"transpose_conv2d_TEMPLATE": {
"op": Op.TRANSPOSE_CONV2D,
"operands": (1, 2),
"rank": (4, 4),
"build_fcn": (
build_transpose_conv2d,
TosaTensorGen.tgTransposeConv2D,
TosaArgGen.agTransposeConv2D,
),
"qgen": TosaQuantGen.qgConv,
"types": TYPE_CONV,
"invalid_test_validators": (TosaInvalidValidator.ivNonPositiveOutputShape,),
"template": True,
},
# Activation functions
"clamp": {
"op": Op.CLAMP,
"operands": (1, 0),
"build_fcn": (build_clamp, TosaTensorGen.tgBasic, None),
"types": TYPE_NARROW_INT_FP,
},
"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,
},
# Elementwise Binary Operators
"add": {
"op": Op.ADD,
"operands": (2, 0),
"build_fcn": (build_binary_broadcast, TosaTensorGen.tgBroadcastFuzz, None),
"types": TYPE_FI32,
"error_if_validators": (TosaErrorValidator.evRankMismatch, TosaErrorValidator.evWrongInputType, TosaErrorValidator.evWrongOutputType,
TosaErrorValidator.evWrongInputList, TosaErrorValidator.evWrongOutputList)
},
"arithmetic_right_shift": {
"op": Op.ARITHMETIC_RIGHT_SHIFT,
"operands": (2, 0),
"build_fcn": (
build_arithmetic_right_shift,
TosaTensorGen.tgBroadcastFuzz,
TosaArgGen.agArithmeticRightShift,
),
"types": TYPE_INT,
},
"bitwise_and": {
"op": Op.BITWISE_AND,
"operands": (2, 0),
"build_fcn": (build_binary_broadcast, TosaTensorGen.tgBroadcastFuzz, None),
"types": TYPE_INT,
"error_if_validators": (TosaErrorValidator.evRankMismatch, TosaErrorValidator.evWrongInputType, TosaErrorValidator.evWrongOutputType,
TosaErrorValidator.evWrongInputList, TosaErrorValidator.evWrongOutputList)
},
"bitwise_or": {
"op": Op.BITWISE_OR,
"operands": (2, 0),
"build_fcn": (build_binary_broadcast, TosaTensorGen.tgBroadcastFuzz, None),
"types": TYPE_INT,
"error_if_validators": (TosaErrorValidator.evRankMismatch, TosaErrorValidator.evWrongInputType, TosaErrorValidator.evWrongOutputType,
TosaErrorValidator.evWrongInputList, TosaErrorValidator.evWrongOutputList)
},
"bitwise_xor": {
"op": Op.BITWISE_XOR,
"operands": (2, 0),
"build_fcn": (build_binary_broadcast, TosaTensorGen.tgBroadcastFuzz, None),
"types": TYPE_INT,
"error_if_validators": (TosaErrorValidator.evRankMismatch, TosaErrorValidator.evWrongInputType, TosaErrorValidator.evWrongOutputType,
TosaErrorValidator.evWrongInputList, TosaErrorValidator.evWrongOutputList)
},
"intdiv": {
"op": Op.INTDIV,
"operands": (2, 0),
"build_fcn": (build_binary_broadcast, TosaTensorGen.tgBroadcastFuzz, None),
"types": [DType.INT32],
"error_if_validators": (TosaErrorValidator.evRankMismatch, TosaErrorValidator.evWrongInputType, TosaErrorValidator.evWrongOutputType,
TosaErrorValidator.evWrongInputList, TosaErrorValidator.evWrongOutputList)
},
"logical_and": {
"op": Op.LOGICAL_AND,
"operands": (2, 0),
"build_fcn": (build_binary_broadcast, TosaTensorGen.tgBroadcastFuzz, None),
"types": TYPE_BOOL,
"error_if_validators": (TosaErrorValidator.evRankMismatch, TosaErrorValidator.evWrongInputType, TosaErrorValidator.evWrongOutputType,
TosaErrorValidator.evWrongInputList, TosaErrorValidator.evWrongOutputList)
},
"logical_left_shift": {
"op": Op.LOGICAL_LEFT_SHIFT,
"operands": (2, 0),
"build_fcn": (build_binary_broadcast, TosaTensorGen.tgBroadcastFuzz, None),
"types": TYPE_INT,
"error_if_validators": (TosaErrorValidator.evRankMismatch, TosaErrorValidator.evWrongInputType, TosaErrorValidator.evWrongOutputType,
TosaErrorValidator.evWrongInputList, TosaErrorValidator.evWrongOutputList)
},
"logical_right_shift": {
"op": Op.LOGICAL_RIGHT_SHIFT,
"operands": (2, 0),
"build_fcn": (build_binary_broadcast, TosaTensorGen.tgBroadcastFuzz, None),
"types": TYPE_INT,
"error_if_validators": (TosaErrorValidator.evRankMismatch, TosaErrorValidator.evWrongInputType, TosaErrorValidator.evWrongOutputType,
TosaErrorValidator.evWrongInputList, TosaErrorValidator.evWrongOutputList)
},
"logical_or": {
"op": Op.LOGICAL_OR,
"operands": (2, 0),
"build_fcn": (build_binary_broadcast, TosaTensorGen.tgBroadcastFuzz, None),
"types": TYPE_BOOL,
"error_if_validators": (TosaErrorValidator.evRankMismatch, TosaErrorValidator.evWrongInputType, TosaErrorValidator.evWrongOutputType,
TosaErrorValidator.evWrongInputList, TosaErrorValidator.evWrongOutputList)
},
"logical_xor": {
"op": Op.LOGICAL_XOR,
"operands": (2, 0),
"build_fcn": (build_binary_broadcast, TosaTensorGen.tgBroadcastFuzz, None),
"types": TYPE_BOOL,
"error_if_validators": (TosaErrorValidator.evRankMismatch, TosaErrorValidator.evWrongInputType, TosaErrorValidator.evWrongOutputType,
TosaErrorValidator.evWrongInputList, TosaErrorValidator.evWrongOutputList)
},
"maximum": {
"op": Op.MAXIMUM,
"operands": (2, 0),
"build_fcn": (build_binary_broadcast, TosaTensorGen.tgBroadcastFuzz, None),
"types": TYPE_FI32,
"error_if_validators": (TosaErrorValidator.evRankMismatch, TosaErrorValidator.evWrongInputType, TosaErrorValidator.evWrongOutputType,
TosaErrorValidator.evWrongInputList, TosaErrorValidator.evWrongOutputList)
},
"minimum": {
"op": Op.MINIMUM,
"operands": (2, 0),
"build_fcn": (build_binary_broadcast, TosaTensorGen.tgBroadcastFuzz, None),
"types": TYPE_FI32,
"error_if_validators": (TosaErrorValidator.evRankMismatch, TosaErrorValidator.evWrongInputType, TosaErrorValidator.evWrongOutputType,
TosaErrorValidator.evWrongInputList, TosaErrorValidator.evWrongOutputList)
},
"mul": {
"op": Op.MUL,
"operands": (2, 0),
"build_fcn": (build_mul, TosaTensorGen.tgBroadcastFuzz, TosaArgGen.agMul),
"types": TYPE_INT_FP,
},
"pow": {
"op": Op.POW,
"operands": (2, 0),
"build_fcn": (build_binary_broadcast, TosaTensorGen.tgBasic, None),
"types": TYPE_FP,
"error_if_validators": (TosaErrorValidator.evRankMismatch, TosaErrorValidator.evWrongInputType, TosaErrorValidator.evWrongOutputType,
TosaErrorValidator.evWrongInputList, TosaErrorValidator.evWrongOutputList)
},
"sub": {
"op": Op.SUB,
"operands": (2, 0),
"build_fcn": (build_binary_broadcast, TosaTensorGen.tgBroadcastFuzz, None),
"types": TYPE_FI32,
"error_if_validators": (TosaErrorValidator.evRankMismatch, TosaErrorValidator.evWrongInputType, TosaErrorValidator.evWrongOutputType,
TosaErrorValidator.evWrongInputList, TosaErrorValidator.evWrongOutputList)
},
"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, TosaArgGen.agTable),
"types": [DType.INT8, DType.INT16],
},
# Elementwise Unary operators
"abs": {
"op": Op.ABS,
"operands": (1, 0),
"build_fcn": (build_unary, TosaTensorGen.tgBasic, None),
"types": TYPE_FI32,
"error_if_validators": (TosaErrorValidator.evWrongInputType, TosaErrorValidator.evWrongOutputType,
TosaErrorValidator.evWrongInputList, TosaErrorValidator.evWrongOutputList)
},
"bitwise_not": {
"op": Op.BITWISE_NOT,
"operands": (1, 0),
"build_fcn": (build_unary, TosaTensorGen.tgBasic, None),
"types": TYPE_INT,
"error_if_validators": (TosaErrorValidator.evWrongInputType, TosaErrorValidator.evWrongOutputType,
TosaErrorValidator.evWrongInputList, TosaErrorValidator.evWrongOutputList)
},
"ceil": {
"op": Op.CEIL,
"operands": (1, 0),
"build_fcn": (build_unary, TosaTensorGen.tgBasic, None),
"types": TYPE_FP,
"error_if_validators": (TosaErrorValidator.evWrongInputType, TosaErrorValidator.evWrongOutputType,
TosaErrorValidator.evWrongInputList, TosaErrorValidator.evWrongOutputList)
},
"clz": {
"op": Op.CLZ,
"operands": (1, 0),
"build_fcn": (build_unary, TosaTensorGen.tgBasic, None),
"types": [DType.INT32],
"error_if_validators": (TosaErrorValidator.evWrongInputType, TosaErrorValidator.evWrongOutputType,
TosaErrorValidator.evWrongInputList, TosaErrorValidator.evWrongOutputList)
},
"exp": {
"op": Op.EXP,
"operands": (1, 0),
"build_fcn": (build_unary, TosaTensorGen.tgBasic, None),
"types": TYPE_FP,
"error_if_validators": (TosaErrorValidator.evWrongInputType, TosaErrorValidator.evWrongOutputType,
TosaErrorValidator.evWrongInputList, TosaErrorValidator.evWrongOutputList)
},
"floor": {
"op": Op.FLOOR,
"operands": (1, 0),
"build_fcn": (build_unary, TosaTensorGen.tgBasic, None),
"types": TYPE_FP,
"error_if_validators": (TosaErrorValidator.evWrongInputType, TosaErrorValidator.evWrongOutputType,
TosaErrorValidator.evWrongInputList, TosaErrorValidator.evWrongOutputList)
},
"log": {
"op": Op.LOG,
"operands": (1, 0),
"build_fcn": (build_unary, TosaTensorGen.tgBasic, None),
"types": TYPE_FP,
"error_if_validators": (TosaErrorValidator.evWrongInputType, TosaErrorValidator.evWrongOutputType,
TosaErrorValidator.evWrongInputList, TosaErrorValidator.evWrongOutputList)
},
"logical_not": {
"op": Op.LOGICAL_NOT,
"operands": (1, 0),
"build_fcn": (build_unary, TosaTensorGen.tgBasic, None),
"types": TYPE_BOOL,
"error_if_validators": (TosaErrorValidator.evWrongInputType, TosaErrorValidator.evWrongOutputType,
TosaErrorValidator.evWrongInputList, TosaErrorValidator.evWrongOutputList)
},
"negate": {
"op": Op.NEGATE,
"operands": (1, 0),
"build_fcn": (build_unary, TosaTensorGen.tgBasic, None),
"qgen": TosaQuantGen.qgUnary,
"types": TYPE_INT_FP,
"error_if_validators": (TosaErrorValidator.evInputZeroPointNotZero, TosaErrorValidator.evOutputZeroPointNotZero,
TosaErrorValidator.evWrongInputType, TosaErrorValidator.evWrongOutputType, TosaErrorValidator.evWrongInputList,
TosaErrorValidator.evWrongOutputList)
},
"reciprocal": {
"op": Op.RECIPROCAL,
"operands": (1, 0),
"build_fcn": (build_unary, TosaTensorGen.tgBasic, None),
"types": TYPE_FP,
"error_if_validators": (TosaErrorValidator.evWrongInputType, TosaErrorValidator.evWrongOutputType,
TosaErrorValidator.evWrongInputList, TosaErrorValidator.evWrongOutputList)
},
"rsqrt": {
"op": Op.RSQRT,
"operands": (1, 0),
"build_fcn": (build_unary, TosaTensorGen.tgBasic, None),
"types": TYPE_FP,
"error_if_validators": (TosaErrorValidator.evWrongInputType, TosaErrorValidator.evWrongOutputType,
TosaErrorValidator.evWrongInputList, TosaErrorValidator.evWrongOutputList)
},
# Elementwise 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,
},
# Reduction operators
"reduce_all": {
"op": Op.REDUCE_ALL,
"operands": (1, 0),
"build_fcn": (build_reduce, TosaTensorGen.tgBasic, TosaArgGen.agAxis),
"types": TYPE_BOOL,
"error_if_validators": (TosaErrorValidator.evAxisLargerRank, TosaErrorValidator.evAxisSmallerZero, TosaErrorValidator.evShapeOfAxisNotOne,
TosaErrorValidator.evWrongInputType, TosaErrorValidator.evWrongOutputType, TosaErrorValidator.evWrongRank,
TosaErrorValidator.evWrongInputList, TosaErrorValidator.evWrongOutputList)
},
"reduce_any": {
"op": Op.REDUCE_ANY,
"operands": (1, 0),
"build_fcn": (build_reduce, TosaTensorGen.tgBasic, TosaArgGen.agAxis),
"types": TYPE_BOOL,
"error_if_validators": (TosaErrorValidator.evAxisLargerRank, TosaErrorValidator.evAxisSmallerZero, TosaErrorValidator.evShapeOfAxisNotOne,
TosaErrorValidator.evWrongInputType, TosaErrorValidator.evWrongOutputType, TosaErrorValidator.evWrongRank,
TosaErrorValidator.evWrongInputList, TosaErrorValidator.evWrongOutputList)
},
"reduce_max": {
"op": Op.REDUCE_MAX,
"operands": (1, 0),
"build_fcn": (build_reduce, TosaTensorGen.tgBasic, TosaArgGen.agAxis),
"types": TYPE_INT_FP,
"error_if_validators": (TosaErrorValidator.evAxisLargerRank, TosaErrorValidator.evAxisSmallerZero, TosaErrorValidator.evShapeOfAxisNotOne,
TosaErrorValidator.evWrongInputType, TosaErrorValidator.evWrongOutputType, TosaErrorValidator.evWrongRank,
TosaErrorValidator.evWrongInputList, TosaErrorValidator.evWrongOutputList)
},
"reduce_min": {
"op": Op.REDUCE_MAX,
"operands": (1, 0),
"build_fcn": (build_reduce, TosaTensorGen.tgBasic, TosaArgGen.agAxis),
"types": TYPE_INT_FP,
"error_if_validators": (TosaErrorValidator.evAxisLargerRank, TosaErrorValidator.evAxisSmallerZero, TosaErrorValidator.evShapeOfAxisNotOne,
TosaErrorValidator.evWrongInputType, TosaErrorValidator.evWrongOutputType, TosaErrorValidator.evWrongRank,
TosaErrorValidator.evWrongInputList, TosaErrorValidator.evWrongOutputList)
},
"reduce_product": {
"op": Op.REDUCE_PRODUCT,
"operands": (1, 0),
"build_fcn": (build_reduce, TosaTensorGen.tgBasic, TosaArgGen.agAxis),
"types": TYPE_FP,
"error_if_validators": (TosaErrorValidator.evAxisLargerRank, TosaErrorValidator.evAxisSmallerZero, TosaErrorValidator.evShapeOfAxisNotOne,
TosaErrorValidator.evWrongInputType, TosaErrorValidator.evWrongOutputType, TosaErrorValidator.evWrongRank,
TosaErrorValidator.evWrongInputList, TosaErrorValidator.evWrongOutputList)
},
"reduce_sum": {
"op": Op.REDUCE_SUM,
"operands": (1, 0),
"build_fcn": (build_reduce, TosaTensorGen.tgBasic, TosaArgGen.agAxis),
"types": TYPE_FI32,
"error_if_validators": (TosaErrorValidator.evAxisLargerRank, TosaErrorValidator.evAxisSmallerZero, TosaErrorValidator.evShapeOfAxisNotOne,
TosaErrorValidator.evWrongInputType, TosaErrorValidator.evWrongOutputType, TosaErrorValidator.evWrongRank,
TosaErrorValidator.evWrongInputList, TosaErrorValidator.evWrongOutputList)
},
# Data layout operators
"concat": {
"op": Op.CONCAT,
"operands": (2, 0),
"build_fcn": (build_concat, TosaTensorGen.tgConcat, TosaArgGen.agAxis),
"types": TYPE_FIB,
},
"pad": {
"op": Op.PAD,
"operands": (1, 0),
"rank": (1, 5),
"build_fcn": (build_pad, TosaTensorGen.tgBasic, TosaArgGen.agPad),
"qgen": TosaQuantGen.qgPad,
"types": TYPE_FIB,
"error_if_validators": (TosaErrorValidator.evInputZeroPointNotZero, TosaErrorValidator.evWrongInputType, TosaErrorValidator.evPadSmallerZero,
TosaErrorValidator.evWrongOutputType, TosaErrorValidator.evWrongInputList, TosaErrorValidator.evWrongOutputList)
},
"reshape": {
"op": Op.RESHAPE,
"operands": (1, 0),
"build_fcn": (build_reshape, TosaTensorGen.tgBasic, TosaArgGen.agReshape),
"types": TYPE_FIB,
"error_if_validators": (TosaErrorValidator.evTensorSizeInputOutputMismatch, TosaErrorValidator.evWrongInputType, TosaErrorValidator.evWrongOutputType,
TosaErrorValidator.evWrongInputList, TosaErrorValidator.evWrongOutputList)
},
"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),
"rank": (1, 4),
"build_fcn": (build_slice, TosaTensorGen.tgBasic, TosaArgGen.agSlice),
"types": TYPE_FIB,
"error_if_validators": (TosaErrorValidator.evStartSmallerZero, TosaErrorValidator.evSizeSmallerEqualZero, TosaErrorValidator.evStartSizeOutsideBounds,
TosaErrorValidator.evSizeOutputShapeMismatch, TosaErrorValidator.evInputSizeStartLengthMismatch, TosaErrorValidator.evWrongRank,
TosaErrorValidator.evWrongInputType, TosaErrorValidator.evWrongOutputType, TosaErrorValidator.evWrongInputList, TosaErrorValidator.evWrongOutputList)
},
"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": (1, 4),
"build_fcn": (
build_transpose,
TosaTensorGen.tgBasic,
TosaArgGen.agTranspose,
),
"types": TYPE_FIB,
"error_if_validators": (TosaErrorValidator.evIndexOutsideBounds, TosaErrorValidator.evIndexUsedTwice, TosaErrorValidator.evWrongRank,
TosaErrorValidator.evWrongInputType, TosaErrorValidator.evWrongOutputType, TosaErrorValidator.evWrongInputList, TosaErrorValidator.evWrongOutputList)
},
# Data nodes
"const": {
"op": Op.CONST,
"operands": (0, 1),
"build_fcn": (build_const, TosaTensorGen.tgBasic, None),
"types": TYPE_FIB,
},
"identity": {
"op": Op.IDENTITY,
"operands": (1, 0),
"build_fcn": (build_unary, TosaTensorGen.tgBasic, None),
"types": TYPE_FIB,
},
# Scatter/Gather
"gather": {
"op": Op.GATHER,
# Only specify 'values' tensor here. 'indices' is generated in op building stage
"operands": (1, 0),
"rank": (3, 3),
"build_fcn": (build_gather, TosaTensorGen.tgBasic, None),
"types": TYPE_INT_FP,
},
"scatter": {
"op": Op.SCATTER,
# Only specify 'values_in' tensor here.
#'indices' and 'input' are generated in op building stage
"operands": (2, 0),
"rank": (3, 3),
"build_fcn": (build_scatter, TosaTensorGen.tgScatter, None),
"types": TYPE_INT_FP,
},
# 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, DType.FLOAT],
"invalid_test_validators": (TosaInvalidValidator.ivWrongDataTypeOrModeResize, TosaInvalidValidator.ivBadStride),
"error_if_validators": (TosaErrorValidator.evMaxDimExceeded, TosaErrorValidator.evStrideSmallerEqualZero, TosaErrorValidator.evStrideLargerDimension,
TosaErrorValidator.evStrideLargerEqualMax, TosaErrorValidator.evOffsetSmallerEqualMin, TosaErrorValidator.evOffsetLargerEqualMax,
TosaErrorValidator.evShiftNotZero, TosaErrorValidator.evShiftSmallerOne, TosaErrorValidator.evShiftLargerEleven, TosaErrorValidator.evWrongInputType,
TosaErrorValidator.evWrongOutputType, TosaErrorValidator.evWrongRank, TosaErrorValidator.evWrongInputList, TosaErrorValidator.evWrongOutputList,
TosaErrorValidator.evBatchMismatch, TosaErrorValidator.evChannelMismatch)
},
# 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),
"rank": (1,4),
"build_fcn": (build_rescale, TosaTensorGen.tgBasic, TosaArgGen.agRescale),
"types": [DType.UINT8, DType.INT8, DType.INT16, DType.INT32, DType.INT48],
"error_if_validators": (TosaErrorValidator.evInputZeroPointNotZero, TosaErrorValidator.evOutputZeroPointNotZero, TosaErrorValidator.evScaleTrue,
TosaErrorValidator.evScaleNotTrue, TosaErrorValidator.evWrongInputType, TosaErrorValidator.evWrongOutputType, TosaErrorValidator.evWrongRank,
TosaErrorValidator.evWrongInputList, TosaErrorValidator.evWrongOutputList)
},
# Custom
# Not implemented.
# Control flow operators
# 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_INT_FP,
},
# 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, rng, a, b, error_name=None):
if error_name != ErrorIf.RankMismatch:
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 and error_name == None:
shape.append(b.shape[i])
else:
shape.append(a.shape[i])
if error_name == ErrorIf.WrongOutputType:
all_dtypes = [DType.INT8, DType.INT16, DType.INT32, DType.INT48, DType.FLOAT]
wrong_dtypes = list(set(all_dtypes) - set([a.dtype]))
outputDType = rng.choice(wrong_dtypes)
else:
outputDType = a.dtype
return ser.addOutput(shape, outputDType)
@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)
@staticmethod
def unaryOp(ser, rng, a, error_name=None):
if error_name == ErrorIf.WrongOutputType:
all_dtypes = [DType.INT8, DType.INT16, DType.INT32, DType.INT48, DType.FLOAT]
wrong_dtypes = list(set(all_dtypes) - set([a.dtype]))
outputDType = rng.choice(wrong_dtypes)
else:
outputDType = a.dtype
return ser.addOutput(a.shape, outputDType)
@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)
@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)
@staticmethod
def reduceOp(ser, rng, a, axis, error_name=None):
shape = a.shape.copy()
if error_name not in [ErrorIf.AxisSmallerZero, ErrorIf.AxisLargerRank, ErrorIf.ShapeOfAxisNotOne]:
shape[axis] = 1
if error_name == ErrorIf.ShapeOfAxisNotOne and shape[axis] == 1:
shape[axis] = rng.integers(2, 10)
if error_name == ErrorIf.WrongOutputType:
all_dtypes = [DType.INT8, DType.INT16, DType.INT32, DType.INT48, DType.FLOAT]
wrong_dtypes = list(set(all_dtypes) - set([a.dtype]))
outputDType = rng.choice(wrong_dtypes)
else:
outputDType = a.dtype
return ser.addOutput(shape, outputDType)
@staticmethod
def argmaxOp(ser, rng, a, axis, error_name=None):
shape = a.shape.copy()
if error_name not in [ErrorIf.AxisSmallerZero, ErrorIf.AxisLargerRank]:
del shape[axis]
if error_name == ErrorIf.ArgmaxOutputRankMismatch:
remove = rng.choice([True, False])
if remove and len(shape) > 1:
del shape[0]
else:
shape.append(1)
elif error_name == ErrorIf.ArgmaxOutputShapeMismatch:
for i in range(len(shape)):
shape[i] = shape[i] + rng.integers(1, 10)
if error_name == ErrorIf.WrongOutputType:
all_dtypes = [DType.INT8, DType.INT16, DType.INT32, DType.INT48, DType.FLOAT]
wrong_dtypes = list(set(all_dtypes) - set([DType.INT32]))
outputDType = rng.choice(wrong_dtypes)
else:
outputDType = DType.INT32
return ser.addOutput(shape, outputDType)
@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
ofm_shape = [ifm.shape[0], h, w, filter.shape[0]]
if 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)
@staticmethod
def conv3dOp(ser, ifm, filter, strides, padding, dilations):
# IFM: NDHWC
# Filter: ODHWI
# OFM: NDHWC
d = (
ifm.shape[1]
- filter.shape[1]
- (filter.shape[1] - 1) * (dilations[0] - 1)
+ padding[0]
+ padding[1]
) // strides[0] + 1
h = (
ifm.shape[2]
- filter.shape[2]
- (filter.shape[2] - 1) * (dilations[1] - 1)
+ padding[2]
+ padding[3]
) // strides[1] + 1
w = (
ifm.shape[3]
- filter.shape[3]
- (filter.shape[3] - 1) * (dilations[2] - 1)
+ padding[4]
+ padding[5]
) // strides[2] + 1
ofm_shape = [ifm.shape[0], d, h, w, filter.shape[0]]
if 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)
@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
ofm_shape = [ifm.shape[0], h, w, filter.shape[2] * filter.shape[3]]
if 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)
@staticmethod
def pool2dOp(ser, rng, ifm, kernel, stride, pad, error_name=None):
# input: NHWC
if stride[0] <= 0 or stride[1] <= 0 or min(pad) < 0:
# If an incorrect stride is used set dimensions to 1, test is invalid anyway.
h = 1
w = 1
else:
h = (ifm.shape[1] + pad[0] + pad[1] + stride[0] - kernel[0]) // stride[0]
w = (ifm.shape[2] + pad[2] + pad[3] + stride[1] - kernel[1]) // stride[1]
if error_name == ErrorIf.PoolingOutputShapeMismatch:
choices = [1, 2, 3, 4, 5]
h = h + rng.choice(choices)
w = w + rng.choice(choices)
ofm_shape = [ifm.shape[0], h, w, ifm.shape[3]]
if error_name == ErrorIf.WrongOutputType:
all_dtypes = [DType.INT8, DType.INT16, DType.INT32, DType.INT48, DType.FLOAT]
wrong_dtypes = list(set(all_dtypes) - set([ifm.dtype]))
outputDType = rng.choice(wrong_dtypes)
else:
outputDType = ifm.dtype
return ser.addOutput(ofm_shape, outputDType)
@staticmethod
def fullyConnectedOp(ser, rng, input, filter, error_name=None):
# input: N, IC
# filter: OC, IC
# output: N, OC
output_shape = [input.shape[0], filter.shape[0]]
if error_name == ErrorIf.WrongOutputType:
if input.dtype == DType.INT8:
incorrect_types = (DType.INT4, DType.INT8, DType.INT16, DType.INT48, DType.FLOAT)
elif input.dtype == DType.INT16:
incorrect_types = (DType.INT4, DType.INT8, DType.INT16, DType.INT32, DType.FLOAT)
elif input.dtype == DType.FLOAT:
incorrect_types = (DType.INT4, DType.INT8, DType.INT16, DType.INT32, DType.INT48)
out_dtype = rng.choice(a=incorrect_types)
elif 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
elif error_name == ErrorIf.WrongInputType:
# Pick some potentially correct output dtype if input type is incorrect
out_dtype = DType.INT32
else:
raise Exception("Unsupported input dtype: {}".format(input.dtype))
return ser.addOutput(output_shape, out_dtype)
@staticmethod
def matmulOp(ser, rng, a, b, error_name=None):
# a: N, H, C
# b: N, C, W
# out: N, H, W
output_shape = [a.shape[0], a.shape[1], b.shape[2]]
if error_name == ErrorIf.WrongOutputType:
if a.dtype == DType.INT8:
incorrect_types = (DType.INT4, DType.INT8, DType.INT16, DType.INT48, DType.FLOAT)
elif a.dtype == DType.INT16:
incorrect_types = (DType.INT4, DType.INT8, DType.INT16, DType.INT32, DType.FLOAT)
elif a.dtype == DType.FLOAT:
incorrect_types = (DType.INT4, DType.INT8, DType.INT16, DType.INT32, DType.INT48)
out_dtype = rng.choice(a=incorrect_types)
elif a.dtype == DType.INT8:
out_dtype = DType.INT32
elif a.dtype == DType.INT16:
out_dtype = DType.INT48
elif a.dtype == DType.FLOAT:
out_dtype = DType.FLOAT
elif error_name == ErrorIf.WrongInputType:
# Pick some potentially correct output dtype if input type is incorrect
out_dtype = DType.INT32
else:
raise Exception("Unsupported input dtype for matmul: {}".format(a.dtype))
return ser.addOutput(output_shape, out_dtype)
@staticmethod
def concatOp(ser, axis, *a):
input1 = a[0]
remaining_inputs = a[1:]
output_shape = input1.shape.copy()
output_shape[axis] = input1.shape[axis]
for tensor in remaining_inputs:
output_shape[axis] += tensor.shape[axis]
return ser.addOutput(output_shape, input1.dtype)
@staticmethod
def padOp(ser, rng, a, padding, error_name=None):
output_shape = a.shape.copy()
for i in range(len(output_shape)):
output_shape[i] = padding[i][0] + padding[i][1] + output_shape[i]
# Fix negative output shape if error_if test causes it
if error_name == ErrorIf.PadSmallerZero and min(output_shape) < 1:
output_shape = [i if i >= 1 else 1 for i in output_shape]
if error_name == ErrorIf.WrongOutputType:
all_dtypes = [DType.INT8, DType.INT16, DType.INT32, DType.INT48, DType.FLOAT]
wrong_dtypes = list(set(all_dtypes) - set([a.dtype]))
outputDType = rng.choice(wrong_dtypes)
else:
outputDType = a.dtype
return ser.addOutput(output_shape, outputDType)
@staticmethod
def reshapeOp(ser, rng, a, shape, error_name=None):
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
if error_name == ErrorIf.TensorSizeInputOutputMismatch:
for i in range(len(output_shape)):
output_shape[i] = output_shape[i] + rng.integers(1, 10)
if error_name == ErrorIf.WrongOutputType:
all_dtypes = [DType.INT8, DType.INT16, DType.INT32, DType.INT48, DType.FLOAT]
wrong_dtypes = list(set(all_dtypes) - set([a.dtype]))
outputDType = rng.choice(wrong_dtypes)
else:
outputDType = a.dtype
return ser.addOutput(output_shape, outputDType)
@staticmethod
def sliceOp(ser, rng, a, start, size, error_name=None):
if error_name == ErrorIf.WrongOutputType:
all_dtypes = [DType.INT8, DType.INT16, DType.INT32, DType.INT48, DType.FLOAT]
wrong_dtypes = list(set(all_dtypes) - set([a.dtype]))
outputDType = rng.choice(wrong_dtypes)
else:
outputDType = a.dtype
if error_name == ErrorIf.SizeOutputShapeMismatch:
output_shape = size.copy()
for index in range(len(output_shape)):
if output_shape[index] <= 2:
output_shape[index] = output_shape[index] + rng.choice([1, 2])
else:
output_shape[index] = output_shape[index] + rng.choice([-2, -1, 1, 2])
else:
output_shape = size.copy()
return ser.addOutput(output_shape, outputDType)
@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)
@staticmethod
def transposeOp(ser, rng, a, perms, error_name=None):
output_shape = a.shape.copy()
assert len(perms) == len(output_shape)
if error_name == ErrorIf.IndexOutsideBounds:
for i in range(len(output_shape)):
output_shape[i] = a.shape[0]
else:
for i in range(len(output_shape)):
output_shape[i] = a.shape[perms[i]]
if error_name == ErrorIf.WrongOutputType:
all_dtypes = [DType.INT8, DType.INT16, DType.INT32, DType.INT48, DType.FLOAT]
wrong_dtypes = list(set(all_dtypes) - set([a.dtype]))
outputDType = rng.choice(wrong_dtypes)
else:
outputDType = a.dtype
return ser.addOutput(output_shape, outputDType)
@staticmethod
def gatherOp(ser, values, indices):
assert len(values.shape) == 3
assert len(indices.shape) == 2
assert values.shape[0] == indices.shape[0]
output_shape = [values.shape[0], indices.shape[1], values.shape[2]]
return ser.addOutput(output_shape, values.dtype)
@staticmethod
def scatterOp(ser, values_in, indices, input):
assert len(values_in.shape) == 3
assert len(indices.shape) == 2
assert len(input.shape) == 3
assert values_in.shape[0] == indices.shape[0] # N
assert input.shape[1] == indices.shape[1] # W
assert values_in.shape[2] == input.shape[2] # C
output_shape = values_in.shape
return ser.addOutput(output_shape, values_in.dtype)
@staticmethod
def tableOp(ser, input):
# Same shape as the input, but dtype dependent on table dtype
assert input.dtype == DType.INT16 or input.dtype == DType.INT8
output_dtype = DType.INT32 if input.dtype == DType.INT16 else DType.INT8
return ser.addOutput(input.shape, output_dtype)
@staticmethod
def resizeOp(
serializer,
rng,
input,
mode,
stride,
offset,
shift,
stride_fp,
offset_fp,
output_dims,
input_dtype,
output_dtype,
error_name = None
):
if error_name == ErrorIf.WrongRank:
output_dims = [input.shape[0], output_dims[0], output_dims[0], input.shape[0]]
else:
if error_name == ErrorIf.BatchMismatch:
output_dims = [input.shape[0] + rng.integers(1, 10), output_dims[0], output_dims[1], input.shape[3]]
elif error_name == ErrorIf.ChannelMismatch:
output_dims = [input.shape[0], output_dims[0], output_dims[1], input.shape[3] + rng.integers(1, 10)]
else:
output_dims = [input.shape[0], output_dims[0], output_dims[1], input.shape[3]]
return serializer.addOutput(output_dims, output_dtype)
@staticmethod
def typeConversionOp(ser, val, out_dtype):
return ser.addOutput(val.shape, out_dtype)
@staticmethod
def transposeConv2DOp(ser, ifm, output_shape):
if 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(output_shape, out_dtype)