CGO’25 新突破:基于MLIR的持久化e-graph技术,彻底解决编译器阶段顺序难题

关键词等式饱和e-graph编译器MLIR、持久化、优化

通过将 e-graph 直接嵌入 MLIR,研究人员让等式饱和贯穿整个编译流程,无需反复翻译、不丢失等价信息,并成功复现了 Herbie 浮点精度优化工具。

现代编译器通常由一系列独立的优化遍(pass)组成,每个遍在中间表示(IR)上执行特定的转换,例如常量折叠、死代码消除、循环不变式外提等。这种模块化设计虽然便于维护和扩展,却引入了一个经典难题——阶段顺序问题(phase‑ordering problem)。某个优化 Pass 可能破坏另一个优化 Pass 的应用机会,而编译器的编写者必须手动决定 Pass 的顺序,这往往依赖经验或启发式,无法保证得到最优结果。

等式饱和(equality saturation) 提供了一种全新的思路。它不再“破坏性”地替换表达式,而是将所有等价表达式都保存在一个称为 e‑graph 的数据结构中,并持续应用重写规则向其中添加新表达式。

当 e‑graph 达到饱和(不再能添加新表达式)或达到某个资源限制后,再根据代价模型从中提取出最优表示。这种方法完全避免了阶段顺序问题,因为所有可能的等价形式都被保留,最终选择可以全局考虑。

等式饱和已经在许多优化挑战中取得成功,例如浮点精度提升(Herbie)、高 Level 综合(SEER)、指令选择(Cranelift)等。然而,现有的集成方式存在明显短板:

  • 要么将程序翻译到外部等式饱和库如 egg、egglog,优化后再翻译回来,导致翻译开销和等价信息丢失;
  • 要么为特定应用定制 e‑graph 实现,难以复用。

本文介绍的工作发表于 CGO 2025,其提出了一种激进而优雅的解决方案:将 e‑graph 直接嵌入编译器的 IR 中,使其成为编译器的一部分,从而在整个编译流程中持久化地保留等价信息

作者基于 MLIR 框架和其 Python 实现 xDSL,设计了新的 eqsat dialect,并改造了 MLIR 的模式匹配基础设施,使开发者可以用完全相同的方式编写重写规则,既可用于传统破坏性重写,也可用于等式饱和。通过复现 Herbie 的核心功能,作者证明了该方法的灵活性和潜力。

一、等式饱和基础:e-graph 与 e-matching

在深入技术细节之前,我们先回顾 e‑graph 的核心概念。

一个 e‑graph 由若干 e‑class 组成,每个 e‑class 包含多个 e‑node ,这些 e‑node 在语义上是等价的。e‑node 类似于抽象语法树中的节点,但它的子节点指向的是 e‑class 而非具体节点。

例如,表达式 a * 2 可以表示为一个 e‑class(记为 C1)包含一个乘法 e‑node,其左子指向变量 a 所在的 e‑class,右子指向常数 2 所在的 e‑class。当应用重写规则 x*2 → x<<1 时,并不会删除原来的乘法节点,而是向 C1 中添加一个新的左移 e‑node,从而将两种表示都保留在同一个 e‑class 中。

为了在这种共享结构上进行模式匹配,需要 e‑matching 算法。与普通模式匹配不同,e‑matching 必须考虑 e‑class 内的多个候选,并利用变量间的相关性来剪枝。高效的 e‑matching 通常将多个模式编译成一个自动机,并共享公共子模式以减少重复工作。

二、现有方法的不足:外部库与定制实现的局限

图 1 清晰地勾勒了当前三种集成方式的对比:

CGO'25 新突破:基于MLIR的持久化e-graph技术,彻底解决编译器阶段顺序难题

