news 2026/7/4 7:02:20

CANN块稀疏注意力掩码选择算子

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CANN块稀疏注意力掩码选择算子

aclnnBSASelectBlockMask

【免费下载链接】ops-transformer本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-transformer

产品支持情况

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

功能说明

  • 接口功能​:aclnnBSASelectBlockMask是BSA(BlockSparseAttention)的前置算子,负责根据Query和Key的内容动态生成blockSparseMask,使BSA的调用链从"手动提供掩码"变为"根据Q/K内容自适应选择稀疏模式"。

  • 计算公式​:

    设blockShape = [blockShapeX, blockShapeY],压缩后块数:

    $$ Xblocks = \lceil Sq / blockShapeX \rceil,\quad Yblocks = \lceil Skv / blockShapeY \rceil $$

    Step1:均值池化压缩 (Mean Pooling Compression)

    当actualBlockLenQuery / actualBlockLenKey为null时(完整压缩):

    $$ q_compressed[b, n, x, d] = \frac{1}{blockShapeX} \sum_{i=0}^{blockShapeX-1} query[b, n, x \cdot blockShapeX + i, d] $$

    $$ k_compressed[b, n, y, d] = \frac{1}{blockShapeY} \sum_{j=0}^{blockShapeY-1} key[b, n, y \cdot blockShapeY + j, d] $$

    当actualBlockLenQuery / actualBlockLenKey非null时(部分压缩),仅对每个block内前actualBlockLen个token取均值:

    $$ q_compressed[b, n, x, d] = \frac{1}{actualBlockLenQ[b,x]} \sum_{i=0}^{actualBlockLenQ[b,x]-1} query[b, n, x \cdot blockShapeX + i, d] $$

    $$ k_compressed[b, n, y, d] = \frac{1}{actualBlockLenK[b,y]} \sum_{j=0}^{actualBlockLenK[b,y]-1} key[b, n, y \cdot blockShapeY + j, d] $$

    Step2a:QK Matmul

    $$ score[b, n, x, y] = scale \cdot \sum_{d=0}^{D-1} q_compressed[b, n, x, d] \cdot k_compressed[b, n, y, d] $$

    Step2b:Softmax

    $$ attn_score[b, n, x, y] = softmax(score[b, n, x, :]) = \frac{\exp(score[b, n, x, y] - m_{final})}{l_{final}} $$

    Step3:TopK选择生成索引

    $$ topk_value = \text{round}(sparsity \times Xblocks \times Yblocks) $$

    $$ \mathcal{indices}= \text{TopK}\left(attn_score[b, n, x, y],; topK_value\right) $$

    其中indices为attn_score[b, n, x, y] 中topk_value个最大值对应的索引集合。

    Step4:生成BlockSparseMask$$ blockSparseMaskOut[b, n, x, y] = \begin{cases} 1 & (b, n, x, y) \in \mathcal{indices} \ 0 & (b, n, x, y) \notin \mathcal{indices} \end{cases} $$

BSASelectBlockMask输入query、key的数据排布格式支持从多种维度排布解读,可通过queryLayout和keyLayout传入。为了方便理解后续支持的具体排布格式(如BNSD、TND等),此处先对排布格式中各缩写字母所代表的维度含义进行统一说明:

  • B:表示输入样本批量大小(Batch)
  • T:B和S合轴紧密排列的长度(Total tokens)
  • S:表示输入样本序列长度(Seq-Length)
  • H:表示隐藏层的大小(Head-Size)
  • N:表示多头数(Head-Num)
  • D:表示隐藏层最小的单元尺寸,需满足D = H / N(Head-Dim)

当前支持的布局:

  • queryLayout: "TND" "BNSD"
  • keyLayout: "TND" "BNSD"

函数原型

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

aclnnStatus aclnnBSASelectBlockMaskGetWorkspaceSize( const aclTensor *query, const aclTensor *key, const aclIntArray *blockShape, const aclIntArray *postBlockShape, const aclIntArray *actualSeqLengths, const aclIntArray *actualSeqLengthsK, const aclIntArray *actualBlockLenQuery, const aclIntArray *actualBlockLenKey, char *queryLayout, char *keyLayout, int64_t numKeyHeads, double scaleValue, double sparsity, aclTensor *blockSparseMaskOut, uint64_t *workspaceSize, aclOpExecutor **executor)
aclnnStatus aclnnBSASelectBlockMask( void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, const aclrtStream stream)

