flowchart TD
subgraph Dialect["MLIRContext (方言生态)"]
Builtin["builtin (module, func)"]
Arith["arith (addf, muli, cmpf)"]
Linalg["linalg (matmul, generic)"]
MemRef["memref (alloc, load, store)"]
LLVM["llvm (ptr, getelementptr)"]
end
Linalg -.->|抽象降级| MemRef
MemRef -.->|指针展开| LLVM
Arith -.->|指令映射| LLVM
第 2 课:Dialect、Type、Attribute 与接口
参考答案版:包含完整代码实现与问答题深度参考解析。建议先独立完成练习题后再展开核对。
本课目标与完成标准
学完后你应能:区分 type 与 attribute,理解 dialect 隔离命名和 interface 带来的跨 dialect 泛化。
通过必须同时满足:
- 独立补齐本课唯一的代码填空题,并通过给定检查;
- 三个问答题均说明因果链,而不是只报术语;
- 能指出至少一个正确性边界和一个性能取舍;
- 总分不低于 8/10,且没有一票否决级概念错误。
前置关系
- 课程前置:编译原理基础、C++ 阅读能力
- 本课在路线中的作用:Dialect 是可组合 IR 词汇表;Type 描述 SSA 值的类别,Attribute 是编译期常量/配置,Interface 提供不依赖具体 op 名的能力契约。
核心心智模型
1. 它是什么,解决什么问题
在传统的单一中间表示(如 GCC GIMPLE 或 LLVM IR)中,所有的优化分析都被迫适配在同一套封闭固化的指令集和类型系统上,导致上层领域特定(Domain-specific)语义在过早降级中被永久抹杀。 MLIR 提出了模块化、可无限扩展的Dialect(方言)生态系统。每个 Dialect 是一组语义自洽、命名隔离的 IR 词汇表(如 arith 负责算术、linalg 负责线性代数、memref 负责显式物理内存、llvm 负责底层硬件映射)。 在 Dialect 内部,四大核心正交正规化概念支撑起整个语义大厦:
- Type(类型):描述 SSA 变量的物理/抽象取值范围与数学类别(如
tensor<2x?xf32>,f32,i64)。 - Attribute(属性):挂载在 Operation 上的编译期已知静态常量或元数据配置(如张量维度的静态排布、浮点常量数值、轴向索引
{axis = 1 : i64})。 - Interface(接口):跨 Dialect 的通用行为契约(如
MemoryEffectOpInterface描述副作用、InferTypeOpInterface描述形状推导)。 本课解决的核心工程问题是:深刻辨析 Type 与 Attribute 的物理分界,理解 Dialect 命名隔离机制,以及如何利用 Interface 实现跨方言的解耦多态分析与优化。
2. 它如何工作
多方言协同与 OpInterface 动态多态分发流程如图 2-1 与图 2-2 所示:
图 2-1:MLIR 多方言协同拓扑与类型系统共存体系
flowchart LR
Op["Operation 实例"] --> Query{"查询接口 dyn_cast<OpInterface>"}
Query -->|命中| CallMethod["分发到特定方言 Trait / C++ 虚表实现"]
Query -->|未实现| Fallback["返回 nullptr / 执行通用保底路径"]
CallMethod --> Result["返回副作用分析 / 形状推导 / 内存别名结果"]
图 2-2:MLIR OpInterface 动态查询与多态解耦分发执行流程
方言前缀路由与语法解析: MLIR 源码以
dialect.op(如arith.constant、linalg.matmul)为唯一标识。解析器AsmParser依据方言前缀路由到对应的 C++ 方言定义,构建 Operation 节点。Type 与 Attribute 的正交约束:
- SSA 值(Value)严格绑定单一 Type,运行时数据在 SSA 管道中流转;
- 属性(Attribute)直接嵌入 Operation 的存储字典中,无需任何 SSA 依赖。
- 例如语句
%c = arith.constant dense<[1.0, 2.0]> : tensor<2xf32>中,dense<[1.0, 2.0]>是一个属性实例(DenseElementsAttr),作为常量的字面值载荷;而tensor<2xf32>是 Type,约束%c的静态形状与元素类型。
接口动态查询与多态分发(Interface Dispatch): 如图 2-2 所示,优化 Pass 在遍历 IR 时,绝不需要使用硬编码的
if (op->getName() == "xxx")。 Pass 通过 C++ RTTI 模式进行概念建模:if (auto memOp = dyn_cast<MemoryEffectOpInterface>(op))。 若该 Operation 实现了该接口,则直接调用接口方法查询其是否具有内存读写副作用,从而将 DCE(死代码消除)或 CSE(公共子表达式消除)算法与具体 Dialect 的具体 Op 完全解耦。
3. 正确性条件与常见误区
运行时数据与编译期属性的混淆(Attribute vs SSA Value): Attribute 的值必须在编译期是完全已知且不可变的字面常量。若张量的维度或步长是由输入数据的运行时计算决定的(例如根据网络包大小动态决定的
batch_size),该数值必须以 SSA Value(index或i64类型操作数)传递,绝对不能试图塞入编译期的 Attribute 中。动态维度标记(Dynamic Dimension
?): 在静态类型中,未知维度用?表示(如tensor<?x128xf32>)。动态维度的真实尺寸必须在运行时通过tensor.dim等操作动态获取为 SSA 值。未注册方言与语义空洞(Unregistered Dialect Trap): 若在
mlir-opt中开启-allow-unregistered-dialect,编译器允许解析任何任意前缀的未知 Op(如foo.bar)。但此时该 Op 不具备任何 Verifier 校验规则、无法执行 Interface 查询,只能作为黑盒语法树节点存在。
4. 性能与工程取舍
高层抽象类型保留 vs 过早降级(Lowering Timing): 保留高层类型(如
tensor<4x16xf32>)能够完整携带张量的高阶数学代数语义,使得切片融合、转置消除、分块 Tiling 等高层图优化极易进行; 若过早将tensor降级为带物理步长的memref或底层llvm.ptr指针,指针别名(Aliasing)与扁平物理跨步会使编译器无法再恢复高维几何形状,造成严重的优化阻断。Interface 虚表分发开销: Interface 底层通过类似 C++ Concept 或轻量级虚表分发实现。虽然有极轻微的寻址开销,但相比于硬编码匹配,它换来了整个编译器架构极高的可维护性与正交扩展性。
具体演示
以 %c = arith.constant dense<[1.0, 2.0]> : tensor<2xf32> 为例:
arith:方言命名空间,避免与其它自定义方言的constant命名冲突。dense<[1.0, 2.0]>:DenseElementsAttr 属性,在编译器内存中以紧凑字节数组高效存储常量数据。tensor<2xf32>:RankedTensorType,静态声明该张量阶数为 1,长度为 2,元素为 32 位浮点数。- 若将返回值错误声明为
tensor<3xf32>,MLIR Verifier 在构建期会立即捕获类型与属性形状不匹配的断言,从编译前端杜绝非法张量。
请在阅读后先合上这一节,用自己的语言复述“输入状态 → 中间状态 → 输出状态”,再做练习。
实践任务:唯一代码填空题
补齐 tensor 常量的类型和 dense attribute。
规则:只能修改 TODO/______ 所在位置;不要删除断言或放宽误差。代码注释说明了每个边界条件。
Code
%%writefile /tmp/lesson02.mlir
module {
func.func @constant() -> tensor<2xf32> {
%c = arith.constant ______ : ______
func.return %c : tensor<2xf32>
}
}检查方法
用 mlir-opt 解析;再把返回类型故意改为 tensor<3xf32>,确认 verifier 拒绝。
提交时请给出:补齐后的代码、实际运行输出(环境不可用时注明“仅静态审查”)以及对失败用例的解释。
Q1
不要背定义:请从输入、状态变化和输出三个阶段解释“Dialect、Type、Attribute 与接口”的工作机制。
你的答案:
Q2
为什么动态 shape 的实际数值不能只写成 attribute?
你的答案:
Q3
设计新 dialect 时,何时复用 builtin type,何时创建自定义 type?
你的答案:
评分与通过规则
- 代码 4 分:正常输入 2 分,边界输入 1 分,解释实现 1 分;
- Q1~Q3 各 2 分;
- 一票否决:结果碰巧正确但核心因果链错误、删除边界检查、把未运行结果说成实测。
需要提示时按四级机制请求:概念区域 → 具体方向 → 关键局部 → 完整答案。