图 1 | 现有研究仅针对单一抽象层级开展工作,要么是(b)协调外部的等式饱和库,要么是(c)为特定使用场景原生支持 e-graph。相比之下,我们的持久化等式饱和方法(d)能够复用现有的编译器转换逻辑,且能在不同抽象层级间保留推导出的等式信息。整体来说,能看出编译器中等式饱和的这三类实现方式中,传统方案或依赖外部库、或仅适配专属场景,均存在抽象层级单一、等式信息易丢失的问题。而本文的持久化方案将 e-graph 嵌入编译器 IR,实现了跨层级的等式保留,还能直接复用编译器现有转换流程,从根本上突破了传统方案的应用局限。

  • 图 1a :传统编译器流程,优化遍顺序固定。
  • 图 1b :调用外部等式饱和库。程序被翻译到库的 IR,饱和后再翻译回编译器 IR。缺点:翻译开销大,且一旦提取,所有其他等价形式立即丢失,后续遍无法再利用这些信息。
  • 图 1c :深度定制,如 Cranelift 的 acyclic e‑graph。它直接使用 e‑graph 表示 IR,但为了编译速度牺牲了通用性如不支持循环,且无法与其他编译器基础设施(如已有的分析遍)协同工作。

本文提出的方法(图 1d)则是在编译器内部以一等公民的身份实现 e‑graph,使其能够贯穿整个编译流程,并重用现有的分析、转换和模式匹配机制。

三、核心创新:将 e-graph 嵌入编译器 IR

3.1 MLIR 基础:可扩展的 SSA 中间表示

本文的实现基于 MLIR(Multi-Level Intermediate Representation)框架。MLIR 将用户可扩展性作为核心设计,通过 dialect 组织不同抽象层次的操作,并利用 SSA(静态单赋值)形式简化分析与转换。

每个操作可以包含 region,而 region 又由基本块组成,支持图区域(graph region)以允许循环定义,如图 2 所示。这种灵活的结构为在编译器内部嵌入 e-graph 提供了理想的基础。

CGO'25 新突破:基于MLIR的持久化e-graph技术,彻底解决编译器阶段顺序难题

图 2 | MLIR 的模块化结构。其以 SSA 为基础,融合了方言、操作、属性等设计,为核心的可扩展性提供了支撑,使得嵌入 e-graph 成为可能。

基于 MLIR 的 Python 实现 xDSL,我们引入了一个新的 dialect——eqsat,它通过四个核心操作在 SSA 形式的 IR 中完整表示 e-graph。

3.2 eqsat dialect 设计

作者在 MLIR 中引入了一个新的 dialect——eqsat,它包含四个核心操作,足以在 SSA 形式的 IR 中完整表示一个 e‑graph:

  • eqsat.eclass 接受一个或多个值(即 e‑node 的结果),产生一个代表该 e‑class 的 SSA 值。它的每个操作数都对应一个 e‑node。
  • eqsat.const_eclass 专为常量设计的变体,它直接持有一个常量属性,可以触发 MLIR 的常量折叠机制。
  • eqsat.egraph 一个图区域(graph region)操作,其体内包含所有与该 e‑graph 相关的 eclass 操作。图区域允许循环定义,这对于表示由重写引入的循环(如 a + 0 → a)至关重要。
  • eqsat.yield 作为 egraph 的终结符,将指定的 e‑class 结果暴露给外部。

将普通函数转换为 e‑graph 的过程非常直接:一个编译器遍会遍历函数,将每个操作的结果用 eqsat.eclass 包装,并将所有对原始值的引用替换为对对应 e‑class 结果的引用。Listing 3 展示了这一转换。

CGO'25 新突破:基于MLIR的持久化e-graph技术,彻底解决编译器阶段顺序难题

Listing 3 | 通过插入 eqsat.eclass 等操作,将普通 IR 转换为 e‑graph 表示。右侧是等价的 e‑graph 可视化,虚线框代表 e‑class。

当应用重写 x*2 → x<<1 时,新的左移操作及其常量被创建,但原有的乘法并未被删除,而是将其结果也添加为同一个 e‑class 的成员Listing 4)。这正是等式饱和的精髓——保留所有等价形式。

