ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

asc-devkit 中 TSCM 输入的 Matmul:L1 Buffer 自定义数据管理下的矩阵乘实现

asc-devkit 中 TSCM 输入的 Matmul:L1 Buffer 自定义数据管理下的矩阵乘实现
  • 人工智能
  • 深度学习
  • 算子库
  • CANN
  • Ascend

【免费下载链接】asc-devkit

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

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

导读

在 CANN asc-devkit 提供的昇腾算子开发语言(Ascend C)中,Matmul 高阶 API 默认由框架自动完成「Global Memory(GM)→ L1 Buffer → L0A/L0B → Cube」的数据搬入流程。而当开发者需要自行掌控矩阵数据在 L1 Buffer 中的摆放与生命周期时,可以使用TSCM(Temp Swap Cache Memory)输入的 Matmul特性:由开发者把 A、B 甚至 Bias 矩阵完整地搬入并驻留在 TSCM(即 L1 Buffer 对应的逻辑内存)中,再通过SetTensorA/SetTensorB将 TSCM 地址直接交给 Matmul 计算。读完本文,你将掌握 TSCM 的逻辑内存含义、适用场景与硬性约束、完整的调用代码范式,以及配套的 Tiling 配置和可运行的仓库样例,能够在算子开发中自主实现"自定义搬入 + 跨 Matmul 对象的全局 Double Buffer"这类高阶调度。

TSCM 是什么:L1 Buffer 的逻辑内存视图

在 AI Core 硬件中,L1 Buffer 是位于 Global Memory 与 L0A/L0B Buffer 之间的通用内部存储,用于暂存 Cube 计算单元需要反复使用的数据,以减少从总线读写的次数。硬件存储单元与搬运单元的详细介绍见基本架构。

TSCM 表示 L1 Buffer 空间对应的逻辑内存。同一个物理内存可能对应多个不同的 TPosition,例如 L1 Buffer 同时映射到 A1、B1、C1 和 TSCM 四个逻辑位置,具体语义取决于当前所服务的计算流程(左矩阵暂存、右矩阵暂存或共享通信),详见数据通路。

从数据结构看,TSCM 是队列机制在目的地址为 TSCM Position 时的别名,与 TQue、TQueBind 属于同一类型结构,定义于 TSCM 简介:

template <TPosition pos, int32_t depth, auto mask = 0> using TSCM = TQueBind<pos, TPosition::TSCM, depth, mask>;

其中:

模板参数含义
pos队列逻辑位置,可以为VECIN、GM,表示 TSCM 数据的来源方向
depth队列深度,表示队列可连续入队/出队的次数。队列深度为 1 时编译器有特殊优化,推荐设置为 1
mask比特位或TQueConfig配置,控制 VECTOR0 与 VECTOR1 映射到 Cube 上的内存位置是否错开等

TSCM 支持AllocTensor/EnQue/DeQue/FreeTensor接口,且必须严格按照AllocTensor → EnQue → DeQue → FreeTensor的顺序配对使用,完整生命周期管理;不支持VacantInQue、HasTensorInQue等查询类接口。

功能与使用场景

功能介绍:开发者可以自行管理 TSCM 以高效利用硬件资源。例如缓存一份 TSCM 数据,在不同使用场景中灵活配置为 Matmul 操作的 A 矩阵、B 矩阵或 Bias 偏置矩阵,实现内存复用与计算效率优化。在 TSCM 输入场景下,用户管理整块 TSCM 内存空间,Matmul 直接使用传入的 TSCM 内存地址,不进行 Global Memory 到 TSCM 的数据搬入(搬入动作由开发者自己完成)。

使用场景:需要自定义数据搬入到 TSCM 及自定义管理的场景,即需要自定义实现数据搬入功能,例如:

  • 非连续搬入;
  • 对搬入数据进行预处理(如格式转换、填充、量化等)。

用户通过自定义管理 TSCM,可以灵活配置 MTE2 流水(GM → L1 Buffer 方向的搬运单元,参见搬运单元),实现跨 Matmul 对象的全局 Double Buffer。Double Buffer 的核心思想是利用 MTE 指令队列与计算指令队列的独立性,让数据搬运与计算并行执行以隐藏搬运时间,详见 Double Buffer。

约束说明:TSCM 输入必须全载

设置为 TSCM 输入的矩阵必须在 TSCM 中全载。所谓全载,即全部的矩阵数据同时搬入并保持在 TSCM 中,而不是像默认 GM 输入那样按迭代分块搬入。这一点也体现在 SetTensorA 的约束中:若设置 TSCM 首地址,默认矩阵可全载、已经位于 TSCM,Iterate 接口无需再进行 GM → L1 Buffer(A1/B1)搬运。

