☰
asc-devkit 掩码寄存器实战:基于 C API 的 reg_load_store_mask 样例深度解析
2026/10/16 2:46:11 网站建设 项目流程
  • 人工智能
  • 深度学习
  • 算子库
  • CANN
  • Ascend

【免费下载链接】asc-devkit

本项目是CANN 推出的昇腾AI处理器专用的算子程序开发语言,原生支持C和C++标准规范,主要由类库和语言扩展层构成,提供多层级API,满足多维场景算子开发诉求。

项目地址:https://gitcode.com/cann/asc-devkit
点击查看免费下载

导读

在昇腾 AI 处理器的 SIMD 向量编程中,掩码寄存器(mask register)是控制"哪些元素参与计算、哪些元素被写回"的关键机制。本文以 asc-devkit 仓库中的 reg_load_store_mask 样例 为核心,完整讲解掩码寄存器的加载(load)、**存储(store)与数据选择(select)**三种典型用法,覆盖从 GM→UB 数据搬运、SIMD VF 函数调用、掩码生成到结果回写校验的完整链路。读完本文,你将能独立读懂并改造基于 Ascend C C API 的掩码寄存器算子样例,掌握asc_loadalign/asc_storealign系列接口与asc_select数据选择编程模式。

样例概述与支持范围

该样例演示了 Ascend C C API 中掩码寄存器的三种核心操作能力:加载、存储与基于掩码的数据选择,包含两个由SCENARIO_NUM编译选项切换的独立场景。支持产品与 CANN 版本要求如下:

产品CANN 版本
Ascend 950PR & 950DT 系列产品>= CANN 9.2.0

样例目录结构(位于仓库根目录examples/02_simd_c_api/03_c_api/00_data_movement/reg_load_store_mask/):

reg_load_store_mask/ ├── figures/ │ └── reg_load_store_mask.png // 场景示意图 ├── scripts/ │ ├── gen_data.py // 生成输入与 golden 数据 │ └── verify_result.py // 校验输出数据 ├── CMakeLists.txt // 编译配置 ├── data_utils.h // 输入/输出文件读写工具 ├── reg_load_store_mask.asc // Ascend C C API 内核实现与 host 调用入口 ├── README.md // 中文说明文档 └── README_en.md // 英文说明文档

掩码寄存器基础概念

在深入样例前,先建立掩码寄存器的底层认知。根据仓库中的 数据类型定义文档,掩码寄存器类型为vector_bool,长度为VL / 8,即 32 字节(256 bit),用于在向量计算中筛选参与计算的元素:

  • 当操作数类型为b8时,每个元素对应 1 bit 掩码;
  • 当操作数类型为b16时,每个元素对应 2 bit 掩码,仅最低有效位(LSB)有效;
  • 当操作数类型为b32时,每个元素对应 4 bit 掩码,仅最低有效位有效;
  • b64操作数的每个元素对应 8 bit 掩码(掩码创建接口不支持b64模式,需先用asc_create_mask_b32创建再通过asc_unpack_lower展开)。

掩码寄存器的设置方式主要有四种:调用asc_create_mask按模式创建、调用asc_update_mask按 count 更新、通过asc_loadalign从 UB 搬入、通过asc_copy从矢量数据寄存器搬入。本样例恰好覆盖了其中"按模式创建"与"从 UB 搬入"两条路径。

从寄存器资源角度看,掩码寄存器数量上限为 8,超出上限会写入预留的 8KB UB 空间并可能引起性能劣化;矢量数据寄存器数量上限为 32。这为算子编写时的资源规划提供了重要约束。

整体数据流与同步模型

样例的完整处理流程(见 reg_load_store_mask.asc 内核入口reg_load_store_mask):

  1. MTE2 搬运:asc_copy_gm2ub将输入数据从 GM(Global Memory)搬运至 UB(Unified Buffer);
  2. 同步:asc_sync_notify(PIPE_MTE2, PIPE_V, EVENT_ID0)与asc_sync_wait(PIPE_MTE2, PIPE_V, EVENT_ID0)确保 MTE2 搬运完成后向量(V)流水线才能开始读取;
  3. VF 调用:asc_vf_call<load_store_mask_register>(场景1)或asc_vf_call<select_with_mask_register>(场景2)调用 SIMD VF 函数,在函数内部完成掩码寄存器的加载/生成与寄存器操作;
  4. 回写与同步:asc_sync_notify(PIPE_V, PIPE_MTE3, EVENT_ID0)+asc_sync_wait保证 V 流水线结果就绪后,asc_copy_ub2gm由 MTE3 将结果从 UB 搬回 GM;
  5. 收尾:内核末尾asc_sync_pipe(PIPE_ALL)完成所有流水的收尾同步。