CGO'25 新突破:基于MLIR的持久化e-graph技术,彻底解决编译器阶段顺序难题

Listing 4 | 应用重写后,eqsat.eclass 现在包含两个成员:乘法和左移,实现了 e‑class 的扩展。

3.3 从传统重写到 e-matching:改造 PDL

MLIR 提供了一个强大的声明式重写框架 PDL(Pattern Definition Language),开发者可以用 PDL 编写模式,然后编译成高效的匹配器(pdl_interp dialect)。

传统上,PDL 匹配器是破坏性的——匹配到的模式会被替换。作者保留了 PDL 的语法,但重新实现了 pdl_interp 的解释器,使其能够感知 eqsat.eclass 的间接层,并支持回溯。

CGO'25 新突破:基于MLIR的持久化e-graph技术,彻底解决编译器阶段顺序难题

图 3 | 传统 MLIR 重写与 eqsat 中操作关联的核心差异。传统框架中操作的结果与定义源一一对应,而 eqsat 因引入 e-class,一个值对应多个等价操作,打破了原有逆操作关系。

关键挑战在于 pdl_interp.get_defining_oppdl_interp.get_result 这类操作。
* 在普通 IR 中,它们互为逆操作;
* 但在 e‑graph 中,一个值可能由多个 e‑node 定义,get_defining_op 需要返回所有可能的定义操作。

解释器通过维护回溯点来处理:当一条路径失败时,回退到最近的选择点并尝试 e‑class 中的下一个 e‑node。

此外,pdl_interp.create_operation 不再盲目创建新操作,而是先检查是否已存在相同的操作(哈希一致性,hashconsing),以避免重复。pdl_interp.replace 则不再删除原操作,而是将替换值加入对应的 e‑class。

3.4 优化 e-matching:避免指数爆炸

将多个 PDL 模式简单地组合成一个匹配器,直接用于 e‑matching 会导致性能灾难。原因在于默认的 PDL 到 pdl_interp 的降级策略优先进行结构检查,再进行语义检查。

例如,对于模式 f(a, g(a)),匹配器会先获取外层操作的两个操作数,然后对内层操作获取其操作数,最后才检查操作名称是否匹配。在 e‑matching 中,每次获取操作数都可能引入多个候选(因为操作数指向的是 e‑class),导致组合爆炸。

作者提出了两种优化:

  1. 优化1:尽早进行语义检查:将操作名称等语义检查尽可能提前,一旦发现不匹配立即回溯,避免不必要的结构探索,见图 4。

CGO'25 新突破:基于MLIR的持久化e-graph技术,彻底解决编译器阶段顺序难题

图 4 | 启用即时语义检查前后的 pdl_interp 匹配代码对比。通过尽早执行语义检查,能够避免回溯点数量的指数级激增。

  1. 优化2:引入 eqsat_pdl_interp.choose 操作:当匹配器从尝试一个模式切换到另一个模式时,原来的回溯点会被保留,造成多个不相关模式的回溯点混杂。choose 操作显式地标记分支点,使得回溯时可以直接跳转到当前模式的选择点,隔离不同模式的回溯栈,见图 5。

CGO'25 新突破:基于MLIR的持久化e-graph技术,彻底解决编译器阶段顺序难题

图 5 | 通过引入 choose 操作分离不同模式的回溯点,避免了跨模式回溯导致的指数级增长问题。

实验表明,这些优化在小规模测试中带来了 2.34 倍的几何平均加速,在后续的 Herbie 案例中达到 2.57 倍。

3.5 利用现有分析实现 e-class 分析

e‑class 分析是 egg 引入的重要扩展,它允许在 e‑class 级别上传播抽象信息(如区间、符号性等)。

MLIR 本身拥有强大的稀疏数据流分析框架,作者巧妙地将其复用:只需为 eqsat.eclass 定义 visit 方法为其操作数的格值的 meet,数据流求解器就能自动在 e‑graph 上传播信息。这意味着任何现有的 MLIR 分析(如常量传播、区间分析)无需修改即可成为 e‑class 分析。

