graph-autofusion V2 节点性能建模实战:基于 af-perf-modeler 的 Cast/Reduce/Compare 算子性能公式推导指南
2026/9/19 6:49:50 网站建设 项目流程

graph-autofusion V2 节点性能建模实战:基于 af-perf-modeler 的 Cast/Reduce/Compare 算子性能公式推导指南

【免费下载链接】graph-autofusionGraph-autofusion 是一个面向昇腾(Ascend)芯片的轻量级、解耦式组件集合,旨在通过自动融合技术加速模型执行。 目前已开源 SuperKernel 组件和 Autofuse 组件,未来将持续开放更多自动融合相关模块。项目地址: https://gitcode.com/cann/graph-autofusion

af-perf-modeler是 graph-autofusion(CANN 昇腾芯片自动融合组件库)内置的 V2 节点性能建模 SKILL,面向 Cast、Reduce、Compare 等算子的性能公式建模、ATT 性能分析、MicroAPI 成本统计以及 codegen 与性能公式对齐场景。本文以该 SKILL 的完整方法论为主线,结合仓库内 codegen 实现、ATT 性能表和参数透传链路的真实源码,系统讲解如何从 AscendC/MicroAPI 源码与 codegen 实际传参反推性能公式,并给出可复用的建模输出模板与自查清单。

一、Skill 定位:graph-autofusion 的 V2 节点性能建模入口

在 graph-autofusion 仓库中,.claude/skills/af-perf-modeler/目录下包含两个文件:README.md 与 SKILL.md。其中 README 给出了 Skill 的定位、功能范围和验证方法,SKILL.md 则承载了完整的建模方法论,两者共同构成 af-perf-modeler 的完整能力描述。

该 Skill 覆盖三类典型工作:

  • 算子性能公式建模:为 Cast、Reduce、Compare 等算子建立可解释、可追溯的性能公式;
  • ATT 性能建模与 MicroAPI 成本分析:基于 ATT(Auto Tiling Tool,自动切分工具)的 VF(Vector Function)性能管线,统计repeat_time/call_count等循环执行参数并累加 MicroAPI 成本;
  • codegen 与性能公式对齐、tiling 参数透传:确保 ATT 性能计算拿到的参数与 codegen 生成代码时实际传给 AscendC API 的参数一一对应。

触发场景

当用户提出以下诉求时,即进入 af-perf-modeler 的作用域:

  • 提到 Cast / Reduce / Compare 等算子的性能公式;
  • 提到 ATT 性能建模、MicroAPI 成本、repeat_time/call_count
  • 提到 codegen 与性能公式对齐、tiling 参数透传。

核心原则

Skill 明确了一条不可妥协的建模红线:性能公式必须从节点对应的 AscendC/MicroAPI 源码和 codegen 实际传参反推,不能只套现有公式或凭经验估算。任何源码入口、API 参数、codegen 传参、分支含义或 MicroAPI 语义不确定的点,都必须先向用户说明并询问,严禁编造公式。

二、建模前提:先定位五类核心源码文件

建模开始前,必须先定位并读取以下文件(SKILL.md 中列出的建模前提,路径均以仓库根目录为基准):

建模要素仓库路径作用
codegen implv2_ascir_codegen_impl.h每个 V2 节点类型对应一个AscIrCodegenImplV2子类,通过GetApiName()/GetApiCallName()指明 API 与调用生成器
AscendC API 源码autofuse/v35/ascendc/api_regbase/GetApiName()指向的算子真实实现及其调用的 helper
codegen API 调用生成逻辑autofuse/v35/codegen/GetApiCallName()指向的{ApiCallName}::Generate,决定生成代码实际传给 API 的参数
性能表perf_param_v2.cppkVfInstructPerfTable等性能表,MicroAPI 到 latency / throughput 的映射
当前性能注册/实现ascir_api_perf_v2.cpp、ascendc_regbase_perf.cpp性能公式的最终落地实现
参数透传参考ascir_node_param.h 中的VectorFuncNodeParamsCastNodeParamscodegen 向 ATT 透传参数的载体

