想象你站在一座大型建筑工地的中央。总建筑师站在高处,盯着画满线条的总平面图,图上标着“此处设观景平台”;结构工程师在办公室摊开施工图,写的是“C30混凝土、配筋率1.2%”;而工人师傅手里的交底单,精确到“坐标(3.2, 5.6)处浇筑M20螺栓”。同一栋楼,三种人说着三种完全不同的“行话”。在 MLIR 的世界里,这套“行话”就是方言(Dialect)——每种方言封装一组相关的操作、类型与属性,服务于特定的抽象层次。高层方言描述“要做什么”,底层方言描述“具体怎么做”,而 Lowering 机制则像工地上的翻译官,把总建筑师的意图逐级翻译成工人能执行的指令。正是这套精妙的方言体系,让 AI 编译器在“神经网络设计”与“硬件指令执行”这两座孤岛之间架起了桥梁。

岗位关联

理解方言体系,等于掌握了 MLIR 的“语法骨架”——对 AI 编译器工程师而言,这是构建自定义编译流程、对接各类 AI 硬件加速生态的核心技能。

目录
  1. 可视化演示
  2. 方言概念
  3. 核心作用
  4. 多级表示
  5. Lowering机制
  6. 核心设计
  7. 生态应用
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示MLIR方言Dialect对操作/类型/属性的封装机制、标准方言的类型分类体系(从高层算子到底层指令)以及通过Lowering实现方言间层次转换的编译流水线,交互切换三种模式探索。

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

'+CAPS.mode1+'

方言概念

回到建筑工地。总建筑师口中的“观景平台”、结构工程师笔下的“悬挑梁”、水电工单子上的“DN50 管径”,每一套行话都在自己的圈子里清晰无误。MLIR 中的 方言(Dialect) 就是这样的行话系统:它把一组相关的 操作(Operation)、类型(Type) 与 属性(Attribute) 打包成一个可独立理解、组合和扩展的模块。

为什么非要这样封装?因为 AI 编译器的输入千奇百怪:有人从 TensorFlow 导入模型,有人从 PyTorch 转出图,还有人直接从手写 IR 开始。如果所有代码都混在一张“大图”里,就像把建筑师的图纸、结构计算书、施工交底单全部叠在一起,谁也看不懂。方言提供了“分类收纳”的机制:每个方言内部有统一的词汇表和语法规则,跨方言则有清晰的边界。

在 MLIR 里,方言中的操作是计算的基本单元,类似一句话里的“动词”;类型是数据的形状与性质,类似“名词”;属性则是“副词”,描述操作的静态配置。三者共同构成一个自洽的封闭体系。数学上可以这样表达一个方言的结构:

正是这种“词汇手册”式的设计,让 MLIR 的不同组件可以各自使用自己熟悉的行话交流,而不会互相干扰。

D=(O,T,A)\mathcal{D} = (\mathcal{O}, \mathcal{T}, \mathcal{A})
这个公式说明,一个方言就是由操作集合、类型集合和属性集合组成的三元组,三者互相配合、缺一不可。
  1. 方言符号$\mathcal{D}$ 表示一个完整的方言,相当于一个专业领域的“词汇手册”,把相关术语整齐地收在一起。
    D\mathcal{D}
  2. 操作集合$\mathcal{O}$ 存放该方言支持的所有计算动作,如 matmul、conv2d、relu 等,就像词典里的动词表。
    O={matmul,conv2d,relu,… }\mathcal{O} = \{\text{matmul}, \text{conv2d}, \text{relu}, \dots\}
  3. 类型集合$\mathcal{T}$ 定义数据如何流动,如张量(tensor)、标量(scalar)、索引(index),相当于词典里的名词表。
    T={tensor,scalar,index,… }\mathcal{T} = \{\text{tensor}, \text{scalar}, \text{index}, \dots\}
  4. 属性集合$\mathcal{A}$ 记录操作的静态信息,比如矩阵乘是否转置、卷积的 padding 大小,相当于词典里的副词表。
    A={transpose,padding,stride,… }\mathcal{A} = \{\text{transpose}, \text{padding}, \text{stride}, \dots\}
  5. 三元组合三个集合一起构成完整的方言,这种组合方式保证了“动词—名词—副词”可以配套使用,形成可复用的模块。
    (O,T,A)(\mathcal{O}, \mathcal{T}, \mathcal{A})

