blob: 0e57a7bc79536dd533b96f8245130e4302451eef [file] [log] [blame]
Eric Kunzee5e26762020-10-13 16:11:07 -07001#!/usr/bin/env python3
2
3# Copyright (c) 2020, ARM Limited.
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17
18import numpy as np
19import argparse
20import sys
21import re
22import os
23import subprocess
24import shlex
25import json
26import glob
27import math
28import queue
29import threading
30import traceback
31import math
32
33from enum import IntEnum, Enum, unique
34
35import tosa_serializer as ts
36from tosa_serializer import *
37import tosa
38
39# Convenience variables to the flatc-generated types that should be enums, but aren't
40DType = tosa.DType.DType()
41Usage = tosa.Usage.Usage()
42Format = tosa.Format.Format()
43Op = tosa.Op.Op()
44ResizeMode = tosa.ResizeMode.ResizeMode()
45
46class TosaQuantGen:
47 '''QuantizedInfo random generator helper functions. Specify with 'qgen': in the operator defintion'''
48 def __init__(self):
49 pass
50
51 @staticmethod
52 def needsQinfo(op, dtype):
53 if dtype == DType.AINT8 or dtype == DType.INT8:
54 return True
55 return False
56
57 @staticmethod
58 def qgUnary(testGen, op, dtype):
59 qinfo = ts.TosaSerializerQuantInfo()
60 if TosaQuantGen.needsQinfo(op, dtype):
61 qinfo.UnaryQuantInfo(testGen.randInt(), testGen.randInt())
62 else:
63 qinfo.UnaryQuantInfo(0, 0)
64 return qinfo
65
66 @staticmethod
67 def qgConv(testGen, op, dtype):
68 qinfo = ts.TosaSerializerQuantInfo()
69 if TosaQuantGen.needsQinfo(op, dtype):
70 qinfo.ConvQuantInfo(testGen.randInt(), testGen.randInt())
71 else:
72 qinfo.ConvQuantInfo(0, 0)
73 return qinfo
74
75 @staticmethod
76 def qgMatmul(testGen, op, dtype):
77 qinfo = ts.TosaSerializerQuantInfo()
78 if TosaQuantGen.needsQinfo(op, dtype):
79 qinfo.MatMulQuantInfo(testGen.randInt(), testGen.randInt())
80 else:
81 qinfo.MatMulQuantInfo(0, 0)
82 return qinfo
83
84 @staticmethod
85 def qgPad(testGen, op, dtype):
86 qinfo = ts.TosaSerializerQuantInfo()
87 if TosaQuantGen.needsQinfo(op, dtype):
88 qinfo.PadQuantInfo(testGen.randInt())
89 else:
90 qinfo.PadQuantInfo(0)
91 return qinfo
92
93 @staticmethod
94 def computeMultiplierAndShift(scaleFp, scale32):
95 # Derived from computeMultiplierAndShiftTosaScale32
96 # Provide a floating-point scaling factor and the scale32 parameter
97 # to compute the multiplier and shift
98
99 if scale32:
100 scaleBits = 31
101 else:
102 scaleBits = 15
103
104 m, shift = math.frexp(scaleFp)
105
106 if scaleFp < 0.0:
107 m = -m
108
109 multiplier = round(m * (1 << scaleBits))
110 assert(multiplier <= (1 << scaleBits))
111
112 if multiplier == (1 << scaleBits):
113 multiplier = multiplier // 2
114 shift = shift + 1
115
116 shift = (-shift) + scaleBits
117 #print('scalefp {} scaleBits {} m {} mult {} shift {}'.format(scaleFp, scaleBits, m, multiplier, shift))
118
119 assert(multiplier <= (1 << scaleBits))
120 assert(shift >= 0 and shift <= 63)
121
122 return multiplier, shift
123
124
125class TosaTensorGen():
126 ''' Tensor generators create a shape list for the placeholder and const tensor
127 data operands for the operator. The actual random data is generated separately for each test.'''
128 def __init__(self):
129 pass
130
131 @staticmethod
132 def tgBasic(testGen, opName, rank):
133 pl, const = opName['operands']
134 shape = testGen.makeShape(rank)
135
136 shape_list = []
137 for i in range(pl + const):
138 shape_list.append(shape.copy())
139
140 return shape_list
141
142 @staticmethod
143 def tgNHWC(testGen, opName, rank):
144 pl, const = opName['operands']
145
146 assert(rank == 4)
147
148 shape = testGen.makeShape(rank)
149
150 # Constrict the batch size?
151 if testGen.args.max_batch_size:
152 shape[0] = (shape[0] % testGen.args.max_batch_size) + 1
153
154 shape_list = []
155 for i in range(pl + const):
156 shape_list.append(shape.copy())
157
158 return shape_list
159
160 @staticmethod
Kevin Cheng77d0f762020-11-24 10:26:32 -0800161 def tgScatter(testGen, opName, rank):
162 pl, const = opName['operands']
163
164 assert(pl == 2)
165 assert(const == 0)
166 assert(rank == 3)
167
168 values_in_shape = testGen.makeShape(rank)
169
170 # Constrict the batch size?
171 if testGen.args.max_batch_size:
172 values_in_shape[0] = (values_in_shape[0] % testGen.args.max_batch_size) + 1
173
174 W = testGen.randInt(testGen.args.tensor_shape_range[0], testGen.args.tensor_shape_range[1])
175 input_shape = [values_in_shape[0], W, values_in_shape[2]]
176
177 shape_list = []
178 shape_list.append(values_in_shape.copy())
179 shape_list.append(input_shape.copy())
180
181 return shape_list
182
183 @staticmethod
Eric Kunzee5e26762020-10-13 16:11:07 -0700184 def tgBroadcastFuzz(testGen, op, rank):
185 shape = testGen.makeShape(rank)
186
187 pl, const = op['operands']
188
189 shape_list = []
190
191 # Choose one of the inputs to broadcast
192 bcast_idx = testGen.randInt(0, pl + const)
193 for i in range(pl + const):
194 shape_bcast = shape.copy()
195
196 # If the chosen input, pick a random index to broadcast
197 if i == bcast_idx:
198 fuzz_idx = testGen.randInt(0, rank)
199 shape_bcast[fuzz_idx] = 1
200
201 shape_list.append(shape_bcast)
202
203 return shape_list
204
205 @staticmethod
206 def tgConv2D(testGen, op, rank):
207 pl, const = op['operands']
208
209 assert(rank == 4)
210
211 # IFM dimensions are NHWC
212 ifm_shape = testGen.makeShape(rank)
213
214 # Constrict the batch size?
215 if testGen.args.max_batch_size:
216 ifm_shape[0] = (ifm_shape[0] % testGen.args.max_batch_size) + 1
217
218 # Get the filter height/width from the operator parameters
219 filter_hw = op['filter']
220
221 # Generate a random OFM depth
222 ofm_depth = testGen.makeShape(1)[0]
223
224 # The filter dimensions are OHWI
225 filter_shape = np.asarray([ofm_depth, filter_hw[0], filter_hw[1], ifm_shape[3]])
226
227 # The bias is OC
228 bias_shape = np.asarray([ofm_depth])
229
230 return [ifm_shape, filter_shape, bias_shape]
231
232 @staticmethod
233 def tgTransposeConv2D(testGen, op, rank):
234 pl, const = op['operands']
235
236 assert(rank == 4)
237
238 # IFM dimensions are NHWC
239 ifm_shape = testGen.makeShape(rank)
240
241 # Constrict the batch size?
242 if testGen.args.max_batch_size:
243 ifm_shape[0] = (ifm_shape[0] % testGen.args.max_batch_size) + 1
244
245 # Get the filter height/width from the operator parameters
246 filter_hw = op['filter']
247
248 # Generate a random OFM depth
249 ofm_depth = testGen.makeShape(1)[0]
250
251 # The filter dimensions are OHWI
252 filter_shape = np.asarray([ofm_depth, filter_hw[0], filter_hw[1], ifm_shape[3]])
253
254 return [ifm_shape, filter_shape]
255
256 @staticmethod
257 def tgDepthwiseConv2D(testGen, op, rank):
258 pl, const = op['operands']
259
260 assert(rank == 4)
261 assert(pl == 1 and const == 2)
262
263 # IFM dimensions are NHWC
264 ifm_shape = testGen.makeShape(rank)
265
266 # Constrict the batch size?
267 if testGen.args.max_batch_size:
268 ifm_shape[0] = (ifm_shape[0] % testGen.args.max_batch_size) + 1
269
270 # Get the filter height/width from the operator parameters
271 # Filter is KH, HW, C, M
272 filter_hw = op['filter']
273
274 # Generate a random OFM depth, but don't let it get too big because
275 # the output depth is M * C
276 filter_m = (testGen.makeShape(1)[0] % (testGen.args.tensor_shape_range[1] // 4)) + 1
277
278 # The filter dimensions are HWCM
279 filter_shape = np.asarray([filter_hw[0], filter_hw[1], ifm_shape[3], filter_m])
280
281 # The bias is M * C
282 bias_shape = np.asarray([ifm_shape[3] * filter_m])
283
284 return [ifm_shape, filter_shape, bias_shape]
285
286 @staticmethod
287 def tgFullyConnected(testGen, op, rank):
288 pl, const = op['operands']
289
290 assert(rank == 2)
291 assert(pl == 2 and const == 0)
292
293 input_shape = testGen.makeShape(rank)
294 filter_oc = testGen.makeShape(1)[0]
295 filter_shape = np.asarray([filter_oc, input_shape[1]])
296
297 bias_shape = np.asarray([filter_oc])
298
299 return [input_shape, filter_shape, bias_shape]
300
301 @staticmethod
302 def tgMatmul(testGen, op, rank):
303 pl, const = op['operands']
304
305 assert(rank == 2)
306 assert(pl == 2 and const == 0)
307
308 a_shape = testGen.makeShape(rank)
309 b_oc = testGen.makeShape(1)[0]
310 b_shape = np.asarray([a_shape[1], b_oc])
311
312 return [a_shape, b_shape]
313
314class TosaArgGen:
315 '''Argument generators create exhaustive or random lists of attributes for operators that take
316 attributes or other parameters. The return value is a list of (descriptive_name, [arglist])
317 tuples where the descriptive_name is appended to the test name and the arglist is expanded
318 as arguments to the operator build function.'''
319 def __init__(self):
320 pass
321
322 @staticmethod
323 def agNone(testGen, opName, shapeList, dtype):
324 '''A trivial argument generator for operators that don't take any
325 non-tensor arguments'''
326 return [('', [])]
327
328 @staticmethod
329 def agAxis(testGen, opName, shapeList, dtype):
330 '''Build the axis argument for operators that take a single axis'''
331 axes = []
332
333 shape = shapeList[0]
334
335 for a in range(0, len(shape)):
336 axes.append(('axis_{}'.format(a), [a]))
337 return axes
338
339 @staticmethod
340 def agConv2D(testGen, opName, shapeList, dtype):
341 arg_list = []
342
343 ifm_shape = shapeList[0]
344 filter_shape = shapeList[1]
345
346 # Must be rank 4
347 assert(len(ifm_shape) == 4)
348 assert(len(filter_shape) == 4)
349
350 maxStride = testGen.args.max_conv_stride
351 maxPadding = testGen.args.max_conv_padding + 1
352 maxDilation = testGen.args.max_conv_dilation
353
354 # Strides, padding, dilations
355 for stride in range(0, maxStride ** 2):
356 for padding in range(0, (maxPadding) ** 4):
357 for dilation in range(0, maxDilation ** 2):
358
359 s = [stride // maxStride + 1,
360 stride % maxStride + 1]
361 p = [(padding // (maxPadding * 4)) % maxPadding,
362 (padding // (maxPadding * 2)) % maxPadding,
363 (padding // (maxPadding * 1)) % maxPadding,
364 padding % maxPadding]
365 d = [ dilation // maxDilation + 1,
366 dilation % maxDilation + 1]
367
368 # 4 padding parameters for regular conv2d
369 arg_list.append(('st{}{}_pad{}{}{}{}_dilat{}{}'.format(s[0], s[1],
370 p[0], p[1], p[2], p[3],
371 d[0], d[1]),
372 [ s, p, d ]))
373 return arg_list
374
375 @staticmethod
376 def agTransposeConv2D(testGen, opName, shapeList, dtype):
377 arg_list = []
378
379 ifm_shape = shapeList[0]
380 filter_shape = shapeList[1]
381
382 # Must be rank 4
383 assert(len(ifm_shape) == 4)
384 assert(len(filter_shape) == 4)
385
386 maxStride = testGen.args.max_conv_stride
387 maxPadding = testGen.args.max_conv_padding + 1
388 maxDilation = testGen.args.max_conv_dilation
389
390 # Strides, padding, dilations
391 for stride in range(0, maxStride ** 2):
392 for out_padding in range(0, (maxPadding) ** 2):
393 for dilation in range(0, maxDilation ** 2):
394
395 s = [stride // maxStride + 1,
396 stride % maxStride + 1]
397 p = [(out_padding // (maxPadding * 1)) % maxPadding,
398 out_padding % maxPadding]
399 d = [ dilation // maxDilation + 1,
400 dilation % maxDilation + 1]
401
402 oh = (ifm_shape[1] - filter_shape[1] - (filter_shape[1] - 1) * (d[0] - 1) + \
403 2 * p[0]) // s[0] + 1
404
405 ow = (ifm_shape[2] - filter_shape[2] - (filter_shape[2] - 1) * (d[1] - 1) + \
406 2 * p[1]) // s[1] + 1
407
408 # Output shape
409 os = [ ifm_shape[0], oh, ow, filter_shape[0] ]
410
411 arg_list.append(('st{}{}_outpad{}{}_dilat{}{}_os{}x{}x{}x{}'.format(s[0], s[1],
412 p[0], p[1],
413 d[0], d[1],
414 os[0], os[1], os[2], os[3]),
415 [ s, p, d, os ]))
416
417 return arg_list
418
419 @staticmethod
420 def agPad(testGen, opName, shapeList, dtype):
421 arg_list = []
422 rank = len(shapeList[0])
423
424 # Exhaustively test combinations of 0/1 padding on each side of each dimension
425 # This process might need some revision for >1 padding, but use rank**2 as a bitmask
426 # for now
427 for v in range(rank ** 2):
428
429 # Create a flat arraypadding4D
430 paddings = np.zeros((rank * 2), dtype=np.int32)
431
432 # Fill in the 1's
433 for r in (range(rank * 2)):
434 if (v >> r) & 1:
435 paddings[r] = 1
436
437 # Reshape back to a 2D array
438 paddings = paddings.reshape((rank, 2))
439
440 arg_list.append(('pad{0:b}'.format(v), [ paddings ]))
441
442 return arg_list
443
444 @staticmethod
445 def agPooling(testGen, opName, shapeList, dtype):
446 arg_list = []
447
448 shape = shapeList[0]
449 assert(len(shape) == 4)
450
451 maxStride = testGen.args.max_pooling_stride
452 maxKernel = testGen.args.max_pooling_kernel
453 maxPadding = testGen.args.max_pooling_padding + 1
454
455 for kernel in range(0, maxKernel ** 2):
456 for stride in range(0, maxStride ** 2):
457 for padding in range(0, maxPadding ** 4):
458 s = [stride // maxStride + 1,
459 stride % maxStride + 1]
460 k = [(kernel // maxKernel) + 2,
461 (kernel % maxKernel) + 2]
462 p = [(padding // (maxPadding * 4)) % maxPadding,
463 (padding // (maxPadding * 2)) % maxPadding,
464 (padding // (maxPadding * 1)) % maxPadding,
465 padding % maxPadding]
466
467 arg_list.append(('st{}{}_kern{}{}_pad{}{}{}{}'.format(s[0], s[1],
468 k[0], k[1],
469 p[0], p[1], p[2], p[3]),
470 [k, s, p]))
471 return arg_list
472
473 @staticmethod
474 def agCast(testGen, opName, shapeList, inDtype):
475 arg_list = []
476
477 # Enumerate the output types here
478 if inDtype == DType.INT8:
479 dtypeList = [ DType.BOOL, DType.INT16, DType.INT32, DType.FLOAT ]
480 elif inDtype == DType.INT16:
481 dtypeList = [ DType.BOOL, DType.INT8, DType.INT32, DType.FLOAT ]
482 elif inDtype == DType.INT32:
483 dtypeList = [ DType.BOOL, DType.INT8, DType.INT16, DType.FLOAT ]
484 elif inDtype == DType.BOOL:
485 dtypeList = [ DType.INT8, DType.INT16, DType.INT32 ]
486 elif inDtype == DType.FLOAT:
487 dtypeList = [ DType.INT8, DType.INT16, DType.INT32 ]
488 else:
489 raise Exception('Unexpected input dtype: {}'.format(inDtype))
490
491 for dtype in dtypeList:
492 arg_list.append(('out{}'.format(DTypeNames[dtype]), [dtype]))
493
494 return arg_list
495
496 @staticmethod
497 def agRescale(testGen, opName, shapeList, inDtype):
498 arg_list = []
499
500 # Enumerate the output types here
501 for dtype in [ DType.AINT8, DType.INT16, DType.INT32 ]:
502 for scale32 in [ False, True ]:
503 for double_round in [ False, True ]:
504 for per_channel in [ False, True ]:
505
506 if inDtype == DType.INT48 and scale32:
507 # Illegal condition. Must be scale32=False
508 continue
509
510 arg_list.append(('out{}_sc{}_dr{}_pc{}'.format(DTypeNames[dtype], int(scale32), int(double_round), int(per_channel)),
511 [dtype, scale32, double_round, per_channel]))
512
513 return arg_list
514
Kevin Chengaee1fac2020-11-11 13:54:06 -0800515 @staticmethod
516 def agMul(testGen, opName, shapeList, dtype):
517 arg_list = []
518
519 if dtype is DType.INT32:
520 for p in range(testGen.args.num_rand_permutations):
521
522 shift = testGen.randInt(0, 32)
523
524 arg_list.append(('perm{}_shift{}'.format(p, shift), [shift]))
525 else:
526 arg_list.append(('shift0', [0]))
527
528 return arg_list
529
530 @staticmethod
531 def agArithmeticRightShift(testGen, opName, shapeList, dtype):
532 arg_list = []
533
534 arg_list.append(('roundTrue', [True]))
535 arg_list.append(('roundFalse', [False]))
536
537 return arg_list
538
Eric Kunzee5e26762020-10-13 16:11:07 -0700539 # Helper function for reshape. Gets some factors of a larger number.
540 @staticmethod
541 def getFactors(val, start=1):
542 factors = []
543
544 for i in range(start, int(np.sqrt(val))):
545 if (val % i) == 0:
546 factors.append(i)
547
548 return factors
549
550 @staticmethod
551 def agReshape(testGen, opName, shapeList, dtype):
552 arg_list = []
553
554 origShape = shapeList[0]
555
556 totalElements = 1
557 for s in origShape:
558 totalElements *= s
559
560 # This code is NOT fast. Fortunately, the numbers are fairly small.
561 factors = TosaArgGen.getFactors(totalElements)
562
563 for p in range(testGen.args.num_rand_permutations):
564 newRank = testGen.randInt(1, 6)
565 newShape = []
566 if (len(factors) < newRank):
567 continue
568
569 remainingElements = totalElements
570 shuffledFactors = testGen.rng.permutation(factors)
571 for i in range(newRank):
572 # pick rank-1 factors
573 newShape.append(shuffledFactors[0])
574 remainingElements = remainingElements // shuffledFactors[0]
575 shuffledFactors = testGen.rng.permutation(TosaArgGen.getFactors(remainingElements))
576 newShape.append(remainingElements)
577
578 # Toss in a -1 sometimes
579 minusOne = testGen.randInt(0, newRank * 4)
580 if minusOne < newRank:
581 newShape[minusOne] = -1
582
583 arg_list.append(('perm{}_rank{}'.format(p, newRank), [newShape]))
584
585 return arg_list
586
587
588 @staticmethod
589 def agTranspose(testGen, opName, shapeList, dtype):
590 arg_list = []
591
592 ifm_shape = shapeList[0]
593
594 perms = range(len(ifm_shape))
595 for p in range(testGen.args.num_rand_permutations):
596 perms = np.int32(testGen.rng.permutation(perms)).tolist()
597
598 # Avoid duplicates
599 found = False
600 for name, other_perm in arg_list:
601 if other_perm[0] == perms:
602 found = True
603 break
604
605 if not found:
606 arg_list.append(('perm{}'.format(p), [perms]))
607
608 return arg_list
609
610 @staticmethod
611 def agSlice(testGen, opName, shapeList, dtype):
612 arg_list = []
613
614 ifm_shape = shapeList[0]
615 rank = len(ifm_shape)
616
617 for p in range(testGen.args.num_rand_permutations):
618 begin = []
619 size = []
620
621 valid=True
622
623 for i in range(rank):
624 if ifm_shape[i] > 1:
625 begin.append(testGen.randInt(0, ifm_shape[i]))
626 size.append(testGen.randInt(0, ifm_shape[i] - begin[i]))
627
628 # Invalid slice size?
629 if size[i] == 0:
630 valid = False
631 else:
632 begin.append(0)
633 size.append(1)
634
635 if valid:
636 arg_list.append(('perm{}'.format(p), [begin, size]))
637 return arg_list
638
639 @staticmethod
640 def agTile(testGen, opName, shapeList, dtype):
641 arg_list = []
642
643 ifm_shape = shapeList[0]
644 rank = len(ifm_shape)
645
646 for p in range(testGen.args.num_rand_permutations):
647
648 # Pick a few random, but small multiple values
649 # because otherwise this has a tendency to generate
650 # enormous tensors
651 multiples = []
652 for i in range(rank):
653 multiples.append(testGen.randInt(1, 4))
654
655 arg_list.append(('perm{}'.format(p), [multiples]))
656
657 return arg_list
658
659 @staticmethod
660 def agResize(testGen, opName, shapeList, dtype):
661 arg_list = []
662
663 ifm_shape = shapeList[0]
664
665 for m in [ResizeMode.NEAREST, ResizeMode.BILINEAR]:
666
667 # Exclude illegal {mode, type} configurations. Pick legal output types
668 if m == ResizeMode.NEAREST and dtype == DType.INT8:
669 outputDTypeList = [ DType.INT32 ]
670 elif m == ResizeMode.NEAREST and dtype == DType.INT16:
671 outputDTypeList = [ DType.INT16 ]
672 elif m == ResizeMode.BILINEAR and dtype == DType.INT8:
673 outputDTypeList = [ DType.INT8 ]
674 elif m == ResizeMode.BILINEAR and dtype == DType.INT16:
675 outputDTypeList = [ DType.INT48 ]
Kevin Cheng77d0f762020-11-24 10:26:32 -0800676 elif dtype == DType.FLOAT:
677 outputDTypeList = [ DType.FLOAT ]
Eric Kunzee5e26762020-10-13 16:11:07 -0700678 else:
679 continue
680
681 for outputDType in outputDTypeList:
682 for perm in range(testGen.args.num_rand_permutations):
683
684 # Randomly generate legal output dimensions and shift
685 # and then compute the stride and offset based on them
686 output_dims = [ testGen.randInt(), testGen.randInt() ]
Kevin Cheng77d0f762020-11-24 10:26:32 -0800687 in_center_h = (ifm_shape[1] - 1) / 2.0
688 in_center_w = (ifm_shape[2] - 1) / 2.0
689 out_center_h = (output_dims[0] - 1) / 2.0
690 out_center_w = (output_dims[1] - 1) / 2.0
Eric Kunzee5e26762020-10-13 16:11:07 -0700691
Kevin Cheng77d0f762020-11-24 10:26:32 -0800692 fp_stride_y = float(ifm_shape[1]) / float(output_dims[0])
693 fp_stride_x = float(ifm_shape[2]) / float(output_dims[1])
694 fp_offset_y = in_center_h - fp_stride_y * out_center_h
695 fp_offset_x = in_center_w - fp_stride_x * out_center_w
Eric Kunzee5e26762020-10-13 16:11:07 -0700696
Kevin Cheng77d0f762020-11-24 10:26:32 -0800697 if outputDType == DType.FLOAT:
698 shift = 0
699 stride = [0, 0]
700 offset = [0, 0]
701 stride_fp = [ fp_stride_y, fp_stride_x]
702 offset_fp = [ fp_offset_y, fp_offset_x]
703 arg_list.append(('mode{}_odim{}x{}_out{}_st{:.2f}x{:.2f}_off{:.2f}x{:.2f}'.format(m, output_dims[0], output_dims[1],
704 testGen.typeStr(outputDType), stride_fp[0], stride_fp[1],
705 offset_fp[0], offset_fp[1]),
706 [m, stride, offset, shift, stride_fp, offset_fp, output_dims, dtype, outputDType]))
707 else:
708 shift = 11
709 unit = float(1 << shift)
710 stride_y = int(round(fp_stride_y * unit))
711 stride_x = int(round(fp_stride_x * unit))
712 offset_y = int(round(fp_offset_y * unit))
713 offset_x = int(round(fp_offset_x * unit))
Eric Kunzee5e26762020-10-13 16:11:07 -0700714
Kevin Cheng77d0f762020-11-24 10:26:32 -0800715 while (stride_y >= 32768 or stride_x >= 32768 or offset_y >= 32768 or offset_x >= 32768 or offset_y < -32768 or offset_x < -32768):
716 shift = shift - 1
717 unit = float(1 << shift)
718 stride_y = int(round(fp_stride_y * unit))
719 stride_x = int(round(fp_stride_x * unit))
720 offset_y = int(round(fp_offset_y * unit))
721 offset_x = int(round(fp_offset_x * unit))
Eric Kunzee5e26762020-10-13 16:11:07 -0700722
Kevin Cheng77d0f762020-11-24 10:26:32 -0800723 stride = [ stride_y, stride_x]
724 offset = [ offset_y, offset_x]
725
726 stride_fp = [0.0, 0.0]
727 offset_fp = [0.0, 0.0]
728
729 arg_list.append(('mode{}_shift{}_odim{}x{}_out{}_st{}x{}_off{}x{}'.format(m, shift, output_dims[0], output_dims[1],
730 testGen.typeStr(outputDType), stride[0], stride[1],
731 offset[0], offset[1]),
732 [m, stride, offset, shift, stride_fp, offset_fp, output_dims, dtype, outputDType]))
Eric Kunzee5e26762020-10-13 16:11:07 -0700733
734 return arg_list
735
736 def agCondIf(testGen, opName, shapeList, dtype):
737 # CondIf generates the condition values here.
738 # Convert to tensors in the build function, along with the
739 # then and else blocks
740 arg_list = []
741
742 for c in [False, True]:
743 arg_list.append(('cond{}'.format(int(c)), [ c ]))
744
745 return arg_list
746
747 def agWhileLoop(testGen, opName, shapeList, dtype):
748 # While loop: 0 iterations, 1, more than 1
749 arg_list = []
750
751 for iter in [0, 1, 4]:
752 arg_list.append(('iter{}'.format(iter), [ iter ]))
753
754 return arg_list
755
756class TosaTestGen:
757 def __init__(self, args):
758 self.args = args
759 self.basePath = args.output_dir
760 self.random_seed = args.random_seed
761 self.ser = None
762 self.rng = np.random.default_rng(self.random_seed)
763 self.createDynamicOpLists()
764 self.initOpListDefaults()
765 self.quantGen = TosaQuantGen()
766 # Force makeShape to do a specific starting shape
767 self.targetted_shape = None
768
769 def createSerializer(self, opName, testPath):
770 self.testPath = os.path.join(opName, testPath)
771
772 fullPath = os.path.join(self.basePath, self.testPath)
773 os.makedirs(fullPath, exist_ok=True)
774 self.ser = ts.TosaSerializer(fullPath)
775
776 def getSerializer(self):
777 return self.ser
778
779 def serialize(self, testName):
780 with open(os.path.join(self.basePath, self.testPath, '{}.tosa'.format(testName)), 'wb') as fd:
781 fd.write(self.ser.serialize())
782
783 with open(os.path.join(self.basePath, self.testPath, 'desc.json'), 'w') as fd:
784 fd.write(self.ser.writeJson('{}.tosa'.format(testName)))
785
786 def getRandTensor(self, shape, dtype):
787 RAND_SHIFT_FACTOR = 0.5
788 RAND_SCALE_FACTOR = 4.0
789
790 if dtype == DType.BOOL:
791 np_dt = np.bool
792 return np.bool_(self.rng.choice(a=[False, True], size=shape))
793 elif dtype == DType.AINT8:
794 return np.int32(self.rng.integers(low=0, high=256, size=shape))
795 elif dtype == DType.INT4:
796 return np.int32(self.rng.integers(low=-7, high=8, size=shape))
797 elif dtype == DType.INT8:
798 return np.int32(self.rng.integers(low=-127, high=128, size=shape))
799 elif dtype == DType.INT16:
800 return np.int32(self.rng.integers(low=-32768, high=32768, size=shape))
801 elif dtype == DType.INT32:
802 return np.int32(self.rng.integers(low=-(1 << 31), high=(1 << 31), size=shape))
803 elif dtype == DType.INT48:
804 return np.int64(self.rng.integers(low=-(1 << 47), high=(1 << 47), size=shape))
805 elif dtype == DType.FLOAT:
806 return np.float32(self.rng.random(size=shape) - RAND_SHIFT_FACTOR * RAND_SCALE_FACTOR)
807 else:
808 raise Exception('Unrecognized Dtype: {}'.format(dtype))
809
810 def buildPlaceholderTensors(self, shape_list, dtype):
811 placeholders = []
812
813 for shape in shape_list:
814 arr = self.getRandTensor(shape, dtype)
815 placeholders.append(self.ser.addPlaceholder(shape, dtype, Usage.ACTIVATION, [], arr))
816
817 return placeholders
818
819 def buildConstTensors(self, shape_list, dtype):
820 consts = []
821
822 for shape in shape_list:
823 arr = self.getRandTensor(shape, dtype)
824 consts.append(self.ser.addConst(shape, dtype, Usage.ACTIVATION, [], arr))
825
826 return consts
827
828 def makeShape(self, rank):
829 if self.targetted_shape:
830 return np.int32(self.targetted_shape)
831 return np.int32(self.rng.integers(low=self.args.tensor_shape_range[0],
832 high=self.args.tensor_shape_range[1],
833 size=rank))
834
835 def setTargetShape(self, shape):
836 self.targetted_shape = shape
837
838 def randInt(self, low=0, high=256):
839 return np.int32(self.rng.integers(low=low, high=high, size=1))[0]
840
841 def getRandNumberDType(self, dtype):
842 if dtype == DType.FLOAT:
843 return self.rng.random()
844 elif dtype == DType.BOOL:
845 return self.rng.choice([False, True])
846 elif dtype == DType.INT4:
847 low, high = (-7, 8)
848 elif dtype == DType.AINT8:
849 low, high = (0, 256)
850 elif dtype == DType.INT8:
851 low, high = (-127, 128)
852 elif dtype == DType.INT16:
853 low, high = (-32768, 32768)
854 elif dtype == DType.INT32:
855 low, high = (-(1<<31), (1<<31))
856 elif dtype == DType.INT48:
857 low, high = (-(1<<47), (1<<47))
858 # Special size
859 return np.int64(self.rng.integers(low, high, size=1))[0]
860 else:
861 raise Exception('Unknown dtype: {}'.format(dtype))
862
863 return np.int32(self.rng.integers(low, high, size=1))[0]
864
865 def shapeStr(self, shape):
866
867 sStr = []
868 # Convert to strings
869 for i in shape:
870 sStr.append(str(i))
871
872 return 'x'.join(sStr)
873
874 def typeStr(self, t):
875 if t == DType.BOOL:
876 return 'b'
877 elif t == DType.AINT8:
878 return 'a8'
879 elif t == DType.INT4:
880 return 'i4'
881 elif t == DType.INT8:
882 return 'i8'
883 elif t == DType.INT16:
884 return 'i16'
885 elif t == DType.INT32:
886 return 'i32'
887 elif t == DType.INT48:
888 return 'i48'
889 elif t == DType.FLOAT:
890 return 'float'
891 else:
892 raise Exception('Unknown dtype, cannot convert to string: {}'.format(t))
893
894 def typeWidth(self, t):
895 ''' Get the datatype width for integer types'''
896 if t == DType.AINT8:
897 return 8
898 elif t == DType.UINT8:
899 return 8
900 elif t == DType.INT4:
901 return 4
902 elif t == DType.INT8:
903 return 8
904 elif t == DType.INT16:
905 return 16
906 elif t == DType.INT32:
907 return 32
908 elif t == DType.INT48:
909 return 48
910 else:
911 raise Exception('Unknown dtype, cannot convert to string: {}'.format(t))
912
913 # Argument generators
914 # Returns a list of tuples (stringDescriptor, [build_fcn_arg_list])
915 # Where the string descriptor is used to generate the test name and
916 # The build_fcn_arg_list is expanded and passed to the operator test
917 # build function
918
919
920 def build_unary(self, op, a, qinfo = None):
921 result_tens = OutputShaper.unaryOp(self.ser, a)
922 self.ser.addOperator(op, [a.name], [result_tens.name], None, qinfo)
923 return result_tens
924
925 def build_binary_broadcast(self, op, a, b):
926 result_tens = OutputShaper.binaryBroadcastOp(self.ser, a, b)
927 self.ser.addOperator(op, [a.name, b.name], [result_tens.name])
928 return result_tens
929
930 def build_binary_nonbroadcast(self, op, a, b):
931 result_tens = OutputShaper.binaryNonBroadcastOp(self.ser, a, b)
932 self.ser.addOperator(op, [a.name, b.name], [result_tens.name])
933 return result_tens
934
Kevin Chengaee1fac2020-11-11 13:54:06 -0800935 def build_arithmetic_right_shift(self, op, a, b, round):
936 result_tens = OutputShaper.binaryBroadcastOp(self.ser, a, b)
937
938 attr = ts.TosaSerializerAttribute()
939 attr.ArithmeticRightShiftAttribute(round)
940
941 self.ser.addOperator(op, [a.name, b.name], [result_tens.name], attr)
942 return result_tens
943
944 def build_mul(self, op, a, b, shift):
Eric Kunzee5e26762020-10-13 16:11:07 -0700945 result_tens = OutputShaper.binaryBroadcastOp(self.ser, a, b)
946
947 # Special for multiply:
948 # Force the result to INT32 for INT types
949 if a.dtype != DType.FLOAT:
950 result_tens.setDtype(DType.INT32)
951
Kevin Chengaee1fac2020-11-11 13:54:06 -0800952 attr = ts.TosaSerializerAttribute()
953 attr.MulAttribute(shift)
954
955 self.ser.addOperator(op, [a.name, b.name], [result_tens.name], attr)
Eric Kunzee5e26762020-10-13 16:11:07 -0700956 return result_tens
957
958 def build_table(self, op, a):
959 # Constant size, random values
960 table_arr = self.getRandTensor([513], DType.INT16)
961 table_tens = self.ser.addConst(table_arr.shape, DType.INT16, Usage.INDEX, [], table_arr)
962
963 result_tens = OutputShaper.tableOp(self.ser, a, table_tens)
964 self.ser.addOperator(op, [a.name, table_tens.name], [result_tens.name], None)
965
966 return result_tens
967
968 def build_select(self, op, cond, a, b):
969
970 # Replace the cond tensor with a boolean tensor since it probably
971 # has the wrong dtype
972 t = self.buildPlaceholderTensors([cond.shape], DType.BOOL)
973 cond = t[0]
974
975 result_tens = OutputShaper.selectOp(self.ser, cond, a, b)
976 self.ser.addOperator(op, [cond.name, a.name, b.name], [result_tens.name])
977
978 return result_tens
979
980 def build_comparison(self, op, a, b):
981 result_tens = OutputShaper.binaryComparisonOp(self.ser, a, b)
982 self.ser.addOperator(op, [a.name, b.name], [result_tens.name])
983 return result_tens
984
985 def build_argmax(self, op, a, axis):
986 result_tens = OutputShaper.argmaxOp(self.ser, a, axis)
987
988 attr = ts.TosaSerializerAttribute()
989 attr.AxisAttribute(axis)
990
991 self.ser.addOperator(op, [a.name], [result_tens.name], attr)
992 return result_tens
993
994 def build_pool2d(self, op, input, kernel, stride, pad, qinfo = None):
995 result_tens = OutputShaper.pool2dOp(self.ser, input, kernel, stride, pad)
996
997 attr = ts.TosaSerializerAttribute()
998 attr.Pool2dAttribute(kernel, stride, pad)
999 input.addFormat(Format.NHWC)
1000
1001 self.ser.addOperator(op, [input.name], [result_tens.name], attr, qinfo)
1002 return result_tens
1003
1004 def build_conv2d(self, op, ifm, filter, bias, strides, padding, dilations, qinfo):
1005 assert(len(padding) == 4)
1006 result_tens = OutputShaper.conv2dOp(self.ser, ifm, filter, strides, padding, dilations)
1007
1008 attr = ts.TosaSerializerAttribute()
1009 attr.Conv2dAttribute(padding, strides, dilations)
1010
1011 ifm.addFormat(Format.NHWC)
1012 # Update the filter ordering
1013 filter.addUsage(Usage.WEIGHT)
1014 filter.addFormat(Format.OHWI)
1015
1016 self.ser.addOperator(op, [ifm.name, filter.name, bias.name], [result_tens.name], attr, qinfo)
1017 return result_tens
1018
1019 def build_transpose_conv2d(self, op, ifm, filter, stride, outpad, dilation, output_shape, qinfo):
1020 assert(len(outpad) == 2)
1021 result_tens = OutputShaper.transposeConv2DOp(self.ser, ifm, output_shape)
1022
1023 attr = ts.TosaSerializerAttribute()
1024 attr.TransposeConv2DAttribute(outpad, stride, dilation, output_shape)
1025
1026 ifm.addFormat(Format.NHWC)
1027 # Update the filter ordering
1028 filter.addUsage(Usage.WEIGHT)
1029 filter.addFormat(Format.OHWI)
1030
1031 # Create bias here since the acc_t depends on (but isn't the same as) the input dtype
1032 # The bias is OC
1033 if ifm.dtype == DType.AINT8 or ifm.dtype == DType.INT8:
1034 bias_type = DType.INT32
1035 elif ifm.dtype == DType.INT16:
1036 bias_type = DType.INT48
1037 elif ifm.dtype == DType.FLOAT:
1038 bias_type = DType.FLOAT
1039 else:
1040 raise Exception('Unsupported dtype for transpose_conv2d: {}'.format(ifm.dtype))
1041
1042 bias_arr = self.getRandTensor([filter.shape[0]], bias_type)
1043 bias_tens = self.ser.addConst([filter.shape[0]], bias_type, [], [], bias_arr)
1044
1045 self.ser.addOperator(op, [ifm.name, filter.name, bias_tens.name], [result_tens.name], attr, qinfo)
1046 return result_tens
1047
1048 def build_depthwise_conv2d(self, op, ifm, filter, bias, strides, padding, dilations, qinfo):
1049 result_tens = OutputShaper.depthwiseConv2dOp(self.ser, ifm, filter, strides, padding, dilations)
1050
1051 attr = ts.TosaSerializerAttribute()
1052 attr.Conv2dAttribute(padding, strides, dilations)
1053
1054 ifm.addFormat(Format.NHWC)
1055 filter.addUsage(Usage.WEIGHT)
1056 filter.addFormat(Format.HWIM)
1057
1058 self.ser.addOperator(op, [ifm.name, filter.name, bias.name], [result_tens.name], attr, qinfo)
1059 return result_tens
1060
1061 def build_fully_connected(self, op, ifm, filter, bias, qinfo):
1062 result_tens = OutputShaper.fullyConnectedOp(self.ser, ifm, filter)
1063
1064 filter.addUsage(Usage.WEIGHT)
1065 self.ser.addOperator(op, [ifm.name, filter.name, bias.name], [result_tens.name], None, qinfo)
1066 return result_tens
1067
1068 def build_matmul(self, op, a, b, qinfo):
1069 result_tens = OutputShaper.matmulOp(self.ser, a, b)
1070 self.ser.addOperator(op, [a.name, b.name], [result_tens.name], None, qinfo)
1071 return result_tens
1072
1073 def build_reduce(self, op, a, axis):
1074 result_tens = OutputShaper.reduceOp(self.ser, a, axis)
1075
1076 attr = ts.TosaSerializerAttribute()
1077 attr.AxisAttribute(axis)
1078
1079 self.ser.addOperator(op, [a.name], result_tens.name, attr)
1080 return result_tens
1081
1082 def build_clamp(self, op, a):
1083 result_tens = OutputShaper.unaryOp(self.ser, a)
1084
1085 attr = ts.TosaSerializerAttribute()
1086
1087 # Get two random ints
1088 v = [self.randInt(), self.randInt()]
1089
1090 if a.dtype == DType.FLOAT:
1091 attr.ClampAttribute(0, 0, min(v), max(v))
1092 else:
1093 attr.ClampAttribute(min(v), max(v), 0, 0)
1094
1095 self.ser.addOperator(op, [a.name], [result_tens.name], attr)
1096 return result_tens
1097
1098 def build_leaky_relu(self, op, a):
1099 result_tens = OutputShaper.unaryOp(self.ser, a)
1100 attr = ts.TosaSerializerAttribute()
1101
1102 attr.LeakyReluAttribute(self.getRandNumberDType(DType.FLOAT))
1103
1104 self.ser.addOperator(op, [a.name], [result_tens.name], attr)
1105 return result_tens
1106
1107 # Needs an additional type/input
1108 def build_prelu(self, op, a):
1109 result_tens = OutputShaper.unaryOp(self.ser, a)
1110
1111 self.ser.addOperator(op, [a.name], [result_tens.name])
1112 return result_tens
1113
1114 def build_relun(self, op, a):
1115 result_tens = OutputShaper.unaryOp(self.ser, a)
1116
1117 attr = ts.TosaSerializerAttribute()
1118
1119 if a.dtype == DType.FLOAT:
1120 attr.ReluNAttribute(0, self.getRandNumberDType(a.dtype))
1121 else:
1122 attr.ReluNAttribute(self.getRandNumberDType(a.dtype), 0)
1123
1124 self.ser.addOperator(op, [a.name], [result_tens.name], attr)
1125 return result_tens
1126
1127 def build_sigmoid(self, op, a):
1128 result_tens = OutputShaper.unaryOp(self.ser, a)
1129 self.ser.addOperator(op, [a.name], [result_tens.name])
1130 return result_tens
1131
1132 def build_tanh(self, op, a):
1133 result_tens = OutputShaper.unaryOp(self.ser, a)
1134 self.ser.addOperator(op, [a.name], [result_tens.name])
1135 return result_tens
1136
1137 def build_concat(self, op, a, b, axis):
1138 result_tens = OutputShaper.concatOp(self.ser, a, b, axis)
1139
1140 attr = ts.TosaSerializerAttribute()
1141 attr.AxisAttribute(axis)
1142
1143 self.ser.addOperator(op, [a.name, b.name], [result_tens.name], attr)
1144
1145 def build_pad(self, op, a, padding, qinfo):
1146 result_tens = OutputShaper.padOp(self.ser, a, padding)
1147
1148 # Need to turn the padding array into a TOSA tensor here.
1149 # This is one of the few tensor operands that does not get
1150 # randomly generated
1151 padding_tens = self.ser.addConst(padding.shape, DType.INT32, [], [], padding)
1152
1153 self.ser.addOperator(op, [a.name, padding_tens.name], [result_tens.name], None, qinfo)
1154
1155 def build_reshape(self, op, a, newShape):
1156 result_tens = OutputShaper.reshapeOp(self.ser, a, newShape)
1157
1158 attr = ts.TosaSerializerAttribute()
1159 attr.ReshapeAttribute(newShape)
1160
1161 self.ser.addOperator(op, [a.name], [result_tens.name], attr)
1162 return result_tens
1163
1164 def build_reverse(self, op, a, axis):
1165 result_tens = OutputShaper.unaryOp(self.ser, a)
1166
1167 attr = ts.TosaSerializerAttribute()
1168 attr.AxisAttribute(axis)
1169
1170 self.ser.addOperator(op, [a.name], [result_tens.name], attr)
1171 return result_tens
1172
1173 def build_transpose(self, op, a, perms):
1174 result_tens = OutputShaper.transposeOp(self.ser, a, perms)
1175
1176 perms_tens = self.ser.addConst([len(perms)], DType.INT32, Usage.ACTIVATION, [], np.int32(perms))
1177
1178 self.ser.addOperator(op, [a.name, perms_tens.name], [result_tens.name])
1179 return result_tens
1180
1181 def build_slice(self, op, a, begin, size):
1182 result_tens = OutputShaper.sliceOp(self.ser, a, begin, size)
1183
1184 attr = ts.TosaSerializerAttribute()
1185 attr.SliceAttribute(begin, size)
1186
1187 self.ser.addOperator(op, [a.name], [result_tens.name], attr)
1188 return result_tens
1189
1190 def build_tile(self, op, a, multiples):
1191 result_tens = OutputShaper.tileOp(self.ser, a, multiples)
1192
1193 attr = ts.TosaSerializerAttribute()
1194 attr.TileAttribute(multiples)
1195
1196 self.ser.addOperator(op, [a.name], [result_tens.name], attr)
1197 return result_tens
1198
1199
Kevin Cheng77d0f762020-11-24 10:26:32 -08001200 def build_gather(self, op, values):
Eric Kunzee5e26762020-10-13 16:11:07 -07001201
1202 # Create a new indicies tensor
1203 # here with data that doesn't exceed the dimensions of the values tensor
1204
Kevin Cheng77d0f762020-11-24 10:26:32 -08001205 K = values.shape[1] # K
1206 W = self.randInt(self.args.tensor_shape_range[0], self.args.tensor_shape_range[1]) # W
1207 indicies_arr = np.int32(self.rng.integers(low=0, high=K, size=[values.shape[0], W])) # (N, W)
Eric Kunzee5e26762020-10-13 16:11:07 -07001208 indicies = self.ser.addConst(indicies_arr.shape, DType.INT32, Usage.INDEX, [], indicies_arr)
1209
Kevin Cheng77d0f762020-11-24 10:26:32 -08001210 result_tens = OutputShaper.gatherOp(self.ser, values, indicies)
Eric Kunzee5e26762020-10-13 16:11:07 -07001211
Kevin Cheng77d0f762020-11-24 10:26:32 -08001212 self.ser.addOperator(op, [values.name, indicies.name], [result_tens.name])
Eric Kunzee5e26762020-10-13 16:11:07 -07001213
1214 return result_tens
1215
Kevin Cheng77d0f762020-11-24 10:26:32 -08001216 def build_scatter(self, op, values_in, input):
1217
1218 # Create a new indicies tensor
1219 # here with data that doesn't exceed the dimensions of the values_in tensor
1220
1221 K = values_in.shape[1] # K
1222 W = input.shape[1] # W
1223 indicies_arr = np.int32(self.rng.integers(low=0, high=K, size=[values_in.shape[0], W])) # (N, W)
1224 indicies = self.ser.addConst(indicies_arr.shape, DType.INT32, Usage.INDEX, [], indicies_arr)
1225
1226 result_tens = OutputShaper.scatterOp(self.ser, values_in, indicies, input)
1227
1228 self.ser.addOperator(op, [values_in.name, indicies.name, input.name], [result_tens.name])
1229
1230 return result_tens
1231
1232 def build_resize(self, op, input, mode, stride, offset, shift, stride_fp, offset_fp, output_dims, input_dtype, output_dtype):
1233 result_tens = OutputShaper.resizeOp(self.ser, input, mode, stride, offset, shift, stride_fp, offset_fp, output_dims, input_dtype, output_dtype)
Eric Kunzee5e26762020-10-13 16:11:07 -07001234
1235 attr = ts.TosaSerializerAttribute()
Kevin Cheng77d0f762020-11-24 10:26:32 -08001236
1237 attr.ResizeAttribute(output_dims, stride, offset, shift, stride_fp, offset_fp, mode)
Eric Kunzee5e26762020-10-13 16:11:07 -07001238
1239 self.ser.addOperator(op, [input.name], [result_tens.name], attr)
1240 return result_tens
1241
1242 def build_identityn(self, op, val, val2):
1243
1244 result_tens = OutputShaper.unaryOp(self.ser, val)
1245 result_tens2 = OutputShaper.unaryOp(self.ser, val2)
1246 self.ser.addOperator(op, [val.name, val2.name], [result_tens.name, result_tens2.name])
1247 return result_tens
1248
1249 def build_placeholder(self, op, val):
1250 # Add an identity op to avoid warning in the reference model
1251 return self.build_unary(Op.IDENTITY, val)
1252
1253 # Type Conversion
1254 def build_cast(self, op, val, out_dtype):
1255 result_tens = OutputShaper.typeConversionOp(self.ser, val, out_dtype)
1256 self.ser.addOperator(op, [val.name], [result_tens.name])
1257 return result_tens
1258
1259 def build_rescale(self, op, val, out_dtype, scale32, double_round, per_channel):
1260 result_tens = OutputShaper.typeConversionOp(self.ser, val, out_dtype)
1261
1262 if per_channel:
1263 nc = val.shape[-1]
1264 else:
1265 nc = 1
1266
1267 in_type_width = self.typeWidth(val.dtype)
1268 out_type_width = self.typeWidth(out_dtype)
1269
1270 if val.dtype == DType.AINT8:
1271 input_zp = self.randInt()
1272 in_type_width = in_type_width + 1
1273 else:
1274 input_zp = 0
1275
1276 if out_dtype == DType.AINT8:
1277 output_zp = self.randInt()
1278 out_type_width = out_type_width + 1
1279 else:
1280 output_zp = 0
1281
1282 # Calculate scale based on:
1283 # scale = a *(2^output_width)/(2^input_width))
1284
1285 a = np.float32(self.rng.random(size=[nc]))
1286 scale_arr = a * np.float32((1 << out_type_width) / (1 << in_type_width))
1287
1288 if scale32:
1289 pass
1290 # Cap the scaling at 2^15 - 1 for scale16
1291 scale_arr = np.clip(scale_arr, 1.0 / (1 << 31), (1 << 31) - 1)
1292 else:
1293 # Cap the scaling at 2^15 - 1 for scale16
1294 scale_arr = np.clip(scale_arr, 1.0 / (1 << 31), 32767.0)
1295
1296 #print('{} {} -> {}'.format(out_type_width, in_type_width, scale_arr))
1297
1298 multiplier_arr = np.int32(np.zeros(shape=[nc]))
1299 shift_arr = np.int32(np.zeros(shape=[nc]))
1300
1301 for i in range(nc):
1302 multiplier_arr[i], shift_arr[i] = TosaQuantGen.computeMultiplierAndShift(scale_arr[i], scale32)
Kevin Chengaee1fac2020-11-11 13:54:06 -08001303 if shift_arr[i] < 2 or shift_arr[i] > 62:
1304 self.ser.setExpectedFailure(True, 'OpRescale: invalid shift value')
Eric Kunzee5e26762020-10-13 16:11:07 -07001305
1306 #print('multiplier {} shift {} inzp {} outzp {}'.format(multiplier_arr, shift_arr, input_zp, output_zp))
1307
1308 attr = ts.TosaSerializerAttribute()
1309 attr.RescaleAttribute(input_zp,
1310 output_zp,
1311 multiplier_arr,
1312 shift_arr,
1313 scale32,
1314 double_round,
1315
1316 per_channel)
1317
1318 self.ser.addOperator(op, [val.name], [result_tens.name], attr)
1319 return result_tens
1320
1321 def build_cond_if_const(self, op, then_tens, else_tens, cond):
1322 # For cond_if with constants, we're supplied with then/else tensors that we ignore
1323 # (except for the generated shap) and the condition. Build Then/Else blocks
1324 # and fill them with const nodes for the body.
1325
1326 # Condition tensor
1327 cond_tens = self.ser.addConst([], DType.BOOL, Usage.ACTIVATION, [], [cond])
1328
1329 # Make then/else tensors
1330 out_shape = then_tens.shape
1331 then_arr = np.int32(self.rng.integers(0, 255, size=out_shape))
1332 else_arr = np.int32(self.rng.integers(0, 255, size=out_shape))
1333
1334 # And the result tensor based on any of the outputs
1335 result_tens = self.ser.addOutput(out_shape, DType.INT32, Usage.ACTIVATION, [])
1336
1337 # Create the attribute with the names of the then/else blocks
1338 then_block = 'THEN_BLOCK'
1339 else_block = 'ELSE_BLOCK'
1340 attr = ts.TosaSerializerAttribute()
1341 attr.CondIfAttribute(then_block, else_block)
1342
1343 # Finally, build the op and the two blocks
1344 self.ser.addOperator(op, [cond_tens.name], [result_tens.name], attr)
1345
1346 self.ser.startBasicBlock(then_block)
1347 # Build the actual then/else tensors inside their blocks
1348 then_tens = self.ser.addConst(out_shape, DType.INT32, Usage.ACTIVATION, [], then_arr)
1349 self.ser.addOutputTensor(then_tens)
1350
1351 self.ser.startBasicBlock(else_block)
1352 else_tens = self.ser.addConst(out_shape, DType.INT32, Usage.ACTIVATION, [], else_arr)
1353 self.ser.addOutputTensor(else_tens)
1354
1355 return result_tens
1356
1357 def build_cond_if_binary(self, op, a, b, cond):
1358 # For cond_if with a binary op in the then/else blocks, take a and b and
1359 # alternately add or subtract them based on the condition
1360
1361 # Condition tensor
1362 cond_tens = self.ser.addConst([], DType.BOOL, Usage.ACTIVATION, [], [cond])
1363
1364 result_tens = self.ser.addOutput(a.shape, a.dtype, Usage.ACTIVATION, [])
1365 self.ser.currBasicBlock.addOutput(result_tens.name)
1366
1367 # Create the attribute with the names of the then/else blocks
1368 then_block = 'THEN_BLOCK'
1369 else_block = 'ELSE_BLOCK'
1370 attr = ts.TosaSerializerAttribute()
1371 attr.CondIfAttribute(then_block, else_block)
1372
1373 # Finally, build the op and the two blocks
1374 self.ser.addOperator(op, [cond_tens.name, a.name, b.name], [result_tens.name], attr)
1375
1376 self.ser.startBasicBlock(then_block)
1377 self.ser.addInputTensor(a)
1378 self.ser.addInputTensor(b)
1379 then_tens = self.ser.addOutput(a.shape, a.dtype, a.usage, a.dformat)
1380 self.ser.addOperator(Op.ADD, [a.name, b.name], [then_tens.name])
1381
1382 self.ser.startBasicBlock(else_block)
1383 self.ser.addInputTensor(a)
1384 self.ser.addInputTensor(b)
1385 else_tens = self.ser.addOutput(a.shape, a.dtype, a.usage, a.dformat)
1386 self.ser.addOperator(Op.SUB, [a.name, b.name], [else_tens.name])
1387
1388 return result_tens
1389
1390 def build_while_loop(self, op, a, iter_val):
1391 iter = self.ser.addPlaceholder([], DType.INT32, Usage.ACTIVATION, [], [np.int32(iter_val)])
1392
1393 cond_block = 'COND_BLOCK'
1394 body_block = 'BODY_BLOCK'
1395
1396 attr = ts.TosaSerializerAttribute()
1397 attr.WhileLoopAttribute(cond_block, body_block)
1398
1399 # Accumulator tensor
1400 #acc = self.ser.addOutput(a.shape, a.dtype, a.usage, a.dformat)
1401 acc_init_val = np.int32(np.zeros(a.shape))
1402 acc = self.ser.addPlaceholder(a.shape, a.dtype, a.usage, a.dformat, acc_init_val)
1403
1404 # Intermediate/output tensors for everything going through the loop
1405 iter_out = self.ser.addIntermediate(iter.shape, iter.dtype, iter.usage, iter.dformat)
1406 a_out = self.ser.addIntermediate(a.shape, a.dtype, a.usage, a.dformat)
1407 acc_out = self.ser.addIntermediate(acc.shape, acc.dtype, acc.usage, acc.dformat)
1408
1409 # While_loop operator
1410 self.ser.addOperator(op,
1411 [iter.name, a.name, acc.name],
1412 [iter_out.name, a_out.name, acc_out.name], attr)
1413
1414 # COND block (input: iter, output: cond_tens )
1415 self.ser.startBasicBlock(cond_block)
1416 self.ser.addInputTensor(iter)
1417 self.ser.addInputTensor(a)
1418 self.ser.addInputTensor(acc)
1419 zero_tens = self.ser.addConst([], DType.INT32, [], [], [np.int32(0)])
1420 cond_tens = self.ser.addOutput([], DType.BOOL, [], [])
1421 self.ser.addOperator(Op.GREATER, [iter.name, zero_tens.name],
1422 [cond_tens.name])
1423
1424 # BODY block (input: a, acc, iter, output: a, acc, iter)
1425 # Note that local intermediate tensors need to be declared here for the outputs
1426 self.ser.startBasicBlock(body_block)
1427 self.ser.addInputTensor(iter)
1428 self.ser.addInputTensor(a)
1429 self.ser.addInputTensor(acc)
1430 one_tens = self.ser.addConst([], DType.INT32, [], [], [np.int32(1)])
1431 iter_body_out = self.ser.addIntermediate(iter.shape, iter.dtype, iter.usage, iter.dformat)
1432 acc_body_out = self.ser.addIntermediate(acc.shape, acc.dtype, acc.usage, acc.dformat)
1433 self.ser.addOperator(Op.ADD, [a.name, acc.name], [acc_body_out.name])
1434 self.ser.addOperator(Op.SUB, [iter.name, one_tens.name], [iter_body_out.name])
1435 self.ser.addOutputTensor(iter_body_out)
1436 self.ser.addOutputTensor(a)
1437 self.ser.addOutputTensor(acc_body_out)
1438
1439 return acc_out
1440
1441
1442 def genOpTestList(self, opName, shapeFilter=[None], rankFilter=None, dtypeFilter=None):
1443
1444 try:
1445 op = self.TOSA_OP_LIST[opName]
1446 except KeyError as e:
1447 raise Exception('Cannot find op with name {}'.format(opName))
1448
1449 # Initialize a new random number generator
1450 self.rng = np.random.default_rng(self.random_seed)
1451
1452 build_fcn, tgen_fcn, agen_fcn = op['build_fcn']
1453
1454 # Generate the lists of arguments
1455 rmin, rmax = op['rank']
1456
1457 # Test list consists of a tuple of:
1458 # (opName, testNameStr, dtype, shapeList, argumentsList)
1459 testList = []
1460
1461 if not shapeFilter:
1462 shapeFilter = [None]
1463
1464 for r in range(rmin, rmax + 1):
1465
1466 # Filter out the rank?
1467 if rankFilter is not None and r not in rankFilter:
1468 continue
1469
1470 for t in op['types']:
1471
1472 # Filter tests based on dtype?
1473 if dtypeFilter is not None:
1474 if t not in dtypeFilter:
1475 continue
1476
1477 # Create the placeholder and const tensors
1478 for shape in shapeFilter:
1479 # A None shape chooses a random shape of a given rank
1480
1481 # Filter out by rank
1482 if shape is not None and len(shape) != r:
1483 continue
1484
1485 self.setTargetShape(shape)
1486 shapeList = tgen_fcn(self, op, r)
1487
1488 shapeStr = self.shapeStr(shapeList[0])
1489 typeStr = self.typeStr(t)
1490
1491 # Argument lists consists of tuples of the (str, []) string representation and the build function argument list
1492 argList = []
1493 if agen_fcn:
1494 argList = agen_fcn(self, opName, shapeList, t)
1495 else:
1496 argList = [('', [])]
1497
1498 for argStr, args in argList:
1499 if argStr:
1500 testStr = '{}_{}_{}_{}'.format(opName, shapeStr, typeStr, argStr)
1501 else:
1502 testStr = '{}_{}_{}'.format(opName, shapeStr, typeStr)
1503
1504 testList.append((opName, testStr, t, shapeList, args))
1505
1506 return testList
1507
1508 def serializeTest(self, opName, testStr, dtype, shapeList, testArgs):
1509 try:
1510 op = self.TOSA_OP_LIST[opName]
1511 except KeyError as e:
1512 raise Exception('Cannot find op with name {}'.format(opName))
1513
1514 # Create a serializer
1515 self.createSerializer(opName, testStr)
1516
1517 build_fcn, tgen_fcn, agen_fcn = op['build_fcn']
1518 pCount, cCount = op['operands']
1519
1520 try:
1521 qgen = op['qgen']
1522 except KeyError:
1523 qgen = None
1524
1525 # Build the random tensor operands and the test
1526 tens = []
Kevin Chengaee1fac2020-11-11 13:54:06 -08001527
1528 # If test is ArithmeticRightShift, force value of operand[1] to be within [0, num_bits]
1529 if op['op'] == Op.ARITHMETIC_RIGHT_SHIFT:
1530 assert pCount == 2 and cCount == 0, 'Op.ArithmeticRightShift must have 2 placeholders, 0 consts'
1531
1532 placeholders = []
1533 for idx, shape in enumerate(shapeList[:]):
1534 if idx == 1:
1535 if dtype == DType.INT8:
1536 arr = np.int32(self.rng.integers(low=0, high=8, size=shape))
1537 elif dtype == DType.INT16:
1538 arr = np.int32(self.rng.integers(low=0, high=16, size=shape))
1539 elif dtype == DType.INT32:
1540 arr = np.int32(self.rng.integers(low=0, high=32, size=shape))
1541 else:
1542 raise Exception('OpArithmeticRightShift: invalid input dtype')
1543 else:
1544 arr = self.getRandTensor(shapeList[0], dtype)
1545 placeholders.append(self.ser.addPlaceholder(shape, dtype, Usage.ACTIVATION, [], arr))
1546
1547 tens.extend(placeholders)
1548 else:
1549 tens.extend(self.buildPlaceholderTensors(shapeList[0:pCount], dtype))
1550 tens.extend(self.buildConstTensors(shapeList[pCount:], dtype))
Eric Kunzee5e26762020-10-13 16:11:07 -07001551
1552 if qgen is not None:
1553 qinfo = qgen(self, op, dtype)
1554 else:
1555 qinfo = None
1556
1557 try:
1558 if qinfo is not None:
1559 resultName = build_fcn(self, op['op'], *tens, *testArgs, qinfo)
1560 else:
1561 resultName = build_fcn(self, op['op'], *tens, *testArgs)
1562 except TypeError as e:
1563 print('build_fcn: {}\nTensors: {}\nArgs: {}\n'.format(build_fcn, tens, testArgs))
1564 raise e
1565
1566 # Save the serialized test
1567 self.serialize('test')
1568
1569 def createDynamicOpLists(self):
1570
1571 # Dynamically create op lists for convolutions with a list of kernel sizes
1572 KERNELS = [ [1, 1], [2, 2], [3, 3], [5, 5], [3, 1], [1, 3] ]
1573
1574 for k in KERNELS:
1575 testName = 'conv2d_{}x{}'.format(k[0], k[1])
1576 self.TOSA_OP_LIST[testName] = self.TOSA_OP_LIST['conv2d_TEMPLATE'].copy()
1577 self.TOSA_OP_LIST[testName]['filter'] = k
1578 self.TOSA_OP_LIST[testName]['template'] = False
1579
1580 testName = 'depthwise_conv2d_{}x{}'.format(k[0], k[1])
1581 self.TOSA_OP_LIST[testName] = self.TOSA_OP_LIST['depthwise_conv2d_TEMPLATE'].copy()
1582 self.TOSA_OP_LIST[testName]['filter'] = k
1583 self.TOSA_OP_LIST[testName]['template'] = False
1584
1585 testName = 'transpose_conv2d_{}x{}'.format(k[0], k[1])
1586 self.TOSA_OP_LIST[testName] = self.TOSA_OP_LIST['transpose_conv2d_TEMPLATE'].copy()
1587 self.TOSA_OP_LIST[testName]['filter'] = k
1588 self.TOSA_OP_LIST[testName]['template'] = False
1589
1590 # Delete any templates after having created any dynamic ops
1591 # This is a two-pass operation because it's bad practice to delete
1592 # keys from dictionaries while iterating
1593 keyList = []
1594 for k in self.TOSA_OP_LIST:
1595 try:
1596 if self.TOSA_OP_LIST[k]['template'] == True:
1597 keyList.append(k)
1598 continue
1599 except KeyError:
1600 pass
1601
1602 for k in keyList:
1603 del self.TOSA_OP_LIST[k]
1604
1605 def initOpListDefaults(self):
1606 '''Fill in default fields for ops if they aren't already specified.
1607 Look for missing required fields (datastructure linting).'''
1608 for op in self.TOSA_OP_LIST:
1609
1610 # Required fields
1611 try:
1612 pl, c = self.TOSA_OP_LIST[op]['operands']
1613 except (KeyError, ValueError, TypeError):
1614 raise Exception('Op {} is missing a valid operand tuple in TOSA_OP_LIST'.format(op))
1615
1616 try:
1617 fcn, tgen, arggen = self.TOSA_OP_LIST[op]['build_fcn']
1618 except (KeyError, ValueError, TypeError):
1619 raise Exception('Op {} is missing a valid build_fcn tuple in TOSA_OP_LIST'.format(op))
1620
1621 try:
1622 types = self.TOSA_OP_LIST[op]['types']
1623 except KeyError as e:
1624 raise Exception('Op {} is missing a valid type list in TOSA_OP_LIST'.format(op))
1625
1626 try:
1627 opcode = self.TOSA_OP_LIST[op]['op']
1628 except KeyError as e:
1629 raise Exception('Op {} is missing the Op field in TOSA_OP_LIST'.format(op))
1630
1631 # Put in default rank range, if missing
1632 try:
1633 rank = self.TOSA_OP_LIST[op]['rank']
1634 except KeyError:
1635 self.TOSA_OP_LIST[op]['rank'] = self.DEFAULT_RANK_RANGE
1636
1637 # Tensor operator list
1638 # 'op': op name
1639 # 'operands': tuple of (placeholder, const) operands
1640 # 'rank': optional, restricts rank to tuple inclusive of (min, max), if not specified, defaults to (1, 4)
1641 # 'build_fcn': tuple of the function to (build_operator(), TensorGen function, ArgGen enum)
1642 # 'types': array of datatypes to be tested
1643 TYPE_FP = [ DType.FLOAT ]
1644
1645 # Type with an aint8
1646 TYPE_INT = [ DType.AINT8, DType.INT16, DType.INT32 ] # Most operators support AINT8 instead of INT8, excludes INT4
1647 TYPE_INT_FP = [ DType.AINT8, DType.INT16, DType.INT32, DType.FLOAT ] # Most operators support AINT8 instead of INT8, excludes INT4
1648
1649 # Types with an int8
1650 TYPE_PURE_INT = [ DType.INT8, DType.INT16, DType.INT32 ] # Note: excludes INT4
1651 TYPE_PURE_INT_FP = [ DType.INT8, DType.INT16, DType.INT32, DType.FLOAT ] # Note: excludes INT4
1652 TYPE_BOOL = [ DType.BOOL ]
1653 TYPE_FI32 = [ DType.FLOAT, DType.INT32 ]
1654 TYPE_FIB = [ DType.FLOAT, DType.AINT8, DType.INT8, DType.INT16, DType.INT32, DType.BOOL ]
1655 TYPE_FI16 = [ DType.FLOAT, DType.INT16 ]
1656
1657 TYPE_NARROW_INT_FP = [ DType.AINT8, DType.INT16, DType.FLOAT ]
1658
1659 DEFAULT_RANK_RANGE = (1, 4)
1660
1661 TOSA_OP_LIST = {
1662 # Binary ops
1663 'add':
1664 { 'op': Op.ADD,
1665 'operands': (2, 0),
1666 'build_fcn': (build_binary_broadcast, TosaTensorGen.tgBroadcastFuzz, None),
1667 'types': TYPE_FI32 },
1668
1669 'arithmetic_right_shift':
1670 { 'op': Op.ARITHMETIC_RIGHT_SHIFT,
1671 'operands': (2, 0),
Kevin Chengaee1fac2020-11-11 13:54:06 -08001672 'build_fcn': (build_arithmetic_right_shift, TosaTensorGen.tgBroadcastFuzz, TosaArgGen.agArithmeticRightShift),
Eric Kunzee5e26762020-10-13 16:11:07 -07001673 'types': TYPE_PURE_INT },
1674
1675 'bitwise_and':
1676 { 'op': Op.BITWISE_AND,
1677 'operands': (2, 0),
1678 'build_fcn': (build_binary_broadcast, TosaTensorGen.tgBroadcastFuzz, None),
1679 'types': TYPE_INT },
1680
1681 'bitwise_or':
1682 { 'op': Op.BITWISE_OR,
1683 'operands': (2, 0),
1684 'build_fcn': (build_binary_broadcast, TosaTensorGen.tgBroadcastFuzz, None),
1685 'types': TYPE_INT },
1686
1687 'bitwise_xor':
1688 { 'op': Op.BITWISE_XOR,
1689 'operands': (2, 0),
1690 'build_fcn': (build_binary_broadcast, TosaTensorGen.tgBroadcastFuzz, None),
1691 'types': TYPE_INT },
1692
1693 'logical_and':
1694 { 'op': Op.LOGICAL_AND,
1695 'operands': (2, 0),
1696 'build_fcn': (build_binary_broadcast, TosaTensorGen.tgBroadcastFuzz, None),
1697 'types': TYPE_BOOL },
1698
1699 'logical_left_shift':
1700 { 'op': Op.LOGICAL_LEFT_SHIFT,
1701 'operands': (2, 0),
1702 'build_fcn': (build_binary_broadcast, TosaTensorGen.tgBroadcastFuzz, None),
1703 'types': TYPE_PURE_INT },
1704
1705 'logical_right_shift':
1706 { 'op': Op.LOGICAL_RIGHT_SHIFT,
1707 'operands': (2, 0),
1708 'build_fcn': (build_binary_broadcast, TosaTensorGen.tgBroadcastFuzz, None),
1709 'types': TYPE_PURE_INT },
1710
1711 'logical_or':
1712 { 'op': Op.LOGICAL_OR,
1713 'operands': (2, 0),
1714 'build_fcn': (build_binary_broadcast, TosaTensorGen.tgBroadcastFuzz, None),
1715 'types': TYPE_BOOL },
1716
1717 'logical_xor':
1718 { 'op': Op.LOGICAL_XOR,
1719 'operands': (2, 0),
1720 'build_fcn': (build_binary_broadcast, TosaTensorGen.tgBroadcastFuzz, None),
1721 'types': TYPE_BOOL },
1722
1723 'max':
1724 { 'op': Op.MAXIMUM,
1725 'operands': (2, 0),
1726 'build_fcn': (build_binary_broadcast, TosaTensorGen.tgBroadcastFuzz, None),
1727 'types': TYPE_FI32 },
1728
1729 'min':
1730 { 'op': Op.MINIMUM,
1731 'operands': (2, 0),
1732 'build_fcn': (build_binary_broadcast, TosaTensorGen.tgBroadcastFuzz, None),
1733 'types': TYPE_FI32 },
1734
1735 'mul':
1736 { 'op': Op.MUL,
1737 'operands': (2, 0),
Kevin Chengaee1fac2020-11-11 13:54:06 -08001738 'build_fcn': (build_mul, TosaTensorGen.tgBroadcastFuzz, TosaArgGen.agMul),
Eric Kunzee5e26762020-10-13 16:11:07 -07001739 'types': TYPE_PURE_INT_FP },
1740
1741 'pow':
1742 { 'op': Op.POW,
1743 'operands': (2, 0),
1744 'build_fcn': (build_binary_broadcast, TosaTensorGen.tgBasic, None),
1745 'types': TYPE_FP },
1746
1747 'sub':
1748 { 'op': Op.SUB,
1749 'operands': (2, 0),
1750 'build_fcn': (build_binary_broadcast, TosaTensorGen.tgBroadcastFuzz, None),
1751 'types': TYPE_FI32 },
1752
1753 'table':
1754 { 'op': Op.TABLE,
1755 # Use the automatic generation functions to create the input array
1756 # but create the table tensor in the build function, as it may be
1757 # a different type from the input
1758 'operands': (1, 0),
1759 'build_fcn': (build_table, TosaTensorGen.tgBasic, None),
1760 'types': [ DType.INT16 ] },
1761
1762 'argmax':
1763 { 'op': Op.ARGMAX,
1764 'operands': (1, 0),
1765 'build_fcn': (build_argmax, TosaTensorGen.tgBasic, TosaArgGen.agAxis),
1766 'types': TYPE_FP },
1767
1768 # Templated operator. Filled in by createDynamicOpLists
1769 'conv2d_TEMPLATE':
1770 { 'op': Op.CONV2D,
1771 'operands': (1, 2),
1772 'rank': (4, 4),
1773 'build_fcn': (build_conv2d, TosaTensorGen.tgConv2D, TosaArgGen.agConv2D),
1774 'qgen': TosaQuantGen.qgConv,
1775 'types': TYPE_FP,
1776 'template': True },
1777
1778 # Templated operator. Filled in by createDynamicOpLists
1779 'depthwise_conv2d_TEMPLATE':
1780 { 'op': Op.DEPTHWISE_CONV2D,
1781 'operands': (1, 2),
1782 'filter': [1, 1],
1783 'rank': (4, 4),
1784 'build_fcn': (build_depthwise_conv2d, TosaTensorGen.tgDepthwiseConv2D, TosaArgGen.agConv2D),
1785 'qgen': TosaQuantGen.qgConv,
1786 'types': TYPE_FP,
1787 'template': True },
1788
1789 # Templated operator. Filled in by createDynamicOpLists
1790 'transpose_conv2d_TEMPLATE':
1791 { 'op': Op.TRANSPOSE_CONV2D,
1792 'operands': (1, 1),
1793 'rank': (4, 4),
1794 'build_fcn': (build_transpose_conv2d, TosaTensorGen.tgTransposeConv2D, TosaArgGen.agTransposeConv2D),
1795 'qgen': TosaQuantGen.qgConv,
1796 'types': TYPE_FP,
1797 'template': True },
1798
1799 'fully_connected':
1800 { 'op': Op.FULLY_CONNECTED,
1801 'operands': (2, 0),
1802 'rank': (2, 2),
1803 'build_fcn': (build_fully_connected, TosaTensorGen.tgFullyConnected, None),
1804 'qgen': TosaQuantGen.qgConv,
1805 'types': TYPE_FP },
1806
1807 'matmul':
1808 { 'op': Op.MATMUL,
1809 'operands': (2, 0),
1810 'rank': (2, 2),
1811 'build_fcn': (build_matmul, TosaTensorGen.tgMatmul, None),
1812 'qgen': TosaQuantGen.qgMatmul,
1813 'types': TYPE_NARROW_INT_FP },
1814
1815 # Unary operators
1816 'abs':
1817 { 'op': Op.ABS,
1818 'operands': (1, 0),
1819 'build_fcn': (build_unary, TosaTensorGen.tgBasic, None),
1820 'types': TYPE_FI32 },
1821
1822 'bitwise_not':
1823 { 'op': Op.BITWISE_NOT,
1824 'operands': (1, 0),
1825 'build_fcn': (build_unary, TosaTensorGen.tgBasic, None),
1826 'types': TYPE_INT },
1827
1828 'ceil':
1829 { 'op': Op.CEIL,
1830 'operands': (1, 0),
1831 'build_fcn': (build_unary, TosaTensorGen.tgBasic, None),
1832 'types': TYPE_FP },
1833
1834 'clz':
1835 { 'op': Op.CLZ,
1836 'operands': (1, 0),
1837 'build_fcn': (build_unary, TosaTensorGen.tgBasic, None),
1838 'types': [ DType.INT32 ] },
1839
1840 'exp':
1841 { 'op': Op.EXP,
1842 'operands': (1, 0),
1843 'build_fcn': (build_unary, TosaTensorGen.tgBasic, None),
1844 'types': TYPE_FP },
1845
1846 'floor':
1847 { 'op': Op.FLOOR,
1848 'operands': (1, 0),
1849 'build_fcn': (build_unary, TosaTensorGen.tgBasic, None),
1850 'types': TYPE_FP },
1851
1852 'log':
1853 { 'op': Op.LOG,
1854 'operands': (1, 0),
1855 'build_fcn': (build_unary, TosaTensorGen.tgBasic, None),
1856 'types': TYPE_FP },
1857
1858 'floor':
1859 { 'op': Op.FLOOR,
1860 'operands': (1, 0),
1861 'build_fcn': (build_unary, TosaTensorGen.tgBasic, None),
1862 'types': TYPE_FP },
1863
1864 'logical_not':
1865 { 'op': Op.LOGICAL_NOT,
1866 'operands': (1, 0),
1867 'build_fcn': (build_unary, TosaTensorGen.tgBasic, None),
1868 'types': TYPE_BOOL },
1869
1870 'negate':
1871 { 'op': Op.NEGATE,
1872 'operands': (1, 0),
1873 'build_fcn': (build_unary, TosaTensorGen.tgBasic, None),
1874 'qgen': TosaQuantGen.qgUnary,
1875 'types': TYPE_INT_FP },
1876
1877 'reciprocal':
1878 { 'op': Op.RECIPROCAL,
1879 'operands': (1, 0),
1880 'build_fcn': (build_unary, TosaTensorGen.tgBasic, None),
1881 'types': TYPE_FP },
1882
1883 'rsqrt':
1884 { 'op': Op.RSQRT,
1885 'operands': (1, 0),
1886 'build_fcn': (build_unary, TosaTensorGen.tgBasic, None),
1887 'types': TYPE_FP },
1888
1889 # Ternary operators
1890 'select':
1891 { 'op': Op.SELECT,
1892 'operands': (3, 0),
1893 'build_fcn': (build_select, TosaTensorGen.tgBroadcastFuzz, None),
1894 'types': TYPE_FIB },
1895
1896 # Comparison operators
1897 'equal':
1898 { 'op': Op.EQUAL,
1899 'operands': (2, 0),
1900 'build_fcn': (build_comparison, TosaTensorGen.tgBroadcastFuzz, None),
1901 'types': TYPE_FI32 },
1902
1903 'greater_equal':
1904 { 'op': Op.GREATER_EQUAL,
1905 'operands': (2, 0),
1906 'build_fcn': (build_comparison, TosaTensorGen.tgBroadcastFuzz, None),
1907 'types': TYPE_FI32 },
1908
1909 'greater':
1910 { 'op': Op.GREATER,
1911 'operands': (2, 0),
1912 'build_fcn': (build_comparison, TosaTensorGen.tgBroadcastFuzz, None),
1913 'types': TYPE_FI32 },
1914
1915 # Pooling operators
1916 'avg_pool2d':
1917 { 'op': Op.AVG_POOL2D,
1918 'operands': (1, 0),
1919 'rank': (4, 4),
1920 'build_fcn': (build_pool2d, TosaTensorGen.tgNHWC, TosaArgGen.agPooling),
1921 'qgen': TosaQuantGen.qgUnary,
1922 'types': TYPE_NARROW_INT_FP },
1923
1924
1925 'max_pool2d':
1926 { 'op': Op.MAX_POOL2D,
1927 'operands': (1, 0),
1928 'rank': (4, 4),
1929 'build_fcn': (build_pool2d, TosaTensorGen.tgNHWC, TosaArgGen.agPooling),
1930 'types': TYPE_NARROW_INT_FP },
1931
1932 # Reduce operators
1933 'reduce_any':
1934 { 'op': Op.REDUCE_ANY,
1935 'operands': (1, 0),
1936 'build_fcn': (build_reduce, TosaTensorGen.tgBasic, TosaArgGen.agAxis),
1937 'types': TYPE_BOOL },
1938
1939 'reduce_all':
1940 { 'op': Op.REDUCE_ALL,
1941 'operands': (1, 0),
1942 'build_fcn': (build_reduce, TosaTensorGen.tgBasic, TosaArgGen.agAxis),
1943 'types': TYPE_BOOL },
1944
1945 'reduce_max':
1946 { 'op': Op.REDUCE_MAX,
1947 'operands': (1, 0),
1948 'build_fcn': (build_reduce, TosaTensorGen.tgBasic, TosaArgGen.agAxis),
1949 'types': TYPE_INT_FP },
1950
1951 'reduce_min':
1952 { 'op': Op.REDUCE_MAX,
1953 'operands': (1, 0),
1954 'build_fcn': (build_reduce, TosaTensorGen.tgBasic, TosaArgGen.agAxis),
1955 'types': TYPE_INT_FP },
1956
1957 'reduce_product':
1958 { 'op': Op.REDUCE_PRODUCT,
1959 'operands': (1, 0),
1960 'build_fcn': (build_reduce, TosaTensorGen.tgBasic, TosaArgGen.agAxis),
1961 'types': TYPE_FP },
1962
1963 'reduce_sum':
1964 { 'op': Op.REDUCE_SUM,
1965 'operands': (1, 0),
1966 'build_fcn': (build_reduce, TosaTensorGen.tgBasic, TosaArgGen.agAxis),
1967 'types': TYPE_FI32 },
1968
1969 # Activation functions
1970 'clamp':
1971 { 'op': Op.CLAMP,
1972 'operands': (1, 0),
1973 'build_fcn': (build_clamp, TosaTensorGen.tgBasic, None),
1974 'types': TYPE_NARROW_INT_FP },
1975
1976 'relun':
1977 { 'op': Op.RELUN,
1978 'operands': (1, 0),
1979 'build_fcn': (build_relun, TosaTensorGen.tgBasic, None),
1980 'types': TYPE_FI32 },
1981
1982 'sigmoid':
1983 { 'op': Op.SIGMOID,
1984 'operands': (1, 0),
1985 'build_fcn': (build_sigmoid, TosaTensorGen.tgBasic, None),
1986 'types': TYPE_FP },
1987
1988 'tanh':
1989 { 'op': Op.TANH,
1990 'operands': (1, 0),
1991 'build_fcn': (build_tanh, TosaTensorGen.tgBasic, None),
1992 'types': TYPE_FP },
1993
1994 # Data layout operators
1995 'concat':
1996 { 'op': Op.CONCAT,
1997 'operands': (2, 0),
1998 'build_fcn': (build_concat, TosaTensorGen.tgBasic, TosaArgGen.agAxis),
1999 'types': TYPE_FIB },
2000
2001 'pad':
2002 { 'op': Op.PAD,
2003 'operands': (1, 0),
2004 'build_fcn': (build_pad, TosaTensorGen.tgBasic, TosaArgGen.agPad),
2005 'qgen': TosaQuantGen.qgPad,
2006 'types': TYPE_FIB },
2007
2008 'reshape':
2009 { 'op': Op.RESHAPE,
2010 'operands': (1, 0),
2011 'build_fcn': (build_reshape, TosaTensorGen.tgBasic, TosaArgGen.agReshape),
2012 'types': TYPE_FIB },
2013
2014 'reverse':
2015 { 'op': Op.REVERSE,
2016 'operands': (1, 0),
2017 'build_fcn': (build_reverse, TosaTensorGen.tgBasic, TosaArgGen.agAxis),
2018 'types': TYPE_FIB },
2019
2020 'slice':
2021 { 'op': Op.SLICE,
2022 'operands': (1, 0),
2023 'build_fcn': (build_slice, TosaTensorGen.tgBasic, TosaArgGen.agSlice),
2024 'types': TYPE_FIB },
2025
2026 'tile':
2027 { 'op': Op.TILE,
2028 'operands': (1, 0),
2029 'build_fcn': (build_tile, TosaTensorGen.tgBasic, TosaArgGen.agTile),
2030 'types': TYPE_FIB },
2031
2032 'transpose':
2033 { 'op': Op.TRANSPOSE,
2034 'operands': (1, 0),
2035 'rank': (2, 4), # Do not allow tranpose on rank=1
2036 'build_fcn': (build_transpose, TosaTensorGen.tgBasic, TosaArgGen.agTranspose),
2037 'types': TYPE_FIB },
2038
2039 # Scatter/Gather
2040 'gather':
2041 { 'op': Op.GATHER,
Kevin Cheng77d0f762020-11-24 10:26:32 -08002042 # Only specify 'values' tensor here. 'indices' is generated in op building stage
Eric Kunzee5e26762020-10-13 16:11:07 -07002043 'operands': (1, 0),
Kevin Cheng77d0f762020-11-24 10:26:32 -08002044 'rank': (3, 3),
2045 'build_fcn': (build_gather, TosaTensorGen.tgBasic, None),
2046 'types': TYPE_INT_FP },
Eric Kunzee5e26762020-10-13 16:11:07 -07002047
Kevin Cheng77d0f762020-11-24 10:26:32 -08002048 'scatter':
2049 { 'op': Op.SCATTER,
2050 # Only specify 'values_in' tensor here.
2051 #'indices' and 'input' are generated in op building stage
2052 'operands': (2, 0),
2053 'rank': (3, 3),
2054 'build_fcn': (build_scatter, TosaTensorGen.tgScatter, None),
2055 'types': TYPE_INT_FP },
Eric Kunzee5e26762020-10-13 16:11:07 -07002056
2057 # Image operations
2058 'resize':
2059 { 'op': Op.RESIZE,
2060 'operands': (1, 0),
2061 'rank': (4, 4),
2062 'build_fcn': ( build_resize, TosaTensorGen.tgNHWC, TosaArgGen.agResize),
Kevin Cheng77d0f762020-11-24 10:26:32 -08002063 'types': [ DType.INT8, DType.INT16, DType.FLOAT ] },
Eric Kunzee5e26762020-10-13 16:11:07 -07002064
2065
2066 # Data nodes
2067 'placeholder':
2068 { 'op': Op.PLACEHOLDER,
2069 'operands': (1, 0),
2070 'build_fcn': ( build_placeholder, TosaTensorGen.tgBasic, None),
2071 'types': TYPE_FIB },
2072
2073 'const':
2074 { 'op': Op.CONST,
2075 'operands': (1, 0),
2076 'build_fcn': ( build_placeholder, TosaTensorGen.tgBasic, None),
2077 'types': TYPE_FIB },
2078
2079
2080 'identity':
2081 { 'op': Op.IDENTITY,
2082 'operands': (1, 0),
2083 'build_fcn': ( build_unary, TosaTensorGen.tgBasic, None),
2084 'types': TYPE_FIB },
2085
2086
2087 'identityn':
2088 { 'op': Op.IDENTITYN,
2089 'operands': (2, 0),
2090 'build_fcn': ( build_identityn, TosaTensorGen.tgBasic, None),
2091 'types': TYPE_FIB },
2092
2093 # Type conversion
2094 'cast':
2095 { 'op': Op.CAST,
2096 'operands': (1, 0),
2097 'build_fcn': ( build_cast, TosaTensorGen.tgBasic, TosaArgGen.agCast ),
2098 'types': [ DType.FLOAT, DType.INT8, DType.INT16, DType.INT32, DType.BOOL ] },
2099
2100 'rescale':
2101 { 'op': Op.RESCALE,
2102 'operands': (1, 0),
2103 'build_fcn': ( build_rescale, TosaTensorGen.tgBasic, TosaArgGen.agRescale ),
2104 'types': [ DType.AINT8, DType.INT16, DType.INT32, DType.INT48 ] },
2105
2106 # Custom
2107 # Not implemented.
2108
2109 # Control flow
2110
2111 # Two varients of cond_if, one that generates one of two constant tensors (no
2112 # inputs to the basic blocks, one output) and another that either adds or subtracts two tensors
2113 # (two inputs to the basic blocks, one output)
2114 'cond_if_const':
2115 { 'op': Op.COND_IF,
2116 'operands': (0, 2),
2117 'build_fcn': ( build_cond_if_const, TosaTensorGen.tgBasic, TosaArgGen.agCondIf ),
2118 'types': [ DType.BOOL ] },
2119
2120 'cond_if_binary':
2121 { 'op': Op.COND_IF,
2122 'operands': (2, 0),
2123 'build_fcn': ( build_cond_if_binary, TosaTensorGen.tgBasic, TosaArgGen.agCondIf ),
2124 'types': TYPE_FI32 },
2125
2126 # while_loop
2127 'while_loop':
2128 { 'op': Op.WHILE_LOOP,
2129 'operands': (0, 1),
2130 'build_fcn': ( build_while_loop, TosaTensorGen.tgBasic, TosaArgGen.agWhileLoop ),
2131 'types': [DType.INT32] },
2132
2133
2134 }
2135
2136class OutputShaper:
2137 # Methods in this class compute the expected output shape and datatype
2138 # for common classes of operations
2139 def __init__(self):
2140 pass
2141
2142 # These methods return arguments that can be used for
2143 # creating a new output tensor
2144 @staticmethod
2145 def binaryBroadcastOp(ser, a, b):
2146 assert(len(a.shape) == len(b.shape))
2147 assert(a.dtype == b.dtype)
2148
2149 shape = []
2150 for i in range(len(a.shape)):
2151 if a.shape[i] == 1:
2152 shape.append(b.shape[i])
2153 else:
2154 shape.append(a.shape[i])
2155
2156 return ser.addOutput(shape, a.dtype, a.usage, a.dformat)
2157
2158 @staticmethod
2159 def binaryNonBroadcastOp(ser, a, b):
2160 assert(len(a.shape) == len(b.shape))
2161 assert(a.dtype == b.dtype)
2162
2163 shape = []
2164 for i in range(len(a.shape)):
2165 assert(a.shape[i] == b.shape[i])
2166 shape.append(a.shape[i])
2167
2168 return ser.addOutput(shape, a.dtype, a.usage, a.dformat)
2169
2170 @staticmethod
2171 def unaryOp(ser, a):
2172 return ser.addOutput(a.shape, a.dtype, a.usage, a.dformat)
2173
2174 @staticmethod
2175 def selectOp(ser, cond, a, b):
2176 assert(len(a.shape) == len(b.shape) and len(a.shape) == len(cond.shape))
2177 assert(a.dtype == b.dtype)
2178
2179 shape = []
2180 for i in range(len(a.shape)):
2181 shape.append(max(cond.shape[i], a.shape[i], b.shape[i]))
2182
2183 return ser.addOutput(shape, a.dtype, a.usage, a.dformat)
2184
2185 @staticmethod
2186 def binaryComparisonOp(ser, a, b):
2187 assert(len(a.shape) == len(b.shape))
2188 assert(a.dtype == b.dtype)
2189
2190 # Do broadcast
2191 shape = []
2192 for i in range(len(a.shape)):
2193 if a.shape[i] == 1:
2194 shape.append(b.shape[i])
2195 else:
2196 shape.append(a.shape[i])
2197
2198 # Force the output type to bool
2199 return ser.addOutput(shape, DType.BOOL, a.usage, a.dformat)
2200
2201 @staticmethod
2202 def reduceOp(ser, a, axis):
2203
2204 shape = a.shape.copy()
2205
2206 shape[axis] = 1
2207
2208 return ser.addOutput(shape, a.dtype, a.usage, a.dformat)
2209
2210 @staticmethod
2211 def argmaxOp(ser, a, axis):
2212 shape = a.shape.copy()
2213 del shape[axis]
2214 return ser.addOutput(shape, DType.INT32, a.usage, a.dformat)
2215
2216 @staticmethod
2217 def conv2dOp(ser, ifm, filter, strides, padding, dilations):
2218
2219 # IFM: NHWC
2220 # Filter: OHWI
2221 # OFM: NHWC
2222
2223 if len(padding) == 2:
2224 # Expand padding to 4 parameters in the case of transpose_conv2d
2225 # From H,W to T,B,L,R
2226 padding = [padding[0], padding[0], padding[1], padding[1]]
2227
2228 h = (ifm.shape[1] - filter.shape[1] - (filter.shape[1] - 1) * (dilations[0] - 1) + \
2229 padding[0] + padding[1]) // strides[0] + 1
2230
2231 w = (ifm.shape[2] - filter.shape[2] - (filter.shape[2] - 1) * (dilations[1] - 1) + \
2232 padding[2] + padding[3]) // strides[1] + 1
2233
2234 if h <= 0 or w <= 0:
2235 # Invalid test parameters?
2236 h = 0
2237 w = 0
2238 ser.setExpectedFailure(True, 'Invalid combination of conv2d parameters')
2239
2240 ofm_shape = [ifm.shape[0], h, w, filter.shape[0]]
2241
2242 if ifm.dtype == DType.AINT8 or ifm.dtype == DType.INT8:
2243 out_dtype = DType.INT32
2244 elif ifm.dtype == DType.INT16:
2245 out_dtype = DType.INT48
2246 elif ifm.dtype == DType.FLOAT:
2247 out_dtype = DType.FLOAT
2248 else:
2249 raise Exception('Unsupported input dtype: {}'.format(ifm.dtype))
2250
2251 return ser.addOutput(ofm_shape, out_dtype, ifm.usage, ifm.dformat)
2252
2253 @staticmethod
2254 def depthwiseConv2dOp(ser, ifm, filter, strides, padding, dilations):
2255 # IFM: NHWC
2256 # Filter: HWCM
2257 # OFM: NHW C*M
2258 h = (ifm.shape[1] - filter.shape[0] - (filter.shape[0] - 1) * (dilations[0] - 1) + \
2259 padding[0] + padding[1]) // strides[0] + 1
2260
2261 w = (ifm.shape[2] - filter.shape[1] - (filter.shape[1] - 1) * (dilations[1] - 1) + \
2262 padding[2] + padding[3]) // strides[1] + 1
2263
2264 if h <= 0 or w <= 0:
2265 # Invalid test parameters?
2266 h = 0
2267 w = 0
2268 ser.setExpectedFailure(True, 'Invalid combination of conv2d parameters')
2269
2270 ofm_shape = [ifm.shape[0], h, w, filter.shape[2] * filter.shape[3]]
2271
2272 if ifm.dtype == DType.AINT8 or ifm.dtype == DType.INT8:
2273 out_dtype = DType.INT32
2274 elif ifm.dtype == DType.INT16:
2275 out_dtype = DType.INT48
2276 elif ifm.dtype == DType.FLOAT:
2277 out_dtype = DType.FLOAT
2278 else:
2279 raise Exception('Unsupported input dtype: {}'.format(ifm.dtype))
2280
2281 return ser.addOutput(ofm_shape, out_dtype, ifm.usage, ifm.dformat)
2282
2283
2284 @staticmethod
2285 def pool2dOp(ser, ifm, kernel, stride, pad):
2286 # input: NHWC
2287 h = (ifm.shape[1] + pad[0] + pad[1] + stride[0] - kernel[0]) // stride[0]
2288 w = (ifm.shape[2] + pad[2] + pad[3] + stride[1] - kernel[1]) // stride[1]
2289
2290 if h <= 0 or w <= 0:
2291 # Invalid test parameters?
2292 h = 0
2293 w = 0
2294 ser.setExpectedFailure(True, 'Invalid combination of pooling parameters')
2295
2296 ofm_shape = [ifm.shape[0], h, w, ifm.shape[3]]
2297 return ser.addOutput(ofm_shape, ifm.dtype, ifm.usage, ifm.dformat)
2298
2299 @staticmethod
2300 def fullyConnectedOp(ser, input, filter):
2301 # input: N, IC
2302 # filter: OC, IC
2303 # output: N, OC
2304
2305 output_shape = [input.shape[0], filter.shape[0]]
2306
2307 if input.dtype == DType.AINT8 or input.dtype == DType.INT8:
2308 out_dtype = DType.INT32
2309 elif input.dtype == DType.INT16:
2310 out_dtype = DType.INT48
2311 elif input.dtype == DType.FLOAT:
2312 out_dtype = DType.FLOAT
2313 else:
2314 raise Exception('Unsupported input dtype: {}'.format(input.dtype))
2315
2316 return ser.addOutput(output_shape, out_dtype, input.usage, input.dformat)
2317
2318 @staticmethod
2319 def matmulOp(ser, a, b):
2320 # a: M, K
2321 # b: K, N
2322 # out: M, N
2323
2324 output_shape = [a.shape[0], b.shape[1]]
2325
2326
2327 if a.dtype == DType.AINT8:
2328 out_dtype = DType.INT32
2329 elif a.dtype == DType.INT16:
2330 out_dtype = DType.INT48
2331 elif a.dtype == DType.FLOAT:
2332 out_dtype = DType.FLOAT
2333 else:
2334 raise Exception('UNsupported input dtype for matmul: {}'.format(a.dtype))
2335
2336 return ser.addOutput(output_shape, out_dtype, a.usage, a.dformat)
2337
2338 @staticmethod
2339 def concatOp(ser, a, b, axis):
2340
2341 output_shape = a.shape.copy()
2342 output_shape[axis] = a.shape[axis] + b.shape[axis]
2343
2344 return ser.addOutput(output_shape, a.dtype, a.usage, a.dformat)
2345
2346 @staticmethod
2347 def padOp(ser, a, padding):
2348
2349 output_shape = a.shape.copy()
2350
2351 for i in range(len(output_shape)):
2352 output_shape[i] = padding[i][0] + padding[i][1] + output_shape[i]
2353
2354 return ser.addOutput(output_shape, a.dtype, a.usage, a.dformat)
2355
2356 @staticmethod
2357 def reshapeOp(ser, a, shape):
2358 output_shape = shape.copy()
2359
2360 totalElements = 1
2361 for i in a.shape:
2362 totalElements *= i
2363
2364 # If there are any -1 elements, figure out what that dimension must be
2365 totalOutputElements = 1
2366 for i in output_shape:
2367 if i != -1:
2368 totalOutputElements *= i
2369
2370 # And fill it in
2371 for i in range(len(output_shape)):
2372 if output_shape[i] == -1:
2373 output_shape[i] = totalElements // totalOutputElements
2374
2375 return ser.addOutput(output_shape, a.dtype, a.usage, a.dformat)
2376
2377 @staticmethod
2378 def sliceOp(ser, a, begin, size):
2379
2380 output_shape = size.copy()
2381 return ser.addOutput(output_shape, a.dtype, a.usage, a.dformat)
2382
2383 @staticmethod
2384 def tileOp(ser, a, multiples):
2385
2386 output_shape = a.shape.copy()
2387 assert(len(multiples) == len(output_shape))
2388
2389 for i in range(len(output_shape)):
2390 output_shape[i] = a.shape[i] * multiples[i]
2391
2392 return ser.addOutput(output_shape, a.dtype, a.usage, a.dformat)
2393
2394 @staticmethod
2395 def transposeOp(ser, a, perms):
2396 output_shape = a.shape.copy()
2397 assert(len(perms) == len(output_shape))
2398
2399 for i in range(len(output_shape)):
2400 output_shape[i] = a.shape[perms[i]]
2401
2402 return ser.addOutput(output_shape, a.dtype, a.usage, a.dformat)
2403
2404 @staticmethod
Kevin Cheng77d0f762020-11-24 10:26:32 -08002405 def gatherOp(ser, values, indices):
2406 assert len(values.shape) == 3
2407 assert len(indices.shape) == 2
2408 assert values.shape[0] == indices.shape[0]
Eric Kunzee5e26762020-10-13 16:11:07 -07002409
Kevin Cheng77d0f762020-11-24 10:26:32 -08002410 output_shape = [values.shape[0], indices.shape[1], values.shape[2]]
2411
2412 return ser.addOutput(output_shape, values.dtype, values.usage, values.dformat)
2413
2414 @staticmethod
2415 def scatterOp(ser, values_in, indices, input):
2416 assert len(values_in.shape) == 3
2417 assert len(indices.shape) == 2
2418 assert len(input.shape) == 3
2419 assert values_in.shape[0] == indices.shape[0] # N
2420 assert input.shape[1] == indices.shape[1] # W
2421 assert values_in.shape[2] == input.shape[2] # C
2422
2423 output_shape = values_in.shape
2424
2425 return ser.addOutput(output_shape, values_in.dtype, values_in.usage, values_in.dformat)
Eric Kunzee5e26762020-10-13 16:11:07 -07002426
2427 @staticmethod
2428 def tableOp(ser, input, table):
2429 # Same shape as the input, but with the type of the table.
2430 return ser.addOutput(input.shape, DType.INT32, input.usage, input.dformat)
2431
2432 @staticmethod
Kevin Cheng77d0f762020-11-24 10:26:32 -08002433 def resizeOp(ser, input, mode, stride, offset, shift, stride_fp, offset_fp, output_dims, input_dtype, output_dtype):
Eric Kunzee5e26762020-10-13 16:11:07 -07002434
2435 output_dims = [input.shape[0], output_dims[0], output_dims[1], input.shape[3]]
2436
Kevin Cheng77d0f762020-11-24 10:26:32 -08002437 if input_dtype == DType.FLOAT:
2438 if stride_fp[0] <= 0 or stride_fp[1] <= 0:
2439 ser.setExpectedFailure(True, 'Negative or zero stride')
2440 else:
2441 if stride[0] <= 0 or stride[1] <= 0:
2442 ser.setExpectedFailure(True, 'Negative or zero stride')
Eric Kunzee5e26762020-10-13 16:11:07 -07002443
Kevin Chengaee1fac2020-11-11 13:54:06 -08002444 if mode == ResizeMode.BILINEAR:
2445 if input_dtype == DType.INT8:
2446 if output_dtype != DType.INT32:
2447 ser.setExpectedFailure(True, 'Invalid output data type')
2448 elif input_dtype == DType.INT16:
2449 if output_dtype != DType.INT48:
2450 ser.setexpectedfailure(true, 'Invalid output data type')
Kevin Cheng77d0f762020-11-24 10:26:32 -08002451 elif input_dtype == DType.FLOAT:
2452 if output_dtype != DType.FLOAT:
2453 ser.setexpectedfailure(true, 'Invalid output data type')
Kevin Chengaee1fac2020-11-11 13:54:06 -08002454 else:
2455 ser.setexpectedfailure(true, 'Invalid input data type')
2456
2457 elif mode == ResizeMode.NEAREST:
2458 if input_dtype == DType.INT8:
2459 if output_dtype != DType.INT8:
2460 ser.setExpectedFailure(True, 'Invalid output data type')
2461 elif input_dtype == DType.INT16:
2462 if output_dtype != DType.INT16:
2463 ser.setexpectedfailure(true, 'Invalid output data type')
Kevin Cheng77d0f762020-11-24 10:26:32 -08002464 elif input_dtype == DType.FLOAT:
2465 if output_dtype != DType.FLOAT:
2466 ser.setexpectedfailure(true, 'Invalid output data type')
Kevin Chengaee1fac2020-11-11 13:54:06 -08002467 else:
2468 ser.setexpectedfailure(true, 'Invalid input data type')
2469
2470 else:
2471 ser.setexpectedfailure(true, 'Invalid resize mode')
2472
Eric Kunzee5e26762020-10-13 16:11:07 -07002473 return ser.addOutput(output_dims, output_dtype, input.usage, input.dformat)
2474
2475 @staticmethod
2476 def typeConversionOp(ser, val, out_dtype):
2477 return ser.addOutput(val.shape, out_dtype, val.usage, val.dformat)
2478
2479 @staticmethod
2480 def transposeConv2DOp(ser, ifm, output_shape):
2481 if ifm.dtype == DType.AINT8 or ifm.dtype == DType.INT8:
2482 out_dtype = DType.INT32
2483 elif ifm.dtype == DType.INT16:
2484 out_dtype = DType.INT48
2485 elif ifm.dtype == DType.FLOAT:
2486 out_dtype = DType.FLOAT
2487 else:
2488 raise Exception('Unsupported input dtype: {}'.format(ifm.dtype))
2489
2490 if output_shape[1] <= 0 or output_shape[2] <= 0:
2491 ser.setExpectedFailure(True, 'Negative output shape')
2492
2493 return ser.addOutput(output_shape, out_dtype, ifm.usage, ifm.dformat)