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 <tensorflow/lite/kernels/internal/optimized/optimized_ops.h>
20 #include <tensorflow/lite/kernels/internal/reference/integer_ops/l2normalization.h>
21
22 #include <algorithm>
23 #include <vector>
24
25 #include "CpuOperationUtils.h"
26 #include "HalInterfaces.h"
27 #include "OperationResolver.h"
28 #include "Tracing.h"
29
30 namespace android {
31 namespace nn {
32 namespace l2_norm {
33
34 constexpr char kOperationName[] = "L2_NORMALIZATION";
35
36 constexpr uint32_t kNumInputs = 2;
37 constexpr uint32_t kInputTensor = 0;
38 constexpr uint32_t kAxisScalar = 1;
39
40 constexpr uint32_t kNumOutputs = 1;
41 constexpr uint32_t kOutputTensor = 0;
42
43 namespace {
44
45 using namespace hal;
46
l2normFloat32Impl(const float * inputData,const Shape & inputShape,int32_t axis,float * outputData,const Shape & outputShape)47 inline bool l2normFloat32Impl(const float* inputData, const Shape& inputShape, int32_t axis,
48 float* outputData, const Shape& outputShape) {
49 NNTRACE_TRANS("l2normFloat32");
50 constexpr float kEpsilon = 1e-6f;
51 const uint32_t outerSize = getNumberOfElements(inputShape, 0, axis);
52 const uint32_t axisSize = getSizeOfDimension(inputShape, axis);
53 const uint32_t innerSize =
54 getNumberOfElements(inputShape, axis + 1, getNumberOfDimensions(inputShape));
55 for (uint32_t outer = 0; outer < outerSize; ++outer) {
56 const float* inputBeg = inputData + outer * axisSize * innerSize;
57 const float* inputEnd = inputBeg + axisSize * innerSize;
58 float* outputBeg = outputData + outer * axisSize * innerSize;
59 for (uint32_t inner = 0; inner < innerSize; ++inner, ++inputBeg, ++inputEnd, ++outputBeg) {
60 float sum = 0.0f;
61 for (const float* p = inputBeg; p < inputEnd; p += innerSize) {
62 float val = *p;
63 sum += val * val;
64 }
65 float l2_norm = std::max(std::sqrt(sum), kEpsilon);
66 float* pOut = outputBeg;
67 for (const float* p = inputBeg; p < inputEnd; p += innerSize, pOut += innerSize) {
68 *pOut = *p / l2_norm;
69 }
70 }
71 }
72 return true;
73 }
74
l2normQuant8Impl(const uint8_t * inputData,const Shape & inputShape,int32_t axis,uint8_t * outputData,const Shape & outputShape)75 inline bool l2normQuant8Impl(const uint8_t* inputData, const Shape& inputShape, int32_t axis,
76 uint8_t* outputData, const Shape& outputShape) {
77 NNTRACE_TRANS("l2normQuant8");
78 const uint32_t outerSize = getNumberOfElements(inputShape, 0, axis);
79 const uint32_t axisSize = getSizeOfDimension(inputShape, axis);
80 const uint32_t innerSize =
81 getNumberOfElements(inputShape, axis + 1, getNumberOfDimensions(inputShape));
82 for (uint32_t outer = 0; outer < outerSize; ++outer) {
83 const uint8_t* inputBeg = inputData + outer * axisSize * innerSize;
84 const uint8_t* inputEnd = inputBeg + axisSize * innerSize;
85 uint8_t* outputBeg = outputData + outer * axisSize * innerSize;
86 for (uint32_t inner = 0; inner < innerSize; ++inner, ++inputBeg, ++inputEnd, ++outputBeg) {
87 int32_t sum = 0;
88 for (const uint8_t* p = inputBeg; p < inputEnd; p += innerSize) {
89 int32_t val = static_cast<int32_t>(*p) - inputShape.offset;
90 sum += val * val;
91 }
92 int32_t invMultiplier, invShift;
93 tflite::GetInvSqrtQuantizedMultiplierExp(sum, -1, &invMultiplier, &invShift);
94 uint8_t* pOut = outputBeg;
95 for (const uint8_t* p = inputBeg; p < inputEnd; p += innerSize, pOut += innerSize) {
96 int32_t val = static_cast<int32_t>(*p) - inputShape.offset;
97 int32_t scaledVal = tflite::MultiplyByQuantizedMultiplierSmallerThanOneExp(
98 val * 128, invMultiplier, invShift) +
99 128;
100 *pOut = static_cast<uint8_t>(std::min(std::max(scaledVal, 0), 255));
101 }
102 }
103 }
104 return true;
105 }
106
l2normQuant8SignedImpl(const int8_t * inputData,const Shape & inputShape,int32_t axis,int8_t * outputData,const Shape & outputShape)107 inline bool l2normQuant8SignedImpl(const int8_t* inputData, const Shape& inputShape, int32_t axis,
108 int8_t* outputData, const Shape& outputShape) {
109 NNTRACE_TRANS("l2normQuant8Signed");
110 const uint32_t outerSize = getNumberOfElements(inputShape, 0, axis);
111 const uint32_t axisSize = getSizeOfDimension(inputShape, axis);
112 const uint32_t innerSize =
113 getNumberOfElements(inputShape, axis + 1, getNumberOfDimensions(inputShape));
114 for (uint32_t outer = 0; outer < outerSize; ++outer) {
115 const int8_t* inputBeg = inputData + outer * axisSize * innerSize;
116 const int8_t* inputEnd = inputBeg + axisSize * innerSize;
117 int8_t* outputBeg = outputData + outer * axisSize * innerSize;
118 for (uint32_t inner = 0; inner < innerSize; ++inner, ++inputBeg, ++inputEnd, ++outputBeg) {
119 int32_t sum = 0;
120 for (const int8_t* p = inputBeg; p < inputEnd; p += innerSize) {
121 int32_t val = static_cast<int32_t>(*p) - inputShape.offset;
122 sum += val * val;
123 }
124 int32_t invMultiplier, invShift;
125 tflite::GetInvSqrtQuantizedMultiplierExp(sum, -1, &invMultiplier, &invShift);
126 int8_t* pOut = outputBeg;
127 for (const int8_t* p = inputBeg; p < inputEnd; p += innerSize, pOut += innerSize) {
128 int32_t val = static_cast<int32_t>(*p) - inputShape.offset;
129 int32_t scaledVal = tflite::MultiplyByQuantizedMultiplierSmallerThanOneExp(
130 val * 128, invMultiplier, invShift);
131 *pOut = static_cast<int8_t>(std::min(std::max(scaledVal, -128), 127));
132 }
133 }
134 }
135 return true;
136 }
137
l2normFloat32(const float * inputData,const Shape & inputShape,int32_t axis,float * outputData,const Shape & outputShape)138 bool l2normFloat32(const float* inputData, const Shape& inputShape, int32_t axis, float* outputData,
139 const Shape& outputShape) {
140 int32_t ndim = getNumberOfDimensions(inputShape);
141 NN_CHECK(handleNegativeAxis(inputShape, &axis));
142 // TFLite optimized implementation only supports computation along the last axis
143 if (axis == ndim - 1) {
144 NNTRACE_COMP("optimized_ops::L2Normalization::float");
145 tflite::L2NormalizationParams param = {.input_zero_point = 0};
146 tflite::optimized_ops::L2Normalization(param, convertShapeToTflshape(inputShape), inputData,
147 convertShapeToTflshape(outputShape), outputData);
148 return true;
149 } else {
150 return l2normFloat32Impl(inputData, inputShape, axis, outputData, outputShape);
151 }
152 }
153
l2normFloat16(const _Float16 * inputData,const Shape & inputShape,int32_t axis,_Float16 * outputData,const Shape & outputShape)154 bool l2normFloat16(const _Float16* inputData, const Shape& inputShape, int32_t axis,
155 _Float16* outputData, const Shape& outputShape) {
156 NNTRACE_TRANS("l2normFloat16");
157 std::vector<float> inputDataFloat32(getNumberOfElements(inputShape));
158 convertFloat16ToFloat32(inputData, &inputDataFloat32);
159 std::vector<float> outputDataFloat32(getNumberOfElements(outputShape));
160
161 l2normFloat32(inputDataFloat32.data(), inputShape, axis, outputDataFloat32.data(), outputShape);
162 convertFloat32ToFloat16(outputDataFloat32, outputData);
163
164 return true;
165 }
166
l2normQuant8(const uint8_t * inputData,const Shape & inputShape,int32_t axis,uint8_t * outputData,const Shape & outputShape)167 bool l2normQuant8(const uint8_t* inputData, const Shape& inputShape, int32_t axis,
168 uint8_t* outputData, const Shape& outputShape) {
169 int32_t ndim = getNumberOfDimensions(inputShape);
170 NN_CHECK(handleNegativeAxis(inputShape, &axis));
171 // TFLite optimized implementation only supports computation along the last axis
172 if (axis == ndim - 1) {
173 NNTRACE_COMP("optimized_ops::L2Normalization::uint8");
174 tflite::L2NormalizationParams param = {.input_zero_point = inputShape.offset};
175 tflite::optimized_ops::L2Normalization(param, convertShapeToTflshape(inputShape), inputData,
176 convertShapeToTflshape(outputShape), outputData);
177 return true;
178 } else {
179 return l2normQuant8Impl(inputData, inputShape, axis, outputData, outputShape);
180 }
181 }
182
l2normQuant8Signed(const int8_t * inputData,const Shape & inputShape,int32_t axis,int8_t * outputData,const Shape & outputShape)183 bool l2normQuant8Signed(const int8_t* inputData, const Shape& inputShape, int32_t axis,
184 int8_t* outputData, const Shape& outputShape) {
185 int32_t ndim = getNumberOfDimensions(inputShape);
186 NN_CHECK(handleNegativeAxis(inputShape, &axis));
187 // TFLite implementation only supports computation along the last axis
188 if (axis == ndim - 1) {
189 NNTRACE_COMP("reference_integer_ops::L2Normalization");
190 const int32_t outerSize = getNumberOfElements(inputShape, 0, axis);
191 const int32_t axisSize = getSizeOfDimension(inputShape, axis);
192 tflite::reference_integer_ops::L2Normalization(inputShape.offset, outerSize, axisSize,
193 inputData, outputData);
194 return true;
195 } else {
196 return l2normQuant8SignedImpl(inputData, inputShape, axis, outputData, outputShape);
197 }
198 }
199
200 } // namespace
201
validate(const IOperationValidationContext * context)202 bool validate(const IOperationValidationContext* context) {
203 NN_RET_CHECK(context->getNumInputs() == kNumInputs ||
204 context->getNumInputs() == kNumInputs - 1);
205 NN_RET_CHECK_EQ(context->getNumOutputs(), kNumOutputs);
206
207 const OperandType inputType = context->getInputType(kInputTensor);
208 std::vector<OperandType> inExpectedTypes = {inputType};
209 if (inputType == OperandType::TENSOR_FLOAT16 || inputType == OperandType::TENSOR_QUANT8_ASYMM) {
210 NN_RET_CHECK(validateHalVersion(context, HalVersion::V1_2));
211 } else if (inputType == OperandType::TENSOR_FLOAT32) {
212 NN_RET_CHECK(validateHalVersion(context, HalVersion::V1_0));
213 } else if (inputType == OperandType::TENSOR_QUANT8_ASYMM_SIGNED) {
214 NN_RET_CHECK(validateHalVersion(context, HalVersion::V1_3));
215 } else {
216 NN_RET_CHECK_FAIL() << "Unsupported tensor type for operation " << kOperationName;
217 }
218 if (context->getNumInputs() == kNumInputs) {
219 inExpectedTypes.push_back(OperandType::INT32);
220 NN_RET_CHECK(validateHalVersion(context, HalVersion::V1_2));
221 } else if (context->getInputShape(kInputTensor).dimensions.size() != 4) {
222 NN_RET_CHECK(validateHalVersion(context, HalVersion::V1_2));
223 }
224 const Shape& input = context->getInputShape(kInputTensor);
225 if (hasKnownRank(input)) {
226 NN_RET_CHECK_LE(getNumberOfDimensions(input), 4);
227 }
228 return validateInputTypes(context, inExpectedTypes) &&
229 validateOutputTypes(context, {inputType});
230 }
231
prepare(IOperationExecutionContext * context)232 bool prepare(IOperationExecutionContext* context) {
233 const Shape& input = context->getInputShape(kInputTensor);
234 int32_t numDimensions = getNumberOfDimensions(input);
235 int32_t axis = context->getNumInputs() == kNumInputs
236 ? context->getInputValue<int32_t>(kAxisScalar)
237 : -1;
238 NN_RET_CHECK_LE(numDimensions, 4);
239 NN_RET_CHECK_GE(axis, -numDimensions);
240 NN_RET_CHECK_LT(axis, numDimensions);
241 Shape output = context->getOutputShape(kOutputTensor);
242 output.type = input.type;
243 output.dimensions = input.dimensions;
244 if (output.type == OperandType::TENSOR_QUANT8_ASYMM) {
245 output.scale = 1.0f / 128.0f;
246 output.offset = 128;
247 } else if (output.type == OperandType::TENSOR_QUANT8_ASYMM_SIGNED) {
248 output.scale = 1.0f / 128.0f;
249 output.offset = 0;
250 } else {
251 output.scale = 0;
252 output.offset = 0;
253 }
254 return context->setOutputShape(kOutputTensor, output);
255 }
256
execute(IOperationExecutionContext * context)257 bool execute(IOperationExecutionContext* context) {
258 int32_t axis = context->getNumInputs() == kNumInputs
259 ? context->getInputValue<int32_t>(kAxisScalar)
260 : -1;
261 NN_RET_CHECK(handleNegativeAxis(context->getInputShape(kInputTensor), &axis));
262 switch (context->getInputType(kInputTensor)) {
263 case OperandType::TENSOR_FLOAT32:
264 return l2normFloat32(context->getInputBuffer<float>(kInputTensor),
265 context->getInputShape(kInputTensor), axis,
266 context->getOutputBuffer<float>(kOutputTensor),
267 context->getOutputShape(kOutputTensor));
268 case OperandType::TENSOR_FLOAT16:
269 return l2normFloat16(context->getInputBuffer<_Float16>(kInputTensor),
270 context->getInputShape(kInputTensor), axis,
271 context->getOutputBuffer<_Float16>(kOutputTensor),
272 context->getOutputShape(kOutputTensor));
273 case OperandType::TENSOR_QUANT8_ASYMM:
274 return l2normQuant8(context->getInputBuffer<uint8_t>(kInputTensor),
275 context->getInputShape(kInputTensor), axis,
276 context->getOutputBuffer<uint8_t>(kOutputTensor),
277 context->getOutputShape(kOutputTensor));
278 case OperandType::TENSOR_QUANT8_ASYMM_SIGNED:
279 return l2normQuant8Signed(context->getInputBuffer<int8_t>(kInputTensor),
280 context->getInputShape(kInputTensor), axis,
281 context->getOutputBuffer<int8_t>(kOutputTensor),
282 context->getOutputShape(kOutputTensor));
283 default:
284 NN_RET_CHECK_FAIL() << "Unsupported tensor type for operation " << kOperationName;
285 }
286 }
287
288 } // namespace l2_norm
289
290 NN_REGISTER_OPERATION(L2_NORMALIZATION, l2_norm::kOperationName, l2_norm::validate,
291 l2_norm::prepare, l2_norm::execute);
292
293 } // namespace nn
294 } // namespace android
295