其中constexpr uint32_t byte_elements_per_vf = asc_get_vf_len() / sizeof(uint8_t)与float_elements_per_vf = asc_get_vf_len() / sizeof(float)表明单次 VF 操作的处理宽度由硬件向量长度VL决定;mask_bytes_per_vf = asc_get_vf_len() / 8则对应掩码寄存器 32 字节(256 bit)的物理长度。asc_get_vf_len声明位于 include/c_api/utils/sys_var.h。

场景 1:基础搬运场景——掩码加载与掩码写回

数据规格

数据名称长度数据类型
输入src1024uint8_t
输出dst1024uint8_t

实现过程

场景 1 的 SIMD VF 函数为load_store_mask_register,实现分三步:

  1. asc_loadalign加载掩码:将输入的前 32B 数据加载进掩码寄存器input_mask。由 gen_data.py 生成的输入中,第 1 个字节(src[0] = 1)和第 32 个字节(src[31] = 1)为 1,其余为 0。由于 32B = 256 bit,按 bit 展开后第 1 和第 249 个掩码位置有效(第 249 bit 对应第 32 字节的最低有效位:(32-1)*8 + 0 = 248,即 0-based 第 248 位;文档以"第 1 和第 249 个位置"表述)。代码中asc_loadalign(input_mask, reinterpret_cast<__ubuf__ uint32_t*>(src))将源指针按uint32_t*解释后搬入掩码寄存器。
  2. asc_duplicate_scalar按掩码填充 +asc_storealign写回:asc_duplicate_scalar(result_reg, 2, input_mask)仅在掩码为 1 的位置写入数值 2,其余位置保持 0;随后asc_storealign(dst, result_reg, input_mask)将结果按掩码写回 UB。最终输出中仅第 1 和第 249 个元素为 2,其余为 0。asc_duplicate_scalar的实现原型可参见 impl/c_api/reg_base_impl/reg_broadcast_intf_impl.h。
  3. asc_create_mask_b8(PAT_ALL)生成全 1 掩码并写回 0xFF:all_mask = asc_create_mask_b8(PAT_ALL)生成全 1 掩码寄存器,asc_storealign(dst + one_repeat_size, all_mask)(省略数据寄存器的重载形式)直接将对应的 32B 0xFF 数据写入 UB 中第 257 至第 288 个元素(即dst + 256起始的 32 字节)。asc_create_mask_b8在 include/c_api/reg_compute/compute/reg_mask.h 中被宏定义为底层指令pset_b8,PAT_ALL表示全掩码模式。

函数开头还通过asc_duplicate_scalar(zero_reg, 0, all_mask)配合循环asc_storealign(dst + i*one_repeat_size, zero_reg, all_mask)将 1024 字节输出缓冲区整体清零,保证未选中位置严格保持 0。

golden 数据验证

gen_data.py 中场景 1 的 golden 构造为:golden[0] = 2、golden[248] = 2、golden[256:288] = 255,与上述三步的预期输出完全一致,可作为自行修改掩码位后的参照模板。

场景 2:复合计算场景——基于掩码寄存器的数据选择

数据规格

数据名称长度数据类型
输入src0256float
输入src1256float
输入mask128uint8_t
输出dst256float

实现过程