因此,TSCM 的 Buffer 大小必须一次性覆盖单核参与计算所需的完整矩阵数据量,例如 A 矩阵需要申请M * K * sizeof(元素类型)字节的空间(见下文代码)。

核心调用流程与代码范式

TSCM 输入 Matmul 的调用流程可概括为五步:申请 TSCM Buffer → 自定义搬运 → 入队/出队 → 设置矩阵输入 → 迭代计算 → 释放 Buffer。以下为文档中的核心调用示例(A 矩阵使用 TSCM 输入,B 矩阵与 Bias 使用 GM 输入):

TQue<TPosition::A1, 1> scm; // 队列逻辑位置A1,队列深度为1 pipe->InitBuffer(scm, 1, tiling.M * tiling.Ka * sizeof(A_T)); // A_TYPE的TPosition为TSCM,B_TYPE的TPosition为GM Matmul<A_TYPE, B_TYPE, C_TYPE, BIAS_TYPE> mm1; REGIST_MATMUL_OBJ(&pipe, GetSysWorkSpacePtr(), mm1); mm1.Init(&tiling); // 自定义A矩阵GM到TSCM的搬运 auto scmTensor = scm.AllocTensor<A_T>(); DataCopy(scmTensor, gm_a, tiling.M * tiling.Ka); scm.EnQue(scmTensor); LocalTensor<A_T> scmLocal = scm.DeQue<A_T>(); // A矩阵设置为TSCM输入,B矩阵为GM输入 mm1.SetTensorA(scmLocal); mm1.SetTensorB(gm_b); mm1.SetBias(gm_bias); mm1.IterateAll(gm_c); scm.FreeTensor(scmLocal);

对每一步骤逐一说明:

  1. 申请并初始化 TSCM Buffer:TQue<TPosition::A1, 1> scm声明队列;pipe->InitBuffer(scm, 1, tiling.M * tiling.Ka * sizeof(A_T))申请深度为 1、大小为整块 A 矩阵数据量(M × Ka 个元素)的 TSCM Buffer。队列深度 1 满足"单次全载、一次性消费"的语义,编译器对该场景有专门优化。
  2. 创建并注册 Matmul 对象:Matmul<A_TYPE, B_TYPE, C_TYPE, BIAS_TYPE> mm1创建对象,其中A_TYPE的 TPosition 必须为TSCM;REGIST_MATMUL_OBJ(&pipe, GetSysWorkSpacePtr(), mm1)完成对象初始化注册,mm1.Init(&tiling)传入 Tiling 参数。
  3. 自定义数据搬入:scm.AllocTensor<A_T>()从队列分配 tensor,DataCopy(scmTensor, gm_a, tiling.M * tiling.Ka)将 GM 中的 A 矩阵连续搬入 TSCM。此处的 DataCopy 可以由任意自定义搬运逻辑(含预处理)替代,这正是本特性的价值所在。
  4. 入队/出队完成同步:scm.EnQue(scmTensor)在搬运完成后将 tensor 入队,scm.DeQue<A_T>()取出用于计算,EnQue/DeQue 之间隐含了搬运完成与计算开始的同步语义(对应 MTE2 与 Cube 流水之间的事件同步)。
  5. 设置输入并计算:mm1.SetTensorA(scmLocal)将 TSCM 地址作为 A 矩阵输入;SetTensorB(gm_b)、SetBias(gm_bias)分别设置 GM 来源的 B 矩阵与 Bias;mm1.IterateAll(gm_c)完成单核全量计算并输出到 GM。
  6. 释放 Buffer:计算完成后scm.FreeTensor(scmLocal)归还 Buffer,供下一次搬入复用。

模板参数与 Tiling 侧的配套配置

TSCM 输入不仅要在核函数(Kernel)侧设置,还必须保证核函数侧、模板参数侧、Tiling 侧三方配置一致。

MatmulType 模板参数

创建 Matmul 对象时,A、B、C、Bias 的类型信息通过MatmulType定义,包括内存逻辑位置、数据格式、数据类型、是否转置、数据排布和是否开启 L1 复用,参见 Matmul 模板参数与 Matmul 使用说明。TSCM 场景下关键参数:

MatmulType 参数TSCM 输入的要求
POSITIONA 矩阵 / B 矩阵可设置为TPosition::TSCM;Ascend 950PR&950DT 系列还支持 Bias 设置为 TSCM
FORMAT输入矩阵设置为 TSCM 时,对应 Format仅支持CubeFormat::NZ(以 Ascend 950PR&950DT 系列为例)
SRCPOSITIONA/B 矩阵 POSITION 配置为 TSCM 时,必须设置 TSCM 中矩阵数据的来源逻辑位置,默认为TPosition::GM,也可为VECOUT

