Home | History | Annotate | Download | only in common
      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 
     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 
     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 
     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 
     56 uint32_t getNumberOfDimensions(const Shape& shape) {
     57     return shape.dimensions.size();
     58 }
     59 
     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 
     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 
     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 
    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 
    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 
    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 
    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 
    197 bool floorPrepare(const Shape& input, Shape* output) {
    198     return SetShape(input, output);
    199 }
    200 
    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 
    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 
    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 
    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 
    313 bool genericActivationPrepare(const Shape& input,
    314                               Shape* output) {
    315     NN_OPS_CHECK(getNumberOfDimensions(input) <= 4);
    316     return SetShape(input, output);
    317 }
    318 
    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 
    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 
    388 bool genericNormalizationPrepare(const Shape& input, Shape* output) {
    389     NN_OPS_CHECK(getNumberOfDimensions(input) == 4);
    390     return SetShape(input, output);
    391 }
    392 
    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 
    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 
    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 
    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 
    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 
    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