aclnnFlashAttentionUnpaddingScoreGrad

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: Computes the backward output of attention in training scenarios, i.e., the backward computation of aclnnFlashAttentionVarLenScore.

  • Formula:

    The forward formula of attention is:

    Y=Dropout(Softmax(Mask(QKT+psed),atten_mask),keep_prob)VY=Dropout(Softmax(Mask(\frac{QK^T+pse}{\sqrt{d}}),atten\_mask),keep\_prob)V

    For ease of expression, variables SS and PP are used to represent the formulas:

    S=Mask(QKT+psed),atten_maskS=Mask(\frac{QK^T+pse}{\sqrt{d}}),atten\_mask P=Dropout(Softmax(S),keep_prob)P=Dropout(Softmax(S),keep\_prob) Y=PVY=PV

    The backward propagation formula of the attention mechanism is:

    dV=PTdYdV=P^TdY dQ=((dS)∗K)ddQ=\frac{((dS)*K)}{\sqrt{d}} dK=((dS)T∗Q)ddK=\frac{((dS)^T*Q)}{\sqrt{d}}

Function Prototype

Each operator is divided into a two-phase API. You must first call the "aclnnFlashAttentionUnpaddingScoreGradGetWorkspaceSize" API to obtain the workspace size required for computation and the executor that contains the operator computation process, and then call the "aclnnFlashAttentionUnpaddingScoreGrad" API to perform the computation.

aclnnStatus aclnnFlashAttentionUnpaddingScoreGradGetWorkspaceSize(
  const aclTensor*   query,
  const aclTensor*   keyIn,
  const aclTensor*   value,
  const aclTensor*   dy,
  const aclTensor*   pseShiftOptional,
  const aclTensor*   dropMaskOptional,
  const aclTensor*   paddingMaskOptional,
  const aclTensor*   attenMaskOptional,
  const aclTensor*   softmaxMaxOptional,
  const aclTensor*   softmaxSumOptional,
  const aclTensor*   softmaxInOptional,
  const aclTensor*   attentionInOptional,
  const aclIntArray* prefixOptional,
  const aclIntArray* actualSeqQLenOptional,
  const aclIntArray* actualSeqKvLenOptional,
  double             scaleValue,
  double             keepProb,
  int64_t            preTokens,
  int64_t            nextTokens,
  int64_t            headNum,
  char*              inputLayout,
  int64_t            innerPrecise,
  int64_t            sparseMode,
  const aclTensor*   dqOut,
  const aclTensor*   dkOut,
  const aclTensor*   dvOut,
  const aclTensor*   dpseOut,
  uint64_t*          workspaceSize,
  aclOpExecutor**    executor)
aclnnStatus aclnnFlashAttentionUnpaddingScoreGrad(
  void*             workspace,
  uint64_t          workspaceSize,
  aclOpExecutor*    executor,
  const aclrtStream stream)