核心作用

工地上同一栋楼,为什么需要三种图纸?因为每种图纸的“分辨率”不同:总平面图 1:500,结构图 1:100,大样图 1:20。方言的核心作用,正是为不同“分辨率”的中间表示提供合适的表达容器。

第一个作用是组织中间表示。在 MLIR 出现之前,编译器的中间表示(IR)往往是单一结构:所有操作都塞进同一套语法体系,结果高层语义和底层细节纠缠不清。方言像一个分类归档系统,把杂乱无章的 IR 按功能域分门别类:训练相关的算子放进 tf 方言,张量运算放进 tosa 方言,指针操作放进 llvm 方言。这样,读者一眼就能看出某段 IR 处于哪个抽象层次。

第二个作用是表达抽象层次。高层方言关注“计算意图”,底层方言关注“机械细节”。方言天然携带“抽象级别”信息——看到 tf.MatMul,你知道这是神经网络里的矩阵乘法;看到 llvm.int.add,你知道这是芯片上的整数加法。不同层次的计算语义不会混淆。

第三个作用是统一语法与语义规则。每个方言内部定义了一套一致的语法结构(操作怎么书写、类型怎么声明),也定义了严格的语义约束(哪些类型合法、哪些属性允许)。这种“内部统一”让工具链可以按方言为单位做校验和优化。整个模型可以看作多个方言操作的组合:

这种“多方言共存”的模块化组织,使得每种抽象层次都有自己的表达空间,也让后续的优化与转换有了清晰的作用对象。

Module={Op1,Op2,…,Opn},Opi∈Ddi\mathrm{Module} = \{\mathrm{Op}_1, \mathrm{Op}_2, \dots, \mathrm{Op}_n\}, \quad \mathrm{Op}_i \in \mathcal{D}_{d_i}
一段 MLIR 程序是由多个操作组成的集合,其中每个操作都可以来自不同的方言,互不冲突。
  1. 模块符号$\mathrm{Module}$ 表示一个完整的编译单元,相当于一栋建筑的总承包合同,涵盖所有分项。
    Module\mathrm{Module}
  2. 操作集合$\{\mathrm{Op}_1, \mathrm{Op}_2, \dots, \mathrm{Op}_n\}$ 表示整个模块由 n 个操作组成,就像合同里列出的所有施工条目。
    {Op1,Op2,…,Opn}\{\mathrm{Op}_1, \mathrm{Op}_2, \dots, \mathrm{Op}_n\}
  3. 归属关系$\mathrm{Op}_i \in \mathcal{D}_{d_i}$ 表示每个操作都隶属于某个方言,就像每项工程都归属一个专业团队。
    Opi∈Ddi\mathrm{Op}_i \in \mathcal{D}_{d_i}
  4. 整体含义不同方言的操作可以在同一个 Module 里共存,正如建筑、结构、水电三个团队能同时服务一栋大楼。
    Module=⋃i=1n{Opi}\mathrm{Module} = \bigcup_{i=1}^{n} \{\mathrm{Op}_i\}

多级表示

一栋建筑从概念到落地,要经历“方案设计—初步设计—施工图—现场交底”多个阶段,每个阶段图纸的颗粒度不同。MLIR 的思想如出一辙:用多级方言来表示计算从“抽象意图”到“具体执行”的完整链条。

高层图操作是起点。它描述的是“做什么”,比如“对一批图片做分类”,对应的是一张包含 conv2d、relu、softmax 等大粒度算子的计算图。这类方言(如 tf、mhlo)贴近前端框架,便于高层优化,比如算子融合、常量折叠。

中层优化变换是中间层。它把高层的大算子拆解为更基本的张量运算,并在这里执行内存布局优化、循环变换等通用优化。比如 linalg 方言用结构化方式描述张量运算,tosa 方言则作为跨厂商的公共表示层,让英伟达、高通、ARM 的芯片都能理解同一套张量运算语义。

底层指令映射则贴近硬件。到了 llvm 方言,操作已经变成整数加减、指针加载、条件跳转等机器指令级描述,再往下就可以直接对接 LLVM 后端生成汇编。这三级层次之间通过 Lowering 连接,形成一条可逐级下钻的链路:

正是这条“逐级细化的表示链”,让 AI 编译器既能保留高层优化空间,又能精准生成底层代码——就像建筑团队既懂设计意图,又能给出可施工的细节。

IR0→Lowering1IR1→Lowering2IR2→ ⋯ IRk\mathrm{IR}_0 \xrightarrow{\text{Lowering}_1} \mathrm{IR}_1 \xrightarrow{\text{Lowering}_2} \mathrm{IR}_2 \xrightarrow{\ \cdots\ } \mathrm{IR}_k
初始的高层 IR 经过多次 Lowering 逐步降低抽象层级,最终得到接近硬件指令的底层 IR。
  1. 初始表示$\mathrm{IR}_0$ 是前端导入的原始计算图,相当于建筑师的方案草图。
    IR0\mathrm{IR}_0
  2. 降级动作$\xrightarrow{\text{Lowering}_1}$ 表示第一次降级操作,由一组转换模式完成,类似于“把方案图深化为初步设计图”。
    →Lowering1\xrightarrow{\text{Lowering}_1}
  3. 中间表示$\mathrm{IR}_1, \mathrm{IR}_2$ 是降级过程中产生的每一层新 IR,相当于逐步细化的设计文档。
    IR1,IR2\mathrm{IR}_1, \mathrm{IR}_2
  4. 最终表示$\mathrm{IR}_k$ 是最后一级表示,已接近目标硬件指令,相当于交给工人的施工交底单。
    IRk\mathrm{IR}_k

Lowering机制

假设翻译官接到指令:“请在二楼安装钢梁。”他不会直接把这句话念给焊工听,而是转换为:“在标高 4.2 米处,吊装 H 型钢,M20 高强螺栓紧固,扭矩 300 牛米。”这个转换过程,就是编译世界里的 Lowering(降级)。

MLIR 的 Lowering 机制包含三个关键要素。首先是层次下降过程:一个操作的抽象级别逐层降低,每经过一次转换,旧的语义被细化成若干条更接近硬件的操作。其次是模式匹配规则:每条规则(Pattern)规定“什么样的高層操作,被替换成什么样的低层操作序列”。编译器的 rewriting driver 会遍历 IR 中的每个操作,找到匹配的模式并执行替换。最后是转换链设计:实际降级很少一步到位,而是由多个 Pass 按顺序拼成一条流水线,每个 Pass 负责一小步,比如“先拆 matmul,再展开循环,最后标量替换”。

模式匹配规则可以这样抽象地表示:

有了模式匹配和转换链,Lowering 就像一支训练有素的翻译团队:每层只翻一小段,但接力起来就能把“高层意图”完整无损地落到“底层指令”。

Pattern:Ophigh ⇒ Opmid,1∘Opmid,2∘⋯∘Opmid,m\mathrm{Pattern}: \quad \mathrm{Op}_{\mathrm{high}} \ \Rightarrow \ \mathrm{Op}_{\mathrm{mid},1} \circ \mathrm{Op}_{\mathrm{mid},2} \circ \cdots \circ \mathrm{Op}_{\mathrm{mid},m}
一条降级规则就是将高层的单个操作,替换为一串更低层次操作的顺序组合,等价于“把一句总指令翻译成多条分指令”。
  1. 模式名称$\mathrm{Pattern}$ 表示一条转换规则,相当于翻译手册里的一个词条。
    Pattern\mathrm{Pattern}
  2. 源操作$\mathrm{Op}_{\mathrm{high}}$ 是被匹配的高层操作,相当于待翻译的“源语言”词句。
    Ophigh\mathrm{Op}_{\mathrm{high}}
  3. 重写箭头$\Rightarrow$ 表示“重写为”,强调这不是简单的别名替换,而是语义等价的结构改写。
    ⇒\Rightarrow
  4. 目标序列$\mathrm{Op}_{\mathrm{mid},1} \circ \mathrm{Op}_{\mathrm{mid},2} \circ \cdots \circ \mathrm{Op}_{\mathrm{mid},m}$ 表示 m 个低层操作按顺序连接,用复合符号 $\circ$ 表示执行先后关系。
    Opmid,1∘Opmid,2∘⋯∘Opmid,m\mathrm{Op}_{\mathrm{mid},1} \circ \mathrm{Op}_{\mathrm{mid},2} \circ \cdots \circ \mathrm{Op}_{\mathrm{mid},m}

