aclnnThnnFusedLstmCellBackward

Applicable Products

Applicable ProductsSupported
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

  • Operator function: Performs backward propagation for the remaining computation after matmul in the four gates of an LSTMCell, computing the gradients of the pre-activation values of the four gates in the forward output, the input cx, and the bias b.

  • Formula:

Variable Definitions

  • Input gradients: δht\delta h_t (gradHy), δct\delta c_t (gradC)

  • Forward cache: i,f,g,oi, f, g, o (activation values of each gate storage), ct−1c_{t-1} (cx), ctc_t (cy)

  • Output gradients: δai,δaf,δag,δao\delta a_i, \delta a_f, \delta a_g, \delta a_o (stored in gradGatesOut), δct−1\delta c_{t-1} (gradCxOut)

Phase 1: Intermediate gradients and state backpropagation

First, compute the contribution of the hidden state to the cell state, and aggregate to obtain the total gradient of the cell at the current time step grad_ctotal\text{grad\_}c_{total}:

gcx=tanh⁡(ct)grad_ctotal=δht⋅o⋅(1−gcx2)+δctδct−1=grad_ctotal⋅f\begin{aligned} gcx &= \tanh(c_t) \\ \text{grad\_}c_{total} &= \delta h_t \cdot o \cdot (1 - gcx^2) + \delta c_t \\ \delta c_{t-1} &= \text{grad\_}c_{total} \cdot f \end{aligned}

Phase 2: Gate Component Gradients (Pre-activation)

Based on the code logic, the gradients δa\delta a of each gate before entering the activation function are calculated as follows:

δao=(δht⋅gcx)⋅o⋅(1−o)δai=(grad_ctotal⋅g)⋅i⋅(1−i)δaf=(grad_ctotal⋅ct−1)⋅f⋅(1−f)δag=(grad_ctotal⋅i)⋅(1−g2)\begin{aligned} \delta a_o &= (\delta h_t \cdot gcx) \cdot o \cdot (1 - o) \\ \delta a_i &= (\text{grad\_}c_{total} \cdot g) \cdot i \cdot (1 - i) \\ \delta a_f &= (\text{grad\_}c_{total} \cdot c_{t-1}) \cdot f \cdot (1 - f) \\ \delta a_g &= (\text{grad\_}c_{total} \cdot i) \cdot (1 - g^2) \end{aligned}

Phase 3: Parameter Gradients (db)

1. Bias gradient (db): Summation over the batch dimension (NN):

δb=∑n=1N[δaiδafδagδao]n\delta b = \sum_{n=1}^{N} \begin{bmatrix} \delta a_i \\ \delta a_f \\ \delta a_g \\ \delta a_o \end{bmatrix}_n

Function Prototype

Each operator is divided into a two-phase API. First call the "aclnnThnnFusedLstmCellBackwardGetWorkspaceSize" API to obtain the workspace size required for computation and the executor that contains the operator computation process, and then call the "aclnnThnnFusedLstmCellBackward" API to perform the computation.

aclnnStatus aclnnThnnFusedLstmCellBackwardGetWorkspaceSize(
  const aclTensor     *gradHyOptional,
  const aclTensor     *gradCOptional,
  const aclTensor     *cx,
  const aclTensor     *cy,
  const aclTensor     *storage,
  bool                hasBias,
  aclTensor           *gradGatesOut,
  aclTensor           *gradCxOut,
  aclTensor           *gradBiasOut,
  uint64_t            *workspaceSize,
  aclOpExecutor       **executor)
aclnnStatus aclnnThnnFusedLstmCellBackward(
  void            *workspace,
  uint64_t        workspaceSize,
  aclOpExecutor   *executor,
  aclrtStream     stream)

