已关闭
[Requirement|需求建议]: 【社区任务】tile算子需求 #897
刘十一创建于  3月6日关闭于  6月8日
刘十一
刘十一
3月6日 创建

Thanks for sending an requirement! Please fill in the following template to help quickly solve your problem.

一、背景信息 (必填)

使用AscendC对TBE实现的tile算子进行重构,实现了AscendC实现的Sqrt算子对Atlas 800I/T A2硬件的适配。

二、价值/作用 (必填)

“Tile算子”是高性能AI计算中用于‌分块(tiling)优化‌的核心编程抽象,广泛应用于GEMM、FlashAttention等关键算子的开发。实现了tile算子的AscendC实现,替代原有TBE算子在昇腾硬件上的适配。

三、设计方案 (必填)

3.1 使能方式

通过 ACLNN 框架使能,对应接口为 aclnnRepeat。调用流程为标准的两段式接口:

  1. aclnnRepeatGetWorkspaceSize:计算 workspace 大小
  2. aclnnRepeat:执行 Tile 计算

底层通过 ACL runtime 调用 AscendC kernel(tile_kernel_<dtype>)完成计算。

3.2 需求总体设计

3.2.1 Host 侧设计

3.2.1.1 分核策略

采用两级自适应分核策略,根据数据规模动态选择:

策略一:按 outerCount 均分(默认)

将输出张量视为 outerCount × outputInnerDim 的二维结构,其中 outputInnerDim = inputShape[N-1] × multiples[N-1]outerCount = totalOutputElems / outputInnerDim。将 outerCount 行均分到各 AI Core:

rowsPerCore = ceil(outerCount / blockDim)
myStartRow = coreId × rowsPerCore
myEndRow = min(myStartRow + rowsPerCore, outerCount)

策略二:按 mult 维度细粒度切分(splitByMult)

outerCount < blockDiminnerMult > 1 时,仅按外层行数分核会导致大量核空闲。此时将工作量按 outerCount × innerMult 二维展开,每个核负责若干 (row, mult) 组合:

totalWork = outerCount × innerMult
workPerCore = ceil(totalWork / blockDim)
每个核处理 [startWork, endWork) 范围内的 (row, mult) 组合
3.2.1.2 数据分块和内存优化策略

数据分块策略:

Tile 算子将输入数据视为多维张量,经 Host 侧维度合并优化后,简化为最多 8 维的等价问题。数据按最内层维度(innerDim)为基本处理单位进行分块:

  • innerDim = inputShape[N-1]:最内层维度大小
  • innerMult = multiples[N-1]:最内层重复倍数
  • outputInnerDim = innerDim × innerMult:输出最内层维度大小
  • outerCount = totalOutputElems / outputInnerDim:外层循环次数

维度合并优化:

Host 侧在 tiling 计算前对 shape 和 multiples 做等价变换,减少 kernel 内循环次数:

  • Rule A(合并直通维度):从内层向外扫描,若 multiples[i] == 1,将维度 i 合并入维度 i-1(shape[i-1] *= shape[i]),扩大 innerDim
  • Rule B(移除平凡维度):移除 shape[i] == 1 && multiples[i] == 1 的维度

合并示例:

[256, 256, 4] × [2, 2, 2]  →  合并后 mult[2]=2 不变,无法合并
                              但 shape=(256,256,4) 的 innerDim=4 保持
[1, 1024]     × [1024, 1]  →  mult[1]=1,合并: shape=[1024], mult=[1024]
                              innerDim 从 1024 不变,但 outerCount 从 1024 降为 1
[128]         × [8192]     →  无法合并(已是 1 维),innerDim=128, innerMult=8192

LocalMemory(UB)使用策略:

每个 AI Core 分配 3 个 buffer,Host 侧通过 PlatformAscendC 动态获取 UB 大小后三等分:

Buffer 大小 用途
inQue (VECIN) ubSize / 3 GM→UB 数据读入缓冲
outQue (VECOUT) ubSize / 3 UB→GM 数据写出缓冲
tmpBuf (VECCALC) ubSize / 3 UB 内临时计算空间(Gather 的 offset 表等)

单次可处理的最大元素数(bufElems)计算公式:

bufElems = bufferBytes / elemBytes (bufferBytes = ubSize / 3,由 Host 侧动态计算)
数据类型 sizeof(T) bufElems 对齐粒度 (C0Count)
float32 / int32 / uint32 4 bufferBytes/4 8
float16 / bfloat16 / int16 / uint16 2 bufferBytes/2 16
int8 / uint8 / bool 1 bufferBytes 32
uint64 8 bufferBytes/8 4

数据对齐处理:

DataCopy 要求搬运元素数为 32 字节对齐(即 C0Count 的整数倍),处理策略为:

  • 读取(GM→UB):向上对齐到 C0Count 的倍数,innerDimAligned = ceil(innerDim / C0Count) × C0Count
  • 写出(UB→GM):对齐时用 DataCopy 精确写出;非对齐时用 DataCopyPad + DataCopyExtParams 精确控制写出字节数
3.2.1.3 tilingKey 规划策略

当前 Tile 算子使用统一的 tiling 结构体 TileTilingData,kernel 侧根据运行时参数动态选择处理路径,不使用 tilingKey 区分编译时分支。TileTilingData 结构体包含以下字段:

字段 类型 说明
numDims int32_t 维度合并后的维度数
inputShape[8] int32_t 输入各维度大小
multiples[8] int32_t 各维度重复倍数
outputShape[8] int32_t 输出各维度大小
inputStrides[8] int32_t 输入各维度 stride
outputStrides[8] int32_t 输出各维度 stride
totalInputElems int32_t 输入总元素数
totalOutputElems int32_t 输出总元素数
elemBytes int32_t 单个元素字节数
blockDim int32_t 使用的 AI Core 核数
ubSize int32_t 单个 buffer 的 UB 大小(字节)

kernel 侧根据以下运行时条件动态选择 5 条处理路径:

条件 路径 说明
outerCount < blockDim && innerMult > 1 ProcessSplitMult 按 mult 维度细粒度分核
innerDim 对齐 && innerMult >= 4 && innerDim*2 <= bufElems ProcessDoubling UB 内倍增写出
outputInnerDim 对齐可放入 UB && innerDim 对齐 && innerMult > 2 && 不走 Doubling ProcessBuild UB 内构建完整输出行
`innerDim 非对齐 && innerMult > 1 && innerDim <= 16 && outputInnerDim <= 32 && (numDims <= 2 elemBytes >= 2)`
以上均不满足 ProcessPerRow 逐行处理(通用兜底)

3.2.2 Kernel 侧设计

3.2.2.1 Kernel 侧实现描述

AscendC Tile kernel 采用模板化设计,通过 TileOpImpl<T> 类封装,支持 12 种数据类型的统一处理。每种类型通过宏 DEFINE_TILE_KERNEL 生成独立的 kernel 入口函数。

整体流程:

  1. Init 阶段:从 GM 读取 TileTilingData,初始化形状、步长等参数;根据分核策略计算当前核的工作范围 [myStartRow, myEndRow);初始化 GM 指针和 UB buffer(inQue、outQue、tmpBuf)。

  2. Process 阶段:根据运行时参数选择最优处理路径(5 条路径),核心思路是将输出张量按 outerCount × outputInnerDim 展开,每次处理若干外层行,对每行在最内层维度上做 innerMult 次重复拼接。

5 条优化路径详述:

路径 1:ProcessSplitMult(多核分 mult)

  • 适用:outerCount < blockDim && innerMult > 1
  • 原理:当外层行数不足以填满所有核时,将工作量按 outerCount × innerMult 展开,每个核处理若干 (row, mult) 组合
  • 实现:读取一行 input 到 UB → UB 内倍增(若 innerDim 对齐)→ 分段写出到 GM 的对应 mult 位置

路径 2:ProcessDoubling(UB 内倍增写出)

  • 适用:innerDim 对齐 && innerMult >= 4 && innerDim * 2 <= bufElems
  • 原理:读取 innerDim 到 UB 后,通过 UB 内 DataCopy 翻倍复制(innerDim → 2×innerDim → 4×innerDim → ...),达到 bufElems 上限后一次性大块写出到 GM
  • 效果:将 innerMult 次 GM 写降为 ceil(outputInnerDim / maxDoubledSize)

