aclnnSparseFlashMlaMetadata

产品支持情况

产品 是否支持
Ascend 950PR/Ascend 950DT
Atlas A3 训练系列产品/Atlas A3 推理系列产品
Atlas A2 训练系列产品/Atlas A2 推理系列产品
Atlas 200I/500 A2 推理产品 ×
Atlas 推理系列产品 ×
Atlas 训练系列产品 ×

功能说明

  • 接口功能:该算子为AICPU算子,SparseFlashMlaMetadata算子为SparseFlashMla算子的前序算子,负责根据输入的序列长度信息和注意力配置参数,生成负载均衡的分核元数据(metadata)。该元数据包含每个AICore上FlashAttention计算任务的Batch、Head、Query分块和KV分块的索引,以及每个VectorCore上FlashDecode归约任务的索引信息。

    该算子不建议单独使用,建议与aclnnSparseFlashMla算子配合使用,形成完整的工作流。

  • 场景简称:SWA(Sliding Window Attention)、CSA(Compressed Sparse Attention)、HCA(Heavily Compressed Attention)。

  • 计算公式:

    该算子为AICPU调度算子,不涉及数值计算。核心流程为:解析各Batch的Q/KV序列长度 → 根据mask模式计算每个S1G块的有效S2范围 → 基于开销模型进行负载均衡分核 → 输出分核元数据。

    输出metadata tensor的shape为(1024,),数据类型为INT32,内部结构如下:

    • FA Metadata区域(AIC_CORE_NUM × 8个INT32),每个AICore的FA阶段任务信息:

      索引 含义
      0 core_enable,该核是否启用
      1 bn2_start,BN2起始索引
      2 m_start,M(S1G)起始索引
      3 s2_start,S2起始索引
      4 bn2_end,BN2结束索引
      5 m_end,M结束索引
      6 s2_end,S2结束索引
      7 first_fd_data_workspace_idx,第一份FD归约数据的workspace偏移
    • FD Metadata区域(AIV_CORE_NUM × 8个INT32),每个AIVCore的FD归约任务信息:

      索引 含义
      0 core_enable,该核是否启用
      1 bn2_idx,归约任务的BN2索引
      2 m_idx,归约任务的M索引
      3 workspace_idx,归约数据在workspace中的存放位置
      4 workspace_num,S2核间切分份数
      5 m_start,M轴起点
      6 m_num,M轴行数
  • 符号说明

    符号 含义
    B Batch Size
    N1/N2 Query/KV头数
    D 每个注意力头的维度
    G GQA分组比,G=N1/N2
    S1/S2 Query/KV序列长度
    S1G S1×G方向的分块索引
    mBaseSize M轴基本块大小,等于G
    s2BaseSize S2轴基本块大小,固定为512

函数原型

每个算子分为两段式接口,必须先调用aclnnSparseFlashMlaMetadataGetWorkspaceSize接口获取计算所需workspace大小以及包含了算子计算流程的执行器,再调用aclnnSparseFlashMlaMetadata执行实际计算。

aclnnStatus aclnnSparseFlashMlaMetadataGetWorkspaceSize(
    const aclTensor *cuSeqlensQOptional,
    const aclTensor *cuSeqlensOriKvOptional,
    const aclTensor *cuSeqlensCmpKvOptional,
    const aclTensor *sequsedQOptional,
    const aclTensor *sequsedOriKvOptional,
    const aclTensor *sequsedCmpKvOptional,
    const aclTensor *cmpResidualKvOptional,
    const aclTensor *oriTopkLengthOptional,
    const aclTensor *cmpTopkLengthOptional,
    int64_t          numHeadsQ,
    int64_t          numHeadsKv,
    int64_t          headDim,
    int64_t          batchSize,
    int64_t          maxSeqlenQ,
    int64_t          maxSeqlenOriKv,
    int64_t          maxSeqlenCmpKv,
    int64_t          oriTopk,
    int64_t          cmpTopk,
    int64_t          cmpRatio,
    int64_t          oriMaskMode,
    int64_t          cmpMaskMode,
    int64_t          oriWinLeft,
    int64_t          oriWinRight,
    const char      *layoutQOptional,
    const char      *layoutKvOptional,
    bool             hasOriKv,
    bool             hasCmpKv,
    const aclTensor *metaData,
    uint64_t        *workspaceSize,
    aclOpExecutor  **executor)
