aclnnFlashAttentionScoreGradV3

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

Computes the backward output of attention in training scenarios, i.e., the backward computation of aclnnFlashAttentionScoreV3. Compared with the aclnnFlashAttentionScoreGradV2 API, this API adds the sinkInOptional parameter and dsinkOut output:

  • Ascend 950PR/Ascend 950DT: The sinkInOptional parameter and dsinkOut output are not supported.

  • When pseType=1, the implementation is the same as aclnnFlashAttentionScoreGrad.

  • When pseType is set to other values, a mul operation is performed first, followed by an add operation.

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

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

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

The backward propagation formula for attention is:

V=PTdYV=P^TdY Q=((dS)∗K)dQ=\frac{((dS)*K)}{\sqrt{d}} K=((dS)T∗Q)dK=\frac{((dS)^T*Q)}{\sqrt{d}}

The computation logic after adding sink is as follows, mainly modifying the relevant softmax_max and softmax_sum logic computation parts.

S=Q@KTS = Q @ K^{T} m=max(sink,max(S))m = max(sink, max(S)) Attention=eS−m@V∑eS−m+esink−mAttention = \frac{e^{S - m} @ V}{\sum e^{S-m} + e^{sink - m}} dSink=reduce(−P∗dP∗SimpleSoftmax(sink,x_max,x_sum))dSink = reduce(-P * dP * SimpleSoftmax(sink, x\_max, x\_sum))

Function Prototype

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

aclnnStatus aclnnFlashAttentionScoreGradV3GetWorkspaceSize(
  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 aclTensor   *sinkInOptional,
  const aclIntArray *prefixOptional,
  const aclIntArray *qStartIdxOptional,
  const aclIntArray *kvStartIdxOptional,
  double             scaleValue,
  double             keepProb,
  int64_t            preTokens,
  int64_t            nextTokens,
  int64_t            headNum,
  char              *inputLayout,
  int64_t            innerPrecise,
  int64_t            sparseMode,
  int64_t            pseType,
  const aclTensor   *dqOut,
  const aclTensor   *dkOut,
  const aclTensor   *dvOut,
  const aclTensor   *dpseOut,
  const aclTensor   *dsinkOut,
  uint64_t          *workspaceSize,
  aclOpExecutor    **executor)
aclnnStatus aclnnFlashAttentionScoreGradV3(
  void             *workspace,
  uint64_t          workspaceSize,
  aclOpExecutor    *executor,
  const aclrtStream stream)

aclnnFlashAttentionScoreGradV3GetWorkspaceSize

  • 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 ND [BNSD], [BSND], [BSH], [SBH] √
    keyIn Input K in the formula. The data type is consistent with query/value. FLOAT16, BFLOAT16 ND [BNSD], [BSND], [BSH], [SBH] √
    value Input In the Formula, V. The data type is consistent with query/keyIn. FLOAT16, BFLOAT16 ND [BNSD], [BSND], [BSH], [SBH] √
    dy Input dY in the formula. - FLOAT16, BFLOAT16 ND [BNSD], [BSND], [BSH], [SBH] √
    pseShiftOptional Optional Input pse in the formula. The data type is consistent with that of query. This parameter must be used together with pseType. FLOAT16, BFLOAT16 ND [B,N,Sq,Skv], [B,N,1,Skv], [1,N,Sq,Skv], [B,N,1024,Skv], [1,N,1024,Skv], [B,N], [N] √
    dropMaskOptional Optional Input Dropout in the formula. - UINT8 ND 0, 1 √
    paddingMaskOptional Optional Input Reserved parameter. Reserved. - - - - -
    qStartIdxOptional Optional Input Represents the external slicing scenario, indicating the starting index of the current chunk's query sequence in the global context. - INT64 ND 0, 1 -
    kvStartIdxOptional Optional Input Indicates the outer scenario, representing the start index of the current block's key and value sequence in the global context. - INT64 ND 0, 1 -
    attenMaskOptional Optional Input In the Formula, atten_mask. A value of 1 indicates that the position does not participate in the computation, and 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 Attention Forward Computation. - FLOAT ND [B,N,Sq,8] ✓
    softmaxSumOptional Optional Input Intermediate output of the attention forward computation. - FLOAT ND [B,N,Sq,8] √
    softmaxInOptional Optional Input Intermediate output of the attention forward computation. Reserved parameter. Not used. - - - -
    attentionInOptional Optional Input Final output of the attention forward computation. The data type and shape are consistent with query. FLOAT16, BFLOAT16 ND [BNSD], [BSND], [BSH], [SBH] √
    sinkInOptional Optional Input sink in the formula. Length is headNum. FLOAT32 ND [headNum] √
    prefixOptional Optional Input N of each batch in the prefix sparse scenario. - INT64 ND 0, 1 -
    scaleValue Optional Input Scale scaling factor in the formula. - DOUBLE - - -
    keepProb Optional Input Proportion of 1s in dropMask. - DOUBLE - - -
    preTokens Optional Input Left boundary of the sparse computation window. - INT64 - - -
    nextTokens Optional Input Right boundary of the sparse computation window. - INT64 - - -
    headNum Input Number of heads on a single card, corresponding to the N axis of query. - INT64 - - -
    inputLayout Input Data layout format of query, key, and value. Supports BSH, SBH, BSND, and BNSD. String - - -
    innerPrecise Optional Input Controls the internal computation precision. Reserved parameter, currently unused. INT64 - - -
    sparseMode Optional Input Sparse mode. Supports configuration values from 0 to 6. INT64 - - -
    pseType Optional Input pse type. Supports configuration values 0 to 3. INT64 - - -
    dqOut Output dQ in the formula. Gradient of query. - FLOAT16、BFLOAT16 ND [BNSD], [BSND], [BSH], [SBH] √
    dkOut Output In the Formula, dK, the gradient of keyIn. - FLOAT16, BFLOAT16 ND [BNSD], [BSND], [BSH], [SBH] √
    dvOut Output In the Formula, dV, the gradient of value. - FLOAT16, BFLOAT16 ND [BNSD], [BSND], [BSH], [SBH] √
    dpseOut Output d(pse) Gradient. Reserved parameter, not in use. - - - -
    dsinkOut Output dSink in the formula, the gradient of d(sinkInOptional). - FLOAT32 ND [headNum] √
    workspaceSize Output Returns the workspace size to be applied for on the Device side. - - - - -
    executor Output Returns the operator executor, which contains the computation flow. - - - - -
  • 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 A mandatory input, output, or mandatory attribute 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, sinkInOptional, dqOut, dkOut, dvOut, or dsinkOut is not within the supported range.
    The data format of query, keyIn, value, dy, pseShiftOptional, dropMaskOptional, paddingMaskOptional, attenMaskOptional, softmaxMaxOptional, softmaxSumOptional, softmaxInOptional, attentionInOptional, sinkInOptional, dqOut, dkOut, dvOut, or dsinkOut is not within the supported range.