核心设计

好的工程队,不是把所有工人都塞进同一个班组,而是按专业分开:土建、安装、装修、幕墙,各司其职,通过标准接口协作。MLIR 的核心设计哲学正是如此——方言是可插拔组件,框架只是“施工总包平台”。

第一个关键词是可扩展插件。任何团队或个人都可以自定义方言:只要注册一组新的操作、类型和属性,就能让 MLIR 认识一门“新语言”。这就像工地请来一位声学顾问,他带着自己的术语(“混响时间”“隔声量”)加入项目,不需要改变总包的管理流程。AI 芯片公司常做的一件事,就是为自家 NPU 定义一套专用方言,让高层模型可以直接降到自己的硬件语义上。

第二个关键词是方言互操作。不同方言的操作可以在同一个 IR 中自由连接:上游方言的输出,直接作为下游方言操作的输入。就像结构工程师的“梁截面”可以直接被水电工程师引用为“穿管位置”的依据。这种互操作性依赖于共享的类型系统和统一的 IR 数据格式。

第三个关键词是模块化架构。MLIR 核心本身不依赖任何特定方言,所有基础设施——Pass 管理、IR 打印、验证器——都通过接口与方言交互。新增方言就像给电脑插上 U 盘,即插即用,无需改动主板。方言间的数据流关系可以这样表达:

正是因为“即插即用”的模块化设计,MLIR 才能在短短几年内生长出一个繁荣的编译器生态——所有人都能贡献自己的“行话”,而无需重写整个框架。

Opi∈Di, Opj∈Dj,Operand⁡(Opi)=Result⁡(Opj)\mathrm{Op}_i \in \mathcal{D}_i, \ \mathrm{Op}_j \in \mathcal{D}_j, \quad \operatorname{Operand}(\mathrm{Op}_i) = \operatorname{Result}(\mathrm{Op}_j)
不同方言中的两个操作可以首尾相接:一个操作的输出成为另一个操作的输入,这是方言互操作的基础。
  1. 两个操作$\mathrm{Op}_i$ 与 $\mathrm{Op}_j$ 分别来自不同方言 $\mathcal{D}_i$ 与 $\mathcal{D}_j$,如同两个不同专业团队派出的代表。
    Opi∈Di,Opj∈Dj\mathrm{Op}_i \in \mathcal{D}_i, \quad \mathrm{Op}_j \in \mathcal{D}_j
  2. 操作数说明$\operatorname{Operand}(\mathrm{Op}_i)$ 是第 i 个操作的输入操作数,相当于该施工班组的“材料需求清单”。
    Operand⁡(Opi)\operatorname{Operand}(\mathrm{Op}_i)
  3. 结果来源$\operatorname{Result}(\mathrm{Op}_j)$ 是第 j 个操作的输出结果,相当于另一个班组交付的半成品。
    Result⁡(Opj)\operatorname{Result}(\mathrm{Op}_j)
  4. 互操作条件等式 $\operatorname{Operand}(\mathrm{Op}_i) = \operatorname{Result}(\mathrm{Op}_j)$ 说明两个不同方言的操作可以无缝衔接,形成数据流。
    Operand⁡(Opi)=Result⁡(Opj)\operatorname{Operand}(\mathrm{Op}_i) = \operatorname{Result}(\mathrm{Op}_j)

生态应用

在真实的大型项目里,总包、设计院、施工队的分工清晰而稳定。MLIR 生态中的几个重要方言,也像现实中的专业团队一样各司其职。

TensorFlow 方言(tf / mhlo) 是“总承包助手”,它直接接收 TensorFlow 或 JAX 模型,把训练图转换成 MLIR 可操作的高层表示。它保留了算子原有的高层语义,比如 tf.MatMul 明确知道这是一次神经网络中的矩阵乘法,方便做批量算子融合。

TOSA 方言 是“设计院标准化图纸”,由 Arm、高通等公司联合定义,目标是把不同框架的高层算子统一成一组跨平台张量运算原语。任何芯片厂商只要实现了对 TOSA 的支持,就能运行所有前端模型——这相当于“按国家标准出图,谁都能施工”。

