news 2026/5/10 3:47:10

CANN算子MTE2预加载优化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CANN算子MTE2预加载优化

MTE2预加载特性介绍

【免费下载链接】cann-samples算子领域高性能实战演进样例与体系化调优知识库项目地址: https://gitcode.com/cann/cann-samples

1. 原理介绍

1.1 背景

在实现启用双缓冲(Double Buffer)的矩阵乘法时,每条 MTE2_PONG 指令必须等待与其配对的 MTE2_PING 指令之后的所有指令全部发射完毕后,才能获得发射机会。因此,若这两条指令之间插入的指令数量过多,超出了芯片预设的指令队列深度,即使不存在同步依赖,MTE2_PONG 指令仍会因队列阻塞而无法发射。

限制 KL1 长度虽能减少指令队列中的指令数量,但在某些 shape 场景下,KL1 已无法进一步缩减,否则会导致性能损失。此时,需借助 MTE2 预加载来避免指令阻塞。

1.2 原理

通过提前将两组数据搬运到 L1 缓存中,使 MTE2 指令得以提前发射,从而避免因指令堵塞而导致的流水线断流。

计算流水图如下

优先发送 PONG 对应的 MTE2,且下一轮的 PING 无需等待。随后再执行上一轮 MTE2(即已发送 PONG 的那一轮)所对应的 MTE1 与 MMAD 指令,从而实现解耦。

1.3 预期效果

  • 消除指令队列阻塞:MTE2_PONG 不再被动等待 PING 之后的所有指令发射完毕,而是可以提前发射并预取数据,避免因指令队列深度不足导致的发射停顿。
  • 提升流水线连续性:在 KL1 无法进一步缩减的场景下,仍能维持计算单元持续工作,减少流水线断流。

2. 实践:使用MTE2预加载特性优化计算流水

2.1 代码

下面演示 M 方向 MTE2 预加载的实现: 首先,在第一轮搬运两份 A 矩阵到 L1 缓存:

// 第一段:处理第一个分片(tileIdx / blockNum == 0 且是第一轮迭代 iter0 == 0) if (tileIdx / blockNum == 0 && iter0 == 0) { // 等待该 L1 缓冲区上之前的 MTE1(L1 -> GM)和 MTE2(GM -> L1)操作完成 AscendC::WaitFlag<AscendC::HardEvent::MTE1_MTE2>(l1BufId); // 为 L1 缓冲区 A 创建张量,并从全局内存中拷贝第一个分片的 A 数据 auto tensorAL1First = AscendC::Te::MakeTensor(AscendC::Te::MakeL1memPtr<T>(l1BufferAOffset[l1BufId]), layoutAL1); auto tensorAGmTileFirst = tensorAGmBlock(AscendC::Te::MakeCoord(0, iter0 * kL1), AscendC::Te::MakeShape(curM, curGmAKL1)); AscendC::Te::Copy(copyGM2L1, tensorAL1First, tensorAGmTileFirst); // 为 L1 缓冲区 B 创建张量,并从全局内存中拷贝第一个分片的 B 数据 auto tensorBL1First = AscendC::Te::MakeTensor(AscendC::Te::MakeL1memPtr<T>(l1BufferBOffset[l1BufId]), layoutBL1); auto tensorBGmTileFirst = tensorBGmBlock(AscendC::Te::MakeCoord(iter0 * kL1, 0), AscendC::Te::MakeShape(curGmBKL1, curN)); AscendC::Te::Copy(copyGM2L1, tensorBL1First, tensorBGmTileFirst); // 设置标志位,同步 MTE2 -> MTE1,表示拷贝完成 AscendC::SetFlag<AscendC::HardEvent::MTE2_MTE1>(l1BufId); }

接下来,在非首轮迭代或首轮迭代的后续分片时,利用双缓冲机制预取下一分片数据到另一块 L1 缓冲区,实现数据加载与计算的重叠:

