aclnnLSTM

Applicable Products

ProductSupported
Ascend 950PR/Ascend 950DT×
Atlas A3 training products/Atlas A3 inference products√
Atlas A2 training products/Atlas A2 inference products√
Atlas 200I/500 A2 inference products×
Atlas inference products√
Atlas training products√

Function

  • Description: The LSTM (Long Short-Term Memory) network is a special type of recurrent neural network (RNN) model. Performs LSTM network computation, receives an input sequence and initial states, and returns an output sequence and final states.

  • Formula:

    (1)ft=σ(Wf[ht−1,xt]+bf)(2)it=σ(Wi[ht−1,xt]+bi)(3)ot=σ(Wo[ht−1,xt]+bo)(4)c~t=tanh(Wc[ht−1,xt]+bc)(5)ct=ft⊙ct−1+it⊙c~t(6)cot=tanh(ct)(7)ht=ot⊙cot\begin{aligned} (1)\qquad f_t &=\sigma(W_f[h_{t-1}, x_t] + b_f) \\ (2)\qquad i_t &=\sigma(W_i[h_{t-1}, x_t] + b_i) \\ (3)\qquad o_t &=\sigma(W_o[h_{t-1}, x_t] + b_o) \\ (4)\qquad \tilde{c}_t &=tanh(W_c[h_{t-1}, x_t] + b_c) \\ (5)\qquad c_t &=f_t ⊙ c_{t-1} + i_t ⊙ \tilde{c}_t \\ (6)\qquad c_{o}^{t} &=tanh(c_t) \\ (7)\qquad h_t &=o_t ⊙ c_{o}^{t} \\ \end{aligned}
    • xt∈Rdx_t ∈ R^{d}: Input vector of the LSTM unit.

    • ft∈(0,1)hf_t ∈ (0, 1)^{h}: forget gate activation vector.

    • it∈(0,1)hi_t ∈ (0, 1)^{h}: input gate and update gate activation vector.

    • ot∈(0,1)ho_t ∈ (0, 1)^{h}: output gate activation vector.

    • hi∈(−1,1)hh_i ∈ (-1, 1)^{h}: hidden state vector, also referred to as the output vector of the LSTM cell.

    • c~t∈(−1,1)h\tilde{c}_t ∈ (-1, 1)^{h}: cell input activation vector.

    • ct∈Rhc_t ∈ R^{h}: cell state vector.

    • W∈Rh×d,(U∈Rh×h)∩(b∈Rh)W ∈ R^{h×d},(U ∈ R^{h×h})∩(b ∈ R^{h}): weight matrices and bias vector parameters learned during training.

Function Prototype

Each operator uses a two-phase API. You must first call the "aclnnLSTMGetWorkspaceSize" API to obtain the input parameters and calculate the required workspace size based on the process, and then call the "aclnnLSTM" API to perform the computation.

aclnnStatus aclnnLSTMGetWorkspaceSize(
    const aclTensor     *input,
    const aclTensorList *params,
    const aclTensorList *hx,
    const aclTensor     *batchSizes,
    bool                 hasBias,
    int64_t              numLayers,
    double               dropout,
    bool                 train,
    bool                 bidirectional,
    bool                 batchFirst,
    aclTensor           *output,
    aclTensor           *hy,
    aclTensor           *cy,
    aclTensorList       *iOut,
    aclTensorList       *jOut,
    aclTensorList       *fOut,
    aclTensorList       *oOut,
    aclTensorList       *hOut,
    aclTensorList       *cOut,
    aclTensorList       *tanhCOut,
    uint64_t            *workspaceSize,
    aclOpExecutor       **executor);
aclnnStatus aclnnLSTM(
  void          *workspace,
  uint64_t       workspaceSize,
  aclOpExecutor *executor,
  aclrtStream    stream)