场景 2 的 SIMD VF 函数为select_with_mask_register:

  1. GM→UB 搬运:src0、src1与掩码数据通过三次asc_copy_gm2ub从 GM 搬入 UB,随后由asc_sync_notify/asc_sync_wait(PIPE_MTE2, PIPE_V)完成流水线同步。
  2. 循环加载:SIMD VF 函数循环float_repeat_times次(256 个 float ÷ 每 VF 的 float 元素数)。每次迭代使用asc_loadalign_postupdate(src0_reg, src0, one_repeat_size)加载 64 个 float 元素到矢量数据寄存器,asc_loadalign_postupdate(select_mask, mask_ptr, mask_repeat_size)加载对应的 32B 掩码数据到掩码寄存器。Post-Update 模式会在加载后自动推进源地址(__ubuf__ 指针&引用语义),无需手动计算偏移。
  3. 按位选择:asc_select(dst_reg, src0_reg, src1_reg, select_mask)按掩码寄存器比特位选择数据——掩码位为 1 时取src0对应元素,为 0 时取src1对应元素。asc_select的 float 重载原型见 impl/c_api/reg_base_impl/reg_permute_sel_intf_impl.h。
  4. 连续写回:asc_storealign_postupdate(dst, dst_reg, one_repeat_size, store_mask)将每轮 64 个 float 结果按asc_create_mask_b32(PAT_ALL)生成的全掩码连续写回 UB,地址随 Post-Update 自动推进,最后 MTE3 将 256 个 float 结果搬回 GM。

掩码位映射与 golden 推导