aclnnBSASelectBlockMaskGetWorkspaceSize

  • 参数说明:

    参数名输入/输出描述使用说明数据类型数据格式维度(shape)非连续Tensor
    query(aclTensor*)输入注意力计算中的query矩阵,即公式中的query。不支持空Tensor。
    支持的shape为:
    • TND: [totalQTokens, headNum, headDim]。
    • BNSD: [batch, headNum, maxQSeqLength, headDim]。
    FLOAT16、BFLOAT16ND3-4×
    key(aclTensor*)输入注意力计算中的key矩阵,即公式中的key。不支持空Tensor。
    支持的shape为:
    • TND: [totalKTokens, numKeyHeads, headDim]。
    • BNSD: [batch, numKeyHeads, maxKSeqLength, headDim]。
    FLOAT16、BFLOAT16ND3-4×
    blockShape(aclIntArray*)输入稀疏块形状数组。指定每个稀疏块的二维尺寸(行数和列数)。
    • 当配置此输入时的元素要求:
      • 必须包含至少两个元素 [blockShapeX, blockShapeY]。
      • blockShapeX: Q方向块大小,必须为64的倍数且大于0。
      • blockShapeY: KV方向块大小,必须为64的倍数且大于0。
    • 如不配置(传nullptr),算子将默认blockShapeX = 128,blockShapeY = 128。
    INT64-1-
    postBlockShape(aclIntArray*)输入预留参数,用于Softmax后二次压缩。当前不支持,必须传入nullptr。----
    actualSeqLengths(aclIntArray*)输入每个batch的query的实际序列长度。
    用于描述变长序列场景下(即含有Padding填充数据的场景),每个Batch中实际有效的query token数量。
    • 变长序列场景(当queryLayout为 "TND" 时):该项输入必须配置。
    • 定长/变长场景(当queryLayout为 "BNSD" 时):
      • 如配置该项,算子会按指定的有效长度处理,忽略Padding部分的数据,提升性能;
      • 如不配置(传nullptr),算子将默认把query shape中的S维度作为有效长度进行全量处理。
    INT64-1-
    actualSeqLengthsK(aclIntArray*)输入key的实际序列长度数组。
    用于描述变长序列场景下(即含有Padding填充数据的场景),每个Batch中实际有效的key token数量。
    • 变长序列场景(当keyLayout为 "TND" 时):该项输入必须配置。
    • >定长/变长场景(当keyLayout为 "BNSD" 时):
      • 如配置该项,算子会按指定的有效长度处理,忽略Padding部分的数据,提升性能;
      • 如不配置(传nullptr),算子将默认把key shape中的S维度作为有效长度进行全量处理。
    INT64-1-
    actualBlockLenQuery(aclIntArray*)输入每个query block内实际压缩的有效seq长度。
    用于部分压缩场景(如末尾不完整块或仅压缩有效token)。
    • 可选输入:
      • BNSD场景:shape为 [B, Xblocks]。
      • TND场景:shape为 [TotalBlockNum_Q](各batch的Xblocks堆叠)。
      • 每个元素取值范围 [0, blockShapeX]。
      • 当actualBlockLen = 0时:对应block的q_cmp填0向量,不会被topK选中。
      • 当actualBlockLen > 0时:仅对前actualBlockLen个token取均值。
    • 如不配置(传nullptr):对query进行完整压缩(使用完整blockShapeX长度)。
    INT64-1-
    actualBlockLenKey(aclIntArray*)输入每个key block内实际压缩的有效seq长度。
    用于部分压缩场景(如末尾不完整块或仅压缩有效token)。
    • 可选输入:
      • BNSD场景:shape为 [B, Yblocks]。
      • TND场景:shape为 [TotalBlockNum_KV](各batch的Yblocks堆叠)。
      • 每个元素取值范围 [0, blockShapeY]。
      • 当actualBlockLen = 0时:对应block的k_cmp填0向量, 不会被topK选中。
      • 当actualBlockLen > 0时:仅对前actualBlockLen个token取均值。
    • 如不配置(传nullptr):对key进行完整压缩(使用完整blockShapeY长度)。
    INT64-1-
    queryLayout(char*)输入query的数据排布格式。指示输入张量在内存中的具体排布。当前仅支持 "TND"、"BNSD",queryLayout与keyLayout需要保持一致。----
    keyLayout(char*)输入key的数据排布格式。指示输入张量在内存中的具体排布。当前仅支持 "TND"、"BNSD",queryLayout与keyLayout需要保持一致。----
    numKeyHeads(int64_t)输入key的注意力头数。当前仅支持MHA,numKeyHeads必须等于numHeads。----
    scaleValue(double)输入缩放系数,即公式中的scale。用于注意力分数的归一化处理。一般设置为1 / sqrt(D)。----
    sparsity(double)输入稀疏度保留比例。指定公式中attn_score中需要保留的块位置占全部块位置的比例。取值范围 (0.0, 1.0)。----
    blockSparseMaskOut(aclTensor*)输出块状稀疏掩码输出,表示根据Q/K内容自适应生成的稀疏pattern。可直接作为BSA算子的blockSparseMask输入。不支持空Tensor。
    shape为 [B, N, Xblocks, Yblocks]:
    • Xblocks = ceilDiv(maxQSeqlen, blockShapeX)。
    • Yblocks = ceilDiv(maxKSeqlen, blockShapeY)。
    • 值为1表示该block参与注意力计算,值为0表示不参与。
    INT8ND4×
    workspaceSize(uint64_t*)输出返回需要在Device侧申请的workspace大小。-----
    executor(aclOpExecutor**)输出返回op执行器,包含了算子计算流程。-----
  • 返回值

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

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