aclnnLSTMGetWorkspaceSize

  • Parameters:

    Parameter Input/Output Description Instruction Data Type Data Format Dimension (Shape) Non-contiguous Tensor
    Input Input Input vector of the LSTM unit.
    • If batchSizes is a null pointer:
      The shape format varies based on the batchFirst parameter:
      • batchFirst=False: (time_step, batch_size, input_size)
      • batchFirst=True: (batch_size, time_step, input_size)
      Description: batchFirst indicates whether the batch dimension is in the first dimension; time_step is the time dimension; batch_size is the number of samples processed at each time step; input_size is the number of input features.
    • If a valid batchSizes is passed:
      Shape format: (time_step * batch_size, input_size)
      Description: The memory layout is the same as (time_step, batch_size, input_size).
    FLOAT16, FLOAT32 ND 3 √
    params input Represents the list of weight and bias tensors in the LSTM operation.

      List length formula: 2 * D * B * num_layers

      • num_layers: Corresponds to the numLayers parameter, indicating the number of LSTM layers.
      • D: D = 2 when bidirection = True; otherwise, D = 1.
      • B: B = 2 when has_biases = True; otherwise, B = 1.

      Special scenario (bidirection = True and has_biases = True):

      Parameter layout: [weight_ih_0, weight_hh_0, bias_ih_0, bias_hh_0, weight_ih_reverse_0, weight_hh_reverse_0, bias_ih_reverse_0, bias_hh_reverse_0]

      Core parameter description (using layer 0 as an example):

      • weight_ih_0: Input weight parameter of layer 0, shape = (4 * hidden_size, cur_input_size)
        Note: cur_input_size is the number of input features for each layer (first layer = input_size; subsequent layers = hidden_size; when bidirectional = 2 * hidden_size).
      • weight_hh_0: Hidden layer weight parameter of layer 0, shape = (4 * hidden_size, hidden_size).
      • bias_ih_0: Input weight bias of layer 0, shape = (4 * hidden_size).
      • bias_hh_0: Hidden layer weight bias of layer 0, shape = (4 * hidden_size).
    FLOAT16, FLOAT32 ND / √
    hx Optional input Specifies the initial hidden and cell state list in the LSTM operation. The list length is 2, and each shape in the list supports 3D (D * num_layers, batch_size, hidden_size). If the input is empty, the initial hidden and cell states are 0. FLOAT16, FLOAT32 ND / √
    batchSizes Optional input. Indicates the number of valid batches that actually participate in the computation at each time step. When nullptr is passed, the input is in fixed-length mode; otherwise, it is in variable-length mode. Shape: (time_step,). The elements must be arranged in descending order, each being a positive integer not exceeding the total number of batches, and the first element must equal the total number of batches. INT64 ND 1 √
    hasBias Input Indicates whether biases exist. / BOOL ND / √
    numLayers Input Specifies the number of LSTM layers. / INT64 ND / √
    dropout Input Indicates the probability of the random mask. This feature is not supported. DOUBLE ND 1 √
    train Input Indicates whether it is in training mode. When train = True, intermediate results are saved during forward LSTM computation for backward propagation. When train = False, no intermediate results are saved during forward computation. BOOL ND / √
    bidirectional Input. Indicates whether it is bidirectional. / BOOL ND / √
    batchFirst Input Indicates whether the input data format places the batch in the first axis (B, T, H). / BOOL ND / √
    output Output Represents the output result of the last layer at each time step in the LSTM operation.
    • If batchSizes is a null pointer:
      When batchFirst=False, the shape supports 3D (time_step, batch_size, D * hidden_size); otherwise, it supports 3D (batch_size, time_step, D * hidden_size).
    • If a valid batchSizes is passed:
      The shape should be (time_step, batch_size, D * hidden_size).
    FLOAT16, FLOAT32 ND 3 √
    hy Output Represents the hidden layer at the last time step of each layer in the LSTM computation (output of Formula (7)). Shape supports 3D (D * num_layers, batch_size, hidden_size). FLOAT16、FLOAT32 ND 3 √
    cy Output Represents the cell state of the last time step of each layer in the LSTM computation (the output of formula (5)). Shape supports 3D (D * num_layers, batch_size, hidden_size). FLOAT16, FLOAT32 ND 3 √
    hy Output Represents the hidden state of the last time step in each layer during the LSTM computation (output of Formula (7)). Shape supports 3D (D * num_layers, batch_size, hidden_size). FLOAT16, FLOAT32 ND 3 √
    cy Output Represents the cell state of the last time step of each layer in the LSTM computation (the output of Formula (5)). The shape supports 3D (D * num_layers, batch_size, hidden_size). FLOAT16, FLOAT32 ND 3 √
    iOut Output Represents the activation value of the input gate in each layer of the LSTM operation (sigmoid output, the output of formula (2)). The list length is D * num_layers. Each shape in the list supports 3D (time_step, batch_size, hidden_size). When train is set to False, there is no output value. FLOAT16, FLOAT32 ND 3 √
    jOut Output Represents the candidate cell state of each layer in the LSTM operation (tanh output, the output of formula (4)). The list length is D * num_layers. Each shape in the list supports 3D (time_step, batch_size, hidden_size). When train is set to False, there is no output value. FLOAT16, FLOAT32 ND 3 √
    fOut Output Represents the activation value of the forget gate in each layer during the LSTM computation (sigmoid output, the output of formula (1)). The list length is D * num_layers. Each shape in the list supports 3D (time_step, batch_size, hidden_size). When train is set to False, there is no output value. FLOAT16, FLOAT32 ND 3 √
    oOut Output Represents the activation value of the output gate in each layer of the LSTM computation (sigmoid output, the output of formula (3)). The list length is D * num_layers. Each element in the list supports 3D (time_step, batch_size, hidden_size). When train=False, there is no output value. FLOAT16, FLOAT32 ND 3 √
    hOut Output Represents the hidden layer output of each layer in the LSTM computation (the output of formula (7)). The list length is D * num_layers. Each element in the list supports 3D (time_step, batch_size, hidden_size). When train=False, there is no output value. FLOAT16, FLOAT32 ND / √
    cOut Output Indicates the final cell state of each layer in the LSTM computation (the output of Formula (5)). The list length is D * num_layers. Each shape in the list supports 3D (time_step, batch_size, hidden_size). When train is set to False, there is no output value. FLOAT16, FLOAT32 ND / √
    tanhCOut Output Represents the output after the final cell state of each layer passes through the tanh activation function in the LSTM operation (the output of Formula (6)). The list length is D * num_layers. Each shape in the list supports 3D (time_step, batch_size, hidden_size). When train is set to False, there is no output value. FLOAT16, FLOAT32 ND / √
    workspaceSize Output Returns the workspace size to be applied for on the Device side. - - - - -
    executor Output Returns the operator executor, which contains the operator computation process. - - - - -
  • Return value

    aclnnStatus: return code. For details, see aclnn Return Code.

    The first-phase API performs input parameter validation and returns an error in the following scenarios:

    Return Code Error Code Description
    ACLNN_ERR_PARAM_NULLPTR 161001 The input parameter is a required input, output, or required attribute, and is a null pointer.
    ACLNN_ERR_PARAM_INVALID 161002 The input parameter is of the aclTensor type but its data type is not within the supported range.

