一文搞懂 HIVM 的 hir.custom_macro:跨 Pipe 宏操作完整解析与速查
2026/9/19 13:22:01 网站建设 项目流程

一文搞懂 HIVM 的 hir.custom_macro:跨 Pipe 宏操作完整解析与速查

【免费下载链接】BiliBiliToolProB 站(bilibili)自动任务工具,支持docker、青龙、k8s等多种部署方式。全面拥抱AI。敏感肌也能用。项目地址: https://gitcode.com/GitHub_Trending/bi/BiliBiliToolPro

hir.custom_macro是 AscendNPU-IR 项目中 HIVM 方言的跨 Pipe 宏操作:把「数据加载 + 向量计算 + 后处理」这类横跨多条流水线的活打包成一个 IR 操作,再由编译器根据你声明的pipe_in/pipe_out自动补齐跨 Pipe 同步。如果你之前只用过单流水线的hir.custom,遇到"进出的管道不一样"的场景就会卡住——这篇就是为此准备的。

场景速览:什么时候需要跨 Pipeline 的宏操作

典型的融合核里,数据往往先由 MTE2 流水线从 GM(全局内存)取回,再交给 V 流水线(Vector Core)做向量运算,最后把结果写回。用hir.custom描述这条链路会立刻碰壁——它只能绑定一条流水线。hir.custom_macro就是为这种"进门管道 ≠ 出门管道"的融合而生:同一个操作上声明入口与出口,中间的握手交给编译器。

先看 IR,再拆细节:两个完整示例与 TableGen 定义

先上手感。下面是一个手写的自定义宏操作,注意属性区里那对pipe_in/pipe_out,它们分别记录了数据进来和结果出去的流水线:

%empty = tensor.empty() : tensor<3x3xf32> %0 = hivm.hir.custom_macro { hivm.tcore_type = #hivm.tcore_type<VECTOR>, hivm.vf_mode = #hivm.vf_mode<SIMD>, hivm.pipe_in = #hivm.pipe<PIPE_MTE2>, hivm.pipe_out = #hivm.pipe<PIPE_V> } "my_custom_op" ins(%arg0, %arg1, %c4_i64, %c0_i32, %c2_i64, %c1_i64, %c2_i32, %c2_i32, %c0_i32, %c0_i32 : memref<?xf32>, tensor<3x3xi64>, i64, i32, i64, i64, i32, i32, i32, i32) outs(%empty : tensor<3x3xf32>) -> tensor<3x3xf32>

对比一个内置宏操作(名字以__builtin_开头):IR 文本里不再手写属性区,核类型、管道、VF 模式全部由编译器在构造时补全:

%empty = tensor.empty() : tensor<3x3xf32> %0 = hivm.hir.custom_macro "__builtin_gather_load" ins(%arg0, %arg1, %c4_i64, %c0_i32, %c2_i64, %c1_i64, %c2_i32, %c2_i32, %c0_i32, %c0_i32 : memref<?xf32>, tensor<3x3xi64>, i64, i32, i64, i64, i32, i32, i32, i32) outs(%empty : tensor<3x3xf32>) -> tensor<3x3xf32>

这两个写法背后的 TableGen 定义位于bishengir/include/bishengir/Dialect/HIVM/IR/HIVMOps.tdCustomMacroOp段约 L1105–L1167):

def CustomMacroOp : HIVM_CustomOp<"custom_macro", [MacroOpTrait]> { let arguments = (ins StrAttr:$name, Variadic<AnyType>:$inputs, Variadic<AnyType>:$outputs); let results = (outs Variadic<AnyType>:$results); }

怎么读这段定义?操作在方言里登记的名字是custom_macro,继承自定义操作族的公共基类HIVM_CustomOp;与hir.custom最本质的差别是它挂着MacroOpTrait——等于提前向编译器报备:这是个要跨多条流水线跑的宏,同步分析时别拿单步操作那套规则套我。arguments段声明了三个输入操作数,results段声明了可变长的结果。

操作拆解:输入、输出与属性的完整清单

操作数一侧,ins段一共有三个:$nameStrAttr类型的字符串,也就是操作名(如"my_custom_op");$inputs是可变长的任意类型参数,承担真正的计算输入;$outputs同样是可变长任意类型,扮演输出缓冲区的角色(DPS init)。outs段则以可变长$results返回结果。

必需属性按职责分成两组:一组回答"在哪儿跑",另一组回答"绑到哪条流水线上"。

执行环境

属性类型用途
hivm.tcore_typeTCoreTypeAttr指定运行的核类型,如VECTOR
hivm.vf_modeVFModeAttr向量执行模式,如SIMD

流水线绑定

属性类型用途
hivm.pipe_inPipeAttr入口流水线:数据从哪条 Pipe 进入宏
hivm.pipe_outPipeAttr出口流水线:结果从哪条 Pipe 离开宏

可选属性

