// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef GEMMLOWP_ALLOW_SLOW_SCALAR_FALLBACK #define GEMMLOWP_ALLOW_SLOW_SCALAR_FALLBACK #endif #include "eight_bit_int_gemm.h" #include <memory> // gemmlowp symbols should have hidden visibility. // currently this is ensured in the build system by // passing -finlines-visibility-hidden. TODO: it would be // safer to hardcode it here with some #pragma's. #include "../public/gemmlowp.h" // Define GEMMLOWP_USE_META_FASTPATH in order to use the fastpath ARM/NEON // code. This code path consists of a number of meta-programmed, automatically // generated GEMM kernels that are suitable for some sizes of input matrices. // Due to the fact that the generated code relies heavily on loop unrolling, // inling and currying of runtime parameters the size of the generated binary // is quite significant (approx. 200kb) which might be prohibitive in // low-memory situations. #if defined(GEMMLOWP_USE_META_FASTPATH) && defined(GEMMLOWP_NEON) #include "../meta/legacy_multi_thread_gemm.h" #else #if defined(GEMMLOWP_USE_META_FASTPATH) #warning "META fast path turned on without NEON!" #endif #endif namespace gemmlowp { namespace eight_bit_int_gemm { namespace { // To be used as template parameter for GlobalLock. // GlobalLock<EightBitIntGemmLockId> is the global lock // on EightBitIntGemm entry points, protecting // EightBitIntGemm's global state. struct EightBitIntGemmLockId; // Global state: consists of one global GemmContext instance. GemmContext* global_context; GemmContext* GetOrCreateGlobalContext() { if (!global_context) { global_context = new GemmContext; } return global_context; } void DestroyGlobalContext() { delete global_context; global_context = nullptr; } template <bool transpose_a, bool transpose_b, bool transpose_c> void EightBitIntGemmImpl(GemmContext* context, int m, int n, int k, const std::uint8_t* a, std::int32_t a_offset, int lda, const std::uint8_t* b, std::int32_t b_offset, int ldb, std::uint8_t* c, std::int32_t c_offset, std::int32_t c_mult_int, std::int32_t c_shift, int ldc, BitDepthSetting bit_depth) { const int lhs_offset = a_offset; const int rhs_offset = b_offset; const int result_offset = c_offset; const int result_mult_int = c_mult_int; const int result_shift = c_shift; static const MapOrder ResultOrder = transpose_c ? MapOrder::RowMajor : MapOrder::ColMajor; static const MapOrder LhsOrder = transpose_a ? MapOrder::RowMajor : MapOrder::ColMajor; static const MapOrder RhsOrder = transpose_b ? MapOrder::RowMajor : MapOrder::ColMajor; MatrixMap<const std::uint8_t, LhsOrder> lhs(a, m, k, lda); MatrixMap<const std::uint8_t, RhsOrder> rhs(b, k, n, ldb); MatrixMap<std::uint8_t, ResultOrder> result(c, m, n, ldc); switch (bit_depth) { #define GEMMLOWP_HANDLE_BIT_DEPTH(BIT_DEPTH_SETTING, BIT_DEPTH_PARAMS) \ case BitDepthSetting::BIT_DEPTH_SETTING: \ Gemm<std::uint8_t, BIT_DEPTH_PARAMS>( \ context, lhs, rhs, &result, lhs_offset, rhs_offset, result_offset, \ result_mult_int, result_shift); \ return; GEMMLOWP_HANDLE_BIT_DEPTH(A8B8, DefaultL8R8BitDepthParams) GEMMLOWP_HANDLE_BIT_DEPTH(A5B7, DefaultL7R5BitDepthParams) default: abort(); #undef GEMMLOWP_HANDLE_BIT_DEPTH } } template <bool transpose_a, bool transpose_b, bool transpose_c> void EightBitIntGemmInt32Impl(GemmContext* context, int m, int n, int k, const std::uint8_t* a, std::int32_t a_offset, int lda, const std::uint8_t* b, std::int32_t b_offset, int ldb, std::int32_t* c, int ldc, BitDepthSetting bit_depth) { const int lhs_offset = a_offset; const int rhs_offset = b_offset; static const MapOrder ResultOrder = transpose_c ? MapOrder::RowMajor : MapOrder::ColMajor; static const MapOrder LhsOrder = transpose_a ? MapOrder::RowMajor : MapOrder::ColMajor; static const MapOrder RhsOrder = transpose_b ? MapOrder::RowMajor : MapOrder::ColMajor; MatrixMap<const std::uint8_t, LhsOrder> lhs(a, m, k, lda); MatrixMap<const std::uint8_t, RhsOrder> rhs(b, k, n, ldb); MatrixMap<std::int32_t, ResultOrder> result(c, m, n, ldc); auto empty_pipeline = std::make_tuple(); switch (bit_depth) { #define GEMMLOWP_HANDLE_BIT_DEPTH_INT32(BIT_DEPTH_SETTING, BIT_DEPTH_PARAMS) \ case BitDepthSetting::BIT_DEPTH_SETTING: \ GemmWithOutputPipeline<std::uint8_t, std::int32_t, BIT_DEPTH_PARAMS>( \ context, lhs, rhs, &result, lhs_offset, rhs_offset, empty_pipeline); \ return; GEMMLOWP_HANDLE_BIT_DEPTH_INT32(A8B8, DefaultL8R8BitDepthParams) GEMMLOWP_HANDLE_BIT_DEPTH_INT32(A5B7, DefaultL7R5BitDepthParams) default: abort(); #undef GEMMLOWP_HANDLE_BIT_DEPTH_INT32 } } class Scratch { public: Scratch() : buffer_(), buffer_32_(nullptr), size_(0) {} void AssureSize(std::int32_t required_size) { if (size_ >= required_size) { return; } buffer_.reset(new std::uint8_t[required_size + 32]); buffer_32_ = buffer_.get() + ((32 - (reinterpret_cast<uintptr_t>(buffer_.get()) % 32)) % 32); assert((reinterpret_cast<uintptr_t>(buffer_32_) % 32) == 0); size_ = required_size; } void Clear() { buffer_.reset(nullptr); buffer_32_ = nullptr; size_ = 0; } std::uint8_t* buffer() { return buffer_32_; } private: std::unique_ptr<std::uint8_t[]> buffer_; std::uint8_t* buffer_32_; std::int32_t size_; }; Scratch* global_scratch = nullptr; Scratch* GetOrCreateGlobalScratch() { if (global_scratch == nullptr) { global_scratch = new Scratch(); } return global_scratch; } void DestroyGlobalScratch() { delete global_scratch; global_scratch = nullptr; } #if defined(GEMMLOWP_USE_META_FASTPATH) && defined(GEMMLOWP_NEON) bool IsRowMajorOrVector(bool transpose, int stride, int rows, int cols) { // Is it row major and nicely packed? if (transpose && stride == cols) { return true; } // Is it a one row vector? (a vector is both row and column major) if (rows == 1) { return true; } return false; } bool IsColumnMajorOrVector(bool transpose, int stride, int rows, int cols) { // Is it column major and nicely packed? if (!transpose && stride == rows) { return true; } // Is it a one column vector? (a vector is both row and column major) if (cols == 1) { return true; } return false; } bool CanHandleMetaFastpath(bool transpose_a, bool transpose_b, bool transpose_c, int m, int n, int k, int lda, int ldb, int ldc, BitDepthSetting depth_setting) { // Meta fastpath only supports 8bit x 8bit and k between 8 and 2048. if (depth_setting != BitDepthSetting::A8B8 || k < 8 || k > 2048) { return false; } // The first operand needs to be a row major matrix or a vector. if (!IsRowMajorOrVector(transpose_a, lda, m, k)) { return false; } // The second operand needs to be a column major matrix or a vector. if (!IsColumnMajorOrVector(transpose_b, ldb, k, n)) { return false; } // The result can either be a row major matrix, a column major matrix or // a vector. if (IsRowMajorOrVector(transpose_c, ldc, m, n)) { return true; } if (IsColumnMajorOrVector(transpose_c, ldc, m, n)) { return true; } return false; } // Assure enough scratch memory is allocated and run the fast path gemm. void MetaGemmQuantized8Bit(GemmContext* context, const std::uint8_t* lhs, const std::uint8_t* rhs, int m, int n, int k, std::int32_t lhs_offset, std::int32_t rhs_offset, std::int32_t sum_offset, std::int32_t multiplicative_offset, std::int32_t shift, bool result_transpose, std::int32_t result_stride, std::uint8_t* result) { Scratch* scratch = GetOrCreateGlobalScratch(); const std::int32_t max_num_threads = context->max_num_threads(); if (IsRowMajorOrVector(result_transpose, result_stride, m, n)) { scratch->AssureSize(meta::gemm_q8_scratch(m, n, k, max_num_threads)); meta::multi_thread_gemm_q8(context->workers_pool(), max_num_threads, scratch->buffer(), lhs, rhs, m, n, k, lhs_offset, rhs_offset, sum_offset, multiplicative_offset, shift, result); } else { scratch->AssureSize(meta::gemm_q8_scratch(n, m, k, max_num_threads)); meta::multi_thread_gemm_q8(context->workers_pool(), max_num_threads, scratch->buffer(), rhs, lhs, n, m, k, rhs_offset, lhs_offset, sum_offset, multiplicative_offset, shift, result); } } // Assure enough scratch memory is allocated and run the 8bit to float fast // path gemm. void MetaGemmFloat(GemmContext* context, const std::uint8_t* lhs, const std::uint8_t* rhs, int m, int n, int k, std::int32_t lhs_offset, std::int32_t rhs_offset, float result_offset, bool result_transpose, std::int32_t result_stride, float* result) { Scratch* scratch = GetOrCreateGlobalScratch(); const std::int32_t max_num_threads = context->max_num_threads(); if (IsRowMajorOrVector(result_transpose, result_stride, m, n)) { scratch->AssureSize(meta::gemm_f_scratch(m, n, k, max_num_threads)); meta::multi_thread_gemm_f(context->workers_pool(), max_num_threads, scratch->buffer(), lhs, rhs, m, n, k, lhs_offset, rhs_offset, result_offset, result); } else { scratch->AssureSize(meta::gemm_f_scratch(n, m, k, max_num_threads)); meta::multi_thread_gemm_f(context->workers_pool(), max_num_threads, scratch->buffer(), rhs, lhs, n, m, k, rhs_offset, lhs_offset, result_offset, result); } } #endif } // end anonymous namespace // Public interface entry points void EightBitIntGemm(bool transpose_a, bool transpose_b, bool transpose_c, int m, int n, int k, const std::uint8_t* a, std::int32_t a_offset, int lda, const std::uint8_t* b, std::int32_t b_offset, int ldb, std::uint8_t* c, std::int32_t c_offset, std::int32_t c_mult_int, std::int32_t c_shift, int ldc, BitDepthSetting bit_depth) { ScopedLock sl(GlobalMutexes::EightBitIntGemm()); GemmContext* context = GetOrCreateGlobalContext(); #if defined(GEMMLOWP_USE_META_FASTPATH) && defined(GEMMLOWP_NEON) if (CanHandleMetaFastpath(transpose_a, transpose_b, transpose_c, m, n, k, lda, ldb, ldc, bit_depth)) { MetaGemmQuantized8Bit(context, a, b, m, n, k, a_offset, b_offset, c_offset, c_mult_int, c_shift, transpose_c, ldc, c); return; } #endif #define GEMMLOWP_HANDLE_CASE(ta, tb, tc) \ if (transpose_a == ta && transpose_b == tb && transpose_c == tc) { \ EightBitIntGemmImpl<ta, tb, tc>(context, m, n, k, a, a_offset, lda, b, \ b_offset, ldb, c, c_offset, c_mult_int, \ c_shift, ldc, bit_depth); \ } GEMMLOWP_HANDLE_CASE(false, false, false) GEMMLOWP_HANDLE_CASE(false, false, true) GEMMLOWP_HANDLE_CASE(false, true, false) GEMMLOWP_HANDLE_CASE(false, true, true) GEMMLOWP_HANDLE_CASE(true, false, false) GEMMLOWP_HANDLE_CASE(true, false, true) GEMMLOWP_HANDLE_CASE(true, true, false) GEMMLOWP_HANDLE_CASE(true, true, true) #undef GEMMLOWP_HANDLE_CASE } void EightBitIntGemm(bool transpose_a, bool transpose_b, bool transpose_c, int m, int n, int k, const std::uint8_t* a, std::int32_t a_offset, std::int32_t lda, const std::uint8_t* b, std::int32_t b_offset, std::int32_t ldb, float* c, float c_offset, std::int32_t ldc, BitDepthSetting bit_depth) { ScopedLock sl(GlobalMutexes::EightBitIntGemm()); GemmContext* context = GetOrCreateGlobalContext(); #if defined(GEMMLOWP_USE_META_FASTPATH) && defined(GEMMLOWP_NEON) if (CanHandleMetaFastpath(transpose_a, transpose_b, transpose_c, m, n, k, lda, ldb, ldc, bit_depth)) { MetaGemmFloat(context, a, b, m, n, k, a_offset, b_offset, c_offset, transpose_c, ldc, c); return; } #endif // TODO(maciekc): implement a float output stage, get rid of scratch memory. Scratch* scratch = GetOrCreateGlobalScratch(); if (transpose_c) { scratch->AssureSize(m * ldc * sizeof(std::int32_t)); } else { scratch->AssureSize(n * ldc * sizeof(std::int32_t)); } std::int32_t* temp_c = reinterpret_cast<std::int32_t*>(scratch->buffer()); #define GEMMLOWP_HANDLE_INT32_CASE(ta, tb, tc) \ if (transpose_a == ta && transpose_b == tb && transpose_c == tc) { \ EightBitIntGemmInt32Impl<ta, tb, tc>(context, m, n, k, a, a_offset, lda, \ b, b_offset, ldb, temp_c, ldc, \ bit_depth); \ } GEMMLOWP_HANDLE_INT32_CASE(false, false, false) GEMMLOWP_HANDLE_INT32_CASE(false, false, true) GEMMLOWP_HANDLE_INT32_CASE(false, true, false) GEMMLOWP_HANDLE_INT32_CASE(false, true, true) GEMMLOWP_HANDLE_INT32_CASE(true, false, false) GEMMLOWP_HANDLE_INT32_CASE(true, false, true) GEMMLOWP_HANDLE_INT32_CASE(true, true, false) GEMMLOWP_HANDLE_INT32_CASE(true, true, true) #undef GEMMLOWP_HANDLE_INT32_CASE if (transpose_c) { // Row major. for (int i = 0; i < m; ++i) { float* dest_row = c + i * ldc; std::int32_t* src_row = temp_c + i * ldc; for (int j = 0; j < n; ++j) { dest_row[j] = static_cast<float>(src_row[j]) * c_offset; } } } else { // Column major. for (int i = 0; i < n; ++i) { float* dest_column = c + i * ldc; std::int32_t* src_column = temp_c + i * ldc; for (int j = 0; j < m; ++j) { dest_column[j] = static_cast<float>(src_column[j]) * c_offset; } } } } void SetMaxNumThreads(int n) { ScopedLock sl(GlobalMutexes::EightBitIntGemm()); GemmContext* context = GetOrCreateGlobalContext(); context->set_max_num_threads(n); } void FreePersistentResources() { ScopedLock sl(GlobalMutexes::EightBitIntGemm()); DestroyGlobalContext(); DestroyGlobalScratch(); } } // namespace eight_bit_int_gemm } // namespace gemmlowp