返回码错误码描述
ACLNN_ERR_PARAM_NULLPTR161001输入query传入的是空指针。
输入key传入的是空指针。
blockSparseMaskOut传入的是空指针。
ACLNN_ERR_PARAM_INVALID161002query、key或者blockSparseMaskOut数据类型不在支持的范围之内。
query和key数据类型不一致。

aclnnBSASelectBlockMask

  • 参数说明:

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

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

约束说明

  • 该接口若与PyTorch配合使用时,需要保证CANN相关包与PyTorch相关包的版本匹配。
  • actualSeqLengths在queryLayout为 "TND" 时必选;actualSeqLengthsK在keyLayout为 "TND" 时必选。
  • 根据算子支持的输入Layout,query张量Shape中对应的head维度大小记为N1,key张量Shape中对应的head维度大小记为N2。必须满足N1 = N2(仅支持MHA)。
  • headDim = 128。
  • blockShapeX和blockShapeY必须为64的倍数。
  • query和key压缩后,query和key对应的Xblocks和Yblocks需满足Xblocks * Yblocks > 1。
  • query和key的数据类型必须一致,仅支持FLOAT16和BFLOAT16。
  • blockSparseMaskOut数据类型为INT8(二值:0或1)。
  • postBlockShape当前不支持,必须传入nullptr。
  • actualBlockLenQuery / actualBlockLenKey若非null,每个元素取值范围 [0, blockShapeX] / [0, blockShapeY];为null时完整压缩。

调用示例

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