aclnnStatus aclnnSparseFlashMlaMetadata(
    void          *workspace,
    uint64_t       workspaceSize,
    aclOpExecutor *executor,
    aclrtStream    stream)

aclnnSparseFlashMlaMetadataGetWorkspaceSize

  • 参数说明

    参数名 输入/输出 描述 使用说明 数据类型 数据格式 维度(shape) 非连续Tensor
    cuSeqlensQOptional(aclTensor*) 输入 表示不同Batch中q的有效token数(前缀和形式)。 layoutQOptional为TND时必须传入。每个元素表示当前batch与之前所有batch的token数总和。 INT32 ND (B+1,)
    cuSeqlensOriKvOptional(aclTensor*) 输入 表示不同Batch中oriKv的有效token数(前缀和形式)。 layoutKvOptional为TND时必须传入。 INT32 ND (B+1,)
    cuSeqlensCmpKvOptional(aclTensor*) 输入 表示不同Batch中cmpKv的有效token数(前缀和形式)。 layoutKvOptional为TND且存在cmpKv时必须传入。 INT32 ND (B+1,)
    sequsedQOptional(aclTensor*) 输入 表示不同Batch中q实际参与运算的token数。 当前暂不支持指定该参数。 INT32 ND (B,)
    sequsedOriKvOptional(aclTensor*) 输入 表示不同Batch中oriKv实际参与运算的token数。 layoutKvOptional为PA_BBND时必须传入;layoutKvOptional为BSND时可选传入,用于指定每个batch的oriKv有效长度;layoutKvOptional为TND时使用cuSeqlensOriKvOptional表达序列边界。 INT32 ND (B,)
    sequsedCmpKvOptional(aclTensor*) 输入 表示不同Batch中cmpKv实际参与运算的token数。 可选输入。传入时shape必须为(B,),作为每个batch的cmp逻辑有效长度,优先于maxSeqlenCmpKv、cuSeqlensCmpKvOptional或PA block table推导;layoutKvOptional为BSND、TND、PA_BBND时均可使用。 INT32 ND (B,)
    cmpResidualKvOptional(aclTensor*) 输入 压缩KV余数,用于按cmp_len * cmpRatio + residual恢复cmp侧mask使用的压缩前KV长度。 在CSA、HCA、cmpRatio不等于1且cmpMaskMode为3场景必传,layoutKvOptional为BSND、TND、PA_BBND时均可使用。 INT32 ND (B,)
    oriTopkLengthOptional(aclTensor*) 输入 预留输入,当前版本不支持传入非空Tensor。 必须传入nullptr或空Tensor;传入非空Tensor会返回参数错误。 INT32 ND -
    cmpTopkLengthOptional(aclTensor*) 输入 预留输入,当前版本不支持传入非空Tensor。 必须传入nullptr或空Tensor;传入非空Tensor会返回参数错误。 INT32 ND -
    numHeadsQ(int64_t) 输入 Query的多头数。 仅支持1、2、4、8、16、32、64、128,numHeadsQ / numHeadsKv仅支持[1,128]范围内的2的幂。 - - - -
    numHeadsKv(int64_t) 输入 KV的多头数。 仅支持1。 - - - -
    headDim(int64_t) 输入 每个注意力头的维度。 仅支持512。 - - - -
    batchSize(int64_t) 输入 输入样本批量大小。 传入0时表示从cuSeqLensQ推断;layoutQ为TND时无需手动指定。 - - - -
    maxSeqlenQ(int64_t) 输入 所有Batch中q的最大有效token数。 传入0时表示由接口推导。 - - - -
    maxSeqlenOriKv(int64_t) 输入 所有Batch中oriKv的最大有效token数。 传入0时表示由接口推导。 - - - -
    maxSeqlenCmpKv(int64_t) 输入 所有Batch中cmpKv的最大有效token数。 传入0时表示由接口推导。 - - - -
    oriTopk(int64_t) 输入 从oriKv中筛选的稀疏token个数。 当前暂不支持传入非0值,仅支持0。 - - - -
    cmpTopk(int64_t) 输入 从cmpKv中筛选的稀疏token个数。 CSA场景下仅支持512或1024,SWA、HCA场景下为0。 - - - -
    cmpRatio(int64_t) 输入 cmpKv相对于压缩前KV长度的压缩倍率,用于恢复cmp侧mask使用的压缩前KV长度。 支持1、4、128;仅传入oriKv时不参与压缩KV计算,CSA场景传4,HCA场景传128。 - - - -
    oriMaskMode(int64_t) 输入 q和oriKv计算的mask模式。 0: No Mask。
    3: RightDownCausal模式。
    4: Band模式。
    - - - -
    cmpMaskMode(int64_t) 输入 q和cmpKv计算的mask模式。 0: No Mask。
    3: RightDownCausal模式。
    - - - -
    oriWinLeft(int64_t) 输入 滑动窗口向左扩展的token数。 支持-1或非负数,其中-1表示窗口不受限。 - - - -
    oriWinRight(int64_t) 输入 滑动窗口向右扩展的token数。 支持-1或非负数,其中-1表示窗口不受限。 - - - -
    layoutQOptional(char*) 输入 标识输入q的数据排布格式。 支持"BSND"和"TND"。 - - - -
    layoutKvOptional(char*) 输入 标识输入KV的数据排布格式。 支持"PA_BBND"、"BSND"和"TND"。 - - - -
    hasOriKv(bool) 输入 是否传入oriKv。 根据是否传入oriKv设置。 - - - -
    hasCmpKv(bool) 输入 是否传入cmpKv。 SWA场景为false,CSA、HCA场景为true。根据是否传入cmpKv设置。 - - - -
    metaData(aclTensor*) 输出 分核元数据输出,供SparseFlashMla算子使用。 - INT32 ND (1024,) ×
    workspaceSize(uint64_t*) 输出 返回需要在Device侧申请的workspace大小。 - - - - -
    executor(aclOpExecutor**) 输出 返回op执行器,包含了算子计算流程。 - - - - -
    • Atlas A2 训练系列产品/Atlas A2 推理系列产品、Atlas A3 训练系列产品/Atlas A3 推理系列产品:numHeadsQ/numHeadsKv支持1、2、4、8、16、32、64、128,oriMaskMode仅支持4,cmpMaskMode仅支持3,oriWinLeft仅支持127,oriWinRight仅支持0。
    • Ascend 950PR/Ascend 950DT:numHeadsQ/numHeadsKv不支持1,oriMaskMode仅支持4,cmpMaskMode仅支持3,oriWinLeft仅支持127,oriWinRight仅支持0。
  • 返回值

    aclnnStatus:返回状态码,具体参见aclnn返回码

    第一段接口完成入参校验,出现以下场景时报错:

    • Atlas A2 训练系列产品/Atlas A2 推理系列产品、Atlas A3 训练系列产品/Atlas A3 推理系列产品:

      返回值 错误码 描述
      ACLNN_ERR_INNER_CREATE_EXECUTOR 561101 创建aclOpExecutor失败。
      ACLNN_ERR_INNER_NULLPTR 561103 workspaceSize或executor为空指针;可选输入做连续化处理后为空指针;或添加SparseFlashMlaMetadata AICPU任务失败。
      ACLNN_ERR_PARAM_INVALID 161002 batchSize或maxSeqlenQ为负数。
      numHeadsQ不在[1,128]范围内,numHeadsKv不为1,numHeadsQ不能被numHeadsKv整除,或numHeadsQ/numHeadsKv不是[1,128]范围内的2的幂。
      headDim不为512。
      oriMaskMode不为4,或cmpMaskMode不为3。
      oriWinLeft不为127,或oriWinRight不为0。
      SWA场景cmpRatio不为1,或cmpRatio与CSA、HCA场景不匹配。
      cmpTopk不为0、512或1024。
      oriTopkLengthOptional或cmpTopkLengthOptional传入非空Tensor。
      layoutQOptional、layoutKvOptional、cuSeqlens、seqused或metaData的shape、数据类型、必选关系不在支持范围内。
    • Ascend 950PR/Ascend 950DT:

      返回值 错误码 描述
      ACLNN_ERR_INNER_CREATE_EXECUTOR 561101 创建aclOpExecutor失败。
      ACLNN_ERR_INNER_NULLPTR 561103 workspaceSize或executor为空指针;可选输入做连续化处理后为空指针;或添加SparseFlashMlaMetadata AICPU任务失败。
      ACLNN_ERR_PARAM_INVALID 161002 batchSize或maxSeqlenQ为负数。
      numHeadsQ不在[1,128]范围内,numHeadsKv不为1,numHeadsQ不能被numHeadsKv整除,或numHeadsQ/numHeadsKv不在[1,128]范围内。
      headDim不为512。
      oriMaskMode不为0、3、4,或cmpMaskMode不为0、3。
      oriWinLeft或oriWinRight小于-1。
      hasCmpKv为true时,cmpRatio不在[1,128]范围内。
      hasCmpKv为true时,cmpTopk为负数。
      layoutQOptional、layoutKvOptional、cuSeqlens、seqused或metaData的shape、数据类型、必选关系不在支持范围内。

