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

674 lines
33 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 AbdelRauf
//
#include <array/NDArrayFactory.h>
#include <ops/declarable/OpRegistrator.h>
#include "cudnnUtils.h"
namespace sd {
namespace ops {
namespace platforms {
// our implementation designed for 1 physical layer
constexpr int numLayers = 1;
// we will copy without using cudnnGetRNNLinLayerMatrixParams : 1 pseudo layer , isBidirectional : 2 pseudo layer
void copyWeights(const cudaStream_t &stream, bool isBidirectional, uint8_t *weightsSpace, size_t weightsSize,
uint8_t *inputWeightsData, uint8_t *recurrentWeightsData, uint8_t *biasesData, LongType inputSize,
int hiddenSize, int dataTypeSize) {
int pseudo_layer_count = isBidirectional ? 2 : 1;
uint8_t *wptr = weightsSpace;
auto wEnd = wptr + weightsSize;
// copy size for 1 full pseudo layer
// in bidirectional 1 layer consist of 2 pseduo layers
auto input_pseudo_size = 4 * inputSize * hiddenSize * dataTypeSize;
auto hidden_pseudo_size = 4 * hiddenSize * hiddenSize * dataTypeSize;
for (LongType i = 0; i < pseudo_layer_count; i++) {
if (wptr + input_pseudo_size + hidden_pseudo_size > wEnd) return;
// copy input weights
if (inputWeightsData) {
cudaMemcpyAsync(wptr, inputWeightsData, input_pseudo_size, cudaMemcpyDeviceToDevice, stream);
inputWeightsData += input_pseudo_size;
}
wptr += input_pseudo_size;
// copy recurrent weights
if (recurrentWeightsData) {
cudaMemcpyAsync(wptr, recurrentWeightsData, hidden_pseudo_size, cudaMemcpyDeviceToDevice, stream);
recurrentWeightsData += hidden_pseudo_size;
}
wptr += hidden_pseudo_size;
}
// copy bias first 4
auto bias_size = 4 * hiddenSize * dataTypeSize;
for (int i = 0; i < pseudo_layer_count; i++) {
// refill first 4 biases
if (biasesData && wptr + bias_size < wEnd) {
cudaMemcpyAsync(wptr, biasesData, bias_size, cudaMemcpyDeviceToDevice, stream);
biasesData += bias_size;
}
wptr += bias_size;
// refill next 4 with zeros
if (wptr + bias_size < wEnd) {
cudaMemsetAsync(wptr, 0, bias_size, stream);
wptr += bias_size;
}
}
// memset the rest
if (wEnd - wptr) cudaMemsetAsync(wptr, 0, wEnd - wptr, stream);
}
void cudnn_rnn_old(LaunchContext *contextPtr, int dataFormat, NDArray *input, NDArray *inputWeights,
NDArray *recurrentWeights, NDArray *biases, NDArray *prevAct, NDArray *prevMemCell,
NDArray *outputActivations, NDArray *finalTimeStepActivations, NDArray *finalMemCellState,
LongType maxSeqLength, LongType batchSize, LongType inputSize, LongType hiddenSize, double cellClip,
bool isBidirectional) {
sd_debug("cudnn rnn api %s \n", "v6");
bool training = false;
cudnnHandle_t handle = *(reinterpret_cast<cudnnHandle_t *>(contextPtr->getCuDnnHandle()));
auto stream = *(contextPtr->getCudaStream());
CHECK_CUDNN_FAILURE_MSG(STRINGIZE(cudnnSetStream), cudnnSetStream(handle, stream));
CudnnTensorList xDescList(maxSeqLength);
CudnnTensorList yDescList(maxSeqLength);
auto cudnnType = cudnnDataType(input->dataType());
auto dataTypeSize = input->sizeOfT();
CudnnTensor hxDesc, cxDesc, hyDesc, cyDesc;
constexpr int rankOf = 3;
const int numDirections = isBidirectional ? 2 : 1;
const int dimsX[rankOf] = {static_cast<int>(batchSize), static_cast<int>(inputSize), 1};
const int stridesX[rankOf] = {static_cast<int>(inputSize), 1, 1};
const int dimsY[rankOf] = {static_cast<int>(batchSize), static_cast<int>(hiddenSize * numDirections), 1};
const int stridesY[rankOf] = {static_cast<int>(hiddenSize * numDirections), 1, 1};
const int dimC[rankOf] = {static_cast<int>(numLayers * numDirections), static_cast<int>(batchSize), static_cast<int>(hiddenSize)};
const int strideC[rankOf] = {static_cast<int>(batchSize * hiddenSize), static_cast<int>(hiddenSize), 1};
for (int i = 0; i < maxSeqLength; i++) {
xDescList.set(i, cudnnType, rankOf, dimsX, stridesX);
yDescList.set(i, cudnnType, rankOf, dimsY, stridesY);
}
auto xDesc0 = xDescList.get(0);
hxDesc.set(cudnnType, rankOf, dimC, strideC);
cxDesc.set(cudnnType, rankOf, dimC, strideC);
hyDesc.set(cudnnType, rankOf, dimC, strideC);
cyDesc.set(cudnnType, rankOf, dimC, strideC);
PointersManager manager(contextPtr, __func__);
// dropout section
DropoutDesc dropoutDesc(nullptr);
// dropout
float dropout = 0;
size_t sizeInBytes = 0;
void *droupoutMem = nullptr;
uint64_t seed = 1; // seed
if (dropout != 0) {
dropoutDesc.create();
CHECK_CUDNN_FAILURE_MSG(STRINGIZE(cudnnDropoutGetStatesSize), cudnnDropoutGetStatesSize(handle, &sizeInBytes));
// allocate and set
droupoutMem = manager.allocateDevMem(sizeInBytes);
dropoutDesc.set(handle, dropout, droupoutMem, sizeInBytes, seed);
}
// RNN
RnnDesc rnnDesc;
cudnnRNNMode_t rnnCellMode = CUDNN_LSTM;
cudnnRNNAlgo_t algo = CUDNN_RNN_ALGO_STANDARD;
auto direction = isBidirectional ? CUDNN_BIDIRECTIONAL : CUDNN_UNIDIRECTIONAL;
auto mathPrec = cudnnType;
// Note: We will set some parameters manually
constexpr auto inputMode = CUDNN_LINEAR_INPUT;
rnnDesc.setUsingOldAPI(handle, inputMode, direction, rnnCellMode, algo, mathPrec, hiddenSize, numLayers, dropoutDesc);
#if CUDNN_VERSION >= CUDNN_CLIPPING_API_VER
if (cellClip > 0 && cudnnGetVersion() >= CUDNN_CLIPPING_API_VER) {
CHECK_CUDNN_FAILURE_MSG(STRINGIZE(cudnnRNNSetClip), cudnnRNNSetClip(handle, rnnDesc, CUDNN_RNN_CLIP_MINMAX,
CUDNN_PROPAGATE_NAN, -cellClip, cellClip));
}
#endif
// set up parameters
size_t weightsSize = 0;
CHECK_CUDNN_FAILURE_MSG(STRINGIZE(cudnnGetRNNParamsSize),
cudnnGetRNNParamsSize(handle, rnnDesc, xDesc0, &weightsSize, cudnnType));
FilterDesc wDesc;
int dimW[] = {static_cast<int>(weightsSize / dataTypeSize), 1, 1};
wDesc.set(cudnnType, CUDNN_TENSOR_NCHW, 3, dimW);
// allocation
void *weightsSpace = manager.allocateDevMem(weightsSize);
size_t workSpaceSizeInBytes = 0;
size_t reserveSpaceSizeInBytes = 0;
CHECK_CUDNN_FAILURE_MSG(
STRINGIZE(cudnnGetRNNWorkspaceSize),
cudnnGetRNNWorkspaceSize(handle, rnnDesc, maxSeqLength, xDescList.getDescriptors(), &workSpaceSizeInBytes));
void *workSpace = manager.allocateDevMem(workSpaceSizeInBytes);
void *reserveSpace = nullptr;
// training
if (training) {
CHECK_CUDNN_FAILURE_MSG(STRINGIZE(cudnnGetRNNTrainingReserveSize),
cudnnGetRNNTrainingReserveSize(handle, rnnDesc, maxSeqLength, xDescList.getDescriptors(),
&reserveSpaceSizeInBytes));
reserveSpace = manager.allocateDevMem(reserveSpaceSizeInBytes);
}
NDArray::prepareSpecialUse({outputActivations, finalTimeStepActivations, finalMemCellState},
{input, inputWeights, recurrentWeights, biases, prevAct, prevMemCell});
uint8_t *biasesData = biases ? (uint8_t *)biases->specialBuffer() : nullptr;
auto prevActData = prevAct ? prevAct->specialBuffer() : nullptr;
auto prevMemCellData = prevMemCell ? prevMemCell->specialBuffer() : nullptr;
auto finalTimeStepActivationsData = finalTimeStepActivations ? finalTimeStepActivations->specialBuffer() : nullptr;
auto finalMemCellStateData = finalMemCellState ? finalMemCellState->specialBuffer() : nullptr;
// dimension 4*nOut implies order it, ft, c't, ot
// input gate, forget gate, new gate, output gate, input gate, forget gate, new gate, output gate
// Note: our weights should be transposed and duplicated with C order to match cudnn ones
NDArray inputWeightsT, recurrentWeightsT;
uint8_t *inputWeightsData = nullptr;
uint8_t *recurrentWeightsData = nullptr;
if (inputWeights) {
inputWeightsT =
inputWeights->rankOf() == 3 ? inputWeights->permute({0, 2, 1}, 0, false).dup('c') : inputWeights->transpose().dup('c');
inputWeightsData = (uint8_t *)inputWeightsT.specialBuffer();
}
if (recurrentWeights) {
recurrentWeightsT = recurrentWeights->rankOf() == 3 ? recurrentWeights->permute({0, 2, 1}, 0, false).dup('c')
: recurrentWeights->transpose().dup('c');
recurrentWeightsData = (uint8_t *)recurrentWeightsT.specialBuffer();
}
// copy without cudnnGetRNNLinLayerMatrixParams
copyWeights(stream, isBidirectional, (uint8_t *)weightsSpace, weightsSize, inputWeightsData, recurrentWeightsData,
biasesData, inputSize, hiddenSize, dataTypeSize);
// permute based on dataformat
NDArray *argX = input;
NDArray *argOutput = outputActivations;
NDArray permutedX, outputH;
if (outputActivations != nullptr && (dataFormat != 0 || outputActivations->ordering() != 'c')) {
outputH = NDArray('c', std::vector<LongType>{maxSeqLength, batchSize, (numDirections * hiddenSize)},
outputActivations->dataType(), contextPtr);
argOutput = &outputH;
}
if (dataFormat == 1) {
permutedX = input->permute({1, 0, 2}, 0, false).dup('c');
argX = &permutedX;
}
auto xData = argX->specialBuffer();
auto yData = argOutput ? argOutput->specialBuffer() : nullptr;
if (training) {
CHECK_CUDNN_FAILURE_MSG(
STRINGIZE(cudnnRNNForwardTraining),
cudnnRNNForwardTraining(handle, rnnDesc, (int)maxSeqLength, xDescList.getDescriptors(), xData, hxDesc,
prevActData, cxDesc, prevMemCellData, wDesc, weightsSpace, yDescList.getDescriptors(),
yData, hyDesc, finalTimeStepActivationsData, cyDesc, finalMemCellStateData, workSpace,
workSpaceSizeInBytes, reserveSpace, reserveSpaceSizeInBytes));
} else {
CHECK_CUDNN_FAILURE_MSG(
STRINGIZE(cudnnRNNForwardInference),
cudnnRNNForwardInference(handle, rnnDesc, (int)maxSeqLength, xDescList.getDescriptors(), xData, hxDesc,
prevActData, cxDesc, prevMemCellData, wDesc, weightsSpace, yDescList.getDescriptors(),
yData, hyDesc, finalTimeStepActivationsData, cyDesc, finalMemCellStateData, workSpace,
workSpaceSizeInBytes));
}
// remap output
if (outputActivations != nullptr && argOutput != outputActivations) {
// refill output
if (dataFormat == 1) {
std::vector<sd::LongType> permute = {1,0,2};
NDArray assign = argOutput->permute(permute, 0, false);
outputActivations->assign(&assign);
}
}
NDArray::registerSpecialUse({outputActivations, finalTimeStepActivations, finalMemCellState},
{input, inputWeights, recurrentWeights, biases, prevAct, prevMemCell});
return;
}
#if CUDNN_VERSION >= CUDNN_NEW_RNN_API_VER
void cudnn_rnn_v8(LaunchContext *contextPtr, int dataFormat, NDArray *input, NDArray *seqLengthArray,
NDArray *inputWeights, NDArray *recurrentWeights, NDArray *biases, NDArray *prevAct,
NDArray *prevMemCell, NDArray *outputActivations, NDArray *finalTimeStepActivations,
NDArray *finalMemCellState, int maxSeqLength, int batchSize, int inputSize, int hiddenSize,
double cellClip, bool isBidirectional) {
sd_debug("cudnn rnn api %s \n", "v8");
// seqLengthArray should be int
NDArray *argSeqNdArray = nullptr;
NDArray seqArrIntData;
if (seqLengthArray) {
if (seqLengthArray->ews() == 1 && seqLengthArray->dataType() == INT32) {
argSeqNdArray = seqLengthArray;
} else {
if (seqLengthArray->dataType() != INT32) {
seqArrIntData = seqLengthArray->cast(INT32);
if (seqArrIntData.ews() != 1) seqArrIntData = seqArrIntData.dup('c');
} else {
seqArrIntData = seqLengthArray->dup('c');
}
argSeqNdArray = &seqArrIntData;
}
} else {
seqArrIntData = NDArray('c', std::vector<LongType>{batchSize}, INT32, contextPtr);
seqArrIntData.assign(maxSeqLength);
argSeqNdArray = &seqArrIntData;
}
PointersManager manager(contextPtr, __func__);
bool training = false;
cudnnHandle_t handle = *(reinterpret_cast<cudnnHandle_t *>(contextPtr->getCuDnnHandle()));
auto stream = *(contextPtr->getCudaStream());
CHECK_CUDNN_FAILURE_MSG(STRINGIZE(cudnnSetStream), cudnnSetStream(handle, stream));
auto cudnnType = cudnnDataType(input->dataType());
auto dataTypeSize = input->sizeOfT();
CudnnTensor hDesc, cDesc;
constexpr int rankOf = 3;
const int numDirections = isBidirectional ? 2 : 1;
const int dimC[rankOf] = {numLayers * numDirections, batchSize, hiddenSize};
const int strideC[rankOf] = {batchSize * hiddenSize, hiddenSize, 1};
hDesc.set(cudnnType, rankOf, dimC, strideC);
cDesc.set(cudnnType, rankOf, dimC, strideC);
// dropout section
DropoutDesc dropoutDesc(nullptr);
// dropout
float dropout = 0;
size_t sizeInBytes = 0;
void *droupoutMem = nullptr;
uint64_t seed = 1; // seed
if (dropout != 0) {
dropoutDesc.create();
CHECK_CUDNN_FAILURE_MSG(STRINGIZE(cudnnDropoutGetStatesSize), cudnnDropoutGetStatesSize(handle, &sizeInBytes));
// allocate and set
droupoutMem = manager.allocateDevMem(sizeInBytes);
dropoutDesc.set(handle, dropout, droupoutMem, sizeInBytes, seed);
}
// RNN
RnnDesc rnnDesc;
cudnnRNNMode_t rnnCellMode = CUDNN_LSTM;
cudnnRNNAlgo_t algo = CUDNN_RNN_ALGO_STANDARD;
auto direction = isBidirectional ? CUDNN_BIDIRECTIONAL : CUDNN_UNIDIRECTIONAL;
auto mathPrec = cudnnType;
// Note: We will set some parameters manually. Some of them could be parameter in future
constexpr auto inputMode = CUDNN_LINEAR_INPUT;
bool use_tensor_ops = false; // could be parameter in future
#if CUDNN_VERSION >= CUDNN_NEW_RNN_API_VER
cudnnMathType_t mathType = use_tensor_ops ? CUDNN_TENSOR_OP_MATH : CUDNN_FMA_MATH;
#else
cudnnMathType_t mathType = use_tensor_ops ? CUDNN_TENSOR_OP_MATH : CUDNN_DEFAULT_MATH;
#endif
// disable projection
int projSize = hiddenSize;
cudnnRNNBiasMode_t bias_mode = CUDNN_RNN_DOUBLE_BIAS;
uint32_t aux_flags = CUDNN_RNN_PADDED_IO_ENABLED;
rnnDesc.set(algo, rnnCellMode, bias_mode, direction, inputMode, cudnnType, mathPrec, mathType, inputSize, hiddenSize,
projSize, numLayers, dropoutDesc, aux_flags);
if (cellClip > 0) {
CHECK_CUDNN_FAILURE_MSG(STRINGIZE(cudnnRNNSetClip), cudnnRNNSetClip(handle, rnnDesc, CUDNN_RNN_CLIP_MINMAX,
CUDNN_PROPAGATE_NAN, -cellClip, cellClip));
}
// set Data desc
RnnDataDesc xDataDesc, yDataDesc;
bool time_major = false;
float padding_fill = 0.0f;
auto hostSeqArr = bufferInHost<int>(*argSeqNdArray);
cudnnRNNDataLayout_t layout =
dataFormat == 0 ? CUDNN_RNN_DATA_LAYOUT_SEQ_MAJOR_UNPACKED : CUDNN_RNN_DATA_LAYOUT_BATCH_MAJOR_UNPACKED;
xDataDesc.set(cudnnType, layout, maxSeqLength, batchSize, inputSize, hostSeqArr, (void *)&padding_fill);
yDataDesc.set(cudnnType, layout, maxSeqLength, batchSize, hiddenSize * numDirections, hostSeqArr,
(void *)&padding_fill);
// set up parameters
size_t weightsSize = 0;
CHECK_CUDNN_FAILURE_MSG(STRINGIZE(cudnnGetRNNWeightSpaceSize),
cudnnGetRNNWeightSpaceSize(handle, rnnDesc, &weightsSize));
// allocation
void *weightsSpace = manager.allocateDevMem(weightsSize);
// Set up work space and reserved memory
void *workSpace = nullptr;
void *reserveSpace = nullptr;
size_t workSpaceSizeInBytes = 0;
size_t reserveSpaceSizeInBytes = 0;
cudnnForwardMode_t fwdMode = training ? CUDNN_FWD_MODE_TRAINING : CUDNN_FWD_MODE_INFERENCE;
CHECK_CUDNN_FAILURE_MSG(
STRINGIZE(cudnnGetRNNTempSpaceSizes),
cudnnGetRNNTempSpaceSizes(handle, rnnDesc, fwdMode, xDataDesc, &workSpaceSizeInBytes, &reserveSpaceSizeInBytes));
workSpace = manager.allocateDevMem(workSpaceSizeInBytes);
// training
if (training) {
reserveSpace = manager.allocateDevMem(reserveSpaceSizeInBytes);
}
NDArray::prepareSpecialUse({outputActivations, finalTimeStepActivations, finalMemCellState},
{input, inputWeights, recurrentWeights, biases, prevAct, prevMemCell, argSeqNdArray});
auto xData = input->specialBuffer();
uint8_t *biasesData = biases ? (uint8_t *)biases->specialBuffer() : nullptr;
auto prevActData = prevAct ? prevAct->specialBuffer() : nullptr;
auto prevMemCellData = prevMemCell ? prevMemCell->specialBuffer() : nullptr;
auto yData = outputActivations ? outputActivations->specialBuffer() : nullptr;
auto finalTimeStepActivationsData = finalTimeStepActivations ? finalTimeStepActivations->specialBuffer() : nullptr;
auto finalMemCellStateData = finalMemCellState ? finalMemCellState->specialBuffer() : nullptr;
// dimension 4*nOut implies order it, ft, c't, ot
// input gate, forget gate, new gate, output gate, input gate, forget gate, new gate, output gate
// Note: our weights should be transposed and duplicated with C order to match cudnn ones
NDArray inputWeightsT, recurrentWeightsT;
uint8_t *inputWeightsData = nullptr;
uint8_t *recurrentWeightsData = nullptr;
if (inputWeights) {
inputWeightsT =
inputWeights->rankOf() == 3 ? inputWeights->permute({0, 2, 1}).dup('c') : inputWeights->transpose().dup('c');
inputWeightsData = (uint8_t *)inputWeightsT.specialBuffer();
}
if (recurrentWeights) {
recurrentWeightsT = recurrentWeights->rankOf() == 3 ? recurrentWeights->permute({0, 2, 1}).dup('c')
: recurrentWeights->transpose().dup('c');
recurrentWeightsData = (uint8_t *)recurrentWeightsT.specialBuffer();
}
// copy without cudnnGetRNNLinLayerMatrixParams
copyWeights(stream, isBidirectional, (uint8_t *)weightsSpace, weightsSize, inputWeightsData, recurrentWeightsData,
biasesData, inputSize, hiddenSize, dataTypeSize);
CHECK_CUDNN_FAILURE_MSG(
STRINGIZE(cudnnRNNForward),
cudnnRNNForward(handle, rnnDesc, fwdMode, (const int32_t *)argSeqNdArray->specialBuffer(), xDataDesc, xData,
yDataDesc, yData, hDesc, prevActData, finalTimeStepActivationsData, cDesc, prevMemCellData,
finalMemCellStateData, weightsSize, weightsSpace, workSpaceSizeInBytes, workSpace,
reserveSpaceSizeInBytes, reserveSpace));
NDArray::registerSpecialUse({outputActivations, finalTimeStepActivations, finalMemCellState},
{input, inputWeights, recurrentWeights, biases, prevAct, prevMemCell});
return;
}
#endif
//////////////////////////////////////////////////////////////////////////
PLATFORM_IMPL(lstmLayer, ENGINE_CUDA) {
const auto dataFormat = INT_ARG(0); // for unidirectional: 0 = [sL, bS, nIn], 1 = [bS, sL ,nIn], 2 = [bS, nIn, sL],
// for bidirectional: 3 = [sL, 2, bS, nOut] (for ONNX)
const LongType directionMode =
INT_ARG(1); // direction: 0 = fwd, 1 = bwd, 2 = bidirectional sum, 3 = bidirectional concat, 4 = bidirectional
// extra output dim (in conjunction with format dataFormat = 3)
const auto hasBiases = B_ARG(0); // indicates whether biases array is provided
const auto hasSeqLenArray = B_ARG(1); // indicates whether seqLen array is provided
const auto hasInitH = B_ARG(2); // indicates whether initial output is provided
const auto hasInitC = B_ARG(3); // indicates whether initial cell state is provided
const auto hasPH = B_ARG(4); // indicates whether peephole connections are present
const auto retFullSeq = B_ARG(5); // indicates whether to return whole time sequence h {h_0, h_1, ... , h_sL-1}
const auto retLastH = B_ARG(6); // indicates whether to return output at last time step only, in this case shape
// would be [bS, nOut] (exact shape depends on dataFormat argument)
const auto retLastC = B_ARG(7); // indicates whether to return cells state at last time step only, in this case shape
// would be [bS, nOut] (exact shape depends on dataFormat argument)
const auto cellClip = T_ARG(0); // cell clipping value, if it = 0 then do not apply clipping
const auto x = INPUT_VARIABLE(0); // input
const auto Wx = INPUT_VARIABLE(1); // input weights
const auto Wr = INPUT_VARIABLE(2); // recurrent weights
int count = 3;
const auto b = hasBiases ? INPUT_VARIABLE(count++) : nullptr; // biases
const auto seqLengthArray = hasSeqLenArray ? INPUT_VARIABLE(count++) : nullptr; // seqLen vector
const auto hI = hasInitH ? INPUT_VARIABLE(count++) : nullptr; // initial output
const auto cI = hasInitC ? INPUT_VARIABLE(count++) : nullptr; // initial cell state
const auto Wp = hasPH ? INPUT_VARIABLE(count++) : nullptr; // peephole weights
count = 0;
auto h = retFullSeq ? OUTPUT_VARIABLE(count++) : nullptr; // output
auto hL = retLastH ? OUTPUT_VARIABLE(count++) : nullptr; // output at last step
auto cL = retLastC ? OUTPUT_VARIABLE(count++) : nullptr; // cell state at last step
REQUIRE_TRUE(cellClip >= 0, 0, "LSTM_LAYER operation: cell clipping value should be nonnegative (>=0) !");
REQUIRE_TRUE(retFullSeq || retLastH || retLastC, 0,
"LSTM_LAYER operation: please specify what output arrays to produce !");
// evaluate dimensions
const LongType seqLength = dataFormat == 3 ? x->sizeAt(0) : x->sizeAt(dataFormat);
const LongType bS = dataFormat == 1 || dataFormat == 2 ? x->sizeAt(0) : x->sizeAt(1);
const LongType nIn = dataFormat == 2 ? x->sizeAt(1) : x->sizeAt(2);
const LongType nOut = Wx->sizeAt(-1) / 4;
const LongType hiddenSize = nOut;
auto contextPtr = block.launchContext();
bool isBidirectional = directionMode >= 2;
if (!isBidirectional) { // no bidirectional
// Wx validation
if (Wx->rankOf() != 2 || Wx->sizeAt(0) != nIn)
REQUIRE_TRUE(false, 0, "LSTM_LAYER operation: wrong shape of input weights, expected is %s, but got %s instead !",
ShapeUtils::shapeAsString({nIn, 4 * nOut}).c_str(), ShapeUtils::shapeAsString(Wx).c_str());
// Wr validation
if (Wr->rankOf() != 2 || Wr->sizeAt(0) != nOut || Wr->sizeAt(1) != 4 * nOut)
REQUIRE_TRUE(false, 0,
"LSTM_LAYER operation: wrong shape of recurrent weights, expected is %s, but got %s instead !",
ShapeUtils::shapeAsString({nOut, 4 * nOut}).c_str(), ShapeUtils::shapeAsString(Wr).c_str());
// biases validation
if (b != nullptr && (b->rankOf() != 1 || b->sizeAt(0) != 4 * nOut))
REQUIRE_TRUE(false, 0, "LSTM_LAYER operation: wrong shape of biases, expected is %s, but got %s instead !",
ShapeUtils::shapeAsString({4 * nOut}).c_str(), ShapeUtils::shapeAsString(b).c_str());
// initial output validation
if (hI != nullptr && (hI->rankOf() != 2 || hI->sizeAt(0) != bS || hI->sizeAt(1) != nOut))
REQUIRE_TRUE(false, 0,
"LSTM_LAYER operation: wrong shape of initial output, expected is %s, but got %s instead !",
ShapeUtils::shapeAsString({bS, nOut}).c_str(), ShapeUtils::shapeAsString(hI).c_str());
// initial cell validation
if (cI != nullptr && (cI->rankOf() != 2 || cI->sizeAt(0) != bS || cI->sizeAt(1) != nOut))
REQUIRE_TRUE(false, 0,
"LSTM_LAYER operation: wrong shape of initial cell state, expected is %s, but got %s instead !",
ShapeUtils::shapeAsString({bS, nOut}).c_str(), ShapeUtils::shapeAsString(cI).c_str());
} else { // bidirectional
// Wx validation
if (Wx->rankOf() != 3 || Wx->sizeAt(0) != 2 || Wx->sizeAt(1) != nIn)
REQUIRE_TRUE(false, 0, "LSTM_LAYER operation: wrong shape of input weights, expected is %s, but got %s instead !",
ShapeUtils::shapeAsString({2, nIn, 4 * nOut}).c_str(), ShapeUtils::shapeAsString(Wx).c_str());
// Wr validation
if (Wr->rankOf() != 3 || Wr->sizeAt(0) != 2 || Wr->sizeAt(1) != nOut || Wr->sizeAt(2) != 4 * nOut)
REQUIRE_TRUE(false, 0,
"LSTM_LAYER operation: wrong shape of recurrent weights, expected is %s, but got %s instead !",
ShapeUtils::shapeAsString({2, nOut, 4 * nOut}).c_str(), ShapeUtils::shapeAsString(Wr).c_str());
// biases validation
if (b != nullptr && (b->rankOf() != 2 || b->sizeAt(0) != 2 || b->sizeAt(1) != 4 * nOut))
REQUIRE_TRUE(false, 0, "LSTM_LAYER operation: wrong shape of biases, expected is %s, but got %s instead !",
ShapeUtils::shapeAsString({2, 4 * nOut}).c_str(), ShapeUtils::shapeAsString(b).c_str());
// initial output validation
if (hI != nullptr && (hI->rankOf() != 3 || hI->sizeAt(0) != 2 || hI->sizeAt(1) != bS || hI->sizeAt(2) != nOut))
REQUIRE_TRUE(false, 0,
"LSTM_LAYER operation: wrong shape of initial output, expected is %s, but got %s instead !",
ShapeUtils::shapeAsString({2, bS, nOut}).c_str(), ShapeUtils::shapeAsString(hI).c_str());
// initial cell validation
if (cI != nullptr && (cI->rankOf() != 3 || cI->sizeAt(0) != 2 || cI->sizeAt(1) != bS || cI->sizeAt(2) != nOut))
REQUIRE_TRUE(false, 0,
"LSTM_LAYER operation: wrong shape of initial cell state, expected is %s, but got %s instead !",
ShapeUtils::shapeAsString({2, bS, nOut}).c_str(), ShapeUtils::shapeAsString(cI).c_str());
}
#if CUDNN_VERSION < CUDNN_NEW_RNN_API_VER
cudnn_rnn_old(contextPtr, dataFormat, x, Wx, Wr, b, hI, cI, h, hL, cL, seqLength, bS, nIn, hiddenSize,
(double)cellClip, isBidirectional);
#else
if (cudnnGetVersion() >= CUDNN_NEW_RNN_API_VER) {
cudnn_rnn_v8(contextPtr, dataFormat, x, seqLengthArray, Wx, Wr, b, hI, cI, h, hL, cL, seqLength, bS, nIn,
hiddenSize, (double)cellClip, isBidirectional);
} else {
cudnn_rnn_old(contextPtr, dataFormat, x, Wx, Wr, b, hI, cI, h, hL, cL, seqLength, bS, nIn, hiddenSize,
(double)cellClip, isBidirectional);
}
#endif
return Status::OK;
}
// Cudnn Lstm:
// Forward inference implemented using v6, and v8 (when version > 8.0.1) api calls.
// As our Cuda Lstm implementation has 1 layer. Cudnn implementation was implemented for 1 physical layer
// Cudnn helper restrictions:
// - all NDArrays should be the same type
// - dataFormat should be 0 or 1
// - only unidirectional (directionMode == 0) and bidirectional concat (directionMode == 3)
// - no peephole connection
// - Clipping is allowed for cudnn version >= 7.2.1
// - SeqLen array is allowed for cudnn version >= 8.0.1
// - gateActivation: sigmoid, cellActivation and outputActivation: tanh
// - NDArrays (excluding the weight arrays, as we have to transpose or permute it) should follow 'c' order and ews()==1
PLATFORM_CHECK(lstmLayer, ENGINE_CUDA) {
const auto dataFormat = INT_ARG(0); // for unidirectional: 0 = [sL, bS, nIn], 1 = [bS, sL ,nIn], 2 = [bS, nIn, sL],
// for bidirectional: 3 = [sL, 2, bS, nOut] (for ONNX)
const auto directionMode =
INT_ARG(1); // direction: 0 = fwd, 1 = bwd, 2 = bidirectional sum, 3 = bidirectional concat, 4 = bidirectional
// extra output dim (in conjunction with format dataFormat = 3)
// integer numbers corresponding to activations: 0=tanh, 1=relu, 2=sigmoid, 3=affine, 4=leaky relu, 5= thresholded
// relu, 6=scaled tanh, 7=hard sigmoid, 8=ELU, 9=softsign, 10=softplus
const auto gateAct = INT_ARG(2); // activation for input (i), forget (f) and output (o) gates
const auto cellAct = INT_ARG(3); // activation for cell state (c)
const auto outAct = INT_ARG(4); // activation for output (h)
const auto hasBiases = B_ARG(0); // indicates whether biases array is provided
const auto hasSeqLenArray = B_ARG(1); // indicates whether seqLen array is provided
const auto hasInitH = B_ARG(2); // indicates whether initial output is provided
const auto hasInitC = B_ARG(3); // indicates whether initial cell state is provided
const auto hasPH = B_ARG(4); // indicates whether peephole connections are present
const auto retFullSeq = B_ARG(5); // indicates whether to return whole time sequence h {h_0, h_1, ... , h_sL-1}
const auto retLastH = B_ARG(6); // indicates whether to return output at last time step only, in this case shape
// would be [bS, nOut] (exact shape depends on dataFormat argument)
const auto retLastC = B_ARG(7); // indicates whether to return cells state at last time step only, in this case shape
// would be [bS, nOut] (exact shape depends on dataFormat argument)
const auto cellClip = T_ARG(0); // cell clipping value, if it = 0 then do not apply clipping
const auto x = INPUT_VARIABLE(0); // input
const auto Wx = INPUT_VARIABLE(1); // input weights
const auto Wr = INPUT_VARIABLE(2); // recurrent weights
int count = 3;
const auto b = hasBiases ? INPUT_VARIABLE(count++) : nullptr; // biases
const auto hI = hasInitH ? INPUT_VARIABLE(count++) : nullptr; // initial output
const auto cI = hasInitC ? INPUT_VARIABLE(count++) : nullptr; // initial cell state
count = 0;
auto h = retFullSeq ? OUTPUT_VARIABLE(count++) : nullptr; // output
auto hL = retLastH ? OUTPUT_VARIABLE(count++) : nullptr; // output at last step
auto cL = retLastC ? OUTPUT_VARIABLE(count++) : nullptr; // cell state at last step
DataType xType = x->dataType();
DataType WxType = Wx->dataType();
DataType WrType = Wr->dataType();
Requirements req("CUDNN LSTMLAYER OP");
// cudnn related restrictions //gateAct: sigmoid, cellAct: tanh adn et cetera
// integer numbers corresponding to activations: 0=tanh, 1=relu, 2=sigmoid, 3=affine,
// 4=leaky relu, 5= thresholded relu, 6=scaled tanh, 7=hard sigmoid, 8=ELU, 9=softsign, 10=softplus
req.expectEq(makeInfoVariable(gateAct, "gate Activation"), makeInfoVariable(2, "sigmoid")) &&
req.expectEq(makeInfoVariable(cellAct, "cell Activation"), makeInfoVariable(2, "tanh")) &&
req.expectEq(makeInfoVariable(outAct, "out Activation"), makeInfoVariable(2, "tanh")) &&
req.expectFalse(makeInfoVariable(hasPH, HAVE_PEEPHOLE), EXPECTED_NOT_SUPPORTED) &&
req.expectIn(makeInfoVariable(directionMode, "directionMode"), {0, 3}) &&
req.expectIn(makeInfoVariable(dataFormat, "data Format"), {0, 1});
if (req) {
// cudnn api version related restrictions in our helpers
size_t cudnn_version = cudnnGetVersion();
// though seqlengthArray was added in earlier versions we do not handle it below 8.0.0.1
#if CUDNN_VERSION < CUDNN_NEW_RNN_API_VER
// implRestrictions = implRestrictions && !hasSeqLenArray;
req.expectFalse(makeInfoVariable(hasSeqLenArray, HAVE_SEQLENARR), EXPECTED_NOT_SUPPORTED);
#else
// implRestrictions = implRestrictions && (cudnn_version >= CUDNN_NEW_RNN_API_VER || !hasSeqLenArray);
if (cudnn_version < CUDNN_NEW_RNN_API_VER) {
req.expectFalse(makeInfoVariable(hasSeqLenArray, HAVE_SEQLENARR), EXPECTED_NOT_SUPPORTED);
}
#endif
// implRestrictions = implRestrictions && (cudnn_version >= CUDNN_CLIPPING_API_VER || cellClip==0);
if (cudnn_version < CUDNN_CLIPPING_API_VER) {
req.expectEq(makeInfoVariable(cellClip, MSG_CELL_CLIPPING), 0);
}
}
// restriction that comes either from not setting Descriptor or not handling manipulation:
// restrict0: the same types
req.expectEq(makeInfoVariable(x->ordering(), ORDERING_MSG_INPUT0), 'c') &&
req.expectEq(makeInfoVariable(WxType, TYPE_MSG_INPUT1), makeInfoVariable(xType, TYPE_MSG_INPUT0)) &&
req.expectEq(makeInfoVariable(WrType, TYPE_MSG_INPUT2), makeInfoVariable(xType, TYPE_MSG_INPUT0));
if (b)
req.expectEq(makeInfoVariable(b->dataType(), TYPE_MSG_INPUT_ "#bias"), makeInfoVariable(xType, TYPE_MSG_INPUT0));
if (hI) {
req.expectEq(makeInfoVariable(hI->dataType(), TYPE_MSG_INPUT_ "#hI"), makeInfoVariable(xType, TYPE_MSG_INPUT0)) &&
req.expectEq(makeInfoVariable(hI->ordering(), ORDERING_MSG_INPUT_ "#hI"), 'c') &&
}
if (cI) {
req.expectEq(makeInfoVariable(cI->dataType(), TYPE_MSG_INPUT_ "#cI"), makeInfoVariable(xType, TYPE_MSG_INPUT0)) &&
req.expectEq(makeInfoVariable(cI->ordering(), ORDERING_MSG_INPUT_ "#cI"), 'c') &&
}
if (h) {
req.expectEq(makeInfoVariable(h->dataType(), TYPE_MSG_OUTPUT_ "#h"), makeInfoVariable(xType, TYPE_MSG_INPUT0)) &&
req.expectEq(makeInfoVariable(h->ordering(), ORDERING_MSG_OUTPUT_ "#h"), 'c') &&
}
if (hL) {
req.expectEq(makeInfoVariable(hL->dataType(), TYPE_MSG_OUTPUT_ "#hL"), makeInfoVariable(xType, TYPE_MSG_INPUT0)) &&
req.expectEq(makeInfoVariable(hL->ordering(), ORDERING_MSG_OUTPUT_ "#hL"), 'c') &&
}
if (cL) {
req.expectEq(makeInfoVariable(cL->dataType(), TYPE_MSG_OUTPUT_ "#cL"), makeInfoVariable(xType, TYPE_MSG_INPUT0)) &&
req.expectEq(makeInfoVariable(cL->ordering(), ORDERING_MSG_OUTPUT_ "#cL"), 'c') &&
}
req.logTheSuccess();
return req;
}
} // namespace platforms
} // namespace ops
} // namespace sd