aclnnLSTM

  • Parameters:

    Parameter Input/Output Description
    workspace Input Address of the workspace memory applied for on the Device side.
    workspaceSize Input Size of the workspace applied for on the Device side, obtained from the first-phase API aclnnLSTMGetWorkspaceSize.
    executor Input Op executor, which contains the operator computation process.
    stream Input Specifies the stream for task execution.
  • Return value

    aclnnStatus: return code. For details, see aclnn Return Code.

Constraints

  • Deterministic computation:

    • aclnnLSTM defaults to a deterministic implementation.
  • All inputs and outputs that support FLOAT16 and FLOAT32 types must have the same data type.

Example

The following provides example code for reference only. For details about compilation and running, see Compile and Run Samples.

/**
 * Copyright (c) 2026 Huawei Technologies Co., Ltd.
 * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
 * CANN Open Software License Agreement Version 2.0 (the "License").
 * Please refer to the License for details. You may not use this file except in compliance with the License.
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
 * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
 * See LICENSE in the root of the software repository for the full text of the License.
 */

/**
 * @file main.cpp
 */

#include <iostream>
#include <vector>
#include "acl/acl.h"
#include "aclnnop/aclnn_lstm.h"

#define CHECK_RET(cond, return_expr) \
    do {                             \
        if (!(cond)) {               \
            return_expr;             \
        }                            \
    } while (0)