以 Cast 为例,在 v2_ascir_codegen_impl.h 中可以看到CastAscIrCodegenImplV2类:GetApiCallName()返回"CastV2ApiCall"GetApiName()返回"CastExtend",并实现了IsVectorFunctionSupportedIsSimtScalarSupported等判定逻辑,用于决定节点走 VF 融合还是 SIMT 标量路径。建模时需据此追溯到真实 API 实现。

三、通用建模流程:八步从源码反推性能公式

1. 定位算子源码入口

v2_ascir_codegen_impl.h找到目标算子对应的GetApiName(),再到autofuse/v35/ascendc/api_regbase/下查找该 API 名称的定义。读取 API 实现及其调用的 helper,不只读 perf 代码,因为性能公式的分支与 MicroAPI 序列完全由源码决定。若 API 名称与源码实现无法唯一对应,先询问用户确认。

2. 确认源码输入参数与 codegen 实际传参

  • 先分析 AscendC API 源码的函数签名,确认参数含义、类型、顺序和模板参数;
  • v2_ascir_codegen_impl.h找到目标算子的GetApiCallName(),据此在autofuse/v35/codegen/下找到{ApiCallName}::Generate
  • Generate中分析生成代码实际传入 AscendC API 的参数,包括inputsoutputs、scalar、offset、stride、mask、loop 参数、tmp buf、axis 信息等。

一个必须警惕的语义陷阱:codegen 的inputs/outputs是生成代码用的Tensor,而 ATT 性能计算的input_shapes/output_shapes是性能建模用的TensorShapeInfo,两者语义对应但不是同一对象,建模时不可混用。

3. 必要参数从 codegen 传递到 ATT

如果性能公式需要 codegen 阶段才能确定的参数(例如 loop merge 后的cal_countouter_repeats、stride、mask mode、特殊分支标记),禁止在 ATT 中凭空假设。参数传递遵循一条标准链路:

EnrichAscirGraphNodeParams 预注册空参数结构体 -> {ApiCallName}::Generate 按实际调用填充具体值 -> FillSpecificParams 提取到 NodeInfo -> perf 函数读取

这条链路在仓库中有完整实现:

  • 预注册入口:ascir_param_builder.h 声明EnrichAscirGraphNodeParams(const af::AscGraph &graph)
  • 参数载体:ascir_node_param.h 中AscirNodeParams::specific_params是一个std::variant,可承载ReduceNodeParamsVectorFuncNodeParamsCastNodeParamsBroadcastNodeParamsCompareNodeParamsWhereNodeParamsUnaryBitWidthChangeNodeParamsTransposeNodeParams等专用结构体;
  • 提取入口:specific_params_builder.h 声明FillSpecificParams(const af::AscNodePtr &ge_node, NodeInfo &node_info)

以 Cast 为例,cast_v2_api_call.cpp 中的FillCastNodeParamsoutput_dimsoutput_stridesinput_strides写入CastNodeParams;该结构体在 ascir_node_param.h 中定义,字段与 SKILL 中强调的"保存output_dimsoutput_stridesinput_strides,仅排除dstsrc"完全对应。

透传时的两条硬性要求:

  • 只预注册空结构:对只能在Generate中确定的参数(如ApiLoopParams、合轴后实参、实际生成分支),EnrichAscirGraphNodeParams只负责预注册空结构体,不要在预注册阶段猜具体值
  • 保存原始符号表达式Generate填充给 ATT 的节点参数必须使用 codegen/merge 阶段的原始符号参数(如merge_infoApiLoopParams中的 repeat、stride、count),不要保存tpipe.tiler.ActualSize(...)tpipe.tiler.Size(...)等用于生成 C++ 调用字符串的展开表达式,因为 ATT 性能公式无法稳定识别这些 tiler 表达式。

这一点在 Cast 的生成代码中有直观对照:CastV2ApiCall::Generate在生成 API 调用字符串时使用tpipe.tiler.Size(param.output_second_to_last_stride)(见 cast_v2_api_call.cpp),而写入节点参数时则使用merge_info.merge_repeatsparam.cal_countparam.output_second_to_last_stride等原始符号(见同文件 L87-L106),两者用途严格分离。

4. 分析源码分支

