aclnnSparseFlashAttention

📄 View Source Code

Applicable Products

ProductSupported
Atlas A2 inference products√
Atlas A3 inference products√

Function

  • Description: sparse_flash_attention (SFA) is an efficient attention computation module designed for large sequence length inference scenarios. This module significantly reduces the computation workload by "computing only the critical parts." However, it introduces a large amount of discrete memory access, which increases data transfer time and thereby affects overall performance.

  • Formula:

    softmax(Q@K~Tdk)@V~\text{softmax}(\frac{Q@\tilde{K}^T}{\sqrt{d_k}})@\tilde{V}

Where K~,V~\tilde{K},\tilde{V} are the Key and Value of higher importance obtained based on a certain selection algorithm (such as lightning_indexer), generally having sparse or block-sparse characteristics, and dkd_k is the dimension of each head of Q,K~Q,\tilde{K}.

Function Prototype

Each operator is divided into a two-phase API. The "aclnnSparseFlashAttentionGetWorkspaceSize" API must be called first to obtain the workspace size required for computation and the executor that contains the operator computation process, and then the "aclnnSparseFlashAttention" API is called to perform the computation.

aclnnStatus aclnnSparseFlashAttentionGetWorkspaceSize(
    const aclTensor     *query,
    const aclTensor     *key,
    const aclTensor     *value,
    const aclTensor     *sparseIndices,
    const aclTensor     *blockTable,
    const aclTensor     *actualSeqLengthsQuery,
    const aclTensor     *actualSeqLengthsKv,
    const aclTensor     *queryRope,
    const aclTensor     *keyRope,
    double              scaleValue,
    int64_t             sparseBlockSize,
    char                *layoutQuery,
    char                *layoutKv,
    int64_t             sparseMode,
    int64_t             preTokens,
    int64_t             nextTokens,
    int64_t             attentionMode,
    bool                returnSoftmaxLse,
    const aclTensor     *attentionOutOut,
    const aclTensor     *softmaxMaxOut,
    const aclTensor     *softmaxSumOut,
    uint64_t            *workspaceSize,
    aclOpExecutor       **executor)
aclnnStatus aclnnSparseFlashAttention(
    void             *workspace,
    uint64_t          workspaceSize,
    aclOpExecutor    *executor,
    const aclrtStream stream)