对于常量折叠,作者采用 eqsat.const_eclass 配合 MLIR 的 fold 机制,在插入新操作时立即尝试折叠,这比 egg 的懒加载 modify 更积极。

四、案例研究:浮点精度优化(Herbie 复现)

为了验证 eqsat 的实用性,作者选择复现著名的浮点表达式精度优化工具 Herbie 的核心部分。Herbie 使用等式饱和搜索与原始表达式在实数域等价、但在浮点运算中误差更小的表达式。

4.1 Herbie 工作原理回顾

给定一个浮点表达式,Herbie 会:
1. 生成随机样本点作为输入。
2. 应用一系列数学重写规则(如分配律、结合律、恒等式),在 e‑graph 中构建大量候选表达式。
3. 对每个候选,用高精度计算其在样本点上的真实值,并与浮点计算结果比较,得到误差。
4. 选择误差最小的表达式输出。

Herbie 最初使用 Racket 实现,后来借助 egg 库大幅加速。图 6 展示了本文实现的流程。

CGO'25 新突破:基于MLIR的持久化e-graph技术,彻底解决编译器阶段顺序难题

图 6 | 结合现有的和全新的编译器转换方法与分析手段,我们复现了 Herbie 工具主循环的单次迭代过程。该图梳理了基于 eqsat 方言复现 Herbie 核心流程的八大步骤,从 FPCore 解析到 MLIR 映射,再到 e-class 插入、PDL 规则转换、等式饱和执行,最终完成精度分析与代码提取。整个流程全程基于编译器原生 IR 与基础设施,无需外部工具转换,验证了 eqsat 方言在实际等式饱和应用中的可行性与兼容性。

4.2 实现流程

  1. 解析与转换:将 Herbie 的输入语言 FP Core 翻译成 MLIR 的 arithmath 方言操作。
  2. 构建 e‑graph:通过插入 eqsat.eclass 将函数体包装成初始 e‑graph。
  3. 样本生成:运行区间分析确定每个变量的有效范围,并在该范围内均匀采样,避免除以零等错误。
  4. 重写规则:将 Herbie 的 egglog 规则集(包含前置条件,如要求某个子表达式非负)手工转换为 PDL 模式。这些前置条件通过 e‑class 分析(区间、非零等)来验证。
  5. 等式饱和:运行改造后的 PDL 匹配器,直到 e‑节点数达到 4000 或无法再添加新节点。
  6. 计算真实值:对于每个 e‑class,只需计算其中一个 e‑节点的高精度值(使用 MPFR 库,1024 位精度),因为所有节点在实数域等价。
  7. 误差计算:对每个 e‑节点,用浮点计算其在样本点上的值,与高精度真实值比较,取对数平均误差作为代价。
  8. 提取:运行贪心选择,将误差作为代价,最终提取出最优表达式并转换回 FP Core 格式,以便与 Herbie 的结果公平比较。

4.3 准确性对比

在 FPBench 的 31 个 Herbie 基准测试上,本文实现与 Herbie 的结果进行了准确性对比,见图 7。

CGO'25 新突破:基于MLIR的持久化e-graph技术,彻底解决编译器阶段顺序难题

图 7 | 我们的方法在 FPBench 大多数基准测试中能够达到与 Herbie 工具相当的精度。精度上的差异源于重写规则的细微不同,以及因早期超时导致的潜在规则遗漏。对于三个表达式精度结果一致(误差在 1% 以内)的基准测试,我们在视觉上做了淡化处理。总得来说,eqsat 与 Herbie 二者整体精度持平,少数案例的差异源于规则集细微不同、部分函数未实现及 e-nodes 数量限制。这一结果验证了 eqsat 方言在浮点表达式精度优化场景下的实际效果,证明其能复现专业等式饱和工具的核心能力,具备实际应用价值。