gen_data.py 中场景 2 的掩码与 golden 构造逻辑值得细读:

  • src0 = np.linspace(0.0, 2.0, 256)(0→2 均匀分布),src1 = np.linspace(2.0, 4.0, 256)(2→4 均匀分布),两者构成天然的"选择对照";
  • mask = np.arange(128)(0~127 的字节序列),每个uint8_t掩码字节覆盖 2 个 float 元素;
  • golden 推导:mask_bit = (mask[index // 2] >> ((index % 2) * 4)) & 1,即取对应字节的低 4 bit(每元素对应 4 bit 掩码,与b32模式 LSB 有效的语义一致),golden[index] = src0[index] if mask_bit else src1[index]。

由此可见,vector_bool掩码寄存器按b32位宽理解时,每个 float 元素对应 4 bit 掩码且仅最低有效位生效,这正是"8 字节掩码覆盖 32 个 b32 元素、128 字节掩码覆盖 256 个 float 元素"的比例关系来源。

核心 C API 接口速查

接口作用关键原型/位置
asc_loadalign从 UB 对齐搬入矢量数据寄存器或掩码寄存器include/c_api/reg_compute/load/loadalign.h
asc_loadalign_postupdate对齐搬入并自动推进源地址(__ubuf__ T*&引用)include/c_api/reg_compute/load/loadalign.h 支持 int8/uint8/fp8/int16/half/bf16/int32/uint32/float/int64 等多种类型重载
asc_storealign将矢量数据寄存器对齐写回 UB,可按掩码只写指定位置storealign.h 同目录族接口
asc_storealign_postupdate对齐写回并自动推进目的地址storealign.h 同目录族接口
asc_create_mask_b8/asc_create_mask_b32按模式创建掩码寄存器(宏映射到底层pset_b8/pset_b32)include/c_api/reg_compute/compute/reg_mask.h
asc_duplicate_scalar将标量按掩码复制填充到矢量寄存器impl/c_api/reg_base_impl/reg_broadcast_intf_impl.h
asc_select按掩码位在两组矢量数据间选择impl/c_api/reg_base_impl/reg_permute_sel_intf_impl.h
asc_vf_call<T>调用__simd_vf__修饰的 SIMD VF 函数内核入口中通过模板实例化调用
asc_sync_notify/asc_sync_wait/asc_sync_pipeMTE2/V/MTE3 流水线同步与收尾内核入口处使用PIPE_MTE2、PIPE_V、PIPE_MTE3、PIPE_ALL

编译与运行

配置环境变量

根据当前环境中的 CANN 开发套件安装方式配置:

source ${install_path}/cann/set_env.sh

说明:${install_path}为 CANN 包安装目录;root 用户默认为/usr/local/Ascend,非 root 用户默认为${HOME}/Ascend。

运行样例

在样例根目录执行以下命令(SCENARIO_NUM可取 1 或 2,默认 1):

SCENARIO_NUM=1 # 选择场景1 mkdir -p build && cd build; # 创建并进入build目录 cmake -DSCENARIO_NUM=$SCENARIO_NUM -DCMAKE_ASC_ARCHITECTURES=dav-3510 ..;make -j; # 编译工程,默认NPU模式 python3 ../scripts/gen_data.py -scenarioNum=$SCENARIO_NUM # 生成测试输入数据和golden数据 ./demo # 执行样例 python3 ../scripts/verify_result.py output/output.bin output/golden.bin # 校验输出数据与golden数据

默认使用 NPU 运行模式;使用 NPU 仿真模式时增加-DCMAKE_ASC_RUN_MODE=sim:

cmake -DSCENARIO_NUM=$SCENARIO_NUM -DCMAKE_ASC_RUN_MODE=sim -DCMAKE_ASC_ARCHITECTURES=dav-3510 ..;make -j; # NPU仿真模式

提示:切换场景或运行模式前,请在build目录删除CMakeCache.txt后重新执行 CMake,避免复用缓存配置。

编译选项

选项可选值说明
SCENARIO_NUM1、2选择掩码寄存器加载、存储和选择场景。
CMAKE_ASC_RUN_MODEnpu、sim选择 NPU 运行或 NPU 仿真模式。
CMAKE_ASC_ARCHITECTURESdav-3510指定 NPU 架构,对应 Ascend 950PR & 950DT 系列产品。

这些选项在 CMakeLists.txt 中均有严格校验:CMAKE_ASC_ARCHITECTURES非dav-3510时报FATAL_ERROR(该 C API 样例仅支持 dav-3510);CMAKE_ASC_RUN_MODE必须匹配^(npu|sim)$;SCENARIO_NUM必须匹配^[1-2]$。同时通过add_definitions(-DSCENARIO_NUM=${SCENARIO_NUM})将场景号以编译宏注入.asc源码(对应源码中constexpr uint32_t scenarioNum = SCENARIO_NUM),这也是两个场景共享同一份内核源码的机制。

host 端执行链路

main函数(位于 reg_load_store_mask.asc)展示了完整的 ACL 运行流程:aclInit→aclrtSetDevice(0)→aclrtCreateStream→ host/device 内存分配(aclrtMallocHost/aclrtMalloc)→ 通过 data_utils.h 的ReadFile读取input.bin(场景1)或input_x.bin/input_y.bin/input_mask.bin(场景2)→aclrtMemcpy(H2D)→ 以reg_load_store_mask<<<1, 0, stream>>>启动内核 →aclrtSynchronizeStream→ D2H 回拷 →WriteFile写出output/output.bin→ 依次释放资源并aclFinalize。所有 ACL 调用均通过CHECK_ACL宏做错误码检查。

结果校验与预期输出

verify_result.py 以uint8_t视角逐字节比对输出与 golden:先检查字节数是否一致,再通过np.where(output != golden)定位不一致位置,最多打印前 101 个不匹配索引(含期望值与实际值)。当输出与 golden 完全一致时,脚本输出:

test pass!

若存在差异,则会打印形如byte index: %06d, expected: %d, actual: %d的逐字节诊断信息并以非零码退出,方便快速定位掩码位配置错误。

小结与扩展建议

reg_load_store_mask 样例以最小的代码体量覆盖了掩码寄存器编程的三类高频用法:

  • 掩码加载:asc_loadalign将 UB 数据按 bit 语义搬入vector_bool掩码寄存器;
  • 掩码写回/填充:asc_storealign结合掩码实现"仅写选中位置",配合asc_duplicate_scalar可完成按掩码的标量广播;
  • 掩码选择:asc_loadalign_postupdate循环加载 +asc_select实现两组数据的按位选择,这是向量化条件分支的标准替代方案。

建议的扩展实验方向:修改 gen_data.py 中的掩码位布局观察选择结果变化;将场景 1 的长度从 1024 调整为非VL整数倍以理解ceil_div重复次数的取整逻辑;对照 数据类型定义文档 中关于寄存器数量上限(掩码 8 个、矢量数据 32 个)的约束,评估复杂算子中的寄存器资源规划。

  • 人工智能
  • 深度学习
  • 算子库
  • CANN
  • Ascend

【免费下载链接】asc-devkit

本项目是CANN 推出的昇腾AI处理器专用的算子程序开发语言,原生支持C和C++标准规范,主要由类库和语言扩展层构成,提供多层级API,满足多维场景算子开发诉求。

项目地址:https://gitcode.com/cann/asc-devkit
点击查看免费下载

相关推荐

上一篇:抖音无水印视频解析怎么这么简单?DouYinBot 让下载、信息、封面一次搞定
下一篇:TurboQuant PPL 调查:从 M2 硬件误判到缺失内核实例化与 Q4_K_M 非对称 KV 救援

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询