想象一家智能玩具工厂:零件被放在传送带上,经过机器加工,变成可以拼装的积木组件。厂长想优化流水线,但他不会把整条生产线拆掉重装,而是交给老师傅一叠“替换卡片”。卡片上写着:只要看到“两个固定齿轮带动一个减速箱”这种接法,就把它整体换成“一体化减速电机”。工人按卡片操作,只改局部,不碰其他地方。 MLIR 里的模式重写(Pattern Rewriting)就是这套“替换卡片”。它把中间表示(IR)看成一张由算子和依赖关系组成的“积木图”,每张卡片描述“看到某个小结构,就替换成另一个等价结构”。编译器会拿着卡片自动扫描、匹配、替换,于是算子融合、常量折叠这些优化,不再需要工程师手写繁琐的遍历逻辑。

岗位关联

对 AI 编译器从业者来说,模式重写是编写 MLIR Pass 的标准方式:学会用“声明规则”代替“手写变换”,能大幅提升优化开发效率,也是进阶编译器优化的必修课。

目录
  1. 可视化演示
  2. 模式重写概述
  3. 匹配与替换
  4. DRR声明式框架
  5. DAG到DAG变换
  6. 典型优化应用
  7. Pass集成与调试
  8. 收益与挑战
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

演示MLIR Pass模式重写机制,展示DAG重写流程、算子融合与常量折叠关键优化。

本节配有交互动效(进入视口后自动加载,约 3KB)。

MLIR模式重写 DAG重写、算子融合与常量折叠三大优化,自动应用变换简化IR Pattern→Rewrite 编译器优化自动化核心

模式重写概述

老师傅走进工厂,看到的不再是一堆零件,而是一张“积木装配图”:每个加工工位是一个节点,工位与工位之间用传送带连接,表示谁的结果被谁使用。工厂要做优化,不是推翻这张图,而是在局部插入“替换卡片”。这张卡片的学名,就叫模式重写。

在 MLIR 中,这个“装配图”就是中间表示(IR)。IR 里的每个操作(Operation)是一个节点,操作之间的数据依赖是边,整体构成一张有向无环图(DAG)。一次 Pass 就是一次“工厂巡检”,而模式重写则是让巡检员不用手工调整每一个齿轮,只需检查图上是否有卡片描述的结构;一旦发现,就按卡片完成替换。

传统的优化 Pass 往往需要手写遍历 IR、判断条件、创建新操作、删除旧操作的代码。模式重写把这些细节交给框架:你定义“看到什么、换成什么”,框架负责寻找匹配、安全改写、维护依赖。MLIR 的 RewritePattern 就是一个可复用的模式对象,PatternRewriter 则是那个严格按操作规程动手的工人。

回到玩具工厂:老师傅不需要认识每个螺丝钉,他只需要看图、认卡片、动手换。MLIR 工程师也一样,把 IR 看成 DAG,把优化看成局部替换,复杂系统就变得可拆解、可组合。

G=(V,E),V={算子},E={(u,v)∣u 的结果被 v 使用}\displaystyle G=(V,E),\quad V=\{\text{算子}\},\quad E=\{(u,v)\mid u\text{ 的结果被 }v\text{ 使用}\}
这个公式把 IR 抽象成图:节点是算子,边是数据依赖。理解这张图,就理解了模式重写为什么是 DAG 上的结构变换。
  1. \(G\) 表示当前 IR 图Pass 操作的中间表示不是文本,而是一张图。
    GG
  2. \(V\) 是算子集合图中的每个节点代表一个 MLIR 操作,比如 mul、add。
    V={算子}V=\{\text{算子}\}
  3. \(E\) 是依赖边集合边表示“某个算子的结果被另一个算子使用”,这正是数据流动的方向。
    E={(u,v)∣u 的结果被 v 使用}E=\{(u,v)\mid u\text{ 的结果被 }v\text{ 使用}\}

匹配与替换

老师傅手中的卡片必须有两部分:上半部分是“匹配条件”,下半部分是“替换动作”。比如“匹配:两个固定齿轮带动一个减速箱;替换:一体化减速电机”。如果匹配条件不满足,卡片绝不能用;如果满足,动手时还要保证接口一致,不能让下游工位找不到输入。

MLIR 的 matchAndRewrite 正是这个结构。第一步,match 检查当前操作及其操作数是否符合 DAG 模式;第二步,rewrite 用 PatternRewriter 创建新操作、替换旧操作。匹配失败就换下一张卡片,匹配成功则继续处理其他位置。这就是“局部作用域”:每次只影响被匹配的那一小块子图,不动整张图。

