深入理解华为 CANN 中的 Broadcast 算子实现:从底层机制到工程化落地

在深度学习算子开发的世界里,Broadcast 是一个看似简单,却在底层实现上极富挑战性的概念。对用户而言,Broadcast 只是让两个 shape 不一致的张量也能 "自然相加";但对于算子开发者而言,它涉及 数据扩维策略、对齐约束、核内 Tiling 切分、UB 缓冲区布局以及对硬件特性的深度理解。
本文将以 Add 算子中的 Broadcast 场景为例,系统讲解 CANN 框架下 Broadcast 机制的设计方式、Tiling 切分策略以及设备侧核函数的完整处理流程。希望通过本文,读者不仅能理解 "怎么做",更能理解 "为什么这样做"。
1. 为什么 Broadcast 在 CANN 算子中如此关键?
在深度学习模型中,Add、Mul、Sub 等基础操作经常用于对不同 shape 的张量执行计算。例如:
- 一个 shape 为 (32, 8) 的张量与
- 一个 shape 为 (32, 1) 的张量相加
框架层面可以轻松处理这种计算,但设备侧算子却不能直接使用不一致的 shape 进行核函数执行。
Broadcast 的核心功能是------
将较小维度的数据在某一个轴上扩展,使其与较大维度保持一致,从而支持逐元素计算。
然而,这看似简单的处理在底层却涉及多维问题:
- 如何理解广播轴、扩展系数(coef)?
- 扩张后的数据在 Unified Buffer 中如何排布?
- GM(Global Memory) 中的读取地址如何对齐?
- 多核 Tiling 后,每个核应处理哪些广播后的数据块?
这一切构成了本文的核心内容。
2. Broadcast 的判定条件与扩展特性
在 CANN 的 Ascend C 编程中,只有满足特定模式的数据才能执行硬件 Broadcast:
- 两个输入的维度数量必须一致;
- 仅允许其中一个维度不同;
- 且较小维度必须为 1。
例如:
| 输入 | shape |
|---|---|
| x | (32, 8) |
| y | (32, 1) |
第二个输入在 axis=1 上为 1,因此能够进行广播,广播系数为:
coef = 8 / 1 = 8
此外,硬件要求输入地址需要 32 字节对齐 ,这对 GM 的读写方式也带来额外限制,因此开发者往往需要使用 DataCopyPad 来规避越界地址或未对齐的问题。
3. Tiling:Broadcast 场景的核心设计挑战
3.1 添加 Broadcast 所需的 Tiling 描述信息
在普通 Add 算子中,我们只需描述输入长度与 tile 切分信息。而当 Broadcast 参与设计时,Tiling 结构体需要额外记录:
- xLen / yLen:两个输入的总长度
- axis:需要进行 Broadcast 的轴
- coef:广播扩展倍数
示例结构:
cpp
struct AddCustomTilingData {
uint32_t xLen;
uint32_t yLen;
uint32_t coef;
uint32_t axis;
...
};
coef 的作用极其关键,它决定了 kernel 中访问 y 数据的方式、扩展后的 shape 计算方式,以及 UB 内的排布方式。
4. Tiling 切分策略:以 "shorterAxisLen" 为核心
为了减少冗余计算、提升多核并行效率,切分逻辑采用以下关键设计:
4.1 以较小维度长度 shorterAxisLen 作为主切分依据
设:
shorterAxisLen = min(xLen, yLen)
totalLength = max(xLen, yLen)
因为较短的维度在 Broadcast 后真正的有效数据仍然只有 shorterAxisLen,在 Tiling 时应以 shorterAxisLen 进行分核。
广播后真实处理的数据长度为:
processed_length = shorterAxisLen * coef
这也是各核通常搬入的 GM 数据量。
5. UB 内数据块长度计算:对齐 coef 与 BUFFER_NUM
Unified Buffer 的利用效率直接影响核函数性能,因此必须对 tileLength 做齐整处理。
核心对齐策略如下:
ubBlockAligned =
if UB_BLOCK_NUM * alignNum / (coef * BUFFER_NUM) * (coef * BUFFER_NUM) == 0:
UB_BLOCK_NUM
else:
UB_BLOCK_NUM * alignNum / (coef * BUFFER_NUM) * (coef * BUFFER_NUM)
这句看似复杂,其背后逻辑十分明确:
👉 为了确保 UB 中的 Y 数据广播后能整齐地与 X 对应,UB 块长度必须同时满足:
- coef 的倍数(广播需要 repeated coef 次)
- BUFFER_NUM 的倍数(流水线分组要求)
- UB_BLOCK_NUM 的基准大小(硬件限制)
这体现了 CANN 算子设计中最重要的一点:
所有 tiled 数据必须"形状可用、对齐可控、排布合理"。
6. 核函数(Device Side)的实现流程详解
核函数执行阶段主要包含三项关键任务:
- 确定 GM 输入指针(长输入与短输入)
- 初始化 UB Buffer
- 根据 coef 处理对齐后的数据搬入与广播计算
6.1 初始化:选择需要广播的输入
cpp
if (tiling.xLen > tiling.yLen) {
longerInputPtr = x;
shorterInputPtr = y;
} else {
longerInputPtr = y;
shorterInputPtr = x;
}
this->coef = tiling.coef;
其中 shorterInputPtr 指向需要进行广播的张量。
6.2 GM Buffer 设置:根据核编号偏移
由于不同核处理不同 tile,需要根据 blockIdx 计算偏移,并对 shorterInputPtr 应用:
offset / coef
以确保访问的是未 broadcast 的数据。
6.3 CopyIn:DataCopyPad 保证 32 字节对齐
因为 broadcast 后数据跨度变化,导致 GM 地址不一定对齐,因此引入 DataCopyPad:
cpp
AscendC::DataCopyPad<dataType>(xLocal, xGm[offset], copyParams, padParams);
AscendC::DataCopyPad<dataType>(yLocal, yGm[offset / coef], copyParams, padParams);
这保证了:
- 地址未对齐也可安全访问
- UB 数据填充完整
7. Compute:Broadcast + Add 的核心实现
7.1 计算广播前后 shape
对于长度 tileLength:
srcShape = { tileLength / coef, 1 }
dstShape = { tileLength / coef, coef }
示意:
| 输入 yLocal | 广播后 broadcastTmp |
|---|---|
| 32 x 1 | 32 x coef |
7.2 调用硬件 Broadcast API
cpp
AscendC::BroadCast<dataType, 2, 1>(broadcastTmpTensor, yLocal, dstShape, srcShape);
该 API 是硬件级广播,具有极高吞吐效率。
7.3 Add 计算(伪代码)
cpp
zLocal = xLocal + broadcastTmpTensor;
实现中通常使用 VectorAdd 或 Add DSL 指令完成。
8. CopyOut:仍然需要 DataCopyPad
由于 tile 的最后一块可能不是对齐长度,需要使用 Pad 写回:
cpp
AscendC::DataCopyPad<dataType>(zGm[offset], zLocal, copyParams);
确保 GM 写回始终合法。
9. 总结:Broadcast 场景下 Add 算子的工程化价值
Broadcast 是许多高层操作的基础,一旦底层实现合理,可以极大提升算子执行效率。
本文总结 Broadcast 场景中最关键的设计点:
★算子设计思路
- 以较小轴长度为切分基础(shorterAxisLen)
- coef 是所有计算的核心倍数参数
★内存访问
- GM 地址必须满足 32 字节对齐,因此 DataCopyPad 必不可少
- broadcast 后数据块必须与 coef × BUFFER_NUM 对齐
★多核并行
- former/tail 切分策略提升核间负载均衡
- tileLength/lastTileLength 区分尾块
★计算部分
- 使用 AscendC::BroadCast API 完成硬件级扩张
- Add 计算在 UB 中即可完成,减少 GM 访问
训练营简介
2025年昇腾CANN训练营第二季,基于CANN开源开放全场景,推出0基础入门系列、码力全开特辑、开发者案例等专题课程,助力不同阶段开发者快速提升算子开发技能。获得Ascend C算子中级认证,即可领取精美证书,完成社区任务更有机会赢取华为手机,平板、开发板等大奖。
报名链接:https://www.hiascend.com/developer/activities/cann20252#cann-camp-2502-intro