#include <iostream> #include <vector> #include <cstring> #include <cmath> #include <cstdint> #include "acl/acl.h" #include "aclnn/opdev/fp16_t.h" #include "aclnnop/aclnn_bsa_select_block_mask.h" using namespace std; #define CHECK_RET(cond, return_expr) \ do { \ if (!(cond)) { \ return_expr; \ } \ } while (0) #define LOG_PRINT(message, ...) \ do { \ printf(message, ##__VA_ARGS__); \ } while (0) int64_t GetShapeSize(const std::vector<int64_t>& shape) { int64_t shapeSize = 1; for (auto i : shape) { shapeSize *= i; } return shapeSize; } int Init(int32_t deviceId, aclrtStream* stream) { // 固定写法,AscendCL初始化 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) { if (shape.empty()) { LOG_PRINT("CreateAclTensor: ERROR - shape is empty\n"); return -1; } for (size_t i = 0; i < shape.size(); ++i) { if (shape[i] <= 0) { LOG_PRINT("CreateAclTensor: ERROR - shape[%zu]=%ld is invalid\n", i, shape[i]); return -1; } } auto size = GetShapeSize(shape) * sizeof(T); if (hostData.size() != static_cast<size_t>(GetShapeSize(shape))) { LOG_PRINT("CreateAclTensor: ERROR - hostData size mismatch: %zu vs %ld\n", hostData.size(), GetShapeSize(shape)); return -1; } *deviceAddr = nullptr; 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); 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); aclrtFree(*deviceAddr); *deviceAddr = nullptr; return ret); std::vector<int64_t> strides(shape.size(), 1); if (shape.size() > 1) { for (int64_t i = static_cast<int64_t>(shape.size()) - 2; i >= 0; i--) { strides[i] = shape[i + 1] * strides[i + 1]; } } *tensor = nullptr; *tensor = aclCreateTensor(shape.data(), shape.size(), dataType, strides.data(), 0, aclFormat::ACL_FORMAT_ND, shape.data(), shape.size(), *deviceAddr); CHECK_RET(*tensor != nullptr, LOG_PRINT("aclCreateTensor failed - returned nullptr\n"); aclrtFree(*deviceAddr); *deviceAddr = nullptr; return -1); return 0; } int main() { // 1. device/stream初始化 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. 设置核心参数 (以BNSD Layout为例) int32_t batch = 1; int32_t numHeads = 4; int32_t numKHeads = 4; // 仅支持MHA: N == KN int32_t qSeqlen = 256 * 1024; // 256K int32_t kSeqlen = 256 * 1024; // 256K int32_t headDim = 128; int32_t blockShapeX = 128; int32_t blockShapeY = 128; // 块数量计算 int32_t ceilQ = (qSeqlen + blockShapeX - 1) / blockShapeX; int32_t ceilK = (kSeqlen + blockShapeY - 1) / blockShapeY; // 3. 构建张量Shape std::vector<int64_t> qShape = {batch, numHeads, qSeqlen, headDim}; std::vector<int64_t> kShape = {batch, numKHeads, kSeqlen, headDim}; std::vector<int64_t> maskShape = {batch, numHeads, ceilQ, ceilK}; // 4. 分配并初始化Host数据 int64_t qSize = GetShapeSize(qShape); int64_t kSize = GetShapeSize(kShape); std::vector<op::fp16_t> qData(qSize, 0.1f); std::vector<op::fp16_t> kData(kSize, 0.1f); // mask输出初始化为0 std::vector<int8_t> maskOutData(GetShapeSize(maskShape), 0); // 创建所有aclTensor void *qAddr = nullptr, *kAddr = nullptr, *maskAddr = nullptr; aclTensor *qTensor = nullptr, *kTensor = nullptr, *maskTensor = nullptr; CreateAclTensor(qData, qShape, &qAddr, aclDataType::ACL_FLOAT16, &qTensor); CreateAclTensor(kData, kShape, &kAddr, aclDataType::ACL_FLOAT16, &kTensor); CreateAclTensor(maskOutData, maskShape, &maskAddr, aclDataType::ACL_INT8, &maskTensor); // 5. 创建aclIntArray属性参数 std::vector<int64_t> blockShapeVec = {blockShapeX, blockShapeY}; aclIntArray *blockShapeArr = aclCreateIntArray(blockShapeVec.data(), blockShapeVec.size()); // 6. 标量与字符串参数配置 char queryLayoutBuffer[16] = "BNSD"; char keyLayoutBuffer[16] = "BNSD"; double scaleValue = 1.0 / std::sqrt(static_cast<double>(headDim)); double sparsity = 0.5; // 7. 调用第一段接口: GetWorkspaceSize uint64_t workspaceSize = 0; aclOpExecutor* executor = nullptr; LOG_PRINT("Calling aclnnBSASelectBlockMaskGetWorkspaceSize...\n"); ret = aclnnBSASelectBlockMaskGetWorkspaceSize( qTensor, kTensor, blockShapeArr, nullptr, nullptr, nullptr, nullptr, nullptr, queryLayoutBuffer, keyLayoutBuffer, static_cast<int64_t>(numKHeads), scaleValue, sparsity, maskTensor, &workspaceSize, &executor ); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("GetWorkspaceSize failed. ERROR: %d\n", ret); return ret); CHECK_RET(executor != nullptr, LOG_PRINT("executor is null after GetWorkspaceSize\n"); return -1); LOG_PRINT("Workspace size required: %lu bytes\n", workspaceSize); // 8. 分配workspace 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); } // 9. 调用第二段接口: 执行计算 LOG_PRINT("Calling aclnnBSASelectBlockMask...\n"); ret = aclnnBSASelectBlockMask(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnBSASelectBlockMask failed. ERROR: %d\n", ret); return ret); // 10. 同步Stream,等待任务执行结束 ret = aclrtSynchronizeStream(stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", ret); return ret); // 11. 将结果拷贝回Host侧打印 (blockSparseMaskOut) int64_t maskSize = GetShapeSize(maskShape); ret = aclrtMemcpy(maskOutData.data(), maskSize * sizeof(int8_t), maskAddr, maskSize * sizeof(int8_t), ACL_MEMCPY_DEVICE_TO_HOST); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("copy result from device to host failed.\n"); return ret); LOG_PRINT("Execution Success! BlockSparseMask output (first 20 elements):\n"); int64_t displayCount = (maskSize < 20) ? maskSize : 20; for (int64_t i = 0; i < displayCount; i++) { LOG_PRINT(" mask index %ld: %u\n", i, static_cast<unsigned int>(maskOutData[i])); } // 12. 释放所有资源 LOG_PRINT("Cleaning up resources...\n"); if (workspaceAddr) { aclrtFree(workspaceAddr); } aclrtFree(qAddr); aclrtFree(kAddr); aclrtFree(maskAddr); aclDestroyTensor(qTensor); aclDestroyTensor(kTensor); aclDestroyTensor(maskTensor); aclDestroyIntArray(blockShapeArr); aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); LOG_PRINT("BSASelectBlockMask Test completed successfully!\n"); return 0; }