系统里往往有几十张卡片,它们的优先级由“模式收益(benefit)”决定。收益越高的卡片越先尝试,避免随机顺序导致永远走不到理想优化。收益只是个相对排序,真正对错还要靠语义等价来把关。

工厂老师傅的经验就这样沉淀成一张张卡片:匹配条件越精准,误伤越少;替换动作越规范,流水线越安全。编写 Pass 时,你也能把优化的“判断”和“动手”分开,让每一部分都能独立测试。

G→(M,R)(G∖M)∪R\displaystyle G \xrightarrow{(M,R)} (G \setminus M) \cup R
这个公式表示:从原图 \(G\) 中去掉被匹配的子图 \(M\),再并上替换生成的 DAG \(R\)。这就是 DAG-to-DAG 变换的一句话概括。
  1. \(M \subseteq G\)匹配条件先选出一个子图,相当于卡片上“看到什么”的部分。
    M⊆GM \subseteq G
  2. \(G \setminus M\)原图去掉匹配部分,剩下的“无关机器”保持原样。
    G∖MG \setminus M
  3. \((G \setminus M) \cup R\)把替换 DAG 接回原图,并重新连接输入输出边。
    (G∖M)∪R(G \setminus M) \cup R

DRR声明式框架

给老师傅写操作说明有许多方式。最笨的方法是直接写机器人底层指令:先移动机械臂,再拧两颗螺丝,最后检查接口。另一种方法是写“卡片”:只需写“看到什么、换成什么”,再由一个文书员自动生成底层指令。MLIR 的 DRR(Declarative Rewrite Rules)就是后者。

DRR 使用 TableGen 描述模式。开发者只需要声明一个 Pat 规则,写下源 DAG 和目标 DAG,其余遍历、匹配、IR 更新代码都由工具生成。mlir-tblgen 就是这个“文书员”,它读取 TableGen 规则,自动生成 C++ 的 RewritePattern 子类。这就是 RewriterGen 的工作:把声明式描述翻译成命令式代码。

这种做法的好处是:优化规则的意图一目了然;C++ 样板代码不再需要手工维护;新增一条优化时,只需增加几行 TableGen,而不是一整个类。DRR 让模式重写拥有了“配置化”的形态,特别适合表达算子融合、常量折叠这类结构清晰的变换。

用“卡片”而不是“机器人指令”,意味着优化知识可以被阅读、评审和复用。DRR 之所以重要,是因为它把“优化意图”和“实现细节”分离,让 AI 编译器里的规则库像工厂手册一样清晰。

P→mlir-tblgenC++ RewritePattern\displaystyle \mathcal{P} \xrightarrow{\text{mlir-tblgen}} \text{C++ RewritePattern}
这里的 \(\mathcal{P}\) 代表用 TableGen 写出的规则集合,箭头表示自动生成过程。开发者维护 \(\mathcal{P}\),编译器维护生成的 C++ 代码。
  1. \(\mathcal{P}\) 是 DRR 规则集合它描述“匹配什么 DAG、替换成什么 DAG”。
    P\mathcal{P}
  2. \(\xrightarrow{\text{mlir-tblgen}}\)表驱动代码生成器把声明式规则翻译成命令式 C++。
    →mlir-tblgen\xrightarrow{\text{mlir-tblgen}}
  3. C++ RewritePattern最终产物是可被 Pass 调用的模式类,能够直接参与重写。
    C++ RewritePattern\text{C++ RewritePattern}

DAG到DAG变换

老师傅最讲究“接口对得上”。替换一个减速箱时,输入轴位置、输出轴转速都必须和原来一致;否则下一道工序就会卡住。模式重写的本质也是如此:不是改一个字符,而是把一个“子 DAG”换成另一个“子 DAG”,并重新接好边界上的边。

MLIR 中,匹配的单元通常是一个操作及其操作数构成的子图。替换时,新子图要接收原来的输入值,也要产出原来的输出值给下游使用。PatternRewriter::replaceOp 这类 API 会主动把旧结果的所有使用者重定向到新结果,所以开发者不用手工维护“谁在用我”的关系。这就是 DAG-to-DAG 的“接口映射”。