属性类型用途
gm_addr_args_indicesDenseI32ArrayAttr索引列表,标记哪些参数携带 GM(全局内存)地址

⚡️ 跨 Pipe 同步:编译器如何自动补齐握手

单 Pipe 的hir.custom只需在自己那条流水线内部做同步;而宏操作横跨两条管道,生产者写过的数据,消费者不能立刻读,中间必须有一次标志位握手。MacroOpTrait就是编译器做这件事的钩子:InjectSyncGraphSyncSolver这两个同步分析 Pass 在构建依赖图时,会读取你声明的hivm.pipe_in/hivm.pipe_out,自动在宏操作前后插上一对set_flag/wait_flag——入口管道写完数据后抬旗,出口管道等旗落下再开工。换句话说:跨哪两条 Pipe 由你在 IR 里声明,什么时机同步由 Pass 推导,整条链路不需要手写任何同步代码。

💡 选型对比:hir.custom 还是 hir.custom_macro

维度hir.customhir.custom_macro
TraitSinglePipeOpTraitMacroOpTrait
Pipe 属性单个hivm.pipehivm.pipe_in+hivm.pipe_out一对
涉及流水线数1 条2 条(入口 + 出口)
同步方式仅限 Pipe 内同步需要跨 Pipe 同步,由 Pass 自动插入
BuiltinInfo 字段coreType、pipe、vfModecoreType、inPipe、outPipe、vfMode

一句话选型建议:整个操作能塞进一条流水线,就用custom;数据要从一条管道进(比如 MTE2 从 GM 取数)、从另一条管道出(比如 V 管道做向量计算),就换custom_macro

📌 API 速查:CustomMacroOp 访问器与 Pipe 名称常量

C++ 侧,CustomMacroOp在基类接口之外还提供了一组专门访问器,方便 Pass 与工具链读写:

方法返回类型作用
getInPipe()PIPE读取入口管道
setInPipe(PIPE)void写入入口管道
getOutPipe()PIPE读取出口管道
setOutPipe(PIPE)void写入出口管道
getCoreType()optional<TCoreType>读取核类型
setCoreType(TCoreType)void写入核类型
getVFMode()optional<VFMode>读取 VF 模式
setVFMode(VFMode)void写入 VF 模式
isBuiltin()bool判断是否为内置宏操作

另外,两个 Pipe 属性名还以类内常量的形式给出,编译器在 IR 上查找属性时用的就是它们:

static constexpr StringLiteral inPipeName = "hivm.pipe_in"; static constexpr StringLiteral outPipeName = "hivm.pipe_out";

编译器会校验什么:五条硬性规则

custom_macro进入编译流程后,以下几条规则会被逐一检查:

  1. MacroOpTrait 语义——操作会被识别为宏操作,同步分析阶段走专门的处理路径,而不是按单流水线 custom 对待。
  2. pipe_in / pipe_out 必须齐备——两个属性缺一不可,编译器靠它们推导该插入哪一对跨 Pipe 同步。
  3. 跨 Pipe 同步插入——InjectSync/GraphSyncSolver依据声明的入口/出口管道,在操作前后自动补上set_flag/wait_flag
  4. DPS 语义——操作实现了DestinationStyleOpInterface$outputs操作数承担目的地缓冲(DPS init)职责,结果直接写进你给的 buffer。
  5. 内置宏额外校验——当isBuiltin()返回 true(即名字是__builtin_前缀的内置宏)时,编译器还会对它的参数做正确性检查。

❓ 你可能想问:三个高频问题

Q:pipe_in 和 pipe_out 到底怎么定?A:顺着数据流方向看。数据"进"宏时走的那条管道是pipe_in——例如从 GM 取数就填PIPE_MTE2;结果"出"宏时走的那条是pipe_out——向量计算落在 V 管道,就填PIPE_V。只要你能说清数据从哪儿来、往哪儿去,选型就定了。

Q:我的操作其实只涉及一条流水线,还要写 custom_macro 吗?A:不用。入口和出口在同一条管道上的话,hir.custom就够了;custom_macro专治入口、出口管道不同的情况,比如 MTE2 加载 + V 管道计算的融合。

Q:set_flag / wait_flag 需要我在 IR 里手写吗?A:不需要。你只负责声明pipe_in/pipe_out,剩下的由InjectSync/GraphSyncSolver两个 Pass 推导跨管道依赖,并自动在宏操作周围插入标志位同步。

延伸阅读

  • TableGen 定义:HIVMOps.td(CustomMacroOp段约 L1105–L1167)
  • 测试用例:custom-op.mlir
  • 关联文档:单 Pipe 版hir.custom解析——01-custom-op.md

【免费下载链接】BiliBiliToolProB 站(bilibili)自动任务工具,支持docker、青龙、k8s等多种部署方式。全面拥抱AI。敏感肌也能用。项目地址: https://gitcode.com/GitHub_Trending/bi/BiliBiliToolPro

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

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

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

立即咨询