/* Copyright 2021 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 "tensorflow/lite/kernels/internal/reference/conv3d.h" #include #include #include #include #include #include "absl/types/span.h" #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/optimized/optimized_ops.h" #include "tensorflow/lite/kernels/internal/runtime_shape.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 conv3d { enum KernelType { kReference, kGenericOptimized, }; // Struct to carry data from Prepare to Eval. const int kTensorNotAllocated = -1; static constexpr size_t kMaxIm2colBufferSizeMobile = 1024 * 1024 * 1024; // 1GB struct OpData { Padding3DValues padding; int im2col_tensor_id = kTensorNotAllocated; bool need_im2col = false; // Disable im2col if the temporary im2col tensor requires too much memory // (i.e. >= kMaxIm2colBufferSizeMobile). bool im2col_oversized = false; int32_t im2col_index; }; void* Init(TfLiteContext* context, const char* buffer, size_t length) { auto* opdata = new OpData; return opdata; } void Free(TfLiteContext* context, void* buffer) { delete static_cast(buffer); } TfLiteStatus AllocateTemporaryTensorsIfRequired( KernelType kernel_type, TfLiteContext* context, TfLiteNode* node, OpData* opdata, TfLiteConv3DParams* params, const TfLiteTensor* filter, size_t im2col_bytes) { int temporaries_count = 0; const bool need_dilated_im2col = params->dilation_width_factor != 1 || params->dilation_height_factor != 1 || params->dilation_depth_factor != 1; const bool need_non_dilated_im2col = params->stride_depth != 1 || params->stride_width != 1 || params->stride_height != 1 || filter->dims->data[2] != 1 || filter->dims->data[1] != 1 || filter->dims->data[0] != 1; opdata->need_im2col = (kernel_type == kGenericOptimized) && (need_dilated_im2col || need_non_dilated_im2col); // On mobile platforms, the generic optimized kernel will not be used if the // temporary im2col tensor requires too much memory. if (IsMobilePlatform() && opdata->need_im2col && im2col_bytes >= kMaxIm2colBufferSizeMobile) { opdata->need_im2col = false; opdata->im2col_oversized = true; } if (opdata->need_im2col) { if (opdata->im2col_tensor_id == kTensorNotAllocated) { TF_LITE_ENSURE_OK( context, context->AddTensors(context, 1, &opdata->im2col_tensor_id)); } opdata->im2col_index = temporaries_count++; } TfLiteIntArrayFree(node->temporaries); node->temporaries = TfLiteIntArrayCreate(temporaries_count); return kTfLiteOk; } TfLiteStatus Prepare(KernelType kernel_type, TfLiteContext* context, TfLiteNode* node) { auto* params = static_cast(node->builtin_data); OpData* opdata = reinterpret_cast(node->user_data); // Check number of inputs/outputs. TF_LITE_ENSURE(context, node->inputs->size == 2 || node->inputs->size == 3); TF_LITE_ENSURE_EQ(context, node->outputs->size, 1); TfLiteTensor* output; TF_LITE_ENSURE_OK(context, GetOutputSafe(context, node, 0, &output)); const TfLiteTensor* input; TF_LITE_ENSURE_OK(context, GetInputSafe(context, node, 0, &input)); const TfLiteTensor* filter; TF_LITE_ENSURE_OK(context, GetInputSafe(context, node, 1, &filter)); // Check dimensionality of input, filter. TF_LITE_ENSURE_EQ(context, input->dims->size, 5); TF_LITE_ENSURE_EQ(context, filter->dims->size, 5); // Check input channels matching filter. TF_LITE_ENSURE_EQ(context, input->dims->data[4], filter->dims->data[3]); // Check types. TfLiteType input_type = input->type; TF_LITE_ENSURE_TYPES_EQ(context, input_type, kTfLiteFloat32); TF_LITE_ENSURE_TYPES_EQ(context, filter->type, kTfLiteFloat32); TF_LITE_ENSURE_TYPES_EQ(context, output->type, input_type); // Check bias. const TfLiteTensor* bias = GetInput(context, node, 2); if (bias) { TF_LITE_ENSURE_TYPES_EQ(context, bias->type, input_type); TF_LITE_ENSURE_EQ(context, NumElements(bias), SizeOfDimension(filter, 4)); } // Filter has shape of [filter_depth, filter_height, filter_width, // in_channels, out_channels]. int batches = input->dims->data[0]; int channels_out = filter->dims->data[4]; int depth = input->dims->data[1]; int height = input->dims->data[2]; int width = input->dims->data[3]; int filter_depth = filter->dims->data[0]; int filter_height = filter->dims->data[1]; int filter_width = filter->dims->data[2]; int input_channel = filter->dims->data[3]; // Matching GetWindowedOutputSize in TensorFlow. int out_width, out_height, out_depth; opdata->padding = ComputePadding3DValues( params->stride_height, params->stride_width, params->stride_depth, params->dilation_height_factor, params->dilation_width_factor, params->dilation_depth_factor, height, width, depth, filter_height, filter_width, filter_depth, params->padding, &out_height, &out_width, &out_depth); std::unique_ptr output_size( TfLiteIntArrayCreate(5), TfLiteIntArrayFree); output_size->data[0] = batches; output_size->data[1] = out_depth; output_size->data[2] = out_height; output_size->data[3] = out_width; output_size->data[4] = channels_out; TF_LITE_ENSURE_OK( context, context->ResizeTensor(context, output, output_size.release())); // Allocate temporary tensors. size_t input_type_size; TF_LITE_ENSURE_STATUS(GetSizeOfType(context, input->type, &input_type_size)); size_t im2col_elements = 0; TF_LITE_ENSURE_OK( context, CheckedShapeProduct( context, {batches, out_depth, out_height, out_width, input_channel, filter_depth, filter_height, filter_width}, "Conv3D im2col tensor has too many elements.", im2col_elements)); size_t im2col_bytes = 0; TF_LITE_ENSURE_MSG(context, MultiplyAndCheckOverflow(im2col_elements, input_type_size, &im2col_bytes) == kTfLiteOk, "%s", "Conv3D im2col tensor is too large."); TF_LITE_ENSURE_OK(context, AllocateTemporaryTensorsIfRequired( kernel_type, context, node, opdata, params, filter, im2col_bytes)); if (opdata->need_im2col) { if (im2col_elements > std::numeric_limits::max()) { TF_LITE_KERNEL_LOG( context, "Conv3D im2col elements (%zu) exceed the 32-bit integer limit.", im2col_elements); return kTfLiteError; } std::unique_ptr im2col_size( TfLiteIntArrayCreate(5), TfLiteIntArrayFree); im2col_size->data[0] = batches; im2col_size->data[1] = out_depth; im2col_size->data[2] = out_height; im2col_size->data[3] = out_width; const RuntimeShape filter_shape = GetTensorShape(filter); int im2col_depth = 0; TF_LITE_ENSURE_MSG( context, filter_shape.CheckedSizeToDimension(/*end=*/4, im2col_depth), "%s", "Conv3D im2col tensor has too many channels."); im2col_size->data[4] = im2col_depth; TfLiteTensor* im2col; node->temporaries->data[opdata->im2col_index] = opdata->im2col_tensor_id; TfLiteStatus status = GetTemporarySafe(context, node, opdata->im2col_index, &im2col); if (status != kTfLiteOk) { return status; } im2col->type = input->type; im2col->allocation_type = kTfLiteArenaRw; status = context->ResizeTensor(context, im2col, im2col_size.release()); TF_LITE_ENSURE_OK(context, status); } return kTfLiteOk; } template TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { return Prepare(kernel_type, context, node); } TfLiteStatus EvalFloat(KernelType kernel_type, TfLiteContext* context, TfLiteNode* node, TfLiteConv3DParams* params, OpData* opdata, const TfLiteTensor* input, const TfLiteTensor* filter, const TfLiteTensor* bias, TfLiteTensor* im2col, TfLiteTensor* output) { float output_activation_min, output_activation_max; CalculateActivationRange(params->activation, &output_activation_min, &output_activation_max); Conv3DParams runtime_params; runtime_params.padding_values = opdata->padding; runtime_params.stride_depth = params->stride_depth; runtime_params.stride_height = params->stride_height; runtime_params.stride_width = params->stride_width; runtime_params.dilation_depth = params->dilation_depth_factor; runtime_params.dilation_height = params->dilation_height_factor; runtime_params.dilation_width = params->dilation_width_factor; runtime_params.float_activation_min = output_activation_min; runtime_params.float_activation_max = output_activation_max; switch (kernel_type) { case kReference: { reference_ops::Conv3D(runtime_params, GetTensorShape(input), GetTensorData(input), GetTensorShape(filter), GetTensorData(filter), GetTensorShape(bias), GetTensorData(bias), GetTensorShape(output), GetTensorData(output)); return kTfLiteOk; } case kGenericOptimized: { return optimized_ops::Conv3D( runtime_params, GetTensorShape(input), GetTensorData(input), GetTensorShape(filter), GetTensorData(filter), GetTensorShape(bias), GetTensorData(bias), GetTensorShape(output), GetTensorData(output), GetTensorShape(im2col), GetTensorData(im2col), CpuBackendContext::GetFromContext(context)); } } } TfLiteStatus Eval(KernelType kernel_type, TfLiteContext* context, TfLiteNode* node) { auto* params = reinterpret_cast(node->builtin_data); OpData* opdata = reinterpret_cast(node->user_data); TfLiteTensor* output; TF_LITE_ENSURE_OK(context, GetOutputSafe(context, node, 0, &output)); const TfLiteTensor* input; TF_LITE_ENSURE_OK(context, GetInputSafe(context, node, 0, &input)); const TfLiteTensor* filter; TF_LITE_ENSURE_OK(context, GetInputSafe(context, node, 1, &filter)); const TfLiteTensor* bias = GetInput(context, node, 2); TfLiteTensor* im2col = opdata->need_im2col ? &context->tensors[opdata->im2col_tensor_id] : nullptr; // Fallback to reference execution path when im2col is needed but disabled. if (opdata->im2col_oversized) { kernel_type = kReference; } switch (input->type) { case kTfLiteFloat32: return EvalFloat(kernel_type, context, node, params, opdata, input, filter, bias, im2col, output); default: TF_LITE_KERNEL_LOG(context, "Type %s currently not supported.", TfLiteTypeGetName(input->type)); return kTfLiteError; } return kTfLiteOk; } template TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { return Eval(kernel_type, context, node); } } // namespace conv3d TfLiteRegistration* Register_CONV_3D_REF() { static TfLiteRegistration r = {conv3d::Init, conv3d::Free, conv3d::Prepare, conv3d::Eval}; return &r; } TfLiteRegistration* Register_CONV_3D_GENERIC_OPT() { static TfLiteRegistration r = {conv3d::Init, conv3d::Free, conv3d::Prepare, conv3d::Eval}; return &r; } TfLiteRegistration* Register_CONV_3D() { return Register_CONV_3D_GENERIC_OPT(); } } // namespace builtin } // namespace ops } // namespace tflite