1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #define LOG_TAG "OperationsUtils"
18 
19 #include "OperationsUtils.h"
20 #include "Operations.h"
21 #include "Utils.h"
22 
23 #include <cmath>
24 
25 namespace android {
26 namespace nn {
27 
SameShape(const Shape & in1,const Shape & in2)28 bool SameShape(const Shape& in1, const Shape& in2) {
29     if (in1.type != in2.type || in1.dimensions.size() != in2.dimensions.size()) {
30         return false;
31     }
32     for (size_t i = 0; i < in1.dimensions.size(); i++) {
33         if (in1.dimensions[i] != in2.dimensions[i]) {
34             return false;
35         }
36     }
37     return true;
38 }
39 
SetShape(const Shape & in,Shape * out)40 bool SetShape(const Shape& in, Shape* out) {
41     if (in.type != out->type || in.dimensions.size() != out->dimensions.size()) {
42         return false;
43     }
44     out->dimensions = in.dimensions;
45     return true;
46 }
47 
getNumberOfElements(const Shape & shape)48 uint32_t getNumberOfElements(const Shape& shape) {
49     uint32_t count = 1;
50     for (size_t i = 0; i < shape.dimensions.size(); i++) {
51         count *= shape.dimensions[i];
52     }
53     return count;
54 }
55 
getNumberOfDimensions(const Shape & shape)56 uint32_t getNumberOfDimensions(const Shape& shape) {
57     return shape.dimensions.size();
58 }
59 
getSizeOfDimension(const Shape & shape,uint32_t dimensionIdx)60 uint32_t getSizeOfDimension(const Shape& shape, uint32_t dimensionIdx) {
61     if (dimensionIdx >= shape.dimensions.size()) {
62         // TODO, log the error
63         return 0;
64     }
65     return shape.dimensions[dimensionIdx];
66 }
67 
QuantizeMultiplierSmallerThanOne(double double_multiplier,int32_t * quantized_multiplier,int32_t * right_shift)68 bool QuantizeMultiplierSmallerThanOne(double double_multiplier,
69                                       int32_t* quantized_multiplier,
70                                       int32_t* right_shift) {
71     NN_OPS_CHECK(double_multiplier >= 0.);
72     NN_OPS_CHECK(double_multiplier < 1.);
73     if (double_multiplier == 0.) {
74         *quantized_multiplier = 0;
75         *right_shift = 0;
76         return true;
77     }
78     NN_OPS_CHECK(double_multiplier > 0.);
79     const double q = std::frexp(double_multiplier, right_shift);
80     *right_shift *= -1;
81     int64_t q_fixed = static_cast<int64_t>(std::round(q * (1ll << 31)));
82     NN_OPS_CHECK(q_fixed <= (1ll << 31));
83     if (q_fixed == (1ll << 31)) {
84         q_fixed /= 2;
85         --*right_shift;
86     }
87     NN_OPS_CHECK(*right_shift >= 0);
88     NN_OPS_CHECK(q_fixed <= std::numeric_limits<int32_t>::max());
89     *quantized_multiplier = static_cast<int32_t>(q_fixed);
90     return true;
91 }
92 
QuantizeMultiplierGreaterThanOne(double double_multiplier,int32_t * quantized_multiplier,int * left_shift)93 bool QuantizeMultiplierGreaterThanOne(double double_multiplier,
94                                       int32_t* quantized_multiplier,
95                                       int* left_shift) {
96     NN_OPS_CHECK(double_multiplier > 1.);
97     const double q = std::frexp(double_multiplier, left_shift);
98     int64_t q_fixed = static_cast<int64_t>(std::round(q * (1ll << 31)));
99     NN_OPS_CHECK(q_fixed <= (1ll << 31));
100     if (q_fixed == (1ll << 31)) {
101         q_fixed /= 2;
102         ++*left_shift;
103     }
104     NN_OPS_CHECK(*left_shift >= 0);
105     NN_OPS_CHECK(q_fixed <= std::numeric_limits<int32_t>::max());
106     *quantized_multiplier = static_cast<int32_t>(q_fixed);
107     return true;
108 }
109 
GetQuantizedConvolutionMultipler(const Shape & inputShape,const Shape & filterShape,const Shape & biasShape,const Shape & outputShape,float * multiplier)110 bool GetQuantizedConvolutionMultipler(const Shape& inputShape,
111                                       const Shape& filterShape,
112                                       const Shape& biasShape,
113                                       const Shape& outputShape,
114                                       float* multiplier) {
115     const float input_product_scale = inputShape.scale * filterShape.scale;
116     const float bias_scale = biasShape.scale;
117     const float output_scale = outputShape.scale;
118 
119     // The following conditions must be guaranteed by the training pipeline.
120     NN_OPS_CHECK(std::abs(input_product_scale - bias_scale) <=
121               1e-6 * std::min(input_product_scale, bias_scale));
122     NN_OPS_CHECK(input_product_scale >= 0);
123     NN_OPS_CHECK(input_product_scale < output_scale);
124     *multiplier = input_product_scale / output_scale;
125     return true;
126 }
127 
CalculateActivationRangeUint8(int32_t activation,const Shape & outputShape,int32_t * act_min,int32_t * act_max)128 void CalculateActivationRangeUint8(int32_t activation,
129                                    const Shape& outputShape,
130                                    int32_t* act_min,
131                                    int32_t* act_max) {
132     const int32_t qmin = std::numeric_limits<uint8_t>::min();
133     const int32_t qmax = std::numeric_limits<uint8_t>::max();
134 
135     const auto scale = outputShape.scale;
136     const auto zero_point = outputShape.offset;
137 
138     auto quantize = [scale, zero_point](float f) {
139         return zero_point + static_cast<int32_t>(std::round(f / scale));
140     };
141 
142     if (activation == kActivationRelu) {
143         *act_min = std::max(qmin, quantize(0.0));
144         *act_max = qmax;
145     } else if (activation == kActivationRelu6) {
146         *act_min = std::max(qmin, quantize(0.0));
147         *act_max = std::min(qmax, quantize(6.0));
148     } else if (activation == kActivationRelu1) {
149         *act_min = std::max(qmin, quantize(-1.0));
150         *act_max = std::min(qmax, quantize(1.0));
151     } else if (activation == kActivationNone){
152         *act_min = qmin;
153         *act_max = qmax;
154     } else {
155         LOG(ERROR) << "Unsupported fused activation function.";
156     }
157 }
158 
CalculateActivationRangeFloat(int32_t activation,float * activation_min,float * activation_max)159 void CalculateActivationRangeFloat(int32_t activation,
160                                    float* activation_min,
161                                    float* activation_max) {
162     if (activation == kActivationRelu) {
163         *activation_min = 0.f;
164         *activation_max = std::numeric_limits<float>::max();
165     } else if (activation == kActivationRelu6) {
166         *activation_min = 0.f;
167         *activation_max = 6.f;
168     } else if (activation == kActivationRelu1) {
169         *activation_min = -1.f;
170         *activation_max = 1.f;
171     } else if (activation == kActivationNone){
172         *activation_min = std::numeric_limits<float>::lowest();
173         *activation_max = std::numeric_limits<float>::max();
174     } else {
175         LOG(ERROR) << "Unsupported fused activation function.";
176     }
177 }
178 
CalculateInputRadius(int input_integer_bits,int input_left_shift)179 int32_t CalculateInputRadius(int input_integer_bits, int input_left_shift) {
180     const double max_input_rescaled = 1.0 * ((1 << input_integer_bits) - 1) *
181                                       (1ll << (31 - input_integer_bits)) /
182                                       (1ll << input_left_shift);
183     // Tighten bound using floor.  Suppose that we could use the exact value.
184     // After scaling the difference, the result would be at the maximum.  Thus we
185     // must ensure that our value has lower magnitude.
186     return static_cast<int32_t>(std::floor(max_input_rescaled));
187 }
188 
addMulPrepare(const Shape & in1,const Shape & in2,Shape * out)189 bool addMulPrepare(const Shape& in1, const Shape& in2, Shape* out) {
190     NN_OPS_CHECK(getNumberOfDimensions(in1) <= 4 && getNumberOfDimensions(in2) <= 4);
191     NN_OPS_CHECK(in1.type == in2.type);
192     if (SameShape(in1, in2)) {
193         return SetShape(in1, out);
194     } else {
195         // BroadcastAdd needed
196         uint32_t numberOfDims1 = getNumberOfDimensions(in1);
197         uint32_t numberOfDims2 = getNumberOfDimensions(in2);
198         uint32_t maxDims = std::max(numberOfDims1, numberOfDims2);
199         out->dimensions = std::vector<uint32_t>(maxDims);
200         for (uint32_t i = 1; i <= maxDims; i++) {
201             uint32_t dim1 = 1;
202             if (i <= numberOfDims1) {
203                 dim1 = getSizeOfDimension(in1, numberOfDims1 - i);
204             }
205             uint32_t dim2 = 1;
206             if (i <= numberOfDims2) {
207                 dim2 = getSizeOfDimension(in2, numberOfDims2 - i);
208             }
209             if (dim1 != dim2 && dim1 != 1 && dim2 != 1) {
210                 LOG(ERROR) << "Dimensions mismatch for BroadcastAdd";
211                 return false;
212             }
213             out->dimensions[maxDims - i] = std::max(dim1, dim2);
214         }
215     }
216     return true;
217 }
218 
floorPrepare(const Shape & input,Shape * output)219 bool floorPrepare(const Shape& input, Shape* output) {
220     return SetShape(input, output);
221 }
222 
dequantizePrepare(const Shape & input,Shape * output)223 bool dequantizePrepare(const Shape& input, Shape* output) {
224     if (input.type != OperandType::TENSOR_QUANT8_ASYMM ||
225             output->type != OperandType::TENSOR_FLOAT32) {
226         LOG(ERROR) << "bad input / output operand type.";
227         return false;
228     }
229     if (input.dimensions.size() != output->dimensions.size()) {
230         LOG(ERROR) << "input and output tensors don't have the same rank.";
231         return false;
232     }
233     output->dimensions = input.dimensions;
234     return true;
235 }
236 
convPrepare(const Shape & input,const Shape & filter,const Shape & bias,int32_t padding_left,int32_t padding_right,int32_t padding_top,int32_t padding_bottom,int32_t stride_width,int32_t stride_height,Shape * output)237 bool convPrepare(const Shape& input,
238                  const Shape& filter,
239                  const Shape& bias,
240                  int32_t padding_left, int32_t padding_right,
241                  int32_t padding_top, int32_t padding_bottom,
242                  int32_t stride_width, int32_t stride_height,
243                  Shape* output) {
244     NN_OPS_CHECK(input.type == filter.type);
245     if (input.type == OperandType::TENSOR_QUANT8_ASYMM) {
246         NN_OPS_CHECK(bias.type == OperandType::TENSOR_INT32);
247     } else {
248         NN_OPS_CHECK(input.type == bias.type);
249     }
250     NN_OPS_CHECK(getNumberOfDimensions(input) == 4);
251     NN_OPS_CHECK(getNumberOfDimensions(filter) == 4);
252     NN_OPS_CHECK(getNumberOfDimensions(bias) == 1);
253 
254     NN_OPS_CHECK(getSizeOfDimension(filter, 0) == getSizeOfDimension(bias, 0));
255     NN_OPS_CHECK(getSizeOfDimension(filter, 3) == getSizeOfDimension(input, 3));
256 
257     uint32_t channels_out = getSizeOfDimension(filter, 0);
258     uint32_t width        = getSizeOfDimension(input, 2);
259     uint32_t height       = getSizeOfDimension(input, 1);
260     uint32_t filterWidth  = getSizeOfDimension(filter, 2);
261     uint32_t filterHeight = getSizeOfDimension(filter, 1);
262     uint32_t batches      = getSizeOfDimension(input, 0);
263 
264     uint32_t outWidth = computeOutSize(width, filterWidth, stride_width,
265                                        padding_left, padding_right);
266     uint32_t outHeight = computeOutSize(height, filterHeight, stride_height,
267                                         padding_top, padding_bottom);
268 
269     output->type = input.type;
270     output->dimensions = {batches, outHeight, outWidth, channels_out};
271     return true;
272 }
273 
depthwiseConvPrepare(const Shape & input,const Shape & filter,const Shape & bias,int32_t padding_left,int32_t padding_right,int32_t padding_top,int32_t padding_bottom,int32_t stride_width,int32_t stride_height,Shape * output)274 bool depthwiseConvPrepare(const Shape& input,
275                           const Shape& filter,
276                           const Shape& bias,
277                           int32_t padding_left, int32_t padding_right,
278                           int32_t padding_top, int32_t padding_bottom,
279                           int32_t stride_width, int32_t stride_height,
280                           Shape* output) {
281     NN_OPS_CHECK(input.type == filter.type);
282     if (input.type == OperandType::TENSOR_QUANT8_ASYMM) {
283         NN_OPS_CHECK(bias.type == OperandType::TENSOR_INT32);
284     } else {
285         NN_OPS_CHECK(input.type == bias.type);
286     }
287     NN_OPS_CHECK(getNumberOfDimensions(input) == 4);
288     NN_OPS_CHECK(getNumberOfDimensions(filter) == 4);
289     NN_OPS_CHECK(getNumberOfDimensions(bias) == 1);
290 
291     NN_OPS_CHECK(getSizeOfDimension(filter, 3) == getSizeOfDimension(bias, 0));
292 
293     uint32_t channels_out = getSizeOfDimension(filter, 3);
294     uint32_t width        = getSizeOfDimension(input, 2);
295     uint32_t height       = getSizeOfDimension(input, 1);
296     uint32_t filterWidth  = getSizeOfDimension(filter, 2);
297     uint32_t filterHeight = getSizeOfDimension(filter, 1);
298     uint32_t batches      = getSizeOfDimension(input, 0);
299 
300     uint32_t outWidth = computeOutSize(width, filterWidth, stride_width,
301                                        padding_left, padding_right);
302     uint32_t outHeight = computeOutSize(height, filterHeight, stride_height,
303                                         padding_top, padding_bottom);
304 
305     output->type = input.type;
306     output->dimensions = {batches, outHeight, outWidth, channels_out};
307     return true;
308 }
309 
310 
genericPoolingPrepare(const Shape & input,int32_t padding_left,int32_t padding_right,int32_t padding_top,int32_t padding_bottom,int32_t stride_width,int32_t stride_height,int32_t filter_width,int32_t filter_height,Shape * output)311 bool genericPoolingPrepare(const Shape& input,
312                            int32_t padding_left, int32_t padding_right,
313                            int32_t padding_top, int32_t padding_bottom,
314                            int32_t stride_width, int32_t stride_height,
315                            int32_t filter_width, int32_t filter_height,
316                            Shape* output) {
317     NN_OPS_CHECK(getNumberOfDimensions(input) == 4);
318 
319     uint32_t batches      = getSizeOfDimension(input, 0);
320     uint32_t width        = getSizeOfDimension(input, 2);
321     uint32_t height       = getSizeOfDimension(input, 1);
322     uint32_t channels_out = getSizeOfDimension(input, 3);
323 
324     uint32_t outWidth = computeOutSize(width, filter_width, stride_width,
325                                        padding_left, padding_right);
326     uint32_t outHeight = computeOutSize(height, filter_height, stride_height,
327                                         padding_top, padding_bottom);
328 
329     output->type = input.type;
330     output->dimensions = {batches, outHeight, outWidth, channels_out};
331     return true;
332 }
333 
334 
genericActivationPrepare(const Shape & input,Shape * output)335 bool genericActivationPrepare(const Shape& input,
336                               Shape* output) {
337     NN_OPS_CHECK(getNumberOfDimensions(input) <= 4);
338     return SetShape(input, output);
339 }
340 
fullyConnectedPrepare(const Shape & input,const Shape & weights,const Shape & bias,Shape * output)341 bool fullyConnectedPrepare(const Shape& input,
342                            const Shape& weights,
343                            const Shape& bias,
344                            Shape* output) {
345     // Check all the parameters of tensor match within themselves and match the
346     // input configuration.
347     NN_OPS_CHECK(input.type == weights.type);
348     if (input.type == OperandType::TENSOR_QUANT8_ASYMM) {
349         NN_OPS_CHECK(bias.type == OperandType::TENSOR_INT32);
350     } else {
351         NN_OPS_CHECK(input.type == bias.type);
352     }
353     // The Tensorflow fully connected layer specification says that input should
354     // be of at least rank 2, so we check. Tflite doesn't check.
355     NN_OPS_CHECK(getNumberOfDimensions(input) >= 2);
356     NN_OPS_CHECK(getNumberOfDimensions(weights) == 2);
357     uint32_t input_n_elements = getNumberOfElements(input);
358     uint32_t num_units  = getSizeOfDimension(weights, 0);
359     uint32_t input_size = getSizeOfDimension(weights, 1);
360     uint32_t batch_size = input_n_elements / input_size;
361 
362     NN_OPS_CHECK(getSizeOfDimension(bias, 0) == num_units);
363     NN_OPS_CHECK(input_size * batch_size == input_n_elements);
364 
365     output->type = input.type;
366     output->dimensions = {batch_size, num_units};
367 
368     return true;
369 }
370 
concatenationPrepare(const std::vector<Shape> & inputShapes,int32_t axis,Shape * output)371 bool concatenationPrepare(const std::vector<Shape>& inputShapes,
372                           int32_t axis,
373                           Shape* output) {
374 
375     int num_inputs = inputShapes.size();
376     OperandType input_type = inputShapes[0].type;
377     uint32_t num_dimensions = getNumberOfDimensions(inputShapes[0]);
378 
379     NN_OPS_CHECK(axis >= 0);
380     NN_OPS_CHECK(axis < (int32_t)num_dimensions);
381 
382     int sumAxis = getSizeOfDimension(inputShapes[0], axis);
383     for (int i = 1; i < num_inputs; ++i) {
384         NN_OPS_CHECK(getNumberOfDimensions(inputShapes[i]) == num_dimensions);
385         NN_OPS_CHECK(inputShapes[i].type == inputShapes[0].type);
386         if (input_type == OperandType::TENSOR_QUANT8_ASYMM) {
387             NN_OPS_CHECK(inputShapes[0].offset == inputShapes[i].offset);
388             NN_OPS_CHECK(inputShapes[0].scale == inputShapes[i].scale);
389         }
390         for (int d = 0; d < (int32_t)num_dimensions; ++d) {
391             if (d == axis) {
392                 sumAxis += getSizeOfDimension(inputShapes[i], axis);
393             } else {
394                 NN_OPS_CHECK(getSizeOfDimension(inputShapes[0], d) ==
395                            getSizeOfDimension(inputShapes[i], d));
396             }
397         }
398     }
399 
400     output->type = input_type;
401     output->dimensions = inputShapes[0].dimensions;
402     output->dimensions[axis] = sumAxis;
403 
404     if (input_type == OperandType::TENSOR_QUANT8_ASYMM) {
405         NN_OPS_CHECK(inputShapes[0].offset == output->offset);
406         NN_OPS_CHECK(inputShapes[0].scale == output->scale);
407     }
408 
409     return true;
410 }
411 
412 
genericNormalizationPrepare(const Shape & input,Shape * output)413 bool genericNormalizationPrepare(const Shape& input, Shape* output) {
414     NN_OPS_CHECK(getNumberOfDimensions(input) == 4);
415     return SetShape(input, output);
416 }
417 
reshapePrepare(const Shape & input,const int32_t * targetDims,const int32_t targetDimsSize,Shape * output)418 bool reshapePrepare(const Shape& input,
419                     const int32_t* targetDims,
420                     const int32_t targetDimsSize,
421                     Shape* output) {
422     // Reshape allows one of the targetDims components to have the
423     // special -1 value, meaning it will be calculated automatically based on the
424     // input. Here we calculate what that dimension should be so that the number
425     // of output elements in the same as the number of input elements.
426     int32_t numInputElements = (int32_t) getNumberOfElements(input);
427 
428     std::vector<uint32_t> outDims(targetDimsSize);
429     int32_t numOutputElements = 1;
430     int32_t strechDim = -1;
431     for (int32_t i = 0; i < targetDimsSize; ++i) {
432         int32_t value = targetDims[i];
433         if (value == -1) {
434             NN_OPS_CHECK(strechDim == -1);
435             strechDim = i;
436         } else {
437             numOutputElements *= value;
438             outDims[i] = (uint32_t)value;
439         }
440     }
441     if (strechDim != -1) {
442         int32_t strechValue = numInputElements / numOutputElements;
443         outDims[strechDim] = (uint32_t) strechValue;
444         numOutputElements *= strechValue;
445     }
446 
447     NN_OPS_CHECK(numInputElements == numOutputElements);
448 
449     output->type = input.type;
450     output->dimensions = outDims;
451     output->offset = input.offset;
452     output->scale = input.scale;
453 
454     return true;
455 }
456 
resizeBilinearPrepare(const Shape & input,int32_t width,int32_t height,Shape * output)457 bool resizeBilinearPrepare(const Shape& input,
458                            int32_t width,
459                            int32_t height,
460                            Shape* output) {
461     NN_OPS_CHECK(getNumberOfDimensions(input) == 4);
462     uint32_t batches  = getSizeOfDimension(input, 0);
463     uint32_t channels = getSizeOfDimension(input, 3);
464 
465     output->type = input.type;
466     output->dimensions = {batches, (uint32_t)height, (uint32_t)width, channels};
467 
468     return true;
469 }
470 
depthToSpacePrepare(const Shape & input,int32_t blockSize,Shape * output)471 bool depthToSpacePrepare(const Shape& input,
472                          int32_t blockSize,
473                          Shape* output) {
474     NN_OPS_CHECK(getNumberOfDimensions(input) == 4);
475     NN_OPS_CHECK(blockSize > 0);
476 
477     uint32_t batches  = getSizeOfDimension(input, 0);
478     uint32_t height   = getSizeOfDimension(input, 1);
479     uint32_t width    = getSizeOfDimension(input, 2);
480     uint32_t channels = getSizeOfDimension(input, 3);
481 
482     NN_OPS_CHECK(channels % (blockSize * blockSize) == 0);
483     output->type = input.type;
484     output->dimensions = {batches,
485                           height * blockSize,
486                           width * blockSize,
487                           channels / (blockSize * blockSize)};
488     output->offset = input.offset;
489     output->scale = input.scale;
490 
491     return true;
492 }
493 
spaceToDepthPrepare(const Shape & input,int32_t blockSize,Shape * output)494 bool spaceToDepthPrepare(const Shape& input,
495                          int32_t blockSize,
496                          Shape* output) {
497     NN_OPS_CHECK(getNumberOfDimensions(input) == 4);
498     NN_OPS_CHECK(blockSize > 0);
499 
500     uint32_t batches  = getSizeOfDimension(input, 0);
501     uint32_t height   = getSizeOfDimension(input, 1);
502     uint32_t width    = getSizeOfDimension(input, 2);
503     uint32_t channels = getSizeOfDimension(input, 3);
504 
505     NN_OPS_CHECK(height % blockSize == 0);
506     NN_OPS_CHECK(width % blockSize == 0);
507 
508     output->type = input.type;
509     output->dimensions = {batches,
510                           height / blockSize,
511                           width / blockSize,
512                           channels * (blockSize * blockSize)};
513     output->offset = input.offset;
514     output->scale = input.scale;
515 
516     return true;
517 }
518 
embeddingLookupPrepare(const Shape & valueShape,const Shape & lookupShape,Shape * outputShape)519 bool embeddingLookupPrepare(const Shape &valueShape,
520                             const Shape &lookupShape,
521                             Shape *outputShape) {
522     NN_OPS_CHECK(getNumberOfDimensions(valueShape) >= 2);
523     NN_OPS_CHECK(getNumberOfDimensions(lookupShape) == 1);
524 
525     const uint32_t rows     = getSizeOfDimension(valueShape, 0);
526     const uint32_t columns  = getSizeOfDimension(valueShape, 1);
527 
528     const uint32_t lookups  = getSizeOfDimension(lookupShape, 0);
529 
530     outputShape->type = valueShape.type;
531     outputShape->dimensions = { lookups, columns };
532     for (uint32_t i = 2; i < getNumberOfDimensions(valueShape); i++) {
533         outputShape->dimensions.push_back(getSizeOfDimension(valueShape, i));
534     }
535     outputShape->offset = valueShape.offset;
536     outputShape->scale = valueShape.scale;
537 
538     return true;
539 }
540 
hashtableLookupPrepare(const Shape & lookupShape,const Shape & keyShape,const Shape & valueShape,Shape * outputShape,Shape * hitShape)541 bool hashtableLookupPrepare(const Shape &lookupShape,
542                             const Shape &keyShape,
543                             const Shape &valueShape,
544                             Shape *outputShape,
545                             Shape *hitShape) {
546     NN_OPS_CHECK(getNumberOfDimensions(lookupShape) == 1);
547     NN_OPS_CHECK(getNumberOfDimensions(keyShape) == 1);
548     NN_OPS_CHECK(getNumberOfDimensions(valueShape) >= 1);
549 
550     const uint32_t lookups  = getSizeOfDimension(lookupShape, 0);
551     const uint32_t keys     = getSizeOfDimension(keyShape, 0);
552     const uint32_t rows     = getSizeOfDimension(valueShape, 0);
553     outputShape->type = valueShape.type;
554     outputShape->dimensions = { lookups };
555     for (uint32_t i = 1; i < getNumberOfDimensions(valueShape); i++) {
556         outputShape->dimensions.push_back(getSizeOfDimension(valueShape, i));
557     }
558     outputShape->offset = valueShape.offset;
559     outputShape->scale = valueShape.scale;
560 
561     hitShape->type = OperandType::TENSOR_QUANT8_ASYMM;
562     hitShape->dimensions = { lookups };
563     hitShape->offset = 0;
564     hitShape->scale = 1.f;
565 
566     return true;
567 }
568 
padPrepare(const Shape & input,const int32_t * paddingsData,const Shape & paddingsShape,Shape * output)569 bool padPrepare(const Shape& input,
570                 const int32_t* paddingsData,
571                 const Shape& paddingsShape,
572                 Shape* output) {
573     // Currently only 4D tensors are supported.
574     uint32_t numInputDims = getNumberOfDimensions(input);
575     NN_OPS_CHECK(numInputDims == 4);
576 
577     // paddings need to be provided as a 2-D int32 tensor.
578     NN_OPS_CHECK(paddingsShape.type == OperandType::TENSOR_INT32);
579     NN_OPS_CHECK(getNumberOfDimensions(paddingsShape) == 2);
580     NN_OPS_CHECK(getSizeOfDimension(paddingsShape, 0) == numInputDims);
581     NN_OPS_CHECK(getSizeOfDimension(paddingsShape, 1) == 2);
582 
583     std::vector<uint32_t> outDims(numInputDims);
584     for (uint32_t i = 0; i < numInputDims; ++i) {
585         int32_t beforePadding = *paddingsData++;
586         int32_t afterPadding = *paddingsData++;
587         // Pad value has to be greater than equal to 0.
588         NN_OPS_CHECK(beforePadding >= 0 && afterPadding >= 0);
589         outDims[i] = beforePadding + getSizeOfDimension(input, i) + afterPadding;
590     }
591     output->type = input.type;
592     output->dimensions = outDims;
593     output->offset = input.offset;
594     output->scale = input.scale;
595 
596     return true;
597 }
598 
batchToSpacePrepare(const Shape & input,const int32_t * blockSizeData,const Shape & blockSizeShape,Shape * output)599 bool batchToSpacePrepare(const Shape& input,
600                          const int32_t* blockSizeData,
601                          const Shape& blockSizeShape,
602                          Shape* output) {
603     // Only 4D NHWC tensors are supported.
604     NN_OPS_CHECK(getNumberOfDimensions(input) == 4);
605 
606     // blockSize need to be provided as a 1-D int32 tensor.
607     NN_OPS_CHECK(blockSizeShape.type == OperandType::TENSOR_INT32);
608     NN_OPS_CHECK(getNumberOfDimensions(blockSizeShape) == 1);
609     // Only applies to spatial dimensions.
610     NN_OPS_CHECK(getSizeOfDimension(blockSizeShape, 0) == 2);
611 
612     uint32_t batches  = getSizeOfDimension(input, 0);
613     uint32_t height   = getSizeOfDimension(input, 1);
614     uint32_t width    = getSizeOfDimension(input, 2);
615     uint32_t channels = getSizeOfDimension(input, 3);
616 
617     NN_OPS_CHECK(batches % (blockSizeData[0] * blockSizeData[1]) == 0);
618     output->type = input.type;
619     output->dimensions = {batches / (blockSizeData[0] * blockSizeData[1]),
620                           height * blockSizeData[0],
621                           width * blockSizeData[1],
622                           channels};
623     output->offset = input.offset;
624     output->scale = input.scale;
625 
626     return true;
627 }
628 
spaceToBatchPrepare(const Shape & input,const int32_t * blockSizeData,const Shape & blockSizeShape,const int32_t * paddingsData,const Shape & paddingsShape,Shape * output)629 bool spaceToBatchPrepare(const Shape& input,
630                          const int32_t* blockSizeData,
631                          const Shape& blockSizeShape,
632                          const int32_t* paddingsData,
633                          const Shape& paddingsShape,
634                          Shape* output) {
635     // Only 4D NHWC tensors are supported.
636     NN_OPS_CHECK(getNumberOfDimensions(input) == 4);
637 
638     // blockSize need to be provided as a 1-D int32 tensor.
639     NN_OPS_CHECK(blockSizeShape.type == OperandType::TENSOR_INT32);
640     NN_OPS_CHECK(getNumberOfDimensions(blockSizeShape) == 1);
641     // Only applies to spatial dimensions.
642     NN_OPS_CHECK(getSizeOfDimension(blockSizeShape, 0) == 2);
643 
644     // paddings need to be provided as a 2-D int32 tensor.
645     NN_OPS_CHECK(paddingsShape.type == OperandType::TENSOR_INT32);
646     NN_OPS_CHECK(getNumberOfDimensions(paddingsShape) == 2);
647     NN_OPS_CHECK(getSizeOfDimension(paddingsShape, 0) == 2);
648     NN_OPS_CHECK(getSizeOfDimension(paddingsShape, 1) == 2);
649 
650     uint32_t batches  = getSizeOfDimension(input, 0);
651     uint32_t height   = getSizeOfDimension(input, 1);
652     uint32_t width    = getSizeOfDimension(input, 2);
653     uint32_t channels = getSizeOfDimension(input, 3);
654 
655     uint32_t paddedHeight = paddingsData[0] + height + paddingsData[1];
656     uint32_t paddedWidth = paddingsData[2] + width + paddingsData[3];
657 
658     NN_OPS_CHECK(paddedHeight % blockSizeData[0] == 0);
659     NN_OPS_CHECK(paddedWidth % blockSizeData[1] == 0);
660 
661     output->type = input.type;
662     output->dimensions = {batches * (blockSizeData[0] * blockSizeData[1]),
663                           paddedHeight / blockSizeData[0],
664                           paddedWidth / blockSizeData[1],
665                           channels};
666     output->offset = input.offset;
667     output->scale = input.scale;
668 
669     return true;
670 }
671 
squeezePrepare(const Shape & input,const int32_t * squeezeDims,const Shape & squeezeDimsShape,Shape * output)672 bool squeezePrepare(const Shape& input,
673                     const int32_t* squeezeDims,
674                     const Shape& squeezeDimsShape,
675                     Shape* output) {
676     int32_t numInputDims = static_cast<int32_t>(getNumberOfDimensions(input));
677 
678     // squeezeDims need to be provided as a 1-D int32 tensor.
679     NN_OPS_CHECK(squeezeDimsShape.type == OperandType::TENSOR_INT32);
680     NN_OPS_CHECK(getNumberOfDimensions(squeezeDimsShape) == 1);
681 
682     int32_t squeezeDimsSize = static_cast<int32_t>(getSizeOfDimension(squeezeDimsShape, 0));
683     std::vector<bool> shouldSqueeze(numInputDims, false);
684     int32_t numDimsSqueezed = 0;
685 
686     if (squeezeDimsSize == 0) {
687         // If squeezeDimsSize is 0, all dims with value 1 will be squeezed.
688         for (int32_t idx = 0; idx < numInputDims; ++idx) {
689             if (getSizeOfDimension(input, idx) == 1) {
690                 shouldSqueeze[idx] = true;
691                 ++numDimsSqueezed;
692             }
693         }
694     } else {
695         for (int32_t idx = 0; idx < squeezeDimsSize; ++idx) {
696             int32_t current = squeezeDims[idx] < 0 ? squeezeDims[idx] + numInputDims
697                                                : squeezeDims[idx];
698             NN_OPS_CHECK(current >= 0 && current < numInputDims &&
699                          getSizeOfDimension(input, current) == 1);
700             if (!shouldSqueeze[current]) ++numDimsSqueezed;
701             shouldSqueeze[current] = true;
702       }
703     }
704 
705     // Sets output dimensions.
706     std::vector<uint32_t> outDims(numInputDims - numDimsSqueezed);
707     for (int32_t inIdx = 0, outIdx = 0; inIdx < numInputDims; ++inIdx) {
708         if (!shouldSqueeze[inIdx]) {
709             outDims[outIdx++] = getSizeOfDimension(input, inIdx);
710         }
711     }
712 
713     output->type = input.type;
714     output->dimensions = outDims;
715     output->offset = input.offset;
716     output->scale = input.scale;
717 
718     return true;
719 }
720 
transposePrepare(const Shape & input,const int32_t * permData,const Shape & permShape,Shape * output)721 bool transposePrepare(const Shape& input,
722                       const int32_t* permData,
723                       const Shape& permShape,
724                       Shape* output) {
725     uint32_t numInputDims = getNumberOfDimensions(input);
726     // Transpose op only supports 1D-4D input arrays.
727     NN_OPS_CHECK(numInputDims <= 4);
728 
729     // perm need to be provided as a 1-D int32 tensor.
730     NN_OPS_CHECK(permShape.type == OperandType::TENSOR_INT32);
731     NN_OPS_CHECK(getNumberOfDimensions(permShape) == 1);
732     NN_OPS_CHECK(numInputDims == getSizeOfDimension(permShape, 0));
733 
734     std::vector<uint32_t> outDims(numInputDims);
735     for (int32_t idx = 0; idx < static_cast<int32_t>(numInputDims); ++idx) {
736         NN_OPS_CHECK(permData[idx] >= 0 && permData[idx] < static_cast<int32_t>(numInputDims));
737         outDims[idx] = getSizeOfDimension(input, permData[idx]);
738     }
739 
740     output->type = input.type;
741     output->dimensions = outDims;
742     output->offset = input.offset;
743     output->scale = input.scale;
744 
745     return true;
746 }
747 
meanPrepare(const Shape & input,const int32_t * axisData,const Shape & axisShape,bool keepDims,Shape * output)748 bool meanPrepare(const Shape& input,
749                  const int32_t* axisData,
750                  const Shape& axisShape,
751                  bool keepDims,
752                  Shape* output) {
753 
754     // perm need to be provided as a 1-D int32 tensor.
755     NN_OPS_CHECK(axisShape.type == OperandType::TENSOR_INT32);
756     NN_OPS_CHECK(getNumberOfDimensions(axisShape) == 1);
757 
758     int32_t numInputDims = static_cast<int32_t>(getNumberOfDimensions(input));
759     int32_t axisSize = static_cast<int32_t>(getSizeOfDimension(axisShape, 0));
760 
761     // Determines size of output tensor.
762     if (keepDims) {
763         std::vector<uint32_t> outDims(numInputDims);
764         for (int32_t idx = 0; idx < numInputDims; ++idx) {
765             bool isAxis = false;
766             for (int32_t axisIdx = 0; axisIdx < axisSize; ++axisIdx) {
767                 if (axisData[axisIdx] == idx || axisData[axisIdx] + numInputDims == idx) {
768                     isAxis = true;
769                     break;
770                 }
771             }
772             if (isAxis) {
773                 outDims[idx] = 1;
774             } else {
775                 outDims[idx] = getSizeOfDimension(input, idx);
776             }
777         }
778         output->dimensions = outDims;
779     } else {
780         // Calculates size of reducing axis.
781         int32_t numReduceAxis = axisSize;
782         for (int32_t i = 0; i < axisSize; ++i) {
783             int32_t current = axisData[i];
784             if (current < 0) {
785                 current += numInputDims;
786             }
787             NN_OPS_CHECK(current >= 0 && current < numInputDims);
788             for (int32_t j = 0; j < i; ++j) {
789                 int32_t previous = axisData[j];
790                 if (previous < 0) {
791                     previous += numInputDims;
792                 }
793                 if (current == previous) {
794                     --numReduceAxis;
795                     break;
796                 }
797             }
798         }
799         // Determines output dimensions.
800         std::vector<uint32_t> outDims(numInputDims - numReduceAxis);
801         int32_t numSkipAxis = 0;
802         for (int32_t idx = 0; idx < numInputDims; ++idx) {
803             bool isAxis = false;
804             for (int32_t axisIdx = 0; axisIdx < axisSize; ++axisIdx) {
805                 if (axisData[axisIdx] == idx || axisData[axisIdx] + numInputDims == idx) {
806                     ++numSkipAxis;
807                     isAxis = true;
808                     break;
809                 }
810             }
811             if (!isAxis) {
812                 outDims[idx - numSkipAxis] = getSizeOfDimension(input, idx);
813             }
814         }
815         output->dimensions = outDims;
816     }
817 
818     output->type = input.type;
819     output->offset = input.offset;
820     output->scale = input.scale;
821 
822     return true;
823 }
824 
stridedSlicePrepare(const Shape & input,const int32_t * beginData,const Shape & beginShape,const int32_t * endData,const Shape & endShape,const int32_t * stridesData,const Shape & stridesShape,int32_t beginMask,int32_t endMask,int32_t shrinkAxisMask,Shape * output)825 bool stridedSlicePrepare(const Shape& input,
826                          const int32_t* beginData, const Shape& beginShape,
827                          const int32_t* endData, const Shape& endShape,
828                          const int32_t* stridesData, const Shape& stridesShape,
829                          int32_t beginMask, int32_t endMask, int32_t shrinkAxisMask,
830                          Shape* output) {
831     uint32_t numInputDims = getNumberOfDimensions(input);
832     // StridedSlice op only supports 1D-4D input arrays.
833     NN_OPS_CHECK(numInputDims <= 4);
834 
835     NN_OPS_CHECK(getNumberOfDimensions(beginShape) == 1);
836     NN_OPS_CHECK(getNumberOfDimensions(endShape) == 1);
837     NN_OPS_CHECK(getNumberOfDimensions(stridesShape) == 1);
838 
839     NN_OPS_CHECK(getSizeOfDimension(beginShape, 0) == numInputDims);
840     NN_OPS_CHECK(getSizeOfDimension(endShape, 0) == numInputDims);
841     NN_OPS_CHECK(getSizeOfDimension(stridesShape, 0) == numInputDims);
842 
843     NN_OPS_CHECK(beginShape.type == OperandType::TENSOR_INT32);
844     NN_OPS_CHECK(endShape.type == OperandType::TENSOR_INT32);
845     NN_OPS_CHECK(stridesShape.type == OperandType::TENSOR_INT32);
846 
847     // Determine size of output tensor and map indices
848     std::vector<uint32_t> outDims;
849     for (int32_t idx = 0; idx < static_cast<int32_t>(numInputDims); idx++) {
850       int32_t dim = static_cast<int32_t>(getSizeOfDimension(input, idx));
851       int32_t stride = stridesData[idx];
852       // stride value has to be non-zero
853       NN_OPS_CHECK(stride != 0);
854       bool positiveStride = stride > 0;
855 
856       int32_t begin = beginMask & (1 << idx)
857               ? positiveStride ? 0 : dim - 1
858               : ClampedIndex(beginData[idx], dim, positiveStride);
859       int32_t end = endMask & (1 << idx)
860               ? positiveStride ? dim : -1
861               : ClampedIndex(endData[idx], dim, positiveStride);
862 
863       // This is valid for both positive and negative strides
864       int32_t outDim = ceil((end - begin) / static_cast<float>(stride));
865       outDim = outDim < 0 ? 0 : static_cast<uint32_t>(outDim);
866       if (!(shrinkAxisMask & (1 << idx))) {
867           outDims.push_back(outDim);
868       } else {
869           if (outDim != 1) {
870               LOG(ERROR) << "Outdim " << idx << " is " << outDim << ", expected 1";
871               NN_OPS_CHECK(outDim == 1);
872           }
873       }
874     }
875 
876     output->type = input.type;
877     output->dimensions = outDims;
878     output->offset = input.offset;
879     output->scale = input.scale;
880 
881     return true;
882 }
883 } // namespace nn
884 } // namespace android
885