Files
2026-07-13 12:47:05 +08:00

777 lines
30 KiB
Plaintext

/*
* ******************************************************************************
* *
* *
* * 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 <cublas_v2.h>
#include <exceptions/cuda_exception.h>
#include <helpers/PointersManager.h>
#include <helpers/ShapeUtils.h>
#include <ops/specials_cuda.h>
#include <numeric>
#include "../MmulHelper.h"
#include "execution/cuda/LaunchDims.h"
namespace sd {
//////////////////////////////////////////////////////////////////////////////
// MXK x KxN = MxN -> actual sequence of axes doesn't matter
template <typename T1, typename T2, typename T3>
static SD_KERNEL void usualCudaGemm(const void* vA, const LongType* aShapeInfo, const void* vB,
const LongType* bShapeInfo, void* vC, const LongType* cShapeInfo,
const int aMaxis, const int aKaxis, const int bKaxis, const int bNaxis,
const int cMaxis, const int cNaxis, const double alpha, const double beta) {
// Cache shape information in shared memory
__shared__ LongType K;
__shared__ LongType cLen;
__shared__ LongType totalThreads;
__shared__ bool betaPresent;
__shared__ T3 alphaZ;
__shared__ T3 betaZ;
__shared__ const LongType* aShape;
__shared__ const LongType* bShape;
__shared__ const LongType* cShape;
__shared__ const LongType* aStride;
__shared__ const LongType* bStride;
__shared__ const LongType* cStride;
__shared__ LongType aRank;
__shared__ LongType bRank;
__shared__ LongType cRank;
__shared__ LongType* coords;
if (threadIdx.x == 0) {
extern __shared__ unsigned char shmem[];
coords = reinterpret_cast<LongType*>(shmem);
// Cache all shape information at start
aRank = shape::rank(aShapeInfo);
bRank = shape::rank(bShapeInfo);
cRank = shape::rank(cShapeInfo);
aShape = shape::shapeOf(aShapeInfo);
bShape = shape::shapeOf(bShapeInfo);
cShape = shape::shapeOf(cShapeInfo);
aStride = shape::stride(aShapeInfo);
bStride = shape::stride(bShapeInfo);
cStride = shape::stride(cShapeInfo);
cLen = shape::length(cShapeInfo);
K = aShape[aKaxis];
betaPresent = beta != 0;
totalThreads = gridDim.x * blockDim.x;
alphaZ = alpha;
betaZ = beta;
}
__syncthreads();
const T1* A = reinterpret_cast<const T1*>(vA);
const T2* B = reinterpret_cast<const T2*>(vB);
T3* C = reinterpret_cast<T3*>(vC);
auto aCoords = coords + threadIdx.x * 6; // 6 = (aRank + bRank + cRank)
auto bCoords = aCoords + 2;
auto cCoords = bCoords + 2;
const auto tid = blockIdx.x * blockDim.x + threadIdx.x;
for (LongType i = tid; i < cLen; i += totalThreads) {
// evaluate C coordinates
INDEX2COORDS(i, cRank, cShape, cCoords);
// evaluate A coordinates
aCoords[aMaxis] = cCoords[cMaxis];
aCoords[aKaxis] = 0;
// evaluate B coordinates
bCoords[bKaxis] = 0;
bCoords[bNaxis] = cCoords[cNaxis];
LongType aOffset, bOffset, cOffset;
COORDS2INDEX(aRank, aStride, aCoords, aOffset);
COORDS2INDEX(bRank, bStride, bCoords, bOffset);
T3 val = A[aOffset] * B[bOffset]; // first iteration
for (LongType j = 1; j < K; ++j) { // rest iterations
aOffset += aStride[aKaxis];
bOffset += bStride[bKaxis];
val = val + A[aOffset] * B[bOffset];
}
COORDS2INDEX(cRank, cStride, cCoords, cOffset);
if (betaPresent)
C[cOffset] = alphaZ * val + betaZ * C[cOffset];
else
C[cOffset] = alphaZ * val;
}
}
////////////////////////////////////////////////////////////////////////
template <typename T1, typename T2, typename T3>
SD_HOST static void usualGemm(const int blocksPerGrid, const int threadsPerBlock, const int sharedMem,
cudaStream_t* stream, const void* vA, const LongType* aShapeInfo, const void* vB,
const LongType* bShapeInfo, void* vC, const LongType* cShapeInfo,
const int aMaxis, const int aKaxis, const int bKaxis, const int bNaxis, const int cMaxis,
const int cNaxis, const double alpha, const double beta) {
usualCudaGemm<T1, T2, T3><<<blocksPerGrid, threadsPerBlock, sharedMem, *stream>>>(
vA, aShapeInfo, vB, bShapeInfo, vC, cShapeInfo, aMaxis, aKaxis, bKaxis, bNaxis, cMaxis, cNaxis, alpha, beta);
DebugHelper::checkGlobalErrorCode("MMUL cuda gemv case failed(...) failed");
}
////////////////////////////////////////////////////////////////////////
// MXN x N = M -> actual sequence of {M,N} axes doesn't matter
template <typename T1, typename T2, typename T3>
static SD_KERNEL void usualCudaGemv(const void* vA, const LongType* aShapeInfo, const void* vX,
const LongType* xShapeInfo, void* vY, const LongType* yShapeInfo,
const int incx, const int incy, const int aMaxis, const double alpha,
const double beta) {
// Cache shape information in shared memory
__shared__ LongType M;
__shared__ LongType N;
__shared__ bool betaPresent;
__shared__ LongType totalThreads;
__shared__ LongType aNstride;
__shared__ LongType aMstride;
__shared__ T3 alphaZ;
__shared__ T3 betaZ;
__shared__ const LongType* aShape;
__shared__ const LongType* aStride;
__shared__ LongType aRank;
if (threadIdx.x == 0) {
N = shape::length(xShapeInfo);
M = shape::length(yShapeInfo);
aRank = shape::rank(aShapeInfo);
aShape = shape::shapeOf(aShapeInfo);
aStride = shape::stride(aShapeInfo);
aMstride = aStride[aMaxis];
aNstride = aStride[aMaxis == 0 ? 1 : 0];
totalThreads = gridDim.x * blockDim.x;
betaPresent = beta != 0;
alphaZ = alpha;
betaZ = beta;
}
__syncthreads();
const T1* A = reinterpret_cast<const T1*>(vA);
const T2* X = reinterpret_cast<const T2*>(vX);
T3* Y = reinterpret_cast<T3*>(vY);
const auto tid = blockIdx.x * blockDim.x + threadIdx.x;
for (LongType i = tid; i < M; i += totalThreads) {
// evaluate offsets
auto aOffset = i * aMstride;
auto xOffset = 0;
T3 val = A[aOffset] * X[xOffset]; // first iteration
for (LongType j = 1; j < N; ++j) { // rest iterations
aOffset += aNstride;
xOffset += incx;
val = val + A[aOffset] * X[xOffset];
}
auto yOffset = i * incy;
if (betaPresent)
Y[yOffset] = alphaZ * val + betaZ * Y[yOffset];
else
Y[yOffset] = alphaZ * val;
}
}
////////////////////////////////////////////////////////////////////////
template <typename T1, typename T2, typename T3>
SD_HOST static void usualGemv(const int blocksPerGrid, const int threadsPerBlock, cudaStream_t* stream, const void* vA,
const LongType* aShapeInfo, const void* vX, const LongType* xShapeInfo, void* vY,
const LongType* yShapeInfo, const int incx, const int incy, const int aMaxis,
const double alpha, const double beta) {
usualCudaGemv<T1, T2, T3><<<blocksPerGrid, threadsPerBlock, 512, *stream>>>(
vA, aShapeInfo, vX, xShapeInfo, vY, yShapeInfo, incx, incy, aMaxis, alpha, beta);
DebugHelper::checkGlobalErrorCode("MMUL cuda gemv case failed(...) failed");
}
//////////////////////////////////////////////////////////////////////////////
template <typename T1, typename T2, typename T3>
static SD_KERNEL void usualCudaDot(const LongType length, const double alpha, const void* vX,
const LongType incx, const void* vY, const LongType incy, const double beta,
void* vZ) {
// Cache values in shared memory
__shared__ T3* pairwiseMul;
__shared__ T3 alphaZ;
__shared__ T3 betaZ;
__shared__ bool betaPresent;
if (threadIdx.x == 0) {
extern __shared__ unsigned char shmem[];
pairwiseMul = reinterpret_cast<T3*>(shmem);
alphaZ = alpha;
betaZ = beta;
betaPresent = beta != 0;
}
__syncthreads();
T1* X = reinterpret_cast<T1*>(const_cast<void*>(vX));
T2* Y = reinterpret_cast<T2*>(const_cast<void*>(vY));
T3* Z = reinterpret_cast<T3*>(vZ);
const int tid = blockIdx.x * blockDim.x + threadIdx.x;
if (tid < length) {
pairwiseMul[tid] = X[tid * incx] * Y[tid * incy];
}
__syncthreads();
if (tid == 0) {
T3 sum = static_cast<T3>(0);
for (LongType i = 0; i < length; ++i) {
sum = sum + pairwiseMul[i];
}
if (betaPresent)
*Z = alphaZ * sum + betaZ * *Z;
else
*Z = alphaZ * sum;
}
}
////////////////////////////////////////////////////////////////////////
template <typename T1, typename T2, typename T3>
SD_HOST static void usualDot(const dim3& launchDims, cudaStream_t* stream,
const LongType length, const double alpha, const void* vX, const LongType incx,
const void* vY, const LongType incy, const double beta, void* vZ) {
usualCudaDot<T1, T2, T3><<<launchDims.x, launchDims.y, launchDims.z, *stream>>>(
length, alpha, vX, incx, vY, incy, beta, vZ);
DebugHelper::checkGlobalErrorCode("concat dot failed(...) failed");
}
//////////////////////////////////////////////////////////////////////////////
// [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 SD_KERNEL void batchedCudaGemm(const void* vA, const LongType* aShapeInfo, const void* vB,
const LongType* bShapeInfo, void* vC, const LongType* cShapeInfo,
const LongType* aBatchDims, const LongType* bBatchDims,
const LongType* cBatchDims, const LongType aMaxis, const LongType aKaxis,
const LongType bKaxis, const LongType bNaxis, const LongType cMaxis,
const LongType cNaxis, const double alpha, const double beta) {
// Cache shape information in shared memory
__shared__ struct {
bool betaPresent;
LongType aRank, bRank, cRank, K;
LongType cLen, totalThreads;
T3 alphaZ, betaZ;
const LongType* aShape;
const LongType* bShape;
const LongType* cShape;
const LongType* aStride;
const LongType* bStride;
const LongType* cStride;
LongType* coords;
} shared;
if (threadIdx.x == 0) {
extern __shared__ unsigned char shmem[];
shared.coords = reinterpret_cast<LongType*>(shmem);
shared.cLen = shape::length(cShapeInfo);
shared.aRank = shape::rank(aShapeInfo);
shared.bRank = shape::rank(bShapeInfo);
shared.cRank = shape::rank(cShapeInfo);
shared.aShape = shape::shapeOf(aShapeInfo);
shared.bShape = shape::shapeOf(bShapeInfo);
shared.cShape = shape::shapeOf(cShapeInfo);
shared.aStride = shape::stride(aShapeInfo);
shared.bStride = shape::stride(bShapeInfo);
shared.cStride = shape::stride(cShapeInfo);
shared.K = shared.aShape[aKaxis];
shared.betaPresent = beta != 0;
shared.totalThreads = gridDim.x * blockDim.x;
shared.alphaZ = alpha;
shared.betaZ = beta;
}
__syncthreads();
const T1* A = reinterpret_cast<const T1*>(vA);
const T2* B = reinterpret_cast<const T2*>(vB);
T3* C = reinterpret_cast<T3*>(vC);
auto aCoords = shared.coords + threadIdx.x * (shared.aRank + shared.bRank + shared.cRank);
auto bCoords = aCoords + shared.aRank;
auto cCoords = bCoords + shared.bRank;
const auto tid = blockIdx.x * blockDim.x + threadIdx.x;
for (LongType i = tid; i < shared.cLen; i += shared.totalThreads) {
// evaluate C coordinates
INDEX2COORDS(i, shared.cRank, shared.cShape, cCoords);
// calculate index of current batch
LongType batchInd;
if (cBatchDims != nullptr) {
COORDS2INDEX(shared.cRank - 2, shared.cStride, cCoords, batchInd);
}
// evaluate A coordinates
if (aBatchDims != nullptr) {
INDEX2COORDS(batchInd, shared.aRank - 2, shared.aShape, aCoords);
}
aCoords[aMaxis] = cCoords[cMaxis];
aCoords[aKaxis] = 0;
// evaluate B coordinates
if (bBatchDims != nullptr) {
INDEX2COORDS(batchInd, shared.bRank - 2, shared.bShape, bCoords);
}
bCoords[bKaxis] = 0;
bCoords[bNaxis] = cCoords[cNaxis];
LongType aOffset, bOffset, cOffset;
COORDS2INDEX(shared.aRank, shared.aStride, aCoords, aOffset);
COORDS2INDEX(shared.bRank, shared.bStride, bCoords, bOffset);
T3 val = A[aOffset] * B[bOffset]; // first iteration
for (LongType j = 1; j < shared.K; ++j) { // rest iterations
aOffset += shared.aStride[aKaxis];
bOffset += shared.bStride[bKaxis];
val = val + A[aOffset] * B[bOffset];
}
COORDS2INDEX(shared.cRank, shared.cStride, cCoords, cOffset);
if (shared.betaPresent)
C[cOffset] = shared.alphaZ * val + shared.betaZ * C[cOffset];
else
C[cOffset] = shared.alphaZ * val;
}
}
////////////////////////////////////////////////////////////////////////
template <typename T1, typename T2, typename T3>
SD_HOST static void batchedGemm(const int blocksPerGrid, const int threadsPerBlock, const int sharedMem,
cudaStream_t* stream, const void* vA, const LongType* aShapeInfo, const void* vB,
const LongType* bShapeInfo, void* vC, const LongType* cShapeInfo,
const LongType* aBatchDims, const LongType* bBatchDims, const LongType* cBatchDims,
const LongType aMaxis, const LongType aKaxis, const LongType bKaxis,
const LongType bNaxis, const LongType cMaxis, const LongType cNaxis, const double alpha,
const double beta) {
batchedCudaGemm<T1, T2, T3><<<blocksPerGrid, threadsPerBlock, sharedMem, *stream>>>(
vA, aShapeInfo, vB, bShapeInfo, vC, cShapeInfo, aBatchDims, bBatchDims, cBatchDims, aMaxis, aKaxis, bKaxis,
bNaxis, cMaxis, cNaxis, alpha, beta);
DebugHelper::checkGlobalErrorCode("batch gemm failed(...) failed");
}
//////////////////////////////////////////////////////////////////////////////
// MXK x KxN = MxN
NDArray* MmulHelper::mmulMxM(NDArray* A, NDArray* B, NDArray* C, double alpha, double beta,
const char outOrder) {
if (A->rankOf() != 2) THROW_EXCEPTION("MmulHelper::mmulMxM cuda: rank of A array is not equal 2 !");
if (B->rankOf() != 2) THROW_EXCEPTION("MmulHelper::mmulMxM cuda: rank of B array is not equal 2 !");
const auto M = A->sizeAt(0);
const auto K = A->sizeAt(1);
const auto N = B->sizeAt(1);
if (C != nullptr && C->rankOf() != 2)
THROW_EXCEPTION("MmulHelper::mmulMxM cuda: rank of C array is not equal 2 !");
if (B->sizeAt(0) != K) THROW_EXCEPTION("MmulHelper::mmulMxM cuda: B array has wrong number of rows !");
if (C != nullptr && C->sizeAt(0) != M)
THROW_EXCEPTION("MmulHelper::mmulMxM cuda: C array has wrong number of rows !");
if (C != nullptr && C->sizeAt(1) != N)
THROW_EXCEPTION("MmulHelper::mmulMxM cuda: C array has wrong number of columns !");
std::vector<LongType> cShape = {M, N};
if (C == nullptr)
C = new NDArray(outOrder, cShape, DataTypeUtils::pickPairwiseResultType(A->dataType(), B->dataType()),
A->getContext());
if (C->isEmpty()) return C;
const int major = Environment::getInstance().capabilities()[AffinityManager::currentDeviceId()].first();
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 typeDouble = ABC && aType == DOUBLE;
const bool typeFloat = ABC && aType == FLOAT32;
const bool typeHalf = ABC && aType == HALF && major >= 6;
const bool typeIntFloat = AB && aType == INT8 && cType == FLOAT32 && major >= 6;
const bool typeHalfFloat = AB && aType == HALF && cType == FLOAT32 && major >= 6;
std::lock_guard<std::mutex> lock(*LaunchContext::deviceMutex());
auto handle = reinterpret_cast<cublasHandle_t*>(A->getContext()->getCublasHandle());
auto stream = A->getContext()->getCudaStream();
auto status = cublasSetStream_v2(*handle, *stream);
if (status != CUBLAS_STATUS_SUCCESS) throw cuda_exception::build("MmulHelper::mmulMxM cuda failed !", status);
if (!typeDouble && !typeFloat && !typeHalf && !typeIntFloat && !typeHalfFloat) {
dim3 dims = getMMulDims(C->lengthOf(),DataTypeUtils::sizeOf(cType));
NDArray::prepareSpecialUse({C}, {A, B});
BUILD_SINGLE_SELECTOR_THRICE(aType, usualGemm,
(dims.y, dims.x, dims.z, stream, A->specialBuffer(),
A->specialShapeInfo(), B->specialBuffer(), B->specialShapeInfo(), C->specialBuffer(),
C->specialShapeInfo(), 0, 1, 0, 1, 0, 1, alpha, beta),
SD_NUMERIC_TYPES)
NDArray::registerSpecialUse({C}, {A, B});
auto cudaResult = cudaStreamSynchronize(*stream);
if (cudaResult != 0) throw cuda_exception::build("MmulHelper::mmulMxM cuda failed !", cudaResult);
} 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');
toDelete.push_back(pA);
aMcont = true;
}
if (!bKcont && !bNcont) {
pB = B->dup('f');
toDelete.push_back(pB);
bKcont = true;
}
if (!cMcont) {
pC = C->dup('f');
toDelete.push_back(pC);
cMcont = true;
}
const bool transA = !aMcont;
const bool transB = !bKcont;
const int lda = (aMcont && aKcont) ? M : transA ? pA->strideAt(0) : pA->strideAt(1);
const int ldb = (bKcont && bNcont) ? K : transB ? pB->strideAt(0) : pB->strideAt(1);
const int ldc = (cMcont && cNcont) ? M : pC->strideAt(1);
const cublasOperation_t transAblas = transA ? CUBLAS_OP_T : CUBLAS_OP_N;
const cublasOperation_t transBblas = transB ? CUBLAS_OP_T : CUBLAS_OP_N;
NDArray::prepareSpecialUse({pC}, {pA, pB});
// choose appropriate cuda gemm api depending on data types
if (typeDouble) {
status = cublasDgemm(*handle, transAblas, transBblas, M, N, K, &alpha, (double*)pA->specialBuffer(), lda,
(double*)pB->specialBuffer(), ldb, &beta, (double*)pC->specialBuffer(), ldc);
} else if (typeFloat) {
float alphaF(alpha), betaF(beta);
status = cublasSgemm(*handle, transAblas, transBblas, M, N, K, &alphaF, (float*)pA->specialBuffer(), lda,
(float*)pB->specialBuffer(), ldb, &betaF, (float*)pC->specialBuffer(), ldc);
} else if (typeHalf) {
float16 alphaH(alpha), betaH(beta);
status = cublasHgemm(*handle, transAblas, transBblas, M, N, K, &alphaH.data, (__half*)pA->specialBuffer(), lda,
(__half*)pB->specialBuffer(), ldb, &betaH.data, (__half*)pC->specialBuffer(), ldc);
} else if (typeIntFloat) {
float alphaF(alpha), betaF(beta);
status = cublasSgemmEx(*handle, transAblas, transBblas, M, N, K, &alphaF, pA->specialBuffer(), CUDA_R_8I, lda,
pB->specialBuffer(), CUDA_R_8I, ldb, &betaF, pC->specialBuffer(), CUDA_R_32F, ldc);
} else if (typeHalfFloat) {
float alphaF(alpha), betaF(beta);
status = cublasSgemmEx(*handle, transAblas, transBblas, M, N, K, &alphaF, pA->specialBuffer(), CUDA_R_16F, lda,
pB->specialBuffer(), CUDA_R_16F, ldb, &betaF, pC->specialBuffer(), CUDA_R_32F, ldc);
}
if (status != CUBLAS_STATUS_SUCCESS) throw cuda_exception::build("MmulHelper::mmulMxM cuda failed !", status);
NDArray::registerSpecialUse({pC}, {pA, pB});
auto cudaResult = cudaStreamSynchronize(*stream);
if (cudaResult != 0) throw cuda_exception::build("MmulHelper::mmulMxM cuda failed !", cudaResult);
if (C != pC) C->assign(pC);
for (int i = toDelete.size() - 1; i >= 0; --i) delete toDelete[i];
}
return C;
}////////////////////////////////////////////////////////////////////////////
// MXN x N = M
NDArray* MmulHelper::mmulMxV(NDArray* A, NDArray* X, NDArray* Y, const double alpha, const double beta,
const char outOrder) {
LongType xLenDim, yLenDim(0);
if (A->rankOf() != 2) THROW_EXCEPTION("MmulHelper::mmulMxV cuda: rank of A array is not equal 2 !");
if (!shape::isCommonVector(X->shapeInfo(), xLenDim))
THROW_EXCEPTION("MmulHelper::mmulMxV cuda: 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 cuda: Y array must be vector !");
if (X->lengthOf() != N) THROW_EXCEPTION("MmulHelper::mmulMxV cuda: X vector has wrong length !");
if (Y != nullptr && Y->lengthOf() != M)
THROW_EXCEPTION("MmulHelper::mmulMxV cuda: Y array has wrong length !");
std::vector<LongType> yShape = {M};
if (Y == nullptr)
Y = new NDArray(outOrder, yShape, DataTypeUtils::pickPairwiseResultType(A->dataType(), X->dataType()),
A->getContext());
if (Y->isEmpty()) return Y;
const int incx = X->strideAt(xLenDim);
const int incy = Y->strideAt(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 typeDouble = AXY && aType == DOUBLE;
const bool typeFloat = AXY && aType == FLOAT32;
std::lock_guard<std::mutex> lock(*LaunchContext::deviceMutex());
auto handle = reinterpret_cast<cublasHandle_t*>(A->getContext()->getCublasHandle());
auto stream = A->getContext()->getCudaStream();
auto status = cublasSetStream_v2(*handle, *stream);
if (status != CUBLAS_STATUS_SUCCESS) throw cuda_exception::build("MmulHelper::mmulMxV cuda failed !", status);
if (!typeDouble && !typeFloat) {
dim3 dims = getGemVDims(M);
NDArray::prepareSpecialUse({Y}, {A, X});
const int blocksPerGrid = dims.x;
const int threadsPerBlock = dims.y;
BUILD_SINGLE_SELECTOR_THRICE(
xType, usualGemv,
(blocksPerGrid,threadsPerBlock,stream, A->specialBuffer(), A->specialShapeInfo(), X->specialBuffer(),
X->specialShapeInfo(), Y->specialBuffer(), Y->specialShapeInfo(), incx, incy, 0, alpha, beta),
SD_NUMERIC_TYPES)
NDArray::registerSpecialUse({Y}, {A, X});
auto cudaResult = cudaStreamSynchronize(*stream);
if (cudaResult != 0) throw cuda_exception::build("MmulHelper::mmulMxV cuda failed !", cudaResult);
} 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'); // dup() already returns NDArray*, no need for new
aMcont = true;
}
const bool transA = !aMcont;
const int lda = (aMcont && aNcont) ? M : transA ? pA->strideAt(0) : pA->strideAt(1);
const cublasOperation_t transAblas = transA ? CUBLAS_OP_T : CUBLAS_OP_N;
NDArray::prepareSpecialUse({Y}, {pA, X});
// choose appropriate cuda gemm api depending on data types
if (typeDouble) {
status = cublasDgemv(*handle, transAblas, transA ? N : M, transA ? M : N, &alpha, (double*)pA->specialBuffer(),
lda, (double*)X->specialBuffer(), incx, &beta, (double*)Y->specialBuffer(), incy);
} else if (typeFloat) {
float alphaF(alpha), betaF(beta);
status = cublasSgemv(*handle, transAblas, transA ? N : M, transA ? M : N, &alphaF, (float*)pA->specialBuffer(),
lda, (float*)X->specialBuffer(), incx, &betaF, (float*)Y->specialBuffer(), incy);
}
if (status != CUBLAS_STATUS_SUCCESS) throw cuda_exception::build("MmulHelper::mmulMxV cuda failed !", status);
auto cudaResult = cudaStreamSynchronize(*stream);
if (cudaResult != 0) throw cuda_exception::build("MmulHelper::mmulMxV cuda failed !", cudaResult);
NDArray::registerSpecialUse({Y}, {pA, X});
if (pA != A) delete pA;
}
return Y;
}////////////////////////////////////////////////////////////////////////////
// (X * Y) = Z[0]
NDArray* MmulHelper::dot(NDArray* X, NDArray* Y, NDArray* Z, const double alpha, const double beta) {
LongType xLenDim(0), yLenDim(0);
if (!shape::isCommonVector(X->shapeInfo(), xLenDim))
THROW_EXCEPTION("MmulHelper::dot cuda: X array must be vector !");
if (!shape::isCommonVector(Y->shapeInfo(), yLenDim))
THROW_EXCEPTION("MmulHelper::dot cuda: Y array must be vector !");
if (Z != nullptr && Z->lengthOf() > 1) {
THROW_EXCEPTION("MmulHelper::dot: Z array must be scalar !");
}
const auto length = X->lengthOf();
if (Y->lengthOf() != length)
THROW_EXCEPTION("MmulHelper::dot cuda: lengths of input vectors are different !");
if (Z == nullptr)
Z = new NDArray(DataTypeUtils::pickPairwiseResultType(X->dataType(), Y->dataType()), X->getContext());
const LongType incx = X->strideAt(xLenDim);
const LongType incy = Y->strideAt(yLenDim);
const auto xType = X->dataType();
const auto yType = Y->dataType();
const auto zType = Z->dataType();
if (!X->isActualOnDeviceSide()) X->syncToDevice();
if (!Y->isActualOnDeviceSide()) Y->syncToDevice();
if (!Z->isActualOnDeviceSide()) Z->syncToDevice();
cudaStream_t* stream = X->getContext()->getCudaStream();
dim3 dims = getMMulDims(length,DataTypeUtils::sizeOf(zType));
NDArray::prepareSpecialUse({Z}, {X, Y});
BUILD_SINGLE_SELECTOR_THRICE(xType, usualDot,
(dims, stream, length, alpha, X->specialBuffer(), incx,
Y->specialBuffer(), incy, beta, Z->specialBuffer()),
SD_NUMERIC_TYPES);
auto cudaResult = cudaStreamSynchronize(*stream);
if (cudaResult != 0) throw cuda_exception::build("MmulHelper::dot cuda failed !", cudaResult);
NDArray::registerSpecialUse({Z}, {X, Y});
return Z;
}
///////////////////////////////////////////////////////////////////
NDArray* MmulHelper::mmulNxN(NDArray* A, NDArray* B, NDArray* C, double alpha, double beta,
const char outOrder) {
const LongType aRank = A->rankOf();
const LongType bRank = B->rankOf();
// input ranks validation
if (aRank > bRank && bRank != 2) {
THROW_EXCEPTION("MmulHelper::mmulNxN: rank of B array should be equal 2 !");
}
else if (bRank > aRank && aRank != 2) {
THROW_EXCEPTION("MmulHelper::mmulNxN: rank of A array should be equal 2 !");
}
else if (aRank == bRank) {
for (int i = 0; i < aRank - 2; ++i)
if (A->sizeAt(i) != B->sizeAt(i))
THROW_EXCEPTION(
"MmulHelper::mmulNxN: shapes of A and B arrays are not suitable for matrix multiplication !");
}
if (A->sizeAt(-1) != B->sizeAt(-2)) {
THROW_EXCEPTION("MmulHelper::mmulNxN: shapes of A and B arrays are not suitable for matrix multiplication !");
}
// validation of C array
auto* cExpectedShapePtr = aRank > bRank ? A->getShapeAsVector() : B->getShapeAsVector();
std::vector<LongType> cExpectedShape = *cExpectedShapePtr;
delete cExpectedShapePtr;
cExpectedShape[cExpectedShape.size() - 2] = A->sizeAt(-2);
cExpectedShape[cExpectedShape.size() - 1] = B->sizeAt(-1);
if (C != nullptr) {
if (!C->isSameShape(cExpectedShape))
THROW_EXCEPTION("MmulHelper::mmulNxN: shape of C array is not suitable for AxB matrix multiplication !");
} else
C = new NDArray(outOrder, cExpectedShape, DataTypeUtils::pickPairwiseResultType(A->dataType(), B->dataType()),
A->getContext());
if (C->isEmpty()) return C;
const LongType cRank = C->rankOf();
const LongType aMaxis(aRank - 2), aKaxis(aRank - 1), bKaxis(bRank - 2), bNaxis(bRank - 1), cMaxis(cRank - 2),
cNaxis(cRank - 1);
const int threadsPerBlock = SD_MAX_NUM_THREADS / 8;
const int blocksPerGrid = (C->lengthOf() + threadsPerBlock - 1) / threadsPerBlock;
const int sharedMem = threadsPerBlock * sizeof(LongType) * (aRank + bRank + cRank) + 128;
PointersManager manager(A->getContext(), "MmulHelper::mmulNxN");
const LongType *aBatchDims(nullptr), *bBatchDims(nullptr), *cBatchDims(nullptr);
std::vector<LongType> aDimsVec = {aMaxis,aKaxis};
std::vector<LongType> *aDims = ShapeUtils::evalDimsToExclude(aRank, 2,aDimsVec.data());
std::vector<LongType> bDimsVec = {bKaxis, bNaxis};
std::vector<LongType> *bDims = ShapeUtils::evalDimsToExclude(bRank,2, bDimsVec.data());
std::vector<LongType> cDimsVec = {cMaxis,2, cNaxis};
std::vector<LongType> *cDims = ShapeUtils::evalDimsToExclude(cRank, cDimsVec.size(),cDimsVec.data());
if (aRank > 2)
aBatchDims = reinterpret_cast<LongType*>(manager.replicatePointer(
aDims->data(), (aRank - 2) * sizeof(LongType)));
if (bRank > 2)
bBatchDims = reinterpret_cast<LongType*>(manager.replicatePointer(
bDims->data(), (bRank - 2) * sizeof(LongType)));
if (cRank > 2)
cBatchDims = reinterpret_cast<LongType*>(manager.replicatePointer(
cDims->data(), (cRank - 2) * sizeof(LongType)));
NDArray::prepareSpecialUse({C}, {A, B});
BUILD_SINGLE_SELECTOR_THRICE(
A->dataType(), batchedGemm,
(blocksPerGrid, threadsPerBlock, sharedMem, A->getContext()->getCudaStream(), A->specialBuffer(),
A->specialShapeInfo(), B->specialBuffer(), B->specialShapeInfo(), C->specialBuffer(), C->specialShapeInfo(),
aBatchDims, bBatchDims, cBatchDims, aMaxis, aKaxis, bKaxis, bNaxis, cMaxis, cNaxis, alpha, beta),
SD_NUMERIC_TYPES)
NDArray::registerSpecialUse({C}, {A, B});
manager.synchronize();
delete aDims;
delete bDims;
delete cDims;
return C;
}
} // namespace sd