aclnnSparseFlashAttentionGetWorkspaceSize

  • Parameters:
  • The dimension meanings of query, key, and value parameters: B (Batch Size) indicates the batch size of input samples, S (Sequence 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, D (Head Dim) indicates the minimum unit size of the hidden layer, and D = H/N is satisfied. T indicates the cumulative sum of sequence lengths of all batch input samples.
  • Q_S and S1 indicate S in the query shape, KV_S and S2 indicate S in the key shape, Q_N and N1 indicate num_query_heads, KV_N and N2 indicate num_key_value_heads, T1 indicates T in the query shape, and T2 indicates the cumulative sum of sequence lengths of input samples in the key shape.
Parameter Input/Output Description Instruction Data Type Data Format Dimension (Shape) Non-contiguous Tensor
query (aclTensor) Input The Query input of the attention structure. Empty tensors are not supported. FLOAT16, BFLOAT16 ND
  • When layout_query is BSND, the shape is (B, S1, N1, D).
  • When layout_query is TND, the shape is (T1, N1, D).
x
key (aclTensor) Input Key input of the attention structure.
  • Empty tensors are not supported.
  • block_num indicates the total number of blocks when PageAttention is used.
FLOAT16, BFLOAT16 ND
  • When layout_kv is PA_BSND, the shape is (block_num, block_size, KV_N, D).
  • When layout_kv is BSND, the shape is (B, S2, KV_N, D).
  • When layout_kv is TND, the shape is (T2, KV_N, D).
x
value (aclTensor) Input Value input of the attention structure. Empty tensors are not supported. FLOAT16, BFLOAT16 ND
  • The shape is the same as the shape of key.
x
sparseIndices (aclTensor) Input Index for discrete retrieval of kvCache.
  • Empty tensors are not supported.
  • sparse_size indicates the number of blocks selected in one discrete retrieval. Ensure that valid values in each row are in the first half and invalid values are in the second half. sparse_size must be greater than 0.
INT32 ND
  • When layout_query is BSND, the shape is (B, Q_S, KV_N, sparse_size).
  • When layout_query is TND, the shape is (Q_T, KV_N, sparse_size).
x
blockTable (aclTensor) Input Indicates the block mapping table used for kvCache storage in PageAttention.
  • Empty tensors are not supported.
  • The length of the second dimension is not less than the number of blocks corresponding to the largest S2 among all batches, i.e., S2_max / block_size rounded up.
INT32 ND The shape supports (B, S2/block_size). x
actualSeqLengthsQuery (aclTensor) Input Indicates the number of valid tokens of the query in different batches.
  • Empty tensors are not supported.
  • If seqlen is not specified, None can be passed, indicating that the length is the same as the S dimension of the query shape.
  • The number of valid tokens for each batch in this input parameter does not exceed the size of the S dimension in the query and is not less than 0. A one-dimensional tensor of length B is supported.
  • When layout_query is TND, this input parameter must be passed, and the number of elements in this input parameter is used as the B value. The value of each element in this parameter indicates the total number of tokens in the current batch and all previous batches.
INT32 ND (B,) x
actualSeqLengthsKv (aclTensor) Input Indicates the number of valid tokens in key and value for different batches.
  • Empty tensors are not supported.
  • If seqlen is not specified, None can be passed in, indicating that the length is the same as the S dimension of key's shape.
  • The number of valid tokens for each batch in this parameter must not exceed the size of dimension S in key/value and must not be less than 0. A one-dimensional tensor of length B is supported.
  • When layout_kv is TND or PA_BSND, this input parameter must be passed in.
  • When layout_kv is TND, the value of each element in this parameter represents the total number of tokens in the current batch and all previous batches, i.e., the prefix sum. Therefore, the value of a subsequent element must be greater than or equal to the value of the preceding element.
INT32 ND (B,) x
queryRope (aclTensor) Input Indicates the rope information of the query in the MLA structure. An empty tensor is not supported. FLOAT16, BFLOAT16 ND
  • When layout_query is TND, the shape is (B, S1, N1, Dr).
  • When layout_query is BSND, the shape is (T1, N1, Dr).
x
keyRope (aclTensor) Input Indicates the RoPE information of the key in the MLA structure. Empty tensors are not supported. FLOAT16, BFLOAT16 ND
  • When layout_kv is TND, the shape is (B, S1, N1, Dr).
  • When layout_kv is BSND, the shape is (T1, N1, Dr).
  • When layout_kv is PA_BSND, the shape is (block_num, block_size, N2, Dr).
x
scaleValue (double) Input Represents the scaling factor. - FLOAT16 - - -
sparseBlockSize (int64_t) Input Indicates the block size in the sparse stage.
  • When sparse_block_size is 1, it is a token-wise sparsification scenario, where each token is treated as an independent unit. When computing importance scores, the independent correlation between each query token and each key-value token is evaluated.
  • When sparse_block_size is greater than 1 and less than or equal to 128, it is a block-wise sparsification scenario, where the token sequence is divided into fixed-size contiguous blocks. Importance evaluation is performed at the block level, and tokens within the same block share the same sparsification decision.
INT64 - - -
layoutQuery (char) Input Indicates the data layout format of the input query.
  • Defaults to "BSND" when not explicitly specified by the user.
  • Supports BSND and TND.
STRING - - -
layoutKv (char) Input Indicates the data layout format of the input key.
  • If not explicitly specified by the user, the default value "BSND" can be passed in.
  • Supports TND, BSND, and PA_BSND, where PA_BSND is used when PageAttention is enabled.
STRING - - -
sparseMode (int64_t) Input Indicates the sparse mode.
  • When sparse_mode is 0, it indicates full computation.
  • When sparse_mode is 3, it indicates the rightDownCausal mode mask, corresponding to the lower-triangular scenario with the dividing line from the bottom-right vertex to the top-left.
INT64 - - -
preTokens (int64_t) Input Used for sparse computation. Indicates the number of previous tokens that the attention needs to compute correlation with. Only the default value 2^63-1 is supported. INT64 - - -
nextTokens (int64_t) Input Used for sparse computation. Indicates the number of subsequent tokens that the attention needs to compute association with. Only the default value 2^63-1 is supported. INT64 - - -
attentionMode (int64_t) Input - Only 2 is supported, which indicates the MLA-absorb mode. INT64 - - -
returnSoftmaxLse (bool) Input Indicates whether to return softmax_max and softmax_sum.
  • True indicates that they are returned, but this is not supported in graph mode. False indicates that they are not returned. The default value is False.
  • This parameter is supported only in training scenarios where layout_kv is not PA_BSND.
BOOL - - -
attentionOut (aclTensor) Output Output in the formula. Empty tensors are not supported. FLOAT16, BFLOAT16 ND
  • When layout_query is BSND, the shape is (B, S1, N1, D).
  • When layout_query is TND, the shape is (T1, N1, D).
x
softmaxMaxOut (aclTensor) Output The softmax_max obtained by taking the max of the result of query multiplied by key in the attention algorithm. Empty tensors are not supported. FLOAT ND
  • When layout_query is BSND, the shape is (B, N2, S1, N1/N2).
  • When layout_query is TND, the shape is (N2, T1, N1/N2).
x
softmaxSumOut (aclTensor) Output The result of query multiplied by key in the attention algorithm, minus softmax_max, then exponentiated, and finally summed to obtain softmax_sum. Empty tensors are not supported. FLOAT ND
  • When layout_query is BSND, the shape is (B, N2, S1, N1/N2).
  • When layout_query is TND, the shape is (N2, T1, N1/N2).
x
workspaceSize (uint64_t*) Output Returns the workspace size to be applied for on the Device side. - - - - -
executor (aclOpExecutor) Output Returns the op executor, which contains the operator 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 Return Code Description
    ACLNN_ERR_PARAM_NULLPTR 161001 If the input parameter is a mandatory input, output, or mandatory attribute and is a null pointer, 161001 is returned.
    ACLNN_ERR_PARAM_INVALID 161002 The data type or data format of query, key, value, sparseIndices, blockTable, actualSeqLengthsQuery, actualSeqLengthsKv, queryRope, keyRope, scaleValue, sparseBlockSize, layoutQuery, layoutKv, sparseMode, attentionMode, returnSoftmaxLse, attentionOut, softmaxMaxOut, or softmaxSumOut is not within the supported range.

aclnnSparseFlashAttention

  • Parameters:

    Parameter Input/Output Description
    workspace Input Workspace memory address applied for on the Device side.
    workspaceSize Input Workspace size applied for on the Device side, obtained from the first-phase API aclnnSparseFlashAttentionGetWorkspaceSize.
    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

  • This API supports use in inference scenarios.

  • This API supports graph mode.

  • N1 supports 1/2/4/8/16/32/64/128.

  • block_size is the number of tokens in a block. The value of block_size must be a multiple of 16, with a maximum of 1024.

  • The D in the query parameter and the D in key and value must be equal to 512, and the Dr in the query_rope parameter and the Dr in key_rope must be equal to 64.

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

  • sparse_block_size must be divisible by block_size.

Example

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

/**
 * Copyright (c) 2024 Huawei Technologies Co., Ltd.
 * This file is a part of the CANN Open Software.
 * Licensed under CANN Open Software License Agreement Version 1.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 test_incre_flash_attention_v4.cpp
 * \brief
 */

#include <iostream>
#include <vector>
#include <cmath>
#include <cstring>
#include "securec.h"
#include "acl/acl.h"
#include "aclnnop/aclnn_sparse_flash_attention.h"

using namespace std;

namespace {

#define CHECK_RET(cond) ((cond) ? true :(false))

#define LOG_PRINT(message, ...)     \
  do {                              \
    (void)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) {
  auto ret = aclInit(nullptr);
  if (!CHECK_RET(ret == ACL_SUCCESS)) {
    LOG_PRINT("aclInit failed. ERROR: %d\n", ret);
    return ret;
  }
  ret = aclrtSetDevice(deviceId);
  if (!CHECK_RET(ret == ACL_SUCCESS)) {
    LOG_PRINT("aclrtSetDevice failed. ERROR: %d\n", ret);
    return ret;
  }
  ret = aclrtCreateStream(stream);
  if (!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);
  auto ret = aclrtMalloc(deviceAddr, size, ACL_MEM_MALLOC_HUGE_FIRST);
  if (!CHECK_RET(ret == ACL_SUCCESS)) {
    LOG_PRINT("aclrtMalloc failed. ERROR: %d\n", ret);
    return ret;
  }

  ret = aclrtMemcpy(*deviceAddr, size, hostData.data(), size, ACL_MEMCPY_HOST_TO_DEVICE);
  if (!CHECK_RET(ret == ACL_SUCCESS)) {
    LOG_PRINT("aclrtMemcpy failed. ERROR: %d\n", ret);
    return ret;
  }

  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];
  }

  *tensor = aclCreateTensor(shape.data(), shape.size(), dataType, strides.data(), 0, aclFormat::ACL_FORMAT_ND,
                            shape.data(), shape.size(), *deviceAddr);
  return 0;
}