aclnnThnnFusedLstmCellBackwardGetWorkspaceSize

  • Parameters:

    Parameter Input/Output Description Instruction Data Type Data Format Dimension (Shape) Non-contiguous Tensor
    gradHyOptional Optional input Indicates the gradient of the hidden state from the LSTM cell forward output. - FLOAT32, FLOAT16 ND [batch, hidden_size] √
    gradCOptional Optional input. Indicates the gradient of the cell state from the LSTM cell forward output. Data type consistent with gradHy. FLOAT32, FLOAT16 ND [batch, hidden_size] √
    cx Input Indicates the forward input cell state of the LSTMCell. The data type is consistent with gradHy. FLOAT32, FLOAT16 ND [batch, hidden_size] √
    cy Input Indicates the cell state of the LSTM cell forward output. The data type is consistent with gradHy. FLOAT32, FLOAT16 ND [batch, hidden_size] √
    storage Input Indicates the activation values of the four gates from the LSTM cell forward output. The data type is consistent with input. FLOAT32, FLOAT16 ND [batch, 4 * hidden_size] √
    hasBias Input Whether to compute the bias gradient. - BOOL - - -
    gradGatesOut Output Indicates the gradient of the four gate pre-activation values in the LSTM cell forward computation. The data type is consistent with input. FLOAT32, FLOAT16 ND [batch, 4 * hidden_size] √
    gradCxOut Output Indicates the gradient of the input cell state in the LSTM cell forward pass. The data type is consistent with input. FLOAT32, FLOAT16 ND [batch, hidden_size] √
    gradBiasOut Output Indicates the gradient of the input bias in the LSTM forward pass. The data type is consistent with input. FLOAT32, FLOAT16 ND [4 * hidden_size] √
    workspaceSize Output Returns the workspace size to be allocated on the Device side. Host-side output parameter. UINT64 - - -
    executor Output Returns the operator executor, which contains the operator computation flow. Host-side output parameter. - - - -
  • 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 an aclTensor, is non-optional, and is a null pointer.
    ACLNN_ERR_PARAM_INVALID 161002 The input parameter is an aclTensor whose data type is not within the supported range.
    The input parameters are aclTensors with inconsistent data types.
    The input parameter is an aclTensor whose shape does not meet the corresponding shape requirements.

aclnnThnnFusedLstmCellBackward

  • 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 through the first-phase API aclnnThnnFusedLstmCellBackwardGetWorkspaceSize.
    executor Input Operator executor, which contains the operator computation flow.
    stream Input Specifies the stream that executes the task.
  • Return value

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

Constraints

  • Deterministic computation:

    • aclnnThnnFusedLstmCellBackward defaults to a deterministic implementation.
  • Boundary value scenario description:

    • When the input is Inf, the output is NAN.

    • When the input is NaN, the output is NaN.

Example

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

#include <iostream>
#include <cmath>
#include <vector>
#include "acl/acl.h"
#include "aclnnop/aclnn_thnn_fused_lstm_cell_backward.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;
}

int Init(int32_t deviceId, aclrtStream* stream) {
  // Boilerplate for AscendCL initialization.
  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-side 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);
  // Copy the host-side data to the device memory by calling aclrtMemcpy.
  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;
}