路径 3:ProcessBuild(UB 内构建完整行)

  • 适用:outputInnerDim 对齐可放入 UB && innerDim 对齐 && innerMult > 2 && 不走 Doubling
  • 原理:在 UB 内一次性构建完整的输出行(多次 DataCopy 将 input 复制 innerMult 次),然后一次 DataCopy 写出
  • 优势:减少 GM 写次数,支持多行批量处理

路径 4:ProcessGather(Gather 元素重排)

  • 适用:innerDim 非对齐 && innerMult > 1 && innerDim <= 16 && outputInnerDim <= 32
  • 原理:利用 AscendC Gather 指令(VEC 管线)的元素级重排能力,绕过 32B 对齐限制。预先构建字节偏移表(offset table),一次 Gather 调用将多行 input 重排为完整的 tile 输出
  • 特点:采用双 buffer 流水线(inQue depth=2),MTE2 prefetch 与 VEC+MTE3 并行;offset 表支持 wrapping 跨 repeat period

路径 5:ProcessPerRow(逐行处理)

  • 适用:通用兜底路径,上述路径均不满足时使用
  • 实现:逐行读取 input → 对每行做 innerMult 次写出到 GM
  • 优化 1(canBigWrite):当 outputInnerDim 对齐时,第 0 次 mult 用大块 DataCopy 写出,后续 mult 用 DataCopyPad 精确修正
  • 优化 2(isRepeat):检测重复行(相同 SrcOff),用 GM→UB→GM 大块复制代替重新读取

源地址计算(SrcOff 函数):

对于第 outerIdx 行输出,其对应的输入起始地址通过多维坐标映射计算:

SrcOff(outerIdx):
  对每个维度 d(从 N-2 到 0):
    outputCoord = outerIdx % outputShape[d]
    inputCoord = outputCoord % inputShape[d]
    offset += inputCoord × inputStrides[d]
    outerIdx /= outputShape[d]
3.2.2.2 AscendC 实现流程图
┌──────────────────────────────────────────────────┐
│                    Init 阶段                      │
│  ├─ 从 GM 读取 TileTilingData                    │
│  ├─ 解析 shape/multiples/strides                 │
│  ├─ 计算 innerDim, innerMult, outerCount          │
│  ├─ 计算对齐参数 alignElems = 32/sizeof(T)        │
│  ├─ 判断分核模式:                                  │
│  │   outerCount < blockDim && innerMult > 1?      │
│  │   ├─ Yes: splitByMult 模式 (按 mult 切分)      │
│  │   └─ No:  按 outerCount 均分                   │
│  ├─ 初始化 GM 指针 (gmIn, gmOut)                  │
│  └─ 初始化 UB buffer (inQue, outQue,    │
│     tmpBuf, 各 ubSize/3 字节)                                  │
└───────────────────────┬──────────────────────────┘
                        │
                        ▼
┌──────────────────────────────────────────────────┐
│               Process 阶段 (路径选择)              │
│  ├─ splitByMult?                                  │
│  │   └─ Yes → ProcessSplitMult                    │
│  ├─ innerDim对齐 && innerMult>=4 && 可倍增?        │
│  │   └─ Yes → ProcessDoubling                     │
│  ├─ outputInnerDim可放UB && innerDim对齐 && >2?    │
│  │   └─ Yes → ProcessBuild                        │
│  ├─ innerDim非对齐 && innerMult>1 &&              │
│  │  innerDim<=16 && outDim<=32 &&                │
│  │  (numDims<=2 || elemBytes>=2)?                │
│  │   └─ Yes → ProcessGather                       │
│  └─ 其他 → ProcessPerRow                          │
└───────────────────────┬──────────────────────────┘
                        │
        ┌───────┬───────┼───────┬──────────┐
        ▼       ▼       ▼       ▼          ▼
┌──────────┐┌─────────┐┌──────┐┌─────────┐┌──────────┐
│SplitMult ││Doubling ││Build ││Gather   ││PerRow    │
│          ││         ││      ││         ││          │
│读input→UB││读input→UB││批量读 ││构建offset││批量读    │
│UB内倍增  ││UB内翻倍  ││input ││表(倍增)  ││input→UB  │
│分段写出  ││大块写出  ││UB内拼 ││双buffer  ││逐行/大块 │
│(按mult段)││(到outDim)││接mult ││流水线:   ││写出mult  │
│         ││尾部处理  ││次    ││Gather重排││次       │
│          ││         ││一次写 ││大块写出  ││重复行检测│
│          ││         ││出    ││         ││→GM复制   │
└──────────┘└─────────┘└──────┘└─────────┘└──────────┘
        │       │       │       │          │
        └───────┴───────┴───────┴──────────┘
                        │
                        ▼
               ┌────────────────┐
               │  pipe_barrier  │
               │  同步等待写完成 │
               │  释放 UB buffer│
               └────────────────┘