除此之外,重写还要考虑约束与副作用。如果一个操作有读写内存等副作用,就不能随意删除或改变执行顺序。合法的模式重写必须保证:匹配条件足够强,替换结果与原行为语义等价,副作用要么保留、要么被证明可消去。

如果把 IR 比作电路图,DAG-to-DAG 就是“替换一个元件并重新焊线”。MLIR 框架替我们检查导线是否接错,工程师则专注设计更好的元件。

In(M)→Operands(R),Out(M)→Results(R)\displaystyle \text{In}(M) \to \text{Operands}(R), \quad \text{Out}(M) \to \text{Results}(R)
这个公式描述替换时的接口对接:匹配子图的输入边要接到新子图的操作数上,匹配子图的输出边要接到新子图的结果上。
  1. \(\text{In}(M)\)匹配子图的输入值,也就是外部算子喂给它的操作数。
    In(M)\text{In}(M)
  2. \(\text{Operands}(R)\)替换 DAG 的操作数,必须与旧输入一一对应。
    Operands(R)\text{Operands}(R)
  3. \(\text{Results}(R)\)新 DAG 产生的结果,要替代旧子图的结果供给下游。
    Results(R)\text{Results}(R)

典型优化应用

工厂常用的三类卡片,正好对应模式重写的三个经典应用。

第一张卡片是“合并工序”:如果看到“乘法工位把结果送到加法工位”,就换成一台能直接计算 a*b+c 的复合机器。这张卡片对应算子融合,减少了中间结果的搬运和内存占用。第二张卡片是“预计算固定件”:如果看到两个常数值相乘,就提前算出答案,贴上新标签。这张卡片对应常量折叠。第三张卡片是“标准化摆放”:把同类零件统一摆到固定位置,让其他卡片更容易命中。这张卡片对应规范化(Canonicalization)。

这三类优化都非常适合用 DRR 声明。算子融合把结构性的计算合并成一个整体;常量折叠把运行期计算变成编译期计算;规范化消除冗余形态,比如 x+0 直接变成 x。它们不改变结果,只改变产生结果的方式。

玩具工厂用这三张卡片,就能让流水线越来越紧凑。AI 编译器也一样,把常见的计算模式写成卡片,编译器就能自动把笨重的计算图优化成精悍的执行图。

(a×b)+c→fma(a,b,c),c1×c2→c1⋅c2\displaystyle (a\times b)+c \to \text{fma}(a,b,c), \qquad c_1\times c_2 \to c_1\cdot c_2
前一个式子展示算子融合:乘加两个算子合并成一个 fma。后一个式子展示常量折叠:两个常量相乘在编译期直接算好。
  1. \((a\times b)+c\)源 DAG 是 mul 的结果作为 add 的一个操作数。
    (a×b)+c(a\times b)+c
  2. \(\text{fma}(a,b,c)\)目标 DAG 是一个融合算子,一条指令完成乘加,省去中间值。
    fma(a,b,c)\text{fma}(a,b,c)
  3. \(c_1\times c_2\)匹配条件要求两个操作数都是编译期常量。
    c1×c2c_1\times c_2
  4. \(c_1\cdot c_2\)替换结果是一个新的常量节点,数值已在编译期算出。
    c1⋅c2c_1\cdot c_2

Pass集成与调试

卡片再漂亮,如果没有人拿着它去车间巡视,也不会产生价值。Pass 就是那个“巡视计划”,而模式重写的驱动方式则是“不断巡逻直到没有卡片可用”。

在 MLIR 中,开发者先把一组 RewritePattern 注册到 RewritePatternSet,再交给贪婪重写驱动(Greedy Rewriter)。它会反复扫描 IR:只要某张卡片能命中,就执行替换;一轮结束后继续下一轮,直到没有任何模式能匹配。这种行为叫“迭代到固定点”。注册规则的 Pass 可以很小,比如 -canonicalize 就是一个以模式重写为主的规范化 Pass。

调试时,mlir-opt 是最常用的实验室:用一个测试文件跑指定 Pass,观察 IR 的变化。配合 -debug-only=mlir-rewriter 这类开关,还能看到每条规则尝试和命中的日志。测试和调试的价值,就像给工厂做“试运行”:先在小块流水线上验证卡片不出错,再放开到完整生产线。

“巡逻到没有卡片命中”看起来简单,却能保证优化结果稳定。Pass 集成到编译流程后,AI 模型的计算图就会在编译期间被自动打磨,开发者只要观察日志和测试就能验证效果。

