两个都有。gitcode.com/cann/cannbot-skills 仓库里既有处理 UB 常驻(状态 buffer 跨循环驻留)的 Skill,也有专门解决 ScalarBound(Scalar 墙)的 Skill。

一、UB 常驻相关 Skill

「融合算子UB状态Buffer跨循环常驻优化设计」 是最直接命中的一项。它的定位说明很明确:针对 FlashAttention / Sparse FlashAttention 等融合算子中的 online softmax 场景,把 softmaxMaxsoftmaxSumsoftmaxExp 三个状态 buffer 一次性分配后常驻 UB,通过 loop % preLoadNum 索引实现双缓冲复用,避免每轮 S2 循环的重复 InitBuffer 开销,并支持读写并行流水。
需要注意它文档里的一段自我声明:SoftmaxFlashV2preLoadNum 等概念不是当前 templates/ 目录下七个 Kernel 已实现的统一接口,各模板(AR-Recompute 的 UpdateCache 二分树、ARA-Recompute 的 cacheBuffer)只是局部 max/sum cache 机制,并非完整的状态驻留方案——这份文档是"建议性参考"而非可直接套用的现成代码。
此外,Vector 侧还有一项相关的「Vector 计算效率优化设计」,其中提到 UB 融合链(通过 VECCALC 队列把中间结果保留在 UB 内不落地 GM,GM 搬运从 2n 次降到 2 次),以及 Counter 模式SetMaskCount(dataSize) 后一次 Vector API 覆盖全部数据,Scalar 开销降 30–50%)——这两条和"常驻 UB"目标是同一方向,可以配合使用。

二、突破 Scalar 墙相关 Skill

