blob: 07c582059f8a2a609348de2b0b28a96b3edce558 [file] [log] [blame]
/*
* 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.
*/
#pragma once
#include "depthfirst_driver.hpp"
#include "utils.hpp"
#if !defined(_WIN64) && !defined(__OpenBSD__)
#include <alloca.h>
#endif /* !defined(_WIN64) && !defined(__OpenBSD__) */
namespace arm_conv {
namespace pooling {
template <typename TInput, typename TOutput, typename OutputStage = Nothing>
class IGenericDepthfirstStrategy;
template <typename TInput, typename TOutput>
class IGenericDepthfirstStrategy<TInput, TOutput, Nothing>
{
public:
virtual ~IGenericDepthfirstStrategy() = default;
typedef void (*KernelType)(
uint64_t window_cells,
uint64_t n_valid_cells,
uint64_t n_channels,
const TInput *const *,
TOutput *
);
virtual KernelType get_kernel(void) const = 0;
};
template <typename TInput, typename TOutput>
class IGenericDepthfirstStrategy<TInput, TOutput, Requantize32>
{
public:
virtual ~IGenericDepthfirstStrategy() = default;
typedef void (*KernelType)(
uint64_t window_cells,
uint64_t n_valid_cells,
uint64_t n_channels,
const TInput *const *,
TOutput *,
const Requantize32 &
);
virtual KernelType get_kernel(void) const = 0;
};
template <typename TInput, typename TOutput, typename OutputStage>
struct Invoker;
template <typename TInput, typename TOutput>
struct Invoker<TInput, TOutput, Nothing>
{
static inline void invoke(
const typename IGenericDepthfirstStrategy<TInput, TOutput, Nothing>::KernelType kern,
uint64_t window_cells,
uint64_t n_valid_cells,
uint64_t n_channels,
const TInput *const *inptrs,
TOutput *outptr,
const Nothing &
)
{
kern(window_cells, n_valid_cells, n_channels, inptrs, outptr);
}
};
template <typename TInput, typename TOutput>
struct Invoker<TInput, TOutput, Requantize32>
{
static inline void invoke(
const typename IGenericDepthfirstStrategy<TInput, TOutput, Requantize32>::KernelType kern,
uint64_t window_cells,
uint64_t n_valid_cells,
uint64_t n_channels,
const TInput *const *inptrs,
TOutput *outptr,
const Requantize32 &qp
)
{
kern(window_cells, n_valid_cells, n_channels, inptrs, outptr, qp);
}
};
template <typename TInput, typename TOutput, typename OutputStage>
class GenericDepthfirstWrapper : public IDepthfirstStrategy
{
using StratType = IGenericDepthfirstStrategy<TInput, TOutput, OutputStage>;
std::unique_ptr<const StratType> m_strat;
const unsigned int window_rows, window_cols;
public:
GenericDepthfirstWrapper(const StratType *strat, const PoolingArgs &args)
: m_strat(strat), window_rows(args.pool_window.rows), window_cols(args.pool_window.cols)
{
}
unsigned int get_input_rows(void) const override { return window_rows; }
unsigned int get_input_cols(void) const override { return window_cols; }
unsigned int get_output_rows(void) const override { return 1; }
unsigned int get_output_cols(void) const override { return 1; }
typename StratType::KernelType get_kernel(void) const { return m_strat->get_kernel(); }
};
template <typename TInput, typename TOutput=TInput, typename OutputStage=Nothing>
class PoolingDepthfirstGeneric : public DepthfirstDriver<TInput, TOutput>
{
const OutputStage m_os;
protected:
size_t get_working_size_per_thread(unsigned int) const override { return 0; }
void initialise_working_space(void *, unsigned int) const override { /* Nothing */ }
/* Compute a portion of the output tensor with padding. */
void compute_tile_padded(
unsigned int output_i, unsigned int output_j,
unsigned int channel_start, unsigned int channel_end,
const TensorSpec<const TInput *> &input,
const TensorSpec<TOutput *> &output,
void *
) const override
{
// Determine start position and padding
const int start_i = static_cast<int>(output_i * this->m_args.pool_stride.rows) - this->m_args.padding.top;
const auto input_i = static_cast<unsigned int>(start_i < 0 ? 0 : start_i);
const auto pad_top = static_cast<unsigned int>(start_i < 0 ? -start_i : 0);
const int end_i = start_i + this->m_args.pool_window.rows;
const auto pad_bottom = static_cast<unsigned int>((unsigned int) end_i < this->m_args.input_rows ? 0 : end_i - this->m_args.input_rows);
const auto valid_rows = this->m_args.pool_window.rows - (pad_top + pad_bottom);
const int start_j = static_cast<int>(output_j * this->m_args.pool_stride.cols) - this->m_args.padding.left;
const auto input_j = static_cast<unsigned int>(start_j < 0 ? 0 : start_j);
const auto pad_left = static_cast<unsigned int>(start_j < 0 ? -start_j : 0);
const int end_j = start_j + this->m_args.pool_window.cols;
const auto pad_right = static_cast<unsigned int>((unsigned int) end_j < this->m_args.input_cols ? 0 : end_j - this->m_args.input_cols);
const auto valid_cols = this->m_args.pool_window.cols - (pad_left + pad_right);
// Determine the number of valid cells and prepare the pointers
const auto n_valid_cells = valid_rows * valid_cols;
auto inptrs = reinterpret_cast<const TInput **>(alloca(n_valid_cells * sizeof(TInput *)));
{
auto my_ptr = inptrs;
auto row_ptr = input.base + input_i*input.ld_row + input_j*input.ld_col + channel_start;
for (auto i = valid_rows; i; i--)
{
auto ptr = row_ptr;
row_ptr += input.ld_row;
for (auto j = valid_cols; j; j--)
{
*(my_ptr++) = ptr;
ptr += input.ld_col;
}
}
}
auto outptr = output.base + output_i*output.ld_row + output_j*output.ld_col + channel_start;
// Some padding variants include (or exclude) the padding values; we handle
// this by computing the extent of the padded input tensor and hence
// computing the total number of cells captured in the pooling window.
const auto bottom_padded_height = this->m_args.input_rows + this->m_args.padding.bottom;
const auto captured_rows = std::min<int>(end_i, bottom_padded_height) - start_i;
const auto right_padded_width = this->m_args.input_cols + this->m_args.padding.right;
const auto captured_cols = std::min<int>(end_j, right_padded_width) - start_j;
const auto captured_cells = captured_rows * captured_cols;
const auto window_cells = this->m_args.exclude_padding ? n_valid_cells : captured_cells;
// Execute the kernel
Invoker<TInput, TOutput, OutputStage>::invoke(
reinterpret_cast<const GenericDepthfirstWrapper<TInput, TOutput, OutputStage> *>(this->m_strat.get())->get_kernel(),
window_cells, n_valid_cells, channel_end - channel_start, inptrs, outptr, m_os
);
}
// Compute a portion of the work with only top/bottom padding.
void compute_row_padded_tile_row(
const unsigned int output_i, unsigned int output_j, unsigned int n_tile_cols,
const unsigned int channel_start, const unsigned int channel_end,
const TensorSpec<const TInput *> &input,
const TensorSpec<TOutput *> &output,
void *
) const override
{
// Determine start position and padding
const int start_i = static_cast<int>(output_i * this->m_args.pool_stride.rows) - this->m_args.padding.top;
const auto input_i = static_cast<unsigned int>(start_i < 0 ? 0 : start_i);
const auto pad_top = static_cast<unsigned int>(start_i < 0 ? -start_i : 0);
const int end_i = start_i + this->m_args.pool_window.rows;
const auto pad_bottom = static_cast<unsigned int>((unsigned int) end_i < this->m_args.input_rows ? 0 : end_i - this->m_args.input_rows);
const auto valid_rows = this->m_args.pool_window.rows - (pad_top + pad_bottom);
const int start_j = static_cast<int>(output_j * this->m_args.pool_stride.cols) - this->m_args.padding.left;
const auto input_j = static_cast<unsigned int>(start_j < 0 ? 0 : start_j);
const auto valid_cols = this->m_args.pool_window.cols;
// Determine the number of valid cells and prepare the pointers
const auto n_valid_cells = valid_rows * valid_cols;
auto inptrs = reinterpret_cast<const TInput **>(alloca(n_valid_cells * sizeof(TInput *)));
{
auto my_ptr = inptrs;
auto row_ptr = input.base + input_i*input.ld_row + input_j*input.ld_col + channel_start;
for (auto i = valid_rows; i; i--)
{
auto ptr = row_ptr;
row_ptr += input.ld_row;
for (auto j = valid_cols; j; j--)
{
*(my_ptr++) = ptr;
ptr += input.ld_col;
}
}
}
auto outptr = output.base + output_i*output.ld_row + output_j*output.ld_col + channel_start;
// Some padding variants include (or exclude) the padding values; we handle
// this by computing the extent of the padded input tensor and hence
// computing the total number of cells captured in the pooling window.
const auto bottom_padded_height = this->m_args.input_rows + this->m_args.padding.bottom;
const auto captured_rows = std::min<int>(end_i, bottom_padded_height) - start_i;
const auto captured_cells = captured_rows * valid_cols;
const auto window_cells = this->m_args.exclude_padding ? n_valid_cells : captured_cells;
for (; n_tile_cols; n_tile_cols--)
{
// Execute the kernel
Invoker<TInput, TOutput, OutputStage>::invoke(
reinterpret_cast<const GenericDepthfirstWrapper<TInput, TOutput, OutputStage> *>(this->m_strat.get())->get_kernel(),
window_cells, n_valid_cells, channel_end - channel_start, inptrs, outptr, m_os
);
// Update the pointers; the output strides by a column and the inputs
// stride by a number of columns.
outptr += output.ld_col;
for (auto n = 0u; n < n_valid_cells; n++)
{
inptrs[n] += this->m_args.pool_stride.cols * input.ld_col;
}
}
}
public:
PoolingDepthfirstGeneric(
const IGenericDepthfirstStrategy<TInput, TOutput, OutputStage> *strat,
const PoolingArgs &args,
const OutputStage &os = {}
)
: DepthfirstDriver<TInput, TOutput>(
new GenericDepthfirstWrapper<TInput, TOutput, OutputStage>(strat, args),
args
),
m_os(os)
{
}
};
} // namespace pooling
} // namespace arm_conv