struct TensorResources {
    void* queryDeviceAddr = nullptr;
    void* keyDeviceAddr = nullptr;
    void* valueDeviceAddr = nullptr;
    void* sparseIndicesDeviceAddr = nullptr;
    void* attentionOutDeviceAddr = nullptr;
    void* softmaxMaxDeviceAddr = nullptr;
    void* softmaxSumDeviceAddr = nullptr;
    void* queryRopeDeviceAddr = nullptr;
    void* keyRopeDeviceAddr = nullptr;

    aclTensor* queryTensor = nullptr;
    aclTensor* keyTensor = nullptr;
    aclTensor* valueTensor = nullptr;
    aclTensor* sparseIndicesTensor = nullptr;
    aclTensor* attentionOutTensor = nullptr;
    aclTensor* softmaxMaxTensor = nullptr;
    aclTensor* softmaxSumTensor = nullptr;
    aclTensor* queryRopeTensor = nullptr;
    aclTensor* keyRopeTensor = nullptr;
};

int InitializeTensors(TensorResources& resources) {
    std::vector<int64_t> queryShape = {1, 2, 1, 512};
    std::vector<int64_t> keyShape = {1, 2, 1, 512};
    std::vector<int64_t> valueShape = {1, 2, 1, 512};
    std::vector<int64_t> sparseIndicesShape = {1, 2, 1, 2};
    std::vector<int64_t> attentionOutShape = {1, 2, 1, 512};
    std::vector<int64_t> softmaxMaxShape = {1, 2, 1, 16};
    std::vector<int64_t> softmaxSumShape = {1, 2, 1, 16};
    std::vector<int64_t> queryRopeShape = {1, 2, 1, 64};
    std::vector<int64_t> keyRopeShape = {1, 2, 1, 64};

    int64_t queryShapeSize = GetShapeSize(queryShape);
    int64_t keyShapeSize = GetShapeSize(keyShape);
    int64_t valueShapeSize = GetShapeSize(valueShape);
    int64_t sparseIndicesShapeSize =  GetShapeSize(sparseIndicesShape);
    int64_t attentionOutShapeSize = GetShapeSize(attentionOutShape);
    int64_t softmaxMaxShapeSize = GetShapeSize(softmaxMaxShape);
    int64_t softmaxSumShapeSize = GetShapeSize(softmaxSumShape);
    int64_t queryRopeShapeSize = GetShapeSize(queryRopeShape);
    int64_t keyRopeShapeSize = GetShapeSize(keyRopeShape);

    std::vector<float> queryHostData(queryShapeSize, 1);
    std::vector<float> keyHostData(keyShapeSize, 1);
    std::vector<float> valueHostData(valueShapeSize, 1);
    std::vector<int32_t> sparseIndicesHostData(sparseIndicesShapeSize, 1);
    std::vector<float> attentionOutHostData(attentionOutShapeSize, 1);
    std::vector<float> softmaxMaxHostData(softmaxMaxShapeSize, 1);
    std::vector<float> softmaxSumHostData(softmaxSumShapeSize, 1);
    std::vector<float> queryRopeHostData(queryRopeShapeSize, 1);
    std::vector<float> keyRopeHostData(keyRopeShapeSize, 1);

    // Create query aclTensor.
    int ret = CreateAclTensor(queryHostData, queryShape, &resources.queryDeviceAddr,
                             aclDataType::ACL_FLOAT16, &resources.queryTensor);
    if (!CHECK_RET(ret == ACL_SUCCESS)) {
      return ret;
    }

    // Create key aclTensor.
    ret = CreateAclTensor(keyHostData, keyShape, &resources.keyDeviceAddr,
                         aclDataType::ACL_FLOAT16, &resources.keyTensor);
    if (!CHECK_RET(ret == ACL_SUCCESS)) {
      return ret;
    }

    // Create value aclTensor.
    ret = CreateAclTensor(valueHostData, valueShape, &resources.valueDeviceAddr,
                         aclDataType::ACL_FLOAT16, &resources.valueTensor);
    if (!CHECK_RET(ret == ACL_SUCCESS)) {
      return ret;
    }

    // Create sparseIndices aclTensor.
    ret = CreateAclTensor(sparseIndicesHostData, sparseIndicesShape, &resources.sparseIndicesDeviceAddr,
                         aclDataType::ACL_INT32, &resources.sparseIndicesTensor);
    if (!CHECK_RET(ret == ACL_SUCCESS)) {
      return ret;
    }

    // Create queryRope aclTensor.
    ret = CreateAclTensor(queryRopeHostData, queryRopeShape, &resources.queryRopeDeviceAddr,
                         aclDataType::ACL_FLOAT16, &resources.queryRopeTensor);
    if (!CHECK_RET(ret == ACL_SUCCESS)) {
      return ret;
    }

    // Create keyRope aclTensor.
    ret = CreateAclTensor(keyRopeHostData, keyRopeShape, &resources.keyRopeDeviceAddr,
                         aclDataType::ACL_FLOAT16, &resources.keyRopeTensor);
    if (!CHECK_RET(ret == ACL_SUCCESS)) {
      return ret;
    }

    // Create attention_out aclTensor.
    ret = CreateAclTensor(attentionOutHostData, attentionOutShape, &resources.attentionOutDeviceAddr,
                         aclDataType::ACL_FLOAT16, &resources.attentionOutTensor);
    if (!CHECK_RET(ret == ACL_SUCCESS)) {
      return ret;
    }

    // Create softmax_max aclTensor.
    ret = CreateAclTensor(softmaxMaxHostData, softmaxMaxShape, &resources.softmaxMaxDeviceAddr,
                         aclDataType::ACL_FLOAT, &resources.softmaxMaxTensor);
    if (!CHECK_RET(ret == ACL_SUCCESS)) {
      return ret;
    }

    // Create softmax_sum aclTensor.
    ret = CreateAclTensor(softmaxSumHostData, softmaxSumShape, &resources.softmaxSumDeviceAddr,
                         aclDataType::ACL_FLOAT, &resources.softmaxSumTensor);
    if (!CHECK_RET(ret == ACL_SUCCESS)) {
      return ret;
    }

    return ACL_SUCCESS;
}

