1 /*
2 * Copyright (C) 2019 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 "Operations"
18
19 #include "IndexedShapeWrapper.h"
20 #include "OperationResolver.h"
21 #include "OperationsUtils.h"
22
23 namespace android {
24 namespace nn {
25 namespace dequantize {
26
27 constexpr uint32_t kNumInputs = 1;
28 constexpr uint32_t kInputTensor = 0;
29
30 constexpr uint32_t kNumOutputs = 1;
31 constexpr uint32_t kOutputTensor = 0;
32
33 namespace {
34
35 template <typename InputType, typename OutputType>
compute(const InputType * inputData,const Shape & inputShape,OutputType * outputData)36 bool compute(const InputType* inputData, const Shape& inputShape, OutputType* outputData) {
37 const int numElements = getNumberOfElements(inputShape);
38 const int32_t zeroPoint = inputShape.offset;
39 const float scale = inputShape.scale;
40 for (int i = 0; i < numElements; ++i) {
41 const int32_t value = inputData[i];
42 outputData[i] = static_cast<OutputType>(scale * (value - zeroPoint));
43 }
44 return true;
45 }
46
47 template <typename OutputType>
computePerChannel(const int8_t * inputData,const Shape & inputShape,OutputType * outputData)48 bool computePerChannel(const int8_t* inputData, const Shape& inputShape, OutputType* outputData) {
49 // First we calculate a stride which is the number of elements we need to
50 // skip to change an index along a dimension with different quantization
51 // scales.
52 const int channelDim =
53 std::get<Operand::SymmPerChannelQuantParams>(inputShape.extraParams).channelDim;
54 int stride = 1;
55 for (int i = getNumberOfDimensions(inputShape) - 1; i > channelDim; --i) {
56 stride *= getSizeOfDimension(inputShape, i);
57 }
58
59 const int numElements = getNumberOfElements(inputShape);
60 const int32_t zeroPoint = inputShape.offset;
61
62 for (int i = 0; i < numElements; ++i) {
63 // To get current index along the quantized dimension we calculate how
64 // many even |strides| we looped through and take this number modulo the
65 // size of the dimension (so that we don't have an overflow if the
66 // channelDim is not 0).
67 const int scaleIndex = (i / stride) % getSizeOfDimension(inputShape, channelDim);
68 const float scale = std::get<Operand::SymmPerChannelQuantParams>(inputShape.extraParams)
69 .scales[scaleIndex];
70 const int32_t value = inputData[i];
71 outputData[i] = static_cast<OutputType>(scale * (value - zeroPoint));
72 }
73 return true;
74 }
75
76 } // namespace
77
validate(const IOperationValidationContext * context)78 Result<Version> validate(const IOperationValidationContext* context) {
79 NN_RET_CHECK_EQ(context->getNumInputs(), kNumInputs);
80 NN_RET_CHECK_EQ(context->getNumOutputs(), kNumOutputs);
81
82 const OperandType inputType = context->getInputType(kInputTensor);
83 const OperandType outputType = context->getOutputType(kOutputTensor);
84
85 const Shape& input = context->getInputShape(kInputTensor);
86 if (hasKnownRank(input)) {
87 NN_RET_CHECK_LE(getNumberOfDimensions(input), 4);
88 }
89
90 if (inputType == OperandType::TENSOR_QUANT8_ASYMM &&
91 outputType == OperandType::TENSOR_FLOAT32) {
92 return Version::ANDROID_OC_MR1;
93 }
94
95 NN_RET_CHECK(inputType == OperandType::TENSOR_QUANT8_ASYMM ||
96 inputType == OperandType::TENSOR_QUANT8_ASYMM_SIGNED ||
97 inputType == OperandType::TENSOR_QUANT8_SYMM ||
98 inputType == OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL)
99 << "Unsupported input operand type for DEQUANTIZE op: " << inputType;
100 NN_RET_CHECK(outputType == OperandType::TENSOR_FLOAT16 ||
101 outputType == OperandType::TENSOR_FLOAT32)
102 << "Unsupported output operand type for DEQUANTIZE op: " << outputType;
103 return Version::ANDROID_Q;
104 }
105
prepare(IOperationExecutionContext * context)106 bool prepare(IOperationExecutionContext* context) {
107 const Shape& input = context->getInputShape(kInputTensor);
108 NN_RET_CHECK_LE(getNumberOfDimensions(input), 4);
109 Shape output = context->getOutputShape(kOutputTensor);
110 output.dimensions = input.dimensions;
111 return context->setOutputShape(kOutputTensor, output);
112 }
113
execute(IOperationExecutionContext * context)114 bool execute(IOperationExecutionContext* context) {
115 // Bypass execution in the case of zero-sized input.
116 if (getNumberOfElements(context->getOutputShape(kOutputTensor)) == 0) return true;
117
118 const OperandType inputType = context->getInputType(kInputTensor);
119 const OperandType outputType = context->getOutputType(kOutputTensor);
120
121 const Shape& inputShape = context->getInputShape(kInputTensor);
122 if (inputType == OperandType::TENSOR_QUANT8_ASYMM) {
123 const uint8_t* inputBuffer = context->getInputBuffer<uint8_t>(kInputTensor);
124 if (outputType == OperandType::TENSOR_FLOAT16) {
125 return compute(inputBuffer, inputShape,
126 context->getOutputBuffer<_Float16>(kOutputTensor));
127 } else if (outputType == OperandType::TENSOR_FLOAT32) {
128 return compute(inputBuffer, inputShape, context->getOutputBuffer<float>(kOutputTensor));
129 }
130 } else if (inputType == OperandType::TENSOR_QUANT8_SYMM) {
131 const int8_t* inputBuffer = context->getInputBuffer<int8_t>(kInputTensor);
132 if (outputType == OperandType::TENSOR_FLOAT16) {
133 return compute(inputBuffer, inputShape,
134 context->getOutputBuffer<_Float16>(kOutputTensor));
135 } else if (outputType == OperandType::TENSOR_FLOAT32) {
136 return compute(inputBuffer, inputShape, context->getOutputBuffer<float>(kOutputTensor));
137 }
138 } else if (inputType == OperandType::TENSOR_QUANT8_ASYMM_SIGNED) {
139 const int8_t* inputBuffer = context->getInputBuffer<int8_t>(kInputTensor);
140 if (outputType == OperandType::TENSOR_FLOAT16) {
141 return compute(inputBuffer, inputShape,
142 context->getOutputBuffer<_Float16>(kOutputTensor));
143 } else if (outputType == OperandType::TENSOR_FLOAT32) {
144 return compute(inputBuffer, inputShape, context->getOutputBuffer<float>(kOutputTensor));
145 }
146 } else if (inputType == OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL) {
147 const int8_t* inputBuffer = context->getInputBuffer<int8_t>(kInputTensor);
148 if (outputType == OperandType::TENSOR_FLOAT16) {
149 return computePerChannel(inputBuffer, inputShape,
150 context->getOutputBuffer<_Float16>(kOutputTensor));
151 } else if (outputType == OperandType::TENSOR_FLOAT32) {
152 return computePerChannel(inputBuffer, inputShape,
153 context->getOutputBuffer<float>(kOutputTensor));
154 }
155 }
156 NN_RET_CHECK_FAIL() << "Unsupported tensor types combination for dequantize op. (input type: "
157 << inputType << " output type: " << outputType << ")";
158 }
159
160 } // namespace dequantize
161
162 NN_REGISTER_OPERATION(DEQUANTIZE, "DEQUANTIZE", dequantize::validate, dequantize::prepare,
163 dequantize::execute, .allowZeroSizedInput = true);
164
165 } // namespace nn
166 } // namespace android
167