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