在仓库样例中,A 矩阵的完整类型声明为:

AscendC::MatmulType<AscendC::TPosition::TSCM, CubeFormat::NZ, AType>

Tiling 侧 SetAType 配置

Tiling 侧通过SetAType设置 A 矩阵的位置、数据格式、数据类型、是否转置等信息,这些信息必须和核函数(Kernel)侧的设置保持一致,参见 SetAType。样例中的 Tiling 配置为:

tilingApi.SetAType( matmul_tiling::TPosition::TSCM, matmul_tiling::CubeFormat::NZ, matmul_tiling::DataType::DT_FLOAT16, IS_TRANS_A); tilingApi.SetBType( matmul_tiling::TPosition::GM, matmul_tiling::CubeFormat::ND, matmul_tiling::DataType::DT_FLOAT16, IS_TRANS_B); tilingApi.SetCType(matmul_tiling::TPosition::GM, matmul_tiling::CubeFormat::ND, matmul_tiling::DataType::DT_FLOAT); tilingApi.SetBiasType(matmul_tiling::TPosition::GM, matmul_tiling::CubeFormat::ND, matmul_tiling::DataType::DT_FLOAT);

即 A 矩阵位置为TSCM、格式为NZ、类型为float16,B 矩阵与 Bias 均为 GM + ND。

转置一致性要求

若 A 矩阵MatmulType的ISTRANS参数设置为false,SetTensorA的isTransposeA只能为false;若强行设置为true,精度会有异常。对于非 half、非 bfloat16_t 输入类型,为确保 Tiling 侧与核函数侧 L1 Buffer 空间计算大小保持一致及结果精度正确,核函数侧ISTRANS、SetTensorA的isTransposeA、Tiling 侧SetAType的isTrans三个参数必须同时为 true 或同时为 false,详见 SetTensorA。

仓库完整样例:编译、运行与结果验证

asc-devkit 仓库提供了与本文场景完全对应的完整算子样例matmul_tscm(自定义数据来源为 GM 的 TSCM 输入的 Matmul 算子样例),位于 examples/01_simd_cpp_api/04_advanced_api/00_matmul/matmul_tscm。样例组成如下:

  • matmul_tscm.asc:核函数与 Host 侧主程序;
  • data_utils.h:二进制数据读写工具;
  • CMakeLists.txt:编译配置;
  • scripts/gen_data.py与scripts/verify_result.py:测试数据生成与结果校验脚本。

样例支持的硬件与软件版本

产品CANN 软件版本
Ascend 950PR & 950DT 系列产品>= CANN 9.1.0
Atlas A3 系列产品>= CANN 9.0.0
Atlas A2 系列产品>= CANN 9.0.0

编译与运行步骤

在样例根目录下执行:

source ${install_path}/cann/set_env.sh # 配置CANN环境变量,install_path为CANN包安装目录,默认 /usr/local/Ascend mkdir -p build && cd build cmake -DCMAKE_ASC_ARCHITECTURES=dav-2201 ..; make -j; # 编译工程,默认npu模式 python3 ../scripts/gen_data.py # 生成测试输入数据 ./demo # 执行编译生成的可执行程序 python3 ../scripts/verify_result.py output/output.bin output/golden.bin # 验证输出结果

编译选项说明:

选项可选值说明
CMAKE_ASC_RUN_MODEnpu(默认)、cpu、sim运行模式:NPU 运行、CPU 调试、NPU 仿真
CMAKE_ASC_ARCHITECTURESdav-2201(默认)、dav-3510NPU 架构:dav-2201 对应 Atlas A2/A3 系列,dav-3510 对应 Ascend 950PR & 950DT 系列

使用 CPU 调试或 NPU 仿真模式时添加-DCMAKE_ASC_RUN_MODE=cpu或-DCMAKE_ASC_RUN_MODE=sim参数,例如:

cmake -DCMAKE_ASC_RUN_MODE=cpu -DCMAKE_ASC_ARCHITECTURES=dav-2201 ..; make -j; # cpu调试模式 cmake -DCMAKE_ASC_RUN_MODE=sim -DCMAKE_ASC_ARCHITECTURES=dav-2201 ..; make -j; # NPU仿真模式

注意:切换编译模式前需清理 cmake 缓存,可在 build 目录下删除CMakeCache.txt后重新 cmake。

执行结果输出test pass!表示精度对比成功。CANN 环境安装方式可参考 quick_start。

样例代码中的 TSCM 使用要点

样例核函数MatmulKernel::Process中完整呈现了"自定义搬入 + TSCM 输入"的实现(matmul_tscm.asc):

