/* * Copyright (c) 2021, 2023 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 "Conv3D.h" #include "arm_compute/core/utils/misc/ShapeCalculator.h" #include "arm_compute/core/utils/quantization/AsymmHelpers.h" #include "support/AclRequires.h" #include "tests/validation/reference/UtilsQuantizedAsymm.h" // Source/Destination Tensor shape indices (N D H W C) constexpr unsigned int batch_dim = 4u; constexpr unsigned int depth_dim = 3u; constexpr unsigned int height_dim = 2u; constexpr unsigned int width_dim = 1u; constexpr unsigned int channel_dim = 0u; // Weight tensor shape indices (D H W Cin Cout) constexpr unsigned int weights_depth_dim = 4u; constexpr unsigned int weights_height_dim = 3u; constexpr unsigned int weights_width_dim = 2u; constexpr unsigned int weights_CHin_dim = 1u; constexpr unsigned int weights_CHout_dim = 0u; namespace arm_compute { namespace test { namespace validation { namespace reference { namespace { inline bool is_valid_pixel(int i, int min, int max) { return (i >= min && i < max); } // Evaluate the weights against an element in a given tensor. template < typename T, typename TB, typename std::enable_if < validation::is_floating_point::value &&validation::is_floating_point::value, int >::type = 0 > T calculate_conv3d(const SimpleTensor &src, const SimpleTensor &weights, const SimpleTensor &bias, const Size3D &dilation, int batch, int z_start, int y_start, int x_start, int ch_out, UniformQuantizationInfo oq_info) { ARM_COMPUTE_UNUSED(oq_info); const unsigned int weights_width = weights.shape()[weights_width_dim]; const unsigned int weights_height = weights.shape()[weights_height_dim]; const unsigned int weights_depth = weights.shape()[weights_depth_dim]; const unsigned int src_channels = src.shape()[channel_dim]; const unsigned int src_width = src.shape()[width_dim]; const unsigned int src_height = src.shape()[height_dim]; const unsigned int src_depth = src.shape()[depth_dim]; T total(0); for(unsigned int weight_d = 0; weight_d < weights_depth; ++weight_d) { const int idx_z = z_start + dilation.depth * weight_d; for(unsigned int weight_y = 0; weight_y < weights_height; ++weight_y) { const int idx_y = y_start + dilation.height * weight_y; for(unsigned int weight_x = 0; weight_x < weights_width; ++weight_x) { const int idx_x = x_start + dilation.width * weight_x; //Check if the point is within padding const bool is_x_valid = is_valid_pixel(idx_x, 0, src_width); const bool is_y_valid = is_valid_pixel(idx_y, 0, src_height); const bool is_z_valid = is_valid_pixel(idx_z, 0, src_depth); const bool is_invalid_pixel = !(is_x_valid && is_y_valid && is_z_valid); if(is_invalid_pixel) { continue; } for(unsigned int ch_in = 0; ch_in < src_channels; ++ch_in) { const T *in_ptr = src.data(); const T *w_ptr = weights.data(); const int in_offset = coord2index(src.shape(), Coordinates{ ch_in, idx_x, idx_y, idx_z, batch }); const int weight_offset = coord2index(weights.shape(), Coordinates{ ch_out, ch_in, weight_x, weight_y, weight_d }); T input_value = in_ptr[in_offset]; T weight_value = w_ptr[weight_offset]; total += (input_value * weight_value); } } } } const TB *b_ptr = bias.data(); TB bias_value = b_ptr[ch_out]; return total + bias_value; } template < typename T, typename TB, ARM_COMPUTE_REQUIRES_TA(std::is_same::value || std::is_same::value) > T calculate_conv3d(const SimpleTensor &src, const SimpleTensor &weights, const SimpleTensor &bias, const Size3D &dilation, int batch, int z_start, int y_start, int x_start, int ch_out, UniformQuantizationInfo oq_info) { const unsigned int weights_width = weights.shape()[weights_width_dim]; const unsigned int weights_height = weights.shape()[weights_height_dim]; const unsigned int weights_depth = weights.shape()[weights_depth_dim]; const unsigned int src_channels = src.shape()[channel_dim]; const unsigned int src_width = src.shape()[width_dim]; const unsigned int src_height = src.shape()[height_dim]; const unsigned int src_depth = src.shape()[depth_dim]; const UniformQuantizationInfo iq_info = src.quantization_info().uniform(); const UniformQuantizationInfo wq_info = weights.quantization_info().uniform(); const int input_offset = -iq_info.offset; const float input_scale = iq_info.scale; int weights_offset = -wq_info.offset; float weights_scale = wq_info.scale; const int output_offset = oq_info.offset; const float output_scale = oq_info.scale; int output_multiplier = 0; int output_shift = 0; const float multiplier = input_scale * weights_scale / output_scale; arm_compute::quantization::calculate_quantized_multiplier(multiplier, &output_multiplier, &output_shift); int32_t total(0); for(unsigned int weight_d = 0; weight_d < weights_depth; ++weight_d) { const int idx_z = z_start + dilation.depth * weight_d; for(unsigned int weight_y = 0; weight_y < weights_height; ++weight_y) { const int idx_y = y_start + dilation.height * weight_y; for(unsigned int weight_x = 0; weight_x < weights_width; ++weight_x) { const int idx_x = x_start + dilation.width * weight_x; //Check if the point is within padding const bool is_x_valid = is_valid_pixel(idx_x, 0, src_width); const bool is_y_valid = is_valid_pixel(idx_y, 0, src_height); const bool is_z_valid = is_valid_pixel(idx_z, 0, src_depth); const bool is_invalid_pixel = !(is_x_valid && is_y_valid && is_z_valid); if(is_invalid_pixel) { continue; } for(unsigned int ch_in = 0; ch_in < src_channels; ++ch_in) { const T *in_ptr = src.data(); const T *w_ptr = weights.data(); const int in_offset = coord2index(src.shape(), Coordinates{ ch_in, idx_x, idx_y, idx_z, batch }); const int weight_offset = coord2index(weights.shape(), Coordinates{ ch_out, ch_in, weight_x, weight_y, weight_d }); T input_value = in_ptr[in_offset]; T weight_value = w_ptr[weight_offset]; total += ((input_value + input_offset) * (weight_value + weights_offset)); } } } } const TB *b_ptr = bias.data(); TB bias_value = b_ptr[ch_out]; total += bias_value; return validation::quantize_down_scale_by_fixedpoint(total, output_multiplier, output_shift, output_offset, std::numeric_limits::lowest(), std::numeric_limits::max()); } } // namespace template SimpleTensor conv3d(const SimpleTensor &src, const SimpleTensor &weights, const SimpleTensor &bias, SimpleTensor &dst, const Conv3dInfo &conv3d_info) { // Compute reference const unsigned int batch_size = src.shape()[batch_dim]; const unsigned int dst_width = dst.shape()[width_dim]; const unsigned int dst_height = dst.shape()[height_dim]; const unsigned int dst_depth = dst.shape()[depth_dim]; const unsigned int src_channels = src.shape()[channel_dim]; const unsigned int weights_out_ch = weights.shape()[weights_CHout_dim]; const unsigned int dst_channels = dst.shape()[channel_dim]; const size_t pad_left = conv3d_info.padding.left; const size_t pad_top = conv3d_info.padding.top; const size_t pad_front = conv3d_info.padding.front; const size_t stride_x = conv3d_info.stride.x(); const size_t stride_y = conv3d_info.stride.y(); const size_t stride_z = conv3d_info.stride.z(); const TensorShape dst_shape = arm_compute::misc::shape_calculator::compute_conv3d_shape(src.shape(), weights.shape(), conv3d_info); ARM_COMPUTE_UNUSED(src_channels, weights_out_ch, dst_channels, dst_shape, weights_CHin_dim); // Number of batches of source and destination tensors must match. ARM_COMPUTE_ERROR_ON(src.shape()[batch_dim] != dst.shape()[batch_dim]); // Input channels in the source and weights must match. ARM_COMPUTE_ERROR_ON(src_channels != weights.shape()[weights_CHin_dim]); // Weight channels in the destination and weights must match. ARM_COMPUTE_ERROR_ON(weights_out_ch != dst_channels); // Bias must match the number of destination channels. ARM_COMPUTE_ERROR_ON(bias.shape()[0] != dst_channels); // Compare given dst tensor shape with expected shape. ARM_COMPUTE_ERROR_ON(dst.shape() != dst_shape); for(unsigned int batch = 0; batch < batch_size; ++batch) { for(unsigned int z_out = 0; z_out < dst_depth; ++z_out) { const int z_start = (z_out * stride_z) - pad_front; for(unsigned int y_out = 0; y_out < dst_height; ++y_out) { const int y_start = (y_out * stride_y) - pad_top; for(unsigned int x_out = 0; x_out < dst_width; ++x_out) { const int x_start = (x_out * stride_x) - pad_left; for(unsigned int ch_out = 0; ch_out < dst_channels; ++ch_out) { T *out_ptr = dst.data(); const int out_offset = coord2index(dst.shape(), Coordinates{ ch_out, x_out, y_out, z_out, batch }); out_ptr[out_offset] = calculate_conv3d(src, weights, bias, conv3d_info.dilation, batch, z_start, y_start, x_start, ch_out, dst.quantization_info().uniform()); } } } } } return dst; } template SimpleTensor conv3d(const SimpleTensor &src, const SimpleTensor &weights, const SimpleTensor &bias, SimpleTensor &dst, const Conv3dInfo &conv3d_info); template SimpleTensor conv3d(const SimpleTensor &src, const SimpleTensor &weights, const SimpleTensor &bias, SimpleTensor &dst, const Conv3dInfo &conv3d_info); template SimpleTensor conv3d(const SimpleTensor &src, const SimpleTensor &weights, const SimpleTensor &bias, SimpleTensor &dst, const Conv3dInfo &conv3d_info); template SimpleTensor conv3d(const SimpleTensor &src, const SimpleTensor &weights, const SimpleTensor &bias, SimpleTensor &dst, const Conv3dInfo &conv3d_info); } // namespace reference } // namespace validation } // namespace test } // namespace arm_compute