G0→G1→⋯→Gn,Gn=Gn−1⇒stop\displaystyle G_0 \to G_1 \to \cdots \to G_n, \quad G_n = G_{n-1} \Rightarrow \text{stop}
这是贪婪重写驱动的终止条件:从初始 IR 开始不断应用规则,直到某轮前后完全一致,说明所有卡片都试过且没有新匹配。
  1. \(G_0\)进入 Pass 时的初始 IR,也就是改造前的流水线。
    G0G_0
  2. \(G_i \to G_{i+1}\)每一轮应用若干条模式重写规则,IR 从一个状态变到下一个状态。
    Gi→Gi+1G_i \to G_{i+1}
  3. \(G_n = G_{n-1}\)如果连续两轮没有产生任何变化,就停止迭代。
    Gn=Gn−1G_n = G_{n-1}

收益与挑战

模式重写真正改变的是“优化开发的方式”。过去,一个优化 Pass 可能涉及复杂的 IR 遍历、节点插入、删除和依赖更新;现在,大多数优化可以写成一张张高可读性的卡片,开发和维护成本大大降低。

但它也有挑战。卡片多了,可能出现“两张卡片同时命中同一个子图”的冲突,需要用模式收益和 Pass 顺序来裁决。更麻烦的是规则之间可能互相触发,造成无限循环。比如一张规则把 a+b 变成 b+a,另一张又把 b+a 变回 a+b,贪婪重写就会来回打转。因此,模式重写不是“规则越多越好”,而是要在收益与复杂度之间找平衡。

MLIR 通过模式收益、规范化、调试开关和测试流水线来管理这些挑战。每个模式都带着一个相对优先级,重写框架按优先级决定尝试顺序;规范化则提前统一 IR 形态,减少规则的排列组合。

老师傅的手册越厚,越需要编号和索引。模式重写让 AI 编译器变得可扩展,但真正成熟的产品,还得靠纪律严明的规则管理、优先级设计和充分测试。

Benefit(p)>Benefit(q)⇒先尝试 p 再尝试 q\displaystyle \text{Benefit}(p) > \text{Benefit}(q) \Rightarrow \text{先尝试 } p \text{ 再尝试 } q
这个公式说明模式收益的作用:收益高的规则优先尝试。收益不是用来判断正确性的,而是用来解决冲突和排序的。
  1. \(\text{Benefit}(p)\)每个模式在注册时有一个收益值,代表它的期望优化价值。
    Benefit(p)\text{Benefit}(p)
  2. \(>\)收益比较决定尝试顺序,高收益规则更早获得匹配机会。
    >>
  3. \(\Rightarrow\)推导出实际执行顺序:先尝试 \(p\),再尝试 \(q\)。
    ⇒先尝试 p 再尝试 q\Rightarrow \text{先尝试 } p \text{ 再尝试 } q

AI 应用场景

场景一:深度学习编译器中的算子融合

阶段:模型图优化 / 编译期计算图变换

解决的问题:传统手工优化 Pass 需要遍历计算图,逐个判断“该不该融合”,代码重复且难以维护。模式重写把融合规则声明成“看到 matmul 后跟 add 就替换为 fma 风格算子”,编译器自动完成匹配与替换,显著减少开发成本。

具体案例:在基于 MLIR 的 AI 编译器中,工程师把 conv2d + bias_add + relu 写成一个 DRR 模式,让 GPU 后端一次生成融合 Kernel,减少显存读写。

场景二:推理引擎的常量折叠

阶段:部署前静态图优化

解决的问题:模型里存在大量在推理时永远不变的常量计算,例如 shape 运算、固定索引拼接。传统做法是用一个特化 Pass 处理,代码难复用。模式重写把“常量操作数”作为匹配条件,一张卡片即可覆盖多种算子。

具体案例:推理引擎加载 ONNX 模型前,用模式重写对 shape + gather + concat 做常量折叠,减少在线推理时的重复计算,降低首延迟。

场景三:自定义硬件后端的 Dialect 下降

阶段:后端代码生成 / Dialect 转换

解决的问题:新 AI 芯片需要把高层的 Tensor 算子下降到芯片支持的底层指令,手工编写下降 Pass 工作量大。模式重写用 DAG-to-DAG 规则描述“高层算子子图 → 底层算子子图”,并自动完成接口连接。

具体案例:某 NPU 编译器把 transpose + matmul 替换为自定义的 tmem_matmul 算子,让后端可以识别并映射到硬件指令,而不需要改动前端框架。