// 第二段:预取下一个分片(用于双缓冲) if (tileIdx / blockNum > 0 || (tileIdx / blockNum == 0 && kL1TileNum > 1 && iter0 + 1 < kL1TileNum)) { // 对于第一个 block 内的后续迭代,更新偏移量和缓冲区 ID if(tileIdx / blockNum == 0) { curOffsetL1 = (iter0 + 1) * kL1; curGmAKL1 = NextCurGmAKL1; curGmBKL1 = NextCurGmBKL1; curL1BufId = 1 - l1BufId; // 在两个 L1 缓冲区之间切换 } // 等待目标缓冲区上之前的 MTE1_MTE2 操作完成 AscendC::WaitFlag<AscendC::HardEvent::MTE1_MTE2>(curL1BufId); // 重新计算下一个分片的布局 layoutAL1 = AscendC::Te::MakeLayoutAL1<T>{}(curM, curGmAKL1); layoutBL1 = AscendC::Te::MakeLayoutBL1<T>{}(curGmBKL1, curN); // 将下一个 A 分片从全局内存拷贝到 L1 缓冲区(辅助缓冲区) auto tensorAL1Sec = AscendC::Te::MakeTensor(AscendC::Te::MakeL1memPtr<T>(l1BufferAOffset[curL1BufId]), layoutAL1); auto tensorAGmTileSec = tensorAGmBlock( AscendC::Te::MakeCoord(0, curOffsetL1), AscendC::Te::MakeShape(curM, curGmAKL1)); AscendC::Te::Copy(copyGM2L1, tensorAL1Sec, tensorAGmTileSec); // 将下一个 B 分片从全局内存拷贝到 L1 缓冲区(辅助缓冲区) auto tensorBL1Sec = AscendC::Te::MakeTensor(AscendC::Te::MakeL1memPtr<T>(l1BufferBOffset[curL1BufId]), layoutBL1); auto tensorBGmTileSec = tensorBGmBlock( AscendC::Te::MakeCoord(curOffsetL1, 0), AscendC::Te::MakeShape(curGmBKL1, curN)); AscendC::Te::Copy(copyGM2L1, tensorBL1Sec, tensorBGmTileSec); // 拷贝完成后设置标志位 AscendC::SetFlag<AscendC::HardEvent::MTE2_MTE1>(curL1BufId); }

关键改动点

  • 初始预加载双份数据: 在首轮(iter0 == 0)时,一次性搬运两份 A 矩阵数据到 L1 缓存,使得 MTE2_PONG 提前发射。
  • 缓冲区切换:在首轮块内,每次预取前更新curL1BufId = 1 - l1BufId,切换到另一块缓冲区;同时更新全局内存偏移量(curOffsetL1)和分片尺寸(curGmAKL1, curGmBKL1)。

2.2 修改注意点

  • 只预取所需分片:条件判断确保只在还有剩余分片时(iter0 + 1 < kL1TileNum)才发起预取,避免无效搬运。
  • 首轮特殊处理边界:首轮(iter0 == 0)预加载两份数据后,需确保首次 MMAD 计算使用的是第一份数据,而非等待第二份搬运完成。
  • 末轮搬运与计算的收尾:在最后一轮迭代时,需注意不再触发下一轮预取(避免越界访问),同时确保最后一轮的计算仍能正确访问已搬运的数据块,不要提前释放或覆盖。
  • 受影响部分:代码复杂度增加,流水管理需注意错开操作,同时指令规模扩大,导致耗时增加。

3. 性能结果对比

3.1 case前后性能

从流水对比图可以看出,开启 MTE2 预加载后,Pong 的 MTE2 指令并未因超过预设队列深度而延后启动,整体 MTE2 处理过程向前平移,计算更加连续。

4. 结论

适用场景:

  • 指令队列深度受限的高密度调度场景:当 PING 与 PONG 指令之间插入的指令数量超出芯片预设队列深度时,传统双缓冲会出现 MTE2_PONG 发射阻塞,预加载机制可有效解除该依赖。
  • KL1 无法进一步缩减的性能敏感场景:在某些矩阵 shape 下,继续缩减 KL1 会导致计算性能下降。此时 MTE2 预加载能够在保持 KL1 不变的前提下,通过提前发射MTE_PONG指令来避免流水线断流。

MTE2 预加载通过提前搬运矩阵到L1,消除了 MTE2_PONG 的发射阻塞,提升了流水线连续性与吞吐效率。

5. 编译 执行

  1. 编译样例

从项目根目录启动构建,参考项目README.md

在仓库根目录下完成编译和安装后,进入当前样例目录:

cmake -S . -B build -DNPU_ARCH=dav-3510 cmake --build build --parallel cmake --install build --prefix ./build_out cd ./build_out/1_Features/instruction_optimization/mte2_preload/

如需单独编译当前样例,可使用以下指令:

cmake --build build --target mte2_preload cp ./Samples/1_Features/instruction_optimization/mte2_preload/scripts/profile_matmul.py ./build/Samples/1_Features/instruction_optimization/mte2_preload/ cd ./build/Samples/1_Features/instruction_optimization/mte2_preload/
  1. 运行样例

使用可执行文件直接执行算子用例,需要指定矩阵乘维度,并随机生成输入数据。

./mte2_preload 1024 2048 4096

打印如下执行结果,证明样例执行成功。