aclnnFlashAttentionScoreGradV3

  • 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 aclnnFlashAttentionScoreGradV3GetWorkspaceSize.
    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:

  • aclnnFlashAttentionScoreGradV3 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 inputs query, key, value, and dy must be equal.

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

  • The inputLayout of input query, key, value, and dy must be consistent.

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

  • The N of input query/dy and the N of key/value may differ, but they must be in a proportional relationship, i.e., Nq/Nkv must be a non-zero integer, with Nq in the value range of 1 to 256.

  • For constraints on data shapes, take inputLayout BSND and BNSD as examples (BSH and SBH where H=N*D):

    • B: Value range: 1 to 2M. When prefixOptional is present, B supports a maximum of 2K.

    • N: Value range: 1 to 256.

    • S: Value range: 1 to 1M.

    • D: Value range: 1 to 768.

    • KeepProb: Value range: (0, 1].

  • The data layout formats of query, key, and value support interpretation from multiple dimensions, where B (Batch) indicates the batch size of input samples, S (Seq-Length) indicates the sequence length of input samples, H (Head-Size) indicates the hidden layer size, N (Head-Num) indicates the number of heads, D (Head-Dim) indicates the minimum unit size of the hidden layer, and D = H/N is satisfied.

  • pseShiftOptional: If Sq is greater than 1024, Sq and Skv of each batch have the same length, and the scenario is a lower triangular mask with sparseMode of 0, 2, or 3, you can enable alibi position encoding compression. In this case, only the last 1024 rows of the original PSE need to be input, achieving memory optimization, i.e., 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 pseType is 2 or 3, the data type must be FLOAT32, and the corresponding supported shape range is [B, N] or [N].

    • If this parameter is not enabled, nullptr must be passed for pseShiftOptional, and 1 must be passed for pseType.

  • innerPrecise: Currently, 0 and 1 are reserved configuration values, and 2 enables invalid row computation. This function avoids precision loss caused by an entire row mask during computation, but this configuration leads to performance degradation.

    If the operator can determine that an invalid row scenario exists, it automatically enables invalid row computation, for example, when sparseMode is 3 and Sq > Skv.

  • Meaning of each pseType value

    pseTypeMeaningNote
    0Externally passed pse, first mul then add.-
    1Externally passed pse, first add then mul.Consistent with the implementation of FlashAttentionScoreGrad.
    2Internally generated pse, first mul then add.-
    3Internally generated pse, first mul then add then sqrt.-
  • The constraints on sparseMode are as follows:

    • When the shape of all attenMaskOptional is less than 2048 and identical, it is recommended to use the default mode to reduce memory usage.

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

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

    • If the user does not specify a value, it is recommended to pass 0.

    • For details about 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 heavier computation workload.

  • 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.

  • In the band scenario, preTokens and nextTokens must have an intersection.

  • In the prefixOptional sparse computation scenario, i.e., sparseMode=5 or sparseMode=6, when Sq > Skv, the value range of N for prefix is [0, Skv]; when Sq <=

    Skv, the value range of N for prefix is [Skv-Sq, Skv].

  • When Sq in pseShiftOptional is greater than 1024 and the shape is BNHS or 1NHS, Sq and Skv must be of equal length.

  • The dimension of sinkInOptional is 1, and its length must be the same as headNum of 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, aclrtStream* stream) {
  // Boilerplate: 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 = 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);
  // Call aclrtMemcpy to copy host-side data to device-side 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);

  // Compute 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 acl API manual.
  // 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 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 = {1, 1, 256, 8};
  std::vector<int64_t> softmaxSumShape = {1, 1, 256, 8};
  std::vector<int64_t> attentionInShape = {256, 1, 128};
  std::vector<int64_t> sinkInOptionalShape = {1};
  std::vector<int64_t> dqShape = {256, 1, 128};
  std::vector<int64_t> dkShape = {256, 1, 128};
  std::vector<int64_t> dvShape = {256, 1, 128};
  std::vector<int64_t> dsinkShape = {1};

  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* sinkInOptionalDeviceAddr = nullptr;

  void* dqDeviceAddr = nullptr;
  void* dkDeviceAddr = nullptr;
  void* dvDeviceAddr = nullptr;
  void* dsinkDeviceAddr = 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* sinkInOptional = nullptr;
  aclTensor* dq = nullptr;
  aclTensor* dk = nullptr;
  aclTensor* dv = nullptr;
  aclTensor* dpse = nullptr;
  aclTensor* dsink = 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> sinkInOptionalHostData(1, 0);
  std::vector<float> dqHostData(32768, 0);
  std::vector<float> dkHostData(32768, 0);
  std::vector<float> dvHostData(32768, 0);
  std::vector<float> dsinkHostData(1, 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(sinkInOptionalHostData, sinkInOptionalShape, &sinkInOptionalDeviceAddr, aclDataType::ACL_FLOAT, &sinkInOptional);
  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);
  ret = CreateAclTensor(dsinkHostData, dsinkShape, &dsinkDeviceAddr, aclDataType::ACL_FLOAT, &dsink);
  CHECK_RET(ret == ACL_SUCCESS, return ret);

  std::vector<int64_t> prefixOp = {0};
  aclIntArray *prefix = aclCreateIntArray(prefixOp.data(), 1);
  std::vector<int64_t> qStartIdxOp = {0};
  std::vector<int64_t> kvStartIdxOp = {0};
  aclIntArray *qStartIdx = aclCreateIntArray(qStartIdxOp.data(), 1);
  aclIntArray *kvStartIdx = aclCreateIntArray(kvStartIdxOp.data(), 1);
  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;
  int64_t pseType = 1;
  char layOut[5] = {'S', 'B', 'H', 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 aclnnFlashAttentionScoreGradV3.
  ret = aclnnFlashAttentionScoreGradV3GetWorkspaceSize(q, k, v, dx, pse, dropMask, padding,
            attenmask, softmaxMax, softmaxSum, softmaxIn, attentionIn, sinkInOptional, prefix, qStartIdx, kvStartIdx,
            scaleValue, keepProb, preTokens, nextTokens, headNum, layOut, innerPrecise, sparseMode, pseType,
            dq, dk, dv, dpse, dsink, &workspaceSize, &executor);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnFlashAttentionScoreGradV3GetWorkspaceSize 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 aclnnFlashAttentionScoreGradV3.
  ret = aclnnFlashAttentionScoreGradV3(workspaceAddr, workspaceSize, executor, stream);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnFlashAttentionScoreGradV3 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);
  PrintOutResult(dsinkShape, &dsinkDeviceAddr);

  // 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(sinkInOptional);
  aclDestroyTensor(dq);
  aclDestroyTensor(dk);
  aclDestroyTensor(dv);
  aclDestroyTensor(dsink);
  // 7. Release device resources.
  aclrtFree(qDeviceAddr);
  aclrtFree(kDeviceAddr);
  aclrtFree(vDeviceAddr);
  aclrtFree(dxDeviceAddr);
  aclrtFree(attenmaskDeviceAddr);
  aclrtFree(softmaxMaxDeviceAddr);
  aclrtFree(softmaxSumDeviceAddr);
  aclrtFree(attentionInDeviceAddr);
  aclrtFree(sinkInOptionalDeviceAddr);
  aclrtFree(dqDeviceAddr);
  aclrtFree(dkDeviceAddr);
  aclrtFree(dvDeviceAddr);
  aclrtFree(dsinkDeviceAddr);
  if (workspaceSize > 0) {
    aclrtFree(workspaceAddr);
  }
  aclrtDestroyStream(stream);
  aclrtResetDevice(deviceId);
  aclFinalize();

  return 0;
}