LCOV - code coverage report
Current view: top level - ut/rnn/single_layer_lstm_grad/op_api - aclnn_lstm_backward.cpp Coverage Total Hit
Test: CHG Lines: 71.1 % 764 543
Test Date: 2026-08-18 10:52:24
Legend: Lines: hit not hit

            Line data    Source code
       1              : /**
       2              :  * Copyright (c) 2025 Huawei Technologies Co., Ltd.
       3              :  * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
       4              :  * CANN Open Software License Agreement Version 2.0 (the "License").
       5              :  * Please refer to the License for details. You may not use this file except in compliance with the License.
       6              :  * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
       7              :  * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
       8              :  * See LICENSE in the root of the software repository for the full text of the License.
       9              :  */
      10              : 
      11              : /*!
      12              :  * \file aclnn_lstm_backward.cpp
      13              :  * \brief
      14              :  */
      15              : #include "aclnn_lstm_backward.h"
      16              : #include "single_layer_lstm_grad.h"
      17              : #include "aclnn_kernels/cast.h"
      18              : #include "aclnn_kernels/contiguous.h"
      19              : #include "aclnn/aclnn_base.h"
      20              : #include "opdev/common_types.h"
      21              : #include "opdev/data_type_utils.h"
      22              : #include "opdev/shape_utils.h"
      23              : #include "opdev/format_utils.h"
      24              : #include "opdev/op_dfx.h"
      25              : #include "opdev/op_executor.h"
      26              : #include "opdev/op_log.h"
      27              : #include "opdev/tensor_view_utils.h"
      28              : #include "opdev/platform.h"
      29              : #include "aclnn_kernels/common/op_error_check.h"
      30              : #include "level0/greater.h"
      31              : #include "aclnn_kernels/cast.h"
      32              : #include "level0/concat.h"
      33              : #include "level0/arange.h"
      34              : #include "aclnn_kernels/reshape.h"
      35              : #include "level0/broadcast_to.h"
      36              : #include "aclnn_kernels/slice.h"
      37              : #include "level0/unsqueeze.h"
      38              : #include "level0/squeeze.h"
      39              : #include "aclnn_kernels/transpose.h"
      40              : #include "level0/add.h"
      41              : #include "level0/zero_op.h"
      42              : using namespace op;
      43              : #ifdef __cplusplus
      44              : extern "C" {
      45              : #endif
      46              : 
      47              : namespace {
      48              : // 通用维度常量
      49              : constexpr int64_t DIM_ZERO = 0;
      50              : constexpr int64_t DIM_ONE = 1;
      51              : constexpr int64_t DIM_TWO = 2;
      52              : constexpr int64_t DIM_THREE = 3;
      53              : 
      54              : // LSTM特定常量
      55              : constexpr int64_t GATE_COUNT = 4; // i, j, f, o 四个门
      56              : constexpr int64_t SINGLE_DIRECTION = 1;
      57              : constexpr int64_t BI_DIRECTION = 2;
      58              : constexpr int64_t HC_TENSOR_COUNT = 2; // h和c两个张量
      59              : constexpr int64_t REDUCE_DIM = 1;
      60              : constexpr int64_t CONCAT_DIM_HIDDEN = 2;
      61              : constexpr int64_t CONCAT_DIM_LAYER = 0;
      62              : constexpr int64_t WEIGHT_INPUT_INDEX = 0;
      63              : constexpr int64_t WEIGHT_HIDDEN_INDEX = 1;
      64              : constexpr int64_t BIAS_INPUT_INDEX = 2;
      65              : constexpr int64_t BIAS_HIDDEN_INDEX = 3;
      66              : constexpr int64_t LSTM_CONFIG_NO_BIAS_NO_BIDIR = 0;
      67              : constexpr int64_t LSTM_CONFIG_BIDIR_ONLY = 1;
      68              : constexpr int64_t LSTM_CONFIG_BIAS_ONLY = 2;
      69              : constexpr int64_t LSTM_CONFIG_BIAS_BIDIR = 3;
      70              : constexpr int64_t SEQUENCE_DIM = 0;
      71              : constexpr int64_t BATCH_DIM = 1;
      72              : constexpr int64_t HIDDEN_DIM = 2;
      73              : constexpr int64_t OUT_NUM = 5;
      74              : 
      75              : // 函数返回结果索引
      76              : constexpr int64_t RESULT_WEIGHT_GRAD_INDEX = 0;
      77              : constexpr int64_t RESULT_BIAS_GRAD_INDEX = 1;
      78              : constexpr int64_t RESULT_INPUT_GRAD_INDEX = 2;
      79              : constexpr int64_t RESULT_HIDDEN_GRAD_INDEX = 3;
      80              : constexpr int64_t RESULT_CELL_GRAD_INDEX = 4;
      81              : 
      82              : // 切片张量数组索引
      83              : constexpr int64_t SLICE_INIT_H_INDEX = 0;
      84              : constexpr int64_t SLICE_INIT_C_INDEX = 1;
      85              : constexpr int64_t SLICE_DH_INDEX = 2;
      86              : constexpr int64_t SLICE_DC_INDEX = 3;
      87              : 
      88              : // 双向张量数组索引
      89              : constexpr int64_t BIDIR_INIT_H_INDEX = 0;
      90              : constexpr int64_t BIDIR_INIT_C_INDEX = 1;
      91              : constexpr int64_t BIDIR_DH_INDEX = 2;
      92              : constexpr int64_t BIDIR_DC_INDEX = 3;
      93              : constexpr int64_t BIDIR_DY_INDEX = 4;
      94              : 
      95              : // 参数索引计算
      96              : constexpr int64_t NUM_NO_B_NO_BIDIR = 2;
      97              : constexpr int64_t NUM_WITH_B_OR_BID = 4;
      98              : constexpr int64_t NUM_WITH_B_AND_BID = 8;
      99              : 
     100              : // 门索引
     101              : constexpr int64_t GATE_I_INDEX = 0;
     102              : constexpr int64_t GATE_J_INDEX = 1;
     103              : constexpr int64_t GATE_F_INDEX = 2;
     104              : constexpr int64_t GATE_O_INDEX = 3;
     105              : 
     106              : // 函数返回索引
     107              : constexpr int64_t INDEX_ZERO = 0;
     108              : constexpr int64_t INDEX_ONE = 1;
     109              : constexpr int64_t INDEX_TWO = 2;
     110              : constexpr int64_t INDEX_THREE = 3;
     111              : constexpr int64_t INDEX_FOUR = 4;
     112              : 
     113              : // 拼接最大数量
     114              : constexpr size_t CONCAT_MAX_NUM = 32;
     115              : 
     116              : static const std::initializer_list<op::DataType> DTYPE_SUPPORT_LIST = {op::DataType::DT_FLOAT,
     117              :                                                                        op::DataType::DT_FLOAT16};
     118              : } // namespace
     119              : 
     120              : static const std::initializer_list<op::DataType> BATCH_SIZES_DTYPE_SUPPORT_LIST = {op::DataType::DT_INT64};
     121              : 
     122              : struct LSTMContinuousTensors {
     123              :     const aclTensor* inputContiguous = nullptr;
     124              :     const aclTensorList* hxContiguous = nullptr;
     125              :     const aclTensorList* paramsContiguous = nullptr;
     126              :     const aclTensor* dyContiguous = nullptr;
     127              :     const aclTensor* dhContiguous = nullptr;
     128              :     const aclTensor* dcContiguous = nullptr;
     129              :     const aclTensorList* iContiguous = nullptr;
     130              :     const aclTensorList* gContiguous = nullptr;
     131              :     const aclTensorList* fContiguous = nullptr;
     132              :     const aclTensorList* oContiguous = nullptr;
     133              :     const aclTensorList* hContiguous = nullptr;
     134              :     const aclTensorList* cContiguous = nullptr;
     135              :     const aclTensorList* tanhcContiguous = nullptr;
     136              :     const aclTensor* batchSizesContiguous = nullptr;
     137              : };
     138              : 
     139              : struct SingleTensorItem {
     140              :     const char* name;
     141              :     const aclTensor* tensor;
     142              : };
     143              : 
     144              : struct TensorListItem {
     145              :     const char* name;
     146              :     const aclTensorList* list;
     147              : };
     148              : 
     149            4 : static const aclTensor* SplitToConcat(std::vector<const aclTensor*> tensorListA, int64_t dim, aclOpExecutor* executor)
     150              : {
     151            4 :     if (tensorListA.size() == 1) {
     152            2 :         return tensorListA[0];
     153              :     }
     154              : 
     155            4 :     while (tensorListA.size() > 1) {
     156              :         std::vector<const aclTensor*> tensorListOnce;
     157              :         std::vector<const aclTensor*> tensorListB;
     158           10 :         for (auto tensor : tensorListA) {
     159            8 :             tensorListOnce.emplace_back(tensor);
     160            8 :             if (tensorListOnce.size() == CONCAT_MAX_NUM) {
     161            0 :                 auto tensorList = executor->AllocTensorList(tensorListOnce.data(), tensorListOnce.size());
     162            0 :                 auto concatTensor = l0op::ConcatD(tensorList, dim, executor);
     163            0 :                 CHECK_RET(concatTensor != nullptr, nullptr);
     164            0 :                 tensorListB.emplace_back(concatTensor);
     165              :                 tensorListOnce.clear();
     166              :             }
     167              :         }
     168            2 :         if (!tensorListOnce.empty()) {
     169            2 :             if (tensorListOnce.size() == 1) {
     170            0 :                 tensorListB.emplace_back(tensorListOnce.front());
     171              :             } else {
     172            2 :                 auto aclTensorListTail = executor->AllocTensorList(tensorListOnce.data(), tensorListOnce.size());
     173            2 :                 auto concatTensorTail = l0op::ConcatD(aclTensorListTail, dim, executor);
     174            2 :                 CHECK_RET(concatTensorTail != nullptr, nullptr);
     175            2 :                 tensorListB.emplace_back(concatTensorTail);
     176              :             }
     177              :             tensorListOnce.clear();
     178              :         }
     179            2 :         tensorListA = tensorListB;
     180              :     }
     181              : 
     182            2 :     CHECK_RET(!tensorListA.empty(), nullptr);
     183            2 :     return tensorListA.front();
     184              : }
     185              : 
     186            0 : static const aclTensor* GetMask(const aclTensor* input, const aclTensor* batchSizes, const aclTensor* h,
     187              :                                 aclOpExecutor* executor)
     188              : {
     189            0 :     auto inputShape = input->GetViewShape();
     190            0 :     auto hShape = h->GetViewShape();
     191            0 :     auto inputDtype = input->GetDataType();
     192            0 :     auto batchSize = inputShape[BATCH_DIM];
     193            0 :     auto timeStep = inputShape[SEQUENCE_DIM];
     194            0 :     auto start = executor->AllocScalar(0);
     195            0 :     auto end = executor->AllocScalar(batchSize);
     196            0 :     auto step = executor->AllocScalar(1);
     197            0 :     gert::Shape arrangeShape;
     198              :     arrangeShape.AppendDim(batchSize);
     199            0 :     auto arrangeSizeTensor = executor->AllocTensor(arrangeShape, op::DataType::DT_INT64, Format::FORMAT_ND);
     200            0 :     CHECK_RET(arrangeSizeTensor != nullptr, nullptr);
     201            0 :     auto arrangeTensor = l0op::Arange(start, end, step, arrangeSizeTensor, false, executor);
     202            0 :     CHECK_RET(arrangeTensor != nullptr, nullptr);
     203            0 :     FVector<int64_t> broadTVector{timeStep, batchSize};
     204            0 :     aclIntArray* broadTArray = executor->AllocIntArray(broadTVector.data(), DIM_TWO);
     205            0 :     CHECK_RET(broadTArray != nullptr, nullptr);
     206            0 :     auto arrangeReshapeTensor = l0op::UnsqueezeNd(arrangeTensor, DIM_ZERO, executor);
     207            0 :     CHECK_RET(arrangeReshapeTensor != nullptr, nullptr);
     208            0 :     auto arrangeBroadTTensor = l0op::BroadcastTo(arrangeReshapeTensor, broadTArray, executor);
     209            0 :     CHECK_RET(arrangeBroadTTensor != nullptr, nullptr);
     210              : 
     211            0 :     auto batchSizesReshapeTensor = l0op::UnsqueezeNd(batchSizes, DIM_ONE, executor);
     212            0 :     CHECK_RET(batchSizesReshapeTensor != nullptr, nullptr);
     213            0 :     auto batchSizesBroadTTensor = l0op::BroadcastTo(batchSizesReshapeTensor, broadTArray, executor);
     214            0 :     CHECK_RET(batchSizesBroadTTensor != nullptr, nullptr);
     215              : 
     216            0 :     const aclTensor* mask = l0op::Greater(batchSizesBroadTTensor, arrangeBroadTTensor, executor);
     217            0 :     CHECK_RET(mask != nullptr, nullptr);
     218            0 :     auto seqLengthWithoutHidden = l0op::Cast(mask, inputDtype, executor);
     219            0 :     CHECK_RET(seqLengthWithoutHidden != nullptr, nullptr);
     220              : 
     221            0 :     FVector<int64_t> broadVector{timeStep, batchSize, hShape[HIDDEN_DIM]};
     222            0 :     aclIntArray* broadArray = executor->AllocIntArray(broadVector.data(), DIM_THREE);
     223            0 :     CHECK_RET(broadArray != nullptr, nullptr);
     224            0 :     auto seqLengthReshape = l0op::UnsqueezeNd(seqLengthWithoutHidden, DIM_TWO, executor);
     225            0 :     CHECK_RET(seqLengthReshape != nullptr, nullptr);
     226            0 :     auto seqlength = l0op::BroadcastTo(seqLengthReshape, broadArray, executor);
     227            0 :     CHECK_RET(seqlength != nullptr, nullptr);
     228              :     return seqlength;
     229              : }
     230              : 
     231            5 : static std::array<const aclTensor*, OUT_NUM> ExecLstmBackward(
     232              :     const aclTensor* input, const aclTensor* initH, const aclTensor* initC, const aclTensor* weight,
     233              :     const aclTensor* bias, const aclTensor* dy, const aclTensor* dh, const aclTensor* dc, const aclTensor* seqLength,
     234              :     const aclTensor* i, const aclTensor* j, const aclTensor* f, const aclTensor* o, const aclTensor* h,
     235              :     const aclTensor* c, const aclTensor* tanhc, aclOpExecutor* executor, bool flagDirection = false)
     236              : {
     237            5 :     const char* direction = flagDirection ? "REDIRECTIONAL" : "UNIDIRECTIONAL";
     238              :     const char* gateOrder = "ifjo";
     239              :     std::array<const aclTensor*, OUT_NUM> nullptrRes{nullptr, nullptr, nullptr, nullptr, nullptr};
     240            5 :     auto result = l0op::SingleLayerLstmGrad(input, weight, bias, nullptr, initH, initC, h, c, dy, dh, dc, i, j, f, o,
     241              :                                             tanhc, seqLength, direction, gateOrder, executor);
     242            5 :     CHECK_RET(result[RESULT_WEIGHT_GRAD_INDEX] != nullptr && result[RESULT_BIAS_GRAD_INDEX] != nullptr &&
     243              :                   result[RESULT_INPUT_GRAD_INDEX] != nullptr && result[RESULT_HIDDEN_GRAD_INDEX] != nullptr &&
     244              :                   result[RESULT_CELL_GRAD_INDEX] != nullptr,
     245              :               nullptrRes);
     246            5 :     return result;
     247              : }
     248              : 
     249            3 : static FVector<const aclTensor*> GetWeightBiasFromParams(const aclTensorList* params, bool hasBias, bool bidirectional,
     250              :                                                          int64_t paramNumPerLayer, int64_t layerIdx,
     251              :                                                          aclOpExecutor* executor)
     252              : {
     253              :     FVector<const aclTensor*> result{};
     254              :     FVector<const aclTensor*> nullptrRes{};
     255            3 :     int64_t layerOffset = (layerIdx - 1) * paramNumPerLayer;
     256              : 
     257              :     // 前向权重
     258            3 :     FVector<const aclTensor*> weightForwardVector = {(*params)[layerOffset + WEIGHT_INPUT_INDEX],
     259            3 :                                                      (*params)[layerOffset + WEIGHT_HIDDEN_INDEX]};
     260            3 :     const aclTensorList* weightForwardList = executor->AllocTensorList(weightForwardVector.data(),
     261              :                                                                        weightForwardVector.size());
     262            3 :     CHECK_RET(weightForwardList != nullptr, nullptrRes);
     263            3 :     auto weightForward = l0op::ConcatD(weightForwardList, 1, executor);
     264            3 :     CHECK_RET(weightForward != nullptr, nullptrRes);
     265            3 :     result.emplace_back(weightForward);
     266              : 
     267            3 :     if (hasBias && bidirectional) {
     268            2 :         auto biasForward = l0op::Add((*params)[layerOffset + BIAS_INPUT_INDEX],
     269            2 :                                      (*params)[layerOffset + BIAS_HIDDEN_INDEX], executor);
     270            2 :         result.emplace_back(biasForward);
     271              :         // 后向权重
     272              :         FVector<const aclTensor*> weightBackwardVector = {
     273            2 :             (*params)[layerOffset + NUM_WITH_B_AND_BID / BI_DIRECTION + WEIGHT_INPUT_INDEX],
     274            2 :             (*params)[layerOffset + NUM_WITH_B_AND_BID / BI_DIRECTION + WEIGHT_HIDDEN_INDEX]};
     275            2 :         const aclTensorList* weightBackwardList = executor->AllocTensorList(weightBackwardVector.data(),
     276              :                                                                             weightBackwardVector.size());
     277            2 :         CHECK_RET(weightBackwardList != nullptr, nullptrRes);
     278            2 :         auto weightBackward = l0op::ConcatD(weightBackwardList, REDUCE_DIM, executor);
     279            2 :         CHECK_RET(weightBackward != nullptr, nullptrRes);
     280            2 :         result.emplace_back(weightBackward);
     281              : 
     282            2 :         auto biasBackward = l0op::Add((*params)[layerOffset + NUM_WITH_B_AND_BID / BI_DIRECTION + BIAS_INPUT_INDEX],
     283            2 :                                       (*params)[layerOffset + NUM_WITH_B_AND_BID / BI_DIRECTION + BIAS_HIDDEN_INDEX],
     284            2 :                                       executor);
     285            2 :         CHECK_RET(biasBackward != nullptr, nullptrRes);
     286            2 :         result.emplace_back(biasBackward);
     287            1 :     } else if (!hasBias && bidirectional) {
     288              :         FVector<const aclTensor*> weightBackwardVector = {
     289            0 :             (*params)[layerOffset + NUM_WITH_B_OR_BID / BI_DIRECTION + WEIGHT_INPUT_INDEX],
     290            0 :             (*params)[layerOffset + NUM_WITH_B_OR_BID / BI_DIRECTION + WEIGHT_HIDDEN_INDEX]};
     291            0 :         const aclTensorList* weightBackwardList = executor->AllocTensorList(weightBackwardVector.data(),
     292              :                                                                             weightBackwardVector.size());
     293            0 :         CHECK_RET(weightBackwardList != nullptr, nullptrRes);
     294            0 :         auto weightBackward = l0op::ConcatD(weightBackwardList, REDUCE_DIM, executor);
     295            0 :         CHECK_RET(weightBackward != nullptr, nullptrRes);
     296            0 :         const aclTensor* emptyTensor = nullptr;
     297            0 :         result.emplace_back(emptyTensor);
     298            0 :         result.emplace_back(weightBackward);
     299            0 :         result.emplace_back(emptyTensor);
     300            1 :     } else if (hasBias && !bidirectional) {
     301            1 :         auto biasForward = l0op::Add((*params)[layerOffset + BIAS_INPUT_INDEX],
     302            1 :                                      (*params)[layerOffset + BIAS_HIDDEN_INDEX], executor);
     303            1 :         CHECK_RET(biasForward != nullptr, nullptrRes);
     304            1 :         result.emplace_back(biasForward);
     305              :     } else {
     306            0 :         const aclTensor* biasForward = nullptr;
     307            0 :         result.emplace_back(biasForward);
     308              :     }
     309              :     return result;
     310              : }
     311              : 
     312            0 : static std::array<const aclTensor*, HC_TENSOR_COUNT * 2> CreateSliceTensors(int64_t layerIndex,
     313              :                                                                             const aclTensor* initHMultiLayer,
     314              :                                                                             const aclTensor* initCMultiLayer,
     315              :                                                                             const aclTensor* dh, const aclTensor* dc,
     316              :                                                                             aclOpExecutor* executor)
     317              : {
     318            0 :     std::array<const aclTensor*, HC_TENSOR_COUNT * 2> result{nullptr, nullptr, nullptr, nullptr};
     319            0 :     auto hShape = initHMultiLayer->GetViewShape();
     320              :     // 创建偏移量数组
     321            0 :     FVector<int64_t> offsetVector{layerIndex, 0, 0};
     322            0 :     aclIntArray* offsetArray = executor->AllocIntArray(offsetVector.data(), offsetVector.size());
     323            0 :     CHECK_RET(offsetArray != nullptr, result);
     324              : 
     325              :     // 创建大小数组
     326            0 :     FVector<int64_t> sizeVector{1, hShape[BATCH_DIM], hShape[HIDDEN_DIM]};
     327            0 :     aclIntArray* sizeArray = executor->AllocIntArray(sizeVector.data(), sizeVector.size());
     328            0 :     CHECK_RET(sizeArray != nullptr, result);
     329              : 
     330              :     // 创建四个切片张量
     331            0 :     result[SLICE_INIT_H_INDEX] = l0op::Slice(initHMultiLayer, offsetArray, sizeArray, executor);
     332            0 :     result[SLICE_INIT_C_INDEX] = l0op::Slice(initCMultiLayer, offsetArray, sizeArray, executor);
     333            0 :     result[SLICE_DH_INDEX] = l0op::Slice(dh, offsetArray, sizeArray, executor);
     334            0 :     result[SLICE_DC_INDEX] = l0op::Slice(dc, offsetArray, sizeArray, executor);
     335            0 :     return result;
     336              : }
     337              : 
     338              : static std::tuple<const aclTensor*, std::vector<const aclTensor*>, std::vector<const aclTensor*>,
     339              :                   std::vector<const aclTensor*>, std::vector<const aclTensor*>>
     340            1 : LstmBackwardSingleLayerDirec(const aclTensor* input, const aclTensor* initHMultiLayer, const aclTensor* initCMultiLayer,
     341              :                              const aclTensorList* params, const aclTensor* dy, const aclTensor* dh, const aclTensor* dc,
     342              :                              const aclTensor* seqLength, const aclTensorList* i, const aclTensorList* j,
     343              :                              const aclTensorList* f, const aclTensorList* o, const aclTensorList* h,
     344              :                              const aclTensorList* c, const aclTensorList* tanhc, int64_t layersTemp, int64_t numLayers,
     345              :                              bool hasBias, int64_t paramNumPerLayer, aclOpExecutor* executor)
     346              : {
     347            1 :     auto inputCur = layersTemp == 1 ? input : (*h)[layersTemp - BI_DIRECTION];
     348            1 :     auto nullptrRes = std::make_tuple(nullptr, std::vector<const aclTensor*>(), std::vector<const aclTensor*>(),
     349            1 :                                       std::vector<const aclTensor*>(), std::vector<const aclTensor*>());
     350              :     const aclTensor* initHCur = initHMultiLayer;
     351              :     const aclTensor* initCCur = initCMultiLayer;
     352              :     const aclTensor* dhCur = dh;
     353              :     const aclTensor* dcCur = dc;
     354            1 :     auto weightBias = GetWeightBiasFromParams(params, hasBias, false, paramNumPerLayer, layersTemp, executor);
     355            1 :     const aclTensor* weightCur = weightBias[0];
     356            1 :     const aclTensor* biasCur = weightBias[1];
     357            1 :     CHECK_RET(weightCur != nullptr, nullptrRes);
     358            1 :     if (hasBias) {
     359            1 :         CHECK_RET(biasCur != nullptr, nullptrRes);
     360              :     }
     361              : 
     362            1 :     if (numLayers > 1) {
     363            0 :         auto sliceTensors = CreateSliceTensors(layersTemp - 1, initHMultiLayer, initCMultiLayer, dh, dc, executor);
     364            0 :         CHECK_RET(sliceTensors[SLICE_INIT_H_INDEX] != nullptr && sliceTensors[SLICE_INIT_C_INDEX] != nullptr &&
     365              :                       sliceTensors[SLICE_DH_INDEX] != nullptr && sliceTensors[SLICE_DC_INDEX] != nullptr,
     366              :                   nullptrRes);
     367              :         initHCur = sliceTensors[SLICE_INIT_H_INDEX];
     368              :         initCCur = sliceTensors[SLICE_INIT_C_INDEX];
     369              :         dhCur = sliceTensors[SLICE_DH_INDEX];
     370              :         dcCur = sliceTensors[SLICE_DC_INDEX];
     371              :     }
     372            1 :     auto result = ExecLstmBackward(inputCur, initHCur, initCCur, weightCur, biasCur, dy, dhCur, dcCur, seqLength,
     373              :                                    (*i)[layersTemp - 1], (*j)[layersTemp - 1], (*f)[layersTemp - 1],
     374              :                                    (*o)[layersTemp - 1], (*h)[layersTemp - 1], (*c)[layersTemp - 1],
     375            1 :                                    (*tanhc)[layersTemp - 1], executor, false);
     376            1 :     std::vector<const aclTensor*> dwVector = {result[RESULT_WEIGHT_GRAD_INDEX]};
     377            1 :     std::vector<const aclTensor*> dbVector = {result[RESULT_BIAS_GRAD_INDEX]};
     378            1 :     std::vector<const aclTensor*> dhPrevVector = {result[RESULT_HIDDEN_GRAD_INDEX]};
     379            1 :     std::vector<const aclTensor*> dcPrevVector = {result[RESULT_CELL_GRAD_INDEX]};
     380            1 :     return std::tie(result[RESULT_INPUT_GRAD_INDEX], dhPrevVector, dcPrevVector, dwVector, dbVector);
     381              : }
     382              : 
     383            3 : static bool CheckTupleNotNull(
     384              :     const std::tuple<const aclTensor*, std::vector<const aclTensor*>, std::vector<const aclTensor*>,
     385              :                      std::vector<const aclTensor*>, std::vector<const aclTensor*>>& tuple)
     386              : {
     387            3 :     if (!std::get<0>(tuple)) {
     388              :         return false;
     389              :     }
     390              :     const auto& dhVec = std::get<INDEX_ONE>(tuple);
     391              :     const auto& dcVec = std::get<INDEX_TWO>(tuple);
     392              :     const auto& dwVec = std::get<INDEX_THREE>(tuple);
     393              :     const auto& dbVec = std::get<INDEX_FOUR>(tuple);
     394            6 :     return std::all_of(dwVec.begin(), dwVec.end(), [](auto* ptr) { return ptr != nullptr; }) &&
     395            3 :            std::all_of(dbVec.begin(), dbVec.end(), [](auto* ptr) { return ptr != nullptr; }) &&
     396            6 :            std::all_of(dhVec.begin(), dhVec.end(), [](auto* ptr) { return ptr != nullptr; }) &&
     397            3 :            std::all_of(dcVec.begin(), dcVec.end(), [](auto* ptr) { return ptr != nullptr; });
     398              : }
     399              : 
     400              : static std::tuple<const aclTensor*, std::vector<const aclTensor*>, std::vector<const aclTensor*>,
     401              :                   std::vector<const aclTensor*>, std::vector<const aclTensor*>>
     402            1 : LstmBackwardMultiLayerDirec(const aclTensor* input, const aclTensor* initHMultiLayer, const aclTensor* initCMultiLayer,
     403              :                             const aclTensorList* params, const aclTensor* dy, const aclTensor* dh, const aclTensor* dc,
     404              :                             const aclTensor* seqLength, const aclTensorList* i, const aclTensorList* j,
     405              :                             const aclTensorList* f, const aclTensorList* o, const aclTensorList* h,
     406              :                             const aclTensorList* c, const aclTensorList* tanhc, int64_t numLayers, int64_t layersTemp,
     407              :                             bool hasBias, int64_t paramNumPerLayer, aclOpExecutor* executor)
     408              : {
     409            1 :     const aclTensor* lastLayerDy = nullptr;
     410              :     std::vector<const aclTensor*> lastLayerDhPrevVector{};
     411              :     std::vector<const aclTensor*> lastLayerDcPrevVector{};
     412              :     std::vector<const aclTensor*> lastLayerDwVector{};
     413              :     std::vector<const aclTensor*> lastLayerDbVector{};
     414            1 :     auto nullptrRes = std::make_tuple(nullptr, std::vector<const aclTensor*>(), std::vector<const aclTensor*>(),
     415            1 :                                       std::vector<const aclTensor*>(), std::vector<const aclTensor*>());
     416            1 :     auto hShape = initHMultiLayer->GetViewShape();
     417            1 :     CHECK_RET(hShape.GetDimNum() == DIM_THREE, nullptrRes);
     418              : 
     419            1 :     if (layersTemp == numLayers) {
     420              :         auto lastLayerOutput = LstmBackwardSingleLayerDirec(input, initHMultiLayer, initCMultiLayer, params, dy, dh, dc,
     421              :                                                             seqLength, i, j, f, o, h, c, tanhc, layersTemp, numLayers,
     422            1 :                                                             hasBias, paramNumPerLayer, executor);
     423            1 :         CHECK_RET(CheckTupleNotNull(lastLayerOutput), nullptrRes);
     424              :         return lastLayerOutput;
     425              :     } else {
     426            0 :         std::tie(lastLayerDy, lastLayerDhPrevVector, lastLayerDcPrevVector, lastLayerDwVector,
     427            0 :                  lastLayerDbVector) = LstmBackwardMultiLayerDirec(input, initHMultiLayer, initCMultiLayer, params, dy,
     428              :                                                                   dh, dc, seqLength, i, j, f, o, h, c, tanhc, numLayers,
     429              :                                                                   layersTemp + 1, hasBias, paramNumPerLayer, executor);
     430            0 :         CHECK_RET(CheckTupleNotNull(std::tie(lastLayerDy, lastLayerDhPrevVector, lastLayerDcPrevVector,
     431              :                                              lastLayerDwVector, lastLayerDbVector)),
     432              :                   nullptrRes);
     433              :     }
     434            0 :     auto weightBias = GetWeightBiasFromParams(params, hasBias, false, paramNumPerLayer, layersTemp, executor);
     435            0 :     const aclTensor* weightCur = weightBias[0];
     436            0 :     const aclTensor* biasCur = weightBias[1];
     437            0 :     CHECK_RET(weightCur != nullptr, nullptrRes);
     438            0 :     if (hasBias) {
     439            0 :         CHECK_RET(biasCur != nullptr, nullptrRes);
     440              :     }
     441              : 
     442            0 :     auto sliceTensors = CreateSliceTensors(layersTemp - 1, initHMultiLayer, initCMultiLayer, dh, dc, executor);
     443              : 
     444            0 :     CHECK_RET(sliceTensors[SLICE_INIT_H_INDEX] != nullptr && sliceTensors[SLICE_INIT_C_INDEX] != nullptr &&
     445              :                   sliceTensors[SLICE_DH_INDEX] != nullptr && sliceTensors[SLICE_DC_INDEX] != nullptr,
     446              :               nullptrRes);
     447              : 
     448            0 :     auto inputCur = layersTemp == 1 ? input : (*h)[layersTemp - BI_DIRECTION];
     449            0 :     auto result = ExecLstmBackward(inputCur, sliceTensors[SLICE_INIT_H_INDEX], sliceTensors[SLICE_INIT_C_INDEX],
     450              :                                    weightCur, biasCur, lastLayerDy, sliceTensors[SLICE_DH_INDEX],
     451              :                                    sliceTensors[SLICE_DC_INDEX], seqLength, (*i)[layersTemp - 1], (*j)[layersTemp - 1],
     452              :                                    (*f)[layersTemp - 1], (*o)[layersTemp - 1], (*h)[layersTemp - 1],
     453              :                                    (*c)[layersTemp - 1], (*tanhc)[layersTemp - 1], executor, false);
     454              : 
     455            0 :     lastLayerDwVector.emplace_back(result[RESULT_WEIGHT_GRAD_INDEX]);
     456            0 :     lastLayerDbVector.emplace_back(result[RESULT_BIAS_GRAD_INDEX]);
     457            0 :     lastLayerDhPrevVector.emplace_back(result[RESULT_HIDDEN_GRAD_INDEX]);
     458            0 :     lastLayerDcPrevVector.emplace_back(result[RESULT_CELL_GRAD_INDEX]);
     459            0 :     return std::tie(result[RESULT_INPUT_GRAD_INDEX], lastLayerDhPrevVector, lastLayerDcPrevVector, lastLayerDwVector,
     460              :                     lastLayerDbVector);
     461              : }
     462              : 
     463            4 : static std::array<const aclTensor*, 5> CreateBidDirectionTensors(const aclTensor* initHMultiLayer,
     464              :                                                                  const aclTensor* initCMultiLayer, const aclTensor* dh,
     465              :                                                                  const aclTensor* dc, const aclTensor* dy,
     466              :                                                                  int64_t layersTemp, aclOpExecutor* executor,
     467              :                                                                  bool isBackward)
     468              : {
     469              :     // 定义统一的错误返回值
     470              :     const std::array<const aclTensor*, 5> nullptrRes = {nullptr, nullptr, nullptr, nullptr, nullptr};
     471            4 :     auto hShape = initHMultiLayer->GetViewShape();
     472            4 :     CHECK_RET(hShape.GetDimNum() == DIM_THREE, nullptrRes);
     473            4 :     auto dyShape = dy->GetViewShape();
     474            4 :     CHECK_RET(dyShape.GetDimNum() == DIM_THREE, nullptrRes);
     475              : 
     476              :     // 根据方向计算偏移量
     477            4 :     int64_t initOffset = isBackward ? (BI_DIRECTION * layersTemp - 1) : (BI_DIRECTION * layersTemp - BI_DIRECTION);
     478            4 :     int64_t dyStart = isBackward ? (dyShape[HIDDEN_DIM] / BI_DIRECTION) : 0;
     479              : 
     480              :     // 创建初始隐藏状态和细胞状态
     481            4 :     FVector<int64_t> offsetVectorInit{initOffset, DIM_ZERO, DIM_ZERO};
     482            4 :     aclIntArray* offsetArrayInit = executor->AllocIntArray(offsetVectorInit.data(), offsetVectorInit.size());
     483            4 :     CHECK_RET(offsetArrayInit != nullptr, nullptrRes);
     484              : 
     485            4 :     FVector<int64_t> sizeVectorInit{1, hShape[BATCH_DIM], hShape[HIDDEN_DIM]};
     486            4 :     aclIntArray* sizeArrayInit = executor->AllocIntArray(sizeVectorInit.data(), sizeVectorInit.size());
     487            4 :     CHECK_RET(sizeArrayInit != nullptr, nullptrRes);
     488              : 
     489            4 :     auto initH = l0op::Slice(initHMultiLayer, offsetArrayInit, sizeArrayInit, executor);
     490            4 :     CHECK_RET(initH != nullptr, nullptrRes);
     491              : 
     492            4 :     auto initC = l0op::Slice(initCMultiLayer, offsetArrayInit, sizeArrayInit, executor);
     493            4 :     CHECK_RET(initC != nullptr, nullptrRes);
     494              : 
     495            4 :     auto dhDir = l0op::Slice(dh, offsetArrayInit, sizeArrayInit, executor);
     496            4 :     CHECK_RET(dhDir != nullptr, nullptrRes);
     497              : 
     498            4 :     auto dcDir = l0op::Slice(dc, offsetArrayInit, sizeArrayInit, executor);
     499            4 :     CHECK_RET(dcDir != nullptr, nullptrRes);
     500              : 
     501              :     // 创建输出梯度
     502            4 :     FVector<int64_t> offsetVectorDy{DIM_ZERO, DIM_ZERO, dyStart};
     503            4 :     aclIntArray* offsetArrayDy = executor->AllocIntArray(offsetVectorDy.data(), offsetVectorDy.size());
     504            4 :     CHECK_RET(offsetArrayDy != nullptr, nullptrRes);
     505              : 
     506            4 :     FVector<int64_t> sizeVectorDy{dyShape[SEQUENCE_DIM], dyShape[BATCH_DIM], dyShape[HIDDEN_DIM] / BI_DIRECTION};
     507            4 :     aclIntArray* sizeArrayDy = executor->AllocIntArray(sizeVectorDy.data(), sizeVectorDy.size());
     508            4 :     CHECK_RET(sizeArrayDy != nullptr, nullptrRes);
     509              : 
     510            4 :     auto dyDir = l0op::Slice(dy, offsetArrayDy, sizeArrayDy, executor);
     511            4 :     CHECK_RET(dyDir != nullptr, nullptrRes);
     512              : 
     513            4 :     return {initH, initC, dhDir, dcDir, dyDir};
     514              : }
     515              : 
     516            2 : static const aclTensor* CreateInputTensor(const aclTensor* input, const aclTensorList* h, int64_t layersTemp,
     517              :                                           aclOpExecutor* executor)
     518              : {
     519            2 :     if (layersTemp == 1) {
     520              :         return input;
     521              :     } else {
     522            1 :         auto inputCurForward = (*h)[BI_DIRECTION * (layersTemp - BI_DIRECTION)];
     523            1 :         CHECK_RET(inputCurForward != nullptr, nullptr);
     524              : 
     525            1 :         auto inputCurBackward = (*h)[BI_DIRECTION * (layersTemp - BI_DIRECTION) + 1];
     526            1 :         CHECK_RET(inputCurBackward != nullptr, nullptr);
     527              : 
     528            1 :         op::FVector<const aclTensor*> inputCurVector = {inputCurForward, inputCurBackward};
     529            1 :         const aclTensorList* inputCurList = executor->AllocTensorList(inputCurVector.data(), inputCurVector.size());
     530            1 :         CHECK_RET(inputCurList != nullptr, nullptr);
     531              : 
     532            1 :         auto result = l0op::ConcatD(inputCurList, CONCAT_DIM_HIDDEN, executor);
     533            1 :         CHECK_RET(result != nullptr, nullptr);
     534              : 
     535              :         return result;
     536              :     }
     537              : }
     538              : 
     539              : static std::tuple<const aclTensor*, std::vector<const aclTensor*>, std::vector<const aclTensor*>,
     540              :                   std::vector<const aclTensor*>, std::vector<const aclTensor*>>
     541            2 : MergeResults(std::array<const aclTensor*, 5> resultForward, std::array<const aclTensor*, 5> resultBackward,
     542              :              std::vector<const aclTensor*>& dhVector, std::vector<const aclTensor*>& dcVector,
     543              :              std::vector<const aclTensor*>& dwVector, std::vector<const aclTensor*>& dbVector, aclOpExecutor* executor)
     544              : {
     545            2 :     auto nullptrRes = std::make_tuple(nullptr, std::vector<const aclTensor*>(), std::vector<const aclTensor*>(),
     546            2 :                                       std::vector<const aclTensor*>(), std::vector<const aclTensor*>());
     547              :     std::vector<const aclTensor*> catDhPrevVector = {resultForward[RESULT_HIDDEN_GRAD_INDEX],
     548            2 :                                                      resultBackward[RESULT_HIDDEN_GRAD_INDEX]};
     549              :     std::vector<const aclTensor*> catDcPrevVector = {resultForward[RESULT_CELL_GRAD_INDEX],
     550            2 :                                                      resultBackward[RESULT_CELL_GRAD_INDEX]};
     551            2 :     dwVector.emplace_back(resultForward[RESULT_WEIGHT_GRAD_INDEX]);
     552            2 :     dwVector.emplace_back(resultBackward[RESULT_WEIGHT_GRAD_INDEX]);
     553            2 :     dbVector.emplace_back(resultForward[RESULT_BIAS_GRAD_INDEX]);
     554            2 :     dbVector.emplace_back(resultBackward[RESULT_BIAS_GRAD_INDEX]);
     555            2 :     dhVector.emplace_back(resultForward[RESULT_HIDDEN_GRAD_INDEX]);
     556            2 :     dhVector.emplace_back(resultBackward[RESULT_HIDDEN_GRAD_INDEX]);
     557            2 :     dcVector.emplace_back(resultForward[RESULT_CELL_GRAD_INDEX]);
     558            2 :     dcVector.emplace_back(resultBackward[RESULT_CELL_GRAD_INDEX]);
     559              : 
     560            2 :     auto dx = l0op::Add(resultForward[RESULT_INPUT_GRAD_INDEX], resultBackward[RESULT_INPUT_GRAD_INDEX], executor);
     561            2 :     CHECK_RET(dx != nullptr, nullptrRes);
     562              :     return std::make_tuple(dx, dhVector, dcVector, dwVector, dbVector);
     563              : }
     564              : 
     565              : static std::tuple<const aclTensor*, std::vector<const aclTensor*>, std::vector<const aclTensor*>,
     566              :                   std::vector<const aclTensor*>, std::vector<const aclTensor*>>
     567            1 : LstmBackwardSingleLayerBidirec(const aclTensor* input, const aclTensor* initHMultiLayer,
     568              :                                const aclTensor* initCMultiLayer, const aclTensorList* params, const aclTensor* dy,
     569              :                                const aclTensor* dh, const aclTensor* dc, const aclTensor* seqLength,
     570              :                                const aclTensorList* i, const aclTensorList* j, const aclTensorList* f,
     571              :                                const aclTensorList* o, const aclTensorList* h, const aclTensorList* c,
     572              :                                const aclTensorList* tanhc, int64_t layersTemp, bool hasBias, int64_t paramNumPerLayer,
     573              :                                aclOpExecutor* executor)
     574              : {
     575            1 :     auto nullptrRes = std::make_tuple(nullptr, std::vector<const aclTensor*>(), std::vector<const aclTensor*>(),
     576            1 :                                       std::vector<const aclTensor*>(), std::vector<const aclTensor*>());
     577              : 
     578              :     const aclTensor* inputCur = nullptr;
     579            1 :     auto weightBias = GetWeightBiasFromParams(params, hasBias, true, paramNumPerLayer, layersTemp, executor);
     580            1 :     const aclTensor* weightForwardCur = weightBias[0];
     581            1 :     CHECK_RET(weightForwardCur != nullptr, nullptrRes);
     582            1 :     const aclTensor* weightBackwardCur = weightBias[INDEX_TWO];
     583            1 :     CHECK_RET(weightBackwardCur != nullptr, nullptrRes);
     584            1 :     const aclTensor* biasForwardCur = weightBias[1];
     585            1 :     const aclTensor* biasBackwardCur = weightBias[INDEX_THREE];
     586            1 :     if (hasBias) {
     587            1 :         CHECK_RET(biasForwardCur != nullptr, nullptrRes);
     588            1 :         CHECK_RET(biasBackwardCur != nullptr, nullptrRes);
     589              :     }
     590              : 
     591            1 :     auto forwardTensors = CreateBidDirectionTensors(initHMultiLayer, initCMultiLayer, dh, dc, dy, layersTemp, executor,
     592              :                                                     false);
     593            1 :     CHECK_RET(forwardTensors[BIDIR_INIT_H_INDEX] != nullptr, nullptrRes); // initHForward
     594            1 :     CHECK_RET(forwardTensors[BIDIR_INIT_C_INDEX] != nullptr, nullptrRes); // initCForward
     595            1 :     CHECK_RET(forwardTensors[BIDIR_DH_INDEX] != nullptr, nullptrRes);     // dhForward
     596            1 :     CHECK_RET(forwardTensors[BIDIR_DC_INDEX] != nullptr, nullptrRes);     // dcForward
     597            1 :     CHECK_RET(forwardTensors[BIDIR_DY_INDEX] != nullptr, nullptrRes);     // dyForward
     598              : 
     599              :     // 创建输入
     600            1 :     inputCur = CreateInputTensor(input, h, layersTemp, executor);
     601            1 :     CHECK_RET(inputCur != nullptr, nullptrRes);
     602              :     // 前向LSTM计算
     603            1 :     auto resultForward = ExecLstmBackward(
     604              :         inputCur, forwardTensors[BIDIR_INIT_H_INDEX], forwardTensors[BIDIR_INIT_C_INDEX], weightForwardCur,
     605              :         biasForwardCur, forwardTensors[BIDIR_DY_INDEX], forwardTensors[BIDIR_DH_INDEX], forwardTensors[BIDIR_DC_INDEX],
     606              :         seqLength, (*i)[layersTemp * BI_DIRECTION - BI_DIRECTION], (*j)[layersTemp * BI_DIRECTION - BI_DIRECTION],
     607              :         (*f)[layersTemp * BI_DIRECTION - BI_DIRECTION], (*o)[layersTemp * BI_DIRECTION - BI_DIRECTION],
     608              :         (*h)[layersTemp * BI_DIRECTION - BI_DIRECTION], (*c)[layersTemp * BI_DIRECTION - BI_DIRECTION],
     609            1 :         (*tanhc)[layersTemp * BI_DIRECTION - BI_DIRECTION], executor, false);
     610              : 
     611            1 :     auto backwardTensors = CreateBidDirectionTensors(initHMultiLayer, initCMultiLayer, dh, dc, dy, layersTemp, executor,
     612              :                                                      true);
     613            1 :     CHECK_RET(backwardTensors[BIDIR_INIT_H_INDEX] != nullptr, nullptrRes); // initHBackward
     614            1 :     CHECK_RET(backwardTensors[BIDIR_INIT_C_INDEX] != nullptr, nullptrRes); // initCBackward
     615            1 :     CHECK_RET(backwardTensors[BIDIR_DH_INDEX] != nullptr, nullptrRes);     // dhBackward
     616            1 :     CHECK_RET(backwardTensors[BIDIR_DC_INDEX] != nullptr, nullptrRes);     // dcBackward
     617            1 :     CHECK_RET(backwardTensors[BIDIR_DY_INDEX] != nullptr, nullptrRes);     // dyBackward
     618              : 
     619              :     // 后向LSTM计算
     620            1 :     auto resultBackward = ExecLstmBackward(
     621              :         inputCur, backwardTensors[BIDIR_INIT_H_INDEX], backwardTensors[BIDIR_INIT_C_INDEX], weightBackwardCur,
     622              :         biasBackwardCur, backwardTensors[BIDIR_DY_INDEX], backwardTensors[BIDIR_DH_INDEX],
     623              :         backwardTensors[BIDIR_DC_INDEX], seqLength, (*i)[layersTemp * BI_DIRECTION - 1],
     624              :         (*j)[layersTemp * BI_DIRECTION - 1], (*f)[layersTemp * BI_DIRECTION - 1], (*o)[layersTemp * BI_DIRECTION - 1],
     625              :         (*h)[layersTemp * BI_DIRECTION - 1], (*c)[layersTemp * BI_DIRECTION - 1],
     626            1 :         (*tanhc)[layersTemp * BI_DIRECTION - 1], executor, true);
     627              : 
     628              :     // 合并结果
     629              :     std::vector<const aclTensor*> dwVector{};
     630              :     std::vector<const aclTensor*> dbVector{};
     631              :     std::vector<const aclTensor*> dhPrevVector{};
     632              :     std::vector<const aclTensor*> dcPrevVector{};
     633              :     auto mergedResult = MergeResults(resultForward, resultBackward, dhPrevVector, dcPrevVector, dwVector, dbVector,
     634            1 :                                      executor);
     635              :     return mergedResult;
     636              : }
     637              : 
     638              : static std::tuple<const aclTensor*, std::vector<const aclTensor*>, std::vector<const aclTensor*>,
     639              :                   std::vector<const aclTensor*>, std::vector<const aclTensor*>>
     640            2 : LstmBackwardMultiLayerBidirec(const aclTensor* input, const aclTensor* initHMultiLayer,
     641              :                               const aclTensor* initCMultiLayer, const aclTensorList* params, const aclTensor* dy,
     642              :                               const aclTensor* dh, const aclTensor* dc, const aclTensor* seqLength,
     643              :                               const aclTensorList* i, const aclTensorList* j, const aclTensorList* f,
     644              :                               const aclTensorList* o, const aclTensorList* h, const aclTensorList* c,
     645              :                               const aclTensorList* tanhc, int64_t numLayers, int64_t layersTemp, bool hasBias,
     646              :                               int64_t paramNumPerLayer, aclOpExecutor* executor)
     647              : {
     648            2 :     const aclTensor* lastLayerDy = nullptr;
     649              :     std::vector<const aclTensor*> lastLayerDhPrevVector{};
     650              :     std::vector<const aclTensor*> lastLayerDcPrevVector{};
     651              :     std::vector<const aclTensor*> lastLayerDwVector{};
     652              :     std::vector<const aclTensor*> lastLayerDbVector{};
     653              : 
     654            2 :     auto nullptrRes = std::make_tuple(nullptr, std::vector<const aclTensor*>(), std::vector<const aclTensor*>(),
     655            2 :                                       std::vector<const aclTensor*>(), std::vector<const aclTensor*>());
     656              : 
     657              :     const aclTensor* inputCur = nullptr;
     658              : 
     659              :     // 递归终止条件:处理最后一层
     660            2 :     if (layersTemp == numLayers) {
     661              :         auto lastLayerOutput = LstmBackwardSingleLayerBidirec(input, initHMultiLayer, initCMultiLayer, params, dy, dh,
     662              :                                                               dc, seqLength, i, j, f, o, h, c, tanhc, layersTemp,
     663            1 :                                                               hasBias, paramNumPerLayer, executor);
     664            1 :         CHECK_RET(CheckTupleNotNull(lastLayerOutput), nullptrRes);
     665              :         return lastLayerOutput;
     666              :     } else {
     667              :         // 递归调用处理下一层
     668            1 :         std::tie(lastLayerDy, lastLayerDhPrevVector, lastLayerDcPrevVector, lastLayerDwVector,
     669            2 :                  lastLayerDbVector) = LstmBackwardMultiLayerBidirec(input, initHMultiLayer, initCMultiLayer, params, dy,
     670              :                                                                     dh, dc, seqLength, i, j, f, o, h, c, tanhc,
     671              :                                                                     numLayers, layersTemp + 1, hasBias,
     672              :                                                                     paramNumPerLayer, executor);
     673            2 :         CHECK_RET(CheckTupleNotNull(std::tie(lastLayerDy, lastLayerDhPrevVector, lastLayerDcPrevVector,
     674              :                                              lastLayerDwVector, lastLayerDbVector)),
     675              :                   nullptrRes);
     676              :     }
     677              :     // 获取当前层权重和偏置
     678            1 :     auto weightBias = GetWeightBiasFromParams(params, hasBias, true, paramNumPerLayer, layersTemp, executor);
     679            1 :     const aclTensor* weightForwardCur = weightBias[0];
     680            1 :     CHECK_RET(weightForwardCur != nullptr, nullptrRes);
     681            1 :     const aclTensor* weightBackwardCur = weightBias[INDEX_TWO];
     682            1 :     CHECK_RET(weightBackwardCur != nullptr, nullptrRes);
     683            1 :     const aclTensor* biasForwardCur = weightBias[1];
     684            1 :     const aclTensor* biasBackwardCur = weightBias[INDEX_THREE];
     685            1 :     if (hasBias) {
     686            1 :         CHECK_RET(biasForwardCur != nullptr, nullptrRes);
     687            1 :         CHECK_RET(biasBackwardCur != nullptr, nullptrRes);
     688              :     }
     689              : 
     690              :     // 创建输入张量
     691            1 :     inputCur = CreateInputTensor(input, h, layersTemp, executor);
     692            1 :     CHECK_RET(inputCur != nullptr, nullptrRes);
     693              : 
     694              :     // 使用 std::array 接收前向张量
     695            1 :     auto forwardTensors = CreateBidDirectionTensors(initHMultiLayer, initCMultiLayer, dh, dc, lastLayerDy, layersTemp,
     696              :                                                     executor, false);
     697            1 :     CHECK_RET(forwardTensors[BIDIR_INIT_H_INDEX] != nullptr, nullptrRes); // initHForward
     698            1 :     CHECK_RET(forwardTensors[BIDIR_INIT_C_INDEX] != nullptr, nullptrRes); // initCForward
     699            1 :     CHECK_RET(forwardTensors[BIDIR_DH_INDEX] != nullptr, nullptrRes);     // dhForward
     700            1 :     CHECK_RET(forwardTensors[BIDIR_DC_INDEX] != nullptr, nullptrRes);     // dcForward
     701            1 :     CHECK_RET(forwardTensors[BIDIR_DY_INDEX] != nullptr, nullptrRes);     // dyForward
     702              : 
     703              :     // 前向LSTM计算
     704            1 :     auto resultForward = ExecLstmBackward(
     705              :         inputCur, forwardTensors[BIDIR_INIT_H_INDEX], forwardTensors[BIDIR_INIT_C_INDEX], weightForwardCur,
     706              :         biasForwardCur, forwardTensors[BIDIR_DY_INDEX], forwardTensors[BIDIR_DH_INDEX], forwardTensors[BIDIR_DC_INDEX],
     707              :         seqLength, (*i)[layersTemp * BI_DIRECTION - BI_DIRECTION], (*j)[layersTemp * BI_DIRECTION - BI_DIRECTION],
     708              :         (*f)[layersTemp * BI_DIRECTION - BI_DIRECTION], (*o)[layersTemp * BI_DIRECTION - BI_DIRECTION],
     709              :         (*h)[layersTemp * BI_DIRECTION - BI_DIRECTION], (*c)[layersTemp * BI_DIRECTION - BI_DIRECTION],
     710            1 :         (*tanhc)[layersTemp * BI_DIRECTION - BI_DIRECTION], executor, false);
     711              : 
     712            1 :     auto backwardTensors = CreateBidDirectionTensors(initHMultiLayer, initCMultiLayer, dh, dc, lastLayerDy, layersTemp,
     713              :                                                      executor, true);
     714            1 :     CHECK_RET(backwardTensors[BIDIR_INIT_H_INDEX] != nullptr, nullptrRes); // initHBackward
     715            1 :     CHECK_RET(backwardTensors[BIDIR_INIT_C_INDEX] != nullptr, nullptrRes); // initCBackward
     716            1 :     CHECK_RET(backwardTensors[BIDIR_DH_INDEX] != nullptr, nullptrRes);     // dhBackward
     717            1 :     CHECK_RET(backwardTensors[BIDIR_DC_INDEX] != nullptr, nullptrRes);     // dcBackward
     718            1 :     CHECK_RET(backwardTensors[BIDIR_DY_INDEX] != nullptr, nullptrRes);     // dyBackward
     719              : 
     720              :     // 后向LSTM计算
     721            1 :     auto resultBackward = ExecLstmBackward(
     722              :         inputCur, backwardTensors[BIDIR_INIT_H_INDEX], backwardTensors[BIDIR_INIT_C_INDEX], weightBackwardCur,
     723              :         biasBackwardCur, backwardTensors[BIDIR_DY_INDEX], backwardTensors[BIDIR_DH_INDEX],
     724              :         backwardTensors[BIDIR_DC_INDEX], seqLength, (*i)[layersTemp * BI_DIRECTION - 1],
     725              :         (*j)[layersTemp * BI_DIRECTION - 1], (*f)[layersTemp * BI_DIRECTION - 1], (*o)[layersTemp * BI_DIRECTION - 1],
     726              :         (*h)[layersTemp * BI_DIRECTION - 1], (*c)[layersTemp * BI_DIRECTION - 1],
     727            1 :         (*tanhc)[layersTemp * BI_DIRECTION - 1], executor, true);
     728              : 
     729              :     // 合并结果并更新权重向量
     730              :     auto mergedResult = MergeResults(resultForward, resultBackward, lastLayerDhPrevVector, lastLayerDcPrevVector,
     731            1 :                                      lastLayerDwVector, lastLayerDbVector, executor);
     732              :     return mergedResult;
     733              : }
     734              : 
     735           18 : static bool CheckTensorListNotNull(const aclTensorList* tensorList)
     736              : {
     737           77 :     for (uint64_t index = 0; index < tensorList->Size(); index++) {
     738           59 :         OP_CHECK_NULL((*tensorList)[index], return false);
     739              :     }
     740              :     return true;
     741              : }
     742              : 
     743            2 : static bool CheckNotNull(const aclTensor* input, const aclTensorList* hc, const aclTensorList* params,
     744              :                          const aclTensorList* i, const aclTensorList* j, const aclTensorList* f, const aclTensorList* o,
     745              :                          const aclTensorList* h, const aclTensorList* c, const aclTensorList* tanhc,
     746              :                          const aclTensor* dx, const aclTensor* dhPrev, const aclTensor* dcPrev,
     747              :                          const aclTensorList* dparams, bool hasBias, int64_t numLayers, bool bidirectional)
     748              : {
     749            2 :     OP_CHECK_NULL(input, return false);
     750            2 :     OP_CHECK_NULL(hc, return false);
     751            2 :     OP_CHECK_NULL(params, return false);
     752            2 :     OP_CHECK_NULL(i, return false);
     753            2 :     OP_CHECK_NULL(j, return false);
     754            2 :     OP_CHECK_NULL(f, return false);
     755            2 :     OP_CHECK_NULL(o, return false);
     756            2 :     OP_CHECK_NULL(h, return false);
     757            2 :     OP_CHECK_NULL(c, return false);
     758            2 :     OP_CHECK_NULL(tanhc, return false);
     759            2 :     OP_CHECK_NULL(dx, return false);
     760            2 :     OP_CHECK_NULL(dhPrev, return false);
     761            2 :     OP_CHECK_NULL(dcPrev, return false);
     762            2 :     OP_CHECK_NULL(dparams, return false);
     763            2 :     uint64_t gateLength = bidirectional ? numLayers * BI_DIRECTION : numLayers;
     764            6 :     bool tensorLengthCheck = gateLength == i->Size() && gateLength == j->Size() && gateLength == f->Size() &&
     765            8 :                              gateLength == o->Size() && gateLength == h->Size() && gateLength == c->Size() &&
     766            2 :                              gateLength == tanhc->Size();
     767            2 :     if (hc->Size() != HC_TENSOR_COUNT) {
     768            0 :         OP_LOGE(ACLNN_ERR_PARAM_INVALID, "For inithc tensorlist, the tensor quantities %ld should be %d.", hc->Size(),
     769              :                 HC_TENSOR_COUNT);
     770            0 :         return false;
     771              :     }
     772            2 :     if (!tensorLengthCheck) {
     773            0 :         OP_LOGE(ACLNN_ERR_PARAM_INVALID,
     774              :                 "For tensor lists such as the 4 gates, the tensor quantities should follow consistent patterns.");
     775            0 :         return false;
     776              :     }
     777            2 :     uint64_t paramsLength = hasBias && bidirectional   ? NUM_WITH_B_AND_BID * numLayers :
     778            1 :                             (hasBias || bidirectional) ? NUM_WITH_B_OR_BID * numLayers :
     779            0 :                                                          NUM_NO_B_NO_BIDIR * numLayers;
     780            2 :     bool paramsLengthCheck = paramsLength == params->Size() && paramsLength == dparams->Size();
     781              :     if (!paramsLengthCheck) {
     782            0 :         OP_LOGE(ACLNN_ERR_PARAM_INVALID, "For tensor lists include params and dparams, the tensor quantities should "
     783              :                                          "follow the pattern related to the weigths.");
     784            0 :         return false;
     785              :     }
     786              : 
     787            6 :     return CheckTensorListNotNull(hc) && CheckTensorListNotNull(i) && CheckTensorListNotNull(j) &&
     788            6 :            CheckTensorListNotNull(f) && CheckTensorListNotNull(o) && CheckTensorListNotNull(h) &&
     789            6 :            CheckTensorListNotNull(c) && CheckTensorListNotNull(tanhc) && CheckTensorListNotNull(dparams);
     790              : }
     791              : 
     792           20 : static bool CheckTensorListFormat(const aclTensorList* tensors, const char* listName, const ge::Format format)
     793              : {
     794           99 :     for (uint64_t idx = 0; idx < tensors->Size(); idx++) {
     795           79 :         if ((*tensors)[idx]->GetStorageFormat() != format) {
     796            0 :             OP_LOGE(ACLNN_ERR_PARAM_INVALID, "%s tensor %lu format only support ND", listName, idx);
     797            0 :             return false;
     798              :         }
     799              :     }
     800           20 :     return true;
     801              : }
     802              : 
     803            2 : static bool CheckTensorListsFormat(const aclTensorList* hc, const aclTensorList* params, const aclTensorList* i,
     804              :                                    const aclTensorList* j, const aclTensorList* f, const aclTensorList* o,
     805              :                                    const aclTensorList* h, const aclTensorList* c, const aclTensorList* tanhc,
     806              :                                    const aclTensorList* dparams)
     807              : {
     808            2 :     if (!CheckTensorListFormat(hc, "hc", Format::FORMAT_NCL))
     809              :         return false;
     810            2 :     if (!CheckTensorListFormat(params, "params", Format::FORMAT_ND))
     811              :         return false;
     812            2 :     if (!CheckTensorListFormat(i, "i", Format::FORMAT_NCL))
     813              :         return false;
     814            2 :     if (!CheckTensorListFormat(j, "j", Format::FORMAT_NCL))
     815              :         return false;
     816            2 :     if (!CheckTensorListFormat(f, "f", Format::FORMAT_NCL))
     817              :         return false;
     818            2 :     if (!CheckTensorListFormat(o, "o", Format::FORMAT_NCL))
     819              :         return false;
     820            2 :     if (!CheckTensorListFormat(h, "h", Format::FORMAT_NCL))
     821              :         return false;
     822            2 :     if (!CheckTensorListFormat(c, "c", Format::FORMAT_NCL))
     823              :         return false;
     824            2 :     if (!CheckTensorListFormat(tanhc, "tanhc", Format::FORMAT_NCL))
     825              :         return false;
     826            2 :     if (!CheckTensorListFormat(dparams, "dparams", Format::FORMAT_ND))
     827              :         return false;
     828              :     return true;
     829              : }
     830              : 
     831            2 : static bool CheckFormatValid(const aclTensor* input, const aclTensorList* hc, const aclTensorList* params,
     832              :                              const aclTensor* dy, const aclTensor* dh, const aclTensor* dc, const aclTensorList* i,
     833              :                              const aclTensorList* j, const aclTensorList* f, const aclTensorList* o,
     834              :                              const aclTensorList* h, const aclTensorList* c, const aclTensorList* tanhc,
     835              :                              const aclTensor* dx, const aclTensor* dhPrev, const aclTensor* dcPrev,
     836              :                              const aclTensorList* dparams, const aclTensor* batchSizes = nullptr)
     837              : {
     838            2 :     auto inputFormat = batchSizes == nullptr ? Format::FORMAT_NCL : Format::FORMAT_ND;
     839            2 :     if (input->GetStorageFormat() != inputFormat) {
     840            0 :         OP_LOGE(ACLNN_ERR_PARAM_INVALID, "input format only support ND/NCL");
     841            0 :         return false;
     842              :     }
     843            2 :     if (dy != nullptr && dy->GetStorageFormat() != inputFormat) {
     844            0 :         OP_LOGE(ACLNN_ERR_PARAM_INVALID, "dy format only support ND/NCL");
     845            0 :         return false;
     846              :     }
     847            2 :     if (dh != nullptr && dh->GetStorageFormat() != Format::FORMAT_NCL) {
     848            0 :         OP_LOGE(ACLNN_ERR_PARAM_INVALID, "dh format only support NCL");
     849            0 :         return false;
     850              :     }
     851            2 :     if (dc != nullptr && dc->GetStorageFormat() != Format::FORMAT_NCL) {
     852            0 :         OP_LOGE(ACLNN_ERR_PARAM_INVALID, "dc format only support NCL");
     853            0 :         return false;
     854              :     }
     855            2 :     if (dx->GetStorageFormat() != inputFormat) {
     856            0 :         OP_LOGE(ACLNN_ERR_PARAM_INVALID, "dx format only support ND/NCL");
     857            0 :         return false;
     858              :     }
     859            2 :     if (dhPrev->GetStorageFormat() != Format::FORMAT_NCL) {
     860            0 :         OP_LOGE(ACLNN_ERR_PARAM_INVALID, "dhPrev format only support NCL");
     861            0 :         return false;
     862              :     }
     863            2 :     if (dcPrev->GetStorageFormat() != Format::FORMAT_NCL) {
     864            0 :         OP_LOGE(ACLNN_ERR_PARAM_INVALID, "dcPrev format only support NCL");
     865            0 :         return false;
     866              :     }
     867            2 :     if (batchSizes != nullptr && batchSizes->GetStorageFormat() != Format::FORMAT_ND) {
     868            0 :         OP_LOGE(ACLNN_ERR_PARAM_INVALID, "batchSizes only support ND");
     869            0 :         return false;
     870              :     }
     871            2 :     if (!CheckTensorListsFormat(hc, params, i, j, f, o, h, c, tanhc, dparams)) {
     872              :         return false;
     873              :     }
     874              :     return true;
     875              : }
     876              : 
     877              : // 检查单个张量的数据类型支持和一致性
     878           12 : static bool CheckSingleTensorDtype(const aclTensor* tensor, const char* tensorName, ge::DataType baseDtype)
     879              : {
     880              :     // 检查是否在支持的数据类型列表中
     881           12 :     OP_CHECK_DTYPE_NOT_SUPPORT(tensor, DTYPE_SUPPORT_LIST, return false);
     882              : 
     883              :     // 检查数据类型一致性
     884           12 :     if (tensor->GetDataType() != baseDtype) {
     885            0 :         OP_LOGE(ACLNN_ERR_PARAM_INVALID, "%s tensor dtype inconsistent, expected: %s, actual: %s.", tensorName,
     886              :                 op::ToString(baseDtype).GetString(), op::ToString(tensor->GetDataType()).GetString());
     887            0 :         return false;
     888              :     }
     889              : 
     890              :     return true;
     891              : }
     892              : 
     893              : // 检查张量列表的数据类型支持和一致性
     894           20 : static bool CheckTensorListDtype(const aclTensorList* tensors, const char* listName, ge::DataType baseDtype)
     895              : {
     896           99 :     for (uint64_t idx = 0; idx < tensors->Size(); idx++) {
     897           79 :         const aclTensor* tensor = (*tensors)[idx];
     898              :         // 检查是否在支持的数据类型列表中
     899           79 :         OP_CHECK_DTYPE_NOT_SUPPORT(tensor, DTYPE_SUPPORT_LIST, return false);
     900              : 
     901              :         // 检查数据类型一致性
     902           79 :         if (tensor->GetDataType() != baseDtype) {
     903            0 :             OP_LOGE(ACLNN_ERR_PARAM_INVALID, "%s tensor %lu dtype inconsistent, expected: %s, actual: %s.", listName,
     904              :                     idx, op::ToString(baseDtype).GetString(), op::ToString(tensor->GetDataType()).GetString());
     905            0 :             return false;
     906              :         }
     907              :     }
     908           20 :     return true;
     909              : }
     910              : 
     911            2 : static bool CheckDtypeValid(const aclTensor* input, const aclTensorList* hc, const aclTensorList* params,
     912              :                             const aclTensor* dy, const aclTensor* dh, const aclTensor* dc, const aclTensorList* i,
     913              :                             const aclTensorList* j, const aclTensorList* f, const aclTensorList* o,
     914              :                             const aclTensorList* h, const aclTensorList* c, const aclTensorList* tanhc,
     915              :                             const aclTensor* dx, const aclTensor* dhPrev, const aclTensor* dcPrev,
     916              :                             const aclTensorList* dparams, const aclTensor* batchSizes = nullptr)
     917              : {
     918            2 :     ge::DataType baseDtype = input->GetDataType();
     919            2 :     const SingleTensorItem singleTensors[] = {{"dy", dy}, {"dh", dh},         {"dc", dc},
     920            2 :                                               {"dx", dx}, {"dhPrev", dhPrev}, {"dcPrev", dcPrev}};
     921            2 :     const TensorListItem listTensors[] = {
     922              :         {"hc", hc}, {"params", params}, {"i", i}, {"j", j},         {"f", f},
     923            2 :         {"o", o},   {"h", h},           {"c", c}, {"tanhc", tanhc}, {"dparams", dparams}};
     924           14 :     for (const auto& item : singleTensors) {
     925           12 :         if (item.tensor != nullptr && !CheckSingleTensorDtype(item.tensor, item.name, baseDtype)) {
     926              :             return false;
     927              :         }
     928              :     }
     929            2 :     if (batchSizes != nullptr && batchSizes->GetDataType() != op::DataType::DT_INT64) {
     930            0 :         OP_LOGE(ACLNN_ERR_PARAM_INVALID, "batchSizes tensor dtype inconsistent, expected: %s, actual: %s.",
     931              :                 op::ToString(op::DataType::DT_INT64).GetString(), op::ToString(batchSizes->GetDataType()).GetString());
     932            0 :         return false;
     933              :     }
     934           22 :     for (const auto& item : listTensors) {
     935           20 :         if (!CheckTensorListDtype(item.list, item.name, baseDtype)) {
     936              :             return false;
     937              :         }
     938              :     }
     939              :     return true;
     940              : }
     941              : 
     942           93 : static bool ValidateInputShape(const aclTensor* input, const std::vector<int64_t>& expected_dims,
     943              :                                const char* tensorName)
     944              : {
     945           93 :     auto shape = input->GetViewShape();
     946           93 :     if (shape.GetDimNum() != expected_dims.size()) {
     947            0 :         OP_LOGE(ACLNN_ERR_PARAM_INVALID, "Input tensor %s has wrong dimension count", tensorName);
     948            0 :         return false;
     949              :     }
     950              : 
     951          312 :     for (size_t i = 0; i < expected_dims.size(); ++i) {
     952          438 :         if (expected_dims[i] != shape.GetDim(i)) {
     953            0 :             OP_LOGE(ACLNN_ERR_PARAM_INVALID, "Input tensor %s dim %zu mismatch", tensorName, i);
     954            0 :             return false;
     955              :         }
     956              :     }
     957           93 :     return true;
     958              : };
     959              : 
     960            2 : static bool ValidateLayerWithBiasAndBidir(const aclTensorList* params, const aclTensorList* dparams, int64_t layerIdx,
     961              :                                           const std::vector<int64_t>& weightInputDim,
     962              :                                           const std::vector<int64_t>& weightHiddenDim,
     963              :                                           const std::vector<int64_t>& biasDim)
     964              : {
     965              :     const int64_t stride = NUM_WITH_B_AND_BID;  // 每层张量数(含bias和双向)
     966              :     const int64_t half = stride / BI_DIRECTION; // 每个方向的张量数
     967              :     // 前向方向 (索引 0~half-1)
     968            4 :     bool ok = ValidateInputShape((*params)[stride * layerIdx + WEIGHT_INPUT_INDEX], weightInputDim, "wi") &&
     969            4 :               ValidateInputShape((*params)[stride * layerIdx + WEIGHT_HIDDEN_INDEX], weightHiddenDim, "wh") &&
     970            4 :               ValidateInputShape((*params)[stride * layerIdx + BIAS_INPUT_INDEX], biasDim, "bi") &&
     971            4 :               ValidateInputShape((*params)[stride * layerIdx + BIAS_HIDDEN_INDEX], biasDim, "bh") &&
     972            4 :               ValidateInputShape((*dparams)[stride * layerIdx + WEIGHT_INPUT_INDEX], weightInputDim, "dwi") &&
     973            4 :               ValidateInputShape((*dparams)[stride * layerIdx + WEIGHT_HIDDEN_INDEX], weightHiddenDim, "dwh") &&
     974            6 :               ValidateInputShape((*dparams)[stride * layerIdx + BIAS_INPUT_INDEX], biasDim, "dbi") &&
     975            2 :               ValidateInputShape((*dparams)[stride * layerIdx + BIAS_HIDDEN_INDEX], biasDim, "dbh");
     976              :     // 反向方向 (索引 half~stride-1)
     977            4 :     ok = ok && ValidateInputShape((*params)[stride * layerIdx + half + WEIGHT_INPUT_INDEX], weightInputDim, "wi") &&
     978            4 :          ValidateInputShape((*params)[stride * layerIdx + half + WEIGHT_HIDDEN_INDEX], weightHiddenDim, "wh") &&
     979            4 :          ValidateInputShape((*params)[stride * layerIdx + half + BIAS_INPUT_INDEX], biasDim, "bi") &&
     980            4 :          ValidateInputShape((*params)[stride * layerIdx + half + BIAS_HIDDEN_INDEX], biasDim, "bh") &&
     981            4 :          ValidateInputShape((*dparams)[stride * layerIdx + half + WEIGHT_INPUT_INDEX], weightInputDim, "dwi") &&
     982            4 :          ValidateInputShape((*dparams)[stride * layerIdx + half + WEIGHT_HIDDEN_INDEX], weightHiddenDim, "dwh") &&
     983            4 :          ValidateInputShape((*dparams)[stride * layerIdx + half + BIAS_INPUT_INDEX], biasDim, "dbi") &&
     984            2 :          ValidateInputShape((*dparams)[stride * layerIdx + half + BIAS_HIDDEN_INDEX], biasDim, "dbh");
     985            2 :     return ok;
     986              : }
     987              : 
     988            1 : static bool ValidateLayerWithBiasOnly(const aclTensorList* params, const aclTensorList* dparams, int64_t layerIdx,
     989              :                                       const std::vector<int64_t>& weightInputDim,
     990              :                                       const std::vector<int64_t>& weightHiddenDim, const std::vector<int64_t>& biasDim)
     991              : {
     992              :     const int64_t stride = NUM_WITH_B_OR_BID; // 每层张量数(含bias,单向)
     993            2 :     return ValidateInputShape((*params)[stride * layerIdx + WEIGHT_INPUT_INDEX], weightInputDim, "wi") &&
     994            2 :            ValidateInputShape((*params)[stride * layerIdx + WEIGHT_HIDDEN_INDEX], weightHiddenDim, "wh") &&
     995            2 :            ValidateInputShape((*params)[stride * layerIdx + BIAS_INPUT_INDEX], biasDim, "bi") &&
     996            2 :            ValidateInputShape((*params)[stride * layerIdx + BIAS_HIDDEN_INDEX], biasDim, "bh") &&
     997            2 :            ValidateInputShape((*dparams)[stride * layerIdx + WEIGHT_INPUT_INDEX], weightInputDim, "dwi") &&
     998            2 :            ValidateInputShape((*dparams)[stride * layerIdx + WEIGHT_HIDDEN_INDEX], weightHiddenDim, "dwh") &&
     999            3 :            ValidateInputShape((*dparams)[stride * layerIdx + BIAS_INPUT_INDEX], biasDim, "dbi") &&
    1000            1 :            ValidateInputShape((*dparams)[stride * layerIdx + BIAS_HIDDEN_INDEX], biasDim, "dbh");
    1001              : }
    1002              : 
    1003            0 : static bool ValidateLayerWithBidirOnly(const aclTensorList* params, const aclTensorList* dparams, int64_t layerIdx,
    1004              :                                        const std::vector<int64_t>& weightInputDim,
    1005              :                                        const std::vector<int64_t>& weightHiddenDim)
    1006              : {
    1007              :     const int64_t stride = NUM_WITH_B_OR_BID; // 无bias时每层张量数(两个方向)
    1008              :     const int64_t half = stride / BI_DIRECTION;
    1009            0 :     bool ok = ValidateInputShape((*params)[stride * layerIdx + WEIGHT_INPUT_INDEX], weightInputDim, "wi") &&
    1010            0 :               ValidateInputShape((*params)[stride * layerIdx + WEIGHT_HIDDEN_INDEX], weightHiddenDim, "wh") &&
    1011            0 :               ValidateInputShape((*dparams)[stride * layerIdx + WEIGHT_INPUT_INDEX], weightInputDim, "dwi") &&
    1012            0 :               ValidateInputShape((*dparams)[stride * layerIdx + WEIGHT_HIDDEN_INDEX], weightHiddenDim, "dwh");
    1013            0 :     ok = ok && ValidateInputShape((*params)[stride * layerIdx + half + WEIGHT_INPUT_INDEX], weightInputDim, "wi") &&
    1014            0 :          ValidateInputShape((*params)[stride * layerIdx + half + WEIGHT_HIDDEN_INDEX], weightHiddenDim, "wh") &&
    1015            0 :          ValidateInputShape((*dparams)[stride * layerIdx + half + WEIGHT_INPUT_INDEX], weightInputDim, "dwi") &&
    1016            0 :          ValidateInputShape((*dparams)[stride * layerIdx + half + WEIGHT_HIDDEN_INDEX], weightHiddenDim, "dwh");
    1017            0 :     return ok;
    1018              : }
    1019              : 
    1020            0 : static bool ValidateLayerNoBiasNoBidir(const aclTensorList* params, const aclTensorList* dparams, int64_t layerIdx,
    1021              :                                        const std::vector<int64_t>& weightInputDim,
    1022              :                                        const std::vector<int64_t>& weightHiddenDim)
    1023              : {
    1024              :     const int64_t stride = NUM_NO_B_NO_BIDIR; // 每层张量数(无bias,单向)
    1025            0 :     return ValidateInputShape((*params)[stride * layerIdx + WEIGHT_INPUT_INDEX], weightInputDim, "wi") &&
    1026            0 :            ValidateInputShape((*params)[stride * layerIdx + WEIGHT_HIDDEN_INDEX], weightHiddenDim, "wh") &&
    1027            0 :            ValidateInputShape((*dparams)[stride * layerIdx + WEIGHT_INPUT_INDEX], weightInputDim, "dwi") &&
    1028            0 :            ValidateInputShape((*dparams)[stride * layerIdx + WEIGHT_HIDDEN_INDEX], weightHiddenDim, "dwh");
    1029              : }
    1030              : 
    1031            5 : static bool CheckGateTensorsForIndex(const aclTensorList* i, const aclTensorList* j, const aclTensorList* f,
    1032              :                                      const aclTensorList* o, const aclTensorList* h, const aclTensorList* c,
    1033              :                                      const aclTensorList* tanhc, int64_t idx, const std::vector<int64_t>& hiddenDim)
    1034              : {
    1035            5 :     if (!ValidateInputShape((*i)[idx], hiddenDim, "i"))
    1036              :         return false;
    1037            5 :     if (!ValidateInputShape((*j)[idx], hiddenDim, "j"))
    1038              :         return false;
    1039            5 :     if (!ValidateInputShape((*f)[idx], hiddenDim, "f"))
    1040              :         return false;
    1041            5 :     if (!ValidateInputShape((*o)[idx], hiddenDim, "o"))
    1042              :         return false;
    1043            5 :     if (!ValidateInputShape((*h)[idx], hiddenDim, "h"))
    1044              :         return false;
    1045            5 :     if (!ValidateInputShape((*c)[idx], hiddenDim, "c"))
    1046              :         return false;
    1047            5 :     if (!ValidateInputShape((*tanhc)[idx], hiddenDim, "tanhc"))
    1048              :         return false;
    1049              :     return true;
    1050              : }
    1051              : 
    1052            2 : static bool ValidateCoreShapes(int64_t numLayers, const aclTensor* input, const std::vector<int64_t>& inputDim,
    1053              :                                const aclTensorList* hc, const std::vector<int64_t>& inithDim, const aclTensor* dx,
    1054              :                                const aclTensor* dhPrev, const aclTensor* dcPrev, const aclTensor* dy,
    1055              :                                const std::vector<int64_t>& outHiddenDim, const aclTensor* dh, const aclTensor* dc)
    1056              : {
    1057            2 :     if (numLayers <= 0)
    1058              :         return false;
    1059            6 :     if (!ValidateInputShape(input, inputDim, "input") || !ValidateInputShape((*hc)[0], inithDim, "inith") ||
    1060            6 :         !ValidateInputShape((*hc)[1], inithDim, "initc") || !ValidateInputShape(dx, inputDim, "dx") ||
    1061            6 :         !ValidateInputShape(dhPrev, inithDim, "dhPrev") || !ValidateInputShape(dcPrev, inithDim, "dcPrev")) {
    1062            0 :         return false;
    1063              :     }
    1064            2 :     if (dy && !ValidateInputShape(dy, outHiddenDim, "dy"))
    1065              :         return false;
    1066            2 :     if (dh && !ValidateInputShape(dh, inithDim, "dh"))
    1067              :         return false;
    1068            2 :     if (dc && !ValidateInputShape(dc, inithDim, "dc"))
    1069              :         return false;
    1070              : 
    1071              :     return true;
    1072              : }
    1073              : 
    1074            2 : static bool CheckShapeValid(const aclTensor* input, const aclTensorList* hc, const aclTensorList* params,
    1075              :                             const aclTensor* dy, const aclTensor* dh, const aclTensor* dc, const aclTensorList* i,
    1076              :                             const aclTensorList* j, const aclTensorList* f, const aclTensorList* o,
    1077              :                             const aclTensorList* h, const aclTensorList* c, const aclTensorList* tanhc,
    1078              :                             const aclTensor* dx, const aclTensor* dhPrev, const aclTensor* dcPrev,
    1079              :                             const aclTensorList* dparams, bool hasBias, int64_t numLayers, bool bidirectional,
    1080              :                             bool batchFirst, const aclTensor* batchSizes = nullptr)
    1081              : {
    1082            2 :     OP_CHECK_WRONG_DIMENSION((*i)[0], DIM_THREE, return false);
    1083              :     bool hasSeqlength = batchSizes != nullptr;
    1084            2 :     size_t inputDimsNum = hasSeqlength ? DIM_TWO : DIM_THREE;
    1085            2 :     OP_CHECK_WRONG_DIMENSION(input, inputDimsNum, return false);
    1086            2 :     if (hasSeqlength) {
    1087            0 :         OP_CHECK_WRONG_DIMENSION(batchSizes, DIM_ONE, return false);
    1088              :     }
    1089            2 :     auto iShape = (*i)[0]->GetViewShape();
    1090            2 :     auto inputShape = input->GetViewShape();
    1091            2 :     int64_t inputSize = inputShape[inputDimsNum - 1];
    1092            2 :     int64_t hiddenSize = iShape[HIDDEN_DIM];
    1093            2 :     int64_t batch = iShape[BATCH_DIM];
    1094              : 
    1095            4 :     int64_t timeStep = hasSeqlength ? batchSizes->GetViewShape()[SEQUENCE_DIM] :
    1096            2 :                        batchFirst   ? inputShape[BATCH_DIM] :
    1097              :                                       inputShape[SEQUENCE_DIM];
    1098            2 :     int64_t bid = bidirectional ? BI_DIRECTION : SINGLE_DIRECTION;
    1099              : 
    1100            2 :     const std::vector<int64_t> biasDim = {GATE_COUNT * hiddenSize};
    1101            2 :     const std::vector<int64_t> inithDim = {numLayers * bid, batch, hiddenSize};
    1102            2 :     const std::vector<int64_t> hiddenDim = {timeStep, batch, hiddenSize};
    1103            2 :     const std::vector<int64_t> outHiddenDim = hasSeqlength ? std::vector<int64_t>{timeStep * batch, hiddenSize * bid} :
    1104            2 :                                               batchFirst   ? std::vector<int64_t>{batch, timeStep, hiddenSize * bid} :
    1105            2 :                                                              std::vector<int64_t>{timeStep, batch, hiddenSize * bid};
    1106            2 :     const std::vector<int64_t> weightHiddenDim = {GATE_COUNT * hiddenSize, hiddenSize};
    1107            2 :     std::vector<int64_t> inputDim = hasSeqlength ? std::vector<int64_t>{timeStep * batch, inputSize} :
    1108            2 :                                     batchFirst   ? std::vector<int64_t>{batch, timeStep, inputSize} :
    1109            2 :                                                    std::vector<int64_t>{timeStep, batch, inputSize};
    1110            2 :     if (!ValidateCoreShapes(numLayers, input, inputDim, hc, inithDim, dx, dhPrev, dcPrev, dy, outHiddenDim, dh, dc))
    1111              :         return false;
    1112            2 :     int typeIdx = (hasBias ? 2 : 0) + (bidirectional ? 1 : 0);
    1113            5 :     for (int64_t layerIdx = 0; layerIdx < numLayers; ++layerIdx) {
    1114            3 :         int64_t curInputSize = (layerIdx == 0) ? inputSize : bid * hiddenSize;
    1115            3 :         const std::vector<int64_t> weightInputDim = {GATE_COUNT * hiddenSize, curInputSize};
    1116              :         bool ok = true;
    1117            3 :         switch (typeIdx) {
    1118            0 :             case LSTM_CONFIG_NO_BIAS_NO_BIDIR:
    1119            0 :                 ok = ValidateLayerNoBiasNoBidir(params, dparams, layerIdx, weightInputDim, weightHiddenDim);
    1120              :                 break;
    1121            0 :             case LSTM_CONFIG_BIDIR_ONLY:
    1122            0 :                 ok = ValidateLayerWithBidirOnly(params, dparams, layerIdx, weightInputDim, weightHiddenDim);
    1123              :                 break;
    1124            1 :             case LSTM_CONFIG_BIAS_ONLY:
    1125            1 :                 ok = ValidateLayerWithBiasOnly(params, dparams, layerIdx, weightInputDim, weightHiddenDim, biasDim);
    1126              :                 break;
    1127            2 :             case LSTM_CONFIG_BIAS_BIDIR:
    1128            2 :                 ok = ValidateLayerWithBiasAndBidir(params, dparams, layerIdx, weightInputDim, weightHiddenDim, biasDim);
    1129              :                 break;
    1130              :         }
    1131            3 :         if (!ok)
    1132              :             return false;
    1133              :     }
    1134              : 
    1135            7 :     for (int64_t gateIdx = 0; gateIdx < numLayers * bid; gateIdx++) {
    1136            5 :         if (!CheckGateTensorsForIndex(i, j, f, o, h, c, tanhc, gateIdx, hiddenDim))
    1137              :             return false;
    1138              :     }
    1139              :     return true;
    1140              : }
    1141              : 
    1142            2 : static aclnnStatus CheckParams(const aclTensor* input, const aclTensorList* hc, const aclTensorList* params,
    1143              :                                const aclTensor* dy, const aclTensor* dh, const aclTensor* dc, const aclTensorList* i,
    1144              :                                const aclTensorList* j, const aclTensorList* f, const aclTensorList* o,
    1145              :                                const aclTensorList* h, const aclTensorList* c, const aclTensorList* tanhc,
    1146              :                                const aclTensor* dx, const aclTensor* dhPrev, const aclTensor* dcPrev,
    1147              :                                const aclTensorList* dparams, bool hasBias, int64_t numLayers, bool bidirectional,
    1148              :                                bool batchFirst, const aclTensor* batchSizes = nullptr)
    1149              : {
    1150              :     // 1. 检查参数是否为空指针
    1151            2 :     CHECK_RET(CheckNotNull(input, hc, params, i, j, f, o, h, c, tanhc, dx, dhPrev, dcPrev, dparams, hasBias, numLayers,
    1152              :                            bidirectional),
    1153              :               ACLNN_ERR_PARAM_NULLPTR);
    1154              :     // 2. 检查输入的数据类型是否在API支持的数据类型范围之内,需要根据api定义校验
    1155            2 :     CHECK_RET(CheckDtypeValid(input, hc, params, dy, dh, dc, i, j, f, o, h, c, tanhc, dx, dhPrev, dcPrev, dparams,
    1156              :                               batchSizes),
    1157              :               ACLNN_ERR_PARAM_INVALID);
    1158              :     // 3. 检查shape是否满足约束
    1159            2 :     CHECK_RET(CheckShapeValid(input, hc, params, dy, dh, dc, i, j, f, o, h, c, tanhc, dx, dhPrev, dcPrev, dparams,
    1160              :                               hasBias, numLayers, bidirectional, batchFirst, batchSizes),
    1161              :               ACLNN_ERR_PARAM_INVALID);
    1162              :     // 4. 检查format是否满足约束
    1163            2 :     CHECK_RET(CheckFormatValid(input, hc, params, dy, dh, dc, i, j, f, o, h, c, tanhc, dx, dhPrev, dcPrev, dparams,
    1164              :                                batchSizes),
    1165              :               ACLNN_ERR_PARAM_INVALID);
    1166              : 
    1167              :     return ACLNN_SUCCESS;
    1168              : }
    1169              : 
    1170            2 : static bool EmptyCheck(const aclTensor* input, const aclTensorList* hc, const aclTensorList* params,
    1171              :                        const aclTensor* dy, const aclTensor* dh, const aclTensor* dc, const aclTensorList* i,
    1172              :                        const aclTensorList* j, // 修正参数名:j -> g
    1173              :                        const aclTensorList* f, const aclTensorList* o, const aclTensorList* h, const aclTensorList* c,
    1174              :                        const aclTensorList* tanhc, const aclTensor* batchSizes = nullptr)
    1175              : {
    1176            2 :     if (input->IsEmpty() || dy->IsEmpty() || dh->IsEmpty() || dc->IsEmpty()) {
    1177            0 :         return false;
    1178              :     }
    1179              : 
    1180            2 :     if (batchSizes != nullptr && batchSizes->IsEmpty()) {
    1181              :         return false;
    1182              :     }
    1183           18 :     auto checkTensorList = [](const aclTensorList* tensorList) {
    1184           77 :         for (uint64_t idx = 0; idx < tensorList->Size(); idx++) {
    1185           59 :             if ((*tensorList)[idx]->IsEmpty()) {
    1186              :                 return false;
    1187              :             }
    1188              :         }
    1189              :         return true;
    1190              :     };
    1191            6 :     if (!checkTensorList(hc) || !checkTensorList(params) || !checkTensorList(i) || !checkTensorList(j) ||
    1192            8 :         !checkTensorList(f) || !checkTensorList(o) || !checkTensorList(h) || !checkTensorList(c) ||
    1193            2 :         !checkTensorList(tanhc)) {
    1194            0 :         return false;
    1195              :     }
    1196              :     return true;
    1197              : }
    1198              : 
    1199           10 : static const aclTensor* GetSliceTensor(const FVector<int64_t> offsetVector, const FVector<int64_t> sizeVector,
    1200              :                                        const aclTensor* input, aclOpExecutor* executor)
    1201              : {
    1202           10 :     aclIntArray* offsetArray = executor->AllocIntArray(offsetVector.data(), offsetVector.size());
    1203           10 :     CHECK_RET(offsetArray != nullptr, nullptr);
    1204           10 :     aclIntArray* sizeArray = executor->AllocIntArray(sizeVector.data(), sizeVector.size());
    1205           10 :     CHECK_RET(sizeArray != nullptr, nullptr);
    1206           10 :     auto res = l0op::Slice(input, offsetArray, sizeArray, executor);
    1207           10 :     CHECK_RET(res != nullptr, nullptr);
    1208              :     return res;
    1209              : }
    1210              : 
    1211              : // 抽取处理LSTM梯度输出的函数
    1212            2 : static std::tuple<const aclTensor*, const aclTensor*, std::vector<const aclTensor*>> GetLstmGradExceptDx(
    1213              :     std::tuple<const aclTensor*, std::vector<const aclTensor*>, std::vector<const aclTensor*>,
    1214              :                std::vector<const aclTensor*>, std::vector<const aclTensor*>>& output,
    1215              :     const aclTensor* input, const aclTensor* dhPrevOut, bool bidirectional, bool hasBias, int64_t numLayers,
    1216              :     aclOpExecutor* executor)
    1217              : {
    1218            2 :     auto nullptrRes = std::make_tuple(nullptr, nullptr, std::vector<const aclTensor*>());
    1219            2 :     std::vector<const aclTensor*> dhPrevVectorReverse = std::get<INDEX_ONE>(output);
    1220            2 :     std::vector<const aclTensor*> dcPrevVectorReverse = std::get<INDEX_TWO>(output);
    1221            2 :     std::vector<const aclTensor*> dwVectorReverse = std::get<INDEX_THREE>(output);
    1222            2 :     std::vector<const aclTensor*> dbVectorReverse = std::get<INDEX_FOUR>(output);
    1223              :     std::vector<const aclTensor*> dhPrevVector{};
    1224              :     std::vector<const aclTensor*> dcPrevVector{};
    1225              :     std::vector<const aclTensor*> dparamsVector{};
    1226              : 
    1227            2 :     int64_t inputSize = input->GetViewShape()[DIM_TWO];
    1228            2 :     int64_t hiddenSize = dhPrevOut->GetViewShape()[HIDDEN_DIM];
    1229            2 :     int64_t bid = bidirectional ? BI_DIRECTION : SINGLE_DIRECTION;
    1230              : 
    1231              :     // 处理每一层的梯度
    1232            5 :     for (int64_t layerIdx = 0; layerIdx < numLayers; layerIdx++) {
    1233            3 :         auto dwFCur = dwVectorReverse[(numLayers - layerIdx - 1) * bid + 0];
    1234            3 :         auto inputSizeCur = layerIdx == 0 ? inputSize : hiddenSize * bid;
    1235              : 
    1236              :         // 前向层的输入权重梯度
    1237            3 :         FVector<int64_t> offsetVectorF{DIM_ZERO, DIM_ZERO};
    1238            3 :         FVector<int64_t> sizeVectorF{GATE_COUNT * hiddenSize, inputSizeCur};
    1239            3 :         auto dwFInputCur = GetSliceTensor(offsetVectorF, sizeVectorF, dwFCur, executor);
    1240              : 
    1241              :         // 前向层的隐藏状态权重梯度
    1242            3 :         FVector<int64_t> offsetVectorB{DIM_ZERO, inputSizeCur};
    1243            3 :         FVector<int64_t> sizeVectorB{GATE_COUNT * hiddenSize, hiddenSize};
    1244            3 :         auto dwFHiddenCur = GetSliceTensor(offsetVectorB, sizeVectorB, dwFCur, executor);
    1245            3 :         dparamsVector.emplace_back(dwFInputCur);
    1246            3 :         dparamsVector.emplace_back(dwFHiddenCur);
    1247              : 
    1248              :         // 处理偏置和双向情况
    1249            3 :         if (hasBias && bidirectional) {
    1250            2 :             auto dwBCur = dwVectorReverse[(numLayers - layerIdx - 1) * bid + 1];
    1251            2 :             auto dwBInputCur = GetSliceTensor(offsetVectorF, sizeVectorF, dwBCur, executor);
    1252            2 :             auto dwBHiddenCur = GetSliceTensor(offsetVectorB, sizeVectorB, dwBCur, executor);
    1253            2 :             auto dbFCur = dbVectorReverse[(numLayers - layerIdx - 1) * bid + 0];
    1254            2 :             auto dbBCur = dbVectorReverse[(numLayers - layerIdx - 1) * bid + 1];
    1255            2 :             dparamsVector.emplace_back(dbFCur);
    1256            2 :             dparamsVector.emplace_back(dbFCur);
    1257            2 :             dparamsVector.emplace_back(dwBInputCur);
    1258            2 :             dparamsVector.emplace_back(dwBHiddenCur);
    1259            2 :             dparamsVector.emplace_back(dbBCur);
    1260            2 :             dparamsVector.emplace_back(dbBCur);
    1261            1 :         } else if (hasBias && !bidirectional) {
    1262            1 :             auto dbFCur = dbVectorReverse[(numLayers - layerIdx - 1) * bid + 0];
    1263            1 :             dparamsVector.emplace_back(dbFCur);
    1264            1 :             dparamsVector.emplace_back(dbFCur);
    1265            0 :         } else if (!hasBias && bidirectional) {
    1266            0 :             auto dwBCur = dwVectorReverse[(numLayers - layerIdx - 1) * bid + 1];
    1267            0 :             auto dwBInputCur = GetSliceTensor(offsetVectorF, sizeVectorF, dwBCur, executor);
    1268            0 :             auto dwBHiddenCur = GetSliceTensor(offsetVectorB, sizeVectorB, dwBCur, executor);
    1269            0 :             dparamsVector.emplace_back(dwBInputCur);
    1270            0 :             dparamsVector.emplace_back(dwBHiddenCur);
    1271              :         }
    1272              : 
    1273              :         // 处理隐藏状态和细胞状态的梯度
    1274            3 :         dhPrevVector.emplace_back(dhPrevVectorReverse[(numLayers - layerIdx - 1) * bid + 0]);
    1275            3 :         dcPrevVector.emplace_back(dcPrevVectorReverse[(numLayers - layerIdx - 1) * bid + 0]);
    1276            3 :         if (bidirectional) {
    1277            2 :             dhPrevVector.emplace_back(dhPrevVectorReverse[(numLayers - layerIdx - 1) * bid + 1]);
    1278            2 :             dcPrevVector.emplace_back(dcPrevVectorReverse[(numLayers - layerIdx - 1) * bid + 1]);
    1279              :         }
    1280              :     }
    1281              : 
    1282              :     // 拼接隐藏状态梯度
    1283            2 :     auto dhPrev = SplitToConcat(dhPrevVector, CONCAT_DIM_LAYER, executor);
    1284              : 
    1285              :     // 拼接细胞状态梯度
    1286            4 :     auto dcPrev = SplitToConcat(dcPrevVector, CONCAT_DIM_LAYER, executor);
    1287            2 :     return std::make_tuple(dhPrev, dcPrev, dparamsVector);
    1288              : }
    1289              : 
    1290           18 : static const aclTensorList* MakeContiguousList(const aclTensorList* tensor_list, aclOpExecutor* executor)
    1291              : {
    1292              :     std::vector<const aclTensor*> tensors_vec;
    1293           77 :     for (size_t i = 0; i < tensor_list->Size(); ++i) {
    1294           59 :         auto contiguous = l0op::Contiguous((*tensor_list)[i], executor);
    1295           59 :         if (contiguous == nullptr) {
    1296            0 :             return nullptr;
    1297              :         }
    1298           59 :         tensors_vec.push_back(contiguous);
    1299              :     }
    1300           18 :     return executor->AllocTensorList(tensors_vec.data(), tensors_vec.size());
    1301              : }
    1302              : 
    1303            2 : static aclnnStatus CreateContiguousTensors(const aclTensor* input, const aclTensorList* hx, const aclTensorList* params,
    1304              :                                            const aclTensor* dy, const aclTensor* dh, const aclTensor* dc,
    1305              :                                            const aclTensorList* i, const aclTensorList* g, const aclTensorList* f,
    1306              :                                            const aclTensorList* o, const aclTensorList* h, const aclTensorList* c,
    1307              :                                            const aclTensorList* tanhc, aclOpExecutor* executor,
    1308              :                                            LSTMContinuousTensors* output, const aclTensor* batchSizes = nullptr)
    1309              : {
    1310            2 :     output->inputContiguous = l0op::Contiguous(input, executor);
    1311            2 :     CHECK_RET(output->inputContiguous != nullptr, ACLNN_ERR_INNER_NULLPTR);
    1312              : 
    1313            2 :     output->hxContiguous = MakeContiguousList(hx, executor);
    1314            2 :     CHECK_RET(output->hxContiguous != nullptr, ACLNN_ERR_INNER_NULLPTR);
    1315              : 
    1316            2 :     output->paramsContiguous = MakeContiguousList(params, executor);
    1317            2 :     CHECK_RET(output->paramsContiguous != nullptr, ACLNN_ERR_INNER_NULLPTR);
    1318              : 
    1319            2 :     output->dyContiguous = l0op::Contiguous(dy, executor);
    1320            2 :     CHECK_RET(output->dyContiguous != nullptr, ACLNN_ERR_INNER_NULLPTR);
    1321              : 
    1322            2 :     output->dhContiguous = l0op::Contiguous(dh, executor);
    1323            2 :     CHECK_RET(output->dhContiguous != nullptr, ACLNN_ERR_INNER_NULLPTR);
    1324              : 
    1325            2 :     output->dcContiguous = l0op::Contiguous(dc, executor);
    1326            2 :     CHECK_RET(output->dcContiguous != nullptr, ACLNN_ERR_INNER_NULLPTR);
    1327              : 
    1328            2 :     output->iContiguous = MakeContiguousList(i, executor);
    1329            2 :     CHECK_RET(output->iContiguous != nullptr, ACLNN_ERR_INNER_NULLPTR);
    1330              : 
    1331            2 :     output->gContiguous = MakeContiguousList(g, executor);
    1332            2 :     CHECK_RET(output->gContiguous != nullptr, ACLNN_ERR_INNER_NULLPTR);
    1333              : 
    1334            2 :     output->fContiguous = MakeContiguousList(f, executor);
    1335            2 :     CHECK_RET(output->fContiguous != nullptr, ACLNN_ERR_INNER_NULLPTR);
    1336              : 
    1337            2 :     output->oContiguous = MakeContiguousList(o, executor);
    1338            2 :     CHECK_RET(output->oContiguous != nullptr, ACLNN_ERR_INNER_NULLPTR);
    1339              : 
    1340            2 :     output->hContiguous = MakeContiguousList(h, executor);
    1341            2 :     CHECK_RET(output->hContiguous != nullptr, ACLNN_ERR_INNER_NULLPTR);
    1342              : 
    1343            2 :     output->cContiguous = MakeContiguousList(c, executor);
    1344            2 :     CHECK_RET(output->cContiguous != nullptr, ACLNN_ERR_INNER_NULLPTR);
    1345              : 
    1346            2 :     output->tanhcContiguous = MakeContiguousList(tanhc, executor);
    1347            2 :     CHECK_RET(output->tanhcContiguous != nullptr, ACLNN_ERR_INNER_NULLPTR);
    1348              : 
    1349            2 :     output->batchSizesContiguous = batchSizes == nullptr ? nullptr : l0op::Contiguous(batchSizes, executor);
    1350              :     if (batchSizes != nullptr) {
    1351            0 :         CHECK_RET(output->batchSizesContiguous != nullptr, ACLNN_ERR_INNER_NULLPTR);
    1352              :     }
    1353              :     return ACL_SUCCESS;
    1354              : }
    1355              : 
    1356              : // 校验LSTM反向传播输出结果
    1357            2 : static aclnnStatus ValidateLstmBackwardOutput(
    1358              :     const std::tuple<const aclTensor*, std::vector<const aclTensor*>, std::vector<const aclTensor*>,
    1359              :                      std::vector<const aclTensor*>, std::vector<const aclTensor*>>& output)
    1360              : {
    1361            2 :     CHECK_RET(std::get<0>(output) != nullptr, ACLNN_ERR_INNER_CREATE_EXECUTOR);
    1362              : 
    1363            7 :     for (uint64_t idx = 0; idx < std::get<INDEX_THREE>(output).size(); idx++) {
    1364            5 :         CHECK_RET(std::get<INDEX_ONE>(output)[idx] != nullptr, ACLNN_ERR_INNER_CREATE_EXECUTOR);
    1365            5 :         CHECK_RET(std::get<INDEX_TWO>(output)[idx] != nullptr, ACLNN_ERR_INNER_CREATE_EXECUTOR);
    1366            5 :         CHECK_RET(std::get<INDEX_THREE>(output)[idx] != nullptr, ACLNN_ERR_INNER_CREATE_EXECUTOR);
    1367              :     }
    1368              : 
    1369            7 :     for (uint64_t idx = 0; idx < std::get<INDEX_FOUR>(output).size(); idx++) {
    1370            5 :         CHECK_RET(std::get<INDEX_FOUR>(output)[idx] != nullptr, ACLNN_ERR_INNER_CREATE_EXECUTOR);
    1371              :     }
    1372              : 
    1373              :     return ACLNN_SUCCESS;
    1374              : }
    1375              : 
    1376              : // 搬出LSTM反向传播结果
    1377            2 : static aclnnStatus CopyLstmBackwardResults(const aclTensor* dx, const aclTensor* dhPrev, const aclTensor* dcPrev,
    1378              :                                            const std::vector<const aclTensor*>& dparamsVector, aclTensor* dxOut,
    1379              :                                            aclTensor* dhPrevOut, aclTensor* dcPrevOut, aclTensorList* dparamsOut,
    1380              :                                            int64_t numLayers, int64_t paramNumPerLayer, aclOpExecutor* executor)
    1381              : {
    1382            2 :     auto dxCopyResult = l0op::ViewCopy(dx, dxOut, executor);
    1383            2 :     CHECK_RET(dxCopyResult != nullptr, ACLNN_ERR_INNER_NULLPTR);
    1384              : 
    1385            2 :     auto dhPrevCopyResult = l0op::ViewCopy(dhPrev, dhPrevOut, executor);
    1386            2 :     CHECK_RET(dhPrevCopyResult != nullptr, ACLNN_ERR_INNER_NULLPTR);
    1387              : 
    1388            2 :     auto dcPrevCopyResult = l0op::ViewCopy(dcPrev, dcPrevOut, executor);
    1389            2 :     CHECK_RET(dcPrevCopyResult != nullptr, ACLNN_ERR_INNER_NULLPTR);
    1390              : 
    1391           22 :     for (int64_t idx = 0; idx < numLayers * paramNumPerLayer; idx++) {
    1392           20 :         auto dparamsCopyResult = l0op::ViewCopy(dparamsVector[idx], (*dparamsOut)[idx], executor);
    1393           20 :         CHECK_RET(dparamsCopyResult != nullptr, ACLNN_ERR_INNER_NULLPTR);
    1394              :     }
    1395              : 
    1396              :     return ACLNN_SUCCESS;
    1397              : }
    1398              : 
    1399            2 : static aclnnStatus ExecLstmInputBackward(const LSTMContinuousTensors* allInput, aclTensor* dxOut, aclTensor* dhPrevOut,
    1400              :                                          aclTensor* dcPrevOut, aclTensorList* dparamsOut, bool batchFirst,
    1401              :                                          bool bidirectional, bool hasBias, int64_t numLayers, aclOpExecutor* executor)
    1402              : {
    1403              :     std::tuple<const aclTensor*, std::vector<const aclTensor*>, std::vector<const aclTensor*>,
    1404              :                std::vector<const aclTensor*>, std::vector<const aclTensor*>>
    1405              :         output;
    1406              :     // T转到第1维
    1407            2 :     FVector<int64_t> newShapeDims = {1, 0, 2};
    1408            2 :     auto perm = executor->AllocIntArray(newShapeDims.data(), newShapeDims.size());
    1409            2 :     CHECK_RET(perm != nullptr, ACLNN_ERR_PARAM_NULLPTR);
    1410            2 :     auto inputTranspose = batchFirst ? l0op::Transpose(allInput->inputContiguous, perm, executor) :
    1411              :                                        allInput->inputContiguous;
    1412            2 :     CHECK_RET(inputTranspose != nullptr, ACLNN_ERR_PARAM_NULLPTR);
    1413            2 :     auto dyTranspose = batchFirst ? l0op::Transpose(allInput->dyContiguous, perm, executor) : allInput->dyContiguous;
    1414            2 :     CHECK_RET(dyTranspose != nullptr, ACLNN_ERR_PARAM_NULLPTR);
    1415              : 
    1416            2 :     int64_t paramNumPerLayer = (hasBias && bidirectional) ? NUM_WITH_B_AND_BID :
    1417            1 :                                (hasBias || bidirectional) ? NUM_WITH_B_OR_BID :
    1418              :                                                             NUM_NO_B_NO_BIDIR;
    1419            1 :     if (!bidirectional) {
    1420            2 :         output = LstmBackwardMultiLayerDirec(
    1421            1 :             inputTranspose, (*(allInput->hxContiguous))[0], (*(allInput->hxContiguous))[1], allInput->paramsContiguous,
    1422            1 :             dyTranspose, allInput->dhContiguous, allInput->dcContiguous, nullptr, allInput->iContiguous,
    1423            1 :             allInput->gContiguous, allInput->fContiguous, allInput->oContiguous, allInput->hContiguous,
    1424            1 :             allInput->cContiguous, allInput->tanhcContiguous, numLayers, 1, hasBias, paramNumPerLayer, executor);
    1425              :     } else {
    1426            2 :         output = LstmBackwardMultiLayerBidirec(
    1427            1 :             inputTranspose, (*allInput->hxContiguous)[0], (*allInput->hxContiguous)[1], allInput->paramsContiguous,
    1428            1 :             dyTranspose, allInput->dhContiguous, allInput->dcContiguous, nullptr, allInput->iContiguous,
    1429            1 :             allInput->gContiguous, allInput->fContiguous, allInput->oContiguous, allInput->hContiguous,
    1430            1 :             allInput->cContiguous, allInput->tanhcContiguous, numLayers, 1, hasBias, paramNumPerLayer, executor);
    1431              :     }
    1432            2 :     CHECK_RET(ValidateLstmBackwardOutput(output) == ACLNN_SUCCESS, ACLNN_ERR_PARAM_NULLPTR);
    1433            2 :     const aclTensor* dhPrev = nullptr;
    1434            2 :     const aclTensor* dcPrev = nullptr;
    1435              :     std::vector<const aclTensor*> dparamsVector{};
    1436              :     // 输出梯度处理
    1437            4 :     std::tie(dhPrev, dcPrev, dparamsVector) = GetLstmGradExceptDx(output, allInput->inputContiguous, dhPrevOut,
    1438              :                                                                   bidirectional, hasBias, numLayers, executor);
    1439            2 :     auto dx = batchFirst ? l0op::Transpose(std::get<0>(output), perm, executor) : std::get<0>(output);
    1440            2 :     CHECK_RET(dx != nullptr, ACLNN_ERR_PARAM_NULLPTR);
    1441            2 :     return CopyLstmBackwardResults(dx, dhPrev, dcPrev, dparamsVector, dxOut, dhPrevOut, dcPrevOut, dparamsOut,
    1442              :                                    numLayers, paramNumPerLayer, executor);
    1443              : }
    1444              : 
    1445            0 : static aclnnStatus ExecLstmDataBackward(const LSTMContinuousTensors* allInput, aclTensor* dxOut, aclTensor* dhPrevOut,
    1446              :                                         aclTensor* dcPrevOut, aclTensorList* dparamsOut, bool bidirectional,
    1447              :                                         bool hasBias, int64_t numLayers, aclOpExecutor* executor)
    1448              : {
    1449              :     std::tuple<const aclTensor*, std::vector<const aclTensor*>, std::vector<const aclTensor*>,
    1450              :                std::vector<const aclTensor*>, std::vector<const aclTensor*>>
    1451              :         output;
    1452            0 :     auto batchSizeShape = allInput->batchSizesContiguous->GetViewShape();
    1453            0 :     auto dataShape = allInput->inputContiguous->GetViewShape();
    1454              : 
    1455              :     // 输入reshape为[T, N, D]
    1456            0 :     FVector<int64_t> reshapeInputVector{batchSizeShape[SEQUENCE_DIM], dataShape[0] / batchSizeShape[SEQUENCE_DIM],
    1457            0 :                                         dataShape[1]};
    1458            0 :     aclIntArray* reshapeInputArray = executor->AllocIntArray(reshapeInputVector.data(), DIM_THREE);
    1459            0 :     CHECK_RET(reshapeInputArray != nullptr, ACLNN_ERR_PARAM_NULLPTR);
    1460            0 :     auto input = l0op::Reshape(allInput->inputContiguous, reshapeInputArray, executor);
    1461            0 :     CHECK_RET(input != nullptr, ACLNN_ERR_PARAM_NULLPTR);
    1462            0 :     FVector<int64_t> reshapeDyVector{batchSizeShape[SEQUENCE_DIM], dataShape[0] / batchSizeShape[SEQUENCE_DIM],
    1463            0 :                                      allInput->dyContiguous->GetViewShape()[1]};
    1464            0 :     aclIntArray* reshapeDyArray = executor->AllocIntArray(reshapeDyVector.data(), DIM_THREE);
    1465            0 :     CHECK_RET(reshapeDyArray != nullptr, ACLNN_ERR_PARAM_NULLPTR);
    1466            0 :     auto dyReshape = l0op::Reshape(allInput->dyContiguous, reshapeDyArray, executor);
    1467            0 :     CHECK_RET(dyReshape != nullptr, ACLNN_ERR_PARAM_NULLPTR);
    1468              :     // batchSizes转Mask
    1469            0 :     const aclTensor* seqLength = GetMask(input, allInput->batchSizesContiguous, allInput->dhContiguous, executor);
    1470            0 :     CHECK_RET(seqLength != nullptr, ACLNN_ERR_PARAM_NULLPTR);
    1471            0 :     int64_t paramNumPerLayer = (hasBias && bidirectional) ? NUM_WITH_B_AND_BID :
    1472            0 :                                (hasBias || bidirectional) ? NUM_WITH_B_OR_BID :
    1473              :                                                             NUM_NO_B_NO_BIDIR;
    1474              : 
    1475            0 :     if (!bidirectional) {
    1476            0 :         output = LstmBackwardMultiLayerDirec(
    1477            0 :             input, (*(allInput->hxContiguous))[0], (*(allInput->hxContiguous))[1], allInput->paramsContiguous,
    1478            0 :             dyReshape, allInput->dhContiguous, allInput->dcContiguous, seqLength, allInput->iContiguous,
    1479            0 :             allInput->gContiguous, allInput->fContiguous, allInput->oContiguous, allInput->hContiguous,
    1480            0 :             allInput->cContiguous, allInput->tanhcContiguous, numLayers, 1, hasBias, paramNumPerLayer, executor);
    1481              :     } else {
    1482            0 :         output = LstmBackwardMultiLayerBidirec(
    1483            0 :             input, (*(allInput->hxContiguous))[0], (*(allInput->hxContiguous))[1], allInput->paramsContiguous,
    1484            0 :             dyReshape, allInput->dhContiguous, allInput->dcContiguous, seqLength, allInput->iContiguous,
    1485            0 :             allInput->gContiguous, allInput->fContiguous, allInput->oContiguous, allInput->hContiguous,
    1486            0 :             allInput->cContiguous, allInput->tanhcContiguous, numLayers, 1, hasBias, paramNumPerLayer, executor);
    1487              :     }
    1488            0 :     CHECK_RET(ValidateLstmBackwardOutput(output) == ACLNN_SUCCESS, ACLNN_ERR_PARAM_NULLPTR);
    1489            0 :     const aclTensor* dhPrev = nullptr;
    1490            0 :     const aclTensor* dcPrev = nullptr;
    1491              :     std::vector<const aclTensor*> dparamsVector{};
    1492            0 :     std::tie(dhPrev, dcPrev, dparamsVector) = GetLstmGradExceptDx(output, input, dhPrevOut, bidirectional, hasBias,
    1493              :                                                                   numLayers, executor);
    1494              : 
    1495            0 :     FVector<int64_t> reshapeVector{dataShape[0], dataShape[1]};
    1496            0 :     aclIntArray* reshapeArray = executor->AllocIntArray(reshapeVector.data(), DIM_TWO);
    1497            0 :     CHECK_RET(reshapeArray != nullptr, ACLNN_ERR_PARAM_NULLPTR);
    1498            0 :     auto dx = l0op::Reshape(std::get<0>(output), reshapeArray, executor);
    1499            0 :     CHECK_RET(dx != nullptr, ACLNN_ERR_PARAM_NULLPTR);
    1500            0 :     return CopyLstmBackwardResults(dx, dhPrev, dcPrev, dparamsVector, dxOut, dhPrevOut, dcPrevOut, dparamsOut,
    1501              :                                    numLayers, paramNumPerLayer, executor);
    1502              :     ;
    1503              : }
    1504              : 
    1505            0 : const aclTensor* ResetAndReshapeTensor(const aclTensor* srcTensor, const FVector<int64_t>& shape,
    1506              :                                        aclOpExecutor* executor)
    1507              : {
    1508            0 :     const aclTensor* zeroTensor = l0op::ZerosLike(srcTensor, executor);
    1509            0 :     OP_CHECK_NULL(zeroTensor, return nullptr);
    1510            0 :     aclIntArray* reshapeArray = executor->AllocIntArray(shape.data(), shape.size());
    1511            0 :     OP_CHECK_NULL(reshapeArray, return nullptr);
    1512            0 :     const aclTensor* reshapedTensor = l0op::Reshape(zeroTensor, reshapeArray, executor);
    1513            0 :     OP_CHECK_NULL(reshapedTensor, return nullptr);
    1514              :     return reshapedTensor;
    1515              : }
    1516              : 
    1517            2 : aclnnStatus PrepareLSTMBackwardNoneInputs(const aclTensor* input, const aclTensorList* hx, const aclTensor* dh,
    1518              :                                           const aclTensor* dc, const aclTensor* dy, bool bidirectional,
    1519              :                                           aclOpExecutor* executor, const aclTensor*& dhOut, const aclTensor*& dcOut,
    1520              :                                           const aclTensor*& dyOut)
    1521              : {
    1522            2 :     dhOut = dh;
    1523            2 :     dcOut = dc;
    1524            2 :     dyOut = dy;
    1525              : 
    1526            2 :     auto dhShape = (*hx)[0]->GetViewShape();
    1527            2 :     if (dh == nullptr) {
    1528            0 :         FVector<int64_t> dhReshapeVec{dhShape[0], dhShape[1], dhShape[2]};
    1529            0 :         dhOut = ResetAndReshapeTensor((*hx)[0], dhReshapeVec, executor);
    1530            0 :         CHECK_RET(dhOut != nullptr, ACLNN_ERR_INNER_CREATE_EXECUTOR);
    1531              :     }
    1532            2 :     if (dc == nullptr) {
    1533            0 :         FVector<int64_t> dcReshapeVec{dhShape[0], dhShape[1], dhShape[2]};
    1534            0 :         dcOut = ResetAndReshapeTensor((*hx)[0], dcReshapeVec, executor);
    1535            0 :         CHECK_RET(dcOut != nullptr, ACLNN_ERR_INNER_CREATE_EXECUTOR);
    1536              :     }
    1537              :     static const int64_t HIDDEN_DIM_INDEX = 2;
    1538            2 :     if (dy == nullptr) {
    1539            0 :         auto dyShape = input->GetViewShape();
    1540            0 :         auto dyType = input->GetDataType();
    1541            0 :         dyShape[HIDDEN_DIM_INDEX] = bidirectional ? dhShape[HIDDEN_DIM_INDEX] * BI_DIRECTION :
    1542              :                                                     dhShape[HIDDEN_DIM_INDEX];
    1543            0 :         const aclTensor* dyAlloc = executor->AllocTensor(dyShape, dyType, Format::FORMAT_ND);
    1544            0 :         CHECK_RET(dyAlloc != nullptr, ACLNN_ERR_INNER_CREATE_EXECUTOR);
    1545            0 :         FVector<int64_t> dyReshapeVec{dyShape[0], dyShape[1], dyShape[2]};
    1546            0 :         dyOut = ResetAndReshapeTensor(dyAlloc, dyReshapeVec, executor);
    1547            0 :         CHECK_RET(dyOut != nullptr, ACLNN_ERR_INNER_CREATE_EXECUTOR);
    1548              :     }
    1549              : 
    1550              :     return ACLNN_SUCCESS;
    1551              : }
    1552              : 
    1553            2 : aclnnStatus aclnnLstmBackwardGetWorkspaceSize(const aclTensor* input, const aclTensorList* hx,
    1554              :                                               const aclTensorList* params, const aclTensor* dy, const aclTensor* dh,
    1555              :                                               const aclTensor* dc, const aclTensorList* i, const aclTensorList* g,
    1556              :                                               const aclTensorList* f, const aclTensorList* o, const aclTensorList* h,
    1557              :                                               const aclTensorList* c, const aclTensorList* tanhc,
    1558              :                                               const aclTensor* batchSizesOptional, bool hasBias, int64_t numLayers,
    1559              :                                               double dropout, bool train, bool bidirectional, bool batchFirst,
    1560              :                                               [[maybe_unused]] const aclBoolArray* outputMask, aclTensor* dxOut,
    1561              :                                               aclTensor* dhPrevOut, aclTensor* dcPrevOut, aclTensorList* dparamsOut,
    1562              :                                               uint64_t* workspaceSize, aclOpExecutor** executor)
    1563              : {
    1564            2 :     L2_DFX_PHASE_1(aclnnLstmBackward,
    1565              :                    DFX_IN(input, hx, params, dy, dh, dc, i, g, f, o, h, c, tanhc, batchSizesOptional, hasBias,
    1566              :                           numLayers, dropout, train, bidirectional, batchFirst),
    1567              :                    DFX_OUT(dxOut, dhPrevOut, dcPrevOut, dparamsOut));
    1568            2 :     auto uniqueExecutor = CREATE_EXECUTOR();
    1569            2 :     CHECK_RET(uniqueExecutor.get() != nullptr, ACLNN_ERR_INNER_CREATE_EXECUTOR);
    1570            2 :     auto ret = CheckParams(input, hx, params, dy, dh, dc, i, g, f, o, h, c, tanhc, dxOut, dhPrevOut, dcPrevOut,
    1571              :                            dparamsOut, hasBias, numLayers, bidirectional, batchFirst, batchSizesOptional);
    1572            2 :     CHECK_RET(ret == ACLNN_SUCCESS, ret);
    1573              :     const aclTensor* dhInput = nullptr;
    1574              :     const aclTensor* dcInput = nullptr;
    1575              :     const aclTensor* dyInput = nullptr;
    1576            2 :     ret = PrepareLSTMBackwardNoneInputs(input, hx, dh, dc, dy, bidirectional, uniqueExecutor.get(), dhInput, dcInput,
    1577              :                                         dyInput);
    1578            2 :     CHECK_RET(ret == ACLNN_SUCCESS, ret);
    1579            2 :     if (!EmptyCheck(input, hx, params, dyInput, dhInput, dcInput, i, g, f, o, h, c, tanhc)) {
    1580            0 :         *workspaceSize = 0;
    1581            0 :         uniqueExecutor.ReleaseTo(executor);
    1582              :         return ACLNN_SUCCESS;
    1583              :     }
    1584              : 
    1585            2 :     LSTMContinuousTensors allInputContiguous;
    1586            2 :     CHECK_RET(CreateContiguousTensors(input, hx, params, dyInput, dhInput, dcInput, i, g, f, o, h, c, tanhc,
    1587              :                                       uniqueExecutor.get(), &allInputContiguous, batchSizesOptional) == ACLNN_SUCCESS,
    1588              :               ACLNN_ERR_PARAM_NULLPTR);
    1589              : 
    1590            2 :     if (batchSizesOptional == nullptr) {
    1591            2 :         ret = ExecLstmInputBackward(&allInputContiguous, dxOut, dhPrevOut, dcPrevOut, dparamsOut, batchFirst,
    1592              :                                     bidirectional, hasBias, numLayers, uniqueExecutor.get());
    1593              :     } else {
    1594            0 :         ret = ExecLstmDataBackward(&allInputContiguous, dxOut, dhPrevOut, dcPrevOut, dparamsOut, bidirectional, hasBias,
    1595              :                                    numLayers, uniqueExecutor.get());
    1596              :     }
    1597            2 :     CHECK_RET(ret == ACLNN_SUCCESS, ACLNN_ERR_INNER_NULLPTR);
    1598            2 :     *workspaceSize = uniqueExecutor->GetWorkspaceSize();
    1599            2 :     uniqueExecutor.ReleaseTo(executor);
    1600              :     return ACLNN_SUCCESS;
    1601            2 : }
    1602              : 
    1603            0 : aclnnStatus aclnnLstmBackward(void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, aclrtStream stream)
    1604              : {
    1605            0 :     L2_DFX_PHASE_2(aclnnLstmBackward);
    1606              :     //  固定写法,调用框架能力,完成计算
    1607            0 :     return CommonOpExecutorRun(workspace, workspaceSize, executor, stream);
    1608            0 : }
    1609              : 
    1610              : #ifdef __cplusplus
    1611              : }
    1612              : #endif
        

Generated by: LCOV version 2.0-1