From 5948634bb97e05934e9eea180ba41dcddf874416 Mon Sep 17 00:00:00 2001 From: Giorgio Arena Date: Fri, 1 Dec 2017 10:42:47 +0000 Subject: COMPMID-617 Add window validation to CLDirectConvolutionLayer Change-Id: Ia642dc68de6a0afe697bbce392e7ee955fa8944b Reviewed-on: https://eu-gerrit-1.euhpc.arm.com/111460 Reviewed-by: Anthony Barbier Tested-by: BSG Visual Compute Jenkins server to access repositories on http://mpd-gerrit.cambridge.arm.com --- .../CL/kernels/CLDirectConvolutionLayerKernel.cpp | 279 +++++++++++++-------- 1 file changed, 169 insertions(+), 110 deletions(-) (limited to 'src/core/CL/kernels/CLDirectConvolutionLayerKernel.cpp') diff --git a/src/core/CL/kernels/CLDirectConvolutionLayerKernel.cpp b/src/core/CL/kernels/CLDirectConvolutionLayerKernel.cpp index aea0161a1d..df0578bc6e 100644 --- a/src/core/CL/kernels/CLDirectConvolutionLayerKernel.cpp +++ b/src/core/CL/kernels/CLDirectConvolutionLayerKernel.cpp @@ -60,6 +60,160 @@ TensorShape get_output_shape(TensorShape input_shape, TensorShape weights_shape, return output_shape; } + +Error validate_arguments(const ITensorInfo *input, const ITensorInfo *weights, const ITensorInfo *biases, const ITensorInfo *output, const PadStrideInfo &conv_info) +{ + ARM_COMPUTE_RETURN_ERROR_ON_DATA_TYPE_CHANNEL_NOT_IN(input, 1, DataType::QS8, DataType::QASYMM8, DataType::QS16, DataType::F16, DataType::F32); + ARM_COMPUTE_RETURN_ERROR_ON_MISMATCHING_DATA_TYPES(input, weights); + ARM_COMPUTE_RETURN_ERROR_ON_MSG(weights->dimension(0) != weights->dimension(1), + "Weights should have same width as length"); + ARM_COMPUTE_RETURN_ERROR_ON_MSG(weights->dimension(0) != 1 && weights->dimension(0) != 3 && weights->dimension(0) != 5, + "Kernel sizes other than 1x1, 3x3 or 5x5 are not supported"); + ARM_COMPUTE_RETURN_ERROR_ON_MSG(weights->dimension(2) != input->dimension(2), + "Weights feature map dimension should match the respective input's one"); + ARM_COMPUTE_RETURN_ERROR_ON_MSG(weights->dimension(0) != weights->dimension(1), + "Only rectangular weights are supported!"); + ARM_COMPUTE_RETURN_ERROR_ON_MSG(weights->num_dimensions() > 4, + "Weights can be at most 4 dimensional"); + ARM_COMPUTE_RETURN_ERROR_ON_MSG((weights->dimension(0) == 1) && std::get<0>(conv_info.stride()) > 3, + "Strides larger than 3 not supported for 1x1 convolution."); + ARM_COMPUTE_RETURN_ERROR_ON_MSG((weights->dimension(0) == 3 || weights->dimension(0) == 5) && std::get<0>(conv_info.stride()) > 2, + "Strides larger than 2 not supported for 3x3 convolution."); + + if(biases != nullptr) + { + if(is_data_type_quantized_asymmetric(input->data_type())) + { + ARM_COMPUTE_RETURN_ERROR_ON_DATA_TYPE_CHANNEL_NOT_IN(biases, 1, DataType::S32); + } + else + { + ARM_COMPUTE_RETURN_ERROR_ON_MISMATCHING_DATA_TYPES(weights, biases); + } + ARM_COMPUTE_RETURN_ERROR_ON_MSG(biases->dimension(0) != weights->dimension(3), + "Biases size and number of input feature maps should match"); + ARM_COMPUTE_RETURN_ERROR_ON_MSG(biases->num_dimensions() > 1, + "Biases should be one dimensional"); + } + + // Checks performed when output is configured + if(output->total_size() != 0) + { + ARM_COMPUTE_RETURN_ERROR_ON_MISMATCHING_DIMENSIONS(output->tensor_shape(), + get_output_shape(input->tensor_shape(), weights->tensor_shape(), conv_info)); + ARM_COMPUTE_RETURN_ERROR_ON_MISMATCHING_DATA_TYPES(input, output); + ARM_COMPUTE_RETURN_ERROR_ON_MISMATCHING_FIXED_POINT(input, output); + } + + return Error{}; +} + +std::pair validate_and_configure_window(ITensorInfo *input, ITensorInfo *weights, ITensorInfo *output, const PadStrideInfo &conv_info, const GPUTarget target) +{ + const unsigned int kernel_size = weights->dimension(0); + const DataType data_type = input->data_type(); + + // Get convolved dimensions + TensorShape output_shape = get_output_shape(input->tensor_shape(), weights->tensor_shape(), conv_info); + + // Output auto inizialitation if not yet initialized + // FIXME: input->clone()->set_tensor_shape(output_shape) doesn't work with subtensors for grouped direct convolutions (AlexNet). + auto_init_if_empty(*output, output_shape, + 1, + input->data_type(), + input->fixed_point_position(), + input->quantization_info()); + + unsigned int conv_stride_x = std::get<0>(conv_info.stride()); + unsigned int conv_stride_y = std::get<1>(conv_info.stride()); + unsigned int conv_pad_left = std::min(conv_info.pad_left(), kernel_size / 2); + unsigned int conv_pad_top = std::min(conv_info.pad_top(), kernel_size / 2); + unsigned int conv_pad_right = std::min(conv_info.pad_right(), kernel_size / 2); + unsigned int conv_pad_bottom = std::min(conv_info.pad_bottom(), kernel_size / 2); + + unsigned int num_elems_read_per_iteration_x = 0; + unsigned int num_elems_read_per_iteration_y = 0; + unsigned int num_elems_written_per_iteration_x = 0; + unsigned int num_elems_written_per_iteration_y = 0; + + Window win = Window(); + bool window_changed = false; + + if((target == GPUTarget::BIFROST) && (kernel_size <= 5) && (conv_stride_x == 1) && (conv_stride_y == 1) && (data_type == DataType::F32)) + { + // Configure kernel window + win = calculate_max_window(*output); + + switch(kernel_size) + { + case 1: + { + num_elems_read_per_iteration_x = 4; + num_elems_read_per_iteration_y = 4; + num_elems_written_per_iteration_x = 4; + num_elems_written_per_iteration_y = 4; + break; + } + case 3: + { + num_elems_read_per_iteration_x = 6; + num_elems_read_per_iteration_y = 5; + num_elems_written_per_iteration_x = 4; + num_elems_written_per_iteration_y = 3; + break; + } + case 5: + { + num_elems_read_per_iteration_x = 8; + num_elems_read_per_iteration_y = 6; + num_elems_written_per_iteration_x = 4; + num_elems_written_per_iteration_y = 2; + break; + } + default: + { + ARM_COMPUTE_ERROR("Kernel size not optimized for Bifrost"); + } + } + } + else + { + bool is_stride2 = ((kernel_size != 1) && (conv_stride_x == 2)); + + num_elems_read_per_iteration_x = 8 + 2 * (kernel_size / 2) + (is_stride2 ? 6 + kernel_size / 2 : 0); + num_elems_read_per_iteration_y = kernel_size; + num_elems_written_per_iteration_x = 8; + num_elems_written_per_iteration_y = 1; + } + + // Calculate right and bottom border + int input_width = input->dimension(0) - kernel_size / 2 + conv_pad_right; + int input_height = input->dimension(1) - kernel_size / 2 + conv_pad_bottom; + + // Add padding only if necessary or it would always result in a window_changed + if(input_width % num_elems_read_per_iteration_x > 0) + { + input_width += num_elems_read_per_iteration_x; + } + if(input_height % num_elems_read_per_iteration_y > 0) + { + input_height += num_elems_read_per_iteration_y; + } + + // Create window and update padding + win = calculate_max_window(*output, Steps(num_elems_written_per_iteration_x, num_elems_written_per_iteration_y)); + + AccessWindowStatic input_access(input, -conv_pad_left, -conv_pad_top, input_width, input_height); + AccessWindowStatic weights_access(weights, 0, 0, kernel_size, kernel_size); + AccessWindowRectangle output_access(output, 0, 0, num_elems_written_per_iteration_x, num_elems_written_per_iteration_y); + + window_changed = update_window_and_padding(win, input_access, weights_access, output_access); + + output_access.set_valid_region(win, ValidRegion(Coordinates(), output->tensor_shape())); + + Error err = (window_changed) ? ARM_COMPUTE_CREATE_ERROR(ErrorCode::RUNTIME_ERROR, "Insufficient Padding!") : Error{}; + return std::make_pair(err, win); +} } // namespace CLDirectConvolutionLayerKernel::CLDirectConvolutionLayerKernel() @@ -83,6 +237,7 @@ void CLDirectConvolutionLayerKernel::configure(const ICLTensor *input, const ICL TensorShape output_shape = get_output_shape(input->info()->tensor_shape(), weights->info()->tensor_shape(), conv_info); // Output auto inizialitation if not yet initialized + // FIXME: input->clone()->set_tensor_shape(output_shape) doesn't work with subtensors for grouped direct convolutions (AlexNet). auto_init_if_empty(*output->info(), output_shape, 1, @@ -91,11 +246,11 @@ void CLDirectConvolutionLayerKernel::configure(const ICLTensor *input, const ICL input->info()->quantization_info()); // Perform validation step - ARM_COMPUTE_ERROR_THROW_ON(CLDirectConvolutionLayerKernel::validate(input->info(), - weights->info(), - (biases != nullptr) ? biases->info() : nullptr, - output->info(), - conv_info)); + ARM_COMPUTE_ERROR_THROW_ON(validate_arguments(input->info(), + weights->info(), + (biases != nullptr) ? biases->info() : nullptr, + output->info(), + conv_info)); _conv_stride_x = std::get<0>(conv_info.stride()); _conv_stride_y = std::get<1>(conv_info.stride()); @@ -126,14 +281,6 @@ void CLDirectConvolutionLayerKernel::configure(const ICLTensor *input, const ICL kernel_name << "_f32_bifrost"; _kernel = static_cast(CLKernelLibrary::get().create_kernel(kernel_name.str(), build_options.options())); - // Configure kernel window - Window win = calculate_max_window(*output->info()); - - unsigned int num_elems_read_per_iteration_x = 0; - unsigned int num_elems_read_per_iteration_y = 0; - unsigned int num_elems_written_per_iteration_x = 0; - unsigned int num_elems_written_per_iteration_y = 0; - // Through extensive experimentation with over 30 representative tensor // shapes, we found a small number of local work size configurations // that result in nearly optimal execution times. Selecting the right @@ -155,10 +302,6 @@ void CLDirectConvolutionLayerKernel::configure(const ICLTensor *input, const ICL { case 1: { - num_elems_read_per_iteration_x = 4; - num_elems_read_per_iteration_y = 4; - num_elems_written_per_iteration_x = 4; - num_elems_written_per_iteration_y = 4; if(mega_ops_ < 1.f) { _lws_hint = cl::NDRange(1, 1, 8); @@ -175,10 +318,6 @@ void CLDirectConvolutionLayerKernel::configure(const ICLTensor *input, const ICL } case 3: { - num_elems_read_per_iteration_x = 6; - num_elems_read_per_iteration_y = 5; - num_elems_written_per_iteration_x = 4; - num_elems_written_per_iteration_y = 3; if(mega_ops_ < 1.f) { _lws_hint = cl::NDRange(1, 1, 8); @@ -199,10 +338,6 @@ void CLDirectConvolutionLayerKernel::configure(const ICLTensor *input, const ICL } case 5: { - num_elems_read_per_iteration_x = 8; - num_elems_read_per_iteration_y = 6; - num_elems_written_per_iteration_x = 4; - num_elems_written_per_iteration_y = 2; if(mega_ops_ < 2.f || mega_ops_ > 80.f) { _lws_hint = cl::NDRange(2, 1, 4); @@ -218,23 +353,6 @@ void CLDirectConvolutionLayerKernel::configure(const ICLTensor *input, const ICL ARM_COMPUTE_ERROR("Kernel size not optimized for Bifrost"); } } - - // Calculate right and bottom border - const int input_width = input->info()->dimension(0) - kernel_size / 2 + conv_pad_right; - const int input_height = input->info()->dimension(1) - kernel_size / 2 + conv_pad_bottom; - - // Create window and update padding - win = calculate_max_window(*output->info(), Steps(num_elems_written_per_iteration_x, num_elems_written_per_iteration_y)); - - AccessWindowStatic input_access(input->info(), -conv_pad_left, -conv_pad_top, input_width + num_elems_read_per_iteration_x, input_height + num_elems_read_per_iteration_y); - AccessWindowStatic weights_access(weights->info(), 0, 0, kernel_size, kernel_size); - AccessWindowRectangle output_access(output->info(), 0, 0, num_elems_written_per_iteration_x, num_elems_written_per_iteration_y); - - update_window_and_padding(win, input_access, weights_access, output_access); - - output_access.set_valid_region(win, ValidRegion(Coordinates(), output->info()->tensor_shape())); - - ICLKernel::configure(win); } else { @@ -254,34 +372,13 @@ void CLDirectConvolutionLayerKernel::configure(const ICLTensor *input, const ICL // Create kernel _kernel = static_cast(CLKernelLibrary::get().create_kernel(is_quantized_asymm ? "direct_convolution_1x1_3x3_5x5_quantized" : kernel_name.str(), build_options.options())); - - // Configure kernel window - - bool is_stride2 = ((kernel_size != 1) && (_conv_stride_x == 2)); - - const unsigned int num_elems_read_per_iteration_x = 8 + 2 * (kernel_size / 2) + (is_stride2 ? 6 + kernel_size / 2 : 0); - const unsigned int num_elems_read_per_iteration_y = kernel_size; - const unsigned int num_elems_written_per_iteration_x = 8; - const unsigned int num_elems_written_per_iteration_y = 1; - - // Calculate right and bottom border - const int input_width = input->info()->dimension(0) - kernel_size / 2 + conv_pad_right; - const int input_height = input->info()->dimension(1) - kernel_size / 2 + conv_pad_bottom; - - // Create window and update padding - Window win = calculate_max_window(*output->info(), Steps(num_elems_written_per_iteration_x, num_elems_written_per_iteration_y)); - - AccessWindowStatic input_access(input->info(), -conv_pad_left, -conv_pad_top, input_width + num_elems_read_per_iteration_x, input_height + num_elems_read_per_iteration_y); - AccessWindowStatic weights_access(weights->info(), 0, 0, kernel_size, kernel_size); - AccessWindowRectangle output_access(output->info(), 0, 0, num_elems_written_per_iteration_x, num_elems_written_per_iteration_y); - - update_window_and_padding(win, input_access, weights_access, output_access); - - output_access.set_valid_region(win, ValidRegion(Coordinates(), output->info()->tensor_shape())); - - ICLKernel::configure(win); } + // Configure kernel window + auto win_config = validate_and_configure_window(input->info(), weights->info(), output->info(), conv_info, gpu_target); + ARM_COMPUTE_ERROR_THROW_ON(win_config.first); + ICLKernel::configure(win_config.second); + // Set static kernel arguments if(is_data_type_quantized_asymmetric(data_type)) { @@ -322,49 +419,11 @@ void CLDirectConvolutionLayerKernel::configure(const ICLTensor *input, const ICL _config_id += support::cpp11::to_string(output->info()->dimension(1)); } -Error CLDirectConvolutionLayerKernel::validate(const ITensorInfo *input, const ITensorInfo *weights, const ITensorInfo *biases, const ITensorInfo *output, const PadStrideInfo &conv_info) +Error CLDirectConvolutionLayerKernel::validate(const ITensorInfo *input, const ITensorInfo *weights, const ITensorInfo *biases, const ITensorInfo *output, const PadStrideInfo &conv_info, + const GPUTarget target) { - ARM_COMPUTE_RETURN_ERROR_ON_DATA_TYPE_CHANNEL_NOT_IN(input, 1, DataType::QS8, DataType::QASYMM8, DataType::QS16, DataType::F16, DataType::F32); - ARM_COMPUTE_RETURN_ERROR_ON_MISMATCHING_DATA_TYPES(input, weights); - ARM_COMPUTE_RETURN_ERROR_ON_MSG(weights->dimension(0) != weights->dimension(1), - "Weights should have same width as length"); - ARM_COMPUTE_RETURN_ERROR_ON_MSG(weights->dimension(0) != 1 && weights->dimension(0) != 3 && weights->dimension(0) != 5, - "Kernel sizes other than 1x1, 3x3 or 5x5 are not supported"); - ARM_COMPUTE_RETURN_ERROR_ON_MSG(weights->dimension(2) != input->dimension(2), - "Weights feature map dimension should match the respective input's one"); - ARM_COMPUTE_RETURN_ERROR_ON_MSG(weights->dimension(0) != weights->dimension(1), - "Only rectangular weights are supported!"); - ARM_COMPUTE_RETURN_ERROR_ON_MSG(weights->num_dimensions() > 4, - "Weights can be at most 4 dimensional"); - ARM_COMPUTE_RETURN_ERROR_ON_MSG((weights->dimension(0) == 1) && std::get<0>(conv_info.stride()) > 3, - "Strides larger than 3 not supported for 1x1 convolution."); - ARM_COMPUTE_RETURN_ERROR_ON_MSG((weights->dimension(0) == 3 || weights->dimension(0) == 5) && std::get<0>(conv_info.stride()) > 2, - "Strides larger than 2 not supported for 3x3 convolution."); - - if(biases != nullptr) - { - if(is_data_type_quantized_asymmetric(input->data_type())) - { - ARM_COMPUTE_RETURN_ERROR_ON_DATA_TYPE_CHANNEL_NOT_IN(biases, 1, DataType::S32); - } - else - { - ARM_COMPUTE_RETURN_ERROR_ON_MISMATCHING_DATA_TYPES(weights, biases); - } - ARM_COMPUTE_RETURN_ERROR_ON_MSG(biases->dimension(0) != weights->dimension(3), - "Biases size and number of input feature maps should match"); - ARM_COMPUTE_RETURN_ERROR_ON_MSG(biases->num_dimensions() > 1, - "Biases should be one dimensional"); - } - - // Checks performed when output is configured - if(output->total_size() != 0) - { - ARM_COMPUTE_RETURN_ERROR_ON_MISMATCHING_DIMENSIONS(output->tensor_shape(), - get_output_shape(input->tensor_shape(), weights->tensor_shape(), conv_info)); - ARM_COMPUTE_RETURN_ERROR_ON_MISMATCHING_DATA_TYPES(input, output); - ARM_COMPUTE_RETURN_ERROR_ON_MISMATCHING_FIXED_POINT(input, output); - } + ARM_COMPUTE_RETURN_ON_ERROR(validate_arguments(input, weights, biases, output, conv_info)); + ARM_COMPUTE_RETURN_ON_ERROR(validate_and_configure_window(input->clone().get(), weights->clone().get(), output->clone().get(), conv_info, target).first); return Error{}; } -- cgit v1.2.1