int main() {
  // 1. (Boilerplate) Initialize the device/stream. For details, see the AscendCL external API list.
  // Set deviceId based on the 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 inputs and outputs. Customize the construction based on the API interface.
  // Define variables.
  int64_t n = 1;
  int64_t hiddenSize = 8;

  // Shape definition.
  std::vector<int64_t> bShape = {hiddenSize * 4};
  std::vector<int64_t> dhShape = {n, hiddenSize};
  std::vector<int64_t> gatesShape = {n, 4 * hiddenSize};

  // Device address pointer.
  void* dhyDeviceAddr = nullptr;
  void* dcDeviceAddr = nullptr;
  void* cxDeviceAddr = nullptr;
  void* cyDeviceAddr = nullptr;
  void* storageDeviceAddr = nullptr;

  // Backward propagation output device address pointer.
  void* dgatesDeviceAddr = nullptr;
  void* dcPrevDeviceAddr = nullptr;
  void* dbDeviceAddr = nullptr;

  // ACL Tensor pointer
  aclTensor* dhy = nullptr;
  aclTensor* dc = nullptr;
  aclTensor* cx = nullptr;
  aclTensor* cy = nullptr;
  aclTensor* storage = nullptr;

  // Backward propagation output ACL Tensor pointer
  aclTensor* dgates = nullptr;
  aclTensor* dcPrev = nullptr;
  aclTensor* db = nullptr;

  std::vector<float> dhyHostData(n * hiddenSize, 1.0f); // 1*1*8 = 8 ones
  std::vector<float> dcHostData(n * hiddenSize, 1.0f); // (8+8)*32 = 16*32 = 512 ones
  std::vector<float> cxHostData(n * hiddenSize, 1.0f); // (8+8)*32 = 16*32 = 512 ones
  std::vector<float> cyHostData(n * hiddenSize, 1.0f); // 32 ones
  std::vector<float> storageHostData(n * hiddenSize * 4, 1.0f); // 32 ones

  // Backward propagation output host data (initialized to 0).
  std::vector<float> dgatesHostData(n * hiddenSize * 4, 0.0f);
  std::vector<float> dcPrevHostData(n * hiddenSize, 0.0f);
  std::vector<float> dbHostData(hiddenSize * 4, 0.0f);

  // Create the dhy aclTensor.
  ret = CreateAclTensor(dhyHostData, dhShape, &dhyDeviceAddr, aclDataType::ACL_FLOAT, &dhy);
  CHECK_RET(ret == ACL_SUCCESS, return ret);

  // Create the params aclTensorList.
  ret = CreateAclTensor(dcHostData, dhShape, &dcDeviceAddr, aclDataType::ACL_FLOAT, &dc);
  CHECK_RET(ret == ACL_SUCCESS, return ret);
  ret = CreateAclTensor(cxHostData, dhShape, &cxDeviceAddr, aclDataType::ACL_FLOAT, &cx);
  CHECK_RET(ret == ACL_SUCCESS, return ret);
  ret = CreateAclTensor(cyHostData, dhShape, &cyDeviceAddr, aclDataType::ACL_FLOAT, &cy);
  CHECK_RET(ret == ACL_SUCCESS, return ret);
  ret = CreateAclTensor(storageHostData, gatesShape, &storageDeviceAddr, aclDataType::ACL_FLOAT, &storage);
  CHECK_RET(ret == ACL_SUCCESS, return ret);

  // Create backward propagation output tensors.
  // Create the dgates aclTensor.
  ret = CreateAclTensor(dgatesHostData, gatesShape, &dgatesDeviceAddr, aclDataType::ACL_FLOAT, &dgates);
  CHECK_RET(ret == ACL_SUCCESS, return ret);

  // Create the dcPrev aclTensor.
  ret = CreateAclTensor(dcPrevHostData, dhShape, &dcPrevDeviceAddr, aclDataType::ACL_FLOAT, &dcPrev);
  CHECK_RET(ret == ACL_SUCCESS, return ret);

  // Create the db aclTensor.
  ret = CreateAclTensor(dbHostData, bShape, &dbDeviceAddr, aclDataType::ACL_FLOAT, &db);
  CHECK_RET(ret == ACL_SUCCESS, return ret);

  // 3. Call the CANN operator library API. Replace with the specific API name.
  uint64_t workspaceSize = 0;
  aclOpExecutor* executor;
  // Call the first-phase API of aclnnThnnFusedLstmCellBackward.
  ret = aclnnThnnFusedLstmCellBackwardGetWorkspaceSize(dhy, dc, cx, cy, storage, true, dgates, dcPrev, db,
    &workspaceSize, &executor);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnThnnFusedLstmCellBackwardGetWorkspaceSize failed. ERROR: %d\n", ret); return ret);
  // Allocate 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 aclnnThnnFusedLstmCellBackward.
  ret = aclnnThnnFusedLstmCellBackward(workspaceAddr, workspaceSize, executor, stream);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnThnnFusedLstmCellBackward 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 this step based on the specific API definition.
  // Print the dparams result.
  auto dgatesSize = GetShapeSize(gatesShape);
  std::vector<float> resultDgatesData(dgatesSize, 0);
  ret = aclrtMemcpy(resultDgatesData.data(), resultDgatesData.size() * sizeof(resultDgatesData[0]), dgatesDeviceAddr,
                    dgatesSize * sizeof(resultDgatesData[0]), ACL_MEMCPY_DEVICE_TO_HOST);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("copy dgates result from device to host failed. ERROR: %d\n", ret); return ret);
  for (int64_t i = 0; i < dgatesSize; i++) {
    LOG_PRINT("result dgates[%ld] is: %f\n", i, resultDgatesData[i]);
  }

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

  auto dcPrevSize = GetShapeSize(dhShape);
  std::vector<float> resultDcPrevData(dcPrevSize, 0);
  ret = aclrtMemcpy(resultDcPrevData.data(), resultDcPrevData.size() * sizeof(resultDcPrevData[0]), dcPrevDeviceAddr,
                    dcPrevSize * sizeof(resultDcPrevData[0]), ACL_MEMCPY_DEVICE_TO_HOST);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("copy dcPrev result from device to host failed. ERROR: %d\n", ret); return ret);
  for (int64_t i = 0; i < dcPrevSize; i++) {
    LOG_PRINT("result dcPrev[%ld] is: %f\n", i, resultDcPrevData[i]);
  }
  // Release the aclTensor.
  aclDestroyTensor(dhy);
  aclDestroyTensor(dc);
  aclDestroyTensor(cx);
  aclDestroyTensor(cy);
  aclDestroyTensor(storage);
  aclDestroyTensor(dgates);
  aclDestroyTensor(dcPrev);
  aclDestroyTensor(db);

  // Release the device resources.
  aclrtFree(dhyDeviceAddr);
  aclrtFree(dcDeviceAddr);
  aclrtFree(cxDeviceAddr);
  aclrtFree(cyDeviceAddr);
  aclrtFree(storageDeviceAddr);
  aclrtFree(dgatesDeviceAddr);
  aclrtFree(dcPrevDeviceAddr);
  aclrtFree(dbDeviceAddr);
  if (workspaceSize > 0) {
    aclrtFree(workspaceAddr);
  }
  aclrtDestroyStream(stream);
  aclrtResetDevice(deviceId);
  aclFinalize();

  return 0;
}