LLVM 方言 是“施工现场包工头”,它包装了 LLVM IR 的语义,让 MLIR 可以直接使用 LLVM 的庞大后端生态(x86、ARM、RISC-V 等)。从 TOSA 降级到 LLVM 方言后,剩下的事情就交给了成熟的 LLVM 编译管线。用一个降级链可以概括:

从 TF 到 TOSA 再到 LLVM,这条经典的降级路径正是 MLIR 方言生态的缩影:高层方言负责理解意图,公共方言负责标准化,底层方言负责落地执行。三者默契配合,让“一次编写、多硬件部署”从理想变成现实。

tf.MatMul → tosa.MatMul → ⋃i llvm.int.fmai\mathrm{tf.MatMul} \ \to \ \mathrm{tosa.MatMul} \ \to \ \bigcup_{i} \ \mathrm{llvm.int.fma}_i
一个 TF 方言中的矩阵乘,先降到 TOSA 方言保持张量语义,再进一步拆成 LLVM 方言中的底层融合乘加指令。
  1. 高层操作$\mathrm{tf.MatMul}$ 是 TensorFlow 方言中的矩阵乘法操作,代表“我要做一次矩阵乘”的意图。
    tf.MatMul\mathrm{tf.MatMul}
  2. 中层操作$\mathrm{tosa.MatMul}$ 是 TOSA 方言中的矩阵乘,语义更明确、更独立于框架,相当于标准化的“设计图纸”。
    tosa.MatMul\mathrm{tosa.MatMul}
  3. 底层指令$\mathrm{llvm.int.fma}_i$ 是 LLVM 方言中的融合乘加指令(fused multiply-add),已经可以映射到具体 CPU 指令。
    ⋃i llvm.int.fmai\bigcup_{i} \ \mathrm{llvm.int.fma}_i
  4. 箭头含义每一步 $\to$ 都是一次 Lowering,等价于把“高层意图”翻译成“图纸”,再把“图纸”翻译成“施工指令”。
    →\to

AI 应用场景

场景 1:AI 新芯片编译器搭建

阶段:推理部署 / 算子库生成

解决的问题:自研 NPU 芯片往往无法直接运行 TensorFlow 或 PyTorch 模型。传统做法是为每个算子手写 C/CUDA 级内核,开发周期长、难以维护;更重要的是,芯片升级后所有算子都要重写。借助方言体系,工程师为 NPU 定义专属方言,再利用 Lowering 把高层模型逐级降到自家方言,实现“一次表达、多处复用”。

具体案例:某 AI 芯片公司要支持 BERT 模型推理。工程师先在 MLIR 中注册 npu 方言,定义 npu.conv2d、npu.gemm 等专用操作;随后编写 tf → tosa → npu 的转换 Pass;最终将 TensorFlow 的数千个节点降级为 NPU 指令,推理性能达到手写算子的 90%,而开发周期从半年缩短到六周。

场景 2:算子融合自动优化

阶段:训练前图优化 / 推理图优化

解决的问题:在传统编译器中,图优化往往只能做代数化简,难以跨界融合多个算子。每次 conv→bn→relu 都会在内存中产生中间张量,造成大量读写开销。MLIR 的方言系统允许编译器定义“融合算子”方言,通过模式匹配把一串小算子融合成一个高效内核,大幅减少内存搬运。

具体案例:某推理引擎团队在 MLIR 中定义 fused.op 方言,编写了一条 Lowering 模式:当检测到连续的 conv2d、batchnorm、relu 时,将其重写为一个融合算子。融合后,中间张量不再落回内存,寄存器直接传递数据,模型端到端推理延迟降低 30%。

场景 3:多硬件一次编写部署

阶段:跨平台部署

解决的问题:同一模型需要在 GPU、CPU、手机 NPU 三种硬件上部署,传统做法是为每个硬件写一套推理代码,三套代码维护成本极高。MLIR 的 TOSA 公共方言让“一次转译、多端下降”成为可能。

具体案例:某云厂商把目标检测模型转为 TOSA 方言作为公共中间态,再由三个后端分别把 TOSA 降到 GPU 方言(通过 LLVM)、CPU 方言(通过 LLVM)和自研 NPU 方言。模型结构变更时只需重新生成一次 TOSA,各后端自动同步更新。

Python 演示

