/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ #include #include #include #include #include "tensorflow/lite/core/c/builtin_op_data.h" #include "tensorflow/lite/core/c/common.h" #include "tensorflow/lite/kernels/cpu_backend_context.h" #include "tensorflow/lite/kernels/internal/compatibility.h" // NOLINTNEXTLINE - This header file shouldn't go to the top. #include "tensorflow/lite/kernels/internal/optimized/integer_ops/transpose_conv.h" #include "tensorflow/lite/kernels/internal/optimized/optimized_ops.h" // NOLINTNEXTLINE - This header file shouldn't go to the top. #include "tensorflow/lite/kernels/internal/portable_tensor_utils.h" #include "tensorflow/lite/kernels/internal/reference/integer_ops/transpose_conv.h" #include "tensorflow/lite/kernels/internal/reference/reference_ops.h" #include "tensorflow/lite/kernels/internal/reference/transpose_conv.h" #include "tensorflow/lite/kernels/internal/tensor.h" #include "tensorflow/lite/kernels/internal/tensor_ctypes.h" #include "tensorflow/lite/kernels/internal/types.h" #include "tensorflow/lite/kernels/kernel_util.h" #include "tensorflow/lite/kernels/padding.h" #include "tensorflow/lite/util.h" namespace tflite { namespace ops { namespace builtin { namespace transpose_conv { // This file has 2 implementation of TransposeConv. enum KernelType { kReference, kGenericOptimized, // Neon-free }; constexpr int kOutputShapeTensor = 0; constexpr int kWeightsTensor = 1; constexpr int kDataInputTensor = 2; constexpr int kBiasTensor = 3; constexpr int kOutputTensor = 0; const int kTensorNotAllocated = -1; struct OpData { // IDs are the arbitrary identifiers used by TF Lite to identify and access // memory buffers. int col2im_id = kTensorNotAllocated; int transposed_weights_id = kTensorNotAllocated; int scratch_tensor_id = kTensorNotAllocated; int input_quantized_id = kTensorNotAllocated; int scaling_factors_id = kTensorNotAllocated; int input_offset_id = kTensorNotAllocated; // col2im is the temporary tensor allocated and used in optimized path for // storing col2im data:gemm result for input_matrix x filter_matrix. int32_t col2im_index; // TfLiteConverter will transpose weights from HWOI to OHWI order. // In optimized path, we will transpose them back to HWOI, this temporary // tensor is allocated for storing transposed weights. int32_t transposed_weights_index; // Scratch tensor is used in the quantized path for storing accumulation // results. int32_t scratch_tensor_index; // Indexes are used for hybrid (dynamic range quantization) path. int32_t input_quantized_index; int32_t scaling_factors_index; int32_t input_offset_index; TfLitePaddingValues padding; // The scaling factor from input to output (aka the 'real multiplier') can // be represented as a fixed point multiplier plus a left shift. int32_t output_multiplier; int output_shift; // Per channel output multiplier and shift. std::vector per_channel_output_multiplier; std::vector per_channel_output_shift; // The range of the fused activation layer. For example for kNone and // uint8_t these would be 0 and 255. int32_t output_activation_min; int32_t output_activation_max; bool has_col2im = false; bool weights_are_transposed = false; TfLiteType quantized_bias_type = kTfLiteNoType; }; void* Init(TfLiteContext* context, const char* buffer, size_t length) { return new OpData; } void Free(TfLiteContext* context, void* buffer) { delete reinterpret_cast(buffer); } TfLiteStatus ResizeTensor(TfLiteContext* context, const TfLiteTensor* shape_tensor, TfLiteTensor* tensor_to_resize) { // Currently only support int32 for output shape. if (shape_tensor->type != kTfLiteInt32) { TF_LITE_KERNEL_LOG(context, "Output shape is %s, not int32.", TfLiteTypeGetName(shape_tensor->type)); return kTfLiteError; } std::unique_ptr shape( TfLiteIntArrayCreate(NumElements(shape_tensor)), TfLiteIntArrayFree); for (int i = 0; i < shape->size; ++i) { shape->data[i] = GetTensorData(shape_tensor)[i]; } return context->ResizeTensor(context, tensor_to_resize, shape.release()); } // Allocate temporary tensors if necessary. template static TfLiteStatus AllocateTemporaryTensorsIfRequired(TfLiteContext* context, TfLiteType input_type, TfLiteType weights_type, TfLiteNode* node) { OpData* data = reinterpret_cast(node->user_data); int temporaries_count = 0; // Allocate col2im tensor. Currently it's only used for optimized kernels. if (kernel_type == kGenericOptimized) { if (data->col2im_id == kTensorNotAllocated) { context->AddTensors(context, 1, &data->col2im_id); } data->col2im_index = temporaries_count; data->has_col2im = true; ++temporaries_count; } // Allocate transposed_weights tensor. Currently it's only used for optimized // float kernels. if (kernel_type == kGenericOptimized) { if (data->transposed_weights_id == kTensorNotAllocated) { context->AddTensors(context, 1, &data->transposed_weights_id); } data->transposed_weights_index = temporaries_count; data->weights_are_transposed = true; ++temporaries_count; } // Allocate scratch buffer tensor if (input_type == kTfLiteUInt8 || input_type == kTfLiteInt8 || input_type == kTfLiteInt16) { if (data->scratch_tensor_id == kTensorNotAllocated) { context->AddTensors(context, 1, &data->scratch_tensor_id); } data->scratch_tensor_index = temporaries_count; ++temporaries_count; } if (input_type == kTfLiteFloat32 && weights_type == kTfLiteInt8) { // Allocate tensor to store the on-the-fly quantized inputs. data->input_quantized_index = temporaries_count; if (data->input_quantized_id == kTensorNotAllocated) { TF_LITE_ENSURE_OK( context, context->AddTensors(context, 1, &data->input_quantized_id)); } ++temporaries_count; // Allocate tensor to store the quantization params computed during // on-the-fly input quantization. data->scaling_factors_index = temporaries_count; if (data->scaling_factors_id == kTensorNotAllocated) { TF_LITE_ENSURE_OK( context, context->AddTensors(context, 1, &data->scaling_factors_id)); } ++temporaries_count; data->input_offset_index = temporaries_count; if (data->input_offset_id == kTensorNotAllocated) { TF_LITE_ENSURE_OK( context, context->AddTensors(context, 1, &data->input_offset_id)); } ++temporaries_count; } TfLiteIntArrayFree(node->temporaries); node->temporaries = TfLiteIntArrayCreate(temporaries_count); return kTfLiteOk; } TfLiteStatus ResizeCol2ImTensor(TfLiteContext* context, const TfLiteTensor* output_shape, const TfLiteTensor* weights, const TfLiteTensor* input, TfLiteTensor* col2im) { if (output_shape->type != kTfLiteInt32) { TF_LITE_KERNEL_LOG(context, "col2im shape is %s, not int32.", TfLiteTypeGetName(output_shape->type)); return kTfLiteError; } TF_LITE_ENSURE_EQ(context, NumElements(output_shape), 4); const RuntimeShape& input_shape = GetTensorShape(input); const RuntimeShape& weights_shape = GetTensorShape(weights); int col2im_rows = 0; TF_LITE_ENSURE_MSG(context, input_shape.CheckedNumElementsInRange( /*start=*/1, /*end=*/3, col2im_rows), "%s", "TransposeConv col2im tensor has too many rows."); int col2im_columns = 0; TF_LITE_ENSURE_MSG( context, weights_shape.CheckedSizeToDimension(/*end=*/3, col2im_columns), "%s", "TransposeConv col2im tensor has too many columns."); std::unique_ptr col2im_shape_array( TfLiteIntArrayCreate(2), TfLiteIntArrayFree); col2im_shape_array->data[0] = col2im_rows; col2im_shape_array->data[1] = col2im_columns; col2im->type = input->type == kTfLiteFloat32 ? kTfLiteFloat32 : kTfLiteInt32; col2im->allocation_type = kTfLiteDynamic; return context->ResizeTensor(context, col2im, col2im_shape_array.release()); } TfLiteStatus ResizeAndTransposeWeights(TfLiteContext* context, const TfLiteTensor* weights, TfLiteTensor* transposed_weights) { std::unique_ptr transposed_weights_shape_array(TfLiteIntArrayCreate(4), TfLiteIntArrayFree); const RuntimeShape& input_shape = GetTensorShape(weights); transposed_weights_shape_array->data[0] = input_shape.Dims(1); transposed_weights_shape_array->data[1] = input_shape.Dims(2); transposed_weights_shape_array->data[2] = input_shape.Dims(0); transposed_weights_shape_array->data[3] = input_shape.Dims(3); transposed_weights->type = weights->type; transposed_weights->allocation_type = kTfLiteDynamic; TF_LITE_ENSURE_STATUS(context->ResizeTensor( context, transposed_weights, transposed_weights_shape_array.release())); // Transpose the weights from OHWI order to HWOI order. TransposeParams transpose_params; transpose_params.perm_count = 4; transpose_params.perm[0] = 1; transpose_params.perm[1] = 2; transpose_params.perm[2] = 0; transpose_params.perm[3] = 3; if (weights->type == kTfLiteFloat32) { optimized_ops::Transpose(transpose_params, input_shape, GetTensorData(weights), GetTensorShape(transposed_weights), GetTensorData(transposed_weights)); } else if (weights->type == kTfLiteUInt8) { optimized_ops::Transpose(transpose_params, input_shape, GetTensorData(weights), GetTensorShape(transposed_weights), GetTensorData(transposed_weights)); } else if (weights->type == kTfLiteInt8) { // int16 transpose_conv also with int8 weights optimized_ops::Transpose(transpose_params, input_shape, GetTensorData(weights), GetTensorShape(transposed_weights), GetTensorData(transposed_weights)); } else { TF_LITE_KERNEL_LOG( context, "Only float32, uint8, int8, int16 is supported currently, got %s.", TfLiteTypeGetName(weights->type)); return kTfLiteError; } return kTfLiteOk; } template TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { OpData* data = reinterpret_cast(node->user_data); auto* params = reinterpret_cast(node->builtin_data); bool has_bias = NumInputs(node) == 4; // Sanity checks on op TF_LITE_ENSURE(context, has_bias || NumInputs(node) == 3); TF_LITE_ENSURE_EQ(context, NumOutputs(node), 1); // Retrieve tensors const TfLiteTensor* output_shape; TF_LITE_ENSURE_OK( context, GetInputSafe(context, node, kOutputShapeTensor, &output_shape)); const TfLiteTensor* weights; TF_LITE_ENSURE_OK(context, GetInputSafe(context, node, kWeightsTensor, &weights)); const TfLiteTensor* input; TF_LITE_ENSURE_OK(context, GetInputSafe(context, node, kDataInputTensor, &input)); const TfLiteTensor* bias = nullptr; TfLiteTensor* output; TF_LITE_ENSURE_OK(context, GetOutputSafe(context, node, kOutputTensor, &output)); // Tensor sanity checks TF_LITE_ENSURE_EQ(context, NumDimensions(output_shape), 1); TF_LITE_ENSURE_EQ(context, NumDimensions(input), 4); TF_LITE_ENSURE_EQ(context, NumDimensions(weights), 4); TF_LITE_ENSURE(context, input->type == kTfLiteFloat32 || input->type == kTfLiteUInt8 || input->type == kTfLiteInt8 || input->type == kTfLiteInt16); if (has_bias) { bias = GetOptionalInputTensor(context, node, kBiasTensor); if (bias) { if (input->type == kTfLiteUInt8 || input->type == kTfLiteInt8) { TF_LITE_ENSURE_TYPES_EQ(context, bias->type, kTfLiteInt32); if (input->type == kTfLiteInt8) { TF_LITE_ENSURE_EQ(context, bias->params.zero_point, 0); } } else if (input->type == kTfLiteInt16) { TF_LITE_ENSURE(context, (bias->type == kTfLiteInt64) || (bias->type == kTfLiteInt32)); TF_LITE_ENSURE_EQ(context, bias->params.zero_point, 0); } else { TF_LITE_ENSURE_TYPES_EQ(context, bias->type, input->type); } TF_LITE_ENSURE_EQ(context, NumElements(bias), SizeOfDimension(weights, 0)); } } if (input->type == kTfLiteInt16) { TF_LITE_ENSURE_EQ(context, weights->type, kTfLiteInt8); TF_LITE_ENSURE_EQ(context, input->params.zero_point, 0); TF_LITE_ENSURE_EQ(context, output->params.zero_point, 0); // Check quantized_bias_type is either kTfLiteInt64 or kTfLiteInt32. if (params->quantized_bias_type != kTfLiteFloat32) { TF_LITE_ENSURE(context, params->quantized_bias_type == kTfLiteInt32 || params->quantized_bias_type == kTfLiteInt64); TF_LITE_ENSURE(context, (bias == nullptr) || bias->type == params->quantized_bias_type); data->quantized_bias_type = params->quantized_bias_type; } } TF_LITE_ENSURE_TYPES_EQ(context, output->type, input->type); // Ensure that weights and inputs have the same channel dimension. // Note: TOCO will reorder weights in the following format: OHWI. TF_LITE_ENSURE_EQ(context, SizeOfDimension(input, 3), SizeOfDimension(weights, 3)); // Allocate col2Im, transposed_weights & scratch Tensor. TF_LITE_ENSURE_STATUS(AllocateTemporaryTensorsIfRequired( context, input->type, weights->type, node)); OpData* user_data = reinterpret_cast(node->user_data); TfLiteTensor* col2im = nullptr; if (data->has_col2im) { node->temporaries->data[data->col2im_index] = data->col2im_id; TF_LITE_ENSURE_OK( context, GetTemporarySafe(context, node, user_data->col2im_index, &col2im)); } if (!IsConstantTensor(output_shape)) { // Defer resizing until Eval(). SetTensorToDynamic(output); if (data->has_col2im) { SetTensorToDynamic(col2im); } } else { TF_LITE_ENSURE_STATUS(ResizeTensor(context, output_shape, output)); if (data->has_col2im) { TF_LITE_ENSURE_STATUS( ResizeCol2ImTensor(context, output_shape, weights, input, col2im)); } } if (data->weights_are_transposed) { node->temporaries->data[data->transposed_weights_index] = data->transposed_weights_id; TfLiteTensor* transposed_weights; TF_LITE_ENSURE_OK( context, GetTemporarySafe(context, node, user_data->transposed_weights_index, &transposed_weights)); if (!IsConstantTensor(weights)) { SetTensorToDynamic(transposed_weights); } else { ResizeAndTransposeWeights(context, weights, transposed_weights); } } if (input->type == kTfLiteUInt8 || input->type == kTfLiteInt8 || input->type == kTfLiteInt16) { node->temporaries->data[data->scratch_tensor_index] = data->scratch_tensor_id; TfLiteTensor* scratch_buffer; TF_LITE_ENSURE_OK( context, GetTemporarySafe(context, node, data->scratch_tensor_index, &scratch_buffer)); if (data->quantized_bias_type != kTfLiteNoType) { scratch_buffer->type = data->quantized_bias_type; } else if (input->type == kTfLiteInt16) { scratch_buffer->type = kTfLiteInt64; } else { scratch_buffer->type = kTfLiteInt32; } scratch_buffer->allocation_type = kTfLiteDynamic; if (!IsConstantTensor(output_shape)) { SetTensorToDynamic(scratch_buffer); } else { TF_LITE_ENSURE_STATUS( ResizeTensor(context, output_shape, scratch_buffer)); } TF_LITE_ENSURE_EQ(context, weights->quantization.type, kTfLiteAffineQuantization); const auto* affine_quantization = reinterpret_cast( weights->quantization.params); const int channels_out = weights->dims->data[0]; TF_LITE_ENSURE(context, affine_quantization); TF_LITE_ENSURE(context, affine_quantization->scale); TF_LITE_ENSURE(context, (affine_quantization->scale->size == 1 || affine_quantization->scale->size == channels_out)); data->per_channel_output_multiplier.resize(channels_out); data->per_channel_output_shift.resize(channels_out); auto* params = reinterpret_cast(node->builtin_data); TF_LITE_ENSURE_STATUS(tflite::PopulateConvolutionQuantizationParams( context, input, weights, bias, output, params->activation, &data->output_multiplier, &data->output_shift, &data->output_activation_min, &data->output_activation_max, data->per_channel_output_multiplier.data(), data->per_channel_output_shift.data(), channels_out)); } if (input->type == kTfLiteFloat32 && weights->type == kTfLiteInt8) { node->temporaries->data[data->input_quantized_index] = data->input_quantized_id; TfLiteTensor* input_quantized; TF_LITE_ENSURE_OK( context, GetTemporarySafe(context, node, data->input_quantized_index, &input_quantized)); input_quantized->type = kTfLiteInt8; input_quantized->allocation_type = kTfLiteArenaRw; if (!TfLiteIntArrayEqual(input_quantized->dims, input->dims)) { TfLiteIntArray* input_quantized_size = TfLiteIntArrayCopy(input->dims); TF_LITE_ENSURE_OK(context, context->ResizeTensor(context, input_quantized, input_quantized_size)); } node->temporaries->data[data->scaling_factors_index] = data->scaling_factors_id; TfLiteTensor* scaling_factors; TF_LITE_ENSURE_OK( context, GetTemporarySafe(context, node, data->scaling_factors_index, &scaling_factors)); scaling_factors->type = kTfLiteFloat32; scaling_factors->allocation_type = kTfLiteArenaRw; // Only one scale factor per batch is typically necessary. See optimized // implementation for why we need to allocate for the height of the inputs // flattened to 2D. const int channels_in = weights->dims->data[3]; TF_LITE_ENSURE(context, channels_in != 0); int input_num_elements = 0; TF_LITE_ENSURE_MSG( context, CheckedNumElements(input, input_num_elements) == kTfLiteOk, "%s", "TransposeConv hybrid input has too many elements."); const int height = input_num_elements / channels_in; int scaling_dims[1] = {height}; if (!TfLiteIntArrayEqualsArray(scaling_factors->dims, 1, scaling_dims)) { std::unique_ptr scaling_factors_size(TfLiteIntArrayCreate(1), TfLiteIntArrayFree); scaling_factors_size->data[0] = height; TF_LITE_ENSURE_OK(context, context->ResizeTensor(context, scaling_factors, scaling_factors_size.release())); } auto* affine_quantization = reinterpret_cast( weights->quantization.params); TF_LITE_ENSURE(context, affine_quantization); TF_LITE_ENSURE(context, affine_quantization->scale); const int channels_out = weights->dims->data[affine_quantization->quantized_dimension]; if (affine_quantization->scale->size != channels_out) { TF_LITE_ENSURE_EQ(context, affine_quantization->scale->size, 1); TfLiteFloatArrayFree(affine_quantization->scale); affine_quantization->scale = TfLiteFloatArrayCreate(channels_out); for (int i = 0; i < channels_out; ++i) { affine_quantization->scale->data[i] = weights->params.scale; } } else { TF_LITE_ENSURE_EQ(context, affine_quantization->scale->size, channels_out); } node->temporaries->data[data->input_offset_index] = data->input_offset_id; TfLiteTensor* input_offsets; TF_LITE_ENSURE_OK(context, GetTemporarySafe(context, node, data->input_offset_index, &input_offsets)); input_offsets->type = kTfLiteInt32; input_offsets->allocation_type = kTfLiteArenaRw; // See above comment for the need to allocate for height of inputs. TF_LITE_ENSURE(context, channels_in != 0); const int input_offset_dims[1] = {height}; if (!TfLiteIntArrayEqualsArray(input_offsets->dims, 1, input_offset_dims)) { std::unique_ptr input_offsets_size(TfLiteIntArrayCreate(1), TfLiteIntArrayFree); input_offsets_size->data[0] = input_offset_dims[0]; TF_LITE_ENSURE_OK(context, context->ResizeTensor(context, input_offsets, input_offsets_size.release())); } } return kTfLiteOk; } template void EvalFloat(TfLiteContext* context, const TfLiteTransposeConvParams* params, const OpData* data, const TfLiteTensor* input, const TfLiteTensor* weights, const TfLiteTensor* bias, const TfLiteTensor* transposed_weights, TfLiteTensor* col2im, TfLiteTensor* output) { float output_activation_min, output_activation_max; CalculateActivationRange(params->activation, &output_activation_min, &output_activation_max); tflite::ConvParams op_params; op_params.padding_type = PaddingType::kSame; op_params.padding_values.width = data->padding.width; op_params.padding_values.height = data->padding.height; op_params.padding_values.width_offset = data->padding.width_offset; op_params.padding_values.height_offset = data->padding.height_offset; op_params.stride_width = params->stride_width; op_params.stride_height = params->stride_height; op_params.float_activation_min = output_activation_min; op_params.float_activation_max = output_activation_max; switch (kernel_type) { case kReference: { reference_ops::TransposeConv( op_params, GetTensorShape(input), GetTensorData(input), GetTensorShape(weights), GetTensorData(weights), GetTensorShape(bias), GetTensorData(bias), GetTensorShape(output), GetTensorData(output), GetTensorShape(col2im), GetTensorData(col2im)); break; } case kGenericOptimized: { optimized_ops::TransposeConvV2( op_params, GetTensorShape(input), GetTensorData(input), GetTensorShape(transposed_weights), GetTensorData(transposed_weights), GetTensorShape(bias), GetTensorData(bias), GetTensorShape(output), GetTensorData(output), GetTensorShape(col2im), GetTensorData(col2im), CpuBackendContext::GetFromContext(context)); break; } } } template void EvalQuantized(TfLiteContext* context, const TfLiteTransposeConvParams* params, OpData* data, const TfLiteTensor* input, const TfLiteTensor* weights, const TfLiteTensor* transposed_weights, const TfLiteTensor* bias, TfLiteTensor* col2im, TfLiteTensor* output, TfLiteTensor* scratch_buffer) { int32_t input_offset = -input->params.zero_point; int32_t filter_offset = -weights->params.zero_point; int32_t output_offset = output->params.zero_point; tflite::ConvParams op_params; op_params.padding_type = PaddingType::kSame; op_params.padding_values.width = data->padding.width; op_params.padding_values.height = data->padding.height; op_params.padding_values.width_offset = data->padding.width_offset; op_params.padding_values.height_offset = data->padding.height_offset; op_params.stride_width = params->stride_width; op_params.stride_height = params->stride_height; op_params.input_offset = input_offset; op_params.output_offset = output_offset; op_params.weights_offset = filter_offset; op_params.output_multiplier = data->output_multiplier; op_params.output_shift = -data->output_shift; op_params.quantized_activation_min = data->output_activation_min; op_params.quantized_activation_max = data->output_activation_max; switch (kernel_type) { case kReference: { reference_ops::TransposeConv( op_params, GetTensorShape(input), GetTensorData(input), GetTensorShape(weights), GetTensorData(weights), GetTensorShape(bias), GetTensorData(bias), GetTensorShape(output), GetTensorData(output), GetTensorShape(col2im), GetTensorData(col2im), GetTensorData(scratch_buffer)); break; } case kGenericOptimized: { optimized_ops::TransposeConvV2( op_params, GetTensorShape(input), GetTensorData(input), GetTensorShape(transposed_weights), GetTensorData(transposed_weights), GetTensorShape(bias), GetTensorData(bias), GetTensorShape(output), GetTensorData(output), GetTensorShape(col2im), GetTensorData(col2im), GetTensorData(scratch_buffer), CpuBackendContext::GetFromContext(context)); break; } } } template void EvalQuantizedPerChannel( TfLiteContext* context, const TfLiteTransposeConvParams* params, OpData* data, const TfLiteTensor* input, const TfLiteTensor* weights, const TfLiteTensor* transposed_weights, const TfLiteTensor* bias, TfLiteTensor* col2im, TfLiteTensor* output, TfLiteTensor* scratch_buffer) { tflite::ConvParams op_params; op_params.padding_type = PaddingType::kSame; op_params.padding_values.width = data->padding.width; op_params.padding_values.height = data->padding.height; op_params.padding_values.width_offset = data->padding.width_offset; op_params.padding_values.height_offset = data->padding.height_offset; op_params.stride_width = params->stride_width; op_params.stride_height = params->stride_height; // Need to flip the sign of input offset to add it directly to the quantized // buffer. op_params.input_offset = -input->params.zero_point; op_params.output_offset = output->params.zero_point; op_params.quantized_activation_min = data->output_activation_min; op_params.quantized_activation_max = data->output_activation_max; switch (kernel_type) { case kReference: { reference_integer_ops::TransposeConv( op_params, data->per_channel_output_multiplier.data(), data->per_channel_output_shift.data(), GetTensorShape(input), GetTensorData(input), GetTensorShape(weights), GetTensorData(weights), GetTensorShape(bias), GetTensorData(bias), GetTensorShape(output), GetTensorData(output), GetTensorShape(col2im), GetTensorData(col2im), GetTensorData(scratch_buffer)); break; } case kGenericOptimized: { optimized_integer_ops::TransposeConvV2( op_params, data->per_channel_output_multiplier.data(), data->per_channel_output_shift.data(), GetTensorShape(input), GetTensorData(input), GetTensorShape(transposed_weights), GetTensorData(transposed_weights), GetTensorShape(bias), GetTensorData(bias), GetTensorShape(output), GetTensorData(output), GetTensorShape(col2im), GetTensorData(col2im), GetTensorData(scratch_buffer), CpuBackendContext::GetFromContext(context)); break; } } } template void EvalQuantizedPerChannel16x8( TfLiteContext* context, const TfLiteTransposeConvParams* params, OpData* data, const TfLiteTensor* input, const TfLiteTensor* weights, const TfLiteTensor* transposed_weights, const TfLiteTensor* bias, TfLiteTensor* col2im, TfLiteTensor* output, TfLiteTensor* scratch_buffer) { tflite::ConvParams op_params; op_params.padding_type = PaddingType::kSame; op_params.padding_values.width = data->padding.width; op_params.padding_values.height = data->padding.height; op_params.padding_values.width_offset = data->padding.width_offset; op_params.padding_values.height_offset = data->padding.height_offset; op_params.stride_width = params->stride_width; op_params.stride_height = params->stride_height; // Need to flip the sign of input offset to add it directly to the quantized // buffer. op_params.input_offset = -input->params.zero_point; op_params.output_offset = output->params.zero_point; op_params.quantized_activation_min = data->output_activation_min; op_params.quantized_activation_max = data->output_activation_max; // To prevent 32bit accum overflow for 16x8 quantization, it enables the // optimized path only when zero_point is 0. bool has_non_zero_point = input->params.zero_point || weights->params.zero_point || output->params.zero_point; if (data->quantized_bias_type == kTfLiteInt32) { if (kernel_type == kReference || has_non_zero_point) { reference_integer_ops::TransposeConv( op_params, data->per_channel_output_multiplier.data(), data->per_channel_output_shift.data(), GetTensorShape(input), GetTensorData(input), GetTensorShape(weights), GetTensorData(weights), GetTensorShape(bias), GetTensorData(bias), GetTensorShape(output), GetTensorData(output), GetTensorShape(col2im), GetTensorData(col2im), GetTensorData(scratch_buffer)); } else { optimized_integer_ops::TransposeConvV2( op_params, data->per_channel_output_multiplier.data(), data->per_channel_output_shift.data(), GetTensorShape(input), GetTensorData(input), GetTensorShape(transposed_weights), GetTensorData(transposed_weights), GetTensorShape(bias), GetTensorData(bias), GetTensorShape(output), GetTensorData(output), GetTensorShape(col2im), GetTensorData(col2im), GetTensorData(scratch_buffer), CpuBackendContext::GetFromContext(context)); } } else { TFLITE_DCHECK(!has_non_zero_point); // Fallback to reference kernel when bias_type is int64 as // there is no optimized kernel for int64 bias yet. reference_integer_ops::TransposeConv( op_params, data->per_channel_output_multiplier.data(), data->per_channel_output_shift.data(), GetTensorShape(input), GetTensorData(input), GetTensorShape(weights), GetTensorData(weights), GetTensorShape(bias), GetTensorData(bias), GetTensorShape(output), GetTensorData(output), GetTensorShape(col2im), GetTensorData(col2im), GetTensorData(scratch_buffer)); } } TfLiteStatus EvalHybrid(TfLiteContext* context, TfLiteNode* node, const TfLiteTransposeConvParams* params, OpData* data, const TfLiteTensor* input, const TfLiteTensor* weights, const TfLiteTensor* bias, TfLiteTensor* output) { float output_activation_min, output_activation_max; CalculateActivationRange(params->activation, &output_activation_min, &output_activation_max); const int batch_size = SizeOfDimension(input, 0); TF_LITE_ENSURE(context, batch_size != 0); const int input_size = NumElements(input) / batch_size; TfLiteTensor* quantized_input_tensor; TF_LITE_ENSURE_OK(context, GetTemporarySafe(context, node, data->input_quantized_index, &quantized_input_tensor)); int8_t* quantized_input_ptr_batch = GetTensorData(quantized_input_tensor); TfLiteTensor* scaling_factors_tensor; TF_LITE_ENSURE_OK(context, GetTemporarySafe(context, node, data->scaling_factors_index, &scaling_factors_tensor)); float* scaling_factors_ptr = GetTensorData(scaling_factors_tensor); TfLiteTensor* input_offset_tensor; TF_LITE_ENSURE_OK(context, GetTemporarySafe(context, node, data->input_offset_index, &input_offset_tensor)); int32_t* input_offset_ptr = GetTensorData(input_offset_tensor); for (int b = 0; b < batch_size; ++b) { const int offset = b * input_size; tensor_utils::AsymmetricQuantizeFloats( GetTensorData(input) + offset, input_size, quantized_input_ptr_batch + offset, &scaling_factors_ptr[b], &input_offset_ptr[b]); } const auto* affine_quantization = reinterpret_cast(weights->quantization.params); tflite::ConvParams op_params; op_params.padding_type = PaddingType::kSame; op_params.padding_values.width = data->padding.width; op_params.padding_values.height = data->padding.height; op_params.padding_values.width_offset = data->padding.width_offset; op_params.padding_values.height_offset = data->padding.height_offset; op_params.stride_width = params->stride_width; op_params.stride_height = params->stride_height; op_params.float_activation_min = output_activation_min; op_params.float_activation_max = output_activation_max; reference_ops::HybridTransposeConv( op_params, scaling_factors_ptr, GetTensorShape(input), quantized_input_ptr_batch, GetTensorShape(weights), GetTensorData(weights), GetTensorShape(bias), GetTensorData(bias), GetTensorShape(output), GetTensorData(output), affine_quantization->scale->data, input_offset_ptr); return kTfLiteOk; } template TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { // Retrieve tensors (All should be allocated by now) const TfLiteTensor* output_shape; TF_LITE_ENSURE_OK( context, GetInputSafe(context, node, kOutputShapeTensor, &output_shape)); const TfLiteTensor* weights; TF_LITE_ENSURE_OK(context, GetInputSafe(context, node, kWeightsTensor, &weights)); const TfLiteTensor* input; TF_LITE_ENSURE_OK(context, GetInputSafe(context, node, kDataInputTensor, &input)); const TfLiteTensor* bias = (NumInputs(node) == 4) ? GetOptionalInputTensor(context, node, kBiasTensor) : nullptr; TfLiteTensor* output; TF_LITE_ENSURE_OK(context, GetOutputSafe(context, node, kOutputTensor, &output)); OpData* data = reinterpret_cast(node->user_data); TfLiteTensor* col2im = data->has_col2im ? GetTemporary(context, node, data->col2im_index) : nullptr; TfLiteTensor* transposed_weights = data->weights_are_transposed ? GetTemporary(context, node, data->transposed_weights_index) : nullptr; const auto* params = reinterpret_cast(node->builtin_data); // Prevent divisions by 0 TF_LITE_ENSURE(context, params->stride_height > 0); TF_LITE_ENSURE(context, params->stride_width > 0); // Resize any deferred dynamic tensors if (IsDynamicTensor(output)) { TF_LITE_ENSURE_OK(context, ResizeTensor(context, output_shape, output)); } TF_LITE_ENSURE_EQ(context, SizeOfDimension(input, 0), SizeOfDimension(output, 0)); if (data->has_col2im && IsDynamicTensor(col2im)) { TF_LITE_ENSURE_OK(context, ResizeCol2ImTensor(context, output_shape, weights, input, col2im)); } // Get height and width of the output image. const int width = SizeOfDimension(output, 2); const int height = SizeOfDimension(output, 1); const int filter_width = SizeOfDimension(weights, 2); const int filter_height = SizeOfDimension(weights, 1); int unused_output_height, unused_output_width; data->padding = ComputePaddingHeightWidth( params->stride_height, params->stride_width, 1, 1, height, width, filter_height, filter_width, params->padding, &unused_output_height, &unused_output_width); // Currently support float32, uint8, int8, int16. switch (input->type) { case kTfLiteFloat32: { if (weights->type == kTfLiteInt8) { TF_LITE_ENSURE_OK(context, EvalHybrid(context, node, params, data, input, weights, bias, output)); } else { // Only for GenericOptimized path, we use transposed weights. if (data->weights_are_transposed) { if (!IsConstantTensor(weights)) { ResizeAndTransposeWeights(context, weights, transposed_weights); } } EvalFloat(context, params, data, input, weights, bias, transposed_weights, col2im, output); } break; } case kTfLiteUInt8: { TfLiteTensor* scratch_buffer; TF_LITE_ENSURE_OK( context, GetTemporarySafe(context, node, data->scratch_tensor_index, &scratch_buffer)); if (IsDynamicTensor(scratch_buffer)) { TF_LITE_ENSURE_OK(context, ResizeTensor(context, output_shape, scratch_buffer)); } if (data->weights_are_transposed) { if (!IsConstantTensor(weights)) { ResizeAndTransposeWeights(context, weights, transposed_weights); } } EvalQuantized(context, params, data, input, weights, transposed_weights, bias, col2im, output, scratch_buffer); break; } case kTfLiteInt8: { TfLiteTensor* scratch_buffer; TF_LITE_ENSURE_OK( context, GetTemporarySafe(context, node, data->scratch_tensor_index, &scratch_buffer)); if (IsDynamicTensor(scratch_buffer)) { TF_LITE_ENSURE_OK(context, ResizeTensor(context, output_shape, scratch_buffer)); } if (data->weights_are_transposed && !IsConstantTensor(weights)) { ResizeAndTransposeWeights(context, weights, transposed_weights); } EvalQuantizedPerChannel(context, params, data, input, weights, transposed_weights, bias, col2im, output, scratch_buffer); break; } case kTfLiteInt16: { TfLiteTensor* scratch_buffer; TF_LITE_ENSURE_OK( context, GetTemporarySafe(context, node, data->scratch_tensor_index, &scratch_buffer)); if (IsDynamicTensor(scratch_buffer)) { TF_LITE_ENSURE_OK(context, ResizeTensor(context, output_shape, scratch_buffer)); } if (data->weights_are_transposed && !IsConstantTensor(weights)) { ResizeAndTransposeWeights(context, weights, transposed_weights); } EvalQuantizedPerChannel16x8( context, params, data, input, weights, transposed_weights, bias, col2im, output, scratch_buffer); break; } default: TF_LITE_KERNEL_LOG(context, "Type '%s' is not currently supported.", TfLiteTypeGetName(input->type)); return kTfLiteError; } return kTfLiteOk; } } // namespace transpose_conv TfLiteRegistration* Register_TRANSPOSECONV_REF() { static TfLiteRegistration r = { transpose_conv::Init, transpose_conv::Free, transpose_conv::Prepare, transpose_conv::Eval}; return &r; } TfLiteRegistration* Register_TRANSPOSECONV_GENERIC_OPT() { static TfLiteRegistration r = { transpose_conv::Init, transpose_conv::Free, transpose_conv::Prepare, transpose_conv::Eval}; return &r; } TfLiteRegistration* Register_TRANSPOSE_CONV() { return Register_TRANSPOSECONV_GENERIC_OPT(); } } // namespace builtin } // namespace ops } // namespace tflite