#define LOG_PRINT(message, ...)         \
    do {                                \
        printf(message, ##__VA_ARGS__); \
    } while (0)

int64_t GetShapeSize(const std::vector<int64_t>& shape)
{
    int64_t shapeSize = 1;
    for (auto i : shape) {
        shapeSize *= i;
    }
    return shapeSize;
}

void PrintOutResult(const std::vector<int64_t>& shape, void** deviceAddr)
{
    auto size = GetShapeSize(shape);
    std::vector<float> resultData(size, 0);
    auto ret = aclrtMemcpy(
        resultData.data(), resultData.size() * sizeof(resultData[0]), *deviceAddr, size * sizeof(resultData[0]),
        ACL_MEMCPY_DEVICE_TO_HOST);
    CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("copy result from device to host failed. ERROR: %d\n", ret); return);
    for (int64_t i = 0; i < size; i++) {
        LOG_PRINT("result[%ld] is: %f\n", i, resultData[i]);
    }
}

int Init(int32_t deviceId, aclrtStream* stream)
{
    // Boilerplate. Initialize AscendCL.
    auto ret = aclInit(nullptr);
    CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclInit failed. ERROR: %d\n", ret); return ret);
    ret = aclrtSetDevice(deviceId);
    CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSetDevice failed. ERROR: %d\n", ret); return ret);
    ret = aclrtCreateStream(stream);
    CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtCreateStream failed. ERROR: %d\n", ret); return ret);
    return 0;
}

template <typename T>
int CreateAclTensor(
    const std::vector<T>& hostData, const std::vector<int64_t>& shape, void** deviceAddr, aclDataType dataType,
    aclTensor** tensor)
{
    auto size = GetShapeSize(shape) * sizeof(T);
    // Call aclrtMalloc to apply for device memory.
    auto ret = aclrtMalloc(deviceAddr, size, ACL_MEM_MALLOC_HUGE_FIRST);
    CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMalloc failed. ERROR: %d\n", ret); return ret);
    // Call aclrtMemcpy to copy host data to device memory.
    ret = aclrtMemcpy(*deviceAddr, size, hostData.data(), size, ACL_MEMCPY_HOST_TO_DEVICE);
    CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMemcpy failed. ERROR: %d\n", ret); return ret);

    // Calculate the strides of a contiguous tensor.
    std::vector<int64_t> strides(shape.size(), 1);
    for (int64_t i = shape.size() - 2; i >= 0; i--) {
        strides[i] = shape[i + 1] * strides[i + 1];
    }

    // Call the aclCreateTensor API to create an aclTensor.
    *tensor = aclCreateTensor(
        shape.data(), shape.size(), dataType, strides.data(), 0, aclFormat::ACL_FORMAT_ND, shape.data(), shape.size(),
        *deviceAddr);
    return 0;
}

template <typename T>
int CreateAclTensorList(
    const std::vector<std::vector<int64_t>>& shapes, void** deviceAddr, aclDataType dataType, aclTensorList** tensor,
    T initVal = 1)
{
    int size = shapes.size();
    aclTensor* tensors[size];
    for (int i = 0; i < size; i++) {
        std::vector<T> hostData(GetShapeSize(shapes[i]), initVal);
        int ret = CreateAclTensor<float>(hostData, shapes[i], deviceAddr + i, dataType, tensors + i);
        CHECK_RET(ret == ACL_SUCCESS, return ret);
    }
    *tensor = aclCreateTensorList(tensors, size);
    return ACL_SUCCESS;
}