Python 演示

python48 行
"""mini_drr.py: 用 Python 模拟 MLIR DRR 的 DAG-to-DAG 模式重写""" def match(pattern, ir):    if isinstance(pattern, str) and pattern.startswith("$"):        return {pattern: ir}    if isinstance(pattern, tuple):        if not isinstance(ir, tuple) or len(pattern) != len(ir):            return None        if pattern[0] != ir[0]:            return None        binds = {}        for p, n in zip(pattern[1:], ir[1:]):            sub = match(p, n)            if sub is None:                return None            binds.update(sub)        return binds    return {pattern: ir} if pattern == ir else None  def apply_rules(node):    # 规则1: 常量折叠 mul(const, const) -> const    binds = match(("mul", ("const", "$x"), ("const", "$y")), node)    if binds is not None:        return ("const", binds["$x"] * binds["$y"])    # 规则2: 算子融合 add(mul(a, b), c) -> fma(a, b, c)    binds = match(("add", ("mul", "$a", "$b"), "$c"), node)    if binds is not None:        return ("fma", binds["$a"], binds["$b"], binds["$c"])    return node  def rewrite(node):    if not isinstance(node, tuple):        return node    while True:        result = apply_rules(node)        if result == node:            break        node = result    new_args = [rewrite(arg) for arg in node[1:]]    return tuple([node[0]] + new_args)  if __name__ == "__main__":    dag = ("add", ("mul", ("const", 3), ("const", 4)), ("const", 5))    print("before:", dag)    print("after :", rewrite(dag))
目的:用元组表示 DAG,用 match 模拟 DRR 的模式匹配,用 apply_rules 模拟声明式替换规则,展示模式重写如何把计算图优化到固定点。
  1. def match(pattern, ir):递归比较“模式卡片”和当前 DAG,返回变量绑定。
  2. if isinstance(pattern, str) and pattern.startswith("$"):$x 是通配符,可绑定任意子节点。
  3. ("mul", ("const", "$x"), ("const", "$y"))这是一个 DRR 风格的源 DAG 模式。
  4. return ("const", binds["$x"] * binds["$y"])常量折叠的替换动作:直接在编译期算出结果。
  5. while True:贪婪重写直到没有规则能改变当前节点,对应 MLIR 的固定点循环。

常见误区

误区我是做模型训练的,只需要用 PyTorch,模式重写是编译器内部细节,与我无关。

正解AI 模型要部署到 GPU、NPU、手机端,都会经过 AI 编译器。你的网络结构、推理性能、内存占用都受计算图优化影响。模式重写正是 AI 编译器实现算子融合、常量折叠等优化的核心手段。它像一个“隐藏的工厂老师傅”,决定模型最终跑多快、占多少资源。

为何常见:模型开发者通常只看到高层框架 API,看不到 MLIR 内部的 IR 和 Pass,因此误以为优化与己无关。

误区模式重写不就是找到字符串 `mul(a,b)`,替换成另一个字符串吗?

正解它针对的是 DAG,而非文本。节点之间的依赖关系、输入输出的连接都必须被重新映射。就像替换工厂里的一台机器,要看清楚传送带从哪进、往哪出,而不是简单撕掉一张标签。MLIR 的 PatternRewriter 会维护这些依赖关系,保证替换后 IR 仍然合法。

为何常见:初学者看到“模式”两个字,容易联想到正则表达式或字符串替换,忽略了 IR 的图结构。

误区既然 DRR 能自动生成 C++ 重写代码,以后就不需要手写重写逻辑了。

正解DRR 擅长表达结构清晰的 DAG-to-DAG 替换,但复杂约束、副作用分析、跨区域修改、需要调用外部分析的优化,仍然要手写 RewritePattern。DRR 更像“常用卡片模板”,不是全能机器人。MLIR 允许 DRR 与 C++ 辅助函数结合,关键是选择合适的表达层次。

为何常见:看到“自动生成”就以为无需编程,忽略了模式匹配背后的正确性和复杂度。

误区我只要把能想到的优化规则都注册进去,编译器自然会找到最优结果。

正解规则之间可能冲突,也可能互相触发产生死循环。比如两条规则可以把一个子图 A 变成 B,又变回 A。MLIR 用模式收益、贪婪驱动和规范化来控制行为。规则库需要像老师的“修改手册”一样编排:分优先级、做测试、避免无限应用。