保持与源码if constexprif、模板参数、dtype 特化、dim 分支、broadcast 分支、scalar 分支、mask mode 分支一致。每个源码分支都要说明触发条件、codegen 参数来源和公式差异,不允许只按 dtype 或经验简化掉源码中的关键分支。

以 Cast 的 VF 支持判定为例,v2_ascir_codegen_impl.h 中的IsVectorFunctionSupported就体现了典型的源码分支约束:Cast 只能处理 2 倍及以内位宽变化(input_dtype_size > output_dtype_size * 2U时拒绝 VF 融合);8 字节与 4 字节之间的转换在 MicroAPI 层要求源/目的使用双寄存器,VF codegen 只生成单寄存器张量会导致设备上仅填充半数 lane(真机验证约 50% mismatch),因此该组合也必须拒绝 VF 融合而保留在根图走CastExtend。这些分支直接影响建模时是否将节点计入 VF 性能管线。

5. 枚举 MicroAPI

以源码中的每个MicroAPI::xxx或 AscendC 基础向量 API 作为最小建模粒度,将其映射到perf_param_v2.cpp中的kVfInstructPerfTable类型。映射前必须查表确认,不要假设表项存在;找不到精确匹配时使用kPlaceholder,并在代码注释中说明真实 MicroAPI 名称和暂用原因。

kVfInstructPerfTable在 perf_param_v2.cpp 中定义,其中既有kAddkSubkCastkCompareScalarNEkUpdateMaskkDuplicatekPlaceholder等指令级条目(每条目给出支持的 dtype 集合及 latency、throughput 数值),也有kCast的 dtype 映射表kVfInstructDtypeMappingPerfTable(见 L465-L484),用于处理需要按输入/输出类型分别判断的场景。

6. 计算 MicroAPI 执行次数

  • 每个源码中的 MicroAPI 都应能追溯到性能公式中的一次VfPerfUtils::AddVfInstructPerf计算,其最后一个参数必须来自源码循环次数或 codegen 透传参数
  • 对嵌套循环计算总次数,例如outer_count * repeat_time
  • 对只在循环外执行一次的Duplicate、mask 创建、scalar 初始化等,次数按源码实际执行次数建模,不要误乘repeat_time
  • 如果循环次数依赖运行时数据值,必须说明无法精确获取的原因,并采用保守估计或要求透传参数。

7. 合并同类 MicroAPI

同一种 MicroAPI 类型可以只调用一次AddVfInstructPerf,最后一个参数使用该 MicroAPI 在当前分支中的总执行次数,合并时必须保留注释或表格追溯每一部分次数的源码位置。合并受以下约束:

  • kPlaceholder,合并键必须包含真实 MicroAPI、操作方向/模式和 dtype,三者都相同才能合并;
  • 即使性能表类型相同,也禁止合并不同真实 MicroAPI(如NotShiftRightsPackUnPack);
  • MicroAPI::DataCopy的 load 与 store 必须分开,不能因为都映射为kPlaceholder而汇总;
  • kPlaceholder直接调用VfPerfUtils::AddVfInstructPerf不要增加AddPlaceholderPerf一类包装函数

8. 套用统一 cost 结构

每个 MicroAPI 通过VfPerfUtils::AddVfInstructPerf(type, dtype, max_latency, all_vf_instruct_cost, count)累加;内部查询 latency 和 throughput,latency 取所有 MicroAPI 的最大值,throughput * count累加到all_vf_instruct_cost。最终公式固定为:

Expr res = VfPerfUtils::GetVFHeadCost() + max_latency + all_vf_instruct_cost; res.Simplify(); perf.pipe_res[PipeType::AIV_VEC] = res;

VfPerfUtils的接口定义见 vf_perf_utils.h,包括AddVfInstructPerfGetVfInstructDtypeMappingPerfGetVFHeadCostGetVectorFunctionPerf等。这套公式结构在 ascendc_regbase_perf.cpp 中有真实落地:res = VfPerfUtils::GetVFHeadCost() + max_latency + all_vf_instruct_cost; perf.pipe_res[PipeType::AIV_VEC] = res;。部分场景(如 mask 相关路径)还会用call_count整体加权:res = (VfPerfUtils::GetVFHeadCost() + max_latency + all_vf_instruct_cost) * call_count;(见同文件 L205-L207),这印证了repeat_time / call_count分析在 Skill 中的重要性。