【免费下载链接】ops-transformer本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-transformer

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/4 7:01:54

终极指南:electron-prebuilt如何简化Electron应用开发流程

终极指南&#xff1a;electron-prebuilt如何简化Electron应用开发流程 【免费下载链接】electron-prebuilt &#x1f382; Retired project. See README 项目地址: https://gitcode.com/gh_mirrors/el/electron-prebuilt 在Electron应用开发的早期阶段&#xff0c;开发人…

作者头像 李华
网站建设 2026/7/4 7:01:39

Optimus架构深度解析:理解数据工作流编排器的内部工作原理

Optimus架构深度解析&#xff1a;理解数据工作流编排器的内部工作原理 【免费下载链接】optimus Optimus is an easy-to-use, reliable, and performant workflow orchestrator for data transformation, data modeling, pipelines, and data quality management. 项目地址: …

作者头像 李华
网站建设 2026/7/4 7:01:19

如何用OpenBoardView免费工具实现专业级PCB电路板分析?

如何用OpenBoardView免费工具实现专业级PCB电路板分析&#xff1f; 【免费下载链接】OpenBoardView View .brd files 项目地址: https://gitcode.com/gh_mirrors/op/OpenBoardView 还在为无法打开不同EDA软件导出的PCB文件而烦恼吗&#xff1f;作为硬件工程师或电子爱好…

作者头像 李华
网站建设 2026/7/4 6:58:20

CANN asc-devkit Conv3D初始化接口

Init 【免费下载链接】asc-devkit 本项目是CANN 推出的昇腾AI处理器专用的算子程序开发语言&#xff0c;原生支持C和C标准规范&#xff0c;主要由类库和语言扩展层构成&#xff0c;提供多层级API&#xff0c;满足多维场景算子开发诉求。 项目地址: https://gitcode.com/cann/…

作者头像 李华
网站建设 2026/7/4 6:57:43

HandPose X性能优化指南:如何让手部检测速度提升300%

HandPose X性能优化指南&#xff1a;如何让手部检测速度提升300% 【免费下载链接】handpose_x 手部21个关键点检测&#xff0c;二维手势姿态&#xff0c;手势识别&#xff0c;pytorch,handpose 项目地址: https://gitcode.com/gh_mirrors/ha/handpose_x 想要让你的手部关…

作者头像 李华