Conv 算子 Scalar 优化【免费下载链接】cannbot-skillsCANNBot 是面向 CANN 开发的用于提升开发效率的系列智能体本仓库为其提供可复用的 Skills 模块。项目地址: https://gitcode.com/cann/cannbot-skillsConv 类算子的 Scalar 优化围绕一个核心思路利用场景特化将运行时可变量尽可能转化为编译期常量从而减少 Scalar 侧的地址计算与分支判断降低 Load/Store 指令占比。1. 固定循环轴与泛化范围原理通用 Conv 实现需要考虑任意 N/H/W/C/K 组合循环轴排列、tiling 粒度、weight 加载策略都做成参数化的这导致 Scalar 在每个循环层级都要做动态地址计算和分支。而depthwise和小 case场景下输入规模受限循环轴是固定的比如上面2种场景下weight 在单核内是全载fullload的 —— 此时可以去除不必要的循环轴降低scalar开销。小 case定义FMAP 和 Weight 能在一轮搬运中全载到 L1 的场景。此时不再需要逐 tile 切分 weight/fmap循环轴可大幅精简。Depthwise 场景的循环轴for group in [0, groupOpt): // 组间循环AIV/AIC 交替 for batch in [0, batchCount): // batch 循环 for m_AL1 in [0, actualM): // 输出空间 M 维ho×wo按 AL1 tile 切 for k in [0, kTotal): // 输入通道 K 维K cin/groups × kh × kwWeight 全载每组 weight 的完整[cout/groups, cin/groups, kh, kw]在SetupGroup阶段一次性加载到 L1B1 buffer整个 group 内的 batch 迭代和 M 迭代复用它。小 case 场景的循环轴for m in [0, actualM): // 输出空间 M 维 for k in [0, kTotal): // 输入通道 K 维K cin × kh × kwWeight 全载LoadWeightL1()在 Process 开头一次性将本 core 的[singleCoreCo, kTotal]weight 加载到 L1之后 M-loop 和 K-loop 中不再搬运 weight。为什么省 Scalar去除无效的循环轴后代码段长度减少少一层循环 少一套地址计算逻辑运行过程中的变量减少循环变量、临时偏移量不再需要运行时额外计算减少不再需要每层循环的动态乘加来推导地址。指令数显著下降。2. 去除 Queue / TBuffer / Tpipe改用 SetFlag/WaitFlag LocalTensor原理Queue / TBuffer / Tpipe 是 Ascend C 的高级抽象它们在底层展开为大量 Scalar 指令队列状态查询、缓冲区索引更新、自动同步插入。在 hot loop 中使用这些抽象Scalar 需要维护 Queue 的读/写指针和状态字为 TBuffer 做地址重映射在 Tpipe 的各阶段之间插入隐式同步当场景足够简单固定循环轴、全载 weight时可以直接用底层原语替代替代对照Queue → SetFlag / WaitFlag显式硬件事件同步 TBuffer → LocalTensor直接指定 TPposition 偏移手动管理缓冲区 Tpipe → 不需要Queue 和 TBuffer 都去掉了Tpipe 只剩空壳简要代码模式同步Queue → SetFlag/WaitFlag// 原模式Queue // EnQueMTE1_M(queue, ...); // 新模式显式事件同步 uint16_t pingPong 0; SetFlagHardEvent::M_MTE1(static_castevent_t(pingPong)); // 生产者信号 WaitFlagHardEvent::MTE1_M(static_castevent_t(pingPong)); // 消费者等待 SetFlagHardEvent::MTE1_M(static_castevent_t(pingPong)); // 消费者完成信号 pingPong ^ 1;缓冲区TBuffer → LocalTensor// 原模式TBuffer // TBuffer al0Buf pipe.GetBufferA2(); // 新模式直接指定位置和大小 constexpr uint32_t L0A_HALF 32768; LocalTensorhalf al0(TPosition::A2, pingPong * L0A_HALF, L0A_HALF / sizeof(half));优化效果示意优化前Queue TBuffer Tpipe: 每条 Load/Copy 指令前 Scalar 需要: - 查询 queue 状态Load - 计算 buffer offsetALU Load - 更新 buffer 索引Store → 典型 Scalar Load/Store 占比 30% 优化后SetFlag/WaitFlag LocalTensor: 每条 Load/Copy 指令前 Scalar 仅需要: - WaitFlag硬件信号量无 Scalar 开销 - offset 来自编译期常量常量折叠0 指令 → Scalar Load/Store 占比可降到 15%3. TilingData 常量化原理在图静态场景编译期已知所有维度参数将 TilingData 声明为constexpr编译器会对所有.convRunInfo.xxx和.convApiTiling.xxx的访问做常量传播 常量折叠。这意味着ri.kh * ri.kw、GCeilDiv(kTotal_, K0_VAL)等表达式在编译期算出结果Load3DBitModeParam的 config0/config1 字段直接嵌入立即数不再产生运行时从内存 Load TilingData 的 Scalar 指令声明方式// 编译期常量 TilingData图静态场景 constexpr Conv2DTilingData kTiling { .convRunInfo { /* 所有字段编译期已知 */ }, .convApiTiling { /* ... */ }, };kernel 内部通过Tiling()访问指针指向kTiling编译器看到constexpr后会将所有成员访问折叠。适用条件场景TilingData 来源可否常量直接调用demo/验证编译期常量可constexprops-nn 集成框架下沉Host 侧运行时计算通过 workspace 传入不可图静态场景常量化后TilingData 相关的 Scalar Load 指令从数十条 → 0。三者叠加效果┌─────────────────────────────┐ │ 固定循环轴场景特化 │ │ 消除分支 动态地址计算 │ └──────────────┬──────────────┘ │ ┌──────────────▼──────────────┐ │ 去除 Queue/TBuffer/Tpipe │ │ 消除队列维护 缓冲区映射 │ └──────────────┬──────────────┘ │ ┌──────────────▼──────────────┐ │ TilingData constexpr │ │ 常量传播 → 消除参数 Load │ └──────────────┬──────────────┘ │ ┌──────────────▼──────────────┐ │ Scalar Load/Store 占比 │ │ 从 90% → 10% 以内 │ └─────────────────────────────┘三者是递进关系固定循环轴是前提场景特化让循环结构可写死再去掉高级抽象Queue/TBuffer/Tpipe 没有存在的必要最后常量化 TilingData把仅存的参数 Load 也消除。单独做一项收效有限三者叠加才能把 Scalar 开销压到最低。【免费下载链接】cannbot-skillsCANNBot 是面向 CANN 开发的用于提升开发效率的系列智能体本仓库为其提供可复用的 Skills 模块。项目地址: https://gitcode.com/cann/cannbot-skills创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
CANN Conv算子Scalar优化
Conv 算子 Scalar 优化【免费下载链接】cannbot-skillsCANNBot 是面向 CANN 开发的用于提升开发效率的系列智能体本仓库为其提供可复用的 Skills 模块。项目地址: https://gitcode.com/cann/cannbot-skillsConv 类算子的 Scalar 优化围绕一个核心思路利用场景特化将运行时可变量尽可能转化为编译期常量从而减少 Scalar 侧的地址计算与分支判断降低 Load/Store 指令占比。1. 固定循环轴与泛化范围原理通用 Conv 实现需要考虑任意 N/H/W/C/K 组合循环轴排列、tiling 粒度、weight 加载策略都做成参数化的这导致 Scalar 在每个循环层级都要做动态地址计算和分支。而depthwise和小 case场景下输入规模受限循环轴是固定的比如上面2种场景下weight 在单核内是全载fullload的 —— 此时可以去除不必要的循环轴降低scalar开销。小 case定义FMAP 和 Weight 能在一轮搬运中全载到 L1 的场景。此时不再需要逐 tile 切分 weight/fmap循环轴可大幅精简。Depthwise 场景的循环轴for group in [0, groupOpt): // 组间循环AIV/AIC 交替 for batch in [0, batchCount): // batch 循环 for m_AL1 in [0, actualM): // 输出空间 M 维ho×wo按 AL1 tile 切 for k in [0, kTotal): // 输入通道 K 维K cin/groups × kh × kwWeight 全载每组 weight 的完整[cout/groups, cin/groups, kh, kw]在SetupGroup阶段一次性加载到 L1B1 buffer整个 group 内的 batch 迭代和 M 迭代复用它。小 case 场景的循环轴for m in [0, actualM): // 输出空间 M 维 for k in [0, kTotal): // 输入通道 K 维K cin × kh × kwWeight 全载LoadWeightL1()在 Process 开头一次性将本 core 的[singleCoreCo, kTotal]weight 加载到 L1之后 M-loop 和 K-loop 中不再搬运 weight。为什么省 Scalar去除无效的循环轴后代码段长度减少少一层循环 少一套地址计算逻辑运行过程中的变量减少循环变量、临时偏移量不再需要运行时额外计算减少不再需要每层循环的动态乘加来推导地址。指令数显著下降。2. 去除 Queue / TBuffer / Tpipe改用 SetFlag/WaitFlag LocalTensor原理Queue / TBuffer / Tpipe 是 Ascend C 的高级抽象它们在底层展开为大量 Scalar 指令队列状态查询、缓冲区索引更新、自动同步插入。在 hot loop 中使用这些抽象Scalar 需要维护 Queue 的读/写指针和状态字为 TBuffer 做地址重映射在 Tpipe 的各阶段之间插入隐式同步当场景足够简单固定循环轴、全载 weight时可以直接用底层原语替代替代对照Queue → SetFlag / WaitFlag显式硬件事件同步 TBuffer → LocalTensor直接指定 TPposition 偏移手动管理缓冲区 Tpipe → 不需要Queue 和 TBuffer 都去掉了Tpipe 只剩空壳简要代码模式同步Queue → SetFlag/WaitFlag// 原模式Queue // EnQueMTE1_M(queue, ...); // 新模式显式事件同步 uint16_t pingPong 0; SetFlagHardEvent::M_MTE1(static_castevent_t(pingPong)); // 生产者信号 WaitFlagHardEvent::MTE1_M(static_castevent_t(pingPong)); // 消费者等待 SetFlagHardEvent::MTE1_M(static_castevent_t(pingPong)); // 消费者完成信号 pingPong ^ 1;缓冲区TBuffer → LocalTensor// 原模式TBuffer // TBuffer al0Buf pipe.GetBufferA2(); // 新模式直接指定位置和大小 constexpr uint32_t L0A_HALF 32768; LocalTensorhalf al0(TPosition::A2, pingPong * L0A_HALF, L0A_HALF / sizeof(half));优化效果示意优化前Queue TBuffer Tpipe: 每条 Load/Copy 指令前 Scalar 需要: - 查询 queue 状态Load - 计算 buffer offsetALU Load - 更新 buffer 索引Store → 典型 Scalar Load/Store 占比 30% 优化后SetFlag/WaitFlag LocalTensor: 每条 Load/Copy 指令前 Scalar 仅需要: - WaitFlag硬件信号量无 Scalar 开销 - offset 来自编译期常量常量折叠0 指令 → Scalar Load/Store 占比可降到 15%3. TilingData 常量化原理在图静态场景编译期已知所有维度参数将 TilingData 声明为constexpr编译器会对所有.convRunInfo.xxx和.convApiTiling.xxx的访问做常量传播 常量折叠。这意味着ri.kh * ri.kw、GCeilDiv(kTotal_, K0_VAL)等表达式在编译期算出结果Load3DBitModeParam的 config0/config1 字段直接嵌入立即数不再产生运行时从内存 Load TilingData 的 Scalar 指令声明方式// 编译期常量 TilingData图静态场景 constexpr Conv2DTilingData kTiling { .convRunInfo { /* 所有字段编译期已知 */ }, .convApiTiling { /* ... */ }, };kernel 内部通过Tiling()访问指针指向kTiling编译器看到constexpr后会将所有成员访问折叠。适用条件场景TilingData 来源可否常量直接调用demo/验证编译期常量可constexprops-nn 集成框架下沉Host 侧运行时计算通过 workspace 传入不可图静态场景常量化后TilingData 相关的 Scalar Load 指令从数十条 → 0。三者叠加效果┌─────────────────────────────┐ │ 固定循环轴场景特化 │ │ 消除分支 动态地址计算 │ └──────────────┬──────────────┘ │ ┌──────────────▼──────────────┐ │ 去除 Queue/TBuffer/Tpipe │ │ 消除队列维护 缓冲区映射 │ └──────────────┬──────────────┘ │ ┌──────────────▼──────────────┐ │ TilingData constexpr │ │ 常量传播 → 消除参数 Load │ └──────────────┬──────────────┘ │ ┌──────────────▼──────────────┐ │ Scalar Load/Store 占比 │ │ 从 90% → 10% 以内 │ └─────────────────────────────┘三者是递进关系固定循环轴是前提场景特化让循环结构可写死再去掉高级抽象Queue/TBuffer/Tpipe 没有存在的必要最后常量化 TilingData把仅存的参数 Load 也消除。单独做一项收效有限三者叠加才能把 Scalar 开销压到最低。【免费下载链接】cannbot-skillsCANNBot 是面向 CANN 开发的用于提升开发效率的系列智能体本仓库为其提供可复用的 Skills 模块。项目地址: https://gitcode.com/cann/cannbot-skills创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考