四、API 参数对齐原则与透传链路

性能模型透传的参数必须与实际 AscendC API 调用保持一一对应:

  • 排除输入、输出 Tensor 后,按 API 签名的原始顺序保存其余参数;不要用repeat_countflattened_count或分支标志替换原始参数;
  • 参数的维度、stride、mask、offset 和 loop 数组要保持与生成代码相同的语义粒度,性能模型只在计算公式内部派生临时量;
  • CastExtend(dst, src, output_dims, output_stride, input_stride)为例,节点参数应保存output_dimsoutput_stridesinput_strides,仅排除dstsrc
  • CastExtend节点参数中的output_dimsoutput_stridesinput_strides应来自merge_info/ApiLoopParams的原始表达式;实际生成 API 调用时可以使用tpipe.tiler,但传给性能公式的节点参数不能使用 tiler 展开结果;
  • 参数链路必须可追溯:EnrichAscirGraphNodeParams预注册 →{ApiCallName}::Generate按实际调用填充 →FillSpecificParams提取 →NodeInfo→ perf 函数;
  • 如果原始 API 参数缺失,必须明确回退到 shape 信息的条件和精度影响;禁止在 ATT 中凭经验重建 codegen 已经确定的参数

在仓库测试中可以看到这条链路的完整验证方式,例如 test_ascir_node_params.cpp 覆盖了节点参数结构体的构建与读取,ascir_reduce_test_helpers.h 展示了EnrichAscirGraphNodeParams(env.graph)att::FillSpecificParams(env.node, node_info)的串联调用,为参数透传链路提供了可执行的测试佐证。

五、MicroAPI 映射规则

一般情况下,性能表类型为 MicroAPI 指令名增加k前缀,例如MicroAPI::Add对应kAddMicroAPI::UpdateMask对应kUpdateMask。Skill 不维护完整的一一映射表,建模时按以下顺序确认:

  1. 提取真实 MicroAPI 指令名,以k + 指令名作为候选性能表类型;
  2. perf_param_v2.cpp和常量定义中确认候选类型真实存在,并确认目标 dtype 受支持;不能只根据命名猜测
  3. 对带模式语义的模板 API,使用性能表中对应的语义后缀,例如CompareScalar<..., CMPMODE::NE>对应kCompareScalarNE(该条目确实存在于 perf_param_v2.cpp);
  4. 如果没有精确表项,使用kPlaceholder,并按真实 MicroAPI、操作方向/模式和 dtype 分开调用;
  5. DataCopy的 load 与 store 即使都没有精确表项,也必须分开建模并分别注释;同一次 load 或 store 的LoadDist/StoreDist模式不额外重复建模;
  6. 如果性能表已有对应 MicroAPI 类型但暂不支持源码中的真实 dtype,仍按源码真实 dtype 调用该 MicroAPI 类型,不要为了命中现有表项改成中间 dtype、输出 dtype 或kPlaceholder;性能表缺项应后续扩展,公式必须先保持源码语义正确。

MicroAPI::DataCopy的统一建模约定:load 使用 input dtype,store 使用 output dtype,LoadDistStoreDist、pack/unpack 模式只作为注释说明,不因模式变化额外增加一条 DataCopy 成本。

六、Placeholder 调用示例:占位与可追溯性

当源码 MicroAPI 在性能表中没有精确条目时,占位调用必须保持"每一项都可追溯到唯一真实操作",下面是对比示例(摘自 SKILL.md 并保留原文语义)。

错误做法:不同真实操作或不同方向被汇总,后续无法替换为精确表项。

VfPerfUtils::AddVfInstructPerf(kPlaceholder, dtype, max_latency, all_vf_instruct_cost, repeat_time * 4);

正确做法:直接调用AddVfInstructPerf,每个占位项都能追溯到唯一真实操作。

