blob: 18e124b83e818f7ca729139f208a470f676434b0 [file] [log] [blame]
/*
* Copyright (c) 2017-2021 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 "arm_gemm.hpp"
#include <cstddef>
#include <limits>
#include <tuple>
// Macro for unreachable code (e.g. impossible default cases on switch)
#define UNREACHABLE(why) __builtin_unreachable()
// Paranoid option for the above with assert
// #define UNREACHABLE(why) assert(0 && why)
namespace arm_gemm {
template<typename T>
std::string get_type_name() {
#ifdef __GNUC__
std::string s = __PRETTY_FUNCTION__;
auto start = s.find("cls_");
if (start==std::string::npos) {
return "(unknown)";
}
for(size_t x = start+4; x<s.size(); x++) {
if (s[x] == ';' || s[x] == ']') {
return s.substr(start+4, x-(start+4));
}
}
return "(unknown)";
#else
return "(unsupported)";
#endif
}
template<typename T>
inline T iceildiv(const T a, const T b) {
return (a + b - 1) / b;
}
template <typename T>
inline T roundup(const T a, const T b) {
T rem = a % b;
if (rem) {
return a + b - rem;
} else {
return a;
}
}
enum class VLType {
None,
SVE,
};
template<typename T>
struct IndirectOutputArg {
struct {
T *base;
size_t stride;
} direct = {};
struct {
T * const *ptr;
size_t offset;
} indirect = {};
bool is_indirect;
// Direct
IndirectOutputArg(T *base, size_t stride) : is_indirect(false) {
direct.base = base;
direct.stride = stride;
}
// Indirect
IndirectOutputArg(T * const * ptr, size_t offset) : is_indirect(true) {
indirect.ptr = ptr;
indirect.offset = offset;
}
IndirectOutputArg() : is_indirect(false) {
direct.base = nullptr;
direct.stride = 0;
}
};
// Check that the provided Requantize32 doesn't have a left shift.
inline bool quant_no_left_shift(const Requantize32 &qp) {
if (qp.per_channel_requant) {
return (qp.per_channel_left_shifts == nullptr);
} else {
return (qp.per_layer_left_shift == 0);
}
}
// Check that the provided Requantize32 is compatible with the "symmetric" hybrid kernels. These don't include row
// sums, so the 'b_offset' has to be zero.
inline bool quant_hybrid_symmetric(const Requantize32 &qp) {
return quant_no_left_shift(qp) && qp.b_offset == 0;
}
// Check that the provided Requantize32 is compatible with the "asymmetric" hybrid kernels. These don't support per
// channel quantization. Technically b_offset==0 cases would work, but it is a waste to sum and then multiply by 0...
inline bool quant_hybrid_asymmetric(const Requantize32 &qp) {
return quant_no_left_shift(qp) /* && qp.b_offset != 0 */ && qp.per_channel_requant==false;
}
template<typename T>
struct IndirectInputArg {
struct {
const T *base;
size_t stride;
} direct = {};
struct {
const T * const * const * ptr;
unsigned int start_row;
unsigned int start_col;
} indirect = {};
bool is_indirect;
// Direct
IndirectInputArg(const T *base, size_t stride) : is_indirect(false) {
direct.base = base;
direct.stride = stride;
}
// Indirect
IndirectInputArg(const T * const * const *ptr, unsigned int start_row, unsigned int start_col) : is_indirect(true) {
indirect.ptr = ptr;
indirect.start_row = start_row;
indirect.start_col = start_col;
}
IndirectInputArg() : is_indirect(false) {
direct.base = nullptr;
direct.stride = 0;
}
};
namespace utils {
// get_vector_length(): Returns SVE vector length for type "T".
//
// It is required that this can be compiled by a compiler in non-SVE mode, but it must be prevented from running (at
// runtime) if SVE is not enabled. Typically this is used by switchyard/driver code which is built in normal mode
// which then calls SVE kernels (compiled accordingly) iff SVE is detected at runtime.
template <typename T>
inline unsigned long get_vector_length() {
#if defined(__aarch64__)
uint64_t vl;
__asm __volatile (
".inst 0x0420e3e0\n" // CNTB X0, ALL, MUL #1
"mov %0, X0\n"
: "=r" (vl)
:
: "x0"
);
return vl / sizeof(T);
#else // !defined(__aarch64__)
return 16 / sizeof(T);
#endif // defined(__aarch64__)
}
// get_vector_length(VLType): Returns vector length for type "T".
//
// This has the same requirements and constraints as the SVE-only form above, so we call into that code for SVE.
template <typename T>
inline unsigned long get_vector_length(VLType vl_type) {
switch (vl_type) {
case VLType::SVE:
return get_vector_length<T>();
default:
return 16 / sizeof(T);
}
}
// get_default_activation_values(): Returns the default values for activation min and max for integer activation.
template <typename T>
inline std::tuple<T, T> get_default_activation_values()
{
const T min = static_cast<T>(std::numeric_limits<T>::min());
const T max = static_cast<T>(std::numeric_limits<T>::max());
return std::make_tuple(min, max);
}
// get_default_activation_values(): Returns the default values for activation min and max for float activation.
template <>
inline std::tuple<float, float> get_default_activation_values()
{
const float min = static_cast<float>(-std::numeric_limits<float>::infinity());
const float max = static_cast<float>(std::numeric_limits<float>::infinity());
return std::make_tuple(min, max);
}
#if defined(__ARM_FP16_ARGS)
// get_default_activation_values(): Returns the default values for activation min and max for __fp16 activation.
template <>
inline std::tuple<__fp16, __fp16> get_default_activation_values()
{
const __fp16 min = static_cast<__fp16>(-std::numeric_limits<float>::infinity());
const __fp16 max = static_cast<__fp16>(std::numeric_limits<float>::infinity());
return std::make_tuple(min, max);
}
#endif // defined(__ARM_FP16_ARGS)
} // utils namespace
} // arm_gemm namespace
using namespace arm_gemm::utils;