aclnnFlashAttentionUnpaddingScoreGradGetWorkspaceSize

  • Parameters

    Parameter Input/Output Description Instruction Data Type Data Format Dimension (Shape) Non-contiguous Tensor
    query Input Q in the formula. The data type is consistent with keyIn/value. FLOAT16, BFLOAT16, FLOAT32 ND [TND] √
    keyIn input K in the formula. The data type is consistent with that of query/value. FLOAT16, BFLOAT16, FLOAT32 ND [TND] √
    value Input V in the formula. The data type is consistent with that of query and keyIn. FLOAT16, BFLOAT16, FLOAT32 ND [TND] √
    dy input dY in the formula. - FLOAT16, BFLOAT16, FLOAT32 ND [TND] √
    pseShiftOptional optional input pse in the formula. The data type is consistent with that of query. FLOAT16, BFLOAT16, FLOAT32 ND [B,N,1024,Skv], [1,N,1024,Skv] √
    dropMaskOptional Optional input. Dropout mask. - UINT8 ND 0, 1 √
    paddingMaskOptional Optional input Reserved parameter. Not currently used. Pass null when calling. - - - -
    attenMaskOptional Optional input atten_mask in the formula. A value of 1 indicates that the position does not participate in the computation, and a value of 0 indicates that the position participates in the computation. BOOL, UINT8 ND [B,N,Sq,Skv], [B,1,Sq,Skv], [1,1,Sq,Skv], [Sq,Skv] √
    softmaxMaxOptional Optional input. Intermediate output of the forward softmax. - FLOAT ND [N,T,8] √
    softmaxSumOptional Optional input. Intermediate output of the forward softmax. - FLOAT ND [N,T,8] √
    softmaxInOptional Optional input Intermediate output of the forward softmax. Reserved parameter, not currently used. - - - -
    attentionInOptional Optional input Forward attention output. Consistent with the data type and shape of query. FLOAT16, BFLOAT16, FLOAT32 ND [TND] √
    prefixOptional Optional input N of each batch in the prefix sparse scenario. - INT64 ND 0, 1 -
    actualSeqQLenOptional Optional input Actual query sequence length. - INT64 ND 1 -
    actualSeqKvLenOptional optional input Actual Key/Value sequence length. - INT64 ND 1 -
    scaleValue Optional input Scale scaling factor. - DOUBLE - - -
    keepProb optional input Proportion of 1s in dropMaskOptional. - DOUBLE - - -
    preTokens Optional input Left boundary of the sliding window in sparse computation. - INT64 - - -
    nextTokens optional input Right boundary of the sliding window in sparse computation. - INT64 - - -
    headNum Input Number of heads on a single card, that is, the length of the N-axis of Query. - INT64 - - -
    inputLayout Input Input Q/K/V data layout. Supports TND. String - - -
    innerPrecise Optional input Controls the internal computation precision. Reserved parameter, not currently used. INT64 - - -
    sparseMode Optional input Sparse mode. Supports configuration of 0 to 8. 5 is not supported. INT64 - - -
    dqOut Output dQ in the formula, indicating the gradient of Query. - FLOAT16, BFLOAT16, FLOAT32 ND [TND] √
    dkOut Output dK in the formula. Gradient of Key. - FLOAT16, BFLOAT16, FLOAT32 ND [TND] √
    dvOut Output dV in the formula, gradient of Value. - FLOAT16, BFLOAT16, FLOAT32 ND [TND] √
    dpseOut Output d(pse) gradient. Reserved parameter, not currently used. - - - -
    workspaceSize Output Returns the workspace size to be applied 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 Value Error Code Description
    ACLNN_ERR_PARAM_NULLPTR 161001 The input parameter is a mandatory input, output, or mandatory attribute, and is a null pointer.
    ACLNN_ERR_PARAM_INVALID 161002 The data type of query, keyIn, value, dy, pseShiftOptional, dropMaskOptional, paddingMaskOptional, attenMaskOptional, softmaxMaxOptional, softmaxSumOptional, softmaxInOptional, attentionInOptional, dqOut, dkOut, or dvOut is not within the supported range.
    The data format of query, keyIn, value, dy, pseShiftOptional, dropMaskOptional, paddingMaskOptional, attenMaskOptional, softmaxMaxOptional, softmaxSumOptional, softmaxInOptional, attentionInOptional, dqOut, dkOut, and dvOut is not within the supported range.

aclnnFlashAttentionUnpaddingScoreGrad

  • Parameters

    Parameter Input/Output Description
    workspace Input Workspace memory address applied on the device side.
    workspaceSize Input Workspace size applied on the device side, obtained from the first-phase API aclnnFlashAttentionUnpaddingScoreGradGetWorkspaceSize.
    executor input Operator executor that contains the operator computation flow.
    stream input Specifies the stream for task execution.
  • Return Value

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

