• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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