for each output/index linear tile: # 按照 [s4,s5,s6,s7] 拉平后的轴分核分块
# 无 reduce 时,tile 覆盖一段 output/index 线性空间
# 有 reduce 时,tile 覆盖完整 reduce 内轴
# IndexPreScalar/GatherScalar/OutputPostScalar 为 SIMT API 调用或是 scalar 运算
AscendC::Simt::VF_CALL {
for each lane element in tile:
index_value = IndexPreScalar(index_gm[output_pos])
value = GatherScalar[input_pos]
value = OutputPostScalar(value)
if has_reduce:
reduce_input[lane_id] = value
else:
output_gm[output_pos] = value
}
if has_reduce:
sync()
output_gm[reduced_pos] = ReduceCall(reduce_input)
input full space: [s0, s1, s2, s3]
output/index full space: [s4, s5, s6, s7]
schedule linear: [s4, s5, s6, s7] -> indirect_load_linear
index/output scalar: one lane -> one output element
SIMT 模板将 output/index 线性化后按 lane 执行,每个 lane 读取 index、计算 input GM 地址、读取 input 值并写回 output。index pre 和 output post 中可标量化的表达式在 lane 内执行;input 链路不承载 input pre 算子链,避免在逐元素 GM 访问路径中引入不匹配的向量化语义。
IndirectLoad 算子设计文档
简介
目的
本文档描述 Graph-autofusion Autofuse 组件中
IndirectLoadASCIR 算子的设计方案。IndirectLoad对应 Inductor 前端中torch.gather(input, dim, index)、index_select、embedding等可归一化为 GatherElements / elementwise index 的访存场景,面向 A5/v35 Autofuse 流程,目标是在垂直融合场景下支持多种执行模板。目标读者包括 Autofuse 开发人员、代码检视人员、测试开发人员。
范围
包含:
gather、index_select、embedding的自动融合后端处理。IndirectLoadASCIR op type。IndirectLoad的输入要求、IR 属性、模板选择信息传递和 codegen 分派。SIMD、SIMT、SK-like三类模板的适用场景、拓扑要求、schedule/tiling/codegen 交接约定。PlatformV2下的 schedule case 生成、ATT/tiling 资源传递和 AscendC codegen 设计。不包含:
背景与问题定义
PyTorch Gather 语义与前端场景
torch.gather(input, dim, index)是逐元素间接读取:输出每个位置都从index的对应位置取索引值,并沿input的dim维读取元素。index_select(input, dim, index)是沿dim维按一维index选择整块 slice,输出 shape 等于将input.shape[dim]替换为index.numel()。例如input[[2, 0], :]可表示为index_select(input, 0, [2, 0])。embedding(weight, indices)是沿weight第 0 维查表,输出 shape 为indices.shape + weight.shape[1:]。例如二维weight下,embedding(weight, [[2, 0]])会取出第 2 行和第 0 行,并在末尾保留 embedding 维度。三者可归一到间接读取语义:
gather是最通用的 elementwise index load;index_select是index仅沿一个维度变化、选择整块 slice 的特化;embedding是dim = 0且输入通常为 embedding 表的特化。在 A5 上,torch.gather 的语义对应内置算子
GatherElements,而其他两个算子对应GatherV2。gather的核心公式为:对非
dim维,index.size(d) <= input.size(d)。以
dim维为界,任何输入的 shape 都可以压成[preDim, gatherDim, postDim],下文简称为外轴、dim 轴、内轴。Inductor NPU 扩展侧会把部分间接访存模式降到
IndirectLoad,后端只消费统一的 ASCIR 表示,不区分前端来源。前端归一化后,Autofuse 只依赖
input、index、dim和 shape/stride 信息生成模板 case;模板选择不直接判断原始 PyTorch API 名称。ASCIR 定义
REG_ASC_IR(IndirectLoad) .Input("x1", "T1") // input输入 .Input("x2", "T2") // index输入 .Output("y", "T1") // 输出 .Attr<int64_t>("dim") // 表示 gather 的轴 .Attr<bool>("need_check_negative") // 是否支持对 index 的运行值做负数归一化 .Attr<bool>("need_check_bound") // 是否支持对 index 的运行值做合法性校验 .ComputeType(ComputeType::kComputeLoad) .Impl();设计目标
IndirectLoadop 表达gather、index_select、embedding语义,在指定场景下与某些算子融合。总体方案
支持的融合范围
%%{init: {'theme': 'base', 'themeVariables': { 'fontSize': '14px', 'nodeSpacing': 10, 'rankSpacing': 20}}}%% flowchart LR XLoad(("load")):::normal --> XBrc(("broadcast?")):::normal --> XEle(("elementwise*")):::normal --> IL(("IndirectLoad")):::indirect ILoad(("load")):::normal --> IBrc(("broadcast?")):::normal --> IEle(("elementwise*")):::normal --> IL IL --> OEle(("elementwise*")):::normal --> R(("reduce?")):::normal --> Store(("store")):::normal classDef normal fill:#fff3bf,stroke:#1e1e1e,color:#000,stroke-width:2px classDef indirect fill:#f9a825,stroke:#f57f17,color:#000,stroke-width:2px融合仅支持垂直方向,
*表示 0 个或多个,?表示 0 个或 1 个。Inductor场景总体架构
代码总体架构
模块间流程交互
模板方案
dim轴内部外轴分核分块,前后继算子链复用 VFCall 能力,循环内保证流水不断superkernel的思想,输入输出链复用 VFCall 能力,与 IndirectLoad 分别独立执行,通过 workspace 串接输入输出数据流与控制流
IndirectLoad的数据流包含 input、index、output 三条链路:dim维坐标。IndirectLoad结果并执行 output post。模块职责
IndirectLoad(input, index, dim)op、IR attr 和 Python 构图入口。template_type分派,生成模板专用 kernel 片段并与普通 API 调用生成流程衔接。需求分析与设计
功能需求 1:ASCIR IndirectLoad 算子
介绍
新增 ASCIR
IndirectLoadop type,表达torch.gather(input, dim, index)语义。输入
x[0]x[1]处理
输出
y[0]IR 属性
dimneed_check_negativeneed_check_boundASCIR op 注册
IndirectLoad需要在autofuse/v35/ascir/generator/ascir_builtin_ops_v2.cpp中通过REG_ASC_IR(IndirectLoad)注册,声明输入、输出、IR attr、ComputeType、dtype 约束和 v35 impl。基础注册绑定IndirectLoadAscIrAttImplV2和IndirectLoadAscIrCodegenImplV2,使图中出现该 op 时可以进入 ATT 和 codegen 流程。Python ASCIR 构图接口
pyascir是 Inductor NPU 扩展生成 Autofuse ASCIR 图的 Python 接口。Inductor NPU 扩展在 codegen 阶段生成ascir.HintGraph、ascir.ops.*、Autofuser.schedule()和Autofuser.codegen()调用,因此IndirectLoad需要通过该接口暴露给上层图生成路径。接入点包括:
autofuse/compiler/py_module/pyascir.h的REGISTERED_OPS中注册IndirectLoad。autofuse/compiler/py_module/pyascir.cpp中暴露dim等 IR attr 的 Python getter/setter。ascir.ops.IndirectLoad("indirect_load")创建节点,并设置indirect_load.attr.ir_attr.dim。前端 lowering 负责判断何时生成
IndirectLoad;Autofuse 侧保证生成后的 Python ASCIR 图可以被 schedule 和 codegen 消费。ATT / Codegen impl 绑定
基础 op 注册需要在
autofuse/v35/ascir/generator/v2_ascir_att_impl.h中声明IndirectLoadAscIrAttImplV2,并在autofuse/v35/ascir/generator/v2_ascir_codegen_impl.h中提供IndirectLoadAscIrCodegenImplV2。codegen impl 负责返回 API call 名称、API 名称、依赖头文件、节点合法性检查和必要的 tmp buffer 信息。最简构图拓扑
Inductor NPU 扩展生成
IndirectLoad时,最小 ASCIR 拓扑只包含 input/index 的Data -> Load、IndirectLoad和输出Store -> Output:核心构图代码:
indirect_load = ascir.ops.IndirectLoad("indirect_load") indirect_load.attr.sched.axis = [z4, z5, z6, z7] indirect_load.attr.ir_attr.dim = 2 indirect_load.x1 = input_load indirect_load.x2 = index_load功能需求 2:模板候选生成
介绍
IndirectLoadScheduleCaseGenerator在PlatformV2::GenerateTasks中识别 IndirectLoad 图,为 SIMD、SIMT、SK-like 生成对应 schedule task,并完成各模板需要的局部图改写和模板信息传递。输入
HintGraph/ImplGraph。处理
SIMD 图改写
改图的核心是为了减小运算量,因此如果 IndirectLoad 把输入扩大,则不改图。例如下图中
dim输出扩大时不前移:%%{init: {'theme': 'base', 'themeVariables': { 'fontSize': '14px', 'nodeSpacing': 10, 'rankSpacing': 20}}}%% graph TD A["input A [3, 4]"] --> B((Relu)) B --> G((IndirectLoad)):::indirect I["index [3, 8], dim=1"]:::indirect --> G G --> S["netoutput [3, 8]"] classDef indirect fill:#f9a825,stroke:#f57f17,color:#000输入不扩大的时候,尽可能沿 input 路前移;如果前置算子是双输入,需要复制一次 IndirectLoad,对性能可能带来影响,则停止前移:
%%{init: {'theme': 'base', 'themeVariables': { 'fontSize': '14px', 'nodeSpacing': 10, 'rankSpacing': 20}}}%% graph LR subgraph Before[变换前] direction TB A1[input A] --> D1((Add)) E1[input B] --> D1 D1 --> B1((Relu)) B1 --> C1((Abs)) C1 --> G1((IndirectLoad)):::indirect I1[index]:::indirect --> G1 G1 --> S1[netoutput] end subgraph After[变换后] direction TB A2[input A] --> D2((Add)) E2[input B] --> D2 D2 --> G2((IndirectLoad)):::indirect I2[index]:::indirect --> G2 G2 --> B2((Relu)) B2 --> C2((Abs)) C2 --> S2[netoutput] end Before -. IndirectLoad 前移 .-> After classDef indirect fill:#f9a825,stroke:#f57f17,color:#000SIMT 图改写
SIMT 的分核策略是将输出 shape 降成1维处理,轴与 input 链路不一致,会导致 input 链路上的算子断流水,因此策略是在 input 链路上无条件前移。
%%{init: {'theme': 'base', 'themeVariables': { 'fontSize': '14px', 'nodeSpacing': 10, 'rankSpacing': 20}}}%% graph LR subgraph Before[变换前] direction TB X1[input] --> A1((Abs)) A1 --> D1((Add)) S1[side input] --> D1 D1 --> G1((IndirectLoad)):::indirect I1[index]:::indirect --> G1 G1 --> C1((Relu)) C1 --> O1[output] end subgraph After[变换后] direction TB X2[input] --> G2A I2A[index]:::indirect --> G2A((IndirectLoad)):::indirect S2[side input] --> G2B I2B[index]:::indirect --> G2B((IndirectLoad)):::indirect G2A --> A2((Abs)) A2 --> D2((Add)) G2B --> D2 D2 --> C2((Relu)) C2 --> O2[output] end Before -. IndirectLoad 前移 .-> After classDef indirect fill:#f9a825,stroke:#f57f17,color:#000输出
功能需求 3:SIMD 模板生成
Kernel伪码
以
input=[s0,s1,s2,s3]、index/output=[s4,s5,s6,s7]、dim=2为例:性能特征
SIMD 模板减少 MTE 搬运,在 tile 内所有计算实现向量化,流水表现较好,理论上是性能最优的模板。
根据
index_select、embedding语义来看,前端将其转成gather之后,会有一个将低维度 input 做一个类似 broadcast 的动作,适合向量化运算,如果可以识别是这种场景,并且 cacheline 利用率较好,优选此模板。基本思路
SIMD 模板按 output/index 的外轴调度。每个 tile 先用 output 外轴坐标定位 input 的一个 UB 输入切片,再在这个输入切片上执行 input pre,最后用 index 构造 offset 并调用
AscendC::Gather。当前的机制要求一张 AscGraph 图上只能由一套循环轴,SIMD 模板按 output/index 的外轴调度,而 input 链与其不同,因此需要做一些特殊的处理。
模板要求
s0 >= s4、s1 >= s5,只计算 input 外轴中[0:s4, 0:s5]对应的输入切片,不遍历 input 外轴剩余部分。Schedule 处理
创建模板的时候,将外轴合并作为 schedule 的唯一轴,强制要求只能按外轴切分;并设置 input pre 和 output/index 保持两套坐标系:
autoschedule 对 SIMD input pre process node 做特殊跳过:
TilingGroup::GenTilingGroup()、Scheduler::TileSplit()、Scheduler::ApplyBlockSplit()不再按普通 elementwise 节点处理这条 input pre 链。这样 input pre 的 input 坐标系不会参与普通 output/index 侧分组,但 codegen 仍可从图中收集这条链路生成 VF 函数。Codegen 处理
最终 kernel 由普通 codegen 和
IndirectLoadRegApiCall::GenerateSimd()配合生成:IndirectLoad节点时,需要反推 input 链路,生成 input 链路上的 VFCall,并生成AscendC::Gather的调用。where语句。功能需求 4:SIMT 模板生成
Kernel伪码
以
input=[s0,s1,s2,s3]、index/output=[s4,s5,s6,s7]、dim=2为例:性能特征
SIMT 对 GM 直接逐元素访问,通过 DCache 机制保证读写性能,同样可以减少 MTE 的搬运,与
gather离散访问的特性更加契合。但是当子图中有
reduce时需要 SIMT/SIMD 混合编程,此外某些 elementwise 算子尚未提供 SIMT API,需要通过 scalar 表达式直接运算。基本思路
SIMT 模板直接逐元素读写 GM,有如下特点需要额外处理:
Schedule 处理
创建模板的时候,将 output/index 的全部轴合并为
indirect_load_linear,并作为唯一 schedule 轴:input Load 保留在图中作为 input GM 边界,但类似 SIMD 流程,autoschedule 也对 SIMT input Load 做一些特殊跳过处理,后续由 codegen 仍可从
IndirectLoad追溯到 input GM tensor。Codegen 处理
最终 kernel 由
IndirectLoadRegApiCall::GenerateSimt()生成单个 SIMT helper 调用:IndirectLoad输入追溯 input Load/Data,取得 input GM tensor。IndirectLoadIndexTransform和IndirectLoadOutputTransform两个__simt_callee__functor。IndirectLoadSimtExtend,由 helper 完成 index 读取、input 地址计算、input GM 读取和 output GM 写回。功能需求 5:SK-like 模板生成
Kernel伪码
以
input=[s0,s1,s2,s3]、index/output=[s4,s5,s6,s7]、dim=2为例:性能特征
该模板适合 IndirectLoad 本体分核明显不足,且前后链路独立执行后能显著提高 core 利用率的场景。对搬运的优化效果有限,收益主要来自多个小算子的头开销合并。
基本思路
SK-like 模板按 input pre、index pre、IndirectLoad、output post 四个逻辑阶段组织,在 graph case 中为阶段边界插入 workspace,并沿用现有 ScheduleGroup / ATT / Codegen 流程完成分组、tiling 和代码生成。除 IndirectLoad 阶段外,其余阶段不新增专用调度语义,复用现有普通 VF/Reduce 能力。workspace 串接方式可参考
autofuse/optimize/fused_graph/fused_graph_modifier.cpp中SubgraphConnectionsToWorkspace、ChangeStartingOutputToWorkspace的处理。input pre、index pre、output post 不内联到 IndirectLoad 阶段内部,而是分别作为独立逻辑阶段处理,并使用各自的分核分块原则。IndirectLoad 阶段只消费 workspace,按 output 外轴合轴后的范围分核分块,构造 offset 并调用
AscendC::Gather。这样 IndirectLoad 阶段的调度范围不会限制前后链路复用现有能力,物理 kernel 和 ScheduleGroup 组织由后续既有流程决定。Schedule 处理
创建模板的时候,需要在复制出的 graph case 上保留四个逻辑阶段的边界、轴和 workspace layout,并将阶段间 Data/Output 边界改写为 Workspace 边界。input pre、index pre、output post 阶段复用现有 schedule/tiling 机制;output post 阶段如果包含 reduce,reduce 的 case 生成、是否进一步拆分、tiling 和 codegen 均交由现有 reduce 能力处理。IndirectLoad 阶段作为专用阶段,单独使用 output 外轴合轴后的 schedule 轴。
input pre、index pre 和 output post 分别按自己的坐标系独立调度;各自 range 由现有 schedule/tiling 结果决定。三个阶段分别保留自己的 actual size 和 usedCore。
IndirectLoad 阶段不复用其它阶段的切分结果,只按
[s4,s5] -> indirect_load_outer后的外轴合轴分核分块。schedule 需要保证四个逻辑阶段之间的依赖顺序,并将 workspace 大小、各阶段 actual size、usedCore 和 group 间变量依赖写入模板信息。最终物理 ScheduleGroup 数量由复用能力决定,不要求与四个逻辑阶段一一对应。Codegen 处理
Codegen 不新增一套固定四段式 kernel 组织。graph case 中的 workspace 边界进入现有 ScheduleGroup 后,input pre、index pre、output post 继续复用现有 codegen 能力,IndirectLoad 阶段生成专用调用:
input_pre_range写入workspace_input。index_pre_range写入workspace_index。workspace_input和workspace_index读取数据,调用AscendC::Gather后写入workspace_gather。workspace_gather的output_post_range读取数据并写回 output GM。功能需求 6:模板选择
介绍
IndirectLoad同时生成 SIMD、SIMT、SK-like 三类候选模板,生成对应打分函数,打分函数随ScheduleTask::score_func传递给 ATT/tiling。ATT/tiling 阶段执行打分函数,根据当前实际 shape、blockDim和资源信息决定候选模板优先级。判定依据
模板选择使用两个判定量:
dim之前各轴的 repeat 乘积input 连续轴判定不依赖
dim位置,只看 input stride 是否连续:index 外轴分核规模按
dim之前的轴计算:模板选择规则
continuous_bytes >= cacheline_size * Cache_thres且index_outer_product >= block_dim * Core_threscontinuous_bytes >= cacheline_size * Cache_thres且index_outer_product < block_dim * Core_thresSIMD 适合 input 连续搬运收益足够、IndirectLoad 本体分核也足够的场景。SK-like 适合 input 连续搬运收益足够,但 IndirectLoad 本体分核不足、前后链路独立分核更有收益的场景。SIMT 作为兜底模板,覆盖连续访问不足、shape 不规则或资源条件不满足的场景。
打分函数生成
IndirectLoadScheduleCaseGenerator在生成 SIMD、SIMT、SK-like 候选 graph case 时,同时生成对应score_func。每个候选 case 对应一个打分函数:打分函数通过
ScheduleTask::score_func传递给 ATT/tiling。ATT/tiling 根据实际tiling_data.block_dim、shape 和模板资源情况执行打分,再选择候选模板。数据流
阈值暂定如下:
Cache_thresCore_thres软件设计
设计原则
IndirectLoad的语义信息和模板实现信息需要解耦。前端图只表达gather(input, dim, index)语义,不携带 SIMD、SIMT、SK-like 的执行策略;模板生成阶段根据 shape、stride、拓扑和资源约束生成候选模板,并把模板内部信息传递给后续 schedule、ATT、codegen 和 runtime 路径。三类模板之间保持候选独立性。任一模板因拓扑、资源或 codegen 约束被否决时,不影响其它模板继续参与选择。模板选择不在图构造阶段固化,而是在 tiling 和运行期资源信息可用后,通过 score func、资源公式和 tiling case 共同决定。
模板元数据只作为 Autofuse 内部信息流转,不改变
IndirectLoad的 ASCIR 语义。template_type、SIMT dcache reserve、SK-like 阶段边界和 workspace layout 等信息用于候选模板分派、资源计算和代码生成,不作为用户可感知的算子语义。关键数据模型
IndirectLoad 语义信息
语义信息包含
input、index、dim、rank、shape、stride 和 dtype,用于描述gather的数学含义。该信息在三类模板中保持一致,模板只能改变执行方式,不能改变输出 shape、索引解释和异常边界。模板选择信息
模板选择信息描述候选模板类型、适用条件和打分函数。SIMD、SIMT、SK-like 分别生成独立候选;每个候选保留自己的拓扑约束、schedule 轴、资源需求和否决条件。打分函数随候选模板进入 ATT/host tiling,用于在实际 shape、
blockDim和资源公式可用时选择优先级。模板资源信息
模板资源信息描述 UB、workspace、tmp buffer、dcache reserve、actual size 和 usedCore 等约束。静态阶段只生成资源公式或资源上界,动态 shape 下的真实资源值由 host tiling 代入实际 shape 和 tiling case 后计算。资源不足时应否决对应 tiling case 或候选模板,而不是生成越界 kernel。
SK-like 阶段信息
SK-like 阶段信息描述 input pre、index pre、IndirectLoad、output post 四个逻辑阶段的边界、workspace layout、阶段间数据依赖和范围映射。四个阶段是逻辑执行边界,不要求与物理
ScheduleGroup或 kernel 组织一一对应。最终执行顺序、workspace 分配和数据可见性由现有 ScheduleGroup / ATT / Codegen / Runtime 流程共同保证。模块职责
IndirectLoad(input, index, dim)语义,保留输入、索引和输出关系。模板设计
SIMD 模板
SIMD 模板以 output/index 外轴作为主 schedule 轴。每个 tile 根据 output 外轴坐标确定 input 输入切片,将 input slice 搬入 UB 后执行 input pre,再由 index 构造
AscendC::Gather所需 offset,最后执行 output post 和可选 reduce。该模板的边界是 input slice、offset tmp 和中间结果必须满足 UB 资源约束。连续 input slice 搬运、tile 内复用和
AscendC::Gather能被流水覆盖时,SIMD 优先级最高;若 input slice 或 tmp buffer 超资源,或者外轴分核不足导致 core 利用率较低,则对应 tiling case 应被否决或降级。SIMT 模板
SIMT 模板将 output/index 线性化后按 lane 执行,每个 lane 读取 index、计算 input GM 地址、读取 input 值并写回 output。index pre 和 output post 中可标量化的表达式在 lane 内执行;input 链路不承载 input pre 算子链,避免在逐元素 GM 访问路径中引入不匹配的向量化语义。
该模板依赖 SIMT GM 访问、DCache reserve 和访存合并能力改善离散访问性能。index 分布具有局部性、lane 访问能落到较少 cacheline 时收益较好;若访问完全离散或 SIMT API 无法覆盖子图表达式,则只能作为通用兜底模板或被对应约束否决。reduce 场景需要与现有 SIMD reduce 能力协同,tiling 时必须保证 reduce 内轴完整性。
SK-like 模板
SK-like 模板面向 IndirectLoad 本体分核不足、但前后链路独立执行后可以提升 core 利用率的场景。模板将融合子图拆成 input pre、index pre、IndirectLoad、output post 四个逻辑阶段,通过 workspace 边界串接阶段间数据,并复用现有 VF/Reduce 的 schedule、ATT 和 codegen 能力。
该模板的收益来自阶段独立分核分块后提升前后链路并行度,代价是额外 workspace 写读和阶段间依赖管理。workspace 搬运进入 critical path 或 workspace size 超资源时,不应优先于 SIMD/SIMT。SK-like 不引入固定四段式 kernel 组织,物理
ScheduleGroup数量和 kernel 组织由现有流程根据 graph case 和 schedule 结果决定。数据流
错误处理和否决策略
Schedule 阶段
Schedule 阶段负责否决语义或拓扑不满足的候选模板。rank、dim、shape、stride、dtype 不合法时,整个 IndirectLoad 模板生成失败;单个模板的拓扑约束不满足时,只否决该模板,不影响其它模板继续生成。
SIMD 需要确认 input slice 能由 output 外轴唯一定位,且 input pre、index pre、output post 的执行空间边界清晰。SIMT 需要确认 input 链路不存在必须在 input 空间执行的 pre 算子链,index pre 和 output post 可转成 SIMT 表达式或标量表达式。SK-like 需要确认四个逻辑阶段可以通过 workspace 边界串接,并能保持数据依赖顺序。
ATT/host tiling 阶段
ATT/host tiling 阶段负责资源合法性判断。SIMD 的 input slice、offset tmp 和中间 UB buffer 超资源时,否决对应 tiling case。SIMT 的 dcache reserve、lane 访问范围和 reduce 内轴完整性不满足时,否决对应 tiling case。SK-like 的 workspace size、阶段 actual size、usedCore 或 group 间依赖不一致时,否决对应 tiling case。
动态 shape 场景下,静态阶段不直接使用未知 shape 做最终判断,只生成可代入的资源公式和 score func;host tiling 使用真实 shape 和 tiling case 计算实际资源,保证 kernel launch 前完成合法性判断。
Codegen 阶段
Codegen 阶段负责校验模板元数据与 schedule 结果一致。若模板类型缺失、tiling data 缺失、workspace layout 不一致、tmp buffer size 与资源公式不一致,或 SIMT helper 无法表达目标子图,应返回错误并记录清晰日志。Codegen 不应在发现资源不合法时生成依赖未定义行为的 kernel。
非功能需求
可维护性
template_type分派,避免在普通 Gather/Load 路径中散落 IndirectLoad 特判。可测试性
可移植性
非 A5 平台不进入该路径。
可靠性
性能
编译时长
新增 schedule task、ATT 和 codegen 分支会增加编译期图遍历、tiling 代码生成和字符串生成成本。应限制在包含 IndirectLoad 的图中触发,不影响普通图路径。
执行性能
AscendC::Gather减少逐元素 GM 读取。内存和产物大小
SIMD 增加 UB offset tmp;SK-like 增加 workspace;SIMT 增加 dcache reserve。SK-like 的 workspace size 由阶段串接边界决定,host tiling 需要写入 workspace size,并保证后续 codegen 和 kernel 消费一致。新增 codegen/helper 会增加少量动态库和头文件产物大小。
特性交叉影响
libascendsk.so和 AOT 接口。AscendC::Gather,SIMT 逐元素计算 input GM 地址并读取 input。安全与兼容性
安全检查
ABI/API 兼容性
Gather、Load、GatherToLoadPass行为。图改写确定性
模板 graph case 从复制图中重新获取 IndirectLoad 节点,避免复用原图节点指针。图改写依赖明确的 axis、node 拓扑和端口关系,不依赖无序容器遍历结果生成最终结构。
Runtime 生命周期约束
SIMD 路径遵循普通 UB queue 生命周期。SIMT 路径跳过普通 UB output 生命周期,但 input/index/output GM 指针必须在 helper 调用期间保持有效。SK-like 路径通过 workspace 边界串接四个逻辑阶段,workspace 生命周期、group 间变量依赖和数据可见性由现有 ScheduleGroup / ATT / Runtime 约束保证。
测试设计
测试边界
测试覆盖 ASCIR 构图、schedule case、template type 写入、tiling/codegen 分派、SIMD/SIMT/SK-like kernel 文本和设备侧正确性。
最简拓扑用于模拟前端直接构造
IndirectLoad:没有 input pre、index pre 和 output post,只保留 input/index 的Data -> Load、IndirectLoad、Store -> Output。from pyautofuse import ascir, Autofuser, AutofuserOptions ascir.utils.set_platform("3510", 1, 245760) graph = ascir.HintGraph("indirect_load_minimal_graph") s0, s1, s2, s3 = [graph.create_size(f"s{i}") for i in range(4)] s4, s5, s6, s7 = [graph.create_size(f"s{i}") for i in range(4, 8)] z0 = graph.create_axis("z0", s0) z1 = graph.create_axis("z1", s1) z2 = graph.create_axis("z2", s2) z3 = graph.create_axis("z3", s3) z4 = graph.create_axis("z4", s4) z5 = graph.create_axis("z5", s5) z6 = graph.create_axis("z6", s6) z7 = graph.create_axis("z7", s7) x = ascir.ops.Data("x", graph) index = ascir.ops.Data("index", graph) y = ascir.ops.Output("y", graph) x.attr.ir_attr.index = 0 x.y.dtype = ascir.dtypes.float16 x.y.axis = [z0, z1, z2, z3] x.y.size = [s0, s1, s2, s3] x.y.strides = [s1 * s2 * s3, s2 * s3, s3, ascir.SizeExpr(1)] index.attr.ir_attr.index = 1 index.y.dtype = ascir.dtypes.int32 index.y.axis = [z4, z5, z6, z7] index.y.size = [s4, s5, s6, s7] index.y.strides = [s5 * s6 * s7, s6 * s7, s7, ascir.SizeExpr(1)] input_load = ascir.ops.Load("input_load") input_load.attr.ir_attr.offset = ascir.SizeExpr(0) input_load.attr.sched.axis = [z0, z1, z2, z3] input_load.x = x input_load.y.dtype = ascir.dtypes.float16 input_load.y.axis = [z0, z1, z2, z3] input_load.y.size = [s0, s1, s2, s3] input_load.y.strides = [s1 * s2 * s3, s2 * s3, s3, ascir.SizeExpr(1)] index_load = ascir.ops.Load("index_load") index_load.attr.ir_attr.offset = ascir.SizeExpr(0) index_load.attr.sched.axis = [z4, z5, z6, z7] index_load.x = index index_load.y.dtype = ascir.dtypes.int32 index_load.y.axis = [z4, z5, z6, z7] index_load.y.size = [s4, s5, s6, s7] index_load.y.strides = [s5 * s6 * s7, s6 * s7, s7, ascir.SizeExpr(1)] indirect_load = ascir.ops.IndirectLoad("indirect_load") indirect_load.attr.sched.axis = [z4, z5, z6, z7] indirect_load.attr.ir_attr.dim = 2 indirect_load.x1 = input_load indirect_load.x2 = index_load indirect_load.y.dtype = ascir.dtypes.float16 indirect_load.y.axis = [z4, z5, z6, z7] indirect_load.y.size = [s4, s5, s6, s7] indirect_load.y.strides = [s5 * s6 * s7, s6 * s7, s7, ascir.SizeExpr(1)] store = ascir.ops.Store("store") store.attr.sched.axis = [z4, z5, z6, z7] store.attr.ir_attr.offset = ascir.SizeExpr(0) store.x = indirect_load store.y.dtype = ascir.dtypes.float16 store.y.axis = [z4, z5, z6, z7] store.y.size = [s4, s5, s6, s7] store.y.strides = [s5 * s6 * s7, s6 * s7, s7, ascir.SizeExpr(1)] y.x = store y.attr.ir_attr.index = 0 y.attr.sched.axis = [z4, z5, z6, z7] y.y.dtype = ascir.dtypes.float16 y.y.axis = [z4, z5, z6, z7] y.y.size = [s4, s5, s6, s7] y.y.strides = [s5 * s6 * s7, s6 * s7, s7, ascir.SizeExpr(1)] options = AutofuserOptions(tiling_lib_path="", tiling_lib_codegen_symbol="") fuser = Autofuser(options) impl_graphs = fuser.schedule(graph) assert impl_graphs tiling_def, host_tiling, op_kernel = fuser.codegen(impl_graphs) assert tiling_def assert host_tiling assert op_kernel测试用例设计
AscendC::Gather代码测试命令
cmake --build build --target aihac_codegen -j 8 PYTHONPATH=build/autofuse/compiler/py_module:$PYTHONPATH python3 autofuse/tests/ut/ascendc/compile/indirect_load_simd.py PYTHONPATH=build/autofuse/compiler/py_module:$PYTHONPATH python3 autofuse/tests/ut/ascendc/compile/indirect_load_simt.py sh build.sh -u --module=autofuse_framework -j 8 sh build.sh -s --module=autofuse_e2e -j 8验收标准
设计文档检查结果