mirror of
https://gitlab.com/libeigen/eigen.git
synced 2026-04-10 11:34:33 +08:00
1186 lines
56 KiB
C++
1186 lines
56 KiB
C++
// This file is part of Eigen, a lightweight C++ template library
|
|
// for linear algebra.
|
|
//
|
|
// Copyright (C) 2022 Intel Corporation
|
|
//
|
|
// This Source Code Form is subject to the terms of the Mozilla
|
|
// Public License v. 2.0. If a copy of the MPL was not distributed
|
|
// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
#ifndef EIGEN_CORE_ARCH_AVX512_TRSM_KERNEL_H
|
|
#define EIGEN_CORE_ARCH_AVX512_TRSM_KERNEL_H
|
|
|
|
#include "../../InternalHeaderCheck.h"
|
|
|
|
#if !defined(EIGEN_USE_AVX512_TRSM_KERNELS)
|
|
#define EIGEN_USE_AVX512_TRSM_KERNELS 1
|
|
#endif
|
|
|
|
#if EIGEN_USE_AVX512_TRSM_KERNELS
|
|
#if !defined(EIGEN_USE_AVX512_TRSM_R_KERNELS)
|
|
#define EIGEN_USE_AVX512_TRSM_R_KERNELS 1
|
|
#endif
|
|
#if !defined(EIGEN_USE_AVX512_TRSM_L_KERNELS)
|
|
#define EIGEN_USE_AVX512_TRSM_L_KERNELS 1
|
|
#endif
|
|
#else // EIGEN_USE_AVX512_TRSM_KERNELS == 0
|
|
#define EIGEN_USE_AVX512_TRSM_R_KERNELS 0
|
|
#define EIGEN_USE_AVX512_TRSM_L_KERNELS 0
|
|
#endif
|
|
|
|
// Need this for some std::min calls.
|
|
#ifdef min
|
|
#undef min
|
|
#endif
|
|
|
|
namespace Eigen {
|
|
namespace internal {
|
|
|
|
#define EIGEN_AVX_MAX_NUM_ACC (int64_t(24))
|
|
#define EIGEN_AVX_MAX_NUM_ROW (int64_t(8)) // Denoted L in code.
|
|
#define EIGEN_AVX_MAX_K_UNROL (int64_t(4))
|
|
#define EIGEN_AVX_B_LOAD_SETS (int64_t(2))
|
|
#define EIGEN_AVX_MAX_A_BCAST (int64_t(2))
|
|
typedef Packet16f vecFullFloat;
|
|
typedef Packet8d vecFullDouble;
|
|
typedef Packet8f vecHalfFloat;
|
|
typedef Packet4d vecHalfDouble;
|
|
|
|
// Compile-time unrolls are implemented here.
|
|
// Note: this depends on macros and typedefs above.
|
|
#include "TrsmUnrolls.inc"
|
|
|
|
#if (EIGEN_USE_AVX512_TRSM_KERNELS) && (EIGEN_COMP_CLANG != 0)
|
|
/**
|
|
* For smaller problem sizes, and certain compilers, using the optimized kernels trsmKernelL/R directly
|
|
* is faster than the packed versions in TriangularSolverMatrix.h.
|
|
*
|
|
* The current heuristic is based on having having all arrays used in the largest gemm-update
|
|
* in triSolve fit in roughly L2Cap (percentage) of the L2 cache. These cutoffs are a bit conservative and could be
|
|
* larger for some trsm cases.
|
|
* The formula:
|
|
*
|
|
* (L*M + M*N + L*N)*sizeof(Scalar) < L2Cache*L2Cap
|
|
*
|
|
* L = number of rows to solve at a time
|
|
* N = number of rhs
|
|
* M = Dimension of triangular matrix
|
|
*
|
|
*/
|
|
#if !defined(EIGEN_ENABLE_AVX512_NOCOPY_TRSM_CUTOFFS)
|
|
#define EIGEN_ENABLE_AVX512_NOCOPY_TRSM_CUTOFFS 1
|
|
#endif
|
|
|
|
#if EIGEN_ENABLE_AVX512_NOCOPY_TRSM_CUTOFFS
|
|
|
|
#if EIGEN_USE_AVX512_TRSM_R_KERNELS
|
|
#if !defined(EIGEN_ENABLE_AVX512_NOCOPY_TRSM_R_CUTOFFS)
|
|
#define EIGEN_ENABLE_AVX512_NOCOPY_TRSM_R_CUTOFFS 1
|
|
#endif // !defined(EIGEN_ENABLE_AVX512_NOCOPY_TRSM_R_CUTOFFS)
|
|
#endif
|
|
|
|
#if EIGEN_USE_AVX512_TRSM_L_KERNELS
|
|
#if !defined(EIGEN_ENABLE_AVX512_NOCOPY_TRSM_L_CUTOFFS)
|
|
#define EIGEN_ENABLE_AVX512_NOCOPY_TRSM_L_CUTOFFS 1
|
|
#endif
|
|
#endif // EIGEN_USE_AVX512_TRSM_L_KERNELS
|
|
|
|
#else // EIGEN_ENABLE_AVX512_NOCOPY_TRSM_CUTOFFS == 0
|
|
#define EIGEN_ENABLE_AVX512_NOCOPY_TRSM_R_CUTOFFS 0
|
|
#define EIGEN_ENABLE_AVX512_NOCOPY_TRSM_L_CUTOFFS 0
|
|
#endif // EIGEN_ENABLE_AVX512_NOCOPY_TRSM_CUTOFFS
|
|
|
|
template <typename Scalar>
|
|
int64_t avx512_trsm_cutoff(int64_t L2Size, int64_t N, double L2Cap) {
|
|
const int64_t U3 = 3 * packet_traits<Scalar>::size;
|
|
const int64_t MaxNb = 5 * U3;
|
|
int64_t Nb = std::min(MaxNb, N);
|
|
double cutoff_d =
|
|
(((L2Size * L2Cap) / (sizeof(Scalar))) - (EIGEN_AVX_MAX_NUM_ROW)*Nb) / ((EIGEN_AVX_MAX_NUM_ROW) + Nb);
|
|
int64_t cutoff_l = static_cast<int64_t>(cutoff_d);
|
|
return (cutoff_l / EIGEN_AVX_MAX_NUM_ROW) * EIGEN_AVX_MAX_NUM_ROW;
|
|
}
|
|
#else // !(EIGEN_USE_AVX512_TRSM_KERNELS) || !(EIGEN_COMP_CLANG != 0)
|
|
#define EIGEN_ENABLE_AVX512_NOCOPY_TRSM_CUTOFFS 0
|
|
#define EIGEN_ENABLE_AVX512_NOCOPY_TRSM_R_CUTOFFS 0
|
|
#define EIGEN_ENABLE_AVX512_NOCOPY_TRSM_L_CUTOFFS 0
|
|
#endif
|
|
|
|
/**
|
|
* Used by gemmKernel for the case A/B row-major and C col-major.
|
|
*/
|
|
template <typename Scalar, typename vec, int64_t unrollM, int64_t unrollN, bool remM, bool remN>
|
|
static EIGEN_ALWAYS_INLINE void transStoreC(PacketBlock<vec, EIGEN_ARCH_DEFAULT_NUMBER_OF_REGISTERS> &zmm,
|
|
Scalar *C_arr, int64_t LDC, int64_t remM_ = 0, int64_t remN_ = 0) {
|
|
EIGEN_UNUSED_VARIABLE(remN_);
|
|
EIGEN_UNUSED_VARIABLE(remM_);
|
|
using urolls = unrolls::trans<Scalar>;
|
|
|
|
constexpr int64_t U3 = urolls::PacketSize * 3;
|
|
constexpr int64_t U2 = urolls::PacketSize * 2;
|
|
constexpr int64_t U1 = urolls::PacketSize * 1;
|
|
|
|
static_assert(unrollN == U1 || unrollN == U2 || unrollN == U3, "unrollN should be a multiple of PacketSize");
|
|
static_assert(unrollM == EIGEN_AVX_MAX_NUM_ROW, "unrollM should be equal to EIGEN_AVX_MAX_NUM_ROW");
|
|
|
|
urolls::template transpose<unrollN, 0>(zmm);
|
|
EIGEN_IF_CONSTEXPR(unrollN > U2) urolls::template transpose<unrollN, 2>(zmm);
|
|
EIGEN_IF_CONSTEXPR(unrollN > U1) urolls::template transpose<unrollN, 1>(zmm);
|
|
|
|
static_assert((remN && unrollN == U1) || !remN, "When handling N remainder set unrollN=U1");
|
|
EIGEN_IF_CONSTEXPR(!remN) {
|
|
urolls::template storeC<std::min(unrollN, U1), unrollN, 0, remM>(C_arr, LDC, zmm, remM_);
|
|
EIGEN_IF_CONSTEXPR(unrollN > U1) {
|
|
constexpr int64_t unrollN_ = std::min(unrollN - U1, U1);
|
|
urolls::template storeC<unrollN_, unrollN, 1, remM>(C_arr + U1 * LDC, LDC, zmm, remM_);
|
|
}
|
|
EIGEN_IF_CONSTEXPR(unrollN > U2) {
|
|
constexpr int64_t unrollN_ = std::min(unrollN - U2, U1);
|
|
urolls::template storeC<unrollN_, unrollN, 2, remM>(C_arr + U2 * LDC, LDC, zmm, remM_);
|
|
}
|
|
}
|
|
else {
|
|
EIGEN_IF_CONSTEXPR((std::is_same<Scalar, float>::value)) {
|
|
// Note: without "if constexpr" this section of code will also be
|
|
// parsed by the compiler so each of the storeC will still be instantiated.
|
|
// We use enable_if in aux_storeC to set it to an empty function for
|
|
// these cases.
|
|
if (remN_ == 15)
|
|
urolls::template storeC<15, unrollN, 0, remM>(C_arr, LDC, zmm, remM_);
|
|
else if (remN_ == 14)
|
|
urolls::template storeC<14, unrollN, 0, remM>(C_arr, LDC, zmm, remM_);
|
|
else if (remN_ == 13)
|
|
urolls::template storeC<13, unrollN, 0, remM>(C_arr, LDC, zmm, remM_);
|
|
else if (remN_ == 12)
|
|
urolls::template storeC<12, unrollN, 0, remM>(C_arr, LDC, zmm, remM_);
|
|
else if (remN_ == 11)
|
|
urolls::template storeC<11, unrollN, 0, remM>(C_arr, LDC, zmm, remM_);
|
|
else if (remN_ == 10)
|
|
urolls::template storeC<10, unrollN, 0, remM>(C_arr, LDC, zmm, remM_);
|
|
else if (remN_ == 9)
|
|
urolls::template storeC<9, unrollN, 0, remM>(C_arr, LDC, zmm, remM_);
|
|
else if (remN_ == 8)
|
|
urolls::template storeC<8, unrollN, 0, remM>(C_arr, LDC, zmm, remM_);
|
|
else if (remN_ == 7)
|
|
urolls::template storeC<7, unrollN, 0, remM>(C_arr, LDC, zmm, remM_);
|
|
else if (remN_ == 6)
|
|
urolls::template storeC<6, unrollN, 0, remM>(C_arr, LDC, zmm, remM_);
|
|
else if (remN_ == 5)
|
|
urolls::template storeC<5, unrollN, 0, remM>(C_arr, LDC, zmm, remM_);
|
|
else if (remN_ == 4)
|
|
urolls::template storeC<4, unrollN, 0, remM>(C_arr, LDC, zmm, remM_);
|
|
else if (remN_ == 3)
|
|
urolls::template storeC<3, unrollN, 0, remM>(C_arr, LDC, zmm, remM_);
|
|
else if (remN_ == 2)
|
|
urolls::template storeC<2, unrollN, 0, remM>(C_arr, LDC, zmm, remM_);
|
|
else if (remN_ == 1)
|
|
urolls::template storeC<1, unrollN, 0, remM>(C_arr, LDC, zmm, remM_);
|
|
}
|
|
else {
|
|
if (remN_ == 7)
|
|
urolls::template storeC<7, unrollN, 0, remM>(C_arr, LDC, zmm, remM_);
|
|
else if (remN_ == 6)
|
|
urolls::template storeC<6, unrollN, 0, remM>(C_arr, LDC, zmm, remM_);
|
|
else if (remN_ == 5)
|
|
urolls::template storeC<5, unrollN, 0, remM>(C_arr, LDC, zmm, remM_);
|
|
else if (remN_ == 4)
|
|
urolls::template storeC<4, unrollN, 0, remM>(C_arr, LDC, zmm, remM_);
|
|
else if (remN_ == 3)
|
|
urolls::template storeC<3, unrollN, 0, remM>(C_arr, LDC, zmm, remM_);
|
|
else if (remN_ == 2)
|
|
urolls::template storeC<2, unrollN, 0, remM>(C_arr, LDC, zmm, remM_);
|
|
else if (remN_ == 1)
|
|
urolls::template storeC<1, unrollN, 0, remM>(C_arr, LDC, zmm, remM_);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GEMM like operation for trsm panel updates.
|
|
* Computes: C -= A*B
|
|
* K must be multipe of 4.
|
|
*
|
|
* Unrolls used are {1,2,4,8}x{U1,U2,U3};
|
|
* For good performance we want K to be large with M/N relatively small, but also large enough
|
|
* to use the {8,U3} unroll block.
|
|
*
|
|
* isARowMajor: is A_arr row-major?
|
|
* isCRowMajor: is C_arr row-major? (B_arr is assumed to be row-major).
|
|
* isAdd: C += A*B or C -= A*B (used by trsm)
|
|
* handleKRem: Handle arbitrary K? This is not needed for trsm.
|
|
*/
|
|
template <typename Scalar, bool isARowMajor, bool isCRowMajor, bool isAdd, bool handleKRem>
|
|
void gemmKernel(Scalar *A_arr, Scalar *B_arr, Scalar *C_arr, int64_t M, int64_t N, int64_t K, int64_t LDA, int64_t LDB,
|
|
int64_t LDC) {
|
|
using urolls = unrolls::gemm<Scalar, isAdd>;
|
|
constexpr int64_t U3 = urolls::PacketSize * 3;
|
|
constexpr int64_t U2 = urolls::PacketSize * 2;
|
|
constexpr int64_t U1 = urolls::PacketSize * 1;
|
|
using vec = typename std::conditional<std::is_same<Scalar, float>::value, vecFullFloat, vecFullDouble>::type;
|
|
int64_t N_ = (N / U3) * U3;
|
|
int64_t M_ = (M / EIGEN_AVX_MAX_NUM_ROW) * EIGEN_AVX_MAX_NUM_ROW;
|
|
int64_t K_ = (K / EIGEN_AVX_MAX_K_UNROL) * EIGEN_AVX_MAX_K_UNROL;
|
|
int64_t j = 0;
|
|
for (; j < N_; j += U3) {
|
|
constexpr int64_t EIGEN_AVX_MAX_B_LOAD = EIGEN_AVX_B_LOAD_SETS * 3;
|
|
int64_t i = 0;
|
|
for (; i < M_; i += EIGEN_AVX_MAX_NUM_ROW) {
|
|
Scalar *A_t = &A_arr[idA<isARowMajor>(i, 0, LDA)], *B_t = &B_arr[0 * LDB + j];
|
|
PacketBlock<vec, EIGEN_ARCH_DEFAULT_NUMBER_OF_REGISTERS> zmm;
|
|
urolls::template setzero<3, EIGEN_AVX_MAX_NUM_ROW>(zmm);
|
|
for (int64_t k = 0; k < K_; k += EIGEN_AVX_MAX_K_UNROL) {
|
|
urolls::template microKernel<isARowMajor, 3, EIGEN_AVX_MAX_NUM_ROW, EIGEN_AVX_MAX_K_UNROL, EIGEN_AVX_MAX_B_LOAD,
|
|
EIGEN_AVX_MAX_A_BCAST>(B_t, A_t, LDB, LDA, zmm);
|
|
B_t += EIGEN_AVX_MAX_K_UNROL * LDB;
|
|
EIGEN_IF_CONSTEXPR(isARowMajor) A_t += EIGEN_AVX_MAX_K_UNROL;
|
|
else A_t += EIGEN_AVX_MAX_K_UNROL * LDA;
|
|
}
|
|
EIGEN_IF_CONSTEXPR(handleKRem) {
|
|
for (int64_t k = K_; k < K; k++) {
|
|
urolls::template microKernel<isARowMajor, 3, EIGEN_AVX_MAX_NUM_ROW, 1, EIGEN_AVX_B_LOAD_SETS * 3,
|
|
EIGEN_AVX_MAX_A_BCAST>(B_t, A_t, LDB, LDA, zmm);
|
|
B_t += LDB;
|
|
EIGEN_IF_CONSTEXPR(isARowMajor) A_t++;
|
|
else A_t += LDA;
|
|
}
|
|
}
|
|
EIGEN_IF_CONSTEXPR(isCRowMajor) {
|
|
urolls::template updateC<3, EIGEN_AVX_MAX_NUM_ROW>(&C_arr[i * LDC + j], LDC, zmm);
|
|
urolls::template storeC<3, EIGEN_AVX_MAX_NUM_ROW>(&C_arr[i * LDC + j], LDC, zmm);
|
|
}
|
|
else {
|
|
transStoreC<Scalar, vec, EIGEN_AVX_MAX_NUM_ROW, U3, false, false>(zmm, &C_arr[i + j * LDC], LDC);
|
|
}
|
|
}
|
|
if (M - i >= 4) { // Note: this block assumes EIGEN_AVX_MAX_NUM_ROW = 8. Should be removed otherwise
|
|
Scalar *A_t = &A_arr[idA<isARowMajor>(i, 0, LDA)];
|
|
Scalar *B_t = &B_arr[0 * LDB + j];
|
|
PacketBlock<vec, EIGEN_ARCH_DEFAULT_NUMBER_OF_REGISTERS> zmm;
|
|
urolls::template setzero<3, 4>(zmm);
|
|
for (int64_t k = 0; k < K_; k += EIGEN_AVX_MAX_K_UNROL) {
|
|
urolls::template microKernel<isARowMajor, 3, 4, EIGEN_AVX_MAX_K_UNROL, EIGEN_AVX_B_LOAD_SETS * 3,
|
|
EIGEN_AVX_MAX_A_BCAST>(B_t, A_t, LDB, LDA, zmm);
|
|
B_t += EIGEN_AVX_MAX_K_UNROL * LDB;
|
|
EIGEN_IF_CONSTEXPR(isARowMajor) A_t += EIGEN_AVX_MAX_K_UNROL;
|
|
else A_t += EIGEN_AVX_MAX_K_UNROL * LDA;
|
|
}
|
|
EIGEN_IF_CONSTEXPR(handleKRem) {
|
|
for (int64_t k = K_; k < K; k++) {
|
|
urolls::template microKernel<isARowMajor, 3, 4, 1, EIGEN_AVX_B_LOAD_SETS * 3, EIGEN_AVX_MAX_A_BCAST>(
|
|
B_t, A_t, LDB, LDA, zmm);
|
|
B_t += LDB;
|
|
EIGEN_IF_CONSTEXPR(isARowMajor) A_t++;
|
|
else A_t += LDA;
|
|
}
|
|
}
|
|
EIGEN_IF_CONSTEXPR(isCRowMajor) {
|
|
urolls::template updateC<3, 4>(&C_arr[i * LDC + j], LDC, zmm);
|
|
urolls::template storeC<3, 4>(&C_arr[i * LDC + j], LDC, zmm);
|
|
}
|
|
else {
|
|
transStoreC<Scalar, vec, EIGEN_AVX_MAX_NUM_ROW, U3, true, false>(zmm, &C_arr[i + j * LDC], LDC, 4);
|
|
}
|
|
i += 4;
|
|
}
|
|
if (M - i >= 2) {
|
|
Scalar *A_t = &A_arr[idA<isARowMajor>(i, 0, LDA)];
|
|
Scalar *B_t = &B_arr[0 * LDB + j];
|
|
PacketBlock<vec, EIGEN_ARCH_DEFAULT_NUMBER_OF_REGISTERS> zmm;
|
|
urolls::template setzero<3, 2>(zmm);
|
|
for (int64_t k = 0; k < K_; k += EIGEN_AVX_MAX_K_UNROL) {
|
|
urolls::template microKernel<isARowMajor, 3, 2, EIGEN_AVX_MAX_K_UNROL, EIGEN_AVX_B_LOAD_SETS * 3,
|
|
EIGEN_AVX_MAX_A_BCAST>(B_t, A_t, LDB, LDA, zmm);
|
|
B_t += EIGEN_AVX_MAX_K_UNROL * LDB;
|
|
EIGEN_IF_CONSTEXPR(isARowMajor) A_t += EIGEN_AVX_MAX_K_UNROL;
|
|
else A_t += EIGEN_AVX_MAX_K_UNROL * LDA;
|
|
}
|
|
EIGEN_IF_CONSTEXPR(handleKRem) {
|
|
for (int64_t k = K_; k < K; k++) {
|
|
urolls::template microKernel<isARowMajor, 3, 2, 1, EIGEN_AVX_B_LOAD_SETS * 3, EIGEN_AVX_MAX_A_BCAST>(
|
|
B_t, A_t, LDB, LDA, zmm);
|
|
B_t += LDB;
|
|
EIGEN_IF_CONSTEXPR(isARowMajor) A_t++;
|
|
else A_t += LDA;
|
|
}
|
|
}
|
|
EIGEN_IF_CONSTEXPR(isCRowMajor) {
|
|
urolls::template updateC<3, 2>(&C_arr[i * LDC + j], LDC, zmm);
|
|
urolls::template storeC<3, 2>(&C_arr[i * LDC + j], LDC, zmm);
|
|
}
|
|
else {
|
|
transStoreC<Scalar, vec, EIGEN_AVX_MAX_NUM_ROW, U3, true, false>(zmm, &C_arr[i + j * LDC], LDC, 2);
|
|
}
|
|
i += 2;
|
|
}
|
|
if (M - i > 0) {
|
|
Scalar *A_t = &A_arr[idA<isARowMajor>(i, 0, LDA)];
|
|
Scalar *B_t = &B_arr[0 * LDB + j];
|
|
PacketBlock<vec, EIGEN_ARCH_DEFAULT_NUMBER_OF_REGISTERS> zmm;
|
|
urolls::template setzero<3, 1>(zmm);
|
|
{
|
|
for (int64_t k = 0; k < K_; k += EIGEN_AVX_MAX_K_UNROL) {
|
|
urolls::template microKernel<isARowMajor, 3, 1, EIGEN_AVX_MAX_K_UNROL, EIGEN_AVX_B_LOAD_SETS * 3, 1>(
|
|
B_t, A_t, LDB, LDA, zmm);
|
|
B_t += EIGEN_AVX_MAX_K_UNROL * LDB;
|
|
EIGEN_IF_CONSTEXPR(isARowMajor) A_t += EIGEN_AVX_MAX_K_UNROL;
|
|
else A_t += EIGEN_AVX_MAX_K_UNROL * LDA;
|
|
}
|
|
EIGEN_IF_CONSTEXPR(handleKRem) {
|
|
for (int64_t k = K_; k < K; k++) {
|
|
urolls::template microKernel<isARowMajor, 3, 1, 1, EIGEN_AVX_B_LOAD_SETS * 3, 1>(B_t, A_t, LDB, LDA, zmm);
|
|
B_t += LDB;
|
|
EIGEN_IF_CONSTEXPR(isARowMajor) A_t++;
|
|
else A_t += LDA;
|
|
}
|
|
}
|
|
EIGEN_IF_CONSTEXPR(isCRowMajor) {
|
|
urolls::template updateC<3, 1>(&C_arr[i * LDC + j], LDC, zmm);
|
|
urolls::template storeC<3, 1>(&C_arr[i * LDC + j], LDC, zmm);
|
|
}
|
|
else {
|
|
transStoreC<Scalar, vec, EIGEN_AVX_MAX_NUM_ROW, U3, true, false>(zmm, &C_arr[i + j * LDC], LDC, 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (N - j >= U2) {
|
|
constexpr int64_t EIGEN_AVX_MAX_B_LOAD = EIGEN_AVX_B_LOAD_SETS * 2;
|
|
int64_t i = 0;
|
|
for (; i < M_; i += EIGEN_AVX_MAX_NUM_ROW) {
|
|
Scalar *A_t = &A_arr[idA<isARowMajor>(i, 0, LDA)], *B_t = &B_arr[0 * LDB + j];
|
|
EIGEN_IF_CONSTEXPR(isCRowMajor) B_t = &B_arr[0 * LDB + j];
|
|
PacketBlock<vec, EIGEN_ARCH_DEFAULT_NUMBER_OF_REGISTERS> zmm;
|
|
urolls::template setzero<2, EIGEN_AVX_MAX_NUM_ROW>(zmm);
|
|
for (int64_t k = 0; k < K_; k += EIGEN_AVX_MAX_K_UNROL) {
|
|
urolls::template microKernel<isARowMajor, 2, EIGEN_AVX_MAX_NUM_ROW, EIGEN_AVX_MAX_K_UNROL, EIGEN_AVX_MAX_B_LOAD,
|
|
EIGEN_AVX_MAX_A_BCAST>(B_t, A_t, LDB, LDA, zmm);
|
|
B_t += EIGEN_AVX_MAX_K_UNROL * LDB;
|
|
EIGEN_IF_CONSTEXPR(isARowMajor) A_t += EIGEN_AVX_MAX_K_UNROL;
|
|
else A_t += EIGEN_AVX_MAX_K_UNROL * LDA;
|
|
}
|
|
EIGEN_IF_CONSTEXPR(handleKRem) {
|
|
for (int64_t k = K_; k < K; k++) {
|
|
urolls::template microKernel<isARowMajor, 2, EIGEN_AVX_MAX_NUM_ROW, 1, EIGEN_AVX_MAX_B_LOAD,
|
|
EIGEN_AVX_MAX_A_BCAST>(B_t, A_t, LDB, LDA, zmm);
|
|
B_t += LDB;
|
|
EIGEN_IF_CONSTEXPR(isARowMajor) A_t++;
|
|
else A_t += LDA;
|
|
}
|
|
}
|
|
EIGEN_IF_CONSTEXPR(isCRowMajor) {
|
|
urolls::template updateC<2, EIGEN_AVX_MAX_NUM_ROW>(&C_arr[i * LDC + j], LDC, zmm);
|
|
urolls::template storeC<2, EIGEN_AVX_MAX_NUM_ROW>(&C_arr[i * LDC + j], LDC, zmm);
|
|
}
|
|
else {
|
|
transStoreC<Scalar, vec, EIGEN_AVX_MAX_NUM_ROW, U2, false, false>(zmm, &C_arr[i + j * LDC], LDC);
|
|
}
|
|
}
|
|
if (M - i >= 4) { // Note: this block assumes EIGEN_AVX_MAX_NUM_ROW = 8. Should be removed otherwise
|
|
Scalar *A_t = &A_arr[idA<isARowMajor>(i, 0, LDA)];
|
|
Scalar *B_t = &B_arr[0 * LDB + j];
|
|
PacketBlock<vec, EIGEN_ARCH_DEFAULT_NUMBER_OF_REGISTERS> zmm;
|
|
urolls::template setzero<2, 4>(zmm);
|
|
for (int64_t k = 0; k < K_; k += EIGEN_AVX_MAX_K_UNROL) {
|
|
urolls::template microKernel<isARowMajor, 2, 4, EIGEN_AVX_MAX_K_UNROL, EIGEN_AVX_MAX_B_LOAD,
|
|
EIGEN_AVX_MAX_A_BCAST>(B_t, A_t, LDB, LDA, zmm);
|
|
B_t += EIGEN_AVX_MAX_K_UNROL * LDB;
|
|
EIGEN_IF_CONSTEXPR(isARowMajor) A_t += EIGEN_AVX_MAX_K_UNROL;
|
|
else A_t += EIGEN_AVX_MAX_K_UNROL * LDA;
|
|
}
|
|
EIGEN_IF_CONSTEXPR(handleKRem) {
|
|
for (int64_t k = K_; k < K; k++) {
|
|
urolls::template microKernel<isARowMajor, 2, 4, 1, EIGEN_AVX_MAX_B_LOAD, EIGEN_AVX_MAX_A_BCAST>(B_t, A_t, LDB,
|
|
LDA, zmm);
|
|
B_t += LDB;
|
|
EIGEN_IF_CONSTEXPR(isARowMajor) A_t++;
|
|
else A_t += LDA;
|
|
}
|
|
}
|
|
EIGEN_IF_CONSTEXPR(isCRowMajor) {
|
|
urolls::template updateC<2, 4>(&C_arr[i * LDC + j], LDC, zmm);
|
|
urolls::template storeC<2, 4>(&C_arr[i * LDC + j], LDC, zmm);
|
|
}
|
|
else {
|
|
transStoreC<Scalar, vec, EIGEN_AVX_MAX_NUM_ROW, U2, true, false>(zmm, &C_arr[i + j * LDC], LDC, 4);
|
|
}
|
|
i += 4;
|
|
}
|
|
if (M - i >= 2) {
|
|
Scalar *A_t = &A_arr[idA<isARowMajor>(i, 0, LDA)];
|
|
Scalar *B_t = &B_arr[0 * LDB + j];
|
|
PacketBlock<vec, EIGEN_ARCH_DEFAULT_NUMBER_OF_REGISTERS> zmm;
|
|
urolls::template setzero<2, 2>(zmm);
|
|
for (int64_t k = 0; k < K_; k += EIGEN_AVX_MAX_K_UNROL) {
|
|
urolls::template microKernel<isARowMajor, 2, 2, EIGEN_AVX_MAX_K_UNROL, EIGEN_AVX_MAX_B_LOAD,
|
|
EIGEN_AVX_MAX_A_BCAST>(B_t, A_t, LDB, LDA, zmm);
|
|
B_t += EIGEN_AVX_MAX_K_UNROL * LDB;
|
|
EIGEN_IF_CONSTEXPR(isARowMajor) A_t += EIGEN_AVX_MAX_K_UNROL;
|
|
else A_t += EIGEN_AVX_MAX_K_UNROL * LDA;
|
|
}
|
|
EIGEN_IF_CONSTEXPR(handleKRem) {
|
|
for (int64_t k = K_; k < K; k++) {
|
|
urolls::template microKernel<isARowMajor, 2, 2, 1, EIGEN_AVX_MAX_B_LOAD, EIGEN_AVX_MAX_A_BCAST>(B_t, A_t, LDB,
|
|
LDA, zmm);
|
|
B_t += LDB;
|
|
EIGEN_IF_CONSTEXPR(isARowMajor) A_t++;
|
|
else A_t += LDA;
|
|
}
|
|
}
|
|
EIGEN_IF_CONSTEXPR(isCRowMajor) {
|
|
urolls::template updateC<2, 2>(&C_arr[i * LDC + j], LDC, zmm);
|
|
urolls::template storeC<2, 2>(&C_arr[i * LDC + j], LDC, zmm);
|
|
}
|
|
else {
|
|
transStoreC<Scalar, vec, EIGEN_AVX_MAX_NUM_ROW, U2, true, false>(zmm, &C_arr[i + j * LDC], LDC, 2);
|
|
}
|
|
i += 2;
|
|
}
|
|
if (M - i > 0) {
|
|
Scalar *A_t = &A_arr[idA<isARowMajor>(i, 0, LDA)];
|
|
Scalar *B_t = &B_arr[0 * LDB + j];
|
|
PacketBlock<vec, EIGEN_ARCH_DEFAULT_NUMBER_OF_REGISTERS> zmm;
|
|
urolls::template setzero<2, 1>(zmm);
|
|
for (int64_t k = 0; k < K_; k += EIGEN_AVX_MAX_K_UNROL) {
|
|
urolls::template microKernel<isARowMajor, 2, 1, EIGEN_AVX_MAX_K_UNROL, EIGEN_AVX_MAX_B_LOAD, 1>(B_t, A_t, LDB,
|
|
LDA, zmm);
|
|
B_t += EIGEN_AVX_MAX_K_UNROL * LDB;
|
|
EIGEN_IF_CONSTEXPR(isARowMajor) A_t += EIGEN_AVX_MAX_K_UNROL;
|
|
else A_t += EIGEN_AVX_MAX_K_UNROL * LDA;
|
|
}
|
|
EIGEN_IF_CONSTEXPR(handleKRem) {
|
|
for (int64_t k = K_; k < K; k++) {
|
|
urolls::template microKernel<isARowMajor, 2, 1, 1, EIGEN_AVX_MAX_B_LOAD, 1>(B_t, A_t, LDB, LDA, zmm);
|
|
B_t += LDB;
|
|
EIGEN_IF_CONSTEXPR(isARowMajor) A_t++;
|
|
else A_t += LDA;
|
|
}
|
|
}
|
|
EIGEN_IF_CONSTEXPR(isCRowMajor) {
|
|
urolls::template updateC<2, 1>(&C_arr[i * LDC + j], LDC, zmm);
|
|
urolls::template storeC<2, 1>(&C_arr[i * LDC + j], LDC, zmm);
|
|
}
|
|
else {
|
|
transStoreC<Scalar, vec, EIGEN_AVX_MAX_NUM_ROW, U2, true, false>(zmm, &C_arr[i + j * LDC], LDC, 1);
|
|
}
|
|
}
|
|
j += U2;
|
|
}
|
|
if (N - j >= U1) {
|
|
constexpr int64_t EIGEN_AVX_MAX_B_LOAD = EIGEN_AVX_B_LOAD_SETS * 1;
|
|
int64_t i = 0;
|
|
for (; i < M_; i += EIGEN_AVX_MAX_NUM_ROW) {
|
|
Scalar *A_t = &A_arr[idA<isARowMajor>(i, 0, LDA)], *B_t = &B_arr[0 * LDB + j];
|
|
PacketBlock<vec, EIGEN_ARCH_DEFAULT_NUMBER_OF_REGISTERS> zmm;
|
|
urolls::template setzero<1, EIGEN_AVX_MAX_NUM_ROW>(zmm);
|
|
for (int64_t k = 0; k < K_; k += EIGEN_AVX_MAX_K_UNROL) {
|
|
urolls::template microKernel<isARowMajor, 1, EIGEN_AVX_MAX_NUM_ROW, EIGEN_AVX_MAX_K_UNROL, EIGEN_AVX_MAX_B_LOAD,
|
|
EIGEN_AVX_MAX_A_BCAST>(B_t, A_t, LDB, LDA, zmm);
|
|
B_t += EIGEN_AVX_MAX_K_UNROL * LDB;
|
|
EIGEN_IF_CONSTEXPR(isARowMajor) A_t += EIGEN_AVX_MAX_K_UNROL;
|
|
else A_t += EIGEN_AVX_MAX_K_UNROL * LDA;
|
|
}
|
|
EIGEN_IF_CONSTEXPR(handleKRem) {
|
|
for (int64_t k = K_; k < K; k++) {
|
|
urolls::template microKernel<isARowMajor, 1, EIGEN_AVX_MAX_NUM_ROW, 1, EIGEN_AVX_B_LOAD_SETS * 1,
|
|
EIGEN_AVX_MAX_A_BCAST>(B_t, A_t, LDB, LDA, zmm);
|
|
B_t += LDB;
|
|
EIGEN_IF_CONSTEXPR(isARowMajor) A_t++;
|
|
else A_t += LDA;
|
|
}
|
|
}
|
|
EIGEN_IF_CONSTEXPR(isCRowMajor) {
|
|
urolls::template updateC<1, EIGEN_AVX_MAX_NUM_ROW>(&C_arr[i * LDC + j], LDC, zmm);
|
|
urolls::template storeC<1, EIGEN_AVX_MAX_NUM_ROW>(&C_arr[i * LDC + j], LDC, zmm);
|
|
}
|
|
else {
|
|
transStoreC<Scalar, vec, EIGEN_AVX_MAX_NUM_ROW, U1, false, false>(zmm, &C_arr[i + j * LDC], LDC);
|
|
}
|
|
}
|
|
if (M - i >= 4) { // Note: this block assumes EIGEN_AVX_MAX_NUM_ROW = 8. Should be removed otherwise
|
|
Scalar *A_t = &A_arr[idA<isARowMajor>(i, 0, LDA)];
|
|
Scalar *B_t = &B_arr[0 * LDB + j];
|
|
PacketBlock<vec, EIGEN_ARCH_DEFAULT_NUMBER_OF_REGISTERS> zmm;
|
|
urolls::template setzero<1, 4>(zmm);
|
|
for (int64_t k = 0; k < K_; k += EIGEN_AVX_MAX_K_UNROL) {
|
|
urolls::template microKernel<isARowMajor, 1, 4, EIGEN_AVX_MAX_K_UNROL, EIGEN_AVX_MAX_B_LOAD,
|
|
EIGEN_AVX_MAX_A_BCAST>(B_t, A_t, LDB, LDA, zmm);
|
|
B_t += EIGEN_AVX_MAX_K_UNROL * LDB;
|
|
EIGEN_IF_CONSTEXPR(isARowMajor) A_t += EIGEN_AVX_MAX_K_UNROL;
|
|
else A_t += EIGEN_AVX_MAX_K_UNROL * LDA;
|
|
}
|
|
EIGEN_IF_CONSTEXPR(handleKRem) {
|
|
for (int64_t k = K_; k < K; k++) {
|
|
urolls::template microKernel<isARowMajor, 1, 4, 1, EIGEN_AVX_MAX_B_LOAD, EIGEN_AVX_MAX_A_BCAST>(B_t, A_t, LDB,
|
|
LDA, zmm);
|
|
B_t += LDB;
|
|
EIGEN_IF_CONSTEXPR(isARowMajor) A_t++;
|
|
else A_t += LDA;
|
|
}
|
|
}
|
|
EIGEN_IF_CONSTEXPR(isCRowMajor) {
|
|
urolls::template updateC<1, 4>(&C_arr[i * LDC + j], LDC, zmm);
|
|
urolls::template storeC<1, 4>(&C_arr[i * LDC + j], LDC, zmm);
|
|
}
|
|
else {
|
|
transStoreC<Scalar, vec, EIGEN_AVX_MAX_NUM_ROW, U1, true, false>(zmm, &C_arr[i + j * LDC], LDC, 4);
|
|
}
|
|
i += 4;
|
|
}
|
|
if (M - i >= 2) {
|
|
Scalar *A_t = &A_arr[idA<isARowMajor>(i, 0, LDA)];
|
|
Scalar *B_t = &B_arr[0 * LDB + j];
|
|
PacketBlock<vec, EIGEN_ARCH_DEFAULT_NUMBER_OF_REGISTERS> zmm;
|
|
urolls::template setzero<1, 2>(zmm);
|
|
for (int64_t k = 0; k < K_; k += EIGEN_AVX_MAX_K_UNROL) {
|
|
urolls::template microKernel<isARowMajor, 1, 2, EIGEN_AVX_MAX_K_UNROL, EIGEN_AVX_MAX_B_LOAD,
|
|
EIGEN_AVX_MAX_A_BCAST>(B_t, A_t, LDB, LDA, zmm);
|
|
B_t += EIGEN_AVX_MAX_K_UNROL * LDB;
|
|
EIGEN_IF_CONSTEXPR(isARowMajor) A_t += EIGEN_AVX_MAX_K_UNROL;
|
|
else A_t += EIGEN_AVX_MAX_K_UNROL * LDA;
|
|
}
|
|
EIGEN_IF_CONSTEXPR(handleKRem) {
|
|
for (int64_t k = K_; k < K; k++) {
|
|
urolls::template microKernel<isARowMajor, 1, 2, 1, EIGEN_AVX_MAX_B_LOAD, EIGEN_AVX_MAX_A_BCAST>(B_t, A_t, LDB,
|
|
LDA, zmm);
|
|
B_t += LDB;
|
|
EIGEN_IF_CONSTEXPR(isARowMajor) A_t++;
|
|
else A_t += LDA;
|
|
}
|
|
}
|
|
EIGEN_IF_CONSTEXPR(isCRowMajor) {
|
|
urolls::template updateC<1, 2>(&C_arr[i * LDC + j], LDC, zmm);
|
|
urolls::template storeC<1, 2>(&C_arr[i * LDC + j], LDC, zmm);
|
|
}
|
|
else {
|
|
transStoreC<Scalar, vec, EIGEN_AVX_MAX_NUM_ROW, U1, true, false>(zmm, &C_arr[i + j * LDC], LDC, 2);
|
|
}
|
|
i += 2;
|
|
}
|
|
if (M - i > 0) {
|
|
Scalar *A_t = &A_arr[idA<isARowMajor>(i, 0, LDA)];
|
|
Scalar *B_t = &B_arr[0 * LDB + j];
|
|
PacketBlock<vec, EIGEN_ARCH_DEFAULT_NUMBER_OF_REGISTERS> zmm;
|
|
urolls::template setzero<1, 1>(zmm);
|
|
{
|
|
for (int64_t k = 0; k < K_; k += EIGEN_AVX_MAX_K_UNROL) {
|
|
urolls::template microKernel<isARowMajor, 1, 1, EIGEN_AVX_MAX_K_UNROL, EIGEN_AVX_MAX_B_LOAD, 1>(B_t, A_t, LDB,
|
|
LDA, zmm);
|
|
B_t += EIGEN_AVX_MAX_K_UNROL * LDB;
|
|
EIGEN_IF_CONSTEXPR(isARowMajor) A_t += EIGEN_AVX_MAX_K_UNROL;
|
|
else A_t += EIGEN_AVX_MAX_K_UNROL * LDA;
|
|
}
|
|
EIGEN_IF_CONSTEXPR(handleKRem) {
|
|
for (int64_t k = K_; k < K; k++) {
|
|
urolls::template microKernel<isARowMajor, 1, 1, 1, EIGEN_AVX_B_LOAD_SETS * 1, 1>(B_t, A_t, LDB, LDA, zmm);
|
|
B_t += LDB;
|
|
EIGEN_IF_CONSTEXPR(isARowMajor) A_t++;
|
|
else A_t += LDA;
|
|
}
|
|
}
|
|
EIGEN_IF_CONSTEXPR(isCRowMajor) {
|
|
urolls::template updateC<1, 1>(&C_arr[i * LDC + j], LDC, zmm);
|
|
urolls::template storeC<1, 1>(&C_arr[i * LDC + j], LDC, zmm);
|
|
}
|
|
else {
|
|
transStoreC<Scalar, vec, EIGEN_AVX_MAX_NUM_ROW, U1, true, false>(zmm, &C_arr[i + j * LDC], LDC, 1);
|
|
}
|
|
}
|
|
}
|
|
j += U1;
|
|
}
|
|
if (N - j > 0) {
|
|
constexpr int64_t EIGEN_AVX_MAX_B_LOAD = EIGEN_AVX_B_LOAD_SETS * 1;
|
|
int64_t i = 0;
|
|
for (; i < M_; i += EIGEN_AVX_MAX_NUM_ROW) {
|
|
Scalar *A_t = &A_arr[idA<isARowMajor>(i, 0, LDA)];
|
|
Scalar *B_t = &B_arr[0 * LDB + j];
|
|
PacketBlock<vec, EIGEN_ARCH_DEFAULT_NUMBER_OF_REGISTERS> zmm;
|
|
urolls::template setzero<1, EIGEN_AVX_MAX_NUM_ROW>(zmm);
|
|
for (int64_t k = 0; k < K_; k += EIGEN_AVX_MAX_K_UNROL) {
|
|
urolls::template microKernel<isARowMajor, 1, EIGEN_AVX_MAX_NUM_ROW, EIGEN_AVX_MAX_K_UNROL, EIGEN_AVX_MAX_B_LOAD,
|
|
EIGEN_AVX_MAX_A_BCAST, true>(B_t, A_t, LDB, LDA, zmm, N - j);
|
|
B_t += EIGEN_AVX_MAX_K_UNROL * LDB;
|
|
EIGEN_IF_CONSTEXPR(isARowMajor) A_t += EIGEN_AVX_MAX_K_UNROL;
|
|
else A_t += EIGEN_AVX_MAX_K_UNROL * LDA;
|
|
}
|
|
EIGEN_IF_CONSTEXPR(handleKRem) {
|
|
for (int64_t k = K_; k < K; k++) {
|
|
urolls::template microKernel<isARowMajor, 1, EIGEN_AVX_MAX_NUM_ROW, 1, EIGEN_AVX_MAX_B_LOAD,
|
|
EIGEN_AVX_MAX_A_BCAST, true>(B_t, A_t, LDB, LDA, zmm, N - j);
|
|
B_t += LDB;
|
|
EIGEN_IF_CONSTEXPR(isARowMajor) A_t++;
|
|
else A_t += LDA;
|
|
}
|
|
}
|
|
EIGEN_IF_CONSTEXPR(isCRowMajor) {
|
|
urolls::template updateC<1, EIGEN_AVX_MAX_NUM_ROW, true>(&C_arr[i * LDC + j], LDC, zmm, N - j);
|
|
urolls::template storeC<1, EIGEN_AVX_MAX_NUM_ROW, true>(&C_arr[i * LDC + j], LDC, zmm, N - j);
|
|
}
|
|
else {
|
|
transStoreC<Scalar, vec, EIGEN_AVX_MAX_NUM_ROW, U1, false, true>(zmm, &C_arr[i + j * LDC], LDC, 0, N - j);
|
|
}
|
|
}
|
|
if (M - i >= 4) { // Note: this block assumes EIGEN_AVX_MAX_NUM_ROW = 8. Should be removed otherwise
|
|
Scalar *A_t = &A_arr[idA<isARowMajor>(i, 0, LDA)];
|
|
Scalar *B_t = &B_arr[0 * LDB + j];
|
|
PacketBlock<vec, EIGEN_ARCH_DEFAULT_NUMBER_OF_REGISTERS> zmm;
|
|
urolls::template setzero<1, 4>(zmm);
|
|
for (int64_t k = 0; k < K_; k += EIGEN_AVX_MAX_K_UNROL) {
|
|
urolls::template microKernel<isARowMajor, 1, 4, EIGEN_AVX_MAX_K_UNROL, EIGEN_AVX_MAX_B_LOAD,
|
|
EIGEN_AVX_MAX_A_BCAST, true>(B_t, A_t, LDB, LDA, zmm, N - j);
|
|
B_t += EIGEN_AVX_MAX_K_UNROL * LDB;
|
|
EIGEN_IF_CONSTEXPR(isARowMajor) A_t += EIGEN_AVX_MAX_K_UNROL;
|
|
else A_t += EIGEN_AVX_MAX_K_UNROL * LDA;
|
|
}
|
|
EIGEN_IF_CONSTEXPR(handleKRem) {
|
|
for (int64_t k = K_; k < K; k++) {
|
|
urolls::template microKernel<isARowMajor, 1, 4, 1, EIGEN_AVX_MAX_B_LOAD, EIGEN_AVX_MAX_A_BCAST, true>(
|
|
B_t, A_t, LDB, LDA, zmm, N - j);
|
|
B_t += LDB;
|
|
EIGEN_IF_CONSTEXPR(isARowMajor) A_t++;
|
|
else A_t += LDA;
|
|
}
|
|
}
|
|
EIGEN_IF_CONSTEXPR(isCRowMajor) {
|
|
urolls::template updateC<1, 4, true>(&C_arr[i * LDC + j], LDC, zmm, N - j);
|
|
urolls::template storeC<1, 4, true>(&C_arr[i * LDC + j], LDC, zmm, N - j);
|
|
}
|
|
else {
|
|
transStoreC<Scalar, vec, EIGEN_AVX_MAX_NUM_ROW, U1, true, true>(zmm, &C_arr[i + j * LDC], LDC, 4, N - j);
|
|
}
|
|
i += 4;
|
|
}
|
|
if (M - i >= 2) {
|
|
Scalar *A_t = &A_arr[idA<isARowMajor>(i, 0, LDA)];
|
|
Scalar *B_t = &B_arr[0 * LDB + j];
|
|
PacketBlock<vec, EIGEN_ARCH_DEFAULT_NUMBER_OF_REGISTERS> zmm;
|
|
urolls::template setzero<1, 2>(zmm);
|
|
for (int64_t k = 0; k < K_; k += EIGEN_AVX_MAX_K_UNROL) {
|
|
urolls::template microKernel<isARowMajor, 1, 2, EIGEN_AVX_MAX_K_UNROL, EIGEN_AVX_MAX_B_LOAD,
|
|
EIGEN_AVX_MAX_A_BCAST, true>(B_t, A_t, LDB, LDA, zmm, N - j);
|
|
B_t += EIGEN_AVX_MAX_K_UNROL * LDB;
|
|
EIGEN_IF_CONSTEXPR(isARowMajor) A_t += EIGEN_AVX_MAX_K_UNROL;
|
|
else A_t += EIGEN_AVX_MAX_K_UNROL * LDA;
|
|
}
|
|
EIGEN_IF_CONSTEXPR(handleKRem) {
|
|
for (int64_t k = K_; k < K; k++) {
|
|
urolls::template microKernel<isARowMajor, 1, 2, 1, EIGEN_AVX_MAX_B_LOAD, EIGEN_AVX_MAX_A_BCAST, true>(
|
|
B_t, A_t, LDB, LDA, zmm, N - j);
|
|
B_t += LDB;
|
|
EIGEN_IF_CONSTEXPR(isARowMajor) A_t++;
|
|
else A_t += LDA;
|
|
}
|
|
}
|
|
EIGEN_IF_CONSTEXPR(isCRowMajor) {
|
|
urolls::template updateC<1, 2, true>(&C_arr[i * LDC + j], LDC, zmm, N - j);
|
|
urolls::template storeC<1, 2, true>(&C_arr[i * LDC + j], LDC, zmm, N - j);
|
|
}
|
|
else {
|
|
transStoreC<Scalar, vec, EIGEN_AVX_MAX_NUM_ROW, U1, true, true>(zmm, &C_arr[i + j * LDC], LDC, 2, N - j);
|
|
}
|
|
i += 2;
|
|
}
|
|
if (M - i > 0) {
|
|
Scalar *A_t = &A_arr[idA<isARowMajor>(i, 0, LDA)];
|
|
Scalar *B_t = &B_arr[0 * LDB + j];
|
|
PacketBlock<vec, EIGEN_ARCH_DEFAULT_NUMBER_OF_REGISTERS> zmm;
|
|
urolls::template setzero<1, 1>(zmm);
|
|
for (int64_t k = 0; k < K_; k += EIGEN_AVX_MAX_K_UNROL) {
|
|
urolls::template microKernel<isARowMajor, 1, 1, EIGEN_AVX_MAX_K_UNROL, EIGEN_AVX_MAX_B_LOAD, 1, true>(
|
|
B_t, A_t, LDB, LDA, zmm, N - j);
|
|
B_t += EIGEN_AVX_MAX_K_UNROL * LDB;
|
|
EIGEN_IF_CONSTEXPR(isARowMajor) A_t += EIGEN_AVX_MAX_K_UNROL;
|
|
else A_t += EIGEN_AVX_MAX_K_UNROL * LDA;
|
|
}
|
|
EIGEN_IF_CONSTEXPR(handleKRem) {
|
|
for (int64_t k = K_; k < K; k++) {
|
|
urolls::template microKernel<isARowMajor, 1, 1, 1, EIGEN_AVX_MAX_B_LOAD, 1, true>(B_t, A_t, LDB, LDA, zmm,
|
|
N - j);
|
|
B_t += LDB;
|
|
EIGEN_IF_CONSTEXPR(isARowMajor) A_t++;
|
|
else A_t += LDA;
|
|
}
|
|
}
|
|
EIGEN_IF_CONSTEXPR(isCRowMajor) {
|
|
urolls::template updateC<1, 1, true>(&C_arr[i * LDC + j], LDC, zmm, N - j);
|
|
urolls::template storeC<1, 1, true>(&C_arr[i * LDC + j], LDC, zmm, N - j);
|
|
}
|
|
else {
|
|
transStoreC<Scalar, vec, EIGEN_AVX_MAX_NUM_ROW, U1, true, true>(zmm, &C_arr[i + j * LDC], LDC, 1, N - j);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Triangular solve kernel with A on left with K number of rhs. dim(A) = unrollM
|
|
*
|
|
* unrollM: dimension of A matrix (triangular matrix). unrollM should be <= EIGEN_AVX_MAX_NUM_ROW
|
|
* isFWDSolve: is forward solve?
|
|
* isUnitDiag: is the diagonal of A all ones?
|
|
* The B matrix (RHS) is assumed to be row-major
|
|
*/
|
|
template <typename Scalar, typename vec, int64_t unrollM, bool isARowMajor, bool isFWDSolve, bool isUnitDiag>
|
|
static EIGEN_ALWAYS_INLINE void triSolveKernel(Scalar *A_arr, Scalar *B_arr, int64_t K, int64_t LDA, int64_t LDB) {
|
|
static_assert(unrollM <= EIGEN_AVX_MAX_NUM_ROW, "unrollM should be equal to EIGEN_AVX_MAX_NUM_ROW");
|
|
using urolls = unrolls::trsm<Scalar>;
|
|
constexpr int64_t U3 = urolls::PacketSize * 3;
|
|
constexpr int64_t U2 = urolls::PacketSize * 2;
|
|
constexpr int64_t U1 = urolls::PacketSize * 1;
|
|
|
|
PacketBlock<vec, EIGEN_AVX_MAX_NUM_ACC> RHSInPacket;
|
|
PacketBlock<vec, EIGEN_AVX_MAX_NUM_ROW> AInPacket;
|
|
|
|
int64_t k = 0;
|
|
while (K - k >= U3) {
|
|
urolls::template loadRHS<isFWDSolve, unrollM, 3>(B_arr + k, LDB, RHSInPacket);
|
|
urolls::template triSolveMicroKernel<isARowMajor, isFWDSolve, isUnitDiag, unrollM, 3>(A_arr, LDA, RHSInPacket,
|
|
AInPacket);
|
|
urolls::template storeRHS<isFWDSolve, unrollM, 3>(B_arr + k, LDB, RHSInPacket);
|
|
k += U3;
|
|
}
|
|
if (K - k >= U2) {
|
|
urolls::template loadRHS<isFWDSolve, unrollM, 2>(B_arr + k, LDB, RHSInPacket);
|
|
urolls::template triSolveMicroKernel<isARowMajor, isFWDSolve, isUnitDiag, unrollM, 2>(A_arr, LDA, RHSInPacket,
|
|
AInPacket);
|
|
urolls::template storeRHS<isFWDSolve, unrollM, 2>(B_arr + k, LDB, RHSInPacket);
|
|
k += U2;
|
|
}
|
|
if (K - k >= U1) {
|
|
urolls::template loadRHS<isFWDSolve, unrollM, 1>(B_arr + k, LDB, RHSInPacket);
|
|
urolls::template triSolveMicroKernel<isARowMajor, isFWDSolve, isUnitDiag, unrollM, 1>(A_arr, LDA, RHSInPacket,
|
|
AInPacket);
|
|
urolls::template storeRHS<isFWDSolve, unrollM, 1>(B_arr + k, LDB, RHSInPacket);
|
|
k += U1;
|
|
}
|
|
if (K - k > 0) {
|
|
// Handle remaining number of RHS
|
|
urolls::template loadRHS<isFWDSolve, unrollM, 1, true>(B_arr + k, LDB, RHSInPacket, K - k);
|
|
urolls::template triSolveMicroKernel<isARowMajor, isFWDSolve, isUnitDiag, unrollM, 1>(A_arr, LDA, RHSInPacket,
|
|
AInPacket);
|
|
urolls::template storeRHS<isFWDSolve, unrollM, 1, true>(B_arr + k, LDB, RHSInPacket, K - k);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Triangular solve routine with A on left and dimension of at most L with K number of rhs. This is essentially
|
|
* a wrapper for triSolveMicrokernel for M = {1,2,3,4,5,6,7,8}.
|
|
*
|
|
* isFWDSolve: is forward solve?
|
|
* isUnitDiag: is the diagonal of A all ones?
|
|
* The B matrix (RHS) is assumed to be row-major
|
|
*/
|
|
template <typename Scalar, bool isARowMajor, bool isFWDSolve, bool isUnitDiag>
|
|
void triSolveKernelLxK(Scalar *A_arr, Scalar *B_arr, int64_t M, int64_t K, int64_t LDA, int64_t LDB) {
|
|
// Note: this assumes EIGEN_AVX_MAX_NUM_ROW = 8. Unrolls should be adjusted
|
|
// accordingly if EIGEN_AVX_MAX_NUM_ROW is smaller.
|
|
using vec = typename std::conditional<std::is_same<Scalar, float>::value, vecFullFloat, vecFullDouble>::type;
|
|
if (M == 8)
|
|
triSolveKernel<Scalar, vec, 8, isARowMajor, isFWDSolve, isUnitDiag>(A_arr, B_arr, K, LDA, LDB);
|
|
else if (M == 7)
|
|
triSolveKernel<Scalar, vec, 7, isARowMajor, isFWDSolve, isUnitDiag>(A_arr, B_arr, K, LDA, LDB);
|
|
else if (M == 6)
|
|
triSolveKernel<Scalar, vec, 6, isARowMajor, isFWDSolve, isUnitDiag>(A_arr, B_arr, K, LDA, LDB);
|
|
else if (M == 5)
|
|
triSolveKernel<Scalar, vec, 5, isARowMajor, isFWDSolve, isUnitDiag>(A_arr, B_arr, K, LDA, LDB);
|
|
else if (M == 4)
|
|
triSolveKernel<Scalar, vec, 4, isARowMajor, isFWDSolve, isUnitDiag>(A_arr, B_arr, K, LDA, LDB);
|
|
else if (M == 3)
|
|
triSolveKernel<Scalar, vec, 3, isARowMajor, isFWDSolve, isUnitDiag>(A_arr, B_arr, K, LDA, LDB);
|
|
else if (M == 2)
|
|
triSolveKernel<Scalar, vec, 2, isARowMajor, isFWDSolve, isUnitDiag>(A_arr, B_arr, K, LDA, LDB);
|
|
else if (M == 1)
|
|
triSolveKernel<Scalar, vec, 1, isARowMajor, isFWDSolve, isUnitDiag>(A_arr, B_arr, K, LDA, LDB);
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* This routine is used to copy B to/from a temporary array (row-major) for cases where B is column-major.
|
|
*
|
|
* toTemp: true => copy to temporary array, false => copy from temporary array
|
|
* remM: true = need to handle remainder values for M (M < EIGEN_AVX_MAX_NUM_ROW)
|
|
*
|
|
*/
|
|
template <typename Scalar, bool toTemp = true, bool remM = false>
|
|
static EIGEN_ALWAYS_INLINE void copyBToRowMajor(Scalar *B_arr, int64_t LDB, int64_t K, Scalar *B_temp, int64_t LDB_,
|
|
int64_t remM_ = 0) {
|
|
EIGEN_UNUSED_VARIABLE(remM_);
|
|
using urolls = unrolls::transB<Scalar>;
|
|
using vecHalf = typename std::conditional<std::is_same<Scalar, float>::value, vecHalfFloat, vecFullDouble>::type;
|
|
PacketBlock<vecHalf, EIGEN_ARCH_DEFAULT_NUMBER_OF_REGISTERS> ymm;
|
|
constexpr int64_t U3 = urolls::PacketSize * 3;
|
|
constexpr int64_t U2 = urolls::PacketSize * 2;
|
|
constexpr int64_t U1 = urolls::PacketSize * 1;
|
|
int64_t K_ = K / U3 * U3;
|
|
int64_t k = 0;
|
|
|
|
for (; k < K_; k += U3) {
|
|
urolls::template transB_kernel<U3, toTemp, remM>(B_arr + k * LDB, LDB, B_temp, LDB_, ymm, remM_);
|
|
B_temp += U3;
|
|
}
|
|
if (K - k >= U2) {
|
|
urolls::template transB_kernel<U2, toTemp, remM>(B_arr + k * LDB, LDB, B_temp, LDB_, ymm, remM_);
|
|
B_temp += U2;
|
|
k += U2;
|
|
}
|
|
if (K - k >= U1) {
|
|
urolls::template transB_kernel<U1, toTemp, remM>(B_arr + k * LDB, LDB, B_temp, LDB_, ymm, remM_);
|
|
B_temp += U1;
|
|
k += U1;
|
|
}
|
|
EIGEN_IF_CONSTEXPR(U1 > 8) {
|
|
// Note: without "if constexpr" this section of code will also be
|
|
// parsed by the compiler so there is an additional check in {load/store}BBlock
|
|
// to make sure the counter is not non-negative.
|
|
if (K - k >= 8) {
|
|
urolls::template transB_kernel<8, toTemp, remM>(B_arr + k * LDB, LDB, B_temp, LDB_, ymm, remM_);
|
|
B_temp += 8;
|
|
k += 8;
|
|
}
|
|
}
|
|
EIGEN_IF_CONSTEXPR(U1 > 4) {
|
|
// Note: without "if constexpr" this section of code will also be
|
|
// parsed by the compiler so there is an additional check in {load/store}BBlock
|
|
// to make sure the counter is not non-negative.
|
|
if (K - k >= 4) {
|
|
urolls::template transB_kernel<4, toTemp, remM>(B_arr + k * LDB, LDB, B_temp, LDB_, ymm, remM_);
|
|
B_temp += 4;
|
|
k += 4;
|
|
}
|
|
}
|
|
if (K - k >= 2) {
|
|
urolls::template transB_kernel<2, toTemp, remM>(B_arr + k * LDB, LDB, B_temp, LDB_, ymm, remM_);
|
|
B_temp += 2;
|
|
k += 2;
|
|
}
|
|
if (K - k >= 1) {
|
|
urolls::template transB_kernel<1, toTemp, remM>(B_arr + k * LDB, LDB, B_temp, LDB_, ymm, remM_);
|
|
B_temp += 1;
|
|
k += 1;
|
|
}
|
|
}
|
|
|
|
#if (EIGEN_USE_AVX512_TRSM_L_KERNELS) && defined(EIGEN_NO_MALLOC)
|
|
/**
|
|
* Reduce blocking sizes so that the size of the temporary workspace needed is less than "limit" bytes,
|
|
* - kB must be at least psize
|
|
* - numM must be at least EIGEN_AVX_MAX_NUM_ROW
|
|
*/
|
|
template <typename Scalar, bool isBRowMajor>
|
|
constexpr std::pair<int64_t, int64_t> trsmBlocking(const int64_t limit) {
|
|
constexpr int64_t psize = packet_traits<Scalar>::size;
|
|
int64_t kB = 15 * psize;
|
|
int64_t numM = 8 * EIGEN_AVX_MAX_NUM_ROW;
|
|
// If B is rowmajor, no temp workspace needed, so use default blocking sizes.
|
|
if (isBRowMajor) return {kB, numM};
|
|
|
|
// Very simple heuristic, prefer keeping kB as large as possible to fully use vector registers.
|
|
for (int64_t k = kB; k > psize; k -= psize) {
|
|
for (int64_t m = numM; m > EIGEN_AVX_MAX_NUM_ROW; m -= EIGEN_AVX_MAX_NUM_ROW) {
|
|
if ((((k + psize - 1) / psize + 4) * psize) * m * sizeof(Scalar) < limit) {
|
|
return {k, m};
|
|
}
|
|
}
|
|
}
|
|
return {psize, EIGEN_AVX_MAX_NUM_ROW}; // Minimum blocking size required
|
|
}
|
|
#endif // (EIGEN_USE_AVX512_TRSM_L_KERNELS) && defined(EIGEN_NO_MALLOC)
|
|
|
|
/**
|
|
* Main triangular solve driver
|
|
*
|
|
* Triangular solve with A on the left.
|
|
* Scalar: Scalar precision, only float/double is supported.
|
|
* isARowMajor: is A row-major?
|
|
* isBRowMajor: is B row-major?
|
|
* isFWDSolve: is this forward solve or backward (true => forward)?
|
|
* isUnitDiag: is diagonal of A unit or nonunit (true => A has unit diagonal)?
|
|
*
|
|
* M: dimension of A
|
|
* numRHS: number of right hand sides (coincides with K dimension for gemm updates)
|
|
*
|
|
* Here are the mapping between the different TRSM cases (col-major) and triSolve:
|
|
*
|
|
* LLN (left , lower, A non-transposed) :: isARowMajor=false, isBRowMajor=false, isFWDSolve=true
|
|
* LUT (left , upper, A transposed) :: isARowMajor=true, isBRowMajor=false, isFWDSolve=true
|
|
* LUN (left , upper, A non-transposed) :: isARowMajor=false, isBRowMajor=false, isFWDSolve=false
|
|
* LLT (left , lower, A transposed) :: isARowMajor=true, isBRowMajor=false, isFWDSolve=false
|
|
* RUN (right, upper, A non-transposed) :: isARowMajor=true, isBRowMajor=true, isFWDSolve=true
|
|
* RLT (right, lower, A transposed) :: isARowMajor=false, isBRowMajor=true, isFWDSolve=true
|
|
* RUT (right, upper, A transposed) :: isARowMajor=false, isBRowMajor=true, isFWDSolve=false
|
|
* RLN (right, lower, A non-transposed) :: isARowMajor=true, isBRowMajor=true, isFWDSolve=false
|
|
*
|
|
* Note: For RXX cases M,numRHS should be swapped.
|
|
*
|
|
*/
|
|
template <typename Scalar, bool isARowMajor = true, bool isBRowMajor = true, bool isFWDSolve = true,
|
|
bool isUnitDiag = false>
|
|
void triSolve(Scalar *A_arr, Scalar *B_arr, int64_t M, int64_t numRHS, int64_t LDA, int64_t LDB) {
|
|
constexpr int64_t psize = packet_traits<Scalar>::size;
|
|
/**
|
|
* The values for kB, numM were determined experimentally.
|
|
* kB: Number of RHS we process at a time.
|
|
* numM: number of rows of B we will store in a temporary array (see below.) This should be a multiple of L.
|
|
*
|
|
* kB was determined by initially setting kB = numRHS and benchmarking triSolve (TRSM-RUN case)
|
|
* performance with M=numRHS.
|
|
* It was observed that performance started to drop around M=numRHS=240. This is likely machine dependent.
|
|
*
|
|
* numM was chosen "arbitrarily". It should be relatively small so B_temp is not too large, but it should be
|
|
* large enough to allow GEMM updates to have larger "K"s (see below.) No benchmarking has been done so far to
|
|
* determine optimal values for numM.
|
|
*/
|
|
#if (EIGEN_USE_AVX512_TRSM_L_KERNELS) && defined(EIGEN_NO_MALLOC)
|
|
/**
|
|
* If EIGEN_NO_MALLOC is requested, we try to reduce kB and numM so the maximum temp workspace required is less
|
|
* than EIGEN_STACK_ALLOCATION_LIMIT. Actual workspace size may be less, depending on the number of vectors to
|
|
* solve.
|
|
* - kB must be at least psize
|
|
* - numM must be at least EIGEN_AVX_MAX_NUM_ROW
|
|
*
|
|
* If B is row-major, the blocking sizes are not reduced (no temp workspace needed).
|
|
*/
|
|
constexpr std::pair<int64_t, int64_t> blocking_ = trsmBlocking<Scalar, isBRowMajor>(EIGEN_STACK_ALLOCATION_LIMIT);
|
|
constexpr int64_t kB = blocking_.first;
|
|
constexpr int64_t numM = blocking_.second;
|
|
/**
|
|
* If the temp workspace size exceeds EIGEN_STACK_ALLOCATION_LIMIT even with the minimum blocking sizes,
|
|
* we throw an assertion. Use -DEIGEN_USE_AVX512_TRSM_L_KERNELS=0 if necessary
|
|
*/
|
|
static_assert(!(((((kB + psize - 1) / psize + 4) * psize) * numM * sizeof(Scalar) >= EIGEN_STACK_ALLOCATION_LIMIT) &&
|
|
!isBRowMajor),
|
|
"Temp workspace required is too large.");
|
|
#else
|
|
constexpr int64_t kB = (3 * psize) * 5; // 5*U3
|
|
constexpr int64_t numM = 8 * EIGEN_AVX_MAX_NUM_ROW;
|
|
#endif
|
|
|
|
int64_t sizeBTemp = 0;
|
|
Scalar *B_temp = NULL;
|
|
EIGEN_IF_CONSTEXPR(!isBRowMajor) {
|
|
/**
|
|
* If B is col-major, we copy it to a fixed-size temporary array of size at most ~numM*kB and
|
|
* transpose it to row-major. Call the solve routine, and copy+transpose it back to the original array.
|
|
* The updated row-major copy of B is reused in the GEMM updates.
|
|
*/
|
|
sizeBTemp = (((std::min(kB, numRHS) + psize - 1) / psize + 4) * psize) * numM;
|
|
}
|
|
|
|
#if !defined(EIGEN_NO_MALLOC)
|
|
EIGEN_IF_CONSTEXPR(!isBRowMajor) B_temp = (Scalar *)handmade_aligned_malloc(sizeof(Scalar) * sizeBTemp, 64);
|
|
#elif (EIGEN_USE_AVX512_TRSM_L_KERNELS) && defined(EIGEN_NO_MALLOC)
|
|
// Use alloca if malloc not allowed, requested temp workspace size should be less than EIGEN_STACK_ALLOCATION_LIMIT
|
|
ei_declare_aligned_stack_constructed_variable(Scalar, B_temp_alloca, sizeBTemp, 0);
|
|
B_temp = B_temp_alloca;
|
|
#endif
|
|
|
|
for (int64_t k = 0; k < numRHS; k += kB) {
|
|
int64_t bK = numRHS - k > kB ? kB : numRHS - k;
|
|
int64_t M_ = (M / EIGEN_AVX_MAX_NUM_ROW) * EIGEN_AVX_MAX_NUM_ROW, gemmOff = 0;
|
|
|
|
// bK rounded up to next multiple of L=EIGEN_AVX_MAX_NUM_ROW. When B_temp is used, we solve for bkL RHS
|
|
// instead of bK RHS in triSolveKernelLxK.
|
|
int64_t bkL = ((bK + (EIGEN_AVX_MAX_NUM_ROW - 1)) / EIGEN_AVX_MAX_NUM_ROW) * EIGEN_AVX_MAX_NUM_ROW;
|
|
const int64_t numScalarPerCache = 64 / sizeof(Scalar);
|
|
// Leading dimension of B_temp, will be a multiple of the cache line size.
|
|
int64_t LDT = ((bkL + (numScalarPerCache - 1)) / numScalarPerCache) * numScalarPerCache;
|
|
int64_t offsetBTemp = 0;
|
|
for (int64_t i = 0; i < M_; i += EIGEN_AVX_MAX_NUM_ROW) {
|
|
EIGEN_IF_CONSTEXPR(!isBRowMajor) {
|
|
int64_t indA_i = isFWDSolve ? i : M - 1 - i;
|
|
int64_t indB_i = isFWDSolve ? i : M - (i + EIGEN_AVX_MAX_NUM_ROW);
|
|
int64_t offB_1 = isFWDSolve ? offsetBTemp : sizeBTemp - EIGEN_AVX_MAX_NUM_ROW * LDT - offsetBTemp;
|
|
int64_t offB_2 = isFWDSolve ? offsetBTemp : sizeBTemp - LDT - offsetBTemp;
|
|
// Copy values from B to B_temp.
|
|
copyBToRowMajor<Scalar, true, false>(B_arr + indB_i + k * LDB, LDB, bK, B_temp + offB_1, LDT);
|
|
// Triangular solve with a small block of A and long horizontal blocks of B (or B_temp if B col-major)
|
|
triSolveKernelLxK<Scalar, isARowMajor, isFWDSolve, isUnitDiag>(
|
|
&A_arr[idA<isARowMajor>(indA_i, indA_i, LDA)], B_temp + offB_2, EIGEN_AVX_MAX_NUM_ROW, bkL, LDA, LDT);
|
|
// Copy values from B_temp back to B. B_temp will be reused in gemm call below.
|
|
copyBToRowMajor<Scalar, false, false>(B_arr + indB_i + k * LDB, LDB, bK, B_temp + offB_1, LDT);
|
|
|
|
offsetBTemp += EIGEN_AVX_MAX_NUM_ROW * LDT;
|
|
}
|
|
else {
|
|
int64_t ind = isFWDSolve ? i : M - 1 - i;
|
|
triSolveKernelLxK<Scalar, isARowMajor, isFWDSolve, isUnitDiag>(
|
|
&A_arr[idA<isARowMajor>(ind, ind, LDA)], B_arr + k + ind * LDB, EIGEN_AVX_MAX_NUM_ROW, bK, LDA, LDB);
|
|
}
|
|
if (i + EIGEN_AVX_MAX_NUM_ROW < M_) {
|
|
/**
|
|
* For the GEMM updates, we want "K" (K=i+8 in this case) to be large as soon as possible
|
|
* to reuse the accumulators in GEMM as much as possible. So we only update 8xbK blocks of
|
|
* B as follows:
|
|
*
|
|
* A B
|
|
* __
|
|
* |__|__ |__|
|
|
* |__|__|__ |__|
|
|
* |__|__|__|__ |__|
|
|
* |********|__| |**|
|
|
*/
|
|
EIGEN_IF_CONSTEXPR(isBRowMajor) {
|
|
int64_t indA_i = isFWDSolve ? i + EIGEN_AVX_MAX_NUM_ROW : M - (i + 2 * EIGEN_AVX_MAX_NUM_ROW);
|
|
int64_t indA_j = isFWDSolve ? 0 : M - (i + EIGEN_AVX_MAX_NUM_ROW);
|
|
int64_t indB_i = isFWDSolve ? 0 : M - (i + EIGEN_AVX_MAX_NUM_ROW);
|
|
int64_t indB_i2 = isFWDSolve ? i + EIGEN_AVX_MAX_NUM_ROW : M - (i + 2 * EIGEN_AVX_MAX_NUM_ROW);
|
|
gemmKernel<Scalar, isARowMajor, isBRowMajor, false, false>(
|
|
&A_arr[idA<isARowMajor>(indA_i, indA_j, LDA)], B_arr + k + indB_i * LDB, B_arr + k + indB_i2 * LDB,
|
|
EIGEN_AVX_MAX_NUM_ROW, bK, i + EIGEN_AVX_MAX_NUM_ROW, LDA, LDB, LDB);
|
|
}
|
|
else {
|
|
if (offsetBTemp + EIGEN_AVX_MAX_NUM_ROW * LDT > sizeBTemp) {
|
|
/**
|
|
* Similar idea as mentioned above, but here we are limited by the number of updated values of B
|
|
* that can be stored (row-major) in B_temp.
|
|
*
|
|
* If there is not enough space to store the next batch of 8xbK of B in B_temp, we call GEMM
|
|
* update and partially update the remaining old values of B which depends on the new values
|
|
* of B stored in B_temp. These values are then no longer needed and can be overwritten.
|
|
*/
|
|
int64_t indA_i = isFWDSolve ? i + EIGEN_AVX_MAX_NUM_ROW : 0;
|
|
int64_t indA_j = isFWDSolve ? gemmOff : M - (i + EIGEN_AVX_MAX_NUM_ROW);
|
|
int64_t indB_i = isFWDSolve ? i + EIGEN_AVX_MAX_NUM_ROW : 0;
|
|
int64_t offB_1 = isFWDSolve ? 0 : sizeBTemp - offsetBTemp;
|
|
gemmKernel<Scalar, isARowMajor, isBRowMajor, false, false>(
|
|
&A_arr[idA<isARowMajor>(indA_i, indA_j, LDA)], B_temp + offB_1, B_arr + indB_i + (k)*LDB,
|
|
M - (i + EIGEN_AVX_MAX_NUM_ROW), bK, i + EIGEN_AVX_MAX_NUM_ROW - gemmOff, LDA, LDT, LDB);
|
|
offsetBTemp = 0;
|
|
gemmOff = i + EIGEN_AVX_MAX_NUM_ROW;
|
|
} else {
|
|
/**
|
|
* If there is enough space in B_temp, we only update the next 8xbK values of B.
|
|
*/
|
|
int64_t indA_i = isFWDSolve ? i + EIGEN_AVX_MAX_NUM_ROW : M - (i + 2 * EIGEN_AVX_MAX_NUM_ROW);
|
|
int64_t indA_j = isFWDSolve ? gemmOff : M - (i + EIGEN_AVX_MAX_NUM_ROW);
|
|
int64_t indB_i = isFWDSolve ? i + EIGEN_AVX_MAX_NUM_ROW : M - (i + 2 * EIGEN_AVX_MAX_NUM_ROW);
|
|
int64_t offB_1 = isFWDSolve ? 0 : sizeBTemp - offsetBTemp;
|
|
gemmKernel<Scalar, isARowMajor, isBRowMajor, false, false>(
|
|
&A_arr[idA<isARowMajor>(indA_i, indA_j, LDA)], B_temp + offB_1, B_arr + indB_i + (k)*LDB,
|
|
EIGEN_AVX_MAX_NUM_ROW, bK, i + EIGEN_AVX_MAX_NUM_ROW - gemmOff, LDA, LDT, LDB);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Handle M remainder..
|
|
int64_t bM = M - M_;
|
|
if (bM > 0) {
|
|
if (M_ > 0) {
|
|
EIGEN_IF_CONSTEXPR(isBRowMajor) {
|
|
int64_t indA_i = isFWDSolve ? M_ : 0;
|
|
int64_t indA_j = isFWDSolve ? 0 : bM;
|
|
int64_t indB_i = isFWDSolve ? 0 : bM;
|
|
int64_t indB_i2 = isFWDSolve ? M_ : 0;
|
|
gemmKernel<Scalar, isARowMajor, isBRowMajor, false, false>(
|
|
&A_arr[idA<isARowMajor>(indA_i, indA_j, LDA)], B_arr + k + indB_i * LDB, B_arr + k + indB_i2 * LDB, bM,
|
|
bK, M_, LDA, LDB, LDB);
|
|
}
|
|
else {
|
|
int64_t indA_i = isFWDSolve ? M_ : 0;
|
|
int64_t indA_j = isFWDSolve ? gemmOff : bM;
|
|
int64_t indB_i = isFWDSolve ? M_ : 0;
|
|
int64_t offB_1 = isFWDSolve ? 0 : sizeBTemp - offsetBTemp;
|
|
gemmKernel<Scalar, isARowMajor, isBRowMajor, false, false>(&A_arr[idA<isARowMajor>(indA_i, indA_j, LDA)],
|
|
B_temp + offB_1, B_arr + indB_i + (k)*LDB, bM, bK,
|
|
M_ - gemmOff, LDA, LDT, LDB);
|
|
}
|
|
}
|
|
EIGEN_IF_CONSTEXPR(!isBRowMajor) {
|
|
int64_t indA_i = isFWDSolve ? M_ : M - 1 - M_;
|
|
int64_t indB_i = isFWDSolve ? M_ : 0;
|
|
int64_t offB_1 = isFWDSolve ? 0 : (bM - 1) * bkL;
|
|
copyBToRowMajor<Scalar, true, true>(B_arr + indB_i + k * LDB, LDB, bK, B_temp, bkL, bM);
|
|
triSolveKernelLxK<Scalar, isARowMajor, isFWDSolve, isUnitDiag>(&A_arr[idA<isARowMajor>(indA_i, indA_i, LDA)],
|
|
B_temp + offB_1, bM, bkL, LDA, bkL);
|
|
copyBToRowMajor<Scalar, false, true>(B_arr + indB_i + k * LDB, LDB, bK, B_temp, bkL, bM);
|
|
}
|
|
else {
|
|
int64_t ind = isFWDSolve ? M_ : M - 1 - M_;
|
|
triSolveKernelLxK<Scalar, isARowMajor, isFWDSolve, isUnitDiag>(&A_arr[idA<isARowMajor>(ind, ind, LDA)],
|
|
B_arr + k + ind * LDB, bM, bK, LDA, LDB);
|
|
}
|
|
}
|
|
}
|
|
|
|
#if !defined(EIGEN_NO_MALLOC)
|
|
EIGEN_IF_CONSTEXPR(!isBRowMajor) handmade_aligned_free(B_temp);
|
|
#endif
|
|
}
|
|
|
|
// Template specializations of trsmKernelL/R for float/double and inner strides of 1.
|
|
#if (EIGEN_USE_AVX512_TRSM_KERNELS)
|
|
#if (EIGEN_USE_AVX512_TRSM_R_KERNELS)
|
|
template <typename Scalar, typename Index, int Mode, bool Conjugate, int TriStorageOrder, int OtherInnerStride>
|
|
struct trsmKernelR;
|
|
|
|
template <typename Index, int Mode, int TriStorageOrder>
|
|
struct trsmKernelR<float, Index, Mode, false, TriStorageOrder, 1> {
|
|
static void kernel(Index size, Index otherSize, const float *_tri, Index triStride, float *_other, Index otherIncr,
|
|
Index otherStride);
|
|
};
|
|
|
|
template <typename Index, int Mode, int TriStorageOrder>
|
|
struct trsmKernelR<double, Index, Mode, false, TriStorageOrder, 1> {
|
|
static void kernel(Index size, Index otherSize, const double *_tri, Index triStride, double *_other, Index otherIncr,
|
|
Index otherStride);
|
|
};
|
|
|
|
template <typename Index, int Mode, int TriStorageOrder>
|
|
EIGEN_DONT_INLINE void trsmKernelR<float, Index, Mode, false, TriStorageOrder, 1>::kernel(
|
|
Index size, Index otherSize, const float *_tri, Index triStride, float *_other, Index otherIncr,
|
|
Index otherStride) {
|
|
EIGEN_UNUSED_VARIABLE(otherIncr);
|
|
triSolve<float, TriStorageOrder != RowMajor, true, (Mode & Lower) != Lower, (Mode & UnitDiag) != 0>(
|
|
const_cast<float *>(_tri), _other, size, otherSize, triStride, otherStride);
|
|
}
|
|
|
|
template <typename Index, int Mode, int TriStorageOrder>
|
|
EIGEN_DONT_INLINE void trsmKernelR<double, Index, Mode, false, TriStorageOrder, 1>::kernel(
|
|
Index size, Index otherSize, const double *_tri, Index triStride, double *_other, Index otherIncr,
|
|
Index otherStride) {
|
|
EIGEN_UNUSED_VARIABLE(otherIncr);
|
|
triSolve<double, TriStorageOrder != RowMajor, true, (Mode & Lower) != Lower, (Mode & UnitDiag) != 0>(
|
|
const_cast<double *>(_tri), _other, size, otherSize, triStride, otherStride);
|
|
}
|
|
#endif // (EIGEN_USE_AVX512_TRSM_R_KERNELS)
|
|
|
|
// These trsm kernels require temporary memory allocation
|
|
#if (EIGEN_USE_AVX512_TRSM_L_KERNELS)
|
|
template <typename Scalar, typename Index, int Mode, bool Conjugate, int TriStorageOrder, int OtherInnerStride>
|
|
struct trsmKernelL;
|
|
|
|
template <typename Index, int Mode, int TriStorageOrder>
|
|
struct trsmKernelL<float, Index, Mode, false, TriStorageOrder, 1> {
|
|
static void kernel(Index size, Index otherSize, const float *_tri, Index triStride, float *_other, Index otherIncr,
|
|
Index otherStride);
|
|
};
|
|
|
|
template <typename Index, int Mode, int TriStorageOrder>
|
|
struct trsmKernelL<double, Index, Mode, false, TriStorageOrder, 1> {
|
|
static void kernel(Index size, Index otherSize, const double *_tri, Index triStride, double *_other, Index otherIncr,
|
|
Index otherStride);
|
|
};
|
|
|
|
template <typename Index, int Mode, int TriStorageOrder>
|
|
EIGEN_DONT_INLINE void trsmKernelL<float, Index, Mode, false, TriStorageOrder, 1>::kernel(
|
|
Index size, Index otherSize, const float *_tri, Index triStride, float *_other, Index otherIncr,
|
|
Index otherStride) {
|
|
EIGEN_UNUSED_VARIABLE(otherIncr);
|
|
triSolve<float, TriStorageOrder == RowMajor, false, (Mode & Lower) == Lower, (Mode & UnitDiag) != 0>(
|
|
const_cast<float *>(_tri), _other, size, otherSize, triStride, otherStride);
|
|
}
|
|
|
|
template <typename Index, int Mode, int TriStorageOrder>
|
|
EIGEN_DONT_INLINE void trsmKernelL<double, Index, Mode, false, TriStorageOrder, 1>::kernel(
|
|
Index size, Index otherSize, const double *_tri, Index triStride, double *_other, Index otherIncr,
|
|
Index otherStride) {
|
|
EIGEN_UNUSED_VARIABLE(otherIncr);
|
|
triSolve<double, TriStorageOrder == RowMajor, false, (Mode & Lower) == Lower, (Mode & UnitDiag) != 0>(
|
|
const_cast<double *>(_tri), _other, size, otherSize, triStride, otherStride);
|
|
}
|
|
#endif // EIGEN_USE_AVX512_TRSM_L_KERNELS
|
|
#endif // EIGEN_USE_AVX512_TRSM_KERNELS
|
|
} // namespace internal
|
|
} // namespace Eigen
|
|
#endif // EIGEN_CORE_ARCH_AVX512_TRSM_KERNEL_H
|