| /* |
| * Copyright (C) 2021 MediaTek Inc., this file is modified on 02/26/2021 |
| * by MediaTek Inc. based on MIT License . |
| * Permission is hereby granted, free of charge, to any person obtaining a copy |
| * of this software and associated documentation files (the ""Software""), to |
| * deal in the Software without restriction, including without limitation the |
| * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or |
| * sell copies of the Software, and to permit persons to whom the Software is |
| * furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be included in |
| * all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| * SOFTWARE. |
| */ |
| |
| #ifndef DELEGATE_MTK_NEURON_NEURON_DELEGATE_BUILDER_H_ |
| #define DELEGATE_MTK_NEURON_NEURON_DELEGATE_BUILDER_H_ |
| |
| #include <algorithm> |
| #include <limits> |
| #include <string> |
| #include <tuple> |
| #include <vector> |
| |
| #include "delegate/mtk_neuron/neuron_delegate_kernel.h" |
| #include "delegate/mtk_neuron/neuron_delegate_utils.h" |
| #include "delegate/mtk_neuron/neuron_delegate_validation.h" |
| #include "delegate/mtk_neuron/neuron_implementation.h" |
| #include "tensorflow/lite/c/builtin_op_data.h" |
| #include "tensorflow/lite/kernels/kernel_util.h" |
| |
| namespace tflite { |
| namespace neuron { |
| |
| extern int ComputeSplitVUnknownSplitSize(const TfLiteOpaqueContext* context, |
| const TfLiteOpaqueNode* node); |
| constexpr int32_t kMinSdkVersionForNeuron13 = 30; |
| |
| static constexpr const char* kExtensionGeneralOpration = |
| "com.mediatek.general_operation"; |
| |
| // Extension operand |
| enum { |
| ADAPTER_EXTENSION_GENERAL_OPERAND_ARGSTRING = 0x0100, |
| ADAPTER_EXTENSION_GENERAL_OPERATION_TYPE = 0x0000, |
| }; |
| |
| // Bit mask for tensor flags. |
| enum { |
| NN_TENSOR_FLAG_SCALAR_AS_TENSOR = 1U << 0, |
| NN_TENSOR_FLAG_INT8_CONVERSION = 1U << 1, |
| NN_TENSOR_FLAG_USE_INT8_ASYMM_SIGNED = 1U << 2, |
| NN_TENSOR_FLAG_FORCE_PER_CHANNEL = 1U << 3, |
| NN_TENSOR_FLAG_INT64_CONVERSION = 1U << 4, |
| }; |
| |
| class DequantizeMapping { |
| public: |
| int DequantizedAnnIndex(int neuron_index, TfLiteType type) const { |
| for (const auto& element : mapping_) { |
| if (neuron_index == std::get<0>(element) && |
| type == std::get<1>(element)) { |
| return std::get<2>(element); |
| } |
| } |
| return -1; |
| } |
| |
| void Add(int neuron_index, TfLiteType type, int dequantized_ann_index) { |
| // This assumes it is not already mapped. |
| mapping_.emplace_back(neuron_index, type, dequantized_ann_index); |
| } |
| |
| private: |
| // Each tuple specifies the ANN (quantized) tensor index, the desired |
| // floating-point type and the matching ANN (dequantized) tensor index. This |
| // could use a map but instead std::vector is used to keep code size lower. |
| std::vector<std::tuple<int, TfLiteType, int>> mapping_; |
| }; |
| |
| // Track tensor indices to Neuron tensor indices mapping. |
| class OperandMapping { |
| public: |
| // Given a TFLite index return the Neuron index. If it doesn't exist |
| // return -1. |
| int lite_index_to_neuron(int index) const { |
| if (index >= 0 && index < lite_tensor_to_neuron_tensor_.size()) |
| return lite_tensor_to_neuron_tensor_[index]; |
| else |
| return -1; |
| } |
| |
| // Neuron uses non tensor operands instead of structs. This creates one |
| // and returns the index. It uses a std::vector and resizes it as needed |
| // keeping -1 to unmapped values. Intermediate tensors likely will not |
| // be mapped. |
| int add_new_non_tensor_operand() { return next_neuron_tensor_index_++; } |
| |
| /// M: NeuroPilot {@ |
| void set_new_non_tensor_operand(int new_index) { |
| next_neuron_tensor_index_ = new_index; |
| } |
| /// M: NeuroPilot @} |
| |
| // This call is necessary for input operands generated by the delegate |
| // to map constant inputs not present in TFLite but required by Neuron, |
| // for example when splitting one input in several ones. |
| int add_delegate_generated_input_neuron_tensors_operand() { |
| return next_neuron_tensor_index_++; |
| } |
| |
| // Add a new mapping from `tflite_index` and return the Neuron tensor |
| // index. |
| int add_new_neuron_tensor_index(int tflite_index) { |
| if (tflite_index >= lite_tensor_to_neuron_tensor_.size()) { |
| lite_tensor_to_neuron_tensor_.resize(tflite_index + 1, -1); |
| } |
| const int new_tensor_index = next_neuron_tensor_index_++; |
| lite_tensor_to_neuron_tensor_[tflite_index] = new_tensor_index; |
| return new_tensor_index; |
| } |
| |
| // Given a TFLite index returns a TFLite type to which a tensor must be |
| // converted during copying the data to the memory allocated for Neuron. |
| // kTfLiteNoType means no conversion is needed. |
| TfLiteType lite_index_to_neuron_type_conversion(int index) const { |
| if (index >= 0 && index < index_to_type_conversion_.size()) |
| return index_to_type_conversion_[index]; |
| else |
| return kTfLiteNoType; |
| } |
| |
| // Add a new mapping from TFLite index to a type conversion. |
| void add_type_conversion(int tflite_index, TfLiteType tflite_type) { |
| if (tflite_index >= index_to_type_conversion_.size()) { |
| index_to_type_conversion_.resize(tflite_index + 1, kTfLiteNoType); |
| } |
| index_to_type_conversion_[tflite_index] = tflite_type; |
| } |
| |
| private: |
| // Next index of neuron tensor |
| int next_neuron_tensor_index_ = 0; |
| |
| // Mapping from lite index. Use a std::vector for speed and code size |
| // rather than a map. |
| std::vector<int> lite_tensor_to_neuron_tensor_; |
| // Mapping from lite index to a type which tensor must be converted to during |
| // the copying of the data to the memory allocated for Neuron. |
| // kTfLiteNoType means no conversion is needed. Use an std::vector for speed |
| // and code size rather than a map. |
| std::vector<TfLiteType> index_to_type_conversion_; |
| }; |
| |
| // Abstract builder for building an op in the Neuron graph. This handles |
| // the disparity between TFLite and Neuron operand types. Neuron has |
| // singular operands for both tensors and parameters, and TFLite separates the |
| // two. |
| class NeuronOpBuilder { |
| public: |
| NeuronOpBuilder(const NeuronApi* neuronapi, TfLiteOpaqueContext* context, |
| OperandMapping* tensor_mapping, |
| DequantizeMapping* dequantize_mapping, NeuronModel* nn_model) |
| : neuronapi_(neuronapi), |
| context_(context), |
| operand_mapping_(tensor_mapping), |
| dequantize_mapping_(dequantize_mapping), |
| nn_model_(nn_model) {} |
| |
| TfLiteStatus AddScalarBoolOperand(bool value) { |
| return AddScalarOperand<bool>(value, NEURON_BOOL); |
| } |
| |
| TfLiteStatus AddScalarInt32Operand(int32_t value) { |
| return AddScalarOperand<int32_t>(value, NEURON_INT32); |
| } |
| |
| TfLiteStatus AddScalarFloat32Operand(float value) { |
| return AddScalarOperand<float>(value, NEURON_FLOAT32); |
| } |
| |
| TfLiteStatus AddScalarExtensionOperand(const uint8_t* value, |
| uint32_t length) { |
| int32_t operand_type; |
| |
| RETURN_TFLITE_ERROR_IF_NEURON_ERROR( |
| context_, |
| neuronapi_->NeuronModel_getExtensionOperandType( |
| nn_model_, kExtensionGeneralOpration, |
| ADAPTER_EXTENSION_GENERAL_OPERAND_ARGSTRING, &operand_type), |
| "getting extension operand type"); |
| |
| NeuronOperandType operand_ext_type; |
| operand_ext_type.type = operand_type; |
| operand_ext_type.scale = 0.0f; |
| operand_ext_type.zeroPoint = 0; |
| operand_ext_type.dimensionCount = 1; |
| uint32_t dims_extenion[1] = {length}; |
| operand_ext_type.dimensions = dims_extenion; |
| |
| RETURN_TFLITE_ERROR_IF_NEURON_ERROR( |
| context_, |
| neuronapi_->NeuronModel_addOperand(nn_model_, &operand_ext_type), |
| "adding extension operand"); |
| const int neuron_index = operand_mapping_->add_new_non_tensor_operand(); |
| RETURN_TFLITE_ERROR_IF_NEURON_ERROR( |
| context_, |
| neuronapi_->NeuronModel_setOperandValue(nn_model_, neuron_index, value, |
| length), |
| "setting new extension operand value"); |
| augmented_inputs_.push_back(neuron_index); |
| return kTfLiteOk; |
| } |
| |
| TfLiteStatus AddVectorInt32Operand(const int32_t* values, |
| uint32_t num_values) { |
| return AddVectorOperand<int32_t>(values, num_values, NEURON_TENSOR_INT32, |
| /*scale=*/0.f, /*zero_point=*/0); |
| } |
| |
| TfLiteStatus AddVectorInt32Operand(const int32_t* values, uint32_t num_values, |
| float scale, int32_t zero_point) { |
| return AddVectorOperand<int32_t>(values, num_values, NEURON_TENSOR_INT32, |
| scale, zero_point); |
| } |
| |
| TfLiteStatus AddVectorFloat32Operand(const float* values, |
| uint32_t num_values) { |
| return AddVectorOperand<float>(values, num_values, NEURON_TENSOR_FLOAT32); |
| } |
| |
| TfLiteStatus AddPoolingParams(void* data) { |
| auto builtin = reinterpret_cast<TfLitePoolParams*>(data); |
| AddScalarInt32Operand(builtin->padding); |
| AddScalarInt32Operand(builtin->stride_width); |
| AddScalarInt32Operand(builtin->stride_height); |
| AddScalarInt32Operand(builtin->filter_width); |
| AddScalarInt32Operand(builtin->filter_height); |
| AddScalarInt32Operand(builtin->activation); |
| return kTfLiteOk; |
| } |
| |
| TfLiteStatus AddTensorInput(int tensor_index, bool hybrid_op, |
| int tensor_flags = 0) { |
| return AddTensor(tensor_index, hybrid_op, &augmented_inputs_, tensor_flags); |
| } |
| |
| TfLiteStatus AddTensorOutput(int tensor_index, int tensor_flags = 0) { |
| return AddTensor(tensor_index, /*hybrid_op=*/false, &augmented_outputs_, |
| tensor_flags); |
| } |
| |
| TfLiteStatus AddAdditionalFloat32OutputTensor(uint32_t dimension_count) { |
| std::vector<uint32_t> dims(dimension_count, 0); |
| return AddFloat32OutputTensor(dimension_count, dims.data(), nullptr); |
| } |
| |
| std::vector<int32_t> TfLiteOpaqueTensorFullDim( |
| const TfLiteOpaqueTensor* tensor) { |
| std::vector<int32_t> dims; |
| for (int i = 0; i < TfLiteOpaqueTensorNumDims(tensor); i++) { |
| dims.push_back(TfLiteOpaqueTensorDim(tensor, i)); |
| } |
| return dims; |
| } |
| |
| TfLiteStatus AddStateFloat32Tensor(int tensor_index, |
| int* ann_tensor_index_out) { |
| TfLiteOpaqueTensor* tensor = |
| TfLiteOpaqueContextGetOpaqueTensor(context_, tensor_index); |
| std::vector<int32_t> full_dim = TfLiteOpaqueTensorFullDim(tensor); |
| // SAFETY: neuron adapter will allocate local memory to save it. |
| return AddFloat32OutputTensor(TfLiteOpaqueTensorNumDims(tensor), |
| reinterpret_cast<uint32_t*>(full_dim.data()), |
| ann_tensor_index_out); |
| } |
| |
| // Add a constant tensor with a single element, intended for broadcast capable |
| // ops. |
| TfLiteStatus AddSingleValueConstantTensor(float value, bool is_quantized) { |
| if (!is_quantized) { |
| return AddVectorFloat32Operand(&value, 1); |
| } else { |
| // in the case that we need to add a quantized tensor, set the value to |
| // 64, zero_point to be 0 and adjust scale accordingly. |
| const uint8_t quant8_value = 64; |
| return AddVectorOperand<uint8_t>(&quant8_value, 1, |
| NEURON_TENSOR_QUANT8_ASYMM, |
| value / quant8_value, 0); |
| } |
| } |
| |
| // Calculate the scale and zero_point for 8-bit unsigned tensor, given float |
| // min and max. zero_point is clamped to [0, 255]. |
| TfLiteStatus CalculateQuantizationParams(float min, float max, float* scale, |
| int* zero_point) { |
| if (max < min) return kTfLiteError; |
| *scale = (max - min) / 255.f; |
| if (min > 0.f) { |
| *zero_point = 0; |
| } else if (max < 0.f) { |
| *zero_point = 255; |
| } else { |
| *zero_point = (0.f - min) / (*scale); |
| } |
| return kTfLiteOk; |
| } |
| |
| // Lower hardswish according to the following equation: |
| // hard_swish[x] = x (ReLU6(x + 3)) / 6 == x * (Relu_N1_to_1(x/3) * 3 + 3) / 6 |
| // = 0.5x * Relu_N1_to_1(x/3) + 0.5x |
| TfLiteStatus AddHardSwish(int lite_input_index, int lite_output_index, |
| bool need_int8_conversion) { |
| TfLiteOpaqueTensor* tensor = |
| TfLiteOpaqueContextGetOpaqueTensor(context_, lite_input_index); |
| float input_scale = TfLiteOpaqueTensorGetQuantizationParams(tensor).scale; |
| int input_zero_point = |
| TfLiteOpaqueTensorGetQuantizationParams(tensor).zero_point; |
| float input_min = 0.f; |
| float input_max = 0.f; |
| int tensor_flags = 0; |
| if (need_int8_conversion) { |
| tensor_flags = tensor_flags | NN_TENSOR_FLAG_INT8_CONVERSION; |
| input_zero_point += 128; |
| } |
| bool is_quantized = false; |
| int nn_type = NEURON_TENSOR_FLOAT32; |
| if (TfLiteOpaqueTensorType(tensor) == kTfLiteInt8 || |
| TfLiteOpaqueTensorType(tensor) == kTfLiteUInt8) { |
| is_quantized = true; |
| nn_type = NEURON_TENSOR_QUANT8_ASYMM; |
| input_min = (0 - input_zero_point) * input_scale; |
| input_max = (255 - input_zero_point) * input_scale; |
| } |
| |
| // Stage1 : s1 = Relu1(x * 1/3) |
| float s1_output_min = 0.f; |
| float s1_output_max = 0.f; |
| int s1_out_ann_index = 0; |
| { |
| float s1_output_scale = 0.f; |
| int s1_output_zero_point = 0; |
| if (is_quantized) { |
| // clamp the output range to [-1, 1] if needed. |
| s1_output_min = input_min / 3.f < -1.f ? -1.f : input_min / 3.f; |
| s1_output_max = input_max / 3.f > 1.f ? 1.f : input_max / 3.f; |
| CalculateQuantizationParams(s1_output_min, s1_output_max, |
| &s1_output_scale, &s1_output_zero_point); |
| } |
| TF_LITE_OPAQUE_ENSURE_STATUS( |
| AddTensorInput(lite_input_index, false, tensor_flags)); |
| const float value3f = 1.f / 3.f; |
| TF_LITE_OPAQUE_ENSURE_STATUS( |
| AddSingleValueConstantTensor(value3f, is_quantized)); |
| TF_LITE_OPAQUE_ENSURE_STATUS(AddScalarInt32Operand(NEURON_FUSED_RELU1)); |
| std::vector<int32_t> full_dim = TfLiteOpaqueTensorFullDim(tensor); |
| // SAFETY: neuron adapter will allocate local memory to save it. |
| TF_LITE_OPAQUE_ENSURE_STATUS(AddAdditionalOutputTensor( |
| TfLiteOpaqueTensorNumDims(tensor), |
| reinterpret_cast<uint32_t*>(full_dim.data()), nn_type, |
| s1_output_scale, s1_output_zero_point, &s1_out_ann_index)); |
| TF_LITE_OPAQUE_ENSURE_STATUS(FinalizeAddOperation(NEURON_MUL)); |
| } |
| |
| // Stage2 : s2 = x / 2 |
| float s2_output_min = input_min / 2.f; |
| float s2_output_max = input_max / 2.f; |
| int s2_out_ann_index = 0; |
| { |
| float s2_output_scale = input_scale / 2.0f; |
| int s2_output_zero_point = input_zero_point; |
| TF_LITE_OPAQUE_ENSURE_STATUS( |
| AddTensorInput(lite_input_index, false, tensor_flags)); |
| const float value2f = 0.5f; |
| TF_LITE_OPAQUE_ENSURE_STATUS( |
| AddSingleValueConstantTensor(value2f, is_quantized)); |
| TF_LITE_OPAQUE_ENSURE_STATUS(AddScalarInt32Operand(NEURON_FUSED_NONE)); |
| std::vector<int32_t> full_dim = TfLiteOpaqueTensorFullDim(tensor); |
| // SAFETY: neuron adapter will allocate local memory to save it. |
| TF_LITE_OPAQUE_ENSURE_STATUS(AddAdditionalOutputTensor( |
| TfLiteOpaqueTensorNumDims(tensor), |
| reinterpret_cast<uint32_t*>(full_dim.data()), nn_type, |
| s2_output_scale, s2_output_zero_point, &s2_out_ann_index)); |
| TF_LITE_OPAQUE_ENSURE_STATUS(FinalizeAddOperation(NEURON_MUL)); |
| } |
| |
| // Stage 3 : s3 = s1 * s2 |
| int s3_out_ann_index = 0; |
| { |
| augmented_inputs_.push_back(s1_out_ann_index); |
| augmented_inputs_.push_back(s2_out_ann_index); |
| TF_LITE_OPAQUE_ENSURE_STATUS(AddScalarInt32Operand(NEURON_FUSED_NONE)); |
| float s3_output_scale = 0.f; |
| int s3_output_zero_point = 0; |
| if (is_quantized) { |
| // the min for stage 3 is always 0.0f. |
| float s3_output_min = 0.f; |
| // the max for stage 3 is max(s1_min * s2_min, s1_max * s3_max). |
| float s3_output_max = |
| s1_output_max * s2_output_max > s1_output_min * s2_output_min |
| ? s1_output_max * s2_output_max |
| : s1_output_min * s2_output_min; |
| CalculateQuantizationParams(s3_output_min, s3_output_max, |
| &s3_output_scale, &s3_output_zero_point); |
| } |
| std::vector<int32_t> full_dim = TfLiteOpaqueTensorFullDim(tensor); |
| // SAFETY: neuron adapter will allocate local memory to save it. |
| TF_LITE_OPAQUE_ENSURE_STATUS(AddAdditionalOutputTensor( |
| TfLiteOpaqueTensorNumDims(tensor), |
| reinterpret_cast<uint32_t*>(full_dim.data()), nn_type, |
| s3_output_scale, s3_output_zero_point, &s3_out_ann_index)); |
| TF_LITE_OPAQUE_ENSURE_STATUS(FinalizeAddOperation(NEURON_MUL)); |
| } |
| |
| // Stage 4: y = s3 + s2 |
| { |
| augmented_inputs_.push_back(s2_out_ann_index); |
| augmented_inputs_.push_back(s3_out_ann_index); |
| TF_LITE_OPAQUE_ENSURE_STATUS(AddScalarInt32Operand(NEURON_FUSED_NONE)); |
| TF_LITE_OPAQUE_ENSURE_STATUS( |
| AddTensorOutput(lite_output_index, tensor_flags)); |
| TF_LITE_OPAQUE_ENSURE_STATUS(FinalizeAddOperation(NEURON_ADD)); |
| } |
| |
| return kTfLiteOk; |
| } |
| |
| // Adds a Dequantize operator and replaces the input tensor index with the |
| // dequantized version. If the dequantized version of the operator already |
| // exists then it is not added again. |
| TfLiteStatus AddDequantize(int nn_input_index, int lite_index, |
| TfLiteType dequantized_type) { |
| const int neuron_index = operand_mapping_->lite_index_to_neuron(lite_index); |
| int dequantized_ann_index = dequantize_mapping_->DequantizedAnnIndex( |
| neuron_index, dequantized_type); |
| |
| if (dequantized_ann_index == -1) { |
| // The dequantized version does not exist yet, it has to be added: a new |
| // Dequantize operation is added, yielding a new tensor. |
| TfLiteOpaqueTensor* tensor = |
| TfLiteOpaqueContextGetOpaqueTensor(context_, lite_index); |
| std::vector<int32_t> full_dim = TfLiteOpaqueTensorFullDim(tensor); |
| // SAFETY: neuron adapter will allocate local memory to save it. |
| NeuronOperandType operand_type{ |
| NEURON_TENSOR_FLOAT32, |
| static_cast<uint32_t>(TfLiteOpaqueTensorNumDims(tensor)), |
| reinterpret_cast<uint32_t*>(full_dim.data()), 0.f, 0}; |
| RETURN_TFLITE_ERROR_IF_NEURON_ERROR( |
| context_, |
| neuronapi_->NeuronModel_addOperand(nn_model_, &operand_type), |
| "adding operand"); |
| dequantized_ann_index = operand_mapping_->add_new_non_tensor_operand(); |
| |
| // Add Dequantize operation. |
| const uint32_t dequantize_input[1] = { |
| static_cast<uint32_t>(neuron_index)}; |
| const uint32_t dequantize_output[1] = { |
| static_cast<uint32_t>(dequantized_ann_index)}; |
| RETURN_TFLITE_ERROR_IF_NEURON_ERROR( |
| context_, |
| neuronapi_->NeuronModel_addOperation(nn_model_, NEURON_DEQUANTIZE, 1, |
| dequantize_input, 1, |
| dequantize_output), |
| "adding operation"); |
| total_op_num_++; |
| dequantize_mapping_->Add(neuron_index, dequantized_type, |
| dequantized_ann_index); |
| } |
| |
| // The input for the original operation is modified so that the operation |
| // now uses the dequantized tensor as input. |
| augmented_inputs_[nn_input_index] = dequantized_ann_index; |
| |
| return kTfLiteOk; |
| } |
| |
| // Add a RESHAPE op which reshapes an NNAPI intermediate output to the |
| // dimensions of the TFLite output tensor. |
| TfLiteStatus AppendReshape(int nn_input_index, int lite_out_tensor_index) { |
| augmented_inputs_.push_back(nn_input_index); |
| TfLiteOpaqueTensor* output_tensor = |
| TfLiteOpaqueContextGetOpaqueTensor(context_, lite_out_tensor_index); |
| std::vector<int32_t> full_dim = TfLiteOpaqueTensorFullDim(output_tensor); |
| // SAFETY: neuron adapter will allocate local memory to save it. |
| TF_LITE_OPAQUE_ENSURE_STATUS(AddVectorInt32Operand( |
| full_dim.data(), |
| static_cast<uint32_t>(TfLiteOpaqueTensorNumDims(output_tensor)))); |
| TF_LITE_OPAQUE_ENSURE_STATUS(AddTensorOutput( |
| lite_out_tensor_index, NN_TENSOR_FLAG_USE_INT8_ASYMM_SIGNED)); |
| TF_LITE_OPAQUE_ENSURE_STATUS(FinalizeAddOperation(NEURON_RESHAPE)); |
| return kTfLiteOk; |
| } |
| |
| // Lower PACK into CONCAT + RESHAPE when possible |
| TfLiteStatus TransformPackIntoSupportedOps(const TfLiteOpaqueNode* node, |
| TfLiteRegistrationExternal* reg) { |
| // Add input tensors for CONCAT, and calculate the dimensions for the |
| // output. |
| int concat_output_ann_index = -1; |
| TfLitePackParams* builtin = reinterpret_cast<TfLitePackParams*>( |
| TfLiteOpaqueNodeGetBuiltinData(node)); |
| const TfLiteOpaqueTensor* input_tensor = TfLiteOpaqueNodeGetInput( |
| const_cast<const TfLiteOpaqueContext*>(context_), node, 0); |
| int axis = builtin->axis < 0 |
| ? TfLiteOpaqueTensorNumDims(input_tensor) + builtin->axis + 1 |
| : builtin->axis; |
| TF_LITE_OPAQUE_ENSURE(context_, |
| axis < TfLiteOpaqueTensorNumDims(input_tensor)); |
| uint32_t concat_dim_size = 0; |
| const int* inputs; |
| int num_inputs; |
| TF_LITE_OPAQUE_ENSURE_STATUS( |
| TfLiteOpaqueNodeInputs(node, &inputs, &num_inputs)); |
| for (int input_pos = 0; input_pos < TfLiteOpaqueNodeNumberOfInputs(node); |
| ++input_pos) { |
| const auto input_index = inputs[input_pos]; |
| concat_dim_size += TfLiteOpaqueTensorDim( |
| TfLiteOpaqueNodeGetInput(context_, node, input_pos), axis); |
| TF_LITE_OPAQUE_ENSURE_STATUS( |
| AddTensorInput(input_index, /*hybrid_op=*/false, |
| NN_TENSOR_FLAG_USE_INT8_ASYMM_SIGNED)); |
| } |
| TF_LITE_OPAQUE_ENSURE_STATUS(AddScalarInt32Operand(axis)); |
| std::vector<uint32_t> concat_output_shape( |
| TfLiteOpaqueTensorNumDims(input_tensor), 0); |
| for (int i = 0; i < concat_output_shape.size(); i++) { |
| if (i == axis) { |
| concat_output_shape[i] = concat_dim_size; |
| } else { |
| concat_output_shape[i] = TfLiteOpaqueTensorDim(input_tensor, i); |
| } |
| } |
| TF_LITE_OPAQUE_ENSURE_STATUS(AddIntermediateOutputTensor( |
| TfLiteOpaqueTensorType(input_tensor), concat_output_shape.size(), |
| concat_output_shape.data(), |
| TfLiteOpaqueTensorGetQuantizationParams(input_tensor).scale, |
| TfLiteOpaqueTensorGetQuantizationParams(input_tensor).zero_point, |
| &concat_output_ann_index)); |
| TF_LITE_OPAQUE_ENSURE_STATUS(FinalizeAddOperation(NEURON_CONCATENATION)); |
| const int* outputs; |
| int num_outputs; |
| TF_LITE_OPAQUE_ENSURE_STATUS( |
| TfLiteOpaqueNodeOutputs(node, &outputs, &num_outputs)); |
| // Reshape the output tensor |
| TF_LITE_OPAQUE_ENSURE_STATUS( |
| AppendReshape(concat_output_ann_index, outputs[0])); |
| return kTfLiteOk; |
| } |
| |
| // Lower SPLIT_V into SLICEs. |
| TfLiteStatus TransformSplitVIntoSupportedOps( |
| int lite_node_index, TfLiteOpaqueNode* node, |
| TfLiteRegistrationExternal* reg) { |
| const auto& input = TfLiteOpaqueNodeGetInput( |
| const_cast<const TfLiteOpaqueContext*>(context_), |
| const_cast<const TfLiteOpaqueNode*>(node), 0); |
| const auto& size_splits_tensor = TfLiteOpaqueNodeGetInput( |
| const_cast<const TfLiteOpaqueContext*>(context_), |
| const_cast<const TfLiteOpaqueNode*>(node), 1); |
| const auto& axis_tensor = TfLiteOpaqueNodeGetInput( |
| const_cast<const TfLiteOpaqueContext*>(context_), |
| const_cast<const TfLiteOpaqueNode*>(node), 2); |
| int input_rank = TfLiteOpaqueTensorNumDims(input); |
| |
| const auto* size_splits = |
| reinterpret_cast<int32_t*>(TfLiteOpaqueTensorData(size_splits_tensor)); |
| int num_splits = TfLiteOpaqueTensorDim(size_splits_tensor, 0); |
| int axis = |
| reinterpret_cast<int32_t*>(TfLiteOpaqueTensorData(axis_tensor))[0]; |
| axis = axis < 0 ? axis + input_rank : axis; |
| TF_LITE_OPAQUE_ENSURE(context_, axis >= 0); |
| TF_LITE_OPAQUE_ENSURE(context_, axis < input_rank); |
| int unknown_split_size = ComputeSplitVUnknownSplitSize(context_, node); |
| |
| // Keep track of the start index of a slice. |
| int slice_begin_index = 0; |
| for (int split_index = 0; split_index < num_splits; split_index++) { |
| int split_size = size_splits[split_index] == -1 |
| ? unknown_split_size |
| : size_splits[split_index]; |
| TF_LITE_OPAQUE_ENSURE(context_, split_size > 0); |
| |
| // Parameters of SLICE. |
| std::vector<int> begin_indices(input_rank); |
| std::vector<int> slice_sizes(input_rank); |
| for (int i = 0; i < input_rank; i++) { |
| if (i == axis) { |
| // Take only the splitted size. |
| begin_indices[i] = slice_begin_index; |
| slice_sizes[i] = split_size; |
| } else { |
| // Take the full size. |
| begin_indices[i] = 0; |
| slice_sizes[i] = TfLiteOpaqueTensorDim(input, i); |
| } |
| } |
| slice_begin_index += split_size; |
| |
| const int *inputs, *outputs; |
| int num_inputs, num_outputs; |
| TF_LITE_OPAQUE_ENSURE_STATUS( |
| TfLiteOpaqueNodeInputs(node, &inputs, &num_inputs)); |
| TF_LITE_OPAQUE_ENSURE_STATUS( |
| TfLiteOpaqueNodeOutputs(node, &outputs, &num_outputs)); |
| // Build NNAPI SLICE inputs and output. |
| TF_LITE_OPAQUE_ENSURE_STATUS(AddTensorInput( |
| inputs[0], |
| /*hybrid_op=*/false, NN_TENSOR_FLAG_USE_INT8_ASYMM_SIGNED)); |
| TF_LITE_OPAQUE_ENSURE_STATUS( |
| AddVectorInt32Operand(begin_indices.data(), begin_indices.size())); |
| TF_LITE_OPAQUE_ENSURE_STATUS( |
| AddVectorInt32Operand(slice_sizes.data(), slice_sizes.size())); |
| int lite_output_index = outputs[split_index]; |
| TF_LITE_OPAQUE_ENSURE_STATUS(AddTensorOutput( |
| lite_output_index, NN_TENSOR_FLAG_USE_INT8_ASYMM_SIGNED)); |
| |
| TF_LITE_OPAQUE_ENSURE_STATUS(FinalizeAddOperation(NEURON_SLICE)); |
| } |
| return kTfLiteOk; |
| } |
| |
| TfLiteStatus GetExtensionOperationType(NeuronOperationType* type) { |
| // Get oem op type |
| int32_t operation_type = NEURON_OEM_OPERATION; |
| if (neuronapi_->android_sdk_version > kMinSdkVersionForNeuron13) { |
| RETURN_TFLITE_ERROR_IF_NEURON_ERROR( |
| context_, |
| neuronapi_->NeuronModel_getExtensionOperationType( |
| nn_model_, kExtensionGeneralOpration, |
| ADAPTER_EXTENSION_GENERAL_OPERATION_TYPE, &operation_type), |
| "getting extension operation type"); |
| } |
| *type = static_cast<NeuronOperationType>(operation_type); |
| return kTfLiteOk; |
| } |
| |
| // Finish emitting the op (of type `type`) into the Neuron. |
| TfLiteStatus FinalizeAddOperation(NeuronOperationType type) { |
| // Actually add a Neuron operation |
| RETURN_TFLITE_ERROR_IF_NEURON_ERROR( |
| context_, |
| neuronapi_->NeuronModel_addOperation( |
| nn_model_, type, static_cast<uint32_t>(augmented_inputs_.size()), |
| augmented_inputs_.data(), |
| static_cast<uint32_t>(augmented_outputs_.size()), |
| augmented_outputs_.data()), |
| "adding operation"); |
| total_op_num_++; |
| augmented_inputs_.clear(); |
| augmented_outputs_.clear(); |
| return kTfLiteOk; |
| } |
| |
| TfLiteStatus AddSingleValueTensorAsScalarOperand(int tensor_index, |
| int nn_type) { |
| TfLiteOpaqueTensor* tensor = |
| TfLiteOpaqueContextGetOpaqueTensor(context_, tensor_index); |
| TF_LITE_OPAQUE_ENSURE_EQ(context_, NumElements(tensor), 1); |
| |
| NeuronOperandType operand_type{.type = nn_type}; |
| RETURN_TFLITE_ERROR_IF_NEURON_ERROR( |
| context_, neuronapi_->NeuronModel_addOperand(nn_model_, &operand_type), |
| "adding operand"); |
| int neuron_tensor_index = |
| operand_mapping_->lite_index_to_neuron(tensor_index); |
| if (neuron_tensor_index != -1) { |
| augmented_inputs_.push_back(neuron_tensor_index); |
| return kTfLiteOk; |
| } |
| // Allocate a new tensor index |
| neuron_tensor_index = |
| operand_mapping_->add_new_neuron_tensor_index(tensor_index); |
| augmented_inputs_.push_back(neuron_tensor_index); |
| |
| const TfLiteType tensor_type = TfLiteOpaqueTensorType(tensor); |
| TfLiteType nn_type_equivalent; |
| TF_LITE_OPAQUE_ENSURE_STATUS( |
| GetEquivalentToNeuronType(context_, nn_type, &nn_type_equivalent)); |
| if (tensor_type != nn_type_equivalent) { |
| operand_mapping_->add_type_conversion(tensor_index, nn_type_equivalent); |
| } |
| return kTfLiteOk; |
| } |
| |
| /// M: NeuroPilot {@ |
| uint32_t GetNextNeuronOperandIdx(void) { |
| return (uint32_t)operand_mapping_->add_new_non_tensor_operand(); |
| } |
| void UpdateNeuronOperandIdx(int new_idx) { |
| operand_mapping_->set_new_non_tensor_operand(new_idx); |
| } |
| |
| std::vector<uint32_t>& GetAugmentedInputs(void) { return augmented_inputs_; } |
| |
| std::vector<uint32_t>& GetAugmentedOutputs(void) { |
| return augmented_outputs_; |
| } |
| |
| NeuronModel* GetNnModel(void) { return nn_model_; } |
| |
| int GetTotalOpNum(void) { return total_op_num_; } |
| /// M: NeuroPilot @} |
| |
| template <typename T> |
| TfLiteStatus AddNewInputConstantTensor( |
| int32_t nn_type, TfLiteType type, const TfLiteIntArray* dims, |
| const std::vector<T>& tensor_value, |
| const TfLiteQuantizationParams& quant_params, int* tensor_index) { |
| TfLiteOpaqueTensorBuilder* tensor_builder = |
| TfLiteOpaqueTensorBuilderCreate(); |
| TfLiteOpaqueTensorBuilderSetType(tensor_builder, type); |
| TfLiteOpaqueTensorBuilderSetAllocationType(tensor_builder, kTfLiteDynamic); |
| TfLiteOpaqueTensorBuilderSetQuantizationParams(tensor_builder, |
| quant_params); |
| // The dynamic tensor need to set the tensor data. Set an empty pointer and |
| // set the data value later after resizeing tensor dimension. |
| void* tensor_data = malloc(tensor_value.size() * sizeof(T)); |
| TfLiteOpaqueTensorBuilderSetData(tensor_builder, tensor_data); |
| |
| int new_tensor_index = -1; |
| TF_LITE_OPAQUE_ENSURE_STATUS(TfLiteOpaqueContextAddTensor( |
| context_, tensor_builder, &new_tensor_index)); |
| TfLiteOpaqueTensorBuilderDelete(tensor_builder); |
| TfLiteOpaqueTensor* new_tensor = |
| TfLiteOpaqueContextGetOpaqueTensor(context_, new_tensor_index); |
| |
| // Not removing the new tensor in case of resizing errors since it will |
| // be cleared by the context |
| TF_LITE_OPAQUE_ENSURE_STATUS(TfLiteOpaqueContextResizeTensor( |
| context_, new_tensor, TfLiteIntArrayCopy(dims))); |
| |
| memcpy(TfLiteOpaqueTensorData(new_tensor), |
| reinterpret_cast<const char*>(tensor_value.data()), |
| tensor_value.size() * sizeof(T)); |
| |
| const uint32_t tensor_rank = static_cast<uint32_t>(dims->size); |
| const uint32_t* tensor_dims = reinterpret_cast<const uint32_t*>(dims->data); |
| NeuronOperandType operand_type{nn_type, tensor_rank, tensor_dims, |
| quant_params.scale, quant_params.zero_point}; |
| |
| const int neuron_tensor_index = |
| operand_mapping_->add_delegate_generated_input_neuron_tensors_operand(); |
| |
| RETURN_TFLITE_ERROR_IF_NEURON_ERROR( |
| context_, neuronapi_->NeuronModel_addOperand(nn_model_, &operand_type), |
| "adding operand"); |
| |
| augmented_inputs_.push_back(neuron_tensor_index); |
| |
| RETURN_TFLITE_ERROR_IF_NEURON_ERROR( |
| context_, |
| neuronapi_->NeuronModel_setOperandValue( |
| nn_model_, neuron_tensor_index, TfLiteOpaqueTensorData(new_tensor), |
| TfLiteOpaqueTensorByteSize(new_tensor)), |
| "setting new operand value"); |
| |
| return kTfLiteOk; |
| } |
| |
| template <typename T> |
| TfLiteStatus AddNewInputConstantTensor( |
| int32_t nn_type, TfLiteType type, std::initializer_list<int> dims, |
| const std::vector<T>& tensor_value, |
| const TfLiteQuantizationParams& quant_params, int* tensor_index) { |
| TfLiteIntArray* dim_array = TfLiteIntArrayCreate(dims.size()); |
| dim_array->size = dims.size(); |
| std::copy(dims.begin(), dims.end(), dim_array->data); |
| |
| const auto result = AddNewInputConstantTensor( |
| nn_type, type, dim_array, tensor_value, quant_params, tensor_index); |
| TfLiteIntArrayFree(dim_array); |
| return result; |
| } |
| |
| TfLiteStatus AddIntermediateOutputTensor(TfLiteType tfl_type, |
| uint32_t dimension_count, |
| const uint32_t* dimension_data, |
| float scale, int32_t zero_point, |
| int* ann_index_out) { |
| int32_t nn_type; |
| switch (tfl_type) { |
| case kTfLiteFloat32: |
| nn_type = NEURON_TENSOR_FLOAT32; |
| break; |
| case kTfLiteInt8: |
| if (zero_point != 0) { |
| nn_type = NEURON_TENSOR_QUANT8_ASYMM_SIGNED; |
| } else { |
| nn_type = NEURON_TENSOR_QUANT8_SYMM; |
| } |
| break; |
| case kTfLiteUInt8: |
| nn_type = NEURON_TENSOR_QUANT8_ASYMM; |
| break; |
| default: |
| return kTfLiteError; |
| } |
| TF_LITE_OPAQUE_ENSURE_STATUS( |
| AddAdditionalOutputTensor(dimension_count, dimension_data, nn_type, |
| scale, zero_point, ann_index_out)); |
| return kTfLiteOk; |
| } |
| |
| private: |
| // Returns a TF Lite type which has the same memory representation as a |
| // provided Neuron type. |
| TfLiteStatus GetEquivalentToNeuronType(TfLiteOpaqueContext* context, |
| int nn_type, TfLiteType* type) { |
| switch (nn_type) { |
| case NEURON_INT32: |
| *type = kTfLiteInt32; |
| return kTfLiteOk; |
| case NEURON_FLOAT32: |
| *type = kTfLiteFloat32; |
| return kTfLiteOk; |
| default: |
| TfLiteOpaqueContextReportError( |
| context, |
| "Neuron Delegate: Can't get an equivalent TF Lite " |
| "type for provided Neuron type: %d.\n", |
| nn_type); |
| return kTfLiteError; |
| } |
| } |
| |
| template <typename T> |
| TfLiteStatus AddScalarOperand(T value, int32_t nn_type) { |
| NeuronOperandType operand_type{.type = nn_type}; |
| RETURN_TFLITE_ERROR_IF_NEURON_ERROR( |
| context_, neuronapi_->NeuronModel_addOperand(nn_model_, &operand_type), |
| "adding operand"); |
| const int neuron_index = operand_mapping_->add_new_non_tensor_operand(); |
| RETURN_TFLITE_ERROR_IF_NEURON_ERROR( |
| context_, |
| neuronapi_->NeuronModel_setOperandValue(nn_model_, neuron_index, &value, |
| sizeof(T)), |
| "setting new operand value"); |
| augmented_inputs_.push_back(neuron_index); |
| return kTfLiteOk; |
| } |
| |
| template <typename T> |
| TfLiteStatus AddVectorOperand(const T* values, uint32_t num_values, |
| int32_t nn_type, float scale, |
| int32_t zero_point) { |
| NeuronOperandType operand_type{.type = nn_type, |
| .dimensionCount = 1, |
| .dimensions = &num_values, |
| .scale = scale, |
| .zeroPoint = zero_point}; |
| |
| RETURN_TFLITE_ERROR_IF_NEURON_ERROR( |
| context_, neuronapi_->NeuronModel_addOperand(nn_model_, &operand_type), |
| "adding operand"); |
| |
| const int neuron_index = operand_mapping_->add_new_non_tensor_operand(); |
| RETURN_TFLITE_ERROR_IF_NEURON_ERROR( |
| context_, |
| neuronapi_->NeuronModel_setOperandValue(nn_model_, neuron_index, values, |
| sizeof(T) * num_values), |
| "settings new operand value"); |
| augmented_inputs_.push_back(neuron_index); |
| return kTfLiteOk; |
| } |
| |
| template <typename T> |
| TfLiteStatus AddVectorOperand(const T* values, uint32_t num_values, |
| int32_t nn_type) { |
| return AddVectorOperand(values, num_values, nn_type, /*scale=*/0.f, |
| /*zero_point=*/0); |
| } |
| |
| TfLiteStatus AddFloat32OutputTensor(uint32_t dimension_count, |
| const uint32_t* dimension_data, |
| int* ann_index_out) { |
| return AddAdditionalOutputTensor( |
| dimension_count, dimension_data, NEURON_TENSOR_FLOAT32, |
| /*scale=*/0.f, /*zero_point=*/0, ann_index_out); |
| } |
| |
| TfLiteStatus AddAdditionalOutputTensor(uint32_t dimension_count, |
| const uint32_t* dimension_data, |
| int32_t nn_type, float scale, |
| int32_t zero_point, |
| int* ann_index_out) { |
| NeuronOperandType operand_type{ |
| .type = nn_type, |
| .dimensionCount = dimension_count, |
| .dimensions = dimension_data, |
| .scale = scale, |
| .zeroPoint = zero_point, |
| }; |
| RETURN_TFLITE_ERROR_IF_NEURON_ERROR( |
| context_, neuronapi_->NeuronModel_addOperand(nn_model_, &operand_type), |
| "adding operand"); |
| const int neuron_index = operand_mapping_->add_new_non_tensor_operand(); |
| augmented_outputs_.push_back(neuron_index); |
| if (ann_index_out) *ann_index_out = neuron_index; |
| return kTfLiteOk; |
| } |
| |
| // Handles the int64 type by converting to int32. |
| TfLiteStatus ConvertInt64ToInt32(const int64_t* input, int32_t* output, |
| size_t num_elements) { |
| for (size_t i = 0; i < num_elements; ++i) { |
| if (input[i] <= std::numeric_limits<int32_t>::max() |
| && input[i] >= std::numeric_limits<int32_t>::min()) { |
| output[i] = static_cast<int32_t>(input[i]); |
| } else { |
| // Exceeds int32_t range, return error. |
| return kTfLiteError; |
| } |
| } |
| return kTfLiteOk; |
| } |
| |
| // Adds a new Neuron tensor that shadows the TF Lite tensor |
| // `tensor_index`. This returns the Neuron tensor index corresponding to |
| // the created tensor. If another caller previously created a Neuron |
| // tensor for `tensor_index` then the existing one is returned. |
| TfLiteStatus AddTensor(int tensor_index, bool hybrid_op, |
| std::vector<uint32_t>* indices, int tensor_flags = 0) { |
| const bool scalar_as_tensor = |
| tensor_flags & NN_TENSOR_FLAG_SCALAR_AS_TENSOR; |
| const bool need_int8_conversion = |
| tensor_flags & NN_TENSOR_FLAG_INT8_CONVERSION; |
| const bool use_int8_asymm_signed = |
| tensor_flags & NN_TENSOR_FLAG_USE_INT8_ASYMM_SIGNED; |
| const bool force_per_channel = |
| tensor_flags & NN_TENSOR_FLAG_FORCE_PER_CHANNEL; |
| const bool need_int64_conversion = |
| tensor_flags & NN_TENSOR_FLAG_INT64_CONVERSION; |
| int neuron_tensor_index = |
| operand_mapping_->lite_index_to_neuron(tensor_index); |
| if (neuron_tensor_index != -1) { |
| indices->push_back(neuron_tensor_index); |
| return kTfLiteOk; |
| } |
| |
| // Allocate a new tensor index |
| neuron_tensor_index = |
| operand_mapping_->add_new_neuron_tensor_index(tensor_index); |
| |
| // Parameters needed for new type. |
| int32_t nn_type = 0; |
| float scale = 0.0f; |
| int32_t zeroPoint = 0; |
| NeuronSymmPerChannelQuantParams ann_perchannel_params; |
| TfLiteOpaqueTensor* tensor = |
| TfLiteOpaqueContextGetOpaqueTensor(context_, tensor_index); |
| TfLiteType tensor_type = TfLiteOpaqueTensorType(tensor); |
| if (hybrid_op && (tensor_type == kTfLiteUInt8)) { |
| // For legacy reason, UINT8 weights in hybrid operators are actually INT8 |
| // values and should be interpreted as such. |
| tensor_type = kTfLiteInt8; |
| } |
| switch (tensor_type) { |
| case kTfLiteNoType: |
| // Tensors added during initialization of Ops don't have a type yet and |
| // should not be registered with the Neuron. |
| indices->push_back(-1); |
| return kTfLiteOk; |
| case kTfLiteFloat32: |
| nn_type = NEURON_TENSOR_FLOAT32; |
| break; |
| case kTfLiteFloat16: |
| nn_type = NEURON_TENSOR_FLOAT16; |
| break; |
| case kTfLiteUInt8: |
| nn_type = NEURON_TENSOR_QUANT8_ASYMM; |
| scale = TfLiteOpaqueTensorGetQuantizationParams(tensor).scale; |
| zeroPoint = TfLiteOpaqueTensorGetQuantizationParams(tensor).zero_point; |
| if (scale == 0) { |
| // TENSOR_QUANT8_ASYMM and NEURON_TENSOR_QUANT8_ASYMM |
| // with zero scale are not valid in Neuron. |
| scale = 1; |
| } |
| break; |
| case kTfLiteInt8: |
| // If explicit int8 conversion is needed, we still need |
| // NEURON_TENSOR_QUANT8_ASYMM type. |
| if (use_int8_asymm_signed) { |
| nn_type = NEURON_TENSOR_QUANT8_ASYMM_SIGNED; |
| } else if (need_int8_conversion) { |
| nn_type = NEURON_TENSOR_QUANT8_ASYMM; |
| } else { |
| nn_type = NEURON_TENSOR_QUANT8_SYMM; |
| } |
| scale = TfLiteOpaqueTensorGetQuantizationParams(tensor).scale; |
| zeroPoint = TfLiteOpaqueTensorGetQuantizationParams(tensor).zero_point; |
| if (TfLiteOpaqueTensorGetQuantization(tensor).type == |
| kTfLiteAffineQuantization) { |
| TfLiteAffineQuantization* quantization_params = |
| static_cast<TfLiteAffineQuantization*>( |
| TfLiteOpaqueTensorGetQuantization(tensor).params); |
| if (quantization_params->scale->size > 1 || force_per_channel) { |
| // Set up per-channel quantization. |
| ann_perchannel_params = { |
| .channelDim = static_cast<uint32_t>( |
| quantization_params->quantized_dimension), |
| .scaleCount = |
| static_cast<uint32_t>(quantization_params->scale->size), |
| .scales = quantization_params->scale->data, |
| }; |
| nn_type = NEURON_TENSOR_QUANT8_SYMM_PER_CHANNEL; |
| scale = 0.0f; |
| zeroPoint = 0; |
| } else if (quantization_params->scale->size == 1) { |
| scale = quantization_params->scale->data[0]; |
| zeroPoint = quantization_params->zero_point->data[0]; |
| } |
| } |
| if (nn_type != NEURON_TENSOR_QUANT8_SYMM_PER_CHANNEL) { |
| if (need_int8_conversion) { |
| zeroPoint += 128; |
| operand_mapping_->add_type_conversion(tensor_index, kTfLiteUInt8); |
| } |
| if (scale == 0) { |
| // QUANT8 tensors with zero scale are not valid in Neuron. |
| scale = 1; |
| } |
| if (zeroPoint != 0) { |
| nn_type = NEURON_TENSOR_QUANT8_ASYMM_SIGNED; |
| } else { |
| nn_type = NEURON_TENSOR_QUANT8_SYMM; |
| } |
| } |
| break; |
| case kTfLiteInt64: |
| // Currently, we are maintaining int64 the same as int32 in AddTensor |
| // to prevent DTS test case conv2D bias64 core dump. |
| case kTfLiteInt32: |
| nn_type = NEURON_TENSOR_INT32; |
| scale = TfLiteOpaqueTensorGetQuantizationParams(tensor).scale; |
| zeroPoint = TfLiteOpaqueTensorGetQuantizationParams(tensor).zero_point; |
| break; |
| case kTfLiteBool: |
| nn_type = NEURON_TENSOR_BOOL8; |
| break; |
| case kTfLiteInt16: |
| scale = TfLiteOpaqueTensorGetQuantizationParams(tensor).scale; |
| zeroPoint = TfLiteOpaqueTensorGetQuantizationParams(tensor).zero_point; |
| if (scale == 0) { |
| scale = 1; |
| } |
| if (zeroPoint != 0) { |
| nn_type = NEURON_EXT_TENSOR_QUANT16_ASYMM_SIGNED; |
| } else { |
| nn_type = NEURON_TENSOR_QUANT16_SYMM; |
| } |
| break; |
| case kTfLiteUInt16: |
| nn_type = NEURON_TENSOR_QUANT16_ASYMM; |
| scale = TfLiteOpaqueTensorGetQuantizationParams(tensor).scale; |
| zeroPoint = TfLiteOpaqueTensorGetQuantizationParams(tensor).zero_point; |
| if (scale == 0) { |
| scale = 1; |
| } |
| break; |
| default: |
| TfLiteOpaqueContextReportError( |
| context_, "Failed to add Neuron tensor: type %s is not supported.", |
| TfLiteTypeGetName(tensor_type)); |
| return kTfLiteError; |
| } |
| uint32_t tensor_rank = |
| static_cast<uint32_t>(TfLiteOpaqueTensorNumDims(tensor)); |
| |
| std::vector<int32_t> full_dim = TfLiteOpaqueTensorFullDim(tensor); |
| uint32_t* tensor_dims = reinterpret_cast<uint32_t*>(full_dim.data()); |
| if (scalar_as_tensor && tensor_rank == 0) { |
| // Use rank 1, shape {1} operand for TFLite scalar tensors. |
| tensor_rank = 1; |
| tensor_dims = &tensor_rank; |
| } |
| if (tensor_rank == 0) { |
| // if the tensor_rank is 0, the dimension ptr must be nullptr. |
| tensor_dims = nullptr; |
| } |
| NeuronOperandType operand_type{nn_type, tensor_rank, tensor_dims, scale, |
| zeroPoint}; |
| RETURN_TFLITE_ERROR_IF_NEURON_ERROR( |
| context_, neuronapi_->NeuronModel_addOperand(nn_model_, &operand_type), |
| "adding operand"); |
| if (nn_type == NEURON_TENSOR_QUANT8_SYMM_PER_CHANNEL) { |
| RETURN_TFLITE_ERROR_IF_NEURON_ERROR( |
| context_, |
| neuronapi_->NeuronModel_setOperandSymmPerChannelQuantParams( |
| nn_model_, neuron_tensor_index, &ann_perchannel_params), |
| "setting new operand per channel quantization params"); |
| } |
| if (TfLiteOpaqueTensorGetAllocationType(tensor) == kTfLiteMmapRo) { |
| if (IsQuantized(tensor_type) && need_int8_conversion) { |
| // We need to to add a tensor and convert the weights into uint8. |
| // Currently this is only needed for fully_connected. The new_tensor is |
| // needed for lifetime management for the converted weights. |
| TfLiteOpaqueTensorBuilder* tensor_builder = |
| TfLiteOpaqueTensorBuilderCreate(); |
| TfLiteOpaqueTensorBuilderSetType(tensor_builder, kTfLiteUInt8); |
| TfLiteOpaqueTensorBuilderSetAllocationType(tensor_builder, |
| kTfLiteDynamic); |
| TfLiteQuantizationParams quant_params{ |
| .scale = scale, |
| .zero_point = zeroPoint, |
| }; |
| TfLiteOpaqueTensorBuilderSetQuantizationParams(tensor_builder, |
| quant_params); |
| // The dynamic tensor need to set the tensor data. Set a tensor pointer |
| // and set the data value later after resizeing tensor dimension. |
| void* tensor_data = malloc(TfLiteOpaqueTensorByteSize(tensor)); |
| TfLiteOpaqueTensorBuilderSetData(tensor_builder, tensor_data); |
| |
| int new_tensor_index = -1; |
| TF_LITE_OPAQUE_ENSURE_STATUS(TfLiteOpaqueContextAddTensor( |
| context_, tensor_builder, &new_tensor_index)); |
| TfLiteOpaqueTensorBuilderDelete(tensor_builder); |
| TfLiteOpaqueTensor* new_tensor = |
| TfLiteOpaqueContextGetOpaqueTensor(context_, new_tensor_index); |
| |
| // Not removing the new tensor in case of resizing errors since it will |
| // be cleared by the context |
| TfLiteIntArray* dim_array = |
| TfLiteIntArrayCreate(TfLiteOpaqueTensorNumDims(tensor)); |
| for (int i = 0; i < dim_array->size; ++i) { |
| dim_array->data[i] = TfLiteOpaqueTensorDim(tensor, i); |
| } |
| TF_LITE_OPAQUE_ENSURE_STATUS( |
| TfLiteOpaqueContextResizeTensor(context_, new_tensor, |
| // Resize Tensor takes ownership of |
| // the dims array passed as param |
| dim_array)); |
| |
| // Convert the int8 value into corresponding uint8 value; |
| const auto num_elements = NumElements(tensor); |
| const auto* ori_tensor_data = |
| reinterpret_cast<int32_t*>(TfLiteOpaqueTensorData(tensor)); |
| auto* new_tensor_data = |
| reinterpret_cast<uint8_t*>(TfLiteOpaqueTensorData(new_tensor)); |
| for (int i = 0; i < num_elements; ++i) { |
| new_tensor_data[i] = |
| static_cast<const uint8_t>(ori_tensor_data[i] + 128); |
| } |
| RETURN_TFLITE_ERROR_IF_NEURON_ERROR( |
| context_, |
| neuronapi_->NeuronModel_setOperandValue( |
| nn_model_, neuron_tensor_index, new_tensor_data, |
| TfLiteOpaqueTensorByteSize(new_tensor)), |
| "setting new operand value"); |
| } else if (IsInt64(tensor_type) && need_int64_conversion) { |
| size_t byte_size = TfLiteOpaqueTensorByteSize(tensor); |
| const size_t element_size = sizeof(int64_t); |
| size_t num_elements = byte_size / element_size; |
| std::vector<int32_t> int32_data(num_elements); |
| |
| TF_LITE_OPAQUE_ENSURE_STATUS( |
| ConvertInt64ToInt32( |
| reinterpret_cast<const int64_t*>( |
| TfLiteOpaqueTensorData(tensor)), |
| int32_data.data(), num_elements)); |
| |
| RETURN_TFLITE_ERROR_IF_NEURON_ERROR( |
| context_, |
| neuronapi_->NeuronModel_setOperandValue( |
| nn_model_, neuron_tensor_index, int32_data.data(), |
| num_elements * sizeof(int32_t)), |
| "setting new operand value"); |
| } else { |
| RETURN_TFLITE_ERROR_IF_NEURON_ERROR( |
| context_, |
| neuronapi_->NeuronModel_setOperandValue( |
| nn_model_, neuron_tensor_index, TfLiteOpaqueTensorData(tensor), |
| TfLiteOpaqueTensorByteSize(tensor)), |
| "setting new operand value"); |
| } |
| } |
| |
| indices->push_back(neuron_tensor_index); |
| return kTfLiteOk; |
| } |
| |
| // Access to Neuron. |
| const NeuronApi* const neuronapi_; |
| |
| // TfLiteOpaqueContext for error handling. |
| TfLiteOpaqueContext* const context_; |
| |
| // Tracks relationship between indices. |
| OperandMapping* const operand_mapping_; |
| |
| // Keeps mapping of ANN quantized tensor and float data type to equivalent |
| // dequantized ANN tensor. For example, tensor #4 (UINT8) + FLOAT32 could map |
| // to tensor #10 (FLOAT32) because a DEQUANTIZE operator was added to convert |
| // tensor #4 to a FLOAT32 tensor. |
| DequantizeMapping* const dequantize_mapping_; |
| |
| // The Neuron model. |
| NeuronModel* const nn_model_; |
| |
| // Inputs and outputs for the current op. These are augmented in the sense |
| // that Neuron uses operands for all arguments, not just tensors, unlike |
| // TensorFlow Lite. |
| std::vector<uint32_t> augmented_inputs_; |
| std::vector<uint32_t> augmented_outputs_; |
| |
| // Number of the ops |
| int total_op_num_ = 0; |
| }; |
| |
| } // namespace neuron |
| } // namespace tflite |
| |
| #endif // DELEGATE_MTK_NEURON_NEURON_DELEGATE_BUILDER_H_ |