Files
deeplearning4j--deeplearning4j/libnd4j/include/helpers/cpu/MmulHelper.cpp
T
2026-07-13 12:47:05 +08:00

664 lines
25 KiB
C++

/* ******************************************************************************
*
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0.
*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
* 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.
*
* SPDX-License-Identifier: Apache-2.0
******************************************************************************/
//
// @author raver119@gmail.com
// @author Yurii Shyrma (iuriish@yahoo.com)
//
#include "../MmulHelper.h"
#include <array/NDArrayFactory.h>
#include <exceptions/datatype_exception.h>
#include <execution/Threads.h>
#include <helpers/BlasHelper.h>
#include <helpers/ShapeUtils.h>
namespace sd {
//////////////////////////////////////////////////////////////////////////////
// MXK x KxN = MxN -> actual sequence of axes doesn't matter
template <typename T1, typename T2, typename T3>
static void usualGemm(NDArray* vA, NDArray* vB, NDArray* vC, const int aMaxis, const int aKaxis,
const int bKaxis, const int bNaxis, const int cMaxis, const int cNaxis, const double alpha,
const double beta) {
T1* A = vA->bufferAsT<T1>();
T2* B = vB->bufferAsT<T2>();
T3* C = vC->bufferAsT<T3>();
if (A == nullptr) {
THROW_EXCEPTION("usualGemm: A is nullptr");
}
if (B == nullptr) {
THROW_EXCEPTION("usualGemm: B is nullptr");
}
if (C == nullptr) {
THROW_EXCEPTION("usualGemm: C is nullptr");
}
const T3 alphaZ = static_cast<T3> (alpha);
const T3 betaZ = static_cast<T3>(beta);
const bool betaPresent = beta;
const sd::LongType* aShapeInfo = vA->shapeInfo();
const sd::LongType* bShapeInfo = vB->shapeInfo();
const sd::LongType* cShapeInfo = vC->shapeInfo();
const int aRank = vA->rankOf();
const int bRank = vB->rankOf();
const int cRank = vC->rankOf();
const sd::LongType cLen = vC->lengthOf();
const int K = vA->sizeAt(aKaxis);
sd::LongType *cShape = shape::shapeOf(cShapeInfo);
sd::LongType *aShape = shape::shapeOf(aShapeInfo);
sd::LongType *bShape = shape::shapeOf(bShapeInfo);
sd::LongType *aStride = shape::stride(aShapeInfo);
sd::LongType *bStride = shape::stride(bShapeInfo);
sd::LongType *cStride = shape::stride(cShapeInfo);
auto func = PRAGMA_THREADS_FOR {
std::vector<sd::LongType> aCoords(aRank), bCoords(bRank), cCoords(cRank);
for (auto i = start; i < stop; i++) {
// evaluate C coordinates
INDEX2COORDS(i, cRank, shape::shapeOf(cShapeInfo), cCoords.data());
// evaluate A coordinates
aCoords[aMaxis] = cCoords[cMaxis];
aCoords[aKaxis] = 0;
// evaluate B coordinates
bCoords[bKaxis] = 0;
bCoords[bNaxis] = cCoords[cNaxis];
sd::LongType aOffset, bOffset, cOffset;
COORDS2INDEX(aRank, aStride, aCoords.data(), aOffset);
COORDS2INDEX(bRank, bStride, bCoords.data(), bOffset);
T3 val = A[aOffset] * B[bOffset]; // first iteration
for (int j = 1; j < K; j++) { // rest iterations
aOffset += aStride[aKaxis];
bOffset += bStride[bKaxis];
val += A[aOffset] * B[bOffset];
}
COORDS2INDEX(cRank, cStride, cCoords.data(), cOffset);
if (betaPresent) {
C[cOffset] = alphaZ * val + betaZ * C[cOffset];
} else {
C[cOffset] = alphaZ * val;
}
}
};
samediff::Threads::parallel_tad(func, 0, cLen);
}
//////////////////////////////////////////////////////////////////////////////
// MXN x N = M -> actual sequence of {M,N} axes doesn't matter
template <typename T1, typename T2, typename T3>
static void usualGemv( NDArray* vA, NDArray* vX, NDArray* vY, const int incx, const int incy,
const int aMaxis, const double alpha, const double beta) {
T1* A = vA->bufferAsT<T1>();
T2* X = vX->bufferAsT<T2>();
T3* Y = vY->bufferAsT<T3>();
const T3 alphaZ = static_cast<T3>(alpha);
const T3 betaZ = static_cast<T3>(beta);
const bool betaPersent = beta;
const sd::LongType* aShapeInfo = vA->shapeInfo();
const sd::LongType* xShapeInfo = vX->shapeInfo();
const sd::LongType* yShapeInfo = vY->shapeInfo();
const int N = vX->lengthOf();
const int M = vY->lengthOf();
const auto aMstride = vA->strideAt(aMaxis);
const auto aNstride = vA->strideAt(aMaxis == 0 ? 1 : 0);
auto func = PRAGMA_THREADS_FOR {
for (auto i = start; i < stop; ++i) {
// evaluate offsets
auto aOffset = i * aMstride;
auto xOffset = 0;
T3 val = A[aOffset] * X[xOffset]; // first iteration
for (int j = 1; j < N; ++j) { // rest iterations
aOffset += aNstride;
xOffset += incx;
val = val + A[aOffset] * X[xOffset];
}
auto yOffset = i * incy;
if (betaPersent)
Y[yOffset] = alphaZ * val + betaZ * Y[yOffset];
else
Y[yOffset] = alphaZ * val;
}
};
samediff::Threads::parallel_tad(func, 0, M);
}
//////////////////////////////////////////////////////////////////////////////
// (X*Y) = Z[0]
template <typename T1, typename T2, typename T3>
static void usualDot(const sd::LongType length, const double alpha, const void* vX, const sd::LongType incx,
const void* vY, const sd::LongType incy, const double beta, void* vZ) {
T1* X = reinterpret_cast<T1*>(const_cast<void*>(vX));
T2* Y = reinterpret_cast<T2*>(const_cast<void*>(vY));
T3* Z = reinterpret_cast<T3*>(vZ);
T3 alphaZ(alpha), betaZ(beta);
const bool betaPersent = beta;
T3 sum = static_cast<T3>(0);
auto func = PRAGMA_THREADS_FOR {
for (sd::LongType i = start; i < stop; ++i) {
sum += X[i * incx] * Y[i * incy];
}
};
samediff::Threads::parallel_for(func, 0, length);
if (betaPersent)
*Z = alphaZ * sum + betaZ * *Z;
else
*Z = alphaZ * sum;
}
//////////////////////////////////////////////////////////////////////////////
// MXK x KxN = MxN
NDArray* MmulHelper::mmulMxM( NDArray* A, NDArray* B, NDArray* C, const double alpha, const double beta,
const char outOrder) {
auto M = A->sizeAt(0);
auto K = A->sizeAt(1);
auto N = B->sizeAt(1);
if (C != nullptr && C->rankOf() != 2) {
std::string errorMessage = "MmulHelper::mmulMxM: rank of C array should be equal to 2, but got " +
std::to_string(C->rankOf()) + ". ";
errorMessage += "C datatype: " + DataTypeUtils::asString(C->dataType());
THROW_EXCEPTION(errorMessage.c_str());
}
if (B->sizeAt(0) != K) {
std::string errorMessage = "MmulHelper::mmulMxM: B array should have the same number of rows as A has columns. ";
errorMessage += "A columns: " + std::to_string(K) + ", ";
errorMessage += "B rows: " + std::to_string(B->sizeAt(0));
THROW_EXCEPTION(errorMessage.c_str());
}
if (C != nullptr && C->sizeAt(0) != M) {
std::string errorMessage = "MmulHelper::mmulMxM: C array should have the same number of rows as A. ";
errorMessage += "A rows: " + std::to_string(M) + ", ";
errorMessage += "C rows: " + std::to_string(C->sizeAt(0));
THROW_EXCEPTION(errorMessage.c_str());}
if (C != nullptr && C->sizeAt(1) != N) {
std::string errorMessage = "MmulHelper::mmulMxM: C array should have the same number of columns as B. ";
errorMessage += "B columns: " + std::to_string(N) + ", ";
errorMessage += "C columns: " + std::to_string(C->sizeAt(1));
THROW_EXCEPTION(errorMessage.c_str());
}
if (C == nullptr) {
std::vector<sd::LongType> shape = {M,N};
C = new NDArray(outOrder, shape, DataTypeUtils::pickPairwiseResultType(A->dataType(), B->dataType()),
A->getContext());
}
if (C->isEmpty()) return C;
const auto aType = A->dataType();
const auto bType = B->dataType();
const auto cType = C->dataType();
const bool AB(aType == bType), AC(aType == cType), ABC(AB && AC);
const bool hasGemm = BlasHelper::getInstance().hasGEMM(aType);
const bool typeDouble = hasGemm && ABC && aType == DataType::DOUBLE;
const bool typeFloat = hasGemm && ABC && aType == DataType::FLOAT32;
if ((!typeFloat && !typeDouble) || !Environment::getInstance().isEnableBlas()) {
BUILD_SINGLE_SELECTOR_THRICE(aType, usualGemm, (A, B, C, 0, 1, 0, 1, 0, 1, alpha, beta), SD_NUMERIC_TYPES);
} else {
std::vector<NDArray*> toDelete;
NDArray *pA(const_cast<NDArray*>(A)), *pB(const_cast<NDArray*>(B)), *pC(const_cast<NDArray*>(C));
bool aMcont = M == 1 || A->strideAt(0) == 1;
bool aKcont = K == 1 || A->strideAt(1) == 1;
bool bKcont = K == 1 || B->strideAt(0) == 1;
bool bNcont = N == 1 || B->strideAt(1) == 1;
bool cMcont = M == 1 || C->strideAt(0) == 1;
bool cNcont = N == 1 || C->strideAt(1) == 1;
if (!aMcont && !aKcont) {
pA = A->dup('f', false);
toDelete.push_back(pA);
aMcont = true;
}
if (!bKcont && !bNcont) {
pB = B->dup('f', false);
toDelete.push_back(pB);
bKcont = true;
}
if (!cMcont && !cNcont) {
pC = C->dup('f', false);
toDelete.push_back(pC);
cMcont = true;
}
const CBLAS_ORDER blasOrder = cMcont ? CblasColMajor : CblasRowMajor;
const bool transA = (!aMcont && cMcont) || (aMcont && !cMcont);
const bool transB = (!bKcont && cMcont) || (bKcont && !cMcont);
const CBLAS_TRANSPOSE transAblas = transA ? CblasTrans : CblasNoTrans;
const CBLAS_TRANSPOSE transBblas = transB ? CblasTrans : CblasNoTrans;
const int lda = (aMcont && aKcont) ? M : !aMcont ? pA->strideAt(0) : pA->strideAt(1);
const int ldb = (bKcont && bNcont) ? K : !bKcont ? pB->strideAt(0) : pB->strideAt(1);
const int ldc = (cMcont && cNcont) ? M : !cMcont ? pC->strideAt(0) : pC->strideAt(1);
// Acquire BLAS lock to prevent OpenBLAS TLS corruption and race conditions
// This serializes external BLAS calls while allowing OpenBLAS to use multiple threads internally
auto blasLock = BlasHelper::getInstance().lockBlas();
if (typeFloat) {
BlasHelper::getInstance().sgemm()(blasOrder, transAblas, transBblas, M, N, K, (float)alpha,
pA->bufferAsT<float>(), lda, pB->bufferAsT<float>(), ldb, (float)beta,
pC->bufferAsT<float>(), ldc);
} else if (typeDouble) {
BlasHelper::getInstance().dgemm()(blasOrder, transAblas, transBblas, M, N, K, (double)alpha,
pA->bufferAsT<double>(), lda, pB->bufferAsT<double>(), ldb, (double)beta,
pC->bufferAsT<double>(), ldc);
}
if (pC != C) {
C->assign(pC);
}
for (auto* arr : toDelete) {
delete arr;
}
}
return C;
}
////////////////////////////////////////////////////////////////////////////
// MXN x N = M
NDArray* MmulHelper::mmulMxV( NDArray* A, NDArray* X, sd::NDArray* Y, const double alpha, const double beta,
const char outOrder) {
if (X->dataType() != A->dataType()) {
std::string errorMessage;
errorMessage = "mmulMxV expects all data types to be the same";
errorMessage += "A: " + DataTypeUtils::asString(A->dataType());
errorMessage += "X: " + DataTypeUtils::asString(X->dataType());
THROW_EXCEPTION(errorMessage.c_str());
}
if (Y != nullptr && X->dataType() != Y->dataType()) {
std::string errorMessage;
errorMessage = "mmulMxV expects all data types to be the same";
errorMessage += "X: " + DataTypeUtils::asString(X->dataType());
errorMessage += "Y: " + DataTypeUtils::asString(Y->dataType());
THROW_EXCEPTION(errorMessage.c_str());
}
sd::LongType xLenDim, yLenDim(0);
if (A->rankOf() != 2) THROW_EXCEPTION("MmulHelper::mmulMxV: rank of A array is not equal 2 !");
if (!shape::isCommonVector(X->shapeInfo(), xLenDim))
THROW_EXCEPTION("MmulHelper::mmulMxV: X array must be vector !");
const auto M = A->sizeAt(0);
const auto N = A->sizeAt(1);
if (Y != nullptr && !shape::isCommonVector(Y->shapeInfo(), yLenDim))
THROW_EXCEPTION("MmulHelper::mmulMxV: Y array must be vector !");
if (X->lengthOf() != N) THROW_EXCEPTION("MmulHelper::mmulMxV: X vector has wrong length !");
if (Y != nullptr && Y->lengthOf() != M) THROW_EXCEPTION("MmulHelper::mmulMxV: Y array has wrong length !");
if (Y == nullptr) {
std::vector<sd::LongType> shape = {M};
Y = new NDArray(outOrder,shape, DataTypeUtils::pickPairwiseResultType(A->dataType(), X->dataType()),
A->getContext());
}
if (Y->isEmpty()) return Y;
const int incx = X->stridesOf()[xLenDim];
const int incy = Y->stridesOf()[yLenDim];
const auto aType = A->dataType();
const auto xType = X->dataType();
const auto yType = Y->dataType();
const bool AX(aType == xType), AY(aType == yType), AXY(AX && AY);
const bool hasGemv = BlasHelper::getInstance().hasGEMV(aType);
const bool typeDouble = hasGemv && AXY && aType == DataType::DOUBLE;
const bool typeFloat = hasGemv && AXY && aType == DataType::FLOAT32;
if ((!typeDouble && !typeFloat) || !Environment::getInstance().isEnableBlas()) {
BUILD_SINGLE_SELECTOR_THRICE(aType, usualGemv, (A, X, Y, incx, incy, 0, alpha, beta), SD_NUMERIC_TYPES);
} else {
NDArray* pA(const_cast<NDArray*>(A));
bool aMcont = M == 1 || A->strideAt(0) == 1;
bool aNcont = N == 1 || A->strideAt(1) == 1;
if (!aMcont && !aNcont) {
pA = A->dup('f', false); // dup() already returns NDArray*, no need for new
aMcont = true;
}
const CBLAS_ORDER blasOrder = aMcont ? CblasColMajor : CblasRowMajor;
const int lda = (aMcont && aNcont) ? M : !aMcont ? pA->strideAt(0) : pA->strideAt(1);
// Acquire BLAS lock to prevent OpenBLAS TLS corruption and race conditions
auto blasLock = BlasHelper::getInstance().lockBlas();
// choose appropriate cuda gemm api depending on data types
if (typeDouble) {
BlasHelper::getInstance().dgemv()(blasOrder, CblasNoTrans, M, N, alpha, (double*)pA->buffer(), lda,
(double*)X->buffer(), incx, beta, (double*)Y->buffer(), incy);
} else if (typeFloat) {
BlasHelper::getInstance().sgemv()(blasOrder, CblasNoTrans, M, N, (float)alpha, (float*)pA->buffer(), lda,
(float*)X->buffer(), incx, (float)beta, (float*)Y->buffer(), incy);
}
// Clean up duplicated array
if (pA != A) {
delete pA;
}
}
return Y;
}
////////////////////////////////////////////////////////////////////////////
// (X * Y) = Z[0]
NDArray* MmulHelper::dot(NDArray* X, NDArray* Y, sd::NDArray* Z, const double alpha, const double beta) {
if (X->dataType() != Y->dataType()) {
std::string errorMessage = "Dot expects all data types to be the same. ";
errorMessage += "X datatype: " + DataTypeUtils::asString(X->dataType()) + ", ";
errorMessage += "Y datatype: " + DataTypeUtils::asString(Y->dataType());
THROW_EXCEPTION(errorMessage.c_str());
}
if (Z != nullptr && X->dataType() != Z->dataType()) {
std::string errorMessage = "Dot expects all data types to be the same. ";
errorMessage += "X datatype: " + DataTypeUtils::asString(X->dataType()) + ", ";
errorMessage += "Z datatype: " + DataTypeUtils::asString(Z->dataType());
THROW_EXCEPTION(errorMessage.c_str());
}
sd::LongType xLenDim(0), yLenDim(0);
if (!shape::isCommonVector(X->shapeInfo(), xLenDim)) {
std::string errorMessage = "MmulHelper::dot: X array must be a vector, but its shape is: ";
for (int i = 0; i < X->rankOf(); ++i) {
errorMessage += std::to_string(X->sizeAt(i));
if (i < X->rankOf() - 1) errorMessage += "x";
}
THROW_EXCEPTION(errorMessage.c_str());
}
if (!shape::isCommonVector(Y->shapeInfo(), yLenDim)) {
std::string errorMessage = "MmulHelper::dot: Y array must be a vector, but its shape is: ";
for (int i = 0; i < Y->rankOf(); ++i) {
errorMessage += std::to_string(Y->sizeAt(i));
if (i < Y->rankOf() - 1) errorMessage += "x";
}
THROW_EXCEPTION(errorMessage.c_str());
}
if (Z != nullptr && Z->lengthOf() > 1) {
std::string errorMessage = "MmulHelper::dot: Z array must be a scalar, but it has length " + std::to_string(Z->lengthOf());
THROW_EXCEPTION(errorMessage.c_str());
}
const auto length = X->lengthOf();
if (Y->lengthOf() != length) {
std::string errorMessage = "MmulHelper::dot: lengths of input vectors are different! ";
errorMessage += "X length: " + std::to_string(X->lengthOf()) + ", ";
errorMessage += "Y length: " + std::to_string(Y->lengthOf());
THROW_EXCEPTION(errorMessage.c_str());
}
if (Z == nullptr)
Z = new NDArray(DataTypeUtils::pickPairwiseResultType(X->dataType(), Y->dataType()), X->getContext());
const sd::LongType incx = X->stridesOf()[xLenDim];
const sd::LongType incy = Y->stridesOf()[yLenDim];
const auto xType = X->dataType();
const auto yType = Y->dataType();
const auto zType = Z->dataType();
BUILD_SINGLE_SELECTOR_THRICE(
xType, usualDot, (length, alpha, X->buffer(), incx, Y->buffer(), incy, beta, Z->buffer()), SD_NUMERIC_TYPES);
return Z;
}
//////////////////////////////////////////////////////////////////////////////
// [bS,M,K] x [bS,K,N] = [bS,M,N]
// [bS,M,K] x [K,N] = [bS,M,N]
// [M,K] x [bS,K,N] = [bS,M,N]
// bS could stand for several axes
template <typename T1, typename T2, typename T3>
static void batchedGemm(NDArray* vA, NDArray* vB, NDArray* vC, const sd::LongType* aBatchDims,
const sd::LongType* bBatchDims, const sd::LongType* cBatchDims, sd::LongType aMaxis, sd::LongType aKaxis,
sd::LongType bKaxis, sd::LongType bNaxis, sd::LongType cMaxis, sd::LongType cNaxis, const double alpha, const double beta) {
T1* A = vA->bufferAsT<T1>();
T2* B = vB->bufferAsT<T2>();
T3* C = vC->bufferAsT<T3>();
const T3 alphaZ = static_cast<T3>(alpha);
const T3 betaZ = static_cast<T3>(beta);
const bool betaPersent = beta;
const sd::LongType* aShapeInfo = vA->shapeInfo();
const sd::LongType* bShapeInfo = vB->shapeInfo();
const sd::LongType* cShapeInfo = vC->shapeInfo();
const sd::LongType aRank = vA->rankOf();
const sd::LongType bRank = vB->rankOf();
const sd::LongType cRank = vC->rankOf();
const sd::LongType cLen = vC->lengthOf();
const sd::LongType K = vA->sizeAt(aKaxis);
sd::LongType *cShape = shape::shapeOf(cShapeInfo);
sd::LongType *aShape = shape::shapeOf(aShapeInfo);
sd::LongType *bShape = shape::shapeOf(bShapeInfo);
sd::LongType *aStride = shape::stride(aShapeInfo);
sd::LongType *bStride = shape::stride(bShapeInfo);
sd::LongType *cStride = shape::stride(cShapeInfo);
auto func = PRAGMA_THREADS_FOR {
std::vector<sd::LongType> aCoords(aRank), bCoords(bRank), cCoords(cRank);
for (sd::LongType i = start; i < stop; ++i) {
// evaluate C coordinates
INDEX2COORDS(i, cRank,cShape, cCoords.data());
// calculate index of current batch
sd::LongType batchInd;
if (cRank > 2) COORDS2INDEX(cRank, cStride, cCoords.data(), batchInd);
// evaluate A coordinates
if (aRank > 2) INDEX2COORDS(batchInd, aRank, aShape, aCoords.data());
aCoords[aMaxis] = cCoords[cMaxis];
aCoords[aKaxis] = 0;
// evaluate B coordinates
if (bRank > 2) INDEX2COORDS(batchInd, bRank, bShape, bCoords.data());
bCoords[bKaxis] = 0;
bCoords[bNaxis] = cCoords[cNaxis];
sd::LongType aOffset, bOffset, cOffset;
COORDS2INDEX(aRank, aStride, aCoords.data(), aOffset);
COORDS2INDEX(bRank, bStride, bCoords.data(), bOffset);
T3 val = A[aOffset] * B[bOffset]; // first iteration
for (int j = 1; j < K; ++j) { // rest iterations
aOffset += aStride[aKaxis];
bOffset += bStride[bKaxis];
val = val + A[aOffset] * B[bOffset];
}
COORDS2INDEX(cRank, cStride, cCoords.data(), cOffset);
if (betaPersent)
C[cOffset] = alphaZ * val + betaZ * C[cOffset];
else
C[cOffset] = alphaZ * val;
}
};
samediff::Threads::parallel_tad(func, 0, cLen);
}
//////////////////////////////////////////////////////////////////////////
NDArray* MmulHelper::mmulNxN( NDArray* A, NDArray* B, NDArray* C, const double alpha, const double beta,
const char outOrder) {
const sd::LongType aRank = A->rankOf();
const sd::LongType bRank = B->rankOf();
auto shapeToString = []( NDArray* arr) {
std::string shape = "[";
for (int i = 0; i < arr->rankOf(); ++i) {
shape += std::to_string(arr->sizeAt(i));
if (i < arr->rankOf() - 1) shape += ",";
}
shape += "]";
return shape;
};
// input ranks validation
if (aRank > bRank && bRank != 2) {
std::string errorMessage = "MmulHelper::mmulNxN: rank of B array should be equal 2, but got " + std::to_string(bRank) +
"! A shape: " + shapeToString(A) + ", B shape: " + shapeToString(B);
THROW_EXCEPTION(errorMessage.c_str());
} else if (bRank > aRank && aRank != 2) {
std::string errorMessage = "MmulHelper::mmulNxN: rank of A array should be equal 2, but got " + std::to_string(aRank) +
"! A shape: " + shapeToString(A) + ", B shape: " + shapeToString(B);
THROW_EXCEPTION(errorMessage.c_str());
} else if (aRank == bRank) {
for (int i = 0; i < aRank - 2; ++i)
if (A->sizeAt(i) != B->sizeAt(i)) {
std::string errorMessage = "MmulHelper::mmulNxN: shapes of A and B arrays are not suitable for matrix multiplication! "
"Mismatch at dimension " + std::to_string(i) + ": A[" + std::to_string(i) + "] = " +
std::to_string(A->sizeAt(i)) + ", B[" + std::to_string(i) + "] = " + std::to_string(B->sizeAt(i)) +
". Full shapes: A " + shapeToString(A) + ", B " + shapeToString(B);
THROW_EXCEPTION(errorMessage.c_str());
}
}
if (A->sizeAt(-1) != B->sizeAt(-2)) {
std::string errorMessage = "MmulHelper::mmulNxN: shapes of A and B arrays are not suitable for matrix multiplication! "
"A's last dimension (" + std::to_string(A->sizeAt(-1)) + ") must match B's second-to-last dimension (" +
std::to_string(B->sizeAt(-2)) + "). Full shapes: A " + shapeToString(A) + ", B " + shapeToString(B);
THROW_EXCEPTION(errorMessage.c_str());
}
// validation of C array
std::vector<sd::LongType> *cExpectedShape = aRank > bRank ? A->getShapeAsVector() : B->getShapeAsVector();
(*cExpectedShape)[cExpectedShape->size() - 2] = A->sizeAt(-2);
(*cExpectedShape)[cExpectedShape->size() - 1] = B->sizeAt(-1);
if (C != nullptr) {
if (!C->isSameShape(*cExpectedShape)) {
std::string errorMessage = "MmulHelper::mmulNxN: shape of C array is not suitable for AxB matrix multiplication! "
"Expected shape: [";
for (size_t i = 0; i < cExpectedShape->size(); ++i) {
errorMessage += std::to_string((*cExpectedShape)[i]);
if (i < cExpectedShape->size() - 1) errorMessage += ",";
}
errorMessage += "], but got: " + shapeToString(C) + ". A shape: " + shapeToString(A) + ", B shape: " + shapeToString(B);
delete cExpectedShape;
THROW_EXCEPTION(errorMessage.c_str());
}
} else {
C = new NDArray(outOrder, *cExpectedShape, B->dataType());
}
if (C->isEmpty()) {
delete cExpectedShape;
return C;
}
const sd::LongType cRank = C->rankOf();
const sd::LongType aMaxis(aRank - 2), aKaxis(aRank - 1), bKaxis(bRank - 2), bNaxis(bRank - 1), cMaxis(cRank - 2),
cNaxis(cRank - 1);
std::vector<sd::LongType> *aBatchDims, *bBatchDims, *cBatchDims;
if (aRank > 2) {
sd::LongType aaxes[2];
aaxes[0] = aMaxis;
aaxes[1] = aKaxis;
aBatchDims = ShapeUtils::evalDimsToExclude(aRank,2,aaxes);
} else {
aBatchDims = new std::vector<sd::LongType>();
}
if (bRank > 2) {
sd::LongType baxes[2];
baxes[0] = bKaxis;
baxes[1] = bNaxis;
bBatchDims = ShapeUtils::evalDimsToExclude(bRank, 2,baxes);
} else {
bBatchDims = new std::vector<sd::LongType>();
}
if (cRank > 2) {
sd::LongType caxes[2];
caxes[0] = cMaxis;
caxes[1] = cNaxis;
cBatchDims = ShapeUtils::evalDimsToExclude(cRank, 2,caxes);
} else {
cBatchDims = new std::vector<sd::LongType>();
}
BUILD_SINGLE_SELECTOR_THRICE(A->dataType(), batchedGemm,
(A, B, C, aBatchDims->data(), bBatchDims->data(), cBatchDims->data(), aMaxis, aKaxis,
bKaxis, bNaxis, cMaxis
, cNaxis, alpha, beta),
SD_NUMERIC_TYPES);
if(aBatchDims != nullptr)
delete aBatchDims;
if(bBatchDims != nullptr)
delete bBatchDims;
if(cBatchDims != nullptr)
delete cBatchDims;
delete cExpectedShape;
return C;
}
} // namespace sd