aclnnSparseFlashMlaMetadata

  • 参数说明

    参数名 输入/输出 描述
    workspace 输入 在Device侧申请的workspace内存地址。
    workspaceSize 输入 在Device侧申请的workspace大小,由第一段接口aclnnSparseFlashMlaMetadataGetWorkspaceSize获取。
    executor 输入 op执行器,包含了算子计算流程。
    stream 输入 指定执行任务的Stream。
  • 返回值

    返回aclnnStatus状态码,具体参见aclnn返回码

约束说明

  • 确定性计算

    • aclnnSparseFlashMlaMetadata默认采用确定性实现,相同输入多次调用结果一致。
  • 使用约束

    • layoutQOptional和layoutKvOptional组合仅支持"BSND"/"BSND"、"TND"/"TND"、"BSND"/"PA_BBND"、"TND"/"PA_BBND";非PA_BBND场景下layoutQOptional和layoutKvOptional必须一致。
    • layoutQOptional为TND时,cuSeqlensQOptional必须传入。
    • layoutKvOptional为PA_BBND时,sequsedOriKvOptional必须传入。BSND场景可选传入sequsedOriKvOptional覆盖每个batch的oriKv有效长度;TND场景使用cuSeqlensOriKvOptional表达oriKv序列边界。
    • layoutKvOptional为TND时,cuSeqlensOriKvOptional必须传入;若hasCmpKv为true,cuSeqlensCmpKvOptional也必须传入。
    • sequsedCmpKvOptional为所有layoutKvOptional下的可选输入,显式传入时用于覆盖cmp侧逻辑有效长度。
    • cmpResidualKvOptionalaclnnSparseFlashMlaMetadataaclnnSparseFlashMla的可选输入,在CSA、HCA、cmpRatio不等于1且cmpMaskMode为3场景必传,用于恢复cmp侧mask使用的压缩前长度。
    • 该算子为AICPU算子,在Host侧CPU上执行,不占用NPU计算资源。