int ExecuteSparseFlashAttention(TensorResources& resources, aclrtStream stream,
                              void** workspaceAddr, uint64_t* workspaceSize) {
    int64_t d = 2;
    double scaleValue = 1 / sqrt(d);
    int64_t sparseBlockSize = 64;
    constexpr const char layerOutStr[] = "BSND";
    constexpr size_t layerOutLen = sizeof(layerOutStr);
    char layoutQuery[layerOutLen];
    char layoutKv[layerOutLen];
    errno_t memcpyRet = memcpy_s(layoutQuery, sizeof(layoutQuery), layerOutStr, layerOutLen);
    if (memcpyRet != 0) {
        LOG_PRINT("memcpy_s layoutQuery failed. ERROR: %d\n", memcpyRet);
        return -1;
    }
    memcpyRet = memcpy_s(layoutKv, sizeof(layoutKv), layerOutStr, layerOutLen);
    if (memcpyRet != 0) {
        LOG_PRINT("memcpy_s layoutKv failed. ERROR: %d\n", memcpyRet);
        return -1;
    }
    int64_t sparseMode = 3;
    int64_t preTokens = 9223372036854775807;
    int64_t nextTokens = 9223372036854775807;
    int64_t attentionMode = 2;
    bool returnSoftmaxLse = false;
    aclOpExecutor* executor;

    int ret = aclnnSparseFlashAttentionGetWorkspaceSize(resources.queryTensor, resources.keyTensor, resources.valueTensor, resources.sparseIndicesTensor, nullptr, nullptr, nullptr, resources.queryRopeTensor, resources.keyRopeTensor,
                                                    scaleValue, sparseBlockSize, layoutQuery, layoutKv, sparseMode, preTokens,
                                                    nextTokens, attentionMode, returnSoftmaxLse, resources.attentionOutTensor, resources.softmaxMaxTensor, resources.softmaxSumTensor, workspaceSize, &executor);
    if (!CHECK_RET(ret == ACL_SUCCESS)) {
        LOG_PRINT("aclnnSparseFlashAttentionGetWorkspaceSize failed. ERROR: %d\n", ret);
        return ret;
    }

    if (*workspaceSize > 0ULL) {
        ret = aclrtMalloc(workspaceAddr, *workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST);
        if (!CHECK_RET(ret == ACL_SUCCESS)) {
            LOG_PRINT("allocate workspace failed. ERROR: %d\n", ret);
            return ret;
        }
    }

    ret = aclnnSparseFlashAttention(*workspaceAddr, *workspaceSize, executor, stream);
    if (!CHECK_RET(ret == ACL_SUCCESS)) {
        LOG_PRINT("aclnnSparseFlashAttention failed. ERROR: %d\n", ret);
        return ret;
    }

    return ACL_SUCCESS;
}

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