python104 行
"""用 Python 模拟 MLIR 方言系统:方言注册与 Lowering 转换链"""from dataclasses import dataclass, fieldfrom typing import Dict, List, Optional  @dataclassclass Operation:    """MLIR 操作:属于某个方言,带属性和输入输出"""    name: str    dialect: str = "generic"    attrs: Dict[str, str] = field(default_factory=dict)    operands: List[Operation] = field(default_factory=list)     def __str__(self) -> str:        return f"{self.dialect}.{self.name}"  class DialectSpec:    """方言:封装一组操作、类型与属性规则"""    def __init__(self, name: str) -> None:        self.name = name        self.ops: set = set()        self.types: set = set()     def register_op(self, op_name: str) -> None:        self.ops.add(op_name)     def register_type(self, type_name: str) -> None:        self.types.add(type_name)     def supports(self, op: Operation) -> bool:        return op.name in self.ops  class DialectRegistry:    """方言注册表:类似建筑工地的各专业施工队名册"""    def __init__(self) -> None:        self.dialects: Dict[str, DialectSpec] = {}     def register(self, d: DialectSpec) -> None:        self.dialects[d.name] = d     def get(self, name: str) -> Optional[DialectSpec]:        return self.dialects.get(name)  def build_registry() -> DialectRegistry:    """构建高层(TF)、中层(TOSA)、底层(LLVM)三类方言"""    reg = DialectRegistry()     high = DialectSpec("tf")    high.register_op("matmul")    high.register_op("relu")    high.register_type("tensor")    reg.register(high)     mid = DialectSpec("tosa")    mid.register_op("matmul")    mid.register_op("add")    mid.register_op("clamp")    mid.register_type("tensor")    reg.register(mid)     low = DialectSpec("llvm")    low.register_op("fma")    low.register_op("add")    low.register_type("i32")    low.register_type("f32")    reg.register(low)     return reg  def lowering(reg: DialectRegistry, op: Operation) -> List[Operation]:    """将某个操作降级为下一层方言中等价操作的序列"""    if op.dialect == "tf" and op.name == "matmul":        target = reg.get("tosa")        if target:            return [Operation("matmul", "tosa", attrs={"pattern": "dot"})]    if op.dialect == "tosa" and op.name == "matmul":        target = reg.get("llvm")        if target:            return [Operation("fma", "llvm", attrs={"dim": str(i)}) for i in range(3)]    return [op]  def main() -> None:    reg = build_registry()    graph_op = Operation("matmul", "tf", attrs={"shape": "8x8"})    print("初始操作:", graph_op)     cur_ops: List[Operation] = [graph_op]    for level in range(3):        nxt: List[Operation] = []        for op in cur_ops:            nxt.extend(lowering(reg, op))        cur_ops = nxt        print(f"第{level+1}级下降:", ", ".join(str(o) for o in cur_ops))     print("终态均为底层:", all(o.dialect == "llvm" for o in cur_ops))  if __name__ == "__main__":    main()
目的:用约 90 行 Python 模拟 MLIR 的方言注册表和 Lowering 转换链,演示一个 tf.matmul 高层操作如何逐级下降到 TOSA 方言,再拆解为 3 个 LLVM 方言的 fma 指令。
  1. class DialectSpec:定义方言类,负责登记操作名与类型名,相当于给一个专业领域建立“行话词典”。
  2. self.ops: set = set()用集合保存该方言支持的操作名,类似词表,只收录本专业认可的“动词”。
  3. def register_op(self, op_name: str) -> None:在方言中注册一个新操作,等价于把新词条加入行话词典。
  4. def supports(self, op: Operation) -> bool:判断某个操作是否属于该方言,用于模式匹配前的“身份校验”。
  5. class DialectRegistry:方言注册表,统一管理所有方言,类似工地的施工队名册。
  6. def lowering(reg: DialectRegistry, op: Operation) -> List[Operation]:核心降级函数,输入一个操作,输出一串更低层次的操作。
  7. if op.dialect == "tf" and op.name == "matmul":模式匹配:命中 TF 方言的 matmul 操作,执行翻译。
  8. nxt.extend(lowering(reg, op))对每个当前操作执行下降并收集结果,构成“逐层接力”的转换链。
  9. all(o.dialect == "llvm" for o in cur_ops)验证终态是否全部为底层方言,类似最终检查“翻译是否都交到施工队手上”。

常见误区

误区初学者看到 MLIR 里有几十种方言,第一反应是“为什么不用一种统一的中间表示?这么多方言只会让编译器变得更难维护。”

