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