void CleanupResources(TensorResources& resources, void* workspaceAddr,
                     aclrtStream stream, int32_t deviceId) {
    if (resources.queryTensor) {
      aclDestroyTensor(resources.queryTensor);
    }
    if (resources.keyTensor) {
      aclDestroyTensor(resources.keyTensor);
    }
    if (resources.valueTensor) {
      aclDestroyTensor(resources.valueTensor);
    }
    if (resources.sparseIndicesTensor) {
      aclDestroyTensor(resources.sparseIndicesTensor);
    }
    if (resources.attentionOutTensor) {
      aclDestroyTensor(resources.attentionOutTensor);
    }
    if (resources.softmaxMaxTensor) {
      aclDestroyTensor(resources.softmaxMaxTensor);
    }
    if (resources.softmaxSumTensor) {
      aclDestroyTensor(resources.softmaxSumTensor);
    }
    if (resources.queryRopeTensor) {
      aclDestroyTensor(resources.queryRopeTensor);
    }
    if (resources.keyRopeTensor) {
      aclDestroyTensor(resources.keyRopeTensor);
    }

    if (resources.queryDeviceAddr) {
      aclrtFree(resources.queryDeviceAddr);
    }
    if (resources.keyDeviceAddr) {
      aclrtFree(resources.keyDeviceAddr);
    }
    if (resources.valueDeviceAddr) {
      aclrtFree(resources.valueDeviceAddr);
    }
    if (resources.sparseIndicesDeviceAddr) {
      aclrtFree(resources.sparseIndicesDeviceAddr);
    }
    if (resources.attentionOutDeviceAddr) {
      aclrtFree(resources.attentionOutDeviceAddr);
    }
    if (resources.softmaxMaxDeviceAddr) {
      aclrtFree(resources.softmaxMaxDeviceAddr);
    }
    if (resources.softmaxSumDeviceAddr) {
      aclrtFree(resources.softmaxSumDeviceAddr);
    }
    if (resources.queryRopeDeviceAddr) {
      aclrtFree(resources.queryRopeDeviceAddr);
    }

    if (resources.keyRopeDeviceAddr) {
      aclrtFree(resources.keyRopeDeviceAddr);
    }

    if (workspaceAddr) {
      aclrtFree(workspaceAddr);
    }
    if (stream) {
      aclrtDestroyStream(stream);
    }

    aclrtResetDevice(deviceId);
    aclFinalize();
}

} // namespace