ProcessGather 详细流水线:

┌─────────────────────────────────────────────────────┐
│ 1. 构建 offset 表 (一次性)                           │
│    ├─ 第0行: SetValue 逐元素设置字节偏移              │
│    ├─ 倍增填充: DataCopy + Adds (向量操作) 至 maxBR行 │
│    └─ 支持 wrapping: offset 循环引用同一组 input      │
├─────────────────────────────────────────────────────┤
│ 2. 双 buffer 流水线处理                              │
│                                                     │
│    batch N:   [MTE2:Read_N] [VEC:Gather_N] [MTE3:Write_N]│
│    batch N+1:          [MTE2:Read_N+1] [VEC:Gather_N+1]  │
│                        ↑ MTE2与VEC+MTE3并行               │
│                                                     │
│    ├─ inQue depth=2: prefetch 下一批 input           │
│    ├─ Gather(outBuf, inBuf, offsetTable, 0, count)  │
│    ├─ DataCopy/DataCopyPad 写出到 GM                 │
│    └─ 按 a-block 对齐边界, 避免跨 a-value 数据污染    │
└─────────────────────────────────────────────────────┘
3.2.2.3 AscendC 实现流程图与 TBE 流程图存在的差异点和原因
差异点 TBE 实现 AscendC 实现 原因
计算模型 基于 TVM 的 broadcast 语义,通过 tbe.broadcast + auto_schedule 自动生成调度 手动实现 5 条优化路径,根据数据特征动态选择最优路径 AscendC 无 TVM 自动调度支持,需手动优化;手动优化可针对特定模式获得更高性能
int8/uint8 处理 先 cast 到 float16,broadcast 后再 cast 回 直接对原始类型做数据搬运,无类型转换 AscendC 的 DataCopy 直接支持任意字节类型搬运,无需绕道 float16;避免了两次类型转换的精度和性能开销
维度合并 通过 adapt_shape 做维度拆分(1 维拆为 2 维),增加维度 Host 侧做维度合并(多维合并为少维),减少维度 AscendC 的分块搬运以 innerDim 为粒度,合并 mult=1 的维度可扩大 innerDim,减少 DMA 次数,大幅提升性能
内存管理 TVM 自动分配 buffer 手动管理 3 个 UB buffer(inQue/outQue/tmpBuf),使用 TQue 保证生产者-消费者安全 AscendC 需要手动管理 UB 内存和流水线同步
小 innerDim 处理 auto_schedule 自动处理 Gather 指令元素级重排,绕过 32B 对齐限制 针对 innerDim < 32B 场景的专项优化,解决 DMA 启动开销远大于数据传输的问题
大 innerMult 处理 auto_schedule 自动处理 UB 内倍增(innerDim→2×→4×→...→bufElems)后大块写出 减少 GM 写次数,从 O(innerMult) 降为 O(log(bufElems/innerDim) + outputInnerDim/bufElems)
多核利用 TVM auto_schedule 自动多核 自适应分核:按 outerCount 均分 或 按 mult 维度细粒度切分 针对 outerCount 不足场景(如 1D 大 mult),按 mult 维度切分可充分利用多核

3.3 支持硬件

硬件平台 芯片型号 说明
Atlas A2 系列产品 Atlas A2 系列 与算子任务书要求一致

3.4 算子约束限制

约束项 说明
维度限制 输入张量维度 1-8 维
数据格式 仅支持 ND 格式
不支持的数据类型 int64、double(任务书允许暂不支持)
广播操作 暂不支持(任务书允许)
multiples 值 各维度 multiples 须为正整数(>= 1)
元素总数 输入和输出的总元素数须在 int32 范围内(<= 2^31 - 1)

四、特性交叉分析