Constraints

  • Deterministic computation:

    • aclnnFlashAttentionUnpaddingScoreGrad defaults to a non-deterministic implementation. Deterministic computation can be enabled through aclrtCtxSetSysParamOpt.
  • When this API is used with PyTorch, ensure that the CANN-related packages match the versions of the PyTorch-related packages.

  • The B (batchsize) of the input query, key, value, and dy must be equal. The inputLayout must be consistent.

  • The D (Head-Dim) of the input query, key, and value must satisfy (qD == kD && kD >= vD).

  • The data types of the input query, key, value, and pseShiftOptional must be consistent.

  • The shapes of the input key and value must be consistent except D. When the D sizes of query, key, and value are the same, the shapes of query and dy must be consistent.

  • Supports unequal N between the input query and key/value, but they must have a proportional relationship, that is, Nq/Nkv must be a non-zero integer, with Nq in the value range of 1 to 256.

  • For constraints on data shapes, taking inputLayout TND as an example:

    • T: The value range is 1 to 1M.

    • N: The value range is 1 to 256.

    • D: The value range is 1 to 768.

    • KeepProb: The value range is (0, 1].

  • The data format of query, key, and value supports only TND, where T is the concatenated and tightly packed data of B and S (SeqLenQ and SeqLenKV of each batch). B (Batch) indicates the batch size of input samples, S (Seq-Length) indicates the sequence length of input samples, H (Head-Size) indicates the size of the hidden layer, N (Head-Num) indicates the number of heads, and D (Head-Dim) indicates the smallest unit dimension of the hidden layer, satisfying D = H/N.

  • pseShiftOptional: If Sq is greater than 1024, Sq and Skv of each batch are of equal length, and the scenario is a lower triangular mask with sparseMode 0, 2, or 3, alibi position encoding compression can be enabled. In this case, only the last 1024 rows of the original PSE need to be input for memory optimization, that is, alibi_compress = ori_pse[:, :, -1024:, :], as detailed below:

    • When the parameter differs for each batch, the shape is BNHSkv (H=1024).

    • When the parameter is the same for each batch, the shape is 1NHSkv (H=1024).

    • If this parameter is not used, nullptr can be passed in.

  • Constraints on sparseMode are as follows:

    • When all attenMaskOptional shapes are smaller than 2048 and identical, it is recommended to use the default mode to reduce memory usage.

    • When configured as 1, 2, or 3, the user-configured preTokens and nextTokens do not take effect.

    • When configured as 0 or 4, ensure that attenMaskOptional is consistent with the ranges of preTokens and nextTokens.

    • If not explicitly specified by the user, it is recommended to pass 0.

    • For detailed descriptions of different sparse modes, see sparse mode description.

  • In some scenarios, if the computation workload is too large, the operator execution may time out (aicore error, with errorStr: timeout or trap error). In this case, axis splitting is recommended. Note: The computation workload is affected by parameters such as B, S, N, and D. Larger values result in a larger computation workload.

  • prefixOptional sparse computation supports only the compression scenario, with sparseMode=6. When Sq > Skv, the N value of prefix ranges from [0, Skv]. When Sq <= Skv, the N value of prefix ranges from [Skv-Sq, Skv].

  • The actualSeqQLenOptional input supports an S length of 0 on a certain batch, in which case the optional input pseShiftOptional is not supported. The length of actualSeqQLenOptional ranges from 1 to 2K. When the prefixOptional input is present, its maximum length is 1K.

  • Constraints on the softmaxMax and softmaxSum parameters: The input format is fixed as [B, N, S, 8], except for the TND input format, which is [T, N, 8]. Note: T = B * S.

  • The value of headNum must be consistent with the N value in the input Query.

Example

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

#include <iostream>
#include <vector>
#include "acl/acl.h"
#include "aclnnop/aclnn_flash_attention_score_grad.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(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("mean result[%ld] is: %f\n", i, resultData[i]);
  }
}

int Init(int32_t deviceId, aclrtContext* context, aclrtStream* stream) {
  // Boilerplate for resource 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 = aclrtCreateContext(context, deviceId);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtCreateContext failed. ERROR: %d\n", ret); return ret);
  ret = aclrtSetCurrentContext(*context);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSetCurrentContext 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 memory on the device side.
  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 data from the host side to the 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;
}