int main() {

    int32_t deviceId = 0;
    aclrtStream stream = nullptr;
    TensorResources resources = {};
    void* workspaceAddr = nullptr;
    uint64_t workspaceSize = 0;
    std::vector<int64_t> attentionOutShape = {1, 2, 1, 16};
    std::vector<int64_t> softmaxMaxShape = {1, 2, 1, 16};
    std::vector<int64_t> softmaxSumShape = {1, 2, 1, 16};
    int ret = ACL_SUCCESS;

    // 1. Initialize device and stream
    ret = Init(deviceId, &stream);
    if (!CHECK_RET(ret == ACL_SUCCESS)) {
        LOG_PRINT("Init acl failed. ERROR: %d\n", ret);
        return ret;
    }


    // 2. Initialize tensors
    ret = InitializeTensors(resources);
    if (!CHECK_RET(ret == ACL_SUCCESS)) {
        CleanupResources(resources, workspaceAddr, stream, deviceId);
        return ret;
    }

    // 3. Execute the operation
    ret = ExecuteSparseFlashAttention(resources, stream, &workspaceAddr, &workspaceSize);
    if (!CHECK_RET(ret == ACL_SUCCESS)) {
        CleanupResources(resources, workspaceAddr, stream, deviceId);
        return ret;
    }

    // 4. Synchronize stream
    ret = aclrtSynchronizeStream(stream);
    if (!CHECK_RET(ret == ACL_SUCCESS)) {
        LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", ret);
        CleanupResources(resources, workspaceAddr, stream, deviceId);
        return ret;
    }

    // 5. Process results
    printf("-----------attentionOut output-----------\n");
    PrintOutResult(attentionOutShape, &resources.attentionOutDeviceAddr);
    printf("-----------softmaxMax output-----------\n");
    PrintOutResult(softmaxMaxShape, &resources.softmaxMaxDeviceAddr);
    printf("-----------softmaxSum output-----------\n");
    PrintOutResult(softmaxSumShape, &resources.softmaxSumDeviceAddr);
    // 6. Cleanup resources
    CleanupResources(resources, workspaceAddr, stream, deviceId);
    return 0;
}