From 154bc1c3e6a0182e2130c7966af3944ee6ca20b3 Mon Sep 17 00:00:00 2001 From: giuros01 Date: Tue, 26 Mar 2019 17:44:40 +0000 Subject: COMPMID-1973: Implement FFTConvolutionLayer on NEON Change-Id: I2e667c0411bda0164a616ffe44473a78de6752c9 Signed-off-by: giuros01 Reviewed-on: https://review.mlplatform.org/c/1066 Reviewed-by: Gian Marco Iodice Tested-by: Arm Jenkins --- src/runtime/NEON/functions/NEConvolutionLayer.cpp | 27 +- src/runtime/NEON/functions/NEFFT1D.cpp | 39 ++- .../NEON/functions/NEFFTConvolutionLayer.cpp | 381 +++++++++++++++++++++ .../NEON/functions/NEPixelWiseMultiplication.cpp | 30 +- .../NEON/functions/NEReductionOperation.cpp | 3 +- 5 files changed, 464 insertions(+), 16 deletions(-) create mode 100644 src/runtime/NEON/functions/NEFFTConvolutionLayer.cpp (limited to 'src/runtime/NEON') diff --git a/src/runtime/NEON/functions/NEConvolutionLayer.cpp b/src/runtime/NEON/functions/NEConvolutionLayer.cpp index 5059162032..a62459b3e8 100644 --- a/src/runtime/NEON/functions/NEConvolutionLayer.cpp +++ b/src/runtime/NEON/functions/NEConvolutionLayer.cpp @@ -73,6 +73,13 @@ void NEConvolutionLayer::configure(ITensor *input, const ITensor *weights, const _function = std::move(f); break; } + case ConvolutionMethod::FFT: + { + auto f = arm_compute::support::cpp14::make_unique(_memory_manager); + f->configure(input, weights, biases, output, conv_info, act_info); + _function = std::move(f); + break; + } default: ARM_COMPUTE_ERROR("Not supported."); break; @@ -97,6 +104,10 @@ Status NEConvolutionLayer::validate(const ITensorInfo *input, const ITensorInfo case ConvolutionMethod::DIRECT: //Validate Gemm-based Convolution ARM_COMPUTE_RETURN_ON_ERROR(NEDirectConvolutionLayer::validate(input, weights, biases, output, conv_info, act_info)); + case ConvolutionMethod::FFT: + // Validate FFT-based convolution layer + ARM_COMPUTE_RETURN_ON_ERROR(NEFFTConvolutionLayer::validate(input, weights, nullptr, output, conv_info, act_info)); + break; default: ARM_COMPUTE_ERROR("Not supported."); break; @@ -148,12 +159,22 @@ ConvolutionMethod NEConvolutionLayer::get_convolution_method(const ITensorInfo * return (*found).second; } - if(dilation != Size2D(1U, 1U) || input->dimension(get_data_layout_dimension_index(input->data_layout(), DataLayoutDimension::CHANNEL)) <= 16) + if(dilation != Size2D(1U, 1U)) { return ConvolutionMethod::GEMM; } - - return bool(NEWinogradConvolutionLayer::validate(input, weights, nullptr, output, conv_info, act_info, enable_fast_math)) ? ConvolutionMethod::WINOGRAD : ConvolutionMethod::GEMM; + else + { + if((weights->dimension(idx_h) > 7) && (input->dimension(idx_c) > output->dimension(idx_c)) && (NEFFTConvolutionLayer::validate(input, weights, nullptr, output, conv_info, act_info))) + { + return ConvolutionMethod::FFT; + } + if(input->dimension(idx_c) < 16) + { + return ConvolutionMethod::GEMM; + } + return bool(NEWinogradConvolutionLayer::validate(input, weights, nullptr, output, conv_info, act_info, enable_fast_math)) ? ConvolutionMethod::WINOGRAD : ConvolutionMethod::GEMM; + } } void NEConvolutionLayer::run() diff --git a/src/runtime/NEON/functions/NEFFT1D.cpp b/src/runtime/NEON/functions/NEFFT1D.cpp index 665efeb440..25ba1c8391 100644 --- a/src/runtime/NEON/functions/NEFFT1D.cpp +++ b/src/runtime/NEON/functions/NEFFT1D.cpp @@ -37,6 +37,9 @@ NEFFT1D::NEFFT1D(std::shared_ptr memory_manager) void NEFFT1D::configure(const ITensor *input, ITensor *output, const FFT1DInfo &config) { + ARM_COMPUTE_ERROR_ON_NULLPTR(input, output); + ARM_COMPUTE_ERROR_THROW_ON(NEFFT1D::validate(input->info(), output->info(), config)); + // Decompose size to radix factors const auto supported_radix = NEFFTRadixStageKernel::supported_radix(); const unsigned int N = input->info()->tensor_shape()[config.axis]; @@ -44,21 +47,25 @@ void NEFFT1D::configure(const ITensor *input, ITensor *output, const FFT1DInfo & ARM_COMPUTE_ERROR_ON(decomposed_vector.empty()); // Flags - _run_scale = config.direction == FFTDirection::Inverse; - _axis = config.axis; + _run_scale = config.direction == FFTDirection::Inverse; + const bool is_c2r = input->info()->num_channels() == 2 && output->info()->num_channels() == 1; // Configure digit reverse + FFTDigitReverseKernelInfo digit_reverse_config; + digit_reverse_config.axis = config.axis; + digit_reverse_config.conjugate = config.direction == FFTDirection::Inverse; TensorInfo digit_reverse_indices_info(TensorShape(input->info()->tensor_shape()[config.axis]), 1, DataType::U32); _digit_reverse_indices.allocator()->init(digit_reverse_indices_info); _memory_group.manage(&_digit_reversed_input); - _digit_reverse_kernel.configure(input, &_digit_reversed_input, &_digit_reverse_indices, config.axis); + _digit_reverse_kernel.configure(input, &_digit_reversed_input, &_digit_reverse_indices, digit_reverse_config); // Create and configure FFT kernels unsigned int Nx = 1; - - _num_ffts = decomposed_vector.size(); + _num_ffts = decomposed_vector.size(); _fft_kernels.resize(_num_ffts); + _axis = config.axis; + for(unsigned int i = 0; i < _num_ffts; ++i) { const unsigned int radix_for_stage = decomposed_vector.at(i); @@ -68,10 +75,20 @@ void NEFFT1D::configure(const ITensor *input, ITensor *output, const FFT1DInfo & fft_kernel_info.radix = radix_for_stage; fft_kernel_info.Nx = Nx; fft_kernel_info.is_first_stage = (i == 0); - _fft_kernels[i].configure(&_digit_reversed_input, i == (_num_ffts - 1) && !is_c2r ? output : nullptr, fft_kernel_info); + _fft_kernels[i].configure(&_digit_reversed_input, ((i == (_num_ffts - 1)) && !is_c2r) ? output : nullptr, fft_kernel_info); + Nx *= radix_for_stage; } + // Configure scale kernel + if(_run_scale) + { + FFTScaleKernelInfo scale_config; + scale_config.scale = static_cast(N); + scale_config.conjugate = config.direction == FFTDirection::Inverse; + is_c2r ? _scale_kernel.configure(&_digit_reversed_input, output, scale_config) : _scale_kernel.configure(output, nullptr, scale_config); + } + // Allocate tensors _digit_reversed_input.allocator()->allocate(); _digit_reverse_indices.allocator()->allocate(); @@ -84,8 +101,9 @@ void NEFFT1D::configure(const ITensor *input, ITensor *output, const FFT1DInfo & Status NEFFT1D::validate(const ITensorInfo *input, const ITensorInfo *output, const FFT1DInfo &config) { ARM_COMPUTE_RETURN_ERROR_ON_NULLPTR(input, output); - ARM_COMPUTE_RETURN_ERROR_ON_DATA_TYPE_CHANNEL_NOT_IN(input, 2, DataType::F32); - ARM_COMPUTE_RETURN_ERROR_ON(config.axis > 1); + ARM_COMPUTE_RETURN_ERROR_ON(input->data_type() != DataType::F32); + ARM_COMPUTE_RETURN_ERROR_ON(input->num_channels() > 2); + ARM_COMPUTE_RETURN_ERROR_ON(std::set({ 0, 1 }).count(config.axis) == 0); // Check if FFT is decomposable const auto supported_radix = NEFFTRadixStageKernel::supported_radix(); @@ -96,6 +114,9 @@ Status NEFFT1D::validate(const ITensorInfo *input, const ITensorInfo *output, co // Checks performed when output is configured if((output != nullptr) && (output->total_size() != 0)) { + // All combinations are supported except real input with real output (i.e., both input channels set to 1) + ARM_COMPUTE_RETURN_ERROR_ON(output->num_channels() == 1 && input->num_channels() == 1); + ARM_COMPUTE_RETURN_ERROR_ON(output->num_channels() > 2); ARM_COMPUTE_RETURN_ERROR_ON_MISMATCHING_SHAPES(input, output); ARM_COMPUTE_RETURN_ERROR_ON_MISMATCHING_DATA_TYPES(input, output); } @@ -107,7 +128,7 @@ void NEFFT1D::run() { MemoryGroupResourceScope scope_mg(_memory_group); - NEScheduler::get().schedule(&_digit_reverse_kernel, (_axis == 0 ? Window::DimY : Window::DimX)); + NEScheduler::get().schedule(&_digit_reverse_kernel, (_axis == 0 ? Window::DimY : Window::DimZ)); for(unsigned int i = 0; i < _num_ffts; ++i) { diff --git a/src/runtime/NEON/functions/NEFFTConvolutionLayer.cpp b/src/runtime/NEON/functions/NEFFTConvolutionLayer.cpp new file mode 100644 index 0000000000..962402549f --- /dev/null +++ b/src/runtime/NEON/functions/NEFFTConvolutionLayer.cpp @@ -0,0 +1,381 @@ +/* + * Copyright (c) 2019 ARM Limited. + * + * SPDX-License-Identifier: MIT + * + * 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. + */ +#include "arm_compute/runtime/NEON/functions/NEFFTConvolutionLayer.h" + +#include "arm_compute/core/ITensor.h" +#include "arm_compute/core/Utils.h" +#include "arm_compute/core/Validate.h" +#include "arm_compute/core/utils/helpers/fft.h" +#include "arm_compute/core/utils/misc/ShapeCalculator.h" + +namespace arm_compute +{ +namespace +{ +int pad_decomposable(int N) +{ + const auto supported_radix = NEFFTRadixStageKernel::supported_radix(); + + int pad = 0; + bool is_decomposed = false; + while(!is_decomposed) + { + const auto decomposed_vector = arm_compute::helpers::fft::decompose_stages(N++, supported_radix); + is_decomposed = !decomposed_vector.empty(); + if(!is_decomposed) + { + ++pad; + } + } + return pad; +} +} // namespace + +NEFFTConvolutionLayer::NEFFTConvolutionLayer(std::shared_ptr memory_manager) + : _memory_group(memory_manager), + _flip_weights_func(), + _permute_input_func(), + _permute_output_func(), + _permute_weights_func(), + _permute_bias_func(), + _pad_input_func(), + _pad_weights_func(), + _transform_input_func(memory_manager), + _transform_weights_func(), + _itransform_output_func(memory_manager), + _prod_func(), + _reduce_func(), + _extract_output_func(), + _bias_add_func(), + _activation_layer_func(), + _permuted_input(), + _permuted_weights(), + _permuted_bias(), + _permuted_output(), + _padded_input(), + _padded_weights(), + _flip_axis(), + _flipped_weights(), + _transformed_input(), + _transformed_weights(), + _input_weights_product(), + _output_product(), + _output_reduced(), + _itransformed_output(), + _reshaped_output(), + _bias_output(), + _original_weights(nullptr), + _original_bias(nullptr), + _is_activationlayer_enabled(false), + _needs_permute(false), + _has_bias(false), + _is_prepared(false) +{ +} + +void NEFFTConvolutionLayer::configure(ITensor *input, const ITensor *weights, const ITensor *biases, ITensor *output, const PadStrideInfo &conv_info, + const ActivationLayerInfo &act_info) +{ + _original_weights = weights; + _original_bias = biases; + + // Flat if bias addition is required + _has_bias = biases != nullptr; + + // Get indices for the width and height + const size_t idx_width = get_data_layout_dimension_index(input->info()->data_layout(), DataLayoutDimension::WIDTH); + const size_t idx_height = get_data_layout_dimension_index(input->info()->data_layout(), DataLayoutDimension::HEIGHT); + + // Input shape, kernel size and output tile + const Size2D input_dims = Size2D(input->info()->tensor_shape()[idx_width], input->info()->tensor_shape()[idx_height]); + const Size2D kernel_size = Size2D(weights->info()->tensor_shape()[idx_width], weights->info()->tensor_shape()[idx_height]); + const Size2D pad_valid = Size2D(pad_decomposable(input_dims.x() + kernel_size.x() - 1), + pad_decomposable(input_dims.y() + kernel_size.y() - 1)); + // Tensors to use + ITensor *input_to_use = input; + const ITensor *weights_to_use = weights; + ITensor *output_to_use = _has_bias ? &_bias_output : output; + + // Permute bias + _permute_bias_func.configure(biases, &_permuted_bias, PermutationVector(1U, 2U, 0U)); + _permuted_bias.info()->set_data_layout(DataLayout::NCHW); + + // Permute input if needed + _needs_permute = input->info()->data_layout() == DataLayout::NHWC; + if(_needs_permute) + { + _memory_group.manage(&_permuted_input); + // Configure the function to transform the input tensor from NHWC -> NCHW + _permute_input_func.configure(input, &_permuted_input, PermutationVector(1U, 2U, 0U)); + _permuted_input.info()->set_data_layout(DataLayout::NCHW); + + // Configure the function to transform the weights tensor from HWI -> IHW + _permute_weights_func.configure(weights, &_permuted_weights, PermutationVector(1U, 2U, 0U)); + _permuted_weights.info()->set_data_layout(DataLayout::NCHW); + + input_to_use = &_permuted_input; + weights_to_use = &_permuted_weights; + } + + // Flip weights + _flipped_weights.allocator()->init(weights_to_use->info()->clone()->set_is_resizable(true).reset_padding()); + _flip_axis.allocator()->init(TensorInfo(TensorShape(2U), 1, DataType::U32)); + _flip_weights_func.configure(weights_to_use, &_flipped_weights, &_flip_axis); + + // Pad weights + const PaddingList padding_w = { { 0, input_dims.x() + pad_valid.x() - 1 }, { 0, input_dims.y() + pad_valid.y() - 1 } }; + _pad_weights_func.configure(&_flipped_weights, &_padded_weights, padding_w); + + // Transform weights + _transform_weights_func = support::cpp14::make_unique(); + _transform_weights_func->configure(&_padded_weights, &_transformed_weights, FFT2DInfo()); + + // Pad input + const PaddingList padding_in = { { 0, kernel_size.x() + pad_valid.x() - 1 }, { 0, kernel_size.y() + pad_valid.y() - 1 } }; + _memory_group.manage(&_padded_input); + _pad_input_func.configure(input_to_use, &_padded_input, padding_in); + if(_needs_permute) + { + _permuted_input.allocator()->allocate(); + } + + // Transform input + _memory_group.manage(&_transformed_input); + _transform_input_func.configure(&_padded_input, &_transformed_input, FFT2DInfo()); + _padded_input.allocator()->allocate(); + + // Perform product + _memory_group.manage(&_output_product); + _prod_func.configure(&_transformed_input, &_transformed_weights, &_output_product); + _transformed_input.allocator()->allocate(); + + // Perform reduction + _memory_group.manage(&_output_reduced); + _reduce_func.configure(&_output_product, &_output_reduced, 2, ReductionOperation::SUM); + _output_product.allocator()->allocate(); + + // Transform output + _memory_group.manage(&_itransformed_output); + FFT2DInfo itranform_info; + itranform_info.direction = FFTDirection::Inverse; + _itransformed_output.allocator()->init(_output_reduced.info()->clone()->set_is_resizable(true).set_num_channels(1).reset_padding()); + _itransform_output_func.configure(&_output_reduced, &_itransformed_output, itranform_info); + _output_reduced.allocator()->allocate(); + + // Reshape output + TensorShape reshaped_shape = _itransformed_output.info()->tensor_shape(); + reshaped_shape.remove_dimension(2); + _reshaped_output.allocator()->init(_itransformed_output.info()->clone()->set_tensor_shape(reshaped_shape)); + + // Extract correct region + const int start_left = kernel_size.x() - conv_info.pad_left() - 1; + const int start_top = kernel_size.y() - conv_info.pad_top() - 1; + const int end_right = _reshaped_output.info()->tensor_shape().x() - (kernel_size.x() - conv_info.pad_right() - 1) - pad_valid.x(); + const int end_botton = _reshaped_output.info()->tensor_shape().y() - (kernel_size.y() - conv_info.pad_bottom() - 1) - pad_valid.y(); + if(_has_bias) + { + _memory_group.manage(&_bias_output); + } + else if(_needs_permute) + { + output_to_use = &_permuted_output; + _memory_group.manage(&_permuted_output); + } + _extract_output_func.configure(&_reshaped_output, output_to_use, Coordinates(start_left, start_top), Coordinates(end_right, end_botton)); + _reshaped_output.allocator()->allocate(); + _itransformed_output.allocator()->allocate(); + + // Add bias + if(biases != nullptr) + { + output_to_use = output; + if(_needs_permute) + { + output_to_use = &_permuted_output; + _memory_group.manage(&_permuted_output); + } + auto_init_if_empty(*output_to_use->info(), *_bias_output.info()); + _bias_add_func.configure(&_bias_output, &_permuted_bias, output_to_use, ConvertPolicy::WRAP); + _bias_output.allocator()->allocate(); + } + + // Permute output + if(_needs_permute) + { + // Configure the function to transform the convoluted output to ACL's native ordering format NCHW + _permuted_output.info()->set_data_layout(DataLayout::NCHW); + _permute_output_func.configure(&_permuted_output, output, PermutationVector(2U, 0U, 1U)); + + // Allocate tensors + _permuted_output.allocator()->allocate(); + } + + // Configure Activation Layer + _is_activationlayer_enabled = act_info.enabled(); + if(_is_activationlayer_enabled) + { + _activation_layer_func.configure(output, nullptr, act_info); + } + + // Setup flip axis data + _flip_axis.allocator()->allocate(); + + auto axis_data = reinterpret_cast(_flip_axis.buffer()); + axis_data[0] = 0; + axis_data[1] = 1; +} + +Status NEFFTConvolutionLayer::validate(const ITensorInfo *input, const ITensorInfo *weights, const ITensorInfo *biases, const ITensorInfo *output, const PadStrideInfo &conv_info, + const ActivationLayerInfo &act_info) +{ + ARM_COMPUTE_RETURN_ERROR_ON_DATA_TYPE_CHANNEL_NOT_IN(input, 1, DataType::F32); + ARM_COMPUTE_RETURN_ERROR_ON_MISMATCHING_DATA_TYPES(input, weights); + + // Get indices for the width and height + const size_t idx_width = get_data_layout_dimension_index(input->data_layout(), DataLayoutDimension::WIDTH); + const size_t idx_height = get_data_layout_dimension_index(input->data_layout(), DataLayoutDimension::HEIGHT); + + // Input shape, kernel size and output tile + const Size2D kernel_size = Size2D(weights->tensor_shape()[idx_width], weights->tensor_shape()[idx_height]); + + // Strides + const auto strides = conv_info.stride(); + ARM_COMPUTE_RETURN_ERROR_ON(strides.first != strides.second && strides.first != 1); + ARM_COMPUTE_RETURN_ERROR_ON(kernel_size.x() != kernel_size.y()); + ARM_COMPUTE_RETURN_ERROR_ON(conv_info.pad_left() != (kernel_size.x() / 2) || conv_info.pad_right() != (kernel_size.x() / 2)); + ARM_COMPUTE_RETURN_ERROR_ON(conv_info.pad_top() != (kernel_size.y() / 2) || conv_info.pad_bottom() != (kernel_size.y() / 2)); + + // Validate biases + if(biases != nullptr) + { + const size_t idx_channels = get_data_layout_dimension_index(input->data_layout(), DataLayoutDimension::CHANNEL); + ARM_COMPUTE_RETURN_ERROR_ON_MISMATCHING_DATA_TYPES(input, biases); + ARM_COMPUTE_RETURN_ERROR_ON(input->tensor_shape()[idx_channels] != biases->tensor_shape().x()); + } + + // Checks performed when output is configured + if((output != nullptr) && (output->total_size() != 0)) + { + ARM_COMPUTE_RETURN_ERROR_ON_MISMATCHING_DATA_TYPES(input, output); + ARM_COMPUTE_RETURN_ERROR_ON((input->tensor_shape()[idx_height] != output->tensor_shape()[idx_height]) || (input->tensor_shape()[idx_width] != output->tensor_shape()[idx_width])); + + // Validate Activation Layer + if(act_info.enabled()) + { + ARM_COMPUTE_RETURN_ON_ERROR(NEActivationLayer::validate(output, nullptr, act_info)); + } + } + + return Status{}; +} + +void NEFFTConvolutionLayer::run() +{ + prepare(); + + MemoryGroupResourceScope scope_mg(_memory_group); + + // Transform input + if(_needs_permute) + { + _permute_input_func.run(); + } + _pad_input_func.run(); + _transform_input_func.run(); + + // Perform operations to frequency domain + _prod_func.run(); + + _reduce_func.run(); + + // Transform output + _itransform_output_func.run(); + _reshaped_output.allocator()->import_memory(_itransformed_output.buffer()); + _extract_output_func.run(); + + // Add bias + if(_has_bias) + { + _bias_add_func.run(); + } + if(_needs_permute) + { + _permute_output_func.run(); + } + + // Run activation layer + if(_is_activationlayer_enabled) + { + _activation_layer_func.run(); + } +} + +void NEFFTConvolutionLayer::prepare() +{ + if(!_is_prepared) + { + // Permute bias to NCHW + if(_original_bias != nullptr) + { + _permuted_bias.allocator()->allocate(); + _permute_bias_func.run(); + _original_bias->mark_as_unused(); + } + + const ITensor *cur_weights = _original_weights; + + // Permute weights + if(_needs_permute) + { + ARM_COMPUTE_ERROR_ON(!cur_weights->is_used()); + + _permuted_weights.allocator()->allocate(); + _permute_weights_func.run(); + cur_weights->mark_as_unused(); + cur_weights = &_permuted_weights; + } + + // Flip weights + _flipped_weights.allocator()->allocate(); + _flip_weights_func.run(); + cur_weights->mark_as_unused(); + + // Pad weights + _padded_weights.allocator()->allocate(); + _pad_weights_func.run(); + _flipped_weights.mark_as_unused(); + _flipped_weights.allocator()->free(); + + // Transform weights to frequency domain + _transformed_weights.allocator()->allocate(); + _transform_weights_func->run(); + _transform_weights_func.reset(); + + _padded_weights.mark_as_unused(); + _padded_weights.allocator()->free(); + + _is_prepared = true; + } +} +} // namespace arm_compute diff --git a/src/runtime/NEON/functions/NEPixelWiseMultiplication.cpp b/src/runtime/NEON/functions/NEPixelWiseMultiplication.cpp index cf6b984717..ef28fe9260 100644 --- a/src/runtime/NEON/functions/NEPixelWiseMultiplication.cpp +++ b/src/runtime/NEON/functions/NEPixelWiseMultiplication.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2018 ARM Limited. + * Copyright (c) 2016-2019 ARM Limited. * * SPDX-License-Identifier: MIT * @@ -29,8 +29,8 @@ #include -using namespace arm_compute; - +namespace arm_compute +{ void NEPixelWiseMultiplication::configure(ITensor *input1, ITensor *input2, ITensor *output, float scale, ConvertPolicy overflow_policy, RoundingPolicy rounding_policy) { auto k = arm_compute::support::cpp14::make_unique(); @@ -51,3 +51,27 @@ Status NEPixelWiseMultiplication::validate(const ITensorInfo *input1, const ITen { return NEPixelWiseMultiplicationKernel::validate(input1, input2, output, scale, overflow_policy, rounding_policy); } + +void NEComplexPixelWiseMultiplication::configure(ITensor *input1, ITensor *input2, ITensor *output) +{ + auto k = arm_compute::support::cpp14::make_unique(); + k->configure(input1, input2, output); + _kernel = std::move(k); + + if(output->info()->dimension(0) > 1) + { + ITensor *broadcasted_info = (input1->info()->dimension(0) == 1) ? input1 : input2; + + if(broadcasted_info->info()->dimension(0) == 1) + { + _border_handler.configure(broadcasted_info, _kernel->border_size(), BorderMode::REPLICATE); + } + } +} + +Status NEComplexPixelWiseMultiplication::validate(const ITensorInfo *input1, const ITensorInfo *input2, const ITensorInfo *output) +{ + return NEComplexPixelWiseMultiplicationKernel::validate(input1, input2, output); +} + +} // namespace arm_compute diff --git a/src/runtime/NEON/functions/NEReductionOperation.cpp b/src/runtime/NEON/functions/NEReductionOperation.cpp index 9f81a403f5..a0aed96521 100644 --- a/src/runtime/NEON/functions/NEReductionOperation.cpp +++ b/src/runtime/NEON/functions/NEReductionOperation.cpp @@ -66,7 +66,8 @@ Status NEReductionOperation::validate(const ITensorInfo *input, const ITensorInf void NEReductionOperation::configure(ITensor *input, ITensor *output, unsigned int axis, ReductionOperation op) { - ARM_COMPUTE_ERROR_ON_DATA_TYPE_CHANNEL_NOT_IN(input, 1, DataType::QASYMM8, DataType::F16, DataType::F32); + ARM_COMPUTE_ERROR_ON_NULLPTR(input, output); + ARM_COMPUTE_ERROR_THROW_ON(NEReductionOperation::validate(input->info(), output->info(), axis, op)); // Configure reduction kernel _reduction_kernel.configure(input, output, axis, op); -- cgit v1.2.1