cann ascendc 算子常驻 ub 的skill 还有 突破scalar墙的skill 下 gitcode.com/cann/cannbot-skills
两个都有。gitcode.com/cann/cannbot-skills 仓库里既有处理 UB 常驻(状态 buffer 跨循环驻留)的 Skill,也有专门解决 ScalarBound(Scalar 墙)的 Skill。
一、UB 常驻相关 Skill
「融合算子UB状态Buffer跨循环常驻优化设计」 是最直接命中的一项。它的定位说明很明确:针对 FlashAttention / Sparse FlashAttention 等融合算子中的 online softmax 场景,把 softmaxMax、softmaxSum、softmaxExp 三个状态 buffer 一次性分配后常驻 UB,通过 loop % preLoadNum 索引实现双缓冲复用,避免每轮 S2 循环的重复 InitBuffer 开销,并支持读写并行流水。
需要注意它文档里的一段自我声明:SoftmaxFlashV2、preLoadNum 等概念不是当前 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 % b改a - (a//b)*b、tl.sqrt(x)/y改x * tl.rsqrt(y)、int64 比较降为 int32/float32 等,配套references/avoid_scalar_lowering.md、load-order.md、loop-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 方向多次循环中累积 softmaxMax、softmaxSum、softmaxExp 三个状态。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_MTE3 | Vector 完成 → 允许 MTE3 覆写 | 状态 buffer 释放控制 |
| MTE3_V | MTE3 完成 → 允许 Vector 读取 | 状态 buffer 数据就绪 |
| 关键参数(Host 侧 TilingData / constInfo): |
struct SoftmaxConstInfo {
uint32_t preLoadNum; // 双缓冲深度,通常取 2
uint32_t softmaxTmpBufferSize; // 单份状态 buffer 大小
// ...
};
预期收益(文档自带的对比表):
| 指标 | naive | optimized |
|---|---|---|
| S2 循环内 InitBuffer 调用 | 每轮 3 次 | 0 次 |
| PipeBarrier 数量 | 每轮若干 | 显著减少 |
| UB 空间利用率 | 低(碎片) | 高(预分配连续布局) |
| 流水重叠度 | 低(必须串行等 MTE3) | 高(双缓冲并行) |
来源算子参考了 ai_infra_sparse_flash_attention_gqa、ai_infra_fused_infer_attention_sink。 | ||
必须知道的边界:文档开头明确声明——SoftmaxFlashV2、preLoadNum 这些接口不是当前 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 类优化 |
| P9 | Hot 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 那份文档自我声明了"建议性参考、非模板现成接口"),再对应到你的算子场景改造。
鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。
更多推荐
所有评论(0)