特性 影响分析
动态 shape 支持。Tiling 参数在 Host 侧动态计算,kernel 侧根据 tiling 数据动态处理。支持 dynamicShapeSupport 和 dynamicRankSupport
多数据类型 支持。通过 C++ 模板化实现,12 种数据类型共用一套 kernel 逻辑,编译时为每种类型生成独立的 kernel 二进制
多核并行 支持。自适应分核策略确保多核场景下负载均衡
数据对齐 支持。kernel 内自动处理对齐/非对齐数据,对齐数据走快速路径(DataCopy),非对齐数据走精确路径(DataCopyPad)

五、可维可测分析

5.1 精度标准 / 性能标准

精度标准:

Tile 为纯数据搬运算子(无浮点计算),精度误差恒为 0。具体验收标准:

测试覆盖:12 种数据类型 × 13 种 shape = 156 组,全部通过。
aclnn 端到端验证:8 种类型 × 12 种 shape = 96 组,通过 torch_npu tensor.repeat() 全部通过。

数据类型 精度要求 实测 max_diff 测试用例数 结论
float32 ≤ 1e-4 0 13 case 达标
float16 ≤ 1e-3 0 13 case 达标
bfloat16 ≤ 1e-3 0 13 case 达标
int32/int16/int8/uint8/uint16/uint32/uint64 exact (0) 0 91 case (7×13) 达标
bool exact (0) 0 13 case 达标
complex64 ≤ 1e-4 0 13 case 达标

性能标准:

使用所有核计算时,性能不低于原有 TBE 算子的 95%。

测试环境:Atlas A2 系列, blockDim=24, warmup=50, repeat=200

6 种数据类型 × 4 个基准场景 = 24 组对比,全部达标(≥ 95%)

数据类型 large_2D (1024²)×(2,2) large_3D (256²×4)×(2³) broadcast (1×1024)×(1024,1) 1D_repeat (128)×(8192)
float32 TBE:18.6µs AC:16.8µs 111% TBE:19.8µs AC:19.4µs 102% TBE:19.1µs AC:7.9µs 241% TBE:18.9µs AC:7.9µs 238%
float16 TBE:19.4µs AC:12.1µs 161% TBE:19.6µs AC:18.9µs 104% TBE:19.1µs AC:7.9µs 241% TBE:18.9µs AC:7.9µs 238%
int32 TBE:19.2µs AC:16.7µs 115% TBE:19.9µs AC:19.3µs 103% TBE:19.0µs AC:7.9µs 242% TBE:18.9µs AC:8.0µs 236%
int16 TBE:19.5µs AC:11.9µs 165% TBE:19.7µs AC:18.6µs 106% TBE:19.2µs AC:8.2µs 235% TBE:18.9µs AC:8.1µs 234%
int8 TBE:18.7µs AC:9.5µs 196% TBE:35.3µs AC:8.3µs 423% TBE:18.4µs AC:7.9µs 233% TBE:17.8µs AC:7.9µs 226%
uint8 TBE:18.0µs AC:9.8µs 184% TBE:35.4µs AC:8.6µs 412% TBE:17.8µs AC:7.9µs 227% TBE:18.0µs AC:7.8µs 232%

最低比率 102%(float32 large_3D),最高 423%(int8 large_3D),24 组全部 ≥ 95%。

likedislike
刘十一刘十一
3月6日 关联了pull request:【社区任务】AscendC实现tile算子开发任务贡献
刘十一刘十一
3月7日 关联了pull request:【社区任务】AscendC实现tile算子开发任务贡献
sunchun成员
3月9日 评论:

/assign @ElevenLiu

likedislike
CANN-robotCANN-robot成员
3月9日 将 ElevenLiu 设为负责人
sunday成员
3月19日 评论:

/assign @ElevenLiu

likedislike
CANN-robot
CANN-robot成员
3月19日 评论:

Notice

This issue is already assigned to ElevenLiu. Please do not assign repeatedly.

likedislike
sunday成员
3月19日 评论:

/assign @ElevenLiu

likedislike
CANN-robot
CANN-robot成员
3月19日 评论:

Notice

This issue is already assigned to ElevenLiu. Please do not assign repeatedly.

likedislike
刘十一刘十一
4月24日 关联了pull request:【社区任务】02-03 Tile算子开发任务贡献
刘十一刘十一
6月8日 issue状态由 进行中 改变为 已完成
刘十一刘十一
6月8日 关闭了 issue