aboutsummaryrefslogtreecommitdiff
path: root/src/core/NEON/kernels/arm_conv/depthwise/depthwise_depthfirst.hpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/NEON/kernels/arm_conv/depthwise/depthwise_depthfirst.hpp')
-rw-r--r--src/core/NEON/kernels/arm_conv/depthwise/depthwise_depthfirst.hpp700
1 files changed, 700 insertions, 0 deletions
diff --git a/src/core/NEON/kernels/arm_conv/depthwise/depthwise_depthfirst.hpp b/src/core/NEON/kernels/arm_conv/depthwise/depthwise_depthfirst.hpp
new file mode 100644
index 0000000000..7b00c9a7af
--- /dev/null
+++ b/src/core/NEON/kernels/arm_conv/depthwise/depthwise_depthfirst.hpp
@@ -0,0 +1,700 @@
+/*
+ * 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 "src/core/NEON/kernels/arm_conv/addressing.hpp"
+#include "depthwise_strategies_common.hpp"
+#include "working_space.hpp"
+
+#ifdef CYCLE_PROFILING
+#include "profiler.hpp"
+#endif
+
+#include <limits>
+
+namespace arm_conv {
+namespace depthwise {
+
+template <typename TInput, typename TWeight, typename TOutput, typename TAccum,
+ typename OutputStage>
+class DepthwiseDepthfirstStrategyCommon
+ : public DepthfirstStrategy<TInput, TWeight, TOutput, TAccum, OutputStage>
+{
+ protected:
+ unsigned int m_output_rows, m_output_cols;
+ unsigned int m_kernel_rows, m_kernel_cols;
+ unsigned int m_stride_rows, m_stride_cols;
+
+ public:
+ DepthwiseDepthfirstStrategyCommon(
+ unsigned int output_rows, unsigned int output_cols,
+ unsigned int kernel_rows, unsigned int kernel_cols,
+ unsigned int stride_rows=1, unsigned int stride_cols=1
+ ) : m_output_rows(output_rows), m_output_cols(output_cols),
+ m_kernel_rows(kernel_rows), m_kernel_cols(kernel_cols),
+ m_stride_rows(stride_rows), m_stride_cols(stride_cols)
+ {
+ }
+
+ DepthwiseDepthfirstStrategyCommon(unsigned int output_size, unsigned int kernel_size, unsigned int stride=1)
+ : DepthwiseDepthfirstStrategyCommon(output_size, output_size, kernel_size, kernel_size, stride, stride)
+ {
+ }
+
+ virtual ~DepthwiseDepthfirstStrategyCommon() {}
+
+ unsigned int get_output_rows() const override { return m_output_rows; }
+ unsigned int get_output_cols() const override { return m_output_cols; }
+
+ unsigned int get_kernel_rows() const override { return m_kernel_rows; }
+ unsigned int get_kernel_cols() const override { return m_kernel_cols; }
+
+ unsigned int get_stride_rows() const override { return m_stride_rows; }
+ unsigned int get_stride_cols() const override { return m_stride_cols; }
+};
+
+template <typename TInput, typename TWeight, typename TOutput, typename TAccum, typename OutputStage=typename DefaultOutputStage<TOutput>::Type>
+class DepthwiseDepthfirstStrategy : public DepthwiseDepthfirstStrategyCommon<TInput, TWeight, TOutput, TAccum, OutputStage>
+{
+ using Parent = DepthwiseDepthfirstStrategyCommon<TInput, TWeight, TOutput, TAccum, OutputStage>;
+
+ public:
+ using Parent::Parent;
+
+ typedef void (*IndirectKernelType)(
+ const TInput *const *input_ptrs,
+ TOutput *const *output_ptrs,
+ const void *params,
+ unsigned int n_channels,
+ const TAccum activation_min,
+ const TAccum activation_max
+ );
+ virtual IndirectKernelType get_indirect_kernel(void) const = 0;
+
+ typedef void (*DirectKernelType)(
+ const unsigned int n_tile_rows, const unsigned int n_tile_cols,
+ const TInput *inptr_base, int64_t ld_input_row, int64_t ld_input_col,
+ TOutput *outptr_base, int64_t ld_output_row, int64_t ld_output_col,
+ const void *params, unsigned int n_channels,
+ const TAccum activation_min,
+ const TAccum activation_max
+ );
+ virtual DirectKernelType get_direct_kernel(void) const = 0;
+};
+
+template <typename TInput, typename TWeight, typename TOutput>
+class DepthwiseDepthfirstStrategy<TInput, TWeight, TOutput, int32_t>
+: public DepthwiseDepthfirstStrategyCommon<TInput, TWeight, TOutput, int32_t, arm_gemm::Requantize32>
+{
+ using Parent = DepthwiseDepthfirstStrategyCommon<TInput, TWeight, TOutput, int32_t, arm_gemm::Requantize32>;
+
+ protected:
+ interleaves::PackingArguments get_packing_args(void) const
+ {
+ return interleaves::PackingArguments(
+ this->get_kernel_rows(), this->get_kernel_cols(), sizeof(TWeight),
+ false, sizeof(int32_t), this->uses_premultiply(), // Don't pack the bias
+ this->get_vl_type(), sizeof(int32_t), this->get_accumulator_depth_vl(),
+ [this] (unsigned int idx, unsigned int &x, unsigned int &y) -> bool
+ { return this->get_kernel_packing_point(idx, x, y); }
+ );
+ }
+
+ public:
+ using Parent::Parent;
+
+ typedef void (*KernelType)(
+ unsigned int, // n_channels,
+ const TInput *const *, // inptrs
+ const TWeight *, // weights
+ const int32_t *, // bias,
+ const arm_gemm::Requantize32 &,
+ const int32_t *, const int32_t *, // requant_muls and requant_shifts
+ TOutput *const * // outptrs
+ );
+ virtual KernelType get_kernel() const = 0;
+
+ size_t get_storage_size(const DepthwiseArgs &args) const override
+ {
+ return interleaves::get_storage_size_generic(get_packing_args(), args);
+ }
+
+ void pack_parameters(
+ const DepthwiseArgs &args, void *buffer,
+ const void *biases, const arm_gemm::Requantize32 &,
+ const void *weights, size_t ld_weight_col, size_t ld_weight_row
+ ) const override
+ {
+ interleaves::pack_parameters_generic(
+ get_packing_args(), args, buffer, biases, weights, ld_weight_col, ld_weight_row);
+ }
+};
+
+template <typename TInput, typename TWeight, typename TOutput, typename TAccum, typename OutputStage>
+class DepthwiseDepthfirstCommon : public DepthfirstDriver<TInput, TWeight, TOutput>
+{
+ using StratType = DepthwiseDepthfirstStrategyCommon<TInput, TWeight, TOutput, TAccum, OutputStage>;
+ OutputStage m_os;
+
+ protected:
+ inline OutputStage &get_output_stage(void) { return m_os; }
+ inline const OutputStage &get_output_stage(void) const { return m_os; }
+
+ bool uses_intermediate_array() const
+ {
+ return this->m_args.channel_multiplier != 1 && this->uses_premultiply();
+ }
+
+ virtual void fill_inptr_array(const DepthwiseArgs &args,
+ const TensorSpec<const TInput *> &input,
+ const TInput **inptr_array, TInput *input_buffer,
+ const unsigned int input_i, const unsigned int input_j,
+ const unsigned int input_pad_top, const unsigned int input_pad_left) const = 0;
+
+ void initialise_inptr_array(const DepthwiseArgs &args,
+ unsigned int output_channel_start, unsigned int output_channel_end,
+ const TensorSpec<const TInput *> &input,
+ const TInput **inptr_array, TInput *input_buffer, TInput *intermediate_buffer,
+ const unsigned int input_i, const unsigned int input_j,
+ const unsigned int input_pad_top, const unsigned int input_pad_left,
+ Tile<TInput> &multiplied_input
+ ) const
+ {
+ // Compute the input pointer array
+ const auto input_channel_start = output_channel_start / args.channel_multiplier;
+
+ const auto last_valid_row = std::min(input_pad_top + args.input_rows - input_i, this->m_strat->get_input_rows());
+ const auto last_valid_col = std::min(input_pad_left + args.input_cols - input_j, this->m_strat->get_input_cols());
+
+ const auto tile_rows = last_valid_row - input_pad_top;
+ const auto tile_cols = last_valid_col - input_pad_left;
+
+ const auto tile_channels = output_channel_end - output_channel_start;
+
+ TensorSpec<const TInput *> tile_tensor(0, 0, 0);
+ if (this->uses_intermediate_array()) {
+ multiplied_input = Tile<TInput>(intermediate_buffer, tile_rows, tile_cols, tile_channels);
+ multiplied_input.load_from(input.base, input.ld_row, input.ld_col,
+ args.input_rows, args.input_cols,
+ input_i, input_j, args.channel_multiplier);
+
+ tile_tensor = TensorSpec<const TInput *>(
+ multiplied_input.array,
+ tile_cols * tile_channels, tile_channels
+ );
+ } else {
+ tile_tensor = TensorSpec<const TInput *>(
+ input.base + input_i*input.ld_row + input_j*input.ld_col + input_channel_start,
+ input.ld_row, input.ld_col
+ );
+ }
+
+ fill_inptr_array(args,
+ tile_tensor,
+ inptr_array, input_buffer,
+ input_i, input_j,
+ input_pad_top,
+ input_pad_left
+ );
+ }
+
+ public:
+ DepthwiseDepthfirstCommon(StratType *const strat, const DepthwiseArgs &args, const OutputStage &os)
+ : DepthfirstDriver<TInput, TWeight, TOutput>(strat, args), m_os(os)
+ {
+ }
+
+ DepthwiseDepthfirstCommon(DepthwiseDepthfirstCommon &) = delete;
+ DepthwiseDepthfirstCommon &operator=(DepthwiseDepthfirstCommon &) = delete;
+
+ size_t get_storage_size(void) const override
+ {
+ return reinterpret_cast<const StratType *>(this->m_strat.get())->
+ get_storage_size(this->m_args);
+ }
+
+ void pack_parameters(void *buffer, const void *biases, const void *weights, size_t ld_weight_col, size_t ld_weight_row) override
+ {
+ reinterpret_cast<const StratType *>(this->m_strat.get())->
+ pack_parameters(this->m_args, buffer, biases, m_os, weights, ld_weight_col, ld_weight_row);
+ }
+};
+
+namespace depthwise_depthfirst {
+
+/* Workspace Element for an array of input pointers as consumed by the
+ * specialised depthwise kernels.
+ */
+template <typename T>
+class InputArrayElement
+{
+ public:
+ struct Workspace
+ {
+ const T **inptr_array;
+ };
+
+ template <class OutputStage>
+ static size_t get_element_size(const WorkspaceArgs<IDepthfirstStrategy, OutputStage> &args)
+ {
+ return sizeof(T **) * args.strategy->get_input_rows() * args.strategy->get_input_cols();
+ }
+
+ template <class WorkspaceType, class OutputStage>
+ static void *initialise(WorkspaceType *ws, void *buffer, const WorkspaceArgs<IDepthfirstStrategy, OutputStage> &args)
+ {
+ ws->inptr_array = reinterpret_cast<const T**>(buffer);
+ return reinterpret_cast<char *>(buffer) + get_element_size(args);
+ }
+};
+
+template <typename TAccum, typename OutputStage, bool IsDot=false>
+struct WorkspaceFinalElement
+{
+ using Element = ActivationsElement<TAccum, OutputStage>;
+};
+
+template <>
+struct WorkspaceFinalElement<int32_t, arm_gemm::Requantize32, false>
+{
+ using Element = RequantizationParametersElement;
+};
+
+template <typename TInput, typename TWeight, typename TOutput, typename TAccum, typename OutputStage>
+struct Invoke
+{
+ constexpr static bool supports_direct_kernel = true;
+
+ template <typename Strat, typename Workspace>
+ static inline void indirect(const Strat *strat, const Workspace *ws, const OutputStage &, const void *params, const TAccum *, unsigned int n_channels)
+ {
+ strat->get_indirect_kernel()(
+ ws->inptr_array,
+ ws->outptr_array,
+ params, n_channels,
+ ws->activation_min, ws->activation_max
+ );
+ }
+
+ template <typename Strat, typename Workspace>
+ static void direct(
+ const Strat *strat, const Workspace *ws, const OutputStage &,
+ unsigned int n_tile_rows, unsigned int n_tile_cols,
+ const TInput *inptr, size_t ld_in_row, size_t ld_in_col,
+ TOutput *outptr, size_t ld_out_row, size_t ld_out_col,
+ const void *params, unsigned int n_channels
+ )
+ {
+ strat->get_direct_kernel()(
+ n_tile_rows, n_tile_cols,
+ inptr, ld_in_row, ld_in_col,
+ outptr, ld_out_row, ld_out_col,
+ params, n_channels, ws->activation_min, ws->activation_max
+ );
+ }
+};
+
+template <typename TInput, typename TWeight, typename TOutput, typename TAccum>
+struct Invoke<TInput, TWeight, TOutput, TAccum, arm_gemm::Requantize32>
+{
+ constexpr static bool supports_direct_kernel = false;
+
+ template <typename Strat, typename Workspace>
+ static inline void indirect(const Strat *strat, const Workspace *ws, const arm_gemm::Requantize32 &qp, const void *params, const TAccum *, unsigned int n_channels)
+ {
+ strat->get_kernel()(
+ n_channels, ws->inptr_array,
+ reinterpret_cast<const TWeight *>(params), ws->bias,
+ qp, ws->requant_muls, ws->requant_shifts,
+ ws->outptr_array
+ );
+ }
+
+ template <typename Strat, typename Workspace>
+ static inline void direct(
+ const Strat *, const Workspace *, const arm_gemm::Requantize32 &,
+ unsigned int, unsigned int, // n_tile_rows, n_tile_cols
+ const TInput *, size_t, size_t, // Input pointer, row stride, column stride
+ TOutput *, size_t, size_t, // Output pointer, row stride, column stride
+ const void *, unsigned int // Parameters, number of channels
+ )
+ {
+ // Do nothing - this should never be reached because entry to it is guarded
+ // by an `if` on a `constexpr static bool`.
+ }
+};
+
+namespace
+{
+
+template <typename OutputStage>
+inline void stash_bias(OutputStage &, const void *) {}
+
+template <>
+inline void stash_bias(arm_gemm::Requantize32 &qp, const void *bias) __attribute__ ((unused));
+
+template <>
+inline void stash_bias(arm_gemm::Requantize32 &qp, const void *bias)
+{
+ qp.bias = reinterpret_cast<const int32_t *>(bias);
+}
+
+}
+
+} // namespace depthwise_depthfirst
+
+template <typename TInput,
+ typename TWeight=TInput,
+ typename TOutput=TInput,
+ typename TAccum=typename DefaultTAccum<TInput>::Type,
+ typename OutputStage=typename DefaultOutputStage<TOutput>::Type>
+class DepthwiseDepthfirst
+: public DepthwiseDepthfirstCommon<TInput, TWeight, TOutput, TAccum, OutputStage>
+{
+ using StratType = DepthwiseDepthfirstStrategy<TInput, TWeight, TOutput, TAccum>;
+ using Parent = DepthwiseDepthfirstCommon<TInput, TWeight, TOutput, TAccum, OutputStage>;
+ using WorkspaceManager = Workspace<
+ OutputArrayElement<TOutput>,
+ depthwise_depthfirst::InputArrayElement<TInput>,
+ InputBufferElement<TInput>,
+ IntermediateBufferElement<TInput>,
+ typename depthwise_depthfirst::WorkspaceFinalElement<TAccum, OutputStage>::Element
+ >;
+ using WorkingSpace = typename WorkspaceManager::WorkspaceType;
+
+ // We keep a copy of the bias and output stage
+ const TAccum *m_bias;
+
+ public:
+ DepthwiseDepthfirst(StratType *const strat, const DepthwiseArgs &args, const OutputStage &os = {})
+ : Parent(strat, args, os), m_bias(nullptr)
+ {
+ }
+
+ DepthwiseDepthfirst(DepthwiseDepthfirst &) = delete;
+ DepthwiseDepthfirst &operator=(DepthwiseDepthfirst &) = delete;
+
+ void pack_parameters(void *buffer, const void *biases, const void *weights, size_t ld_weight_col, size_t ld_weight_row) override
+ {
+ reinterpret_cast<const StratType *>(this->m_strat.get())->pack_parameters(
+ this->m_args, buffer, biases, this->get_output_stage(),
+ weights, ld_weight_col, ld_weight_row
+ );
+ m_bias = reinterpret_cast<const TAccum *>(biases);
+ depthwise_depthfirst::stash_bias(this->get_output_stage(), biases);
+ }
+
+ size_t get_working_size_per_thread() const override
+ {
+ DepthwiseArgs args(this->m_args);
+ return WorkspaceManager::get_sizeof_workspace(
+ WorkspaceArgs<IDepthfirstStrategy, OutputStage>(this->m_strat.get(), args, this->get_output_stage())
+ );
+ }
+
+ void initialise_working_space(void *buffer) const override
+ {
+ DepthwiseArgs args(this->m_args);
+ WorkspaceManager::initialise(
+ buffer, WorkspaceArgs<IDepthfirstStrategy, OutputStage>(this->m_strat.get(), args, this->get_output_stage())
+ );
+ }
+
+ virtual bool supports_direct_padding() const override
+ {
+ using Invoker = depthwise_depthfirst::Invoke<TInput, TWeight, TOutput, TAccum, OutputStage>;
+ return Invoker::supports_direct_kernel && this->uses_intermediate_array();
+ }
+
+ protected:
+
+ void fill_inptr_array(const DepthwiseArgs &args,
+ const TensorSpec<const TInput *> &input,
+ const TInput **inptr_array, TInput *input_buffer,
+ const unsigned int input_i, const unsigned int input_j,
+ const unsigned int input_pad_top, const unsigned int input_pad_left) const override
+ {
+ fill_pointer_array<const TInput>(
+ inptr_array, this->m_strat->get_input_rows(), this->m_strat->get_input_cols(),
+ input.base,
+ input.ld_row, input.ld_col,
+ input_buffer,
+ input_pad_top, args.input_rows - input_i,
+ input_pad_left, args.input_cols - input_j
+ );
+ }
+
+ void compute_tile_padded(
+ const DepthwiseArgs &args,
+ unsigned int output_i, unsigned int output_j,
+ unsigned int output_channel_start, unsigned int output_channel_end,
+ const TensorSpec<const TInput *> &input,
+ const TensorSpec<TOutput *> &output,
+ const void *parameters,
+ void *working_space_raw
+ ) const override
+ {
+ // Get the working space
+ auto ws = reinterpret_cast<WorkingSpace *>(working_space_raw);
+
+ // Compute the input pointer array
+ const int ii = static_cast<int>(output_i * args.stride_rows) - args.padding.top;
+ const auto input_pad_top = static_cast<unsigned int>(ii < 0 ? -ii : 0);
+ const auto input_i = static_cast<unsigned int>(ii < 0 ? 0 : ii);
+
+ const int ij = static_cast<int>(output_j * args.stride_cols) - args.padding.left;
+ const auto input_pad_left = static_cast<unsigned int>(ij < 0 ? -ij : 0);
+ const auto input_j = static_cast<unsigned int>(ij < 0 ? 0 : ij);
+
+ Tile<TInput> multiplied_input;
+ this->initialise_inptr_array(args, output_channel_start, output_channel_end, input,
+ ws->inptr_array, ws->input_buffer, ws->intermediate_buffer,
+ input_i, input_j, input_pad_top, input_pad_left, multiplied_input);
+
+ // Compute the output pointer array
+ fill_pointer_array(
+ ws->outptr_array, this->m_strat->get_output_rows(), this->m_strat->get_output_cols(),
+ output.base + output_i*output.ld_row + output_j*output.ld_col + output_channel_start,
+ output.ld_row, output.ld_col,
+ ws->output_buffer,
+ 0, args.output_rows - output_i, // Top padding, # valid rows
+ 0, args.output_cols - output_j // Left padding, # valid columns
+ );
+
+ // Execute the kernel
+ depthwise_depthfirst::Invoke<TInput, TWeight, TOutput, TAccum, OutputStage>::indirect(
+ reinterpret_cast<const StratType *>(this->m_strat.get()),
+ ws, this->get_output_stage(), parameters, m_bias, output_channel_end - output_channel_start
+ );
+ }
+
+ void compute_row_padded_tile_row(
+ const DepthwiseArgs &args,
+ const unsigned int output_i, unsigned int output_j, unsigned int n_tile_cols,
+ const unsigned int output_channel_start, const unsigned int output_channel_end,
+ const TensorSpec<const TInput *> &input,
+ const TensorSpec<TOutput *> &output,
+ const void *parameters,
+ void *working_space
+ ) const override
+ {
+ using Invoker = depthwise_depthfirst::Invoke<TInput, TWeight, TOutput, TAccum, OutputStage>;
+ auto ws = reinterpret_cast<WorkingSpace *>(working_space);
+ const auto strat = reinterpret_cast<const StratType *>(this->m_strat.get());
+ const auto os = this->get_output_stage();
+
+ // Compute top and bottom padding; hence fill in the initial pointer arrays.
+ const int ii = static_cast<int>(output_i * args.stride_rows) - args.padding.top;
+ const auto input_pad_top = static_cast<unsigned int>(ii < 0 ? -ii : 0);
+
+ const auto input_i = static_cast<unsigned int>(ii < 0 ? 0 : ii);
+ auto input_j = output_j * args.stride_cols - args.padding.left;
+
+ // Valid input rows is the smallest of the input rows that aren't padding for this tile, and the number of rows
+ // available.
+ const auto valid_input_rows = std::min(strat->get_input_rows() - input_pad_top, args.input_rows - input_i);
+ const auto valid_output_rows = std::min(strat->get_output_rows(), args.output_rows - output_i);
+
+ const auto input_point_stride = input.ld_col * this->m_strat->get_output_cols() * args.stride_cols;
+ const auto output_point_stride = output.ld_col * this->m_strat->get_output_cols();
+
+ Tile<TInput> multiplied_input;
+ this->initialise_inptr_array(args, output_channel_start, output_channel_end, input,
+ ws->inptr_array, ws->input_buffer, ws->intermediate_buffer,
+ input_i, input_j, input_pad_top, 0, multiplied_input);
+
+ fill_pointer_array(
+ ws->outptr_array, this->m_strat->get_output_rows(), this->m_strat->get_output_cols(),
+ output.base + output_i*output.ld_row + output_j*output.ld_col + output_channel_start,
+ output.ld_row, output.ld_col,
+ ws->output_buffer,
+ 0, args.output_rows - output_i, // Top padding, # valid rows
+ 0, args.output_cols - output_j // Left padding, # valid columns
+ );
+
+ for (; n_tile_cols; n_tile_cols--)
+ {
+ // Execute the kernel
+ Invoker::indirect(
+ strat, ws, os, parameters, m_bias, output_channel_end - output_channel_start
+ );
+
+ // Update all unpadded pointers
+ if (this->uses_intermediate_array()) {
+ input_j += input_point_stride / input.ld_col;
+ multiplied_input.load_from(input.base,
+ input.ld_row, input.ld_col,
+ args.input_rows, args.input_cols,
+ input_i, input_j, args.channel_multiplier);
+ } else {
+ {
+ auto ptr = ws->inptr_array + strat->get_input_cols() * input_pad_top;
+ for (auto n = input_pad_top; n < (valid_input_rows + input_pad_top); n++)
+ {
+ for (auto m = 0u; m < strat->get_input_cols(); m++)
+ {
+ *(ptr++) += input_point_stride;
+ }
+ }
+ }
+ }
+
+ {
+ auto ptr = ws->outptr_array;
+ for (auto n = 0u; n < valid_output_rows * strat->get_output_cols(); n++)
+ {
+ *(ptr++) += output_point_stride;
+ }
+ }
+ }
+ }
+
+ void compute_tiles_unpadded(
+ const DepthwiseArgs &args,
+ unsigned int output_i, const unsigned int output_j,
+ unsigned int n_tile_rows, unsigned int n_tile_cols,
+ unsigned int output_channel_start, unsigned int output_channel_end,
+ const TensorSpec<const TInput *> &input,
+ const TensorSpec<TOutput *> &output,
+ const void *parameters,
+ void *working_space_raw
+ ) const override
+ {
+ using Invoker = depthwise_depthfirst::Invoke<TInput, TWeight, TOutput, TAccum, OutputStage>;
+ auto ws = reinterpret_cast<WorkingSpace *>(working_space_raw);
+ const auto strat = reinterpret_cast<const StratType *>(this->m_strat.get());
+ const auto os = this->get_output_stage();
+
+ if (Invoker::supports_direct_kernel)
+ {
+ PaddingValues tile_padding = {
+ args.kernel_cols / 2,
+ args.kernel_rows / 2,
+ args.kernel_cols / 2,
+ args.kernel_rows / 2
+ };
+
+ // If the direct kernel is supported, then use it.
+ // Compute the base pointers we'll use in the tile.
+ auto outptr = output.base + output_channel_start + output_i * output.ld_row + output_j * output.ld_col;
+ const int start_input_i = output_i * args.stride_rows - args.padding.top;
+ const int start_input_j = output_j * args.stride_cols - args.padding.left;
+ auto inptr = input.base + output_channel_start + start_input_i * input.ld_row + start_input_j * input.ld_col;
+
+ auto ld_row = input.ld_row;
+ auto ld_col = input.ld_col;
+
+ const auto tile_rows = this->m_strat->get_output_rows() * args.stride_rows * n_tile_rows + tile_padding.top + tile_padding.bottom;
+ const auto tile_cols = this->m_strat->get_output_cols() * args.stride_cols * n_tile_cols + tile_padding.left + tile_padding.right;
+ const auto tile_channels = output_channel_end - output_channel_start;
+
+ Tile<TInput> multiplied_input;
+ if (this->uses_intermediate_array()) {
+ multiplied_input = Tile<TInput>(ws->intermediate_buffer, tile_rows, tile_cols, tile_channels);
+ multiplied_input.load_from(input.base,
+ input.ld_row, input.ld_col,
+ args.input_rows, args.input_cols,
+ start_input_i, start_input_j, args.channel_multiplier);
+
+ ld_row = tile_cols * tile_channels;
+ ld_col = tile_channels;
+ inptr = multiplied_input.array;
+ }
+
+ // Execute the kernel
+ Invoker::direct(
+ strat, ws, os,
+ n_tile_rows, n_tile_cols,
+ inptr, ld_row, ld_col,
+ outptr, output.ld_row, output.ld_col,
+ parameters, output_channel_end - output_channel_start
+ );
+ }
+ else
+ {
+ // Otherwise, we repeatedly call the padded kernel but use our knowledge
+ // of the tensor structure to avoid recomputing the pointer array.
+
+ const auto n_input_pointers = this->m_strat->get_input_rows() * this->m_strat->get_input_cols();
+ const auto input_point_stride = input.ld_col * this->m_strat->get_output_cols() * args.stride_cols;
+ const auto n_output_pointers = this->m_strat->get_output_rows() * this->m_strat->get_output_cols();
+ const auto output_point_stride = output.ld_col * this->m_strat->get_output_cols();
+
+ // For each tile row, initialise the input and output pointer arrays. For
+ // each subsequent tile we simply update the pointers.
+ for (unsigned int tile_i = 0; tile_i < n_tile_rows; tile_i++)
+ {
+ const int input_i = static_cast<int>(output_i * args.stride_rows) - args.padding.top;
+ int input_j = static_cast<int>(output_j * args.stride_cols) - args.padding.left;
+
+ Tile<TInput> multiplied_input;
+ this->initialise_inptr_array(args, output_channel_start, output_channel_end, input,
+ ws->inptr_array, ws->input_buffer, ws->intermediate_buffer,
+ input_i, input_j, 0, 0, multiplied_input);
+
+ // Compute the output pointer array
+ fill_pointer_array(
+ ws->outptr_array, this->m_strat->get_output_rows(), this->m_strat->get_output_cols(),
+ output.base + output_i*output.ld_row + output_j*output.ld_col + output_channel_start,
+ output.ld_row, output.ld_col,
+ ws->output_buffer,
+ 0, args.output_rows,
+ 0, args.output_cols
+ );
+
+ for (unsigned int tile_j = 0; tile_j < n_tile_cols; tile_j++)
+ {
+ // Invoke the indirect kernel for this tile
+ depthwise_depthfirst::Invoke<TInput, TWeight, TOutput, TAccum, OutputStage>::indirect(
+ strat, ws, os, parameters, m_bias, output_channel_end - output_channel_start
+ );
+
+ // Progress the pointers
+ if (this->uses_intermediate_array()) {
+ input_j += input_point_stride / input.ld_col;
+ multiplied_input.load_from(input.base,
+ input.ld_row, input.ld_col,
+ args.input_rows, args.input_cols, input_i, input_j, args.channel_multiplier);
+ } else {
+ for (auto i = 0u; i < n_input_pointers; i++)
+ {
+ ws->inptr_array[i] += input_point_stride;
+ }
+ }
+
+ for (auto i = 0u; i < n_output_pointers; i++)
+ {
+ ws->outptr_array[i] += output_point_stride;
+ }
+ }
+
+ output_i += this->m_strat->get_output_rows();
+ }
+ }
+ }
+};
+
+} // namespace depthwise
+} // namespace arm_conv