// MicroAPI::DataCopy (load). VfPerfUtils::AddVfInstructPerf(kPlaceholder, input_dtype, max_latency, all_vf_instruct_cost, repeat_time); // MicroAPI::DataCopy (store). VfPerfUtils::AddVfInstructPerf(kPlaceholder, output_dtype, max_latency, all_vf_instruct_cost, repeat_time); // MicroAPI::Pack<uint32_t, int64_t>, two calls with the same mode and dtype. VfPerfUtils::AddVfInstructPerf(kPlaceholder, int64_dtype, max_latency, all_vf_instruct_cost, repeat_time * 2); // MicroAPI::UnPack<uint64_t, uint32_t>. VfPerfUtils::AddVfInstructPerf(kPlaceholder, uint32_dtype, max_latency, all_vf_instruct_cost, repeat_time);

注意:kPlaceholder在性能表中真实存在(perf_param_v2.cpp),latency 与 throughput 均为 0,因此占位项只是成本占位,真正约束公式语义的是其唯一可追溯性。仓库中的实际 perf 实现也遵循直接调用原则,例如 ascendc_regbase_perf.cpp 中对无法精确映射的 MicroAPI 直接以kPlaceholder调用AddVfInstructPerf

七、建模输出模板

完成建模后,按以下模板输出结构化结论(SKILL.md 原文模板,此处完整保留):

### 源码依据 - codegen impl:`...` - API 名称:`...` - API 源码:`...` - API 调用生成:`...::{ApiCallName}::Generate` - 性能表:`.../perf_param_v2.cpp` - 当前 perf 实现:`.../ascendc_regbase_perf.cpp` ### 参数来源 | 源码参数 | codegen 传参来源 | ATT 是否可直接获取 | 处理方式 | | --- | --- | --- | --- | | `dst` | `outputs[0]` | 是/否 | ... | | `src` | `inputs[0]` | 是/否 | ... | | `loop_param` | `ApiLoopParams` | 否 | 需通过节点参数透传 | ### 分支分析 - 分支 A:触发条件,参数来源,源码行,公式差异 - 分支 B:触发条件,参数来源,源码行,公式差异 ### MicroAPI 计数 | MicroAPI | perf 类型 | 执行次数 | dtype | 说明 | | --- | --- | --- | --- | --- | ### 公式 ```cpp Expr max_latency = CreateExpr(0); Expr all_vf_instruct_cost = CreateExpr(0); // AddVfInstructPerf(...) Expr res = VfPerfUtils::GetVFHeadCost() + max_latency + all_vf_instruct_cost; ``` ### 不确定点 - 无;或列出需要用户确认/源码待确认的问题。

八、常见错误自查表

建模中最容易犯的错误集中在"公式与源码脱节"和"参数来源不清晰"两类,SKILL.md 给出的错误对照表完整如下:

错误正确做法
直接复述现有 perf 公式先读 API 源码和 codegen 传参,再解释现有公式是否简化
只看input_shapes/output_shapes同时看{ApiCallName}::Generate,确认实际传入 API 的参数
需要 loop 参数却在 ATT 中猜测通过节点参数从 codegen 透传,或说明无法精确获取
Generate 才能确定的参数却要求EnrichAscirGraphNodeParams计算具体值EnrichAscirGraphNodeParams只预注册空结构,Generate填具体值
性能函数只能访问NodeInfo却让 ATT 直接读 NodeFillSpecificParams,把节点参数提取到NodeInfo
只写入节点参数但 ATT 读取路径不明确明确链路:预注册空结构 → Generate 填值 → FillSpecificParams → NodeInfo → perf
给 ATT 的节点参数保存tpipe.tiler.Size/ActualSize展开值保存merge_info/ApiLoopParams原始符号表达式,tiler 表达式只用于生成 C++ 调用字符串
忽略if constexpr或 scalar/mask 分支每个源码分支单独列触发条件和计数
把所有 MicroAPI 都乘repeat_time按源码实际循环层级计算次数
找不到性能表项就跳过使用kPlaceholder并注释真实 MicroAPI
性能表暂不支持真实 dtype 就换成支持的 dtype保持源码真实 MicroAPI 类型和 dtype,后续扩展性能表
多个同类 MicroAPI 重复调用多次合并为一次AddVfInstructPerf,次数求和
把不同真实 MicroAPI 汇总到一个kPlaceholder按真实 MicroAPI、方向/模式和 dtype 分开调用
DataCopyload 和 store 合并load 与 store 分开调用并分别注释
DataCopy的 pack/unpack 模式再额外补一条成本DataCopy 只按 load/store 建模,dtype 分别使用 input/output
用辅助函数包装kPlaceholder直接调用VfPerfUtils::AddVfInstructPerf(kPlaceholder, ...)
UpdateMask使用kPlaceholder使用性能表已有的kUpdateMask
未说明不确定点不确定时先提问或列入"不确定点"