总体而言,两者在大多数基准上精度相当。少数差异归因于:
* 本文未实现某些函数,如 hypot
* 规则集略有不同。本文使用 egglog 规则,Herbie 有自己的一套;
* Herbie 可能使用了一些无法证明其安全性的规则,而本文依赖 e‑class 分析确保规则应用前提成立;
* 非确定性:由于 e‑节点数限制,不同规则应用顺序可能导致不同的最终候选。

有趣的是,在某些基准上本文实现反而优于 Herbie,同样可能是因为规则集差异和探索顺序的偶然性。

4.4 性能分析与讨论

性能方面,本文实现比 Herbie 慢约 400 倍(几何平均,见图 8)。

CGO'25 新突破:基于MLIR的持久化e-graph技术,彻底解决编译器阶段顺序难题

图 8 | 我们的实现版本比 Herbie 工具慢得多,这主要是因为 Herbie 和 egg 库的实现中包含诸多性能优化手段,而这些优化目前尚未应用到我们基于 xDSL 的实现中。该图对比了 eqsat 与 Herbie 在基准测试中的运行耗时,前者存在显著的速度差距,核心原因包括 xDSL 的 Python 实现效率低于 Herbie 的 Rust+Racket 组合、MPFR 高精度计算未做动态精度优化、还需执行重型区间分析。但这一差距并非算法层面的问题,论文指出将实现迁移到 C++ 的 MLIR 后,性能可得到大幅提升。

主要原因:
* 语言层面:xDSL 基于 Python,而 Herbie 后端是 Rust(egg)和 Racket,两者都远快于 Python。
* 高精度计算开销:本文使用固定 1024 位 MPFR 计算所有样本,而 Herbie 动态调整精度以加速。
* 分析开销:egglog 规则要求复杂的区间分析,Herbie 默认避免在饱和过程中运行分析。
* 优化程度:egg 经过多年打磨,而 eqsat 尚处于原型阶段。

尽管如此,仅考虑等式饱和阶段(不包括高精度计算),组合匹配器相比逐个匹配模式仍有 2.57 倍的加速见图 9,验证了匹配优化的有效性。

CGO'25 新突破:基于MLIR的持久化e-graph技术,彻底解决编译器阶段顺序难题

图 9 | 将所有的 PDL 模式整合为单个 pdl_interp 执行等式饱和,与逐个匹配每个模式的方式相比,能带来显著的速度提升。该比较验证了本文 pdl_interp 优化的实际效果,将所有重写模式整合为单个匹配例程,相比逐个匹配模式,实现了 2.57 倍的几何平均速度提升。这是因为整合后的匹配例程能复用公共子模式的匹配逻辑,避免重复计算,结合前文的即时语义检查、选择操作优化,让 e-matching 的效率得到了多重提升。

五、相关工作

等式饱和与编译器的结合已有诸多探索。论文将其分为三类:

  • 外部库集成:如 egg 和 egglog,被用于 Herbie、SEER、ROVER 等。优点是性能好,缺点是需要来回翻译且无法保留等价信息。
  • 定制实现:如 Cranelift 的 acyclic e‑graph,将 e‑graph 作为 IR 的核心,但为速度牺牲了通用性(不支持循环),且难以复用编译器其他部分。
  • 混合方法:DialEgg 将 MLIR 与 egglog 连接,降低了翻译开销,但仍需在每次调用后丢弃 e‑graph,无法跨遍持久化。

本文工作属于“深度嵌入”,将 e‑graph 作为编译器的一等公民,既保留了通用性,又实现了持久化,并能重用现有分析、转换和模式匹配。

总结与展望

本文提出了一种将等式饱和持久化嵌入编译器的新方法。通过在 MLIR 中引入 eqsat dialect,并改造 PDL 匹配器,作者展示了如何让 e‑graph 贯穿整个编译流程,避免翻译开销和信息丢失,同时能够利用编译器现有的丰富基础设施。案例研究成功复现了 Herbie 的核心功能,证明了方法的可行性。