有一项专门命中的 Skill:「CANN:Scalar性能优化指南」。它开篇就把"墙"的概念点出来了:Scalar 单元负责标量计算、地址计算、指令参数构造与分发,当它无法及时把指令发到 Cube/Vector/MTE 的 Issue 队列时,其他流水线出现 bubble、整个算子被拖慢,这种现象就叫 ScalarBound
它的核心结论和 9 条编码原则(P1–P9)直接针对这道墙:

  • 主因不是 Scalar 计算指令多,而是 Load/Store 指令多(典型占比超 30%),根源是编译器寄存器 Spill
  • P1 结构体内慎用数组(动态下标会放弃整个结构体的常量传播)
  • P2 循环主尾块分离(Prologue / Hot Loop 零分支 / Epilogue 三段式)
  • P3 显式编写循环代码(for 的回跳走专用硬件,不进分支预测)
  • P4 尽量使用局部变量(成员变量受别名分析压制)
  • P5 变量定义贴近使用位置(缩短活跃范围、降 Spill 概率)
  • P6 避免多级指针解引用
  • P7 避免超大结构体(>64B 跨 cacheline 必 Spill)
  • P8 用 constexpr/模板参数承载编译期常量
  • P9 Hot Loop 内不构造对象、不取地址
    另一项是 「Conv算子Scalar优化」,走的是场景特化路线:利用 depthwise 和小 case 场景固定循环轴、weight 在 L1 全载,去除不必要的循环轴与动态地址计算;并进一步把 Queue/TBuffer/Tpipe 这些会展开为大量 Scalar 指令的高级抽象,替换为 SetFlag/WaitFlag + LocalTensor 的底层原语。
    还有 「Triton-Ascend通用优化洞察」(内含 latency-optimizer 的 references)从 Triton 侧提供了避免标量降级的条件表:a % ba - (a//b)*btl.sqrt(x)/yx * tl.rsqrt(y)、int64 比较降为 int32/float32 等,配套 references/avoid_scalar_lowering.mdload-order.mdloop-invariant-hoisting.md 等专项文档。

三、定位与边界说明

  • 常驻 UB 那份文档自己也标注了:它是融合算子 UB 状态管理的"建议性参考",当前仓库模板里的七个 Kernel 并未实现该完整方案,只有局部的 max/sum cache 机制——即"有方法论、有双缓冲设计、有布局与索引公式,但不是开箱即用的模板"。
  • Scalar 墙 的两份文档(通用 9 原则 + Conv 场景特化)属于知识库类 Skill(被动查询、提供最佳实践),不是需要执行的工具脚本;而 Triton 侧的优化洞察明确说明已剔除与 latency-optimizer 重复的内容,详细规则在 references/ 子文档里。
    如果你想进一步确认这些 Skill 在仓库里的具体路径,最直接的方式是 clone 后 ls(README 给了目录布局:plugins-official/ops/references/ 等结构),或者告诉 Claude Code “帮我安装几个 skills,项目地址是 https://gitcode.com/cann/cannbot-skills”,让 Agent 自动定位。

下面把这两个 Skill 的具体内容拆开讲——每个 Skill 的名称、解决的问题、核心机制、关键代码模式、参数配置和适用边界。

一、UB 常驻相关 Skill

仓库里命中"常驻 UB"的有两份文档,一份针对融合算子的 online softmax 状态管理,一份针对 Elementwise 算子族的 UB/TBuf 复用。

Skill 1:融合算子UB状态Buffer跨循环常驻优化设计

解决的问题:FlashAttention / Sparse FlashAttention 里的 online softmax 需要在 S2 方向多次循环中累积 softmaxMaxsoftmaxSumsoftmaxExp 三个状态。naive 写法每轮循环都重新 InitBuffer 分配/释放这三个 UB buffer,引入不必要的 PipeBarrier 和分配开销。这份文档把三个 buffer 一次性分配后常驻 UB 不释放,通过双缓冲索引实现读写并行流水。
核心机制——常驻 + 双缓冲索引
UB 地址空间按 ping/pong 两组连续布局,三份状态各占一段:

[softmaxMaxBuff: SOFTMAX_TMP_BUFFER_SIZE * preLoadNum]
[softmaxSumBuff: SOFTMAX_TMP_BUFFER_SIZE * preLoadNum]
[softmaxExpBuff: SOFTMAX_TMP_BUFFER_SIZE * preLoadNum]

每轮循环用取余方式选中其中一组:

outIdx = loop % preLoadNum;
softmaxOutOffset = outIdx * SOFTMAX_TMP_BUFFER_SIZE / sizeof(COMPUTE_T);

preLoadNum = 2 时即为经典 pingpong:Vector PIPE 处理第 N 轮数据的同时,MTE3 向第 N+1 轮 buffer 写上一轮结果,实现读写并行。InitBuffer 只在算子初始化阶段调用一次,之后整轮 S2 循环不再重复分配。
事件同步模型(两类事件解耦"算"与"搬"):

事件含义用途
V_MTE3Vector 完成 → 允许 MTE3 覆写状态 buffer 释放控制
MTE3_VMTE3 完成 → 允许 Vector 读取状态 buffer 数据就绪
关键参数(Host 侧 TilingData / constInfo):
struct SoftmaxConstInfo {
    uint32_t preLoadNum;           // 双缓冲深度,通常取 2
    uint32_t softmaxTmpBufferSize; // 单份状态 buffer 大小
    // ...
};

预期收益(文档自带的对比表)

指标naiveoptimized
S2 循环内 InitBuffer 调用每轮 3 次0 次
PipeBarrier 数量每轮若干显著减少
UB 空间利用率低(碎片)高(预分配连续布局)
流水重叠度低(必须串行等 MTE3)高(双缓冲并行)
来源算子参考了 ai_infra_sparse_flash_attention_gqaai_infra_fused_infer_attention_sink
必须知道的边界:文档开头明确声明——SoftmaxFlashV2preLoadNum 这些接口不是当前 templates/ 目录下七个 Kernel 已实现的统一接口,各模板只有局部 max/sum cache 机制(如 AR-Recompute 的 UpdateCache 二分树、ARA-Recompute 的 cacheBuffer)。也就是说这是一份建议性参考设计,不是开箱即用的模板代码。

Skill 2:Elementwise UB 常驻复用与 Bank 冲突规避

这是 2026 年 5 月通过 PR #229 合入的新文档,位于 ops/ascendc-performance-best-practices/reference/elementwise/ub_resident_design.md,共 8 章,覆盖:优化目标与收益(消除重复搬运、规避 bank 冲突)、常驻复用 vs 分时复用的架构对比、UB Bank 结构、paramSize / paddingSize / zoneOffset 关键参数、naive 重复搬运 → 常驻复用 + zone 分区 + padding 错开的核心循环改法、naive→ub_resident 关键修改点对照表、UB 预算/串行边界等约束、常见问题与选型自检清单。它和 Skill 1 方向一致(都是让数据留在 UB 不落地),但面向 Elementwise 算子族,且额外处理了 UB Bank 冲突问题。

二、突破 Scalar 墙相关 Skill

命中"Scalar 墙"的是一份通用指南 + 一份场景特化文档,前者给方法论,后者给 Conv 算子的落地改法。

Skill 3:CANN Scalar性能优化指南

它对"墙"的定义:Scalar 单元负责标量计算、地址计算、指令参数构造与分发。当它无法及时把指令发到 Cube / Vector / MTE 的 Issue 队列时,其他流水线出现 bubble、整个算子被拖慢——这个现象就叫 ScalarBound
核心结论(整份指南的总纲):主因不是 Scalar 计算指令多,而是 Load/Store 指令多(典型占比超过 30%),其根源是编译器寄存器 Spill。因此所有原则的共同目标是:帮编译器更好地完成寄存器分配、别名分析和常量传播,从而减少 Load/Store。
9 条编码原则(P1–P9)具体内容

编号原则具体做法与原因
P1结构体内慎用数组动态下标的结构体数组会让编译器放弃整个结构体的常量传播
P2循环主尾块分离拆成 Prologue / Hot Loop(零分支)/ Epilogue 三段式,避免热路径每轮 3 次分支判断
P3显式编写循环代码用嵌套 for 替代隐式状态机;for 的回跳走专用硬件,不进分支预测
P4尽量使用局部变量成员变量受别名分析压制,跨函数调用后必须从内存重新 Load
P5变量定义贴近使用位置缩短活跃范围 = 缩短寄存器占用 = 降低 Spill 概率
P6避免多级指针解引用每级解引用都是带依赖的 Load,用值类型聚合体替代
P7避免超大结构体> 64B 跨 cacheline 必 Spill;超寄存器容量;指针传递引入别名
P8用 constexpr / 模板参数承载编译期常量const 成员对编译器仍是 runtime Load;只有进入常量折叠才能链式带动 P1 类优化
P9Hot Loop 内不构造对象、不取地址构造/析构展开是大量初始化 Store;取地址让编译器假设变量可被外部修改、强制 Spill
指南还明确了它和算子族优化的协同关系:两者正交——算子族优化(tiling / MTE 策略)决定"做什么计算和搬运",Scalar 优化决定"这些指令能不能被及时分发出去";顺序上以算子族优化为先。

Skill 4:Conv 算子 Scalar 优化

这份走的是场景特化路线:把运行时可变量尽可能转化为编译期常量,减少 Scalar 侧的地址计算与分支判断。两条主打法:
打法 1——固定循环轴 + Weight 全载 L1。通用 Conv 要应对任意 N/H/W/C/K 组合,循环轴、tiling 粒度、weight 加载策略全是参数化的,导致 Scalar 每层循环都做动态地址计算和分支。而 depthwise 和小 case(FMAP + Weight 能一轮搬运全载到 L1)场景下循环轴是固定的,可以直接砍掉多余循环轴:

  • depthwise 场景循环轴:group → batch → m_AL1(输出 M 维,按 AL1 tile 切)→ k(输入通道 K 维),每组 weight 的完整 [cout/groups, cin/groups, kh, kw] 在 SetupGroup 阶段一次性加载到 L1(B1 buffer),整个 group 内的 batch 迭代和 M 迭代复用。
  • 小 case 场景更简:只剩 m → k 两层循环,LoadWeightL1() 在 Process 开头一次性把本 core 的 [singleCoreCo, kTotal] weight 加载到 L1,之后 M-loop 和 K-loop 中不再搬运 weight。
    砍轴的收益是三重的:代码段变短(少一层循环 = 少一套地址计算逻辑)、运行时变量减少(循环变量、临时偏移量不再需要)、运行时额外计算减少(不再需要每层循环的动态乘加推地址)。
    打法 2——去掉 Queue / TBuffer / Tpipe 高级抽象,改用底层原语。这些 Ascend C 抽象在底层会展开为大量 Scalar 指令(队列状态查询、缓冲区索引更新、隐式同步插入)。当场景足够简单时直接替换:
    | 高级抽象 | 底层替代 |
    |—|—|
    | Queue | SetFlag / WaitFlag(显式硬件事件同步) |
    | TBuffer | LocalTensor(直接指定 TPposition + 偏移,手动管理缓冲区) |
    | Tpipe | 不需要(前两者都去掉后只剩空壳) |
    文档给出的代码模式(pingpong 事件同步替代 Queue):
// 原模式(Queue)
// EnQue<MTE1_M>(queue, ...);
// 新模式(显式事件同步)
uint16_t pingPong = 0;
SetFlag<HardEvent::M_MTE1>(static_cast<event_t>(pingPong));  // 生产者信号
WaitFlag<HardEvent::MTE1_M>(static_cast<event_t>(pingPong)); // 消费者等待
SetFlag<HardEvent::MTE1_M>(static_cast<event_t>(pingPong));  // 消费者完成信号
pingPong ^= 1;

三、两者怎么配合用

这两类 Skill 针对的是昇腾 AI Core 上两类不同但常同时出现的瓶颈:UB 常驻解决"数据搬运开销"(消除重复 InitBuffer / 重复 GM 搬入搬出、拉流水重叠度),Scalar 优化解决"指令发射瓶颈"(减少 Load/Store、帮编译器做寄存器分配)。在融合算子里往往是:先按 Skill 3/4 把 ScalarBound 排掉(否则指令发不出去,后面白优化),再按 Skill 1/2 把 UB 常驻和双缓冲拉起来——这与 Scalar 指南自己说的"以算子族优化为先"是一致的逻辑。
一个提醒:上面引用的内容主要来自仓库文档的公开镜像(CSDN 的 gitblog 转载、AtomGit PR 记录),仓库本身在 gitcode.com/cann/cannbot-skills,目录结构是 ops/(正式 Skill)、ops-lab/(实验)、reference/(参考设计文档)。如果要把某份落地到你的项目,最稳妥的做法是 clone 仓库后核对原文(尤其 Skill 1 那份文档自我声明了"建议性参考、非模板现成接口"),再对应到你的算子场景改造。

Logo

鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。

更多推荐