int main()
{
    // 1. (Boilerplate) Initialize the device/stream. Refer to the AscendCL external API list.
    // Fill in the deviceId based on your actual device.
    int32_t deviceId = 0;
    aclrtStream stream;
    auto ret = Init(deviceId, &stream);
    CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("Init acl failed. ERROR: %d\n", ret); return ret);

    // 2. Construct the inputs and outputs. You need to customize the construction based on the API interface.
    int time_step = 1;
    int batch_size = 1;
    int hidden_size = 1;
    int input_size = hidden_size;
    int64_t numLayers = 1;
    bool isbias = false;
    bool batchFirst = false;
    bool bidirection = false;
    bool isTraining = true;
    int64_t d_scale = bidirection == true ? 2 : 1;

    std::vector<int64_t> inputShape = {time_step, batch_size, input_size};
    std::vector<int64_t> outputShape = {time_step, batch_size, d_scale * hidden_size};
    std::vector<int64_t> hycyShape = {numLayers * d_scale, batch_size, hidden_size};
    std::vector<std::vector<int64_t>> paramsListShape = {};

    std::vector<std::vector<int64_t>> outIListShape = {};
    std::vector<std::vector<int64_t>> outJListShape = {};
    std::vector<std::vector<int64_t>> outFListShape = {};
    std::vector<std::vector<int64_t>> outOListShape = {};
    std::vector<std::vector<int64_t>> outHListShape = {};
    std::vector<std::vector<int64_t>> outCListShape = {};
    std::vector<std::vector<int64_t>> outTanhCListShape = {};

    auto cur_input_size = input_size;
    for (int i = 0; i < numLayers; i++) {
        paramsListShape.push_back({hidden_size * 4, cur_input_size});
        paramsListShape.push_back({hidden_size * 4, hidden_size});

        outIListShape.push_back({time_step, batch_size, hidden_size});
        outJListShape.push_back({time_step, batch_size, hidden_size});
        outFListShape.push_back({time_step, batch_size, hidden_size});
        outOListShape.push_back({time_step, batch_size, hidden_size});
        if (isTraining == true) {
            outHListShape.push_back({time_step, batch_size, hidden_size});
            outCListShape.push_back({time_step, batch_size, hidden_size});
        } else {
            outHListShape.push_back({batch_size, hidden_size});
            outCListShape.push_back({batch_size, hidden_size});
        }
        outTanhCListShape.push_back({time_step, batch_size, hidden_size});
        cur_input_size = hidden_size;
    }

    void* inputDeviceAddr = nullptr;
    void* paramsListDeviceAddr[2 * numLayers];

    void* outputDeviceAddr = nullptr;
    void* hyDeviceAddr = nullptr;
    void* cyDeviceAddr = nullptr;
    void* outIListDeviceAddr[numLayers];
    void* outJListDeviceAddr[numLayers];
    void* outFListDeviceAddr[numLayers];
    void* outOListDeviceAddr[numLayers];
    void* outHListDeviceAddr[numLayers];
    void* outCListDeviceAddr[numLayers];
    void* outTanhCListDeviceAddr[numLayers];

    aclTensor* input = nullptr;
    aclTensorList* params = nullptr;

    aclTensor* output = nullptr;
    aclTensor* hy = nullptr;
    aclTensor* cy = nullptr;
    aclTensorList* outIList = nullptr;
    aclTensorList* outJList = nullptr;
    aclTensorList* outFList = nullptr;
    aclTensorList* outOList = nullptr;
    aclTensorList* outHList = nullptr;
    aclTensorList* outCList = nullptr;
    aclTensorList* outTanhCList = nullptr;

    std::vector<float> inputHostData(GetShapeSize(inputShape), 1);

    ret = CreateAclTensor<float>(inputHostData, inputShape, &inputDeviceAddr, aclDataType::ACL_FLOAT, &input);
    CHECK_RET(ret == ACL_SUCCESS, return ret);

    ret = CreateAclTensorList<float>(paramsListShape, paramsListDeviceAddr, aclDataType::ACL_FLOAT, ¶ms, 1.0);
    CHECK_RET(ret == ACL_SUCCESS, return ret);

    std::vector<float> outputHostData(GetShapeSize(outputShape), 1);
    ret = CreateAclTensor<float>(outputHostData, outputShape, &outputDeviceAddr, aclDataType::ACL_FLOAT, &output);
    CHECK_RET(ret == ACL_SUCCESS, return ret);

    std::vector<float> hycyHostData(GetShapeSize(hycyShape), 1);
    ret = CreateAclTensor<float>(hycyHostData, hycyShape, &hyDeviceAddr, aclDataType::ACL_FLOAT, &hy);
    CHECK_RET(ret == ACL_SUCCESS, return ret);

    ret = CreateAclTensor<float>(hycyHostData, hycyShape, &cyDeviceAddr, aclDataType::ACL_FLOAT, &cy);
    CHECK_RET(ret == ACL_SUCCESS, return ret);

    ret = CreateAclTensorList<float>(outIListShape, outIListDeviceAddr, aclDataType::ACL_FLOAT, &outIList, 0.0);
    CHECK_RET(ret == ACL_SUCCESS, return ret);

    ret = CreateAclTensorList<float>(outJListShape, outJListDeviceAddr, aclDataType::ACL_FLOAT, &outJList, 0.0);
    CHECK_RET(ret == ACL_SUCCESS, return ret);

    ret = CreateAclTensorList<float>(outFListShape, outFListDeviceAddr, aclDataType::ACL_FLOAT, &outFList, 0.0);
    CHECK_RET(ret == ACL_SUCCESS, return ret);

    ret = CreateAclTensorList<float>(outOListShape, outOListDeviceAddr, aclDataType::ACL_FLOAT, &outOList, 0.0);
    CHECK_RET(ret == ACL_SUCCESS, return ret);

    ret = CreateAclTensorList<float>(outHListShape, outHListDeviceAddr, aclDataType::ACL_FLOAT, &outHList, 0.0);
    CHECK_RET(ret == ACL_SUCCESS, return ret);

    ret = CreateAclTensorList<float>(outCListShape, outCListDeviceAddr, aclDataType::ACL_FLOAT, &outCList, 0.0);
    CHECK_RET(ret == ACL_SUCCESS, return ret);

    ret = CreateAclTensorList<float>(
        outTanhCListShape, outTanhCListDeviceAddr, aclDataType::ACL_FLOAT, &outTanhCList, 0.0);
    CHECK_RET(ret == ACL_SUCCESS, return ret);

    // 3. Call the CANN operator library API. Modify it to the specific API name.
    uint64_t workspaceSize = 0;
    aclOpExecutor* executor;

    // Call the aclnnLSTM first-phase API.
    ret = aclnnLSTMGetWorkspaceSize(
        input, params, nullptr, nullptr, isbias, numLayers, 0.0, isTraining, bidirection, batchFirst, output, hy, cy,
        outIList, outJList, outFList, outOList, outHList, outCList, outTanhCList, &workspaceSize, &executor);

    CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnLSTMGetWorkspaceSize failed. ERROR: %d\n", ret); return ret);

    // Apply for device memory based on the workspaceSize calculated by the first-phase API.
    void* workspaceAddr = nullptr;
    if (workspaceSize > 0) {
        ret = aclrtMalloc(&workspaceAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST);
        CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("allocate workspace failed. ERROR: %d\n", ret); return ret);
    }

    // Call the second-phase API of aclnnLSTM.
    ret = aclnnLSTM(workspaceAddr, workspaceSize, executor, stream);
    CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnLSTM failed. ERROR: %d\n", ret); return ret);

    // 4. (Boilerplate) Synchronously wait for task execution to complete.
    ret = aclrtSynchronizeStream(stream);
    CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", ret); return ret);

    // 5. Obtain the output values and copy the results from the device memory to the host memory. Modify based on the specific API definition.

    PrintOutResult(outputShape, &outputDeviceAddr);

    // 6. Release aclTensor and aclScalar. Modify based on the specific API definition.
    aclDestroyTensor(input);
    aclDestroyTensorList(params);
    aclDestroyTensor(output);
    aclDestroyTensor(hy);
    aclDestroyTensor(cy);
    aclDestroyTensorList(outIList);
    aclDestroyTensorList(outJList);
    aclDestroyTensorList(outFList);
    aclDestroyTensorList(outOList);
    aclDestroyTensorList(outHList);
    aclDestroyTensorList(outCList);
    aclDestroyTensorList(outTanhCList);

    //   // 7. Release device resources.
    aclrtFree(inputDeviceAddr);
    aclrtFree(outputDeviceAddr);
    aclrtFree(hyDeviceAddr);
    aclrtFree(cyDeviceAddr);
    for (int i = 0; i < numLayers; i++) {
        aclrtFree(outIListDeviceAddr[i]);
        aclrtFree(outJListDeviceAddr[i]);
        aclrtFree(outFListDeviceAddr[i]);
        aclrtFree(outOListDeviceAddr[i]);
        aclrtFree(outHListDeviceAddr[i]);
        aclrtFree(outCListDeviceAddr[i]);
        aclrtFree(outTanhCListDeviceAddr[i]);
    }

    if (workspaceSize > 0) {
        aclrtFree(workspaceAddr);
    }
    aclrtDestroyStream(stream);
    aclrtResetDevice(deviceId);
    aclFinalize();

    return 0;
}