/* ****************************************************************************** * * * 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 #include #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(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(batchSize), static_cast(inputSize), 1}; const int stridesX[rankOf] = {static_cast(inputSize), 1, 1}; const int dimsY[rankOf] = {static_cast(batchSize), static_cast(hiddenSize * numDirections), 1}; const int stridesY[rankOf] = {static_cast(hiddenSize * numDirections), 1, 1}; const int dimC[rankOf] = {static_cast(numLayers * numDirections), static_cast(batchSize), static_cast(hiddenSize)}; const int strideC[rankOf] = {static_cast(batchSize * hiddenSize), static_cast(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(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{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 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{batchSize}, INT32, contextPtr); seqArrIntData.assign(maxSeqLength); argSeqNdArray = &seqArrIntData; } PointersManager manager(contextPtr, __func__); bool training = false; cudnnHandle_t handle = *(reinterpret_cast(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(*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