// Copy aMatrix from gm to tscm AscendC::TSCM<AscendC::TPosition::GM, 1> scm; pipe->InitBuffer(scm, 1, tiling.M * tiling.Ka * sizeof(AType)); auto scmTensor = scm.AllocTensor<AType>(); DataCopy(scmTensor, aGlobal, tiling.M * tiling.Ka); scm.EnQue(scmTensor); AscendC::LocalTensor<AType> scmLocal = scm.DeQue<AType>(); matmulObj.SetTensorA(scmLocal, IS_TRANS_A); matmulObj.SetTensorB(bGlobal, IS_TRANS_B); if (tiling.isBias) { matmulObj.SetBias(biasGlobal); } matmulObj.IterateAll(cGlobal); matmulObj.End(); scm.FreeTensor(scmLocal);

与文档示例略有差异的是,样例使用TSCM<TPosition::GM, 1>声明队列(来源为 GM),Buffer 大小按整块 A 矩阵M * Ka元素全载申请,并在尾块场景通过matmulObj.SetTail(tailM, tailN)处理多核切分的非对齐边界。样例同时给出了多核切分的偏移计算方式(CalcOffset,按 M 方向优先切分 core 索引)与 Host 侧 Tiling 生成、ACL 内存申请、kernel launch 的完整流程,可作为自研算子的直接模板。

底层数据通路与设计要点

TSCM 输入在数据流中的位置

默认 Matmul 的 Cube 计算数据流为GM → L1 → L0A/L0B → Cube → L0C → FixPipe → GM。TSCM 输入场景下,GM → L1(TSCM)这一段由开发者通过DataCopy(MTE2 流水)自行完成,Matmul 只负责TSCM(L1)→ L0A/L0B → Cube → L0C → GM的后续环节。同时需要注意,NPU 架构版本 3510(Ascend 950PR & 950DT)删除了 GM 到 L0A/L0B 的直接数据通路,L1 Buffer 成为矩阵计算输入的必经中转,TSCM 管理的价值在该架构上更为突出,详见数据通路。

TSCM Buffer 数量与资源限制

由于 TSCM 分配的 Buffer 中存储着同步事件 eventID,且该结构与 Cube 类高阶 API(Matmul 高阶 API、宏函数调用方式)共同使用,因此同一个 TPosition 上 TSCM Buffer 的数量与硬件的同步事件 eventID 以及 Matmul 对象数量有关。具体限制如下(见 TSCM 简介):

  • L1 Buffer(TSCM)从 Unified Buffer(UB,VECIN)发起的 Buffer 块数量与 Matmul 对象数量之和最大为 10 个;
  • 不允许申请的 TSCM Buffer 超出规格限制,超出规格可能引起未定义行为;
  • 从 UB 发起的 L1 Buffer(TSCM)队列内部实现会占用核间同步 flagId,若与CrossCoreSetFlag/CrossCoreWaitFlag同时使用需注意避免 flagId 冲突。

生命周期与内存复用建议

  • TSCM 队列的AllocTensor → EnQue → DeQue → FreeTensor必须严格配对执行,EnQue返回 false 表示队列已满、入队失败(见 EnQue),DeQue返回队列中取出的 LocalTensor(见 DeQue)。
  • 推荐队列深度为 1;深度为 1 时编译器有特殊优化,性能通常更好。
  • 开发者可缓存一份 TSCM 数据,在不同使用场景中灵活配置为 A 矩阵、B 矩阵或 Bias 矩阵,实现内存复用;配合自定义 MTE2 搬运节奏,可以实现跨 Matmul 对象的全局 Double Buffer,将数据搬运时间隐藏在计算流水之后。

小结

TSCM 输入的 Matmul 是 Ascend C 中"内存管理下放给开发者"的关键特性:它以 TSCM 逻辑位置封装 L1 Buffer,让矩阵数据在进入 Cube 之前完全处于开发者的掌控之下。使用时需要把握三个核心点:矩阵必须在 TSCM 全载(Buffer 大小按整块数据申请)、核函数侧/Tiling 侧配置严格一致(POSITION=TSCM、Format=NZ、isTrans 对齐)、队列生命周期严格配对(AllocTensor → EnQue → DeQue → FreeTensor)。在此基础上,开发者即可自由实现非连续搬入、数据预处理以及跨 Matmul 对象的全局 Double Buffer 等高阶调度,相关完整范式可直接参考仓库中的 matmul_tscm 样例。

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

【免费下载链接】asc-devkit

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

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

相关推荐

上一篇:终极指南:Insomnia模块化架构与扩展机制深度解析
下一篇:Monad BFT监控与日志分析:运维人员必备指南

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进