九、验证要求与验证方法

公式完成后的逐项检查清单

完成公式后至少检查以下事项(来自 SKILL.md 的验证要求,逐条保留):

  • API 源码入口能从GetApiName()追溯;
  • API 调用参数能从{ApiCallName}::Generate追溯;
  • ATT 使用的额外参数若来自 codegen,已通过节点参数传递;若性能函数只能访问NodeInfo,必须确认EnrichAscirGraphNodeParams已预注册空结构、Generate已填值、FillSpecificParams已提取到NodeInfo
  • 公式中的每个AddVfInstructPerf都能追溯到源码 MicroAPI;
  • 每个循环次数都能追溯到源码循环、codegen 参数或明确的保守估计;
  • 每个 MicroAPI 类型都在perf_param_v2.cpp中存在;不存在时已用kPlaceholder并说明;
  • 每个kPlaceholder都有唯一的真实 MicroAPI、操作方向/模式和 dtype;load/store 及不同真实类型未合并;
  • kPlaceholder均直接调用VfPerfUtils::AddVfInstructPerf,未增加包装函数;
  • MicroAPI::UpdateMask使用kUpdateMask
  • 最终 cost 使用VfPerfUtils::GetVFHeadCost() + max_latency + all_vf_instruct_cost

Skill 生效验证

在 opencode 中输入以下指令验证 Skill 是否生效(README 提供的官方验证方法):

  • "计算cast算子的性能公式"
  • "分析compare算子性能公式"

十、深入源码与测试:进一步学习路径

如果希望进一步验证本 Skill 方法论在仓库中的落地情况,可以按以下路径继续阅读:

  • codegen 侧:v2_ascir_codegen_impl.h 定义全部 V2 节点的 codegen 实现(含GetApiName/GetApiCallName);cast_v2_api_call.cpp 展示CastV2ApiCall::Generate如何填充CastNodeParams;reg_api_call/ 目录下还有compare_v2_api_call.cppunary_bitwidth_change_api_call_v2.cppreg_load_api_call.cpp等同类实现;
  • ATT 侧:ascendc_regbase_perf.cpp 与 ascir_reduce_api_perf_v2.cpp 是性能公式的落地实现,perf_param_v2.cpp 是性能表数据源;
  • 测试侧:test_ascir_perf_v2.cpp、test_reduce_min_max_api_perf_v2.cpp 覆盖 ATT 性能公式,test_codegen_cast_reg_api_call.cpp 与 test_codegen_compare_reg_api_call.cpp 覆盖 codegen 传参,test_ascir_node_params.cpp 覆盖节点参数结构体。

结语

af-perf-modeler 提供了一套"源码 → codegen 传参 → 节点参数透传 → ATT 性能公式"全链路可追溯的 V2 节点建模方法论。对开发者而言,掌握这套流程意味着:面对任意 V2 算子,都能从GetApiName()/GetApiCallName()出发,沿EnrichAscirGraphNodeParamsGenerateFillSpecificParamsNodeInfo→ perf 函数的链路,推导出与真实生成代码语义一致的性能公式,而不是复制粘贴一份无法解释的既有公式。这套方法同时是 codegen、ATT 与性能模型三方对齐的工程规范,值得在新增算子建模或性能问题定位时直接复用。

【免费下载链接】graph-autofusionGraph-autofusion 是一个面向昇腾(Ascend)芯片的轻量级、解耦式组件集合,旨在通过自动融合技术加速模型执行。 目前已开源 SuperKernel 组件和 Autofuse 组件,未来将持续开放更多自动融合相关模块。项目地址: https://gitcode.com/cann/graph-autofusion

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询