matmul run successfully!

如果存在精度问题,则会打印错误数据,并显示如下结果。

matmul run failed!
  1. 测试性能 运行性能测试脚本,指定矩阵乘法的维度后执行。
python3 profile_matmul.py 1024 2048 4096

打印如下执行结果,证明样例性能测试成功。

[Profile Breakdowm] +--------------+------------+---------+------------+----------+----------+-------------+----------------+ | candidate | kernel(us) | mac(us) | scalar(us) | mte1(us) | mte2(us) | fixpipe(us) | icache_miss(%) | +==============+============+=========+============+==========+==========+=============+================+ | mte2_preload | 63.288 | 41.880 | 5.924 | 14.299 | 32.722 | 2.543 | 0.500 | +--------------+------------+---------+------------+----------+----------+-------------+----------------+

与相同规模下的基础 MatMul 算子开启 double-buffer对比:

[Profile Breakdowm] +-----------+------------+---------+------------+----------+----------+-------------+----------------+ | candidate | kernel(us) | mac(us) | scalar(us) | mte1(us) | mte2(us) | fixpipe(us) | icache_miss(%) | +===========+============+=========+============+==========+==========+=============+================+ | n_buffer | 66.000 | 40.810 | 2.558 | 10.659 | 37.595 | 1.980 | 1.200 | +-----------+------------+---------+------------+----------+----------+-------------+----------------+

可以看到,整体kernel运算时间缩短,性能有所提升。

6. 支持架构

NPU ARCH 3510

【免费下载链接】cann-samples算子领域高性能实战演进样例与体系化调优知识库项目地址: https://gitcode.com/cann/cann-samples

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

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

ARM PMU性能监控寄存器详解与优化实践

1. ARM PMU性能监控寄存器深度解析在处理器性能分析和优化领域&#xff0c;ARM架构的性能监控单元(Performance Monitoring Unit, PMU)扮演着关键角色。作为硬件级别的性能监测模块&#xff0c;PMU通过一组精密的寄存器实现对处理器内部各种事件的计数和监控。这些寄存器不仅为…

作者头像 李华
网站建设 2026/5/10 3:46:05

ARM CoreLink L2C-310缓存控制器勘误解析与解决方案

1. ARM CoreLink L2C-310缓存控制器勘误深度解析作为ARMv7架构下广泛应用的二级缓存控制器&#xff0c;CoreLink L2C-310&#xff08;代号PL310&#xff09;在Cortex-A9/ARM11 MPCore系统中承担着关键的内存层次管理职责。但在实际工程应用中&#xff0c;其r1版本存在多个需要开…

作者头像 李华
网站建设 2026/5/10 3:45:26

第三部分-Dockerfile与镜像构建——15. 多阶段构建

15. 多阶段构建 1. 多阶段构建概述 多阶段构建是 Docker 17.05 引入的特性&#xff0c;允许在单个 Dockerfile 中使用多个 FROM 语句&#xff0c;每个阶段可以独立构建&#xff0c;最终只选择需要的文件复制到最终镜像中&#xff0c;从而大幅减小镜像体积。 ┌────────…

作者头像 李华
网站建设 2026/5/10 3:39:55

基于MCP协议的AI调试实践:让Claude成为你的代码调试搭档

1. 项目概述&#xff1a;当Claude成为你的调试搭档 如果你是一名开发者&#xff0c;那么“调试”这两个字&#xff0c;大概率是你日常工作中最耗时、也最令人头疼的部分之一。面对一个诡异的bug&#xff0c;你需要在IDE、终端、浏览器控制台之间反复横跳&#xff0c;设置断点&a…

作者头像 李华
网站建设 2026/5/10 3:38:47

做企业软件的定制软件开发公司解决方案商

当你决定为你的企业定制一款软件时&#xff0c;你期待的蓝图是什么&#xff1f;或许是降本增效的利器&#xff0c;或许是开拓新市场的战车。然而&#xff0c;现实往往骨感。大量企业主满怀希望地投入资金后&#xff0c;等来的却是一堆“烂尾”代码、无休止的返工和交付即失联的…

作者头像 李华
网站建设 2026/5/10 3:36:34

基于Alexa技能与无服务器架构的香港地铁实时查询系统开发实战

1. 项目概述与核心价值最近在折腾智能音箱的技能开发&#xff0c;发现一个挺有意思的开源项目&#xff1a;tomfong/hk-mtr-next-train-skill。这是一个为香港地铁&#xff08;MTR&#xff09;乘客量身定做的语音技能&#xff0c;让你动动嘴皮子&#xff0c;就能问出下一班车什么…

作者头像 李华