int main() {
  // 1. (Boilerplate) Initialize the device/stream. See the ACL API manual.
  // Set deviceId based on the actual device.
  int32_t deviceId = 0;
  aclrtContext context;
  aclrtStream stream;
  auto ret = Init(deviceId, &context, &stream);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("Init acl failed. ERROR: %d\n", ret); return ret);

  // 2. Construct the inputs and outputs. Custom construction is required based on the API.
  std::vector<int64_t> qShape = {256, 1, 128};
  std::vector<int64_t> kShape = {256, 1, 128};
  std::vector<int64_t> vShape = {256, 1, 128};
  std::vector<int64_t> dxShape = {256, 1, 128};
  std::vector<int64_t> attenmaskShape = {256, 256};
  std::vector<int64_t> softmaxMaxShape = {256, 1, 8};
  std::vector<int64_t> softmaxSumShape = {256, 1, 8};
  std::vector<int64_t> attentionInShape = {256, 1, 128};

  std::vector<int64_t> dqShape = {256, 1, 128};
  std::vector<int64_t> dkShape = {256, 1, 128};
  std::vector<int64_t> dvShape = {256, 1, 128};

  void* qDeviceAddr = nullptr;
  void* kDeviceAddr = nullptr;
  void* vDeviceAddr = nullptr;
  void* dxDeviceAddr = nullptr;
  void* attenmaskDeviceAddr = nullptr;
  void* softmaxMaxDeviceAddr = nullptr;
  void* softmaxSumDeviceAddr = nullptr;
  void* attentionInDeviceAddr = nullptr;
  void* dqDeviceAddr = nullptr;
  void* dkDeviceAddr = nullptr;
  void* dvDeviceAddr = nullptr;

  aclTensor* q = nullptr;
  aclTensor* k = nullptr;
  aclTensor* v = nullptr;
  aclTensor* dx = nullptr;
  aclTensor* pse = nullptr;
  aclTensor* dropMask = nullptr;
  aclTensor* padding = nullptr;
  aclTensor* attenmask = nullptr;
  aclTensor* softmaxMax = nullptr;
  aclTensor* softmaxSum = nullptr;
  aclTensor* softmaxIn = nullptr;
  aclTensor* attentionIn = nullptr;
  aclTensor* dq = nullptr;
  aclTensor* dk = nullptr;
  aclTensor* dv = nullptr;
  aclTensor* dpse = nullptr;

  std::vector<float> qHostData(32768, 1);
  std::vector<float> kHostData(32768, 1);
  std::vector<float> vHostData(32768, 1);
  std::vector<float> dxHostData(32768, 1);
  std::vector<uint8_t> attenmaskHostData(65536, 0);
  std::vector<float> softmaxMaxHostData(2048, 3.0);
  std::vector<float> softmaxSumHostData(2048, 3.0);
  std::vector<float> attentionInHostData(32768, 1);
  std::vector<float> dqHostData(32768, 0);
  std::vector<float> dkHostData(32768, 0);
  std::vector<float> dvHostData(32768, 0);

  ret = CreateAclTensor(qHostData, qShape, &qDeviceAddr, aclDataType::ACL_FLOAT, &q);
  CHECK_RET(ret == ACL_SUCCESS, return ret);
  ret = CreateAclTensor(kHostData, kShape, &kDeviceAddr, aclDataType::ACL_FLOAT, &k);
  CHECK_RET(ret == ACL_SUCCESS, return ret);
  ret = CreateAclTensor(vHostData, vShape, &vDeviceAddr, aclDataType::ACL_FLOAT, &v);
  CHECK_RET(ret == ACL_SUCCESS, return ret);
  ret = CreateAclTensor(dxHostData, dxShape, &dxDeviceAddr, aclDataType::ACL_FLOAT, &dx);
  CHECK_RET(ret == ACL_SUCCESS, return ret);
  ret = CreateAclTensor(attenmaskHostData, attenmaskShape, &attenmaskDeviceAddr, aclDataType::ACL_UINT8, &attenmask);
  CHECK_RET(ret == ACL_SUCCESS, return ret);
  ret = CreateAclTensor(softmaxMaxHostData, softmaxMaxShape, &softmaxMaxDeviceAddr, aclDataType::ACL_FLOAT, &softmaxMax);
  CHECK_RET(ret == ACL_SUCCESS, return ret);
  ret = CreateAclTensor(softmaxSumHostData, softmaxSumShape, &softmaxSumDeviceAddr, aclDataType::ACL_FLOAT, &softmaxSum);
  CHECK_RET(ret == ACL_SUCCESS, return ret);
  ret = CreateAclTensor(attentionInHostData, attentionInShape, &attentionInDeviceAddr, aclDataType::ACL_FLOAT, &attentionIn);
  CHECK_RET(ret == ACL_SUCCESS, return ret);
  ret = CreateAclTensor(dqHostData, dqShape, &dqDeviceAddr, aclDataType::ACL_FLOAT, &dq);
  CHECK_RET(ret == ACL_SUCCESS, return ret);
  ret = CreateAclTensor(dkHostData, dkShape, &dkDeviceAddr, aclDataType::ACL_FLOAT, &dk);
  CHECK_RET(ret == ACL_SUCCESS, return ret);
  ret = CreateAclTensor(dvHostData, dvShape, &dvDeviceAddr, aclDataType::ACL_FLOAT, &dv);
  CHECK_RET(ret == ACL_SUCCESS, return ret);

  std::vector<int64_t> prefixOp = {0};
  aclIntArray* prefix = aclCreateIntArray(prefixOp.data(), 1);
  std::vector<int64_t>  acSeqQLenOp = {256};
  std::vector<int64_t>  acSeqKvLenOp = {256};
  aclIntArray* acSeqQLen = aclCreateIntArray(acSeqQLenOp.data(), acSeqQLenOp.size());
  aclIntArray* acSeqKvLen = aclCreateIntArray(acSeqKvLenOp.data(), acSeqKvLenOp.size());
  double scaleValue = 0.088388;
  double keepProb = 1;
  int64_t preTokens = 65536;
  int64_t nextTokens = 65536;
  int64_t headNum = 1;
  int64_t innerPrecise = 0;
  int64_t sparseMode = 0;

  char layOut[5] = {'T', 'N', 'D', 0};

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

  // Call the first-phase API of aclnnFlashAttentionUnpaddingScoreGrad.
  ret = aclnnFlashAttentionUnpaddingScoreGradGetWorkspaceSize(q, k, v, dx, pse, dropMask, padding,
              attenmask, softmaxMax, softmaxSum, softmaxIn, attentionIn, prefix, acSeqQLen, acSeqKvLen,
              scaleValue, keepProb, preTokens, nextTokens, headNum, layOut, innerPrecise, sparseMode,
              dq, dk, dv, dpse, &workspaceSize, &executor);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnFlashAttentionUnpaddingScoreGradGetWorkspaceSize 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 aclnnFlashAttentionUnpaddingScoreGrad.
  ret = aclnnFlashAttentionUnpaddingScoreGrad(workspaceAddr, workspaceSize, executor, stream);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnFlashAttentionUnpaddingScoreGrad failed. ERROR: %d\n", ret); return ret);

  // 4. (Boilerplate) Synchronize and 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 value and copy the result from the device memory to the host memory. Modify this based on the specific API definition.
  PrintOutResult(dqShape, &dqDeviceAddr);
  PrintOutResult(dkShape, &dkDeviceAddr);
  PrintOutResult(dvShape, &dvDeviceAddr);

  // 6. Release aclTensor and aclScalar. Modify based on the specific API definition.
  aclDestroyTensor(q);
  aclDestroyTensor(k);
  aclDestroyTensor(v);
  aclDestroyTensor(dx);
  aclDestroyTensor(attenmask);
  aclDestroyTensor(softmaxMax);
  aclDestroyTensor(softmaxSum);
  aclDestroyTensor(attentionIn);
  aclDestroyTensor(dq);
  aclDestroyTensor(dk);
  aclDestroyTensor(dv);

  // 7. Release device resources.
  aclrtFree(qDeviceAddr);
  aclrtFree(kDeviceAddr);
  aclrtFree(vDeviceAddr);
  aclrtFree(dxDeviceAddr);
  aclrtFree(attenmaskDeviceAddr);
  aclrtFree(softmaxMaxDeviceAddr);
  aclrtFree(softmaxSumDeviceAddr);
  aclrtFree(attentionInDeviceAddr);
  aclrtFree(dqDeviceAddr);
  aclrtFree(dkDeviceAddr);
  aclrtFree(dvDeviceAddr);
  if (workspaceSize > 0) {
    aclrtFree(workspaceAddr);
  }
  aclrtDestroyStream(stream);
  aclrtDestroyContext(context);
  aclrtResetDevice(deviceId);
  aclFinalize();

  return 0;
}