正解方言就像工地上的不同专业图纸。你不会因为“图纸太多”就要求建筑师、结构师、水电工共用一张图——那样图面会乱到谁也看不懂。方言的意义正是让每种抽象层次拥有精确的表达工具,各司其职,再通过 Lowering 连接起来。

为何常见:传统编译器(如 LLVM)的 IR 是单一结构的,这种“单级 IR”思维惯性让初学者难以理解“多级方言”的必然性。

误区以为 Lowering 是一次破坏性操作:执行完转换后,原来的高层 IR 就消失了,之后再也无法查看或复用。

正解MLIR 的 IR 是持久化数据结构,Lowering 过程会构建新的低层 IR,而旧 IR 可以保留、打印或回滚。这就像翻译官翻译完一份英文合同,原文仍然留在档案柜里,随时可以调出核对。

为何常见:看到“rewrite(重写)”这个词时,直觉上把它等同于文本编辑器的“覆盖保存”,忽略了编译器 IR 的可持久化特性。

误区认为模型中的每个操作都必须完整穿过“高层→中层→底层→硬件”的所有阶段,缺一不可。

正解实际上 MLIR 允许不同操作停留在不同层次。已经适合底层执行的操作可以直接保留在 LLVM 方言,只有高层的算子才需要降级;甚至多个 Pass 可以并行处理不同区域,最终在任意层次生成目标代码。工地上的情况也一样——预制构件在工厂就完成了,不需要在施工现场再加工一次。

为何常见:教程中为了展示完整的降级链条,常以串行路径举例,容易让读者误以为这是唯一路径。

误区看到 `tf.matmul`、`tosa.matmul` 这种写法,就以为方言只是把操作名加一个前缀做区分,像 Python 的 `namespace`。

正解前缀只是命名约定的表面现象。方言的真正价值在于它携带了完整的类型系统和语义约束:tf 方言中的 tensor 类型和 llvm 方言中的 i32 类型遵循不同规则,方言决定了操作能否在某个上下文中合法出现。就像“浇筑”这个词,在建筑队和蛋糕店的含义完全不同,本质区别不在于词本身,而在于背后整套行业规范。

为何常见:字符串前缀是最直观的可见特征,初学者容易从表面的点号表示以偏概全。

误区以为降级就像查英汉字典,一个单词对应一个翻译,照着替换就行。

正解一个高层操作往往需要拆分为多条低层操作,并伴随类型转换、内存分配、控制流重构等复杂逻辑。比如 matmul 降级到 LLVM 方言,不仅要拆成循环和乘加指令,还要处理数据布局、对齐方式等问题。这更像把“举办一场宴会”翻译成“订场地→买菜谱→雇厨师→排座位”的完整执行方案。

为何常见:看到 Pattern 的“匹配→替换”模型后,误以为这就是正则表达式式的字符串替换,忽略了编译器转换中隐含的语义分析和资源分配。

面试题

Q1. MLIR 中 Dialect 的核心组成是什么?

单选题 · easy

  • A.操作、类型、属性← 正确答案
  • B.指令、寄存器、地址
  • C.函数、变量、常量
  • D.模式、规则、流水线

答案:A。方言(Dialect)在 MLIR 中是封装一组相关操作(Op)、类型(Type)与属性(Attr)的可组合模块,三者像“动词、名词、副词”一样配套使用。B 属于硬件层面的概念,C 属于通用编程语言概念,D 是 Lowering 相关机制而非方言组成。

Q2. 下列哪一项是 MLIR 生态中的真实方言?

单选题 · easy

  • A.LLVM 方言← 正确答案
  • B.Python 语法树
  • C.CUDA 核心库
  • D.Git 版本仓库

答案:A。LLVM 方言封装了 LLVM IR 的语义,是 MLIR 常用的底层方言。Python 语法树是解释器概念,CUDA 核心库是 GPU 运行时库,Git 仓库是版本管理工具,它们都与 MLIR 方言无直接关系。

Q3. Lowering 在 MLIR 中的核心作用是什么?

单选题 · easy

  • A.将高层方言操作转换为低层方言操作← 正确答案
  • B.将源代码直接编译成二进制文件
  • C.把抽象层次从低层提升到高层
  • D.将所有方言强行合并为一种方言