为何常见:局部看每条规则都没错,但系统层面缺少顺序和终止性意识,容易忽略相互作用。

面试题

Q1. 在 MLIR 中,“模式重写”主要把 IR 看作哪种结构?

单选题 · easy

  • A.线性文本列表
  • B.算子构成的 DAG← 正确答案
  • C.无限循环数组
  • D.随机键值表

答案:B。模式重写的核心输入是算子和数据依赖组成的 DAG,而不是文本列表。理解这一点才能理解匹配子图、替换和重连边这些动作。

Q2. DRR 声明式重写规则通常写在什么类型的文件中?

单选题 · easy

  • A..mlir 文件
  • B.TableGen 文件← 正确答案
  • C.Python 文件
  • D..json 文件

答案:B。DRR 使用 TableGen 描述规则,再通过 mlir-tblgen 生成 C++ 代码。.mlir 是测试输入输出用的 IR 文件,不是规则定义文件。

Q3. 一个模式由“匹配子图”和“替换 DAG”组成,替换后编译器必须保证什么?

单选题 · mid

  • A.输入输出关系重新连接← 正确答案
  • B.所有常量都会变大
  • C.Pass 数量自动变多
  • D.内存占用必然减少

答案:A。模式重写不是孤立地换节点,还要把旧子图的输入输出边重新接到新 DAG 上。保证连接合法是替换动作的基本要求,优化效果并非必然。

Q4. 对 mul(const 2, const 3) 做常量折叠,最可能得到什么?

单选题 · mid

  • A.mul(2, 3)
  • B.const 6← 正确答案
  • C.add(2, 3)
  • D.const 5

答案:B。两个操作数都是编译期常量,模式重写可以在编译期直接算出 2 * 3 = 6,生成 const 6。这对应工厂里的“预计算固定件”卡片。

Q5. MLIR 中为什么通常不直接删除 Operation,而要借助 PatternRewriter?

单选题 · hard

  • A.C++ 语言不允许删除对象
  • B.需要维护依赖与替换映射← 正确答案
  • C.因为这样运行速度更快
  • D.因为可以少写单元测试

答案:B。直接删节点会导致下游使用悬空值。PatternRewriter 会先更新所有使用关系,再删除旧操作,保证 SSA 形式的 IR 始终合法,这也让替换动作更安全。

Q6. DAG-to-DAG 模式匹配时,下列哪些可以作为匹配条件?

多选题 · mid

  • A.算子的名称← 正确答案
  • B.操作数类型← 正确答案
  • C.操作数来源是否常量← 正确答案
  • D.当前系统时间

答案:ABC。模式可以检查算子类型、操作数类型、常量属性等静态信息。系统时间不是 IR 的属性,也不能参与确定性编译优化。

Q7. 声明式 DRR 带来的好处包括哪些?

多选题 · hard

  • A.减少手写样板代码← 正确答案
  • B.自动生成 C++ 模式类← 正确答案
  • C.规则可读性更高← 正确答案
  • D.一定比手写 Pass 运行更快

答案:ABC。DRR 把规则像卡片一样描述,并通过 TableGen 自动生成 C++ 代码,代码量和维护成本都更低。但生成的代码不一定比精心手写更快,也仍然需要正确性测试。

Q8. 贪婪重写驱动通常什么时候停止?

单选题 · easy

  • A.运行时间到点
  • B.不再有规则命中← 正确答案
  • C.调用固定次数
  • D.固定点已到达

答案:B。贪婪重写会反复应用模式,直到某一轮没有任何规则能改变 IR,也就是达到固定点。时间到点和调用次数都不是标准终止条件。

Q9. 下列哪些优化可以用模式重写表达?

多选题 · mid

  • A.算子融合← 正确答案
  • B.常量折叠← 正确答案
  • C.冗余拷贝消除← 正确答案
  • D.安装软件依赖

答案:ABC。算子融合、常量折叠、冗余拷贝消除都是计算图上的结构变换,可以用“匹配子图 + 替换 DAG”描述。安装依赖不是编译优化问题。

Q10. 当多个模式都能匹配同一个 IR 子图时,MLIR 如何决定优先尝试哪个?

单选题 · hard

  • A.按文件名排序
  • B.按模式收益排序← 正确答案
  • C.随机选择模式
  • D.按输入张量大小排序

答案:B。每个模式注册时带有一个模式收益(benefit),收益高的规则优先尝试。这样设计可以避免随机选择带来的不确定优化结果,也能解决部分规则冲突。