调用示例

调用示例代码如下,仅供参考,具体编译和执行过程请参考编译与运行样例

/**
 * @file test_aclnn_sparse_flash_mla_metadata.cpp
 */
#include <iostream>
#include <vector>
#include <cmath>
#include <cstring>
#include <limits>
#include <functional>
#include <utility>
#include "acl/acl.h"
#include "aclnnop/aclnn_sparse_flash_mla_metadata.h"

#define CHECK_LOG_RET(cond, ret_val, fmt, ...)      \
    do {                                            \
        if (!(cond)) {                              \
            printf(fmt "\n", ##__VA_ARGS__);        \
            return (ret_val);                       \
        }                                           \
    } while (0)

// 参考 sparse_flash_mla_metadata.h
constexpr uint32_t AIC_CORE_MAX_NUM = 36;
constexpr uint32_t AIV_CORE_MAX_NUM = 72;
constexpr uint32_t SMLA_METADATA_TOTAL_SIZE = 1024;
constexpr uint32_t FA_METADATA_SIZE = 9;
constexpr uint32_t FD_METADATA_SIZE = 8;

// FA Metadata Index Definitions
constexpr uint32_t FA_CORE_ENABLE_INDEX = 0;
constexpr uint32_t FA_BN2_START_INDEX = 1;
constexpr uint32_t FA_M_START_INDEX = 2;
constexpr uint32_t FA_S2_START_INDEX = 3;
constexpr uint32_t FA_BN2_END_INDEX = 4;
constexpr uint32_t FA_M_END_INDEX = 5;
constexpr uint32_t FA_S2_END_INDEX = 6;
constexpr uint32_t FA_FIRST_FD_DATA_WORKSPACE_IDX_INDEX = 7;
constexpr uint32_t FA_S2_MAX_NUM = 8;

// FD Metadata Index Definitions
constexpr uint32_t FD_CORE_ENABLE_INDEX = 0;
constexpr uint32_t FD_BN2_IDX_INDEX = 1;
constexpr uint32_t FD_M_IDX_INDEX = 2;
constexpr uint32_t FD_WORKSPACE_IDX_INDEX = 3;
constexpr uint32_t FD_WORKSPACE_NUM_INDEX = 4;
constexpr uint32_t FD_M_START_INDEX = 5;
constexpr uint32_t FD_M_NUM_INDEX = 6;

struct SmlaMetadata {
    uint32_t faMetadata[AIC_CORE_MAX_NUM][FA_METADATA_SIZE];
    uint32_t fdMetadata[AIV_CORE_MAX_NUM][FD_METADATA_SIZE];
};

struct ScopeGuard
{
    explicit ScopeGuard(std::function<void()> onExitScope) : m_exitFunc(std::move(onExitScope)),
        m_isDismissed(false) {}
    // 禁止拷贝
    ScopeGuard(const ScopeGuard&) = delete;
    ScopeGuard& operator=(const ScopeGuard&) = delete;

    ~ScopeGuard()
    {
        if (!m_isDismissed) {
            m_exitFunc();
        }
    }

    void Dismiss()
    {
        m_isDismissed = true;
    }

    std::function<void()> m_exitFunc;
    bool m_isDismissed;
};

struct Tensor {
    void *hostAddr { nullptr };
    void *deviceAddr { nullptr };
    aclTensor *data { nullptr };
};

struct ArgScenario {
    bool hasCuSeq { false };
    bool hasSeqused { false };
};

struct ArgContext {
    // required input
    int64_t numHeadsQ { 0 };
    int64_t numHeadsKv { 0 };
    int64_t headDim { 0 };
    // optional input
    Tensor cuSeqlensQOptional {};
    Tensor cuSeqlensOriKvOptional {};
    Tensor cuSeqlensCmpKvOptional {};
    Tensor sequsedQOptional {};
    Tensor sequsedOriKvOptional {};
    Tensor sequsedCmpKvOptional {};
    Tensor cmpResidualKvOptional {};
    Tensor oriTopkLengthOptional {};
    Tensor cmpTopkLengthOptional {};
    int64_t batchSize { 0 };
    int64_t maxSeqlenQ { 0 };
    int64_t maxSeqlenOriKv { 0 };
    int64_t maxSeqlenCmpKv { 0 };
    int64_t oriTopk { 0 };
    int64_t cmpTopk { 0 };
    int64_t cmpRatio { 0 };
    int64_t oriMaskMode { 0 };
    int64_t cmpMaskMode { 0 };
    int64_t oriWinLeft { -1 };
    int64_t oriWinRight { -1 };
    char *layoutQOptional { nullptr };
    char *layoutKvOptional { nullptr };
    bool hasOriKv { true };
    bool hasCmpKv { true };
    // output
    Tensor metadata {};
};

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

aclnnStatus Init(int32_t deviceId, aclrtStream* stream)
{
    // 固定写法,初始化
    auto ret = aclInit(nullptr);
    CHECK_LOG_RET(ret == ACL_SUCCESS, ret, "aclInit failed. ERROR: %d", ret);
    ret = aclrtSetDevice(deviceId);
    CHECK_LOG_RET(ret == ACL_SUCCESS, ret, "aclrtSetDevice failed. ERROR: %d", ret);
    ret = aclrtCreateStream(stream);
    CHECK_LOG_RET(ret == ACL_SUCCESS, ret, "aclrtCreateStream failed. ERROR: %d", ret);
    return ACL_SUCCESS;
}

void Finalize(int32_t deviceId, aclrtStream stream)
{
    aclrtDestroyStream(stream);
    aclrtResetDevice(deviceId);
    aclFinalize();
}

aclnnStatus CreateTensor(aclDataType dataType, const std::vector<int64_t> &shape, Tensor &tensor)
{
    auto size = GetShapeSize(shape) * aclDataTypeSize(dataType);
    // 调用aclrtMallocHost申请host侧内存
    auto ret = aclrtMallocHost(&(tensor.hostAddr), size);
    CHECK_LOG_RET(ret == ACL_SUCCESS, ret, "aclrtMallocHost failed. ERROR: %d", ret);
    memset(tensor.hostAddr, 0, size);
    // 调用aclrtMalloc申请device侧内存
    ret = aclrtMalloc(&(tensor.deviceAddr), size, ACL_MEM_MALLOC_HUGE_FIRST);
    CHECK_LOG_RET(ret == ACL_SUCCESS, ret, "aclrtMalloc failed. ERROR: %d", ret);
    // 调用aclCreateTensor接口创建aclTensor
    tensor.data = aclCreateTensor(shape.data(), shape.size(), dataType, nullptr, 0, aclFormat::ACL_FORMAT_ND,
        shape.data(), shape.size(), tensor.deviceAddr);
    CHECK_LOG_RET(tensor.data != nullptr, ACL_ERROR_FAILURE, "aclCreateTensor failed");
    // 调用aclrtMemcpy将host侧数据拷贝到device侧内存上
    ret = aclrtMemcpy(tensor.deviceAddr, size, tensor.hostAddr, size, ACL_MEMCPY_HOST_TO_DEVICE);
    CHECK_LOG_RET(ret == ACL_SUCCESS, ret, "aclrtMemcpy failed. ERROR: %d", ret);
    return ACL_SUCCESS;
}

void DestroyTensor(Tensor &tensor)
{
    if (tensor.data != nullptr) {
        aclDestroyTensor(tensor.data);
        tensor.data = nullptr;
    }
    if (tensor.deviceAddr != nullptr) {
        aclrtFree(tensor.deviceAddr);
        tensor.deviceAddr = nullptr;
    }
    if (tensor.hostAddr != nullptr) {
        aclrtFreeHost(tensor.hostAddr);
        tensor.hostAddr = nullptr;
    }
}

void DestroyArgs(ArgContext &context)
{
    DestroyTensor(context.metadata);
    DestroyTensor(context.cuSeqlensQOptional);
    DestroyTensor(context.cuSeqlensOriKvOptional);
    DestroyTensor(context.cuSeqlensCmpKvOptional);
    DestroyTensor(context.sequsedQOptional);
    DestroyTensor(context.sequsedOriKvOptional);
    DestroyTensor(context.sequsedCmpKvOptional);
    DestroyTensor(context.cmpResidualKvOptional);
    DestroyTensor(context.oriTopkLengthOptional);
    DestroyTensor(context.cmpTopkLengthOptional);

    if (context.layoutQOptional != nullptr) {
        free(context.layoutQOptional);
        context.layoutQOptional = nullptr;
    }
    if (context.layoutKvOptional != nullptr) {
        free(context.layoutKvOptional);
        context.layoutKvOptional = nullptr;
    }
}

aclnnStatus CreateArgs(const ArgScenario &scenario, ArgContext &context)
{
    ScopeGuard argsGuard([&] { DestroyArgs(context); });
    aclnnStatus ret;

    context.numHeadsQ = 64;
    context.numHeadsKv = 1;
    context.headDim = 512;
    ret = CreateTensor(aclDataType::ACL_INT32, { SMLA_METADATA_TOTAL_SIZE }, context.metadata);     // 1024: Fix size
    CHECK_LOG_RET(ret == ACL_SUCCESS, ret, "Create metadata failed. Error: %d", ret);
    context.oriTopk = 0;
    context.cmpTopk = 0;
    context.cmpRatio = 128;
    context.oriMaskMode = 4;
    context.cmpMaskMode = 3;
    context.oriWinLeft = 127;
    context.oriWinRight = 0;
    context.layoutQOptional = (char *)malloc(sizeof(char) * 16);
    context.layoutKvOptional = (char *)malloc(sizeof(char) * 16);
    CHECK_LOG_RET(context.layoutQOptional != nullptr, ACL_ERROR_FAILURE, "Create layoutQOptional failed");
    CHECK_LOG_RET(context.layoutKvOptional != nullptr, ACL_ERROR_FAILURE, "Create layoutKvOptional failed");
    strcpy(context.layoutQOptional, "BSND");                // BSND,TND
    strcpy(context.layoutKvOptional, "BSND");               // BSND,TND,PA_BBND
    context.hasOriKv = true;
    context.hasCmpKv = true;

    context.batchSize = 4;
    context.maxSeqlenOriKv = 1024;
    context.maxSeqlenCmpKv = 1024;
    context.maxSeqlenQ = 1024;

    if (scenario.hasCuSeq) {
        // (B+1,), first element is always 0
        ret = CreateTensor(aclDataType::ACL_INT32, { context.batchSize + 1 }, context.cuSeqlensQOptional);
        CHECK_LOG_RET(ret == ACL_SUCCESS, ret, "Create cuSeqlensQOptional failed. Error: %d", ret);
        ret = CreateTensor(aclDataType::ACL_INT32, { context.batchSize + 1 }, context.cuSeqlensOriKvOptional);
        CHECK_LOG_RET(ret == ACL_SUCCESS, ret, "Create cuSeqlensOriKvOptional failed. Error: %d", ret);
        ret = CreateTensor(aclDataType::ACL_INT32, { context.batchSize + 1 }, context.cuSeqlensCmpKvOptional);
        CHECK_LOG_RET(ret == ACL_SUCCESS, ret, "Create cuSeqlensCmpKvOptional failed. Error: %d", ret);
    }

    if (scenario.hasSeqused) {
        // (B,)
        ret = CreateTensor(aclDataType::ACL_INT32, { context.batchSize }, context.sequsedQOptional);
        CHECK_LOG_RET(ret == ACL_SUCCESS, ret, "Create sequsedQOptional failed. Error: %d", ret);
        ret = CreateTensor(aclDataType::ACL_INT32, { context.batchSize }, context.sequsedOriKvOptional);
        CHECK_LOG_RET(ret == ACL_SUCCESS, ret, "Create sequsedOriKvOptional failed. Error: %d", ret);
        ret = CreateTensor(aclDataType::ACL_INT32, { context.batchSize }, context.sequsedCmpKvOptional);
        CHECK_LOG_RET(ret == ACL_SUCCESS, ret, "Create sequsedCmpKvOptional failed. Error: %d", ret);
    }

    if (context.hasCmpKv && context.cmpRatio != 1 && context.cmpMaskMode == 3) {
        // (B,)
        ret = CreateTensor(aclDataType::ACL_INT32, { context.batchSize }, context.cmpResidualKvOptional);
        CHECK_LOG_RET(ret == ACL_SUCCESS, ret, "Create cmpResidualKvOptional failed. Error: %d", ret);
    }

    argsGuard.Dismiss();
    return ACL_SUCCESS;
}

int main() {
    // 1. (固定写法)device/stream初始化,参考对外接口列表
    // 根据自己的实际device填写deviceId
    int32_t deviceId = 0;
    aclrtStream stream;
    auto ret = Init(deviceId, &stream);
    CHECK_LOG_RET(ret == ACL_SUCCESS, ret, "Init acl failed. ERROR: %d", ret);
    ScopeGuard sysGuard([&] { Finalize(deviceId, stream); });

    // 2. 构造输入与输出,需要根据API的接口定义构造
    ArgScenario scenario {};
    scenario.hasCuSeq = false;
    scenario.hasSeqused = false;
    ArgContext context {};
    ret = CreateArgs(scenario, context);
    CHECK_LOG_RET(ret == ACL_SUCCESS, ret, "Create input arguments failed. ERROR: %d", ret);
    ScopeGuard argsGuard([&] { DestroyArgs(context); });

    // 3. 调用CANN算子库API,需要修改为具体的API
    // 调用aclnnSparseFlashMlaMetadata第一段接口
    uint64_t workspaceSize = 0;
    aclOpExecutor *executor = nullptr;
    void *workspaceAddr = nullptr;
    ret = aclnnSparseFlashMlaMetadataGetWorkspaceSize(
        context.cuSeqlensQOptional.data, context.cuSeqlensOriKvOptional.data, context.cuSeqlensCmpKvOptional.data,
        context.sequsedQOptional.data, context.sequsedOriKvOptional.data, context.sequsedCmpKvOptional.data,
        context.cmpResidualKvOptional.data, context.oriTopkLengthOptional.data, context.cmpTopkLengthOptional.data,
        context.numHeadsQ, context.numHeadsKv, context.headDim, context.batchSize, context.maxSeqlenQ,
        context.maxSeqlenOriKv, context.maxSeqlenCmpKv, context.oriTopk, context.cmpTopk, context.cmpRatio,
        context.oriMaskMode, context.cmpMaskMode, context.oriWinLeft, context.oriWinRight, context.layoutQOptional,
        context.layoutKvOptional, context.hasOriKv, context.hasCmpKv, context.metadata.data, &workspaceSize, &executor);
    CHECK_LOG_RET(ret == ACL_SUCCESS, ret,
        "aclnnSparseFlashMlaMetadataGetWorkspaceSize failed. ERROR: %d\n", ret);

    if (workspaceSize > static_cast<uint64_t>(0)) {
        ret = aclrtMalloc(&workspaceAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST);
        CHECK_LOG_RET(ret == ACL_SUCCESS, ret, "allocate workspace failed. ERROR: %d\n", ret);
    }
    ScopeGuard workspaceGuard([&] {
        if (workspaceAddr != nullptr) {
            aclrtFree(workspaceAddr);
            workspaceAddr = nullptr;
        }
    });

    // 调用aclnnSparseFlashMlaMetadata第二段接口
    ret = aclnnSparseFlashMlaMetadata(workspaceAddr, workspaceSize, executor, stream);
    CHECK_LOG_RET(ret == ACL_SUCCESS, ret, "aclnnSparseFlashMlaMetadata failed. ERROR: %d\n", ret);

    // 4. (固定写法)同步等待任务执行结束
    ret = aclrtSynchronizeStream(stream);
    CHECK_LOG_RET(ret == ACL_SUCCESS, ret, "aclrtSynchronizeStream failed. ERROR: %d\n", ret);

    // 5. 打印输出
    SmlaMetadata result {};
    ret = aclrtMemcpy(&result, sizeof(result), context.metadata.deviceAddr, sizeof(result), ACL_MEMCPY_DEVICE_TO_HOST);
    CHECK_LOG_RET(ret == ACL_SUCCESS, ret, "aclrtMemcpy failed. ERROR: %d\n", ret);

    for (uint32_t i = 0; i < AIC_CORE_MAX_NUM; ++i) {
        printf("AIC Core%u\n", i);
        printf("    Core Enable : %u\n", result.faMetadata[i][FA_CORE_ENABLE_INDEX]);
        printf("    Start BN2   : %u\n", result.faMetadata[i][FA_BN2_START_INDEX]);
        printf("    Start M     : %u\n", result.faMetadata[i][FA_M_START_INDEX]);
        printf("    Start S2    : %u\n", result.faMetadata[i][FA_S2_START_INDEX]);
        printf("    End BN2     : %u\n", result.faMetadata[i][FA_BN2_END_INDEX]);
        printf("    End M       : %u\n", result.faMetadata[i][FA_M_END_INDEX]);
        printf("    End S2      : %u\n", result.faMetadata[i][FA_S2_END_INDEX]);
        printf("    First Worksapce Index : %u\n", result.faMetadata[i][FA_FIRST_FD_DATA_WORKSPACE_IDX_INDEX]);
        printf("    Max S2 Block Num : %u\n", result.faMetadata[i][FA_S2_MAX_NUM]);
    }
    for (uint32_t i = 0; i < AIV_CORE_MAX_NUM; ++i) {
        printf("AIV Core%u\n", i);
        printf("    Core Enable             : %u\n", result.fdMetadata[i][FD_CORE_ENABLE_INDEX]);
        printf("    FD Task BN2 Idx         : %u\n", result.fdMetadata[i][FD_BN2_IDX_INDEX]);
        printf("    FD Task M Idx           : %u\n", result.fdMetadata[i][FD_M_IDX_INDEX]);
        printf("    FD Task S2 Idx          : %u\n", result.fdMetadata[i][FD_WORKSPACE_IDX_INDEX]);
        printf("    FD Task Workspace Num   : %u\n", result.fdMetadata[i][FD_WORKSPACE_NUM_INDEX]);
        printf("    FD Subtask M Start      : %u\n", result.fdMetadata[i][FD_M_START_INDEX]);
        printf("    FD Subtask M Num        : %u\n", result.fdMetadata[i][FD_M_NUM_INDEX]);
    }

    return 0;
}