答案:A。Lowering 的本质是“降级翻译”:把抽象程度高的方言操作改写为抽象程度低、更接近硬件的操作序列。它不是源码到二进制的直接编译,也不是提升抽象层次或合并方言。

Q4. 关于高层方言与底层方言,以下描述准确的是?

单选题 · mid

  • A.高层方言更接近硬件,底层方言更抽象
  • B.高层方言抽象度高,底层方言接近硬件← 正确答案
  • C.方言之间没有任何层次关系
  • D.所有方言都必须放在同一个 IR 文件中

答案:B。高层方言描述“计算意图”(如 tf 方言),抽象度高;底层方言描述“机器指令”(如 llvm 方言),贴近硬件。A 说反了,C 忽略了方言的层次性,D 并不要求所有方言必须同文件。

Q5. 在 MLIR 中,一个操作(Operation)最重要的特征是?

单选题 · mid

  • A.它代表一次计算,且从属于某个方言← 正确答案
  • B.它只表示硬件处理器上的原始指令
  • C.它与方言无关,是通用计算节点
  • D.它只是属性集合中的一项配置

答案:A。MLIR 的操作是计算的基本单元,每个 Operation 必然属于某个方言,由方言定义其语义和合法性约束。操作并非仅指硬件指令,也不脱离方言存在,更不是属性的一部分。

Q6. 关于方言中的“类型(Type)”,以下说法正确的是?

单选题 · mid

  • A.描述数据的形状与性质,如张量、标量← 正确答案
  • B.只能表示硬件寄存器的位宽
  • C.类型全部由 LLVM 方言统一提供
  • D.类型与操作无关,可任意混用

答案:A。类型系统描述数据的结构特征(张量、标量、索引等),是方言中“名词”层面的核心概念。类型不只是位宽,不限于 LLVM 方言,而且每种方言会定义自己合法的类型组合。

Q7. 以下哪一項最佳描述了 MLIR 方言的可扩展性?

单选题 · hard

  • A.通过注册新方言来扩展操作、类型和属性← 正确答案
  • B.每新增需求都需要修改 MLIR 框架核心
  • C.通过宏预处理可以在编译期生成任意新语法
  • D.只能使用官方预置的内置方言集合

答案:A。MLIR 的核心设计就是“可插拔方言”:任何人可创建并注册新方言,从而扩展操作、类型与属性。这种模块化避免了修改 MLIR 核心,比宏预处理更工程化,也不限制在官方方言集合中。

Q8. 以下哪些方言属于 MLIR 生态中的“中层优化 / 底层对接”方言?

多选题 · mid

  • A.TOSA 中间方言← 正确答案
  • B.LLVM 底层方言← 正确答案
  • C.TF 高层图方言
  • D.前端数据流方言

答案:AB。TOSA 方言提供跨平台张量运算的公共中间表示,LLVM 方言直接对接 LLVM 后端,属于中低层。TF 方言和前端数据流方言用于接收模型、描述高层计算意图,属于高层方言。

Q9. 关于 Lowering 转换链,正确的说法有哪些?

多选题 · hard

  • A.转换链由多个 Pattern 按序组合而成← 正确答案
  • B.一次 Lowering 通常涉及模式匹配与类型适配← 正确答案
  • C.转换链只能执行一次且不可复用
  • D.转换链可跨多个 IR 层次逐步下降← 正确答案

答案:ABD。转换链由多个转换模式串联形成,每个模式负责一小步“翻译”,过程中需要处理类型系统适配;而且整个链条可以跨多个层次复用,并不只是一次性操作。C 说是“只能执行一次”明显错误。

Q10. 以下哪些正确描述了 MLIR 方言体系的核心设计思想?

多选题 · extreme

  • A.每个方言独立管理自身的操作、类型和属性← 正确答案
  • B.方言间可通过统一基础设施实现互操作← 正确答案
  • C.所有方言必须依次顺序下降才能工作
  • D.模块化架构允许第三方自定义方言← 正确答案

答案:ABD。方言体系倡导“自治 + 互操作 + 模块化”:每个方言独立管理自己的词汇表,通过共享的基础设施自由连接,第三方也能按需注册新方言。C 的“必须顺序下降”是典型的误解,不同操作可以停留在适合的方言层次,无需全部穿完整个链条。