• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
152         *act_min = qmin;
153         *act_max = qmax;
154     }
155 }
156 
CalculateInputRadius(int input_integer_bits,int input_left_shift)157 int32_t CalculateInputRadius(int input_integer_bits, int input_left_shift) {
158     const double max_input_rescaled = 1.0 * ((1 << input_integer_bits) - 1) *
159                                       (1ll << (31 - input_integer_bits)) /
160                                       (1ll << input_left_shift);
161     // Tighten bound using floor.  Suppose that we could use the exact value.
162     // After scaling the difference, the result would be at the maximum.  Thus we
163     // must ensure that our value has lower magnitude.
164     return static_cast<int32_t>(std::floor(max_input_rescaled));
165 }
166 
addMulPrepare(const Shape & in1,const Shape & in2,Shape * out)167 bool addMulPrepare(const Shape& in1, const Shape& in2, Shape* out) {
168     NN_OPS_CHECK(getNumberOfDimensions(in1) <= 4 && getNumberOfDimensions(in2) <= 4);
169     NN_OPS_CHECK(in1.type == in2.type);
170     if (SameShape(in1, in2)) {
171         return SetShape(in1, out);
172     } else {
173         // BroadcastAdd needed
174         uint32_t numberOfDims1 = getNumberOfDimensions(in1);
175         uint32_t numberOfDims2 = getNumberOfDimensions(in2);
176         uint32_t maxDims = std::max(numberOfDims1, numberOfDims2);
177         out->dimensions = std::vector<uint32_t>(maxDims);
178         for (uint32_t i = 1; i <= maxDims; i++) {
179             uint32_t dim1 = 1;
180             if (i <= numberOfDims1) {
181                 dim1 = getSizeOfDimension(in1, numberOfDims1 - i);
182             }
183             uint32_t dim2 = 1;
184             if (i <= numberOfDims2) {
185                 dim2 = getSizeOfDimension(in2, numberOfDims2 - i);
186             }
187             if (dim1 != dim2 && dim1 != 1 && dim2 != 1) {
188                 LOG(ERROR) << "Dimensions mismatch for BroadcastAdd";
189                 return false;
190             }
191             out->dimensions[maxDims - i] = std::max(dim1, dim2);
192         }
193     }
194     return true;
195 }
196 
floorPrepare(const Shape & input,Shape * output)197 bool floorPrepare(const Shape& input, Shape* output) {
198     return SetShape(input, output);
199 }
200 
dequantizePrepare(const Shape & input,Shape * output)201 bool dequantizePrepare(const Shape& input, Shape* output) {
202     if (input.type != OperandType::TENSOR_QUANT8_ASYMM ||
203             output->type != OperandType::TENSOR_FLOAT32) {
204         LOG(ERROR) << "bad input / output operand type.";
205         return false;
206     }
207     if (input.dimensions.size() != output->dimensions.size()) {
208         LOG(ERROR) << "input and output tensors don't have the same rank.";
209         return false;
210     }
211     output->dimensions = input.dimensions;
212     return true;
213 }
214 
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)215 bool convPrepare(const Shape& input,
216                  const Shape& filter,
217                  const Shape& bias,
218                  int32_t padding_left, int32_t padding_right,
219                  int32_t padding_top, int32_t padding_bottom,
220                  int32_t stride_width, int32_t stride_height,
221                  Shape* output) {
222     NN_OPS_CHECK(input.type == filter.type);
223     if (input.type == OperandType::TENSOR_QUANT8_ASYMM) {
224         NN_OPS_CHECK(bias.type == OperandType::TENSOR_INT32);
225     } else {
226         NN_OPS_CHECK(input.type == bias.type);
227     }
228     NN_OPS_CHECK(getNumberOfDimensions(input) == 4);
229     NN_OPS_CHECK(getNumberOfDimensions(filter) == 4);
230     NN_OPS_CHECK(getNumberOfDimensions(bias) == 1);
231 
232     NN_OPS_CHECK(getSizeOfDimension(filter, 0) == getSizeOfDimension(bias, 0));
233     NN_OPS_CHECK(getSizeOfDimension(filter, 3) == getSizeOfDimension(input, 3));
234 
235     uint32_t channels_out = getSizeOfDimension(filter, 0);
236     uint32_t width        = getSizeOfDimension(input, 2);
237     uint32_t height       = getSizeOfDimension(input, 1);
238     uint32_t filterWidth  = getSizeOfDimension(filter, 2);
239     uint32_t filterHeight = getSizeOfDimension(filter, 1);
240     uint32_t batches      = getSizeOfDimension(input, 0);
241 
242     uint32_t outWidth = computeOutSize(width, filterWidth, stride_width,
243                                        padding_left, padding_right);
244     uint32_t outHeight = computeOutSize(height, filterHeight, stride_height,
245                                         padding_top, padding_bottom);
246 
247     output->type = input.type;
248     output->dimensions = {batches, outHeight, outWidth, channels_out};
249     return true;
250 }
251 
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)252 bool depthwiseConvPrepare(const Shape& input,
253                           const Shape& filter,
254                           const Shape& bias,
255                           int32_t padding_left, int32_t padding_right,
256                           int32_t padding_top, int32_t padding_bottom,
257                           int32_t stride_width, int32_t stride_height,
258                           Shape* output) {
259     NN_OPS_CHECK(input.type == filter.type);
260     if (input.type == OperandType::TENSOR_QUANT8_ASYMM) {
261         NN_OPS_CHECK(bias.type == OperandType::TENSOR_INT32);
262     } else {
263         NN_OPS_CHECK(input.type == bias.type);
264     }
265     NN_OPS_CHECK(getNumberOfDimensions(input) == 4);
266     NN_OPS_CHECK(getNumberOfDimensions(filter) == 4);
267     NN_OPS_CHECK(getNumberOfDimensions(bias) == 1);
268 
269     NN_OPS_CHECK(getSizeOfDimension(filter, 3) == getSizeOfDimension(bias, 0));
270 
271     uint32_t channels_out = getSizeOfDimension(filter, 3);
272     uint32_t width        = getSizeOfDimension(input, 2);
273     uint32_t height       = getSizeOfDimension(input, 1);
274     uint32_t filterWidth  = getSizeOfDimension(filter, 2);
275     uint32_t filterHeight = getSizeOfDimension(filter, 1);
276     uint32_t batches      = getSizeOfDimension(input, 0);
277 
278     uint32_t outWidth = computeOutSize(width, filterWidth, stride_width,
279                                        padding_left, padding_right);
280     uint32_t outHeight = computeOutSize(height, filterHeight, stride_height,
281                                         padding_top, padding_bottom);
282 
283     output->type = input.type;
284     output->dimensions = {batches, outHeight, outWidth, channels_out};
285     return true;
286 }
287 
288 
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)289 bool genericPoolingPrepare(const Shape& input,
290                            int32_t padding_left, int32_t padding_right,
291                            int32_t padding_top, int32_t padding_bottom,
292                            int32_t stride_width, int32_t stride_height,
293                            int32_t filter_width, int32_t filter_height,
294                            Shape* output) {
295     NN_OPS_CHECK(getNumberOfDimensions(input) == 4);
296 
297     uint32_t batches      = getSizeOfDimension(input, 0);
298     uint32_t width        = getSizeOfDimension(input, 2);
299     uint32_t height       = getSizeOfDimension(input, 1);
300     uint32_t channels_out = getSizeOfDimension(input, 3);
301 
302     uint32_t outWidth = computeOutSize(width, filter_width, stride_width,
303                                        padding_left, padding_right);
304     uint32_t outHeight = computeOutSize(height, filter_height, stride_height,
305                                         padding_top, padding_bottom);
306 
307     output->type = input.type;
308     output->dimensions = {batches, outHeight, outWidth, channels_out};
309     return true;
310 }
311 
312 
genericActivationPrepare(const Shape & input,Shape * output)313 bool genericActivationPrepare(const Shape& input,
314                               Shape* output) {
315     NN_OPS_CHECK(getNumberOfDimensions(input) <= 4);
316     return SetShape(input, output);
317 }
318 
fullyConnectedPrepare(const Shape & input,const Shape & weights,const Shape & bias,Shape * output)319 bool fullyConnectedPrepare(const Shape& input,
320                            const Shape& weights,
321                            const Shape& bias,
322                            Shape* output) {
323     // Check all the parameters of tensor match within themselves and match the
324     // input configuration.
325     NN_OPS_CHECK(input.type == weights.type);
326     if (input.type == OperandType::TENSOR_QUANT8_ASYMM) {
327         NN_OPS_CHECK(bias.type == OperandType::TENSOR_INT32);
328     } else {
329         NN_OPS_CHECK(input.type == bias.type);
330     }
331     NN_OPS_CHECK(getNumberOfDimensions(input) >= 2);
332     uint32_t input_size = getNumberOfElements(input);
333     uint32_t num_units  = getSizeOfDimension(weights, 0);
334     uint32_t batch_size = input_size / getSizeOfDimension(weights, 1);
335 
336     NN_OPS_CHECK(getSizeOfDimension(bias, 0) == num_units);
337     NN_OPS_CHECK(getSizeOfDimension(weights, 1) * batch_size == input_size);
338     NN_OPS_CHECK(getNumberOfDimensions(weights) == 2);
339 
340     output->type = input.type;
341     output->dimensions = {batch_size, num_units};
342 
343     return true;
344 }
345 
concatenationPrepare(const std::vector<Shape> & inputShapes,int32_t axis,Shape * output)346 bool concatenationPrepare(const std::vector<Shape>& inputShapes,
347                           int32_t axis,
348                           Shape* output) {
349 
350     int num_inputs = inputShapes.size();
351     OperandType input_type = inputShapes[0].type;
352     uint32_t num_dimensions = getNumberOfDimensions(inputShapes[0]);
353 
354     NN_OPS_CHECK(axis >= 0);
355     NN_OPS_CHECK(axis < (int32_t)num_dimensions);
356 
357     int sum_axis = getSizeOfDimension(inputShapes[0], axis);
358     for (int i = 1; i < num_inputs; ++i) {
359         NN_OPS_CHECK(getNumberOfDimensions(inputShapes[i]) == num_dimensions);
360         NN_OPS_CHECK(inputShapes[i].type == inputShapes[0].type);
361         if (input_type == OperandType::TENSOR_QUANT8_ASYMM) {
362             NN_OPS_CHECK(inputShapes[0].offset == inputShapes[i].offset);
363             NN_OPS_CHECK(inputShapes[0].scale == inputShapes[i].scale);
364         }
365         for (int d = 0; d < (int32_t)num_dimensions; ++d) {
366             if (d == axis) {
367                 sum_axis += getSizeOfDimension(inputShapes[i], axis);
368             } else {
369                 NN_OPS_CHECK(getSizeOfDimension(inputShapes[0], d) ==
370                            getSizeOfDimension(inputShapes[i], d));
371             }
372         }
373     }
374 
375     output->type = input_type;
376     output->dimensions = inputShapes[0].dimensions;
377     output->dimensions[axis] = sum_axis;
378 
379     if (input_type == OperandType::TENSOR_QUANT8_ASYMM) {
380         NN_OPS_CHECK(inputShapes[0].offset == output->offset);
381         NN_OPS_CHECK(inputShapes[0].scale == output->scale);
382     }
383 
384     return true;
385 }
386 
387 
genericNormalizationPrepare(const Shape & input,Shape * output)388 bool genericNormalizationPrepare(const Shape& input, Shape* output) {
389     NN_OPS_CHECK(getNumberOfDimensions(input) == 4);
390     return SetShape(input, output);
391 }
392 
reshapePrepare(const Shape & input,const int32_t * targetDims,const int32_t targetDimsSize,Shape * output)393 bool reshapePrepare(const Shape& input,
394                     const int32_t* targetDims,
395                     const int32_t targetDimsSize,
396                     Shape* output) {
397     // Reshape allows one of the targetDims components to have the
398     // special -1 value, meaning it will be calculated automatically based on the
399     // input. Here we calculate what that dimension should be so that the number
400     // of output elements in the same as the number of input elements.
401     int32_t numInputElements = (int32_t) getNumberOfElements(input);
402 
403     std::vector<uint32_t> outDims(targetDimsSize);
404     int32_t numOutputElements = 1;
405     int32_t strechDim = -1;
406     for (int32_t i = 0; i < targetDimsSize; ++i) {
407         int32_t value = targetDims[i];
408         if (value == -1) {
409             NN_OPS_CHECK(strechDim == -1);
410             strechDim = i;
411         } else {
412             numOutputElements *= value;
413             outDims[i] = (uint32_t)value;
414         }
415     }
416     if (strechDim != -1) {
417         int32_t strechValue = numInputElements / numOutputElements;
418         outDims[strechDim] = (uint32_t) strechValue;
419         numOutputElements *= strechValue;
420     }
421 
422     NN_OPS_CHECK(numInputElements == numOutputElements);
423 
424     output->type = input.type;
425     output->dimensions = outDims;
426     output->offset = input.offset;
427     output->scale = input.scale;
428 
429     return true;
430 }
431 
resizeBilinearPrepare(const Shape & input,int32_t width,int32_t height,Shape * output)432 bool resizeBilinearPrepare(const Shape& input,
433                            int32_t width,
434                            int32_t height,
435                            Shape* output) {
436     NN_OPS_CHECK(getNumberOfDimensions(input) == 4);
437     uint32_t batches  = getSizeOfDimension(input, 0);
438     uint32_t channels = getSizeOfDimension(input, 3);
439 
440     output->type = input.type;
441     output->dimensions = {batches, (uint32_t)height, (uint32_t)width, channels};
442 
443     return true;
444 }
445 
depthToSpacePrepare(const Shape & input,int32_t blockSize,Shape * output)446 bool depthToSpacePrepare(const Shape& input,
447                          int32_t blockSize,
448                          Shape* output) {
449     NN_OPS_CHECK(getNumberOfDimensions(input) == 4);
450     NN_OPS_CHECK(blockSize > 0);
451 
452     uint32_t batches  = getSizeOfDimension(input, 0);
453     uint32_t height   = getSizeOfDimension(input, 1);
454     uint32_t width    = getSizeOfDimension(input, 2);
455     uint32_t channels = getSizeOfDimension(input, 3);
456 
457     NN_OPS_CHECK(channels % (blockSize * blockSize) == 0);
458     output->type = input.type;
459     output->dimensions = {batches,
460                           height * blockSize,
461                           width * blockSize,
462                           channels / (blockSize * blockSize)};
463     output->offset = input.offset;
464     output->scale = input.scale;
465 
466     return true;
467 }
468 
spaceToDepthPrepare(const Shape & input,int32_t blockSize,Shape * output)469 bool spaceToDepthPrepare(const Shape& input,
470                          int32_t blockSize,
471                          Shape* output) {
472     NN_OPS_CHECK(getNumberOfDimensions(input) == 4);
473     NN_OPS_CHECK(blockSize > 0);
474 
475     uint32_t batches  = getSizeOfDimension(input, 0);
476     uint32_t height   = getSizeOfDimension(input, 1);
477     uint32_t width    = getSizeOfDimension(input, 2);
478     uint32_t channels = getSizeOfDimension(input, 3);
479 
480     NN_OPS_CHECK(height % blockSize == 0);
481     NN_OPS_CHECK(width % blockSize == 0);
482 
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 
embeddingLookupPrepare(const Shape & valueShape,const Shape & lookupShape,Shape * outputShape)494 bool embeddingLookupPrepare(const Shape &valueShape,
495                             const Shape &lookupShape,
496                             Shape *outputShape) {
497     NN_OPS_CHECK(getNumberOfDimensions(valueShape) >= 2);
498     NN_OPS_CHECK(getNumberOfDimensions(lookupShape) == 1);
499 
500     const uint32_t rows     = getSizeOfDimension(valueShape, 0);
501     const uint32_t columns  = getSizeOfDimension(valueShape, 1);
502 
503     const uint32_t lookups  = getSizeOfDimension(lookupShape, 0);
504 
505     outputShape->type = valueShape.type;
506     outputShape->dimensions = { lookups, columns };
507     for (uint32_t i = 2; i < getNumberOfDimensions(valueShape); i++) {
508         outputShape->dimensions.push_back(getSizeOfDimension(valueShape, i));
509     }
510     outputShape->offset = valueShape.offset;
511     outputShape->scale = valueShape.scale;
512 
513     return true;
514 }
515 
hashtableLookupPrepare(const Shape & lookupShape,const Shape & keyShape,const Shape & valueShape,Shape * outputShape,Shape * hitShape)516 bool hashtableLookupPrepare(const Shape &lookupShape,
517                             const Shape &keyShape,
518                             const Shape &valueShape,
519                             Shape *outputShape,
520                             Shape *hitShape) {
521     NN_OPS_CHECK(getNumberOfDimensions(lookupShape) == 1);
522     NN_OPS_CHECK(getNumberOfDimensions(keyShape) == 1);
523     NN_OPS_CHECK(getNumberOfDimensions(valueShape) >= 1);
524 
525     const uint32_t lookups  = getSizeOfDimension(lookupShape, 0);
526     const uint32_t keys     = getSizeOfDimension(keyShape, 0);
527     const uint32_t rows     = getSizeOfDimension(valueShape, 0);
528     outputShape->type = valueShape.type;
529     outputShape->dimensions = { lookups };
530     for (uint32_t i = 1; i < getNumberOfDimensions(valueShape); i++) {
531         outputShape->dimensions.push_back(getSizeOfDimension(valueShape, i));
532     }
533     outputShape->offset = valueShape.offset;
534     outputShape->scale = valueShape.scale;
535 
536     hitShape->type = OperandType::TENSOR_QUANT8_ASYMM;
537     hitShape->dimensions = { lookups };
538     hitShape->offset = 0;
539     hitShape->scale = 1.f;
540 
541     return true;
542 }
543 
544 } // namespace nn
545 } // namespace android
546