未来工作包括:

  • 将实现移植到 C++ 版本的 MLIR 中,以大幅提升性能。
  • 结合引导式等式饱和(guided equality saturation),利用编译器中的具体目标(如循环深度、指令数)引导探索方向。
  • 支持混合代价模型,例如在性能与浮点精度之间权衡。
  • 扩展对控制流的支持,目前 PDL 尚不能匹配带有区域的操作(如循环),未来有望实现对控制流本身的等式饱和。

等式饱和曾被质疑其高昂的开销,但随着硬件性能提升和算法优化,它正逐渐从学术玩具变为实用的编译器技术。这项工作为编译器社区提供了一种低门槛、高灵活性的集成方案,有望加速等式饱和在工业编译器中的普及。


关注“鲸栖”小程序,掌握最新AI资讯

本文来自网络搜集,不代表鲸林向海立场,如有侵权,联系删除。转载请注明出处:https://www.itsolotime.com/archives/22128

(0)
上一篇 21小时前
下一篇 12小时前

相关推荐

  • 构建真正会“思考”的AI:Agentic RAG全面指南

    注:本文为技术内容,诸如 RAG、Agentic、Vector Database、SQL、Embedding、Cross-Encoder、LLM 等专业术语均保留英文原文,以保证准确性与可检索性。 🤔 问题:为何多数 AI 助手显得“笨拙” 设想你向一位财务分析师提问:“我们公司表现如何?” 一位初级分析师可能会匆忙给出几个数字。而一位资深专家则会先停下来,…

    2025年11月28日
    14000
  • OpenAI研究员揭秘:Codex内部评估机制与AI产品落地的50+实战经验

    昨日,两位来自 OpenAI 及前微软的 AI 产品一线从业者——Aishwarya Naresh Reganti 与 Kiriti Badam,在 Lenny 的播客节目中深入分享了他们在超过 50 个 AI 产品落地项目中的实践经验与教训。 这些经验源于反复的试错与总结。播客主持人 Lenny 提炼出一个核心观点:痛苦是新的护城河。 两位嘉宾均具备深厚的…

    2026年1月12日
    15800
  • 资深工程师构建AI系统的实战方法论:从约束到防御性设计

    Image by SORA “我该用哪一个模型?”——初级工程师会这样问。“哪里会先坏?”——资深工程师会这样问。 大多数 AI 程序在演示中光鲜亮丽,在生产中却悄无声息地失效,原因就在这里。 AI 并没有让软件工程变简单。它只是揭示了谁本来就做得好。 模型是最容易的部分——如果你见过一个 AI 功能在预发布环境里一切顺利,却在真实流量、脏数据和不可预测的用…

    2026年1月25日
    9200
  • 2026年开源LLM选型指南:从模型规模到架构实战,打造安全可控的AI应用

    到了2026年,关于AI的讨论焦点已经发生了根本性转变。我们早已告别了“聊天机器人演示”的早期阶段。如今,严肃的企业正在构建自己的内部AI解决方案。他们深刻认识到,虽然外部API服务便捷,但公司的核心数据才是最具价值的资产,他们不愿将处理这些数据的关键“大脑”长期租用给外部供应商。 与此同时,技术领域也迎来了一个重大里程碑:开源大语言模型(LLM)的性能已经…

    2026年2月6日
    30200
  • 微软MIRA:基于4540亿医疗时序数据的通用基座模型,突破不规则采样难题

    MIRA团队 投稿 在大模型(LLM)与计算机视觉(CV)争相重塑医疗行业的今天,我们似乎已经拥有了功能强大的数字助手:它们能够像放射科医生一样精准解读CT影像,也能像内科医生一样撰写病历摘要。 但医疗AI世界中,仍有一块关键拼图缺失——那就是理解“生命动态演变”的能力。 △ 图1.不同模态的医疗数据 正如图1所示,如果将患者的生命历